diff --git a/backend/src/routes/groups.js b/backend/src/routes/groups.js
index 5523578..0c2e2af 100644
--- a/backend/src/routes/groups.js
+++ b/backend/src/routes/groups.js
@@ -33,12 +33,37 @@ router.get(endpoints.GET_ALL_GROUPS, async (req, res) => {
// Alle Gruppen für Moderation abrufen (mit Freigabestatus) - MUSS VOR den :groupId routen stehen!
router.get('/moderation/groups', async (req, res) => {
try {
- const groups = await GroupRepository.getAllGroupsWithModerationInfo();
+ const { workshopOnly, platform } = req.query;
+
+ let groups;
+
+ if (workshopOnly === 'true') {
+ // Filter: Nur Gruppen mit Werkstatt-Consent aber ohne Social Media
+ groups = await GroupRepository.getGroupsByConsentStatus(true, null);
+ } else if (platform) {
+ // Filter: Gruppen mit bestimmter Social Media Platform
+ groups = await GroupRepository.getGroupsByConsentStatus(true, platform);
+ } else {
+ // Alle Gruppen mit Consent-Daten
+ groups = await GroupRepository.getAllGroupsWithModerationInfo();
+ }
+
+ // Füge Consent-Daten für jede Gruppe hinzu
+ const groupsWithConsents = await Promise.all(
+ groups.map(async (group) => {
+ const consents = await GroupRepository.getSocialMediaConsentsForGroup(group.groupId);
+ return {
+ ...group,
+ socialMediaConsents: consents
+ };
+ })
+ );
+
res.json({
- groups,
- totalCount: groups.length,
- pendingCount: groups.filter(g => !g.approved).length,
- approvedCount: groups.filter(g => g.approved).length
+ groups: groupsWithConsents,
+ totalCount: groupsWithConsents.length,
+ pendingCount: groupsWithConsents.filter(g => !g.approved).length,
+ approvedCount: groupsWithConsents.filter(g => g.approved).length
});
} catch (error) {
console.error('Error fetching moderation groups:', error);
diff --git a/docs/FEATURE_PLAN-social-media.md b/docs/FEATURE_PLAN-social-media.md
index 931e123..d1b2ea5 100644
--- a/docs/FEATURE_PLAN-social-media.md
+++ b/docs/FEATURE_PLAN-social-media.md
@@ -713,71 +713,70 @@ export const uploadImageBatch = async (files, metadata, descriptions, consents)
#### Backend Tasks
-**Task 1.1: Datenbank-Migrationen** ⏱️ 1-2h
-- [ ] Migration 005: `display_in_workshop`, `consent_timestamp`, `management_token` zu `groups` hinzufügen
-- [ ] Migration 006: `social_media_platforms` und `group_social_media_consents` Tabellen erstellen
-- [ ] Standard-Plattformen (Facebook, Instagram, TikTok) einfügen
-- [ ] Migrationen testen (up/down)
+**Task 1.1: Datenbank-Migrationen** ⏱️ 1-2h ✅ ERLEDIGT (nur manuell)
+- [x] Migration 005: `display_in_workshop`, `consent_timestamp`, `management_token` zu `groups` hinzufügen
+- [x] Migration 006: `social_media_platforms` und `group_social_media_consents` Tabellen erstellen
+- [x] Standard-Plattformen (Facebook, Instagram, TikTok) einfügen
+- [ ] ⚠️ Automatisches Migrationssystem funktioniert nicht - nur manuelle Ausführung über sqlite3 möglich
-**Task 1.2: Repository-Erweiterungen** ⏱️ 3-4h
-- [ ] `GroupRepository`: `createGroupWithConsent()` implementieren
-- [ ] `GroupRepository`: `getGroupWithConsents()` implementieren
-- [ ] `GroupRepository`: `getGroupsByConsentStatus()` für Moderation-Filter
-- [ ] `SocialMediaRepository`: Neue Klasse erstellen
-- [ ] `SocialMediaRepository`: Platform-Management-Methoden
-- [ ] `SocialMediaRepository`: Consent-Management-Methoden
-- [ ] Unit-Tests für neue Repository-Methoden
+**Task 1.2: Repository-Erweiterungen** ⏱️ 3-4h ✅ ERLEDIGT
+- [x] `GroupRepository`: `createGroupWithConsent()` implementieren
+- [x] `GroupRepository`: `getGroupWithConsents()` implementieren
+- [x] `GroupRepository`: `getGroupsByConsentStatus()` für Moderation-Filter
+- [x] `SocialMediaRepository`: Neue Klasse erstellen
+- [x] `SocialMediaRepository`: Platform-Management-Methoden
+- [x] `SocialMediaRepository`: Consent-Management-Methoden
+- [ ] Unit-Tests für neue Repository-Methoden (TODO: später)
-**Task 1.3: API-Routes** ⏱️ 3-4h
-- [ ] Route `GET /api/social-media/platforms` erstellen
-- [ ] Route `POST /api/groups/:groupId/consents` erstellen
-- [ ] Route `GET /api/groups/:groupId/consents` erstellen
-- [ ] Route `GET /api/admin/groups/by-consent` für Moderation-Filter
-- [ ] Route `GET /api/admin/consents/export` für CSV/JSON Export
-- [ ] Validierung und Error-Handling
-- [ ] Integration-Tests für Routes
+**Task 1.3: API-Routes** ⏱️ 3-4h ✅ ERLEDIGT
+- [x] Route `GET /api/social-media/platforms` erstellen
+- [x] Route `POST /api/groups/:groupId/consents` erstellen
+- [x] Route `GET /api/groups/:groupId/consents` erstellen
+- [x] Route `GET /api/admin/groups/by-consent` für Moderation-Filter
+- [x] Route `GET /api/admin/consents/export` für CSV/JSON Export
+- [x] Validierung und Error-Handling
+- [ ] Integration-Tests für Routes (TODO: später)
-**Task 1.4: Upload-Route Anpassung** ⏱️ 2h
-- [ ] `batchUpload.js`: Consent-Parameter entgegennehmen
-- [ ] Validierung: `workshopConsent` muss true sein
-- [ ] Consent-Daten mit Gruppe speichern
-- [ ] Timestamp setzen
-- [ ] Response um `groupId` erweitern
-- [ ] Error-Handling bei fehlender Zustimmung
+**Task 1.4: Upload-Route Anpassung** ⏱️ 2h ✅ ERLEDIGT
+- [x] `batchUpload.js`: Consent-Parameter entgegennehmen
+- [x] Validierung: `workshopConsent` muss true sein
+- [x] Consent-Daten mit Gruppe speichern
+- [x] Timestamp setzen
+- [x] Response um `groupId` erweitern
+- [x] Error-Handling bei fehlender Zustimmung
#### Frontend Tasks
-**Task 1.5: ConsentCheckboxes Komponente** ⏱️ 4-5h
-- [ ] Komponente erstellen mit Material-UI
-- [ ] Aufklärungstext-Alert implementieren
-- [ ] Pflicht-Checkbox für Werkstatt-Anzeige
-- [ ] Dynamische Plattform-Liste vom Backend laden
-- [ ] Social Media Checkboxen generieren
-- [ ] Icon-Mapping für Plattformen
-- [ ] Widerrufs-Hinweis anzeigen
-- [ ] Responsive Design
-- [ ] Props für Disabled-State und onChange-Callback
+**Task 1.5: ConsentCheckboxes Komponente** ⏱️ 4-5h ✅ ERLEDIGT
+- [x] Komponente erstellen mit Material-UI
+- [x] Aufklärungstext-Alert implementieren
+- [x] Pflicht-Checkbox für Werkstatt-Anzeige
+- [x] Dynamische Plattform-Liste vom Backend laden
+- [x] Social Media Checkboxen generieren
+- [x] Icon-Mapping für Plattformen
+- [x] Widerrufs-Hinweis anzeigen
+- [x] Responsive Design
+- [x] Props für Disabled-State und onChange-Callback
-**Task 1.6: UploadSuccessDialog Komponente** ⏱️ 2-3h
-- [ ] Dialog-Komponente mit Material-UI erstellen
-- [ ] Gruppen-ID prominent anzeigen
-- [ ] Copy-to-Clipboard für Gruppen-ID
-- [ ] Aufklärungstext über Prüfung anzeigen
-- [ ] Kontakt-Information einbinden
-- [ ] Responsive Design
-- [ ] Animation für Success-State
+**Task 1.6: UploadSuccessDialog Komponente** ⏱️ 2-3h ✅ ERLEDIGT (als inline Content)
+- [x] Success-Content mit Gruppen-ID prominent anzeigen
+- [x] Aufklärungstext über Prüfung anzeigen
+- [x] Kontakt-Information einbinden
+- [x] Responsive Design
+- [x] Animation für Success-State
+- [x] Inline statt Dialog (User-Request)
-**Task 1.7: MultiUploadPage Integration** ⏱️ 2-3h
-- [ ] State für Consents hinzufügen
-- [ ] ConsentCheckboxes einbinden (vor Upload-Button)
-- [ ] Upload-Button nur aktivieren wenn `workshopConsent = true`
-- [ ] Consents-Validation in `handleUpload()`
-- [ ] Consents an Backend senden
-- [ ] UploadSuccessDialog nach Upload anzeigen
-- [ ] Gruppen-ID aus Response verarbeiten
-- [ ] Error-Handling für fehlende Zustimmung
+**Task 1.7: MultiUploadPage Integration** ⏱️ 2-3h ✅ ERLEDIGT
+- [x] State für Consents hinzufügen
+- [x] ConsentCheckboxes einbinden (nach DescriptionInput - User-Request)
+- [x] Upload-Button nur aktivieren wenn `workshopConsent = true`
+- [x] Consents-Validation in `handleUpload()`
+- [x] Consents an Backend senden
+- [x] Success-Content nach Upload anzeigen (inline)
+- [x] Gruppen-ID aus Response verarbeiten
+- [x] Error-Handling für fehlende Zustimmung
-**Task 1.8: Moderation Panel - Consent-Anzeige** ⏱️ 3-4h
+**Task 1.8: Moderation Panel - Consent-Anzeige** ⏱️ 3-4h ⏳ TODO
- [ ] ConsentBadges Komponente erstellen
- [ ] Social Media Icons/Chips anzeigen
- [ ] Badges in Gruppen-Liste integrieren
@@ -785,7 +784,7 @@ export const uploadImageBatch = async (files, metadata, descriptions, consents)
- [ ] Tooltip mit Consent-Timestamp
- [ ] Visuelle Unterscheidung (Werkstatt-only vs. Social Media)
-**Task 1.9: Moderation Panel - Filter & Export** ⏱️ 3-4h
+**Task 1.9: Moderation Panel - Filter & Export** ⏱️ 3-4h ⏳ TODO
- [ ] Filter-Dropdown für Consent-Status
- [ ] API-Abfrage mit Filter-Parametern
- [ ] Export-Button implementieren
diff --git a/frontend/src/Components/ComponentUtils/ConsentBadges.js b/frontend/src/Components/ComponentUtils/ConsentBadges.js
new file mode 100644
index 0000000..421c51c
--- /dev/null
+++ b/frontend/src/Components/ComponentUtils/ConsentBadges.js
@@ -0,0 +1,82 @@
+import React from 'react';
+import { Box, Chip, Tooltip } from '@mui/material';
+import CheckCircleIcon from '@mui/icons-material/CheckCircle';
+import FacebookIcon from '@mui/icons-material/Facebook';
+import InstagramIcon from '@mui/icons-material/Instagram';
+import MusicNoteIcon from '@mui/icons-material/MusicNote';
+import WorkIcon from '@mui/icons-material/Work';
+
+const ICON_MAP = {
+ 'Facebook': FacebookIcon,
+ 'Instagram': InstagramIcon,
+ 'MusicNote': MusicNoteIcon,
+};
+
+const ConsentBadges = ({ group }) => {
+ // Workshop consent badge (always show if consented)
+ const workshopBadge = group.display_in_workshop && (
+
{subtitle}
} + + {/* Consent Badges (only in moderation mode for groups) */} + {mode === 'moderation' && item.groupId && ( +{description}
)} diff --git a/frontend/src/Components/Pages/ModerationGroupsPage.js b/frontend/src/Components/Pages/ModerationGroupsPage.js index d26fe8e..a0776a6 100644 --- a/frontend/src/Components/Pages/ModerationGroupsPage.js +++ b/frontend/src/Components/Pages/ModerationGroupsPage.js @@ -1,12 +1,15 @@ import React, { useState, useEffect } from 'react'; import { Helmet } from 'react-helmet'; import { useNavigate } from 'react-router-dom'; -import { Container } from '@mui/material'; +import { Container, Box, FormControl, InputLabel, Select, MenuItem, Button } from '@mui/material'; +import FileDownloadIcon from '@mui/icons-material/FileDownload'; +import FilterListIcon from '@mui/icons-material/FilterList'; import Swal from 'sweetalert2/dist/sweetalert2.js'; import Navbar from '../ComponentUtils/Headers/Navbar'; import Footer from '../ComponentUtils/Footer'; import ImageGallery from '../ComponentUtils/ImageGallery'; import DeletionLogSection from '../ComponentUtils/DeletionLogSection'; +import ConsentBadges from '../ComponentUtils/ConsentBadges'; import { getImageSrc } from '../../Utils/imageUtils'; const ModerationGroupsPage = () => { @@ -15,16 +18,53 @@ const ModerationGroupsPage = () => { const [error, setError] = useState(null); const [selectedGroup, setSelectedGroup] = useState(null); const [showImages, setShowImages] = useState(false); + const [consentFilter, setConsentFilter] = useState('all'); + const [platforms, setPlatforms] = useState([]); const navigate = useNavigate(); useEffect(() => { loadModerationGroups(); + loadPlatforms(); }, []); + useEffect(() => { + loadModerationGroups(); + }, [consentFilter]); + + const loadPlatforms = async () => { + try { + const response = await fetch('/api/social-media/platforms'); + if (response.ok) { + const data = await response.json(); + setPlatforms(data); + } + } catch (error) { + console.error('Fehler beim Laden der Plattformen:', error); + } + }; + const loadModerationGroups = async () => { try { setLoading(true); - const response = await fetch('/moderation/groups'); + + // Build URL with filter params + let url = '/moderation/groups'; + const params = new URLSearchParams(); + + if (consentFilter !== 'all') { + if (consentFilter === 'workshop-only') { + params.append('workshopOnly', 'true'); + } else { + // Platform filter (facebook, instagram, tiktok) + params.append('platform', consentFilter); + } + } + + if (params.toString()) { + url += '?' + params.toString(); + } + + const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); @@ -155,6 +195,41 @@ const ModerationGroupsPage = () => { navigate(`/moderation/groups/${group.groupId}`); }; + const exportConsentData = async () => { + try { + const response = await fetch('/api/admin/consents/export?format=csv'); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `consent-export-${new Date().toISOString().split('T')[0]}.csv`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + + await Swal.fire({ + icon: 'success', + title: 'Export erfolgreich', + text: 'Consent-Daten wurden als CSV heruntergeladen.', + timer: 2000, + showConfirmButton: false + }); + } catch (error) { + console.error('Fehler beim Export:', error); + await Swal.fire({ + icon: 'error', + title: 'Fehler', + text: 'Fehler beim Export der Consent-Daten: ' + error.message + }); + } + }; + if (loading) { return