Project-Image-Uploader/frontend/src/Components/ComponentUtils/ImageDescriptionManager.js
matthias.lotz bd7bdac000 refactor: Complete UI refactoring with modular components
- Refactored ManagementPortalPage, MultiUploadPage, ModerationGroupImagesPage
- Created reusable modular components with mode support:
  * ImageDescriptionManager (manage/moderate modes)
  * GroupMetadataEditor (edit/upload/moderate modes)
  * ConsentManager (edit/upload modes)
- Replaced Material-UI Buttons with HTML buttons + CSS classes
- Fixed image descriptions upload (preview ID to filename mapping)
- Reduced ModerationGroupImagesPage from 281 to 107 lines
- Updated ModerationGroupsPage and GroupsOverviewPage button styles
- All pages now use consistent Paper boxes with headings
- Inline Material-UI Alerts instead of SweetAlert2 popups (except destructive actions)
- Icons: 💾 save, ↩ discard, 🗑️ delete consistently used
2025-11-15 18:17:14 +01:00

187 lines
5.1 KiB
JavaScript

import React, { useState } from 'react';
import { Box, Typography, Paper } from '@mui/material';
import Swal from 'sweetalert2';
import ImageGallery from './ImageGallery';
/**
* Manages image descriptions with save functionality
* Wraps ImageGallery and provides batch save for all descriptions
*
* @param mode - 'manage' (uses token) or 'moderate' (uses groupId)
*/
function ImageDescriptionManager({
images,
token,
groupId,
enableReordering = false,
onReorder,
onRefresh,
mode = 'manage'
}) {
const [imageDescriptions, setImageDescriptions] = useState({});
const [originalDescriptions, setOriginalDescriptions] = useState({});
const [isEditMode, setIsEditMode] = useState(false);
const [saving, setSaving] = useState(false);
// Initialize descriptions from images
React.useEffect(() => {
if (images && images.length > 0) {
const descriptions = {};
images.forEach(img => {
descriptions[img.id] = img.imageDescription || '';
});
setImageDescriptions(descriptions);
setOriginalDescriptions(descriptions);
}
}, [images]);
const handleDescriptionChange = (imageId, description) => {
setImageDescriptions(prev => ({
...prev,
[imageId]: description
}));
};
const hasChanges = () => {
return JSON.stringify(imageDescriptions) !== JSON.stringify(originalDescriptions);
};
const handleSave = async () => {
if (!hasChanges()) {
Swal.fire({
icon: 'info',
title: 'Keine Änderungen',
text: 'Es wurden keine Änderungen an den Beschreibungen vorgenommen.'
});
return;
}
try {
setSaving(true);
// Build descriptions array for API
const descriptions = Object.entries(imageDescriptions).map(([imageId, description]) => ({
imageId: parseInt(imageId),
description: description || null
}));
// Different API endpoints for manage vs moderate
const endpoint = mode === 'moderate'
? `/groups/${groupId}/images/batch-description`
: `/api/manage/${token}/images/descriptions`;
const method = mode === 'moderate' ? 'PATCH' : 'PUT';
const res = await fetch(endpoint, {
method: method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ descriptions })
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error || 'Fehler beim Speichern der Beschreibungen');
}
await Swal.fire({
icon: 'success',
title: 'Gespeichert',
text: 'Bildbeschreibungen wurden erfolgreich aktualisiert.',
timer: 2000,
showConfirmButton: false
});
// Update original descriptions
setOriginalDescriptions(imageDescriptions);
// Refresh data if callback provided
if (onRefresh) {
await onRefresh();
}
} catch (error) {
console.error('Error saving descriptions:', error);
Swal.fire({
icon: 'error',
title: 'Fehler',
text: error.message || 'Beschreibungen konnten nicht gespeichert werden'
});
} finally {
setSaving(false);
}
};
const handleDiscard = () => {
setImageDescriptions(originalDescriptions);
setIsEditMode(false);
};
const handleEditToggle = () => {
if (isEditMode && hasChanges()) {
// Warn user if trying to leave edit mode with unsaved changes
Swal.fire({
icon: 'warning',
title: 'Ungespeicherte Änderungen',
text: 'Du hast ungespeicherte Änderungen. Bitte speichere oder verwerfe sie zuerst.',
confirmButtonText: 'OK'
});
return; // Don't toggle edit mode
}
if (isEditMode) {
// Discard changes when leaving edit mode without saving
setImageDescriptions({ ...originalDescriptions });
}
setIsEditMode(!isEditMode);
};
return (
<Paper
sx={{
p: 3,
borderRadius: '12px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
border: '2px solid #e0e0e0'
}}
>
{/* Component Header */}
<Typography variant="h6" gutterBottom sx={{ fontWeight: 600, mb: 2 }}>
Bildbeschreibungen
</Typography>
<ImageGallery
items={images}
mode="preview"
isEditMode={isEditMode}
onEditMode={handleEditToggle}
enableReordering={enableReordering}
onReorder={onReorder}
imageDescriptions={imageDescriptions}
onDescriptionChange={handleDescriptionChange}
/>
{hasChanges() && (
<Box sx={{ mt: 2, display: 'flex', gap: 1, justifyContent: 'center' }}>
<button
className="btn btn-success"
onClick={handleSave}
disabled={saving}
>
{saving ? '⏳ Speichern...' : '💾 Beschreibungen speichern'}
</button>
<button
className="btn btn-secondary"
onClick={handleDiscard}
disabled={saving}
>
Verwerfen
</button>
</Box>
)}
</Paper>
);
}
export default ImageDescriptionManager;