diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..e9b69c0
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,20 @@
+# TODO / Offene Punkte
+
+Diese Datei listet die noch offenen Arbeiten, die ich im Projekt verfolge. Ich pflege sie, sobald ich Aufgaben erledige.
+
+## Aktuelle Aufgaben
+
+- [ ] CSS-Sweep: Duplikate finden und Regeln in `frontend/src/Components/ComponentUtils/Css/main.css` zentralisieren
+- [ ] Page-CSS bereinigen: Entfernen von Regeln, die jetzt in `main.css` sind
+- [ ] README: Kurzbeschreibung des Style-Guides und wo zentrale Klassen liegen
+- [ ] Persistentes Reordering: Drag-and-drop in `ImagePreviewGallery` + Backend-Endpunkt
+- [ ] Kleine Smoke-Tests: Frontend-Build lokal laufen lassen und UI quick-check
+
+## Erledigte Aufgaben
+- [x] Alte Dateien entfernt (`ModerationPage.js`, alte `GroupImagesPage.js`)
+- [x] Moderation-Detailseite angepasst (zeigt jetzt nur Bilder, Metadaten-Editor und Save/Back)
+- [x] Routing: `/moderation` und `/moderation/groups/:groupId` sowie `/groups/:groupId` (public) gesetzt
+
+---
+
+Wenn du möchtest, dass ich jetzt einzelne Aufgaben aus der "Aktuelle Aufgaben"-Liste abarbeite, sag mir welche Priorität hat (z.B. zuerst `main.css` aufsetzen und die größten Duplikate entfernen).
\ No newline at end of file
diff --git a/frontend/src/App.js b/frontend/src/App.js
index 87c225c..f6b5113 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -5,7 +5,6 @@ import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import UploadedImage from './Components/Pages/UploadedImagePage';
import MultiUploadPage from './Components/Pages/MultiUploadPage';
import SlideshowPage from './Components/Pages/SlideshowPage';
-import GroupsOverviewPage from './Components/Pages/GroupsOverviewPage';
import ModerationGroupsPage from './Components/Pages/ModerationGroupsPage';
import ModerationGroupImagesPage from './Components/Pages/ModerationGroupImagesPage';
import PublicGroupImagesPage from './Components/Pages/PublicGroupImagesPage';
@@ -19,7 +18,7 @@ function App() {
-
+ {/* Groups overview removed; public group listing is handled elsewhere. */}
diff --git a/frontend/src/Components/Pages/Css/GroupImagesPage.css b/frontend/src/Components/Pages/Css/GroupImagesPage.css
index 949e1d1..cb30de1 100644
--- a/frontend/src/Components/Pages/Css/GroupImagesPage.css
+++ b/frontend/src/Components/Pages/Css/GroupImagesPage.css
@@ -1,3 +1,6 @@
-.header-text { font-family: roboto; font-weight: 400; font-size: 28px; text-align: center; margin-bottom: 10px; color: #333333; }
-.subheader-text { font-family: roboto; font-weight: 300; font-size: 16px; color: #666666; text-align: center; margin-bottom: 30px; }
-.action-buttons { display: flex; gap: 15px; justify-content: center; margin-top: 20px; flex-wrap: wrap; }
+@import '../../ComponentUtils/Css/main.css';
+
+/* Page-specific styles for group images edit page */
+.header-text { font-weight: 400; font-size: 28px; text-align: center; margin-bottom: 10px; color:#333333; }
+.subheader-text { font-weight: 300; font-size: 16px; color:#666666; text-align:center; margin-bottom:30px; }
+.action-buttons { display:flex; gap:15px; justify-content:center; margin-top:20px; flex-wrap:wrap; }
diff --git a/frontend/src/Components/Pages/Css/GroupsOverviewPage.css b/frontend/src/Components/Pages/Css/GroupsOverviewPage.css
index 06fa9d9..3e3276e 100644
--- a/frontend/src/Components/Pages/Css/GroupsOverviewPage.css
+++ b/frontend/src/Components/Pages/Css/GroupsOverviewPage.css
@@ -1,6 +1,8 @@
-.header-card { border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin-bottom: 30px; text-align: center; padding: 20px; }
-.header-title { font-family: roboto; font-weight: 500; font-size: 28px; color: #333333; margin-bottom: 10px; }
-.header-subtitle { font-family: roboto; font-size: 16px; color: #666666; margin-bottom: 20px; }
-.empty-state { text-align:center; padding:60px 20px; }
-.loading-container { text-align:center; padding:60px 20px; }
+@import '../../ComponentUtils/Css/main.css';
+
+/* Page-specific rules for GroupsOverviewPage */
+.header-card { margin-bottom: 30px; text-align: center; padding: 20px; }
+.header-title { font-weight: 500; font-size: 28px; color: #333333; margin-bottom: 10px; }
+.header-subtitle { font-size: 16px; color: #666666; margin-bottom: 20px; }
+
@media (max-width:800px) { .nav__links, .cta { display:none; } }
diff --git a/frontend/src/Components/Pages/Css/ModerationPage.css b/frontend/src/Components/Pages/Css/ModerationPage.css
index c20f3a3..e48f959 100644
--- a/frontend/src/Components/Pages/Css/ModerationPage.css
+++ b/frontend/src/Components/Pages/Css/ModerationPage.css
@@ -1,76 +1,16 @@
-.moderation-page {
- max-width: 1200px;
- margin: 0 auto;
- padding: 20px;
-}
+@import '../../ComponentUtils/Css/main.css';
-.moderation-page h1 {
- text-align: center;
- color: #333;
- margin-bottom: 30px;
-}
+/* Page-specific moderation styles */
+.moderation-page { max-width: 1200px; margin: 0 auto; padding: 20px; }
+.moderation-page h1 { text-align:center; color:#333; margin-bottom:30px; }
+.moderation-loading, .moderation-error { text-align:center; padding:50px; font-size:18px; }
+.moderation-error { color:#dc3545; }
-.moderation-loading, .moderation-error {
- text-align: center;
- padding: 50px;
- font-size: 18px;
-}
+.moderation-stats { display:flex; justify-content:center; gap:40px; margin-bottom:40px; padding:20px; background:#f8f9fa; border-radius:12px; }
+.stat-item { text-align:center; }
+.stat-number { display:block; font-size:2.5rem; font-weight:bold; color:#007bff; }
+.stat-label { display:block; font-size:0.9rem; color:#6c757d; margin-top:5px; }
-.moderation-error {
- color: #dc3545;
-}
-
-/* Statistiken */
-.moderation-stats {
- display: flex;
- justify-content: center;
- gap: 40px;
- margin-bottom: 40px;
- padding: 20px;
- background: #f8f9fa;
- border-radius: 12px;
-}
-
-.stat-item {
- text-align: center;
-}
-
-.stat-number {
- display: block;
- font-size: 2.5rem;
- font-weight: bold;
- color: #007bff;
-}
-
-.stat-label {
- display: block;
- font-size: 0.9rem;
- color: #6c757d;
- margin-top: 5px;
-}
-
-/* Sections */
-.moderation-section {
- margin-bottom: 50px;
-}
-
-.moderation-section h2 {
- color: #333;
- border-bottom: 2px solid #e9ecef;
- padding-bottom: 10px;
- margin-bottom: 25px;
-}
-
-.no-groups {
- text-align: center;
- color: #6c757d;
- font-style: italic;
- padding: 30px;
-}
-
-/* Groups Grid */
-.groups-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
- gap: 20px;
-}
\ No newline at end of file
+.moderation-section { margin-bottom:50px; }
+.moderation-section h2 { color:#333; border-bottom:2px solid #e9ecef; padding-bottom:10px; margin-bottom:25px; }
+.no-groups { text-align:center; color:#6c757d; font-style:italic; padding:30px; }
\ No newline at end of file
diff --git a/frontend/src/Components/Pages/GroupImagesPage.js b/frontend/src/Components/Pages/GroupImagesPage.js
deleted file mode 100644
index cf56c47..0000000
--- a/frontend/src/Components/Pages/GroupImagesPage.js
+++ /dev/null
@@ -1,200 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { useParams, useHistory } from 'react-router-dom';
-import { Button, Card, CardContent, Typography, Container } from '@material-ui/core';
-import Swal from 'sweetalert2/dist/sweetalert2.js';
-import 'sweetalert2/src/sweetalert2.scss';
-
-// Components
-import Navbar from '../ComponentUtils/Headers/Navbar';
-import Footer from '../ComponentUtils/Footer';
-import ImagePreviewGallery from '../ComponentUtils/MultiUpload/ImagePreviewGallery';
-import DescriptionInput from '../ComponentUtils/MultiUpload/DescriptionInput';
-import GroupCard from '../ComponentUtils/GroupCard';
-
-
-import '../Pages/Css/GroupImagesPage.css';
-
-const GroupImagesPage = () => {
- // use CSS classes from GroupImagesPage.css
- const { groupId } = useParams();
- const history = useHistory();
- const [group, setGroup] = useState(null);
- const [loading, setLoading] = useState(true);
- const [saving, setSaving] = useState(false);
- const [error, setError] = useState(null);
-
- // selectedImages will hold objects compatible with ImagePreviewGallery
- const [selectedImages, setSelectedImages] = useState([]);
- const [metadata, setMetadata] = useState({ year: new Date().getFullYear(), title: '', description: '', name: '' });
-
- useEffect(() => {
- loadGroup();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [groupId]);
-
- const loadGroup = async () => {
- try {
- setLoading(true);
- const res = await fetch(`/moderation/groups/${groupId}`);
- if (!res.ok) throw new Error('Nicht gefunden');
- const data = await res.json();
- setGroup(data);
-
- // Map group's images to preview-friendly objects
- if (data.images && data.images.length > 0) {
- const mapped = data.images.map(img => ({
- remoteUrl: `/download/${img.fileName}`,
- originalName: img.originalName || img.fileName,
- id: img.id
- }));
- setSelectedImages(mapped);
- }
-
- // populate metadata from group
- setMetadata({
- year: data.year || new Date().getFullYear(),
- title: data.title || '',
- description: data.description || '',
- name: data.name || ''
- });
- } catch (e) {
- setError('Fehler beim Laden der Gruppe');
- } finally {
- setLoading(false);
- }
- };
-
- const handleChange = (field, value) => {
- setGroup({ ...group, [field]: value });
- setMetadata(prev => ({ ...prev, [field]: value }));
- };
-
- const handleSave = async () => {
- if (!group) return;
- setSaving(true);
- try {
- // Use metadata state (controlled by DescriptionInput) as source of truth
- const payload = {
- title: metadata.title,
- description: metadata.description,
- year: metadata.year,
- name: metadata.name
- };
-
- const res = await fetch(`/groups/${groupId}`, {
- method: 'PATCH',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(payload)
- });
-
- if (!res.ok) {
- const body = await res.json().catch(() => ({}));
- throw new Error(body.message || 'Speichern fehlgeschlagen');
- }
-
- Swal.fire({ icon: 'success', title: 'Gruppe erfolgreich aktualisiert', timer: 1500, showConfirmButton: false });
- history.push('/moderation');
- } catch (e) {
- console.error(e);
- Swal.fire({ icon: 'error', title: 'Fehler beim Speichern', text: e.message });
- } finally {
- setSaving(false);
- }
- };
-
- const handleDeleteImage = async (imageId) => {
- if (!window.confirm('Bild wirklich löschen?')) return;
- try {
- const res = await fetch(`/groups/${groupId}/images/${imageId}`, { method: 'DELETE' });
- if (!res.ok) throw new Error('Löschen fehlgeschlagen');
- // Aktualisiere lokale Ansicht
- const newImages = group.images.filter(img => img.id !== imageId);
- setGroup({ ...group, images: newImages, imageCount: (group.imageCount || 0) - 1 });
- setSelectedImages(prev => prev.filter(img => img.id !== imageId));
- Swal.fire({ icon: 'success', title: 'Bild gelöscht', timer: 1200, showConfirmButton: false });
- } catch (e) {
- console.error(e);
- Swal.fire({ icon: 'error', title: 'Fehler beim Löschen des Bildes' });
- }
- };
-
- const handleRemoveImage = (indexToRemove) => {
- // If it's a remote image mapped with id, call delete
- const img = selectedImages[indexToRemove];
- if (img && img.id) {
- handleDeleteImage(img.id);
- return;
- }
- setSelectedImages(prev => prev.filter((_, index) => index !== indexToRemove));
- };
-
- const approveGroup = async (approved) => {
- try {
- const response = await fetch(`/groups/${groupId}/approve`, {
- method: 'PATCH',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ approved })
- });
- if (!response.ok) throw new Error('Freigabe fehlgeschlagen');
- const updated = { ...group, approved };
- setGroup(updated);
- Swal.fire({ icon: 'success', title: approved ? 'Gruppe freigegeben' : 'Gruppe gesperrt', timer: 1200, showConfirmButton: false });
- } catch (e) {
- console.error(e);
- Swal.fire({ icon: 'error', title: 'Fehler beim Aktualisieren des Freigabestatus' });
- }
- };
-
- const deleteGroup = async () => {
- if (!window.confirm('Gruppe wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.')) return;
- try {
- const res = await fetch(`/groups/${groupId}`, { method: 'DELETE' });
- if (!res.ok) throw new Error('Löschen fehlgeschlagen');
- Swal.fire({ icon: 'success', title: 'Gruppe gelöscht', timer: 1200, showConfirmButton: false });
- history.push('/moderation');
- } catch (e) {
- console.error(e);
- Swal.fire({ icon: 'error', title: 'Fehler beim Löschen der Gruppe' });
- }
- };
-
- if (loading) return
Lade Gruppe...
;
- if (error) return {error}
;
- if (!group) return Gruppe nicht gefunden
;
-
- return (
-
-
-
-
- {/* Use shared GroupCard for top summary/actions */}
- approveGroup(approved)}
- onViewImages={() => { /* already on this page */ }}
- onDelete={() => deleteGroup()}
- isPending={!group.approved}
- />
-
-
-
- {selectedImages.length > 0 && (
- <>
-
-
-
-
-
-
- >
- )}
-
-
-
-
-
- );
-
-};
-
-export default GroupImagesPage;
diff --git a/frontend/src/Components/Pages/GroupsOverviewPage.js b/frontend/src/Components/Pages/GroupsOverviewPage.js
deleted file mode 100644
index fa5c219..0000000
--- a/frontend/src/Components/Pages/GroupsOverviewPage.js
+++ /dev/null
@@ -1,201 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { useHistory } from 'react-router-dom';
-import { Helmet } from 'react-helmet';
-import '../Pages/Css/GroupsOverviewPage.css';
-import {
- Container,
- Card,
- CardContent,
- Typography,
- Button,
- Grid,
- CardMedia,
- Box,
- CircularProgress,
- Chip
-} from '@material-ui/core';
-import {
- Slideshow as SlideshowIcon,
- Add as AddIcon,
- Home as HomeIcon
-} from '@material-ui/icons';
-import Swal from 'sweetalert2/dist/sweetalert2.js';
-
-// Components
-import Navbar from '../ComponentUtils/Headers/Navbar';
-import Footer from '../ComponentUtils/Footer';
-import GroupCard from '../ComponentUtils/GroupCard';
-
-// Utils
-import { fetchAllGroups, deleteGroup } from '../../Utils/batchUpload';
-
-// Styles
-import '../../App.css';
-
-function GroupsOverviewPage() {
- // use CSS classes from GroupsOverviewPage.css
- const history = useHistory();
-
- const [groups, setGroups] = useState([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
-
- useEffect(() => {
- loadGroups();
- }, []);
-
- const loadGroups = async () => {
- try {
- setLoading(true);
- const response = await fetchAllGroups();
- setGroups(response.groups || []);
- setError(null);
- } catch (err) {
- setError(err.message);
- console.error('Error loading groups:', err);
- } finally {
- setLoading(false);
- }
- };
-
- const handleViewSlideshow = (groupId) => {
- history.push(`/slideshow/${groupId}`);
- };
-
- const handleCreateNew = () => {
- history.push('/multi-upload');
- };
-
- const handleGoHome = () => {
- history.push('/');
- };
-
- const formatDate = (dateString) => {
- return new Date(dateString).toLocaleDateString('de-DE', {
- year: 'numeric',
- month: 'short',
- day: 'numeric'
- });
- };
-
- if (loading) {
- return (
-
-
-
-
-
-
- Slideshows werden geladen...
-
-
-
-
-
- );
- }
-
- return (
-
-
- Gruppenübersicht - Interne Verwaltung
-
-
-
-
-
-
-
- {/* Header */}
-
-
- 🎬 Alle Slideshows
-
-
- Verwalten Sie Ihre hochgeladenen Bildersammlungen
-
-
-
- }
- size="large"
- >
- ➕ Neue Slideshow erstellen
-
-
- }
- size="large"
- >
- 🏠 Zur Startseite
-
-
-
-
- {/* Groups Grid */}
- {error ? (
-
-
- 😕 Fehler beim Laden
-
-
- {error}
-
-
-
- ) : groups.length === 0 ? (
-
-
- 📸 Keine Slideshows vorhanden
-
-
- Erstellen Sie Ihre erste Slideshow, indem Sie mehrere Bilder hochladen.
-
-
-
- ) : (
- <>
-
-
- 📊 {groups.length} Slideshow{groups.length !== 1 ? 's' : ''} gefunden
-
-
-
-
- {groups.map((group) => (
-
- { /* no-op on public page */ }}
- onViewImages={() => handleViewSlideshow(group.groupId)}
- onDelete={() => { /* no-op on public page */ }}
- isPending={false}
- showActions={false}
- />
-
- ))}
-
- >
- )}
-
-
-
-
-
-
- );
-}
-
-export default GroupsOverviewPage;
\ No newline at end of file
diff --git a/frontend/src/Components/Pages/ModerationGroupImagesPage.js b/frontend/src/Components/Pages/ModerationGroupImagesPage.js
index 32a4ad0..aae1807 100644
--- a/frontend/src/Components/Pages/ModerationGroupImagesPage.js
+++ b/frontend/src/Components/Pages/ModerationGroupImagesPage.js
@@ -9,7 +9,6 @@ import Navbar from '../ComponentUtils/Headers/Navbar';
import Footer from '../ComponentUtils/Footer';
import ImagePreviewGallery from '../ComponentUtils/MultiUpload/ImagePreviewGallery';
import DescriptionInput from '../ComponentUtils/MultiUpload/DescriptionInput';
-import GroupCard from '../ComponentUtils/GroupCard';
import '../Pages/Css/GroupImagesPage.css';
@@ -122,35 +121,7 @@ const ModerationGroupImagesPage = () => {
setSelectedImages(prev => prev.filter((_, index) => index !== indexToRemove));
};
- const approveGroup = async (approved) => {
- try {
- const response = await fetch(`/groups/${groupId}/approve`, {
- method: 'PATCH',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ approved })
- });
- if (!response.ok) throw new Error('Freigabe fehlgeschlagen');
- const updated = { ...group, approved };
- setGroup(updated);
- Swal.fire({ icon: 'success', title: approved ? 'Gruppe freigegeben' : 'Gruppe gesperrt', timer: 1200, showConfirmButton: false });
- } catch (e) {
- console.error(e);
- Swal.fire({ icon: 'error', title: 'Fehler beim Aktualisieren des Freigabestatus' });
- }
- };
-
- const deleteGroup = async () => {
- if (!window.confirm('Gruppe wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.')) return;
- try {
- const res = await fetch(`/groups/${groupId}`, { method: 'DELETE' });
- if (!res.ok) throw new Error('Löschen fehlgeschlagen');
- Swal.fire({ icon: 'success', title: 'Gruppe gelöscht', timer: 1200, showConfirmButton: false });
- history.push('/moderation');
- } catch (e) {
- console.error(e);
- Swal.fire({ icon: 'error', title: 'Fehler beim Löschen der Gruppe' });
- }
- };
+ // Note: approve/delete group actions are intentionally removed from this page
if (loading) return Lade Gruppe...
;
if (error) return {error}
;
@@ -161,16 +132,7 @@ const ModerationGroupImagesPage = () => {
- {/* Use shared GroupCard for top summary/actions */}
- approveGroup(approved)}
- onViewImages={() => { /* already on this page */ }}
- onDelete={() => deleteGroup()}
- isPending={!group.approved}
- />
-
-
+
{selectedImages.length > 0 && (
<>
diff --git a/frontend/src/Components/Pages/ModerationPage.js b/frontend/src/Components/Pages/ModerationPage.js
deleted file mode 100644
index d69d71c..0000000
--- a/frontend/src/Components/Pages/ModerationPage.js
+++ /dev/null
@@ -1,301 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { Helmet } from 'react-helmet';
-import { useHistory } from 'react-router-dom';
-import { Container } from '@material-ui/core';
-import './Css/ModerationPage.css';
-import Navbar from '../ComponentUtils/Headers/Navbar';
-import Footer from '../ComponentUtils/Footer';
-import GroupCard from '../ComponentUtils/GroupCard';
-
-const ModerationPage = () => {
- const [groups, setGroups] = useState([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
- const [selectedGroup, setSelectedGroup] = useState(null);
- const [showImages, setShowImages] = useState(false);
- const history = useHistory();
-
- useEffect(() => {
- loadModerationGroups();
- }, []);
-
- const loadModerationGroups = async () => {
- try {
- setLoading(true);
- const response = await fetch('/moderation/groups');
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
-
- const data = await response.json();
- setGroups(data.groups);
- } catch (error) {
- console.error('Fehler beim Laden der Moderations-Gruppen:', error);
- setError('Fehler beim Laden der Gruppen');
- } finally {
- setLoading(false);
- }
- };
-
- const approveGroup = async (groupId, approved) => {
- try {
- const response = await fetch(`/groups/${groupId}/approve`, {
- method: 'PATCH',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({ approved: approved })
- });
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
-
- // Update local state
- setGroups(groups.map(group =>
- group.groupId === groupId
- ? { ...group, approved: approved }
- : group
- ));
- } catch (error) {
- console.error('Fehler beim Freigeben der Gruppe:', error);
- alert('Fehler beim Freigeben der Gruppe');
- }
- };
-
- const deleteImage = async (groupId, imageId) => {
- console.log('deleteImage called with:', { groupId, imageId });
- console.log('API_URL:', window._env_.API_URL);
-
- try {
- // Use relative URL to go through Nginx proxy
- const url = `/groups/${groupId}/images/${imageId}`;
- console.log('DELETE request to:', url);
-
- const response = await fetch(url, {
- method: 'DELETE'
- });
-
- console.log('Response status:', response.status);
- console.log('Response ok:', response.ok);
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
-
- // Remove image from selectedGroup
- if (selectedGroup && selectedGroup.groupId === groupId) {
- const updatedImages = selectedGroup.images.filter(img => img.id !== imageId);
- setSelectedGroup({
- ...selectedGroup,
- images: updatedImages,
- imageCount: updatedImages.length
- });
- }
-
- // Update group image count
- setGroups(groups.map(group =>
- group.groupId === groupId
- ? { ...group, imageCount: group.imageCount - 1 }
- : group
- ));
-
- } catch (error) {
- console.error('Fehler beim Löschen des Bildes:', error);
- console.error('Error details:', error.message, error.stack);
- alert('Fehler beim Löschen des Bildes: ' + error.message);
- }
- };
-
- const deleteGroup = async (groupId) => {
- if (!window.confirm('Gruppe wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.')) {
- return;
- }
-
- try {
- const response = await fetch(`/groups/${groupId}`, {
- method: 'DELETE'
- });
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
-
- setGroups(groups.filter(group => group.groupId !== groupId));
- if (selectedGroup && selectedGroup.groupId === groupId) {
- setSelectedGroup(null);
- setShowImages(false);
- }
- } catch (error) {
- console.error('Fehler beim Löschen der Gruppe:', error);
- alert('Fehler beim Löschen der Gruppe');
- }
- };
-
- // Navigate to the dedicated group images page
- const viewGroupImages = (group) => {
- history.push(`/groups/${group.groupId}`);
- };
-
- if (loading) {
- return Lade Gruppen...
;
- }
-
- if (error) {
- return {error}
;
- }
-
- const pendingGroups = groups.filter(g => !g.approved);
- const approvedGroups = groups.filter(g => g.approved);
-
- return (
-
-
-
- Moderation - Interne Verwaltung
-
-
-
-
-
- Moderation
-
-
-
- {pendingGroups.length}
- Wartend
-
-
- {approvedGroups.length}
- Freigegeben
-
-
- {groups.length}
- Gesamt
-
-
-
-
-
- {pendingGroups.length}
- Wartend
-
-
- {approvedGroups.length}
- Freigegeben
-
-
- {groups.length}
- Gesamt
-
-
-
- {/* Wartende Gruppen */}
-
- 🔍 Wartende Freigabe ({pendingGroups.length})
- {pendingGroups.length === 0 ? (
- Keine wartenden Gruppen
- ) : (
-
- {pendingGroups.map(group => (
-
- ))}
-
- )}
-
-
- {/* Freigegebene Gruppen */}
-
- ✅ Freigegebene Gruppen ({approvedGroups.length})
- {approvedGroups.length === 0 ? (
- Keine freigegebenen Gruppen
- ) : (
-
- {approvedGroups.map(group => (
-
- ))}
-
- )}
-
-
- {/* Bilder-Modal */}
- {showImages && selectedGroup && (
- {
- setShowImages(false);
- setSelectedGroup(null);
- }}
- onDeleteImage={deleteImage}
- />
- )}
-
-
-
- );
-};
-
-// `GroupCard` has been extracted to `../ComponentUtils/GroupCard`
-
-const ImageModal = ({ group, onClose, onDeleteImage }) => {
- return (
-
-
e.stopPropagation()}>
-
-
{group.title}
-
-
-
-
-
-
Jahr: {group.year}
-
Ersteller: {group.name}
- {group.description && (
-
Beschreibung: {group.description}
- )}
-
Bilder: {group.images.length}
-
-
-
- {group.images.map(image => (
-
-

-
- {image.originalName}
-
-
-
- ))}
-
-
-
-
- );
-};
-
-export default ModerationPage;
\ No newline at end of file