Project-Image-Uploader/frontend/src/Components/Pages/ManagementPortalPage.js

315 lines
9.9 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Container, Card, CardContent, Typography, Box, Button } from '@mui/material';
import Swal from 'sweetalert2';
import NavbarUpload from '../ComponentUtils/Headers/NavbarUpload';
import Footer from '../ComponentUtils/Footer';
import Loading from '../ComponentUtils/LoadingAnimation/Loading';
import ImageGalleryCard from '../ComponentUtils/ImageGalleryCard';
import ConsentBadges from '../ComponentUtils/ConsentBadges';
import MultiImageDropzone from '../ComponentUtils/MultiUpload/MultiImageDropzone';
import ImageDescriptionManager from '../ComponentUtils/ImageDescriptionManager';
import GroupMetadataEditor from '../ComponentUtils/GroupMetadataEditor';
import ConsentManager from '../ComponentUtils/ConsentManager';
import DeleteGroupButton from '../ComponentUtils/DeleteGroupButton';
/**
* ManagementPortalPage - Self-service management for uploaded groups
*
* Modulare Struktur mit individuellen Komponenten:
* - ImageDescriptionManager: Bildbeschreibungen bearbeiten
* - GroupMetadataEditor: Gruppenmetadaten bearbeiten
* - ConsentManager: Einwilligungen verwalten
* - DeleteGroupButton: Gruppe löschen
*/
function ManagementPortalPage() {
const { token } = useParams();
const navigate = useNavigate();
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [group, setGroup] = useState(null);
// Load group data
const loadGroup = async () => {
try {
setLoading(true);
setError(null);
const res = await fetch(`/api/manage/${token}`);
if (res.status === 404) {
setError('Ungültiger oder abgelaufener Verwaltungslink');
return;
}
if (res.status === 429) {
setError('Zu viele Anfragen. Bitte versuchen Sie es später erneut.');
return;
}
if (!res.ok) {
throw new Error('Fehler beim Laden der Gruppe');
}
const response = await res.json();
const data = response.data || response;
// Transform data
const transformedData = {
...data,
displayInWorkshop: data.displayInWorkshop || data.display_in_workshop,
consentTimestamp: data.consentTimestamp || data.consent_timestamp,
consents: {
workshopConsent: (data.displayInWorkshop === 1 || data.display_in_workshop === 1),
socialMediaConsents: (data.socialMediaConsents || [])
.filter(c => c.consented === 1 && c.revoked === 0)
.map(c => ({ platformId: c.platform_id, consented: true }))
},
metadata: {
year: data.year || new Date().getFullYear(),
title: data.title || '',
description: data.description || '',
name: data.name || ''
},
images: (data.images || []).map(img => ({
...img,
remoteUrl: `/download/${img.fileName}`,
originalName: img.originalName || img.fileName,
id: img.id,
imageDescription: img.imageDescription || ''
}))
};
setGroup(transformedData);
} catch (e) {
console.error('Error loading group:', e);
setError('Fehler beim Laden der Gruppe');
} finally {
setLoading(false);
}
};
useEffect(() => {
if (token) {
loadGroup();
}
}, [token]); // eslint-disable-line react-hooks/exhaustive-deps
// Handle adding new images
const handleImagesSelected = async (newImages) => {
try {
const formData = new FormData();
newImages.forEach(file => {
formData.append('images', file);
});
const res = await fetch(`/api/manage/${token}/images`, {
method: 'POST',
body: formData
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error || 'Fehler beim Hochladen');
}
await Swal.fire({
icon: 'success',
title: 'Bilder hinzugefügt',
text: `${newImages.length} Bild(er) wurden erfolgreich hinzugefügt.`,
timer: 2000,
showConfirmButton: false
});
// Reload group data
await loadGroup();
} catch (error) {
console.error('Error adding images:', error);
Swal.fire({
icon: 'error',
title: 'Fehler',
text: error.message || 'Bilder konnten nicht hinzugefügt werden'
});
}
};
const handleReorder = async (newOrder) => {
if (!group || !group.groupId) {
console.error('No groupId available for reordering');
return;
}
try {
const imageIds = newOrder.map(img => img.id);
// Use token-based management API
const response = await fetch(`/api/manage/${token}/reorder`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ imageIds: imageIds })
});
if (!response.ok) {
throw new Error('Reihenfolge konnte nicht gespeichert werden');
}
await Swal.fire({
icon: 'success',
title: 'Gespeichert',
text: 'Die neue Reihenfolge wurde gespeichert.',
timer: 1500,
showConfirmButton: false
});
await loadGroup();
} catch (error) {
console.error('Error reordering images:', error);
Swal.fire({
icon: 'error',
title: 'Fehler',
text: error.message || 'Reihenfolge konnte nicht gespeichert werden'
});
}
};
if (loading) {
return (
<div className="allContainer">
<NavbarUpload />
<Container maxWidth="lg" sx={{ pt: '20px', pb: '40px', minHeight: '80vh', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Loading />
</Container>
<Footer />
</div>
);
}
if (error) {
return (
<div className="allContainer">
<NavbarUpload />
<Container maxWidth="lg" sx={{ pt: '20px', pb: '40px', minHeight: '80vh' }}>
<Card sx={{ borderRadius: '12px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)', p: '20px', textAlign: 'center' }}>
<Typography variant="h5" color="error" gutterBottom>
{error}
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
{error}
</Typography>
<Button variant="contained" onClick={() => navigate('/')}>
Zur Startseite
</Button>
</Card>
</Container>
<Footer />
</div>
);
}
return (
<div className="allContainer">
<NavbarUpload />
<Container maxWidth="lg" sx={{ pt: '20px', pb: '40px', minHeight: '80vh' }}>
<Card sx={{ borderRadius: '12px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)', p: '20px', mb: '20px' }}>
<CardContent>
<Typography sx={{ fontFamily: 'roboto', fontWeight: 400, fontSize: '28px', textAlign: 'center', mb: '10px', color: '#333333' }}>
Mein Upload verwalten
</Typography>
<Typography sx={{ fontFamily: 'roboto', fontWeight: 300, fontSize: '16px', color: '#666666', textAlign: 'center', mb: '30px' }}>
Hier können Sie Ihre hochgeladenen Bilder verwalten, Metadaten bearbeiten und Einwilligungen ändern.
</Typography>
{/* Group Overview */}
{group && (
<Box sx={{ mb: 3 }}>
<ImageGalleryCard
item={group}
showActions={false}
isPending={!group.approved}
mode="group"
hidePreview={true}
/>
<Box sx={{ mt: 2 }}>
<Typography variant="subtitle2" gutterBottom sx={{ fontWeight: 600 }}>
Erteilte Einwilligungen:
</Typography>
<ConsentBadges group={group} />
</Box>
</Box>
)}
{/* Add Images Dropzone */}
<Box sx={{ mb: 3 }}>
<Typography variant="h6" gutterBottom sx={{ fontWeight: 600 }}>
Weitere Bilder hinzufügen
</Typography>
<MultiImageDropzone
onImagesSelected={handleImagesSelected}
selectedImages={[]}
/>
</Box>
{/* Image Descriptions Manager */}
{group && group.images && group.images.length > 0 && (
<Box sx={{ mb: 3 }}>
<ImageDescriptionManager
images={group.images}
token={token}
enableReordering={true}
onReorder={handleReorder}
onRefresh={loadGroup}
/>
</Box>
)}
{/* Group Metadata Editor */}
{group && (
<Box sx={{ mb: 3 }}>
<GroupMetadataEditor
initialMetadata={group.metadata}
token={token}
onRefresh={loadGroup}
/>
</Box>
)}
{/* Consent Manager */}
{group && (
<Box sx={{ mb: 3 }}>
<ConsentManager
initialConsents={group.consents}
token={token}
groupId={group.groupId}
onRefresh={loadGroup}
/>
</Box>
)}
{/* Delete Group Button */}
{group && (
<Box sx={{ mt: 4, display: 'flex', justifyContent: 'center' }}>
<DeleteGroupButton
token={token}
groupName={group.title || group.name || 'diese Gruppe'}
/>
</Box>
)}
</CardContent>
</Card>
</Container>
<div className="footerContainer">
<Footer />
</div>
</div>
);
}
export default ManagementPortalPage;