stabile version

This commit is contained in:
2026-04-11 11:04:48 +02:00
parent d1425f4f7d
commit 2804c3392e
9 changed files with 318 additions and 161 deletions

View File

@@ -8,8 +8,8 @@ server {
} }
location /api { location /api {
proxy_pass https://restapi.testsite.deinedorfapp.de; proxy_pass http://backend:5173;
proxy_set_header Host restapi.testsite.deinedorfapp.de; proxy_set_header Host backend;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
} }
} }

View File

@@ -10,6 +10,7 @@ import PostsPage from './pages/PostsPage';
import CreateNewsPage from './pages/CreateNewsPage'; import CreateNewsPage from './pages/CreateNewsPage';
import OrganizationPage from './pages/OrganizationPage'; import OrganizationPage from './pages/OrganizationPage';
import PushPage from './pages/PushPage'; import PushPage from './pages/PushPage';
import HomePage from './pages/HomePage';
import EditOrganizationPage from "./pages/EditOrganizationPage.jsx"; import EditOrganizationPage from "./pages/EditOrganizationPage.jsx";
const theme = createTheme({ const theme = createTheme({
@@ -53,7 +54,7 @@ export default function App() {
</PrivateRoute> </PrivateRoute>
} }
> >
<Route index element={<Navigate to="/users" replace />} /> <Route index element={<HomePage />} />
<Route <Route
path="users" path="users"
element={ element={

View File

@@ -1,9 +1,16 @@
import axios from 'axios'; import axios from 'axios';
/*
const axiosInstance = axios.create({ const axiosInstance = axios.create({
baseURL: '/api', baseURL: '/api',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}); });
*/
const axiosInstance = axios.create({
baseURL: 'https://api.testsite.deinedorfapp.de/api',
//baseURL: 'http://localhost:5173/api',
headers: { 'Content-Type': 'application/json' },
});
axiosInstance.interceptors.request.use((config) => { axiosInstance.interceptors.request.use((config) => {
const token = localStorage.getItem('jwt_token'); const token = localStorage.getItem('jwt_token');

View File

@@ -1,4 +1,3 @@
import { useState } from 'react';
import { useNavigate, useLocation, Outlet } from 'react-router-dom'; import { useNavigate, useLocation, Outlet } from 'react-router-dom';
import { import {
Box, Drawer, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Box, Drawer, List, ListItem, ListItemButton, ListItemIcon, ListItemText,
@@ -10,91 +9,109 @@ import NotificationsActiveIcon from '@mui/icons-material/NotificationsActive';
import CorporateFareIcon from '@mui/icons-material/CorporateFare'; import CorporateFareIcon from '@mui/icons-material/CorporateFare';
import LogoutIcon from '@mui/icons-material/Logout'; import LogoutIcon from '@mui/icons-material/Logout';
import DashboardIcon from '@mui/icons-material/Dashboard'; import DashboardIcon from '@mui/icons-material/Dashboard';
import HomeIcon from '@mui/icons-material/Home';
import { useAuth } from '../context/AuthContext'; import { useAuth } from '../context/AuthContext';
const DRAWER_WIDTH = 240; const DRAWER_WIDTH = 240;
const navItems = [ const navItems = [
{ label: 'Users', path: '/users', icon: <PeopleIcon />, roles: ['ADMIN'] }, { label: 'Home', path: '/', icon: <HomeIcon />, roles: null },
{ label: 'Posts', path: '/posts', icon: <ArticleIcon />, roles: ['ADMIN', 'REPORTER'] }, { label: 'Users', path: '/users', icon: <PeopleIcon />, roles: ['ADMIN'] },
{ label: 'Push', path: '/push', icon: <NotificationsActiveIcon />, roles: ['ADMIN'] }, { label: 'Posts', path: '/posts', icon: <ArticleIcon />, roles: ['ADMIN', 'REPORTER'] },
{ label: 'Organization', path: '/organizations', icon: <CorporateFareIcon />, roles: ['ADMIN', 'REPORTER'] }, { label: 'Push', path: '/push', icon: <NotificationsActiveIcon />, roles: ['ADMIN'] },
{ label: 'Organization', path: '/organizations', icon: <CorporateFareIcon />, roles: ['ADMIN', 'SITE_OWNER'] },
]; ];
function getActiveLabel(pathname) {
// Exakter Match für "/" zuerst prüfen, dann startsWith für den Rest
const exact = navItems.find((i) => i.path === pathname);
if (exact) return exact.label;
const partial = navItems.find((i) => i.path !== '/' && pathname.startsWith(i.path));
return partial?.label ?? 'Admin';
}
export default function Layout() { export default function Layout() {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const { logout } = useAuth(); const { logout, role } = useAuth();
const visibleNavItems = navItems.filter(
(item) => !item.roles || item.roles.includes(role)
);
return ( return (
<Box sx={{ display: 'flex' }}> <Box sx={{ display: 'flex' }}>
{/* Sidebar */} {/* Sidebar */}
<Drawer <Drawer
variant="permanent" variant="permanent"
sx={{ sx={{
width: DRAWER_WIDTH, width: DRAWER_WIDTH,
flexShrink: 0, flexShrink: 0,
'& .MuiDrawer-paper': { '& .MuiDrawer-paper': {
width: DRAWER_WIDTH, width: DRAWER_WIDTH,
boxSizing: 'border-box', boxSizing: 'border-box',
borderRight: '1px solid', borderRight: '1px solid',
borderColor: 'divider', borderColor: 'divider',
}, },
}} }}
> >
<Toolbar sx={{ px: 2 }}> <Toolbar sx={{ px: 2 }}>
<DashboardIcon sx={{ mr: 1, color: 'primary.main' }} /> <DashboardIcon sx={{ mr: 1, color: 'primary.main' }} />
<Typography variant="h6" fontWeight={600} color="primary.main"> <Typography variant="h6" fontWeight={600} color="primary.main">
Admin Panel Admin Panel
</Typography> </Typography>
</Toolbar> </Toolbar>
<Divider /> <Divider />
<List sx={{ px: 1, pt: 1 }}> <List sx={{ px: 1, pt: 1 }}>
{navItems.map((item) => ( {visibleNavItems.map((item) => (
<ListItem key={item.path} disablePadding sx={{ mb: 0.5 }}> <ListItem key={item.path} disablePadding sx={{ mb: 0.5 }}>
<ListItemButton <ListItemButton
selected={location.pathname.startsWith(item.path)} selected={
onClick={() => navigate(item.path)} item.path === '/'
sx={{ borderRadius: 2 }} ? location.pathname === '/'
> : location.pathname.startsWith(item.path)
<ListItemIcon sx={{ minWidth: 36 }}>{item.icon}</ListItemIcon> }
<ListItemText primary={item.label} /> onClick={() => navigate(item.path)}
sx={{ borderRadius: 2 }}
>
<ListItemIcon sx={{ minWidth: 36 }}>{item.icon}</ListItemIcon>
<ListItemText primary={item.label} />
</ListItemButton>
</ListItem>
))}
</List>
<Box sx={{ flexGrow: 1 }} />
<Divider />
<List sx={{ px: 1, py: 1 }}>
<ListItem disablePadding>
<ListItemButton onClick={logout} sx={{ borderRadius: 2 }}>
<ListItemIcon sx={{ minWidth: 36 }}><LogoutIcon /></ListItemIcon>
<ListItemText primary="Logout" />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
))} </List>
</List> </Drawer>
<Box sx={{ flexGrow: 1 }} />
<Divider />
<List sx={{ px: 1, py: 1 }}>
<ListItem disablePadding>
<ListItemButton onClick={logout} sx={{ borderRadius: 2 }}>
<ListItemIcon sx={{ minWidth: 36 }}><LogoutIcon /></ListItemIcon>
<ListItemText primary="Logout" />
</ListItemButton>
</ListItem>
</List>
</Drawer>
{/* Main content */} {/* Main content */}
<Box component="main" sx={{ flexGrow: 1, minHeight: '100vh', bgcolor: 'grey.50' }}> <Box component="main" sx={{ flexGrow: 1, minHeight: '100vh', bgcolor: 'grey.50' }}>
<AppBar <AppBar
position="static" position="static"
elevation={0} elevation={0}
sx={{ bgcolor: 'white', borderBottom: '1px solid', borderColor: 'divider' }} sx={{ bgcolor: 'white', borderBottom: '1px solid', borderColor: 'divider' }}
> >
<Toolbar> <Toolbar>
<Typography variant="h6" color="text.primary" sx={{ flexGrow: 1 }}> <Typography variant="h6" color="text.primary" sx={{ flexGrow: 1 }}>
{navItems.find((i) => location.pathname.startsWith(i.path))?.label ?? 'Admin'} {getActiveLabel(location.pathname)}
</Typography> </Typography>
<Tooltip title="Account"> <Tooltip title="Account">
<Avatar sx={{ width: 32, height: 32, bgcolor: 'primary.main', fontSize: 14 }}>A</Avatar> <Avatar sx={{ width: 32, height: 32, bgcolor: 'primary.main', fontSize: 14 }}>A</Avatar>
</Tooltip> </Tooltip>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<Box sx={{ p: 3 }}> <Box sx={{ p: 3 }}>
<Outlet /> <Outlet />
</Box>
</Box> </Box>
</Box> </Box>
</Box>
); );
} }

View File

@@ -30,28 +30,35 @@ const EMPTY_FORM = {
active: false, active: false,
} }
export default function EditOrganizationPage() { export default function EditOrganizationPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const [form, setForm] = useState(EMPTY_FORM); const [form, setForm] = useState(EMPTY_FORM);
const [files, setFiles] = useState([]); const [name, setName] = useState('');
const [previews, setPreviews] = useState([]);
const [existingImages, setExistingImages] = useState([]);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
const { id } = useParams(); const { id } = useParams();
const [data, setData] = useState([]);
// Titelbild
const [titleImageUrl, setTitleImageUrl] = useState(null);
const [titleImageFile, setTitleImageFile] = useState(null);
const [titleImagePreview, setTitleImagePreview] = useState(null);
// Weitere Bilder
const [otherImageUrls, setOtherImageUrls] = useState([]);
const [otherFiles, setOtherFiles] = useState([]);
const [otherPreviews, setOtherPreviews] = useState([]);
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
try { try {
const response = await axiosInstance.get('/organizations/' + id); const response = await axiosInstance.get('/organization/' + id);
const data = response.data; const data = response.data;
setData(data); setName(data.name || '');
setForm({ setForm({
description: data.description || '', description: data.description || '',
phone: data.phone || '', phone: data.number || '',
email: data.email || '', email: data.email || '',
address: data.address || '', address: data.address || '',
website: data.website || '', website: data.website || '',
@@ -59,7 +66,9 @@ export default function EditOrganizationPage() {
active: data.active || false, active: data.active || false,
}); });
setExistingImages(data.images || []); const imgs = data.pictures || [];
setTitleImageUrl(imgs[0] || null);
setOtherImageUrls(imgs.slice(1));
} catch (err) { } catch (err) {
setError(err.response?.data || 'Fehler beim Laden'); setError(err.response?.data || 'Fehler beim Laden');
} }
@@ -75,31 +84,74 @@ export default function EditOrganizationPage() {
})); }));
}; };
const handleRemoveExisting = (index) => { // Titelbild-Handler
setExistingImages((prev) => prev.filter((_, i) => i !== index)); const handleTitleImageChange = (e) => {
}; const f = e.target.files[0];
if (!f) return;
const handleSubmit = (e) => { if (titleImagePreview) URL.revokeObjectURL(titleImagePreview);
/* setTitleImageFile(f);
Drauf achten alte und neue Bilder zu verwenden setTitleImagePreview(URL.createObjectURL(f));
Vielleicht extra Feld für Anzeigebild, anstatt erstes zu nehmen
*/
}
const handleFileChange = (e) => {
const selected = Array.from(e.target.files);
setFiles((prev) => [...prev, ...selected]);
const newPreviews = selected.map((f) => URL.createObjectURL(f));
setPreviews((prev) => [...prev, ...newPreviews]);
e.target.value = ''; e.target.value = '';
}; };
const handleRemoveFile = (index) => { const handleRemoveTitleImage = () => {
URL.revokeObjectURL(previews[index]); if (titleImagePreview) URL.revokeObjectURL(titleImagePreview);
setFiles((prev) => prev.filter((_, i) => i !== index)); setTitleImageUrl(null);
setPreviews((prev) => prev.filter((_, i) => i !== index)); setTitleImageFile(null);
setTitleImagePreview(null);
}; };
// Weitere Bilder-Handler
const handleOtherFilesChange = (e) => {
const selected = Array.from(e.target.files);
setOtherFiles((prev) => [...prev, ...selected]);
const newPreviews = selected.map((f) => URL.createObjectURL(f));
setOtherPreviews((prev) => [...prev, ...newPreviews]);
e.target.value = '';
};
const handleRemoveOtherFile = (index) => {
URL.revokeObjectURL(otherPreviews[index]);
setOtherFiles((prev) => prev.filter((_, i) => i !== index));
setOtherPreviews((prev) => prev.filter((_, i) => i !== index));
};
const handleRemoveOtherUrl = (index) => {
setOtherImageUrls((prev) => prev.filter((_, i) => i !== index));
};
const handleSubmit = async (e) => {
e.preventDefault();
setSaving(true);
setError('');
try {
const formData = new FormData();
formData.append(
'data',
new Blob([JSON.stringify({
...form,
existingTitleImage: titleImageUrl || null,
existingOtherImages: otherImageUrls,
})], { type: 'application/json' })
);
if (titleImageFile) formData.append('images', titleImageFile);
otherFiles.forEach((f) => formData.append('images', f));
await axiosInstance.put(`/organization/${id}`, formData, {
headers: { 'Content-Type': undefined }
});
navigate('/organizations');
} catch (err) {
setError(err.response?.data || 'Fehler beim Speichern');
setSaving(false);
}
};
const hasTitleImage = titleImageUrl || titleImageFile;
return ( return (
<Box> <Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
@@ -110,17 +162,18 @@ export default function EditOrganizationPage() {
</Box> </Box>
<Card sx={{ maxWidth: 680 }}> <Card sx={{ maxWidth: 680 }}>
<CardContent sx={{ p: 3}}> <CardContent sx={{ p: 3 }}>
<Box component="form" onSubmit={handleSubmit}> <Box component="form" onSubmit={handleSubmit}>
<Stack spacing={3}> <Stack spacing={3}>
{error && <Alert severity="error">{error}</Alert>} {error && <Alert severity="error">{error}</Alert>}
<TextField label="Beschreibung" name="description" value={form.description} onChange={handleChange} required fullWidth autoFocus multiline rows={6}/> <TextField label="Name" value={name} fullWidth InputProps={{ readOnly: true }} helperText="Der Name kann nicht geändert werden." />
<TextField label="Telefonnummber" name="phone" value={form.phone} onChange={handleChange} required fullWidth/> <TextField label="Beschreibung" name="description" value={form.description} onChange={handleChange} required fullWidth autoFocus multiline rows={6} />
<TextField label="Email Adresse" name="email" value={form.email} onChange={handleChange} required fullWidth/> <TextField label="Telefonnummer" name="phone" value={form.phone} onChange={handleChange} required fullWidth />
<TextField label="Adresse" name="address" value={form.address} onChange={handleChange} required fullWidth/> <TextField label="Email Adresse" name="email" value={form.email} onChange={handleChange} required fullWidth />
<TextField label="Webseite" name="website" value={form.website} onChange={handleChange} required fullWidth/> <TextField label="Adresse" name="address" value={form.address} onChange={handleChange} required fullWidth />
<TextField label="Kontaktperson" name="contactPerson" value={form.contactPerson} onChange={handleChange} required fullWidth/> <TextField label="Webseite" name="website" value={form.website} onChange={handleChange} required fullWidth />
<TextField label="Kontaktperson" name="contactPerson" value={form.contactPerson} onChange={handleChange} required fullWidth />
<FormControlLabel <FormControlLabel
control={ control={
<Checkbox <Checkbox
@@ -136,60 +189,125 @@ export default function EditOrganizationPage() {
<Divider /> <Divider />
{/* Bild-Upload */} {/* ── Titelbild ── */}
<Box> <Box>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1.5 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1.5 }}>
<Typography variant="subtitle2" fontWeight={500}>Bilder</Typography> <Chip
{files.length > 0 && ( icon={<StarIcon sx={{ fontSize: '13px !important' }} />}
<Typography variant="caption" color="text.secondary"> label="Titelbild"
Das erste Bild wird als Vorschaubild verwendet size="small"
</Typography> color="primary"
)} sx={{ height: 22, fontSize: 11 }}
{existingImages.map((src, i) => ( />
<Box key={`existing-${i}`}> <Typography variant="caption" color="text.secondary">
<img src={src} width={64} height={64} /> Wird als Vorschaubild der Organisation verwendet
<IconButton onClick={() => handleRemoveExisting(i)}> </Typography>
<DeleteOutlineIcon />
</IconButton>
</Box>
))}
</Box> </Box>
<Stack spacing={1.5}> {hasTitleImage ? (
{previews.map((src, i) => ( <Box
sx={{
display: 'flex', alignItems: 'center', gap: 1.5,
p: 1, borderRadius: 1,
border: '1px solid',
borderColor: 'primary.main',
bgcolor: 'primary.50',
}}
>
<Box <Box
key={i} component="img"
src={titleImagePreview || titleImageUrl}
alt="Titelbild"
sx={{ width: 72, height: 72, objectFit: 'cover', borderRadius: 1, flexShrink: 0 }}
/>
<Box sx={{ flex: 1, minWidth: 0 }}>
<Typography variant="body2" color="text.secondary" sx={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{titleImageFile ? titleImageFile.name : titleImageUrl}
</Typography>
{/* Titelbild tauschen ohne zu löschen */}
<Button
component="label"
size="small"
sx={{ mt: 0.5, px: 0, minWidth: 0, fontSize: 12 }}
>
Bild ersetzen
<input type="file" hidden accept="image/*" onChange={handleTitleImageChange} />
</Button>
</Box>
<IconButton size="small" color="error" onClick={handleRemoveTitleImage}>
<DeleteOutlineIcon fontSize="small" />
</IconButton>
</Box>
) : (
<Button
component="label"
startIcon={<AddPhotoAlternateOutlinedIcon />}
size="small"
variant="outlined"
sx={{ alignSelf: 'flex-start' }}
>
Titelbild hochladen
<input type="file" hidden accept="image/*" onChange={handleTitleImageChange} />
</Button>
)}
</Box>
<Divider />
{/* ── Weitere Bilder ── */}
<Box>
<Typography variant="subtitle2" fontWeight={500} mb={1.5}>
Weitere Bilder
</Typography>
<Stack spacing={1.5}>
{/* Bestehende weitere Bilder */}
{otherImageUrls.map((src, i) => (
<Box
key={`existing-${i}`}
sx={{ sx={{
display: 'flex', alignItems: 'center', gap: 1.5, display: 'flex', alignItems: 'center', gap: 1.5,
p: 1, borderRadius: 1, p: 1, borderRadius: 1,
border: '1px solid', border: '1px solid',
borderColor: i === 0 ? 'primary.main' : 'divider', borderColor: 'divider',
bgcolor: i === 0 ? 'primary.50' : 'transparent',
}} }}
> >
<Box <Box
component="img" component="img"
src={src} src={src}
alt={`Vorschau ${i + 1}`} alt={`Bild ${i + 1}`}
sx={{ width: 64, height: 64, objectFit: 'cover', borderRadius: 1, flexShrink: 0 }} sx={{ width: 64, height: 64, objectFit: 'cover', borderRadius: 1, flexShrink: 0 }}
/> />
<Box sx={{ flex: 1, minWidth: 0 }}> <Typography variant="body2" color="text.secondary" sx={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}> {src}
{i === 0 && ( </Typography>
<Chip <IconButton size="small" color="error" onClick={() => handleRemoveOtherUrl(i)}>
icon={<StarIcon sx={{ fontSize: '13px !important' }} />} <DeleteOutlineIcon fontSize="small" />
label="Vorschaubild" </IconButton>
size="small" </Box>
color="primary" ))}
sx={{ height: 20, fontSize: 11 }}
/> {/* Neu hinzugefügte weitere Bilder */}
)} {otherPreviews.map((src, i) => (
</Box> <Box
<Typography variant="body2" color="text.secondary" sx={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> key={`new-${i}`}
{files[i]?.name} sx={{
</Typography> display: 'flex', alignItems: 'center', gap: 1.5,
</Box> p: 1, borderRadius: 1,
<IconButton size="small" color="error" onClick={() => handleRemoveFile(i)}> border: '1px solid',
borderColor: 'divider',
}}
>
<Box
component="img"
src={src}
alt={`Neues Bild ${i + 1}`}
sx={{ width: 64, height: 64, objectFit: 'cover', borderRadius: 1, flexShrink: 0 }}
/>
<Typography variant="body2" color="text.secondary" sx={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{otherFiles[i]?.name}
</Typography>
<IconButton size="small" color="error" onClick={() => handleRemoveOtherFile(i)}>
<DeleteOutlineIcon fontSize="small" /> <DeleteOutlineIcon fontSize="small" />
</IconButton> </IconButton>
</Box> </Box>
@@ -201,8 +319,8 @@ export default function EditOrganizationPage() {
size="small" size="small"
sx={{ alignSelf: 'flex-start' }} sx={{ alignSelf: 'flex-start' }}
> >
{files.length === 0 ? 'Vorschaubild hinzufügen' : 'Weiteres Bild hinzufügen'} Bild hinzufügen
<input type="file" hidden accept="image/*" multiple onChange={handleFileChange} /> <input type="file" hidden accept="image/*" multiple onChange={handleOtherFilesChange} />
</Button> </Button>
</Stack> </Stack>
</Box> </Box>
@@ -212,16 +330,16 @@ export default function EditOrganizationPage() {
<Button <Button
type="submit" type="submit"
variant="contained" variant="contained"
disabled={saving}
startIcon={saving ? <CircularProgress size={16} color="inherit" /> : null} startIcon={saving ? <CircularProgress size={16} color="inherit" /> : null}
> >
Speichern Speichern
</Button> </Button>
</Box> </Box>
</Stack> </Stack>
</Box> </Box>
</CardContent> </CardContent>
</Card> </Card>
</Box> </Box>
) );
} }

14
src/pages/HomePage.jsx Normal file
View File

@@ -0,0 +1,14 @@
import { Box, Typography } from '@mui/material';
export default function HomePage() {
return (
<Box>
<Typography variant="h5" fontWeight={600} gutterBottom>
Willkommen im Admin Panel
</Typography>
<Typography variant="body1" color="text.secondary">
Wähle links im Menü einen Bereich aus.
</Typography>
</Box>
);
}

View File

@@ -23,7 +23,7 @@ export default function LoginPage() {
setLoading(true); setLoading(true);
try { try {
await login(email, password); await login(email, password);
navigate('/users'); navigate('/');
} catch (err) { } catch (err) {
setError(err.response?.data?.message ?? 'Login fehlgeschlagen'); setError(err.response?.data?.message ?? 'Login fehlgeschlagen');
} finally { } finally {

View File

@@ -122,11 +122,11 @@ export default function OrganizationPage() {
width: 120, width: 120,
sortable: false, sortable: false,
renderCell: ({ row }) => ( renderCell: ({ row }) => (
<Tooltip title="Löschen"> <Tooltip title="">
<IconButton size="small" color="error" onClick={() => handleDelete(row.id)}> <IconButton title="löschen" size="small" color="error" onClick={() => handleDelete(row.id)}>
<DeleteOutlineIcon fontSize="small" /> <DeleteOutlineIcon fontSize="small" />
</IconButton> </IconButton>
<IconButton size="small" color="grey" onClick={() => handleEdit(row.id)}> <IconButton title="bearbeiten" size="small" color="grey" onClick={() => handleEdit(row.id)}>
<EditOutlinedIcon fontSize="small" /> <EditOutlinedIcon fontSize="small" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>

View File

@@ -60,7 +60,7 @@ export default function PostsPage() {
{ field: 'author', headerName: 'Autor', width: 130 }, { field: 'author', headerName: 'Autor', width: 130 },
{ field: 'category', headerName: 'Kategorie', width: 130 }, { field: 'category', headerName: 'Kategorie', width: 130 },
{ {
field: 'picture', field: 'pictures',
headerName: 'Bilder', headerName: 'Bilder',
width: 80, width: 80,
sortable: false, sortable: false,
@@ -140,11 +140,11 @@ export default function PostsPage() {
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
{Array.isArray(detailNews?.picture) && detailNews.picture.length > 0 && ( {Array.isArray(detailNews?.pictures) && detailNews.pictures.length > 0 && (
<Box> <Box>
<Typography variant="caption" color="text.secondary">Bilder</Typography> <Typography variant="caption" color="text.secondary">Bilder</Typography>
<Stack spacing={1} sx={{ mt: 0.5 }}> <Stack spacing={1} sx={{ mt: 0.5 }}>
{detailNews.picture.map((url, i) => ( {detailNews.pictures.map((url, i) => (
<Box <Box
key={i} key={i}
component="img" component="img"