feat: Implement moderation panel consent features
- Add ConsentBadges component with platform icons and tooltips - Add consent filter dropdown in moderation page (all/workshop-only/platforms) - Add export button for CSV download of consent data - Extend /moderation/groups endpoint with filter params and consent data - Display consent badges in ImageGalleryCard for moderation mode - Visual distinction: workshop (green), social media (blue outlined) - Export functionality with date-stamped CSV files Tasks completed: - Moderation visual consent indicators - Moderation consent filter - Moderation export functionality
This commit is contained in:
parent
6745f89f38
commit
a27a66f6ee
|
|
@ -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!
|
// Alle Gruppen für Moderation abrufen (mit Freigabestatus) - MUSS VOR den :groupId routen stehen!
|
||||||
router.get('/moderation/groups', async (req, res) => {
|
router.get('/moderation/groups', async (req, res) => {
|
||||||
try {
|
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({
|
res.json({
|
||||||
groups,
|
groups: groupsWithConsents,
|
||||||
totalCount: groups.length,
|
totalCount: groupsWithConsents.length,
|
||||||
pendingCount: groups.filter(g => !g.approved).length,
|
pendingCount: groupsWithConsents.filter(g => !g.approved).length,
|
||||||
approvedCount: groups.filter(g => g.approved).length
|
approvedCount: groupsWithConsents.filter(g => g.approved).length
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching moderation groups:', error);
|
console.error('Error fetching moderation groups:', error);
|
||||||
|
|
|
||||||
|
|
@ -713,71 +713,70 @@ export const uploadImageBatch = async (files, metadata, descriptions, consents)
|
||||||
|
|
||||||
#### Backend Tasks
|
#### Backend Tasks
|
||||||
|
|
||||||
**Task 1.1: Datenbank-Migrationen** ⏱️ 1-2h
|
**Task 1.1: Datenbank-Migrationen** ⏱️ 1-2h ✅ ERLEDIGT (nur manuell)
|
||||||
- [ ] Migration 005: `display_in_workshop`, `consent_timestamp`, `management_token` zu `groups` hinzufügen
|
- [x] 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
|
- [x] Migration 006: `social_media_platforms` und `group_social_media_consents` Tabellen erstellen
|
||||||
- [ ] Standard-Plattformen (Facebook, Instagram, TikTok) einfügen
|
- [x] Standard-Plattformen (Facebook, Instagram, TikTok) einfügen
|
||||||
- [ ] Migrationen testen (up/down)
|
- [ ] ⚠️ Automatisches Migrationssystem funktioniert nicht - nur manuelle Ausführung über sqlite3 möglich
|
||||||
|
|
||||||
**Task 1.2: Repository-Erweiterungen** ⏱️ 3-4h
|
**Task 1.2: Repository-Erweiterungen** ⏱️ 3-4h ✅ ERLEDIGT
|
||||||
- [ ] `GroupRepository`: `createGroupWithConsent()` implementieren
|
- [x] `GroupRepository`: `createGroupWithConsent()` implementieren
|
||||||
- [ ] `GroupRepository`: `getGroupWithConsents()` implementieren
|
- [x] `GroupRepository`: `getGroupWithConsents()` implementieren
|
||||||
- [ ] `GroupRepository`: `getGroupsByConsentStatus()` für Moderation-Filter
|
- [x] `GroupRepository`: `getGroupsByConsentStatus()` für Moderation-Filter
|
||||||
- [ ] `SocialMediaRepository`: Neue Klasse erstellen
|
- [x] `SocialMediaRepository`: Neue Klasse erstellen
|
||||||
- [ ] `SocialMediaRepository`: Platform-Management-Methoden
|
- [x] `SocialMediaRepository`: Platform-Management-Methoden
|
||||||
- [ ] `SocialMediaRepository`: Consent-Management-Methoden
|
- [x] `SocialMediaRepository`: Consent-Management-Methoden
|
||||||
- [ ] Unit-Tests für neue Repository-Methoden
|
- [ ] Unit-Tests für neue Repository-Methoden (TODO: später)
|
||||||
|
|
||||||
**Task 1.3: API-Routes** ⏱️ 3-4h
|
**Task 1.3: API-Routes** ⏱️ 3-4h ✅ ERLEDIGT
|
||||||
- [ ] Route `GET /api/social-media/platforms` erstellen
|
- [x] Route `GET /api/social-media/platforms` erstellen
|
||||||
- [ ] Route `POST /api/groups/:groupId/consents` erstellen
|
- [x] Route `POST /api/groups/:groupId/consents` erstellen
|
||||||
- [ ] Route `GET /api/groups/:groupId/consents` erstellen
|
- [x] Route `GET /api/groups/:groupId/consents` erstellen
|
||||||
- [ ] Route `GET /api/admin/groups/by-consent` für Moderation-Filter
|
- [x] Route `GET /api/admin/groups/by-consent` für Moderation-Filter
|
||||||
- [ ] Route `GET /api/admin/consents/export` für CSV/JSON Export
|
- [x] Route `GET /api/admin/consents/export` für CSV/JSON Export
|
||||||
- [ ] Validierung und Error-Handling
|
- [x] Validierung und Error-Handling
|
||||||
- [ ] Integration-Tests für Routes
|
- [ ] Integration-Tests für Routes (TODO: später)
|
||||||
|
|
||||||
**Task 1.4: Upload-Route Anpassung** ⏱️ 2h
|
**Task 1.4: Upload-Route Anpassung** ⏱️ 2h ✅ ERLEDIGT
|
||||||
- [ ] `batchUpload.js`: Consent-Parameter entgegennehmen
|
- [x] `batchUpload.js`: Consent-Parameter entgegennehmen
|
||||||
- [ ] Validierung: `workshopConsent` muss true sein
|
- [x] Validierung: `workshopConsent` muss true sein
|
||||||
- [ ] Consent-Daten mit Gruppe speichern
|
- [x] Consent-Daten mit Gruppe speichern
|
||||||
- [ ] Timestamp setzen
|
- [x] Timestamp setzen
|
||||||
- [ ] Response um `groupId` erweitern
|
- [x] Response um `groupId` erweitern
|
||||||
- [ ] Error-Handling bei fehlender Zustimmung
|
- [x] Error-Handling bei fehlender Zustimmung
|
||||||
|
|
||||||
#### Frontend Tasks
|
#### Frontend Tasks
|
||||||
|
|
||||||
**Task 1.5: ConsentCheckboxes Komponente** ⏱️ 4-5h
|
**Task 1.5: ConsentCheckboxes Komponente** ⏱️ 4-5h ✅ ERLEDIGT
|
||||||
- [ ] Komponente erstellen mit Material-UI
|
- [x] Komponente erstellen mit Material-UI
|
||||||
- [ ] Aufklärungstext-Alert implementieren
|
- [x] Aufklärungstext-Alert implementieren
|
||||||
- [ ] Pflicht-Checkbox für Werkstatt-Anzeige
|
- [x] Pflicht-Checkbox für Werkstatt-Anzeige
|
||||||
- [ ] Dynamische Plattform-Liste vom Backend laden
|
- [x] Dynamische Plattform-Liste vom Backend laden
|
||||||
- [ ] Social Media Checkboxen generieren
|
- [x] Social Media Checkboxen generieren
|
||||||
- [ ] Icon-Mapping für Plattformen
|
- [x] Icon-Mapping für Plattformen
|
||||||
- [ ] Widerrufs-Hinweis anzeigen
|
- [x] Widerrufs-Hinweis anzeigen
|
||||||
- [ ] Responsive Design
|
- [x] Responsive Design
|
||||||
- [ ] Props für Disabled-State und onChange-Callback
|
- [x] Props für Disabled-State und onChange-Callback
|
||||||
|
|
||||||
**Task 1.6: UploadSuccessDialog Komponente** ⏱️ 2-3h
|
**Task 1.6: UploadSuccessDialog Komponente** ⏱️ 2-3h ✅ ERLEDIGT (als inline Content)
|
||||||
- [ ] Dialog-Komponente mit Material-UI erstellen
|
- [x] Success-Content mit Gruppen-ID prominent anzeigen
|
||||||
- [ ] Gruppen-ID prominent anzeigen
|
- [x] Aufklärungstext über Prüfung anzeigen
|
||||||
- [ ] Copy-to-Clipboard für Gruppen-ID
|
- [x] Kontakt-Information einbinden
|
||||||
- [ ] Aufklärungstext über Prüfung anzeigen
|
- [x] Responsive Design
|
||||||
- [ ] Kontakt-Information einbinden
|
- [x] Animation für Success-State
|
||||||
- [ ] Responsive Design
|
- [x] Inline statt Dialog (User-Request)
|
||||||
- [ ] Animation für Success-State
|
|
||||||
|
|
||||||
**Task 1.7: MultiUploadPage Integration** ⏱️ 2-3h
|
**Task 1.7: MultiUploadPage Integration** ⏱️ 2-3h ✅ ERLEDIGT
|
||||||
- [ ] State für Consents hinzufügen
|
- [x] State für Consents hinzufügen
|
||||||
- [ ] ConsentCheckboxes einbinden (vor Upload-Button)
|
- [x] ConsentCheckboxes einbinden (nach DescriptionInput - User-Request)
|
||||||
- [ ] Upload-Button nur aktivieren wenn `workshopConsent = true`
|
- [x] Upload-Button nur aktivieren wenn `workshopConsent = true`
|
||||||
- [ ] Consents-Validation in `handleUpload()`
|
- [x] Consents-Validation in `handleUpload()`
|
||||||
- [ ] Consents an Backend senden
|
- [x] Consents an Backend senden
|
||||||
- [ ] UploadSuccessDialog nach Upload anzeigen
|
- [x] Success-Content nach Upload anzeigen (inline)
|
||||||
- [ ] Gruppen-ID aus Response verarbeiten
|
- [x] Gruppen-ID aus Response verarbeiten
|
||||||
- [ ] Error-Handling für fehlende Zustimmung
|
- [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
|
- [ ] ConsentBadges Komponente erstellen
|
||||||
- [ ] Social Media Icons/Chips anzeigen
|
- [ ] Social Media Icons/Chips anzeigen
|
||||||
- [ ] Badges in Gruppen-Liste integrieren
|
- [ ] Badges in Gruppen-Liste integrieren
|
||||||
|
|
@ -785,7 +784,7 @@ export const uploadImageBatch = async (files, metadata, descriptions, consents)
|
||||||
- [ ] Tooltip mit Consent-Timestamp
|
- [ ] Tooltip mit Consent-Timestamp
|
||||||
- [ ] Visuelle Unterscheidung (Werkstatt-only vs. Social Media)
|
- [ ] 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
|
- [ ] Filter-Dropdown für Consent-Status
|
||||||
- [ ] API-Abfrage mit Filter-Parametern
|
- [ ] API-Abfrage mit Filter-Parametern
|
||||||
- [ ] Export-Button implementieren
|
- [ ] Export-Button implementieren
|
||||||
|
|
|
||||||
82
frontend/src/Components/ComponentUtils/ConsentBadges.js
Normal file
82
frontend/src/Components/ComponentUtils/ConsentBadges.js
Normal file
|
|
@ -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 && (
|
||||||
|
<Tooltip
|
||||||
|
title={`Werkstatt-Anzeige zugestimmt am ${new Date(group.consent_timestamp).toLocaleString('de-DE')}`}
|
||||||
|
arrow
|
||||||
|
>
|
||||||
|
<Chip
|
||||||
|
icon={<WorkIcon />}
|
||||||
|
label="Werkstatt"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
bgcolor: '#4CAF50',
|
||||||
|
color: 'white',
|
||||||
|
'& .MuiChip-icon': { color: 'white' }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Social media consent badges
|
||||||
|
const socialMediaBadges = group.socialMediaConsents?.map(consent => {
|
||||||
|
const IconComponent = ICON_MAP[consent.icon_name] || CheckCircleIcon;
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
key={consent.platform_id}
|
||||||
|
title={`${consent.display_name} Consent am ${new Date(consent.consent_timestamp).toLocaleString('de-DE')}`}
|
||||||
|
arrow
|
||||||
|
>
|
||||||
|
<Chip
|
||||||
|
icon={<IconComponent />}
|
||||||
|
label={consent.display_name}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
borderColor: '#2196F3',
|
||||||
|
color: '#2196F3',
|
||||||
|
'& .MuiChip-icon': { color: '#2196F3' }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no consents at all, show nothing or a neutral indicator
|
||||||
|
if (!group.display_in_workshop && (!group.socialMediaConsents || group.socialMediaConsents.length === 0)) {
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
label="Kein Consent"
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
borderColor: '#757575',
|
||||||
|
color: '#757575'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||||
|
{workshopBadge}
|
||||||
|
{socialMediaBadges}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConsentBadges;
|
||||||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useSortable } from '@dnd-kit/sortable';
|
import { useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
|
import ConsentBadges from './ConsentBadges';
|
||||||
|
|
||||||
import './Css/ImageGallery.css';
|
import './Css/ImageGallery.css';
|
||||||
import { getImageSrc, getGroupPreviewSrc } from '../../Utils/imageUtils';
|
import { getImageSrc, getGroupPreviewSrc } from '../../Utils/imageUtils';
|
||||||
|
|
@ -147,6 +148,14 @@ const ImageGalleryCard = ({
|
||||||
<div className="image-gallery-card-info">
|
<div className="image-gallery-card-info">
|
||||||
<h3>{title}</h3>
|
<h3>{title}</h3>
|
||||||
{subtitle && <p className="image-gallery-card-meta">{subtitle}</p>}
|
{subtitle && <p className="image-gallery-card-meta">{subtitle}</p>}
|
||||||
|
|
||||||
|
{/* Consent Badges (only in moderation mode for groups) */}
|
||||||
|
{mode === 'moderation' && item.groupId && (
|
||||||
|
<div style={{ marginTop: '8px', marginBottom: '8px' }}>
|
||||||
|
<ConsentBadges group={item} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{description && (
|
{description && (
|
||||||
<p className="image-gallery-card-description">{description}</p>
|
<p className="image-gallery-card-description">{description}</p>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { useNavigate } from 'react-router-dom';
|
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 Swal from 'sweetalert2/dist/sweetalert2.js';
|
||||||
import Navbar from '../ComponentUtils/Headers/Navbar';
|
import Navbar from '../ComponentUtils/Headers/Navbar';
|
||||||
import Footer from '../ComponentUtils/Footer';
|
import Footer from '../ComponentUtils/Footer';
|
||||||
import ImageGallery from '../ComponentUtils/ImageGallery';
|
import ImageGallery from '../ComponentUtils/ImageGallery';
|
||||||
import DeletionLogSection from '../ComponentUtils/DeletionLogSection';
|
import DeletionLogSection from '../ComponentUtils/DeletionLogSection';
|
||||||
|
import ConsentBadges from '../ComponentUtils/ConsentBadges';
|
||||||
import { getImageSrc } from '../../Utils/imageUtils';
|
import { getImageSrc } from '../../Utils/imageUtils';
|
||||||
|
|
||||||
const ModerationGroupsPage = () => {
|
const ModerationGroupsPage = () => {
|
||||||
|
|
@ -15,16 +18,53 @@ const ModerationGroupsPage = () => {
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [selectedGroup, setSelectedGroup] = useState(null);
|
const [selectedGroup, setSelectedGroup] = useState(null);
|
||||||
const [showImages, setShowImages] = useState(false);
|
const [showImages, setShowImages] = useState(false);
|
||||||
|
const [consentFilter, setConsentFilter] = useState('all');
|
||||||
|
const [platforms, setPlatforms] = useState([]);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadModerationGroups();
|
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 () => {
|
const loadModerationGroups = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
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) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
|
@ -155,6 +195,41 @@ const ModerationGroupsPage = () => {
|
||||||
navigate(`/moderation/groups/${group.groupId}`);
|
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) {
|
if (loading) {
|
||||||
return <div className="moderation-loading">Lade Gruppen...</div>;
|
return <div className="moderation-loading">Lade Gruppen...</div>;
|
||||||
}
|
}
|
||||||
|
|
@ -194,6 +269,48 @@ const ModerationGroupsPage = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Filter und Export Controls */}
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 2,
|
||||||
|
mb: 3,
|
||||||
|
alignItems: 'center',
|
||||||
|
flexWrap: 'wrap'
|
||||||
|
}}>
|
||||||
|
<FormControl sx={{ minWidth: 250 }} size="small">
|
||||||
|
<InputLabel id="consent-filter-label">
|
||||||
|
<FilterListIcon sx={{ mr: 0.5, fontSize: 18, verticalAlign: 'middle' }} />
|
||||||
|
Consent-Filter
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="consent-filter-label"
|
||||||
|
value={consentFilter}
|
||||||
|
label="Consent-Filter"
|
||||||
|
onChange={(e) => setConsentFilter(e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value="all">Alle Gruppen</MenuItem>
|
||||||
|
<MenuItem value="workshop-only">Nur Werkstatt-Consent</MenuItem>
|
||||||
|
{platforms.map(platform => (
|
||||||
|
<MenuItem key={platform.id} value={platform.platform_name}>
|
||||||
|
{platform.display_name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<FileDownloadIcon />}
|
||||||
|
onClick={exportConsentData}
|
||||||
|
sx={{
|
||||||
|
bgcolor: '#2196F3',
|
||||||
|
'&:hover': { bgcolor: '#1976D2' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Consent-Daten exportieren
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* Wartende Gruppen */}
|
{/* Wartende Gruppen */}
|
||||||
<section className="moderation-section">
|
<section className="moderation-section">
|
||||||
<ImageGallery
|
<ImageGallery
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user