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 */}
+
+
+
+
+
+
+ ) : (
+ }
+ size="small"
+ variant="outlined"
+ sx={{ alignSelf: 'flex-start' }}
+ >
+ Titelbild hochladen
+
+
+ )}
+
+
+
+
+ {/* ── 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() {
: null}
>
Speichern
-
- )
+ );
}
\ 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) => (