diff --git a/nginx.conf b/nginx.conf index 9021112..a256f76 100644 --- a/nginx.conf +++ b/nginx.conf @@ -8,8 +8,8 @@ server { } location /api { - proxy_pass https://restapi.testsite.deinedorfapp.de; - proxy_set_header Host restapi.testsite.deinedorfapp.de; + proxy_pass http://backend:5173; + proxy_set_header Host backend; proxy_set_header X-Real-IP $remote_addr; } } \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index 74c27e3..456282f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -10,6 +10,7 @@ import PostsPage from './pages/PostsPage'; import CreateNewsPage from './pages/CreateNewsPage'; import OrganizationPage from './pages/OrganizationPage'; import PushPage from './pages/PushPage'; +import HomePage from './pages/HomePage'; import EditOrganizationPage from "./pages/EditOrganizationPage.jsx"; const theme = createTheme({ @@ -53,7 +54,7 @@ export default function App() { } > - } /> + } /> { const token = localStorage.getItem('jwt_token'); diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx index 0fe82e3..d38d206 100644 --- a/src/components/Layout.jsx +++ b/src/components/Layout.jsx @@ -1,4 +1,3 @@ -import { useState } from 'react'; import { useNavigate, useLocation, Outlet } from 'react-router-dom'; import { 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 LogoutIcon from '@mui/icons-material/Logout'; import DashboardIcon from '@mui/icons-material/Dashboard'; +import HomeIcon from '@mui/icons-material/Home'; import { useAuth } from '../context/AuthContext'; const DRAWER_WIDTH = 240; const navItems = [ - { label: 'Users', path: '/users', icon: , roles: ['ADMIN'] }, - { label: 'Posts', path: '/posts', icon: , roles: ['ADMIN', 'REPORTER'] }, - { label: 'Push', path: '/push', icon: , roles: ['ADMIN'] }, - { label: 'Organization', path: '/organizations', icon: , roles: ['ADMIN', 'REPORTER'] }, + { label: 'Home', path: '/', icon: , roles: null }, + { label: 'Users', path: '/users', icon: , roles: ['ADMIN'] }, + { label: 'Posts', path: '/posts', icon: , roles: ['ADMIN', 'REPORTER'] }, + { label: 'Push', path: '/push', icon: , roles: ['ADMIN'] }, + { label: 'Organization', path: '/organizations', icon: , 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() { const navigate = useNavigate(); const location = useLocation(); - const { logout } = useAuth(); + const { logout, role } = useAuth(); + + const visibleNavItems = navItems.filter( + (item) => !item.roles || item.roles.includes(role) + ); return ( - - {/* Sidebar */} - - - - - Admin Panel - - - - - {navItems.map((item) => ( - - navigate(item.path)} - sx={{ borderRadius: 2 }} - > - {item.icon} - + + {/* Sidebar */} + + + + + Admin Panel + + + + + {visibleNavItems.map((item) => ( + + navigate(item.path)} + sx={{ borderRadius: 2 }} + > + {item.icon} + + + + ))} + + + + + + + + - ))} - - - - - - - - - - - - + + - {/* Main content */} - - - - - {navItems.find((i) => location.pathname.startsWith(i.path))?.label ?? 'Admin'} - - - A - - - - - + {/* Main content */} + + + + + {getActiveLabel(location.pathname)} + + + A + + + + + + - ); -} +} \ No newline at end of file diff --git a/src/pages/EditOrganizationPage.jsx b/src/pages/EditOrganizationPage.jsx index 967c576..c0e1b68 100644 --- a/src/pages/EditOrganizationPage.jsx +++ b/src/pages/EditOrganizationPage.jsx @@ -30,28 +30,35 @@ const EMPTY_FORM = { active: false, } + export default function EditOrganizationPage() { const navigate = useNavigate(); const [form, setForm] = useState(EMPTY_FORM); - const [files, setFiles] = useState([]); - const [previews, setPreviews] = useState([]); - const [existingImages, setExistingImages] = useState([]); + const [name, setName] = useState(''); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); 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(() => { const fetchData = async () => { try { - const response = await axiosInstance.get('/organizations/' + id); + const response = await axiosInstance.get('/organization/' + id); const data = response.data; - setData(data); - + setName(data.name || ''); setForm({ description: data.description || '', - phone: data.phone || '', + phone: data.number || '', email: data.email || '', address: data.address || '', website: data.website || '', @@ -59,7 +66,9 @@ export default function EditOrganizationPage() { active: data.active || false, }); - setExistingImages(data.images || []); + const imgs = data.pictures || []; + setTitleImageUrl(imgs[0] || null); + setOtherImageUrls(imgs.slice(1)); } catch (err) { setError(err.response?.data || 'Fehler beim Laden'); } @@ -75,31 +84,74 @@ export default function EditOrganizationPage() { })); }; - const handleRemoveExisting = (index) => { - setExistingImages((prev) => prev.filter((_, i) => i !== index)); - }; - - const handleSubmit = (e) => { - /* - Drauf achten alte und neue Bilder zu verwenden - 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]); + // Titelbild-Handler + const handleTitleImageChange = (e) => { + const f = e.target.files[0]; + if (!f) return; + if (titleImagePreview) URL.revokeObjectURL(titleImagePreview); + setTitleImageFile(f); + setTitleImagePreview(URL.createObjectURL(f)); e.target.value = ''; }; - const handleRemoveFile = (index) => { - URL.revokeObjectURL(previews[index]); - setFiles((prev) => prev.filter((_, i) => i !== index)); - setPreviews((prev) => prev.filter((_, i) => i !== index)); + const handleRemoveTitleImage = () => { + if (titleImagePreview) URL.revokeObjectURL(titleImagePreview); + setTitleImageUrl(null); + 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 ( @@ -110,17 +162,18 @@ export default function EditOrganizationPage() { - + {error && {error}} - - - - - - + + + + + + + - {/* Bild-Upload */} + {/* ── Titelbild ── */} - - Bilder - {files.length > 0 && ( - - Das erste Bild wird als Vorschaubild verwendet - - )} - {existingImages.map((src, i) => ( - - - handleRemoveExisting(i)}> - - - - ))} + + } + label="Titelbild" + size="small" + color="primary" + sx={{ height: 22, fontSize: 11 }} + /> + + Wird als Vorschaubild der Organisation verwendet + - - {previews.map((src, i) => ( + {hasTitleImage ? ( + + + + {titleImageFile ? titleImageFile.name : titleImageUrl} + + {/* Titelbild tauschen ohne zu löschen */} + + + + + + + ) : ( + + )} + + + + + {/* ── Weitere Bilder ── */} + + + Weitere Bilder + + + + {/* Bestehende weitere Bilder */} + {otherImageUrls.map((src, i) => ( + - - - {i === 0 && ( - } - label="Vorschaubild" - size="small" - color="primary" - sx={{ height: 20, fontSize: 11 }} - /> - )} - - - {files[i]?.name} - - - handleRemoveFile(i)}> + + {src} + + handleRemoveOtherUrl(i)}> + + + + ))} + + {/* Neu hinzugefügte weitere Bilder */} + {otherPreviews.map((src, i) => ( + + + + {otherFiles[i]?.name} + + handleRemoveOtherFile(i)}> @@ -201,8 +319,8 @@ export default function EditOrganizationPage() { size="small" sx={{ alignSelf: 'flex-start' }} > - {files.length === 0 ? 'Vorschaubild hinzufügen' : 'Weiteres Bild hinzufügen'} - + Bild hinzufügen + @@ -212,16 +330,16 @@ export default function EditOrganizationPage() { - - ) + ); } \ No newline at end of file diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx new file mode 100644 index 0000000..6a0080a --- /dev/null +++ b/src/pages/HomePage.jsx @@ -0,0 +1,14 @@ +import { Box, Typography } from '@mui/material'; + +export default function HomePage() { + return ( + + + Willkommen im Admin Panel + + + Wähle links im Menü einen Bereich aus. + + + ); +} \ No newline at end of file diff --git a/src/pages/LoginPage.jsx b/src/pages/LoginPage.jsx index 7059994..b305ce9 100644 --- a/src/pages/LoginPage.jsx +++ b/src/pages/LoginPage.jsx @@ -23,7 +23,7 @@ export default function LoginPage() { setLoading(true); try { await login(email, password); - navigate('/users'); + navigate('/'); } catch (err) { setError(err.response?.data?.message ?? 'Login fehlgeschlagen'); } finally { diff --git a/src/pages/OrganizationPage.jsx b/src/pages/OrganizationPage.jsx index 05b4c96..6354d85 100644 --- a/src/pages/OrganizationPage.jsx +++ b/src/pages/OrganizationPage.jsx @@ -122,11 +122,11 @@ export default function OrganizationPage() { width: 120, sortable: false, renderCell: ({ row }) => ( - - handleDelete(row.id)}> + + handleDelete(row.id)}> - handleEdit(row.id)}> + handleEdit(row.id)}> diff --git a/src/pages/PostsPage.jsx b/src/pages/PostsPage.jsx index d1eba8c..e201811 100644 --- a/src/pages/PostsPage.jsx +++ b/src/pages/PostsPage.jsx @@ -60,7 +60,7 @@ export default function PostsPage() { { field: 'author', headerName: 'Autor', width: 130 }, { field: 'category', headerName: 'Kategorie', width: 130 }, { - field: 'picture', + field: 'pictures', headerName: 'Bilder', width: 80, sortable: false, @@ -140,11 +140,11 @@ export default function PostsPage() { - {Array.isArray(detailNews?.picture) && detailNews.picture.length > 0 && ( + {Array.isArray(detailNews?.pictures) && detailNews.pictures.length > 0 && ( Bilder - {detailNews.picture.map((url, i) => ( + {detailNews.pictures.map((url, i) => (