speichern ohne funktion
This commit is contained in:
parent
566eb3aed6
commit
8d2d27c71d
|
|
@ -27,7 +27,7 @@ services:
|
|||
networks:
|
||||
- image-uploader-internal
|
||||
volumes:
|
||||
- app-data:/usr/src/app/data
|
||||
- app-data:/usr/src/app/src/data
|
||||
|
||||
volumes:
|
||||
app-data:
|
||||
|
|
|
|||
|
|
@ -103,7 +103,11 @@ function ImagePreviewGallery({ images, onRemoveImage, onReorderImages }) {
|
|||
<CardMedia
|
||||
component="img"
|
||||
className={classes.imageMedia}
|
||||
image={URL.createObjectURL(image)}
|
||||
image={
|
||||
// If image is a File-like object, create an object URL.
|
||||
// If it's a remote image descriptor, use its remoteUrl or url field.
|
||||
image && image.remoteUrl ? image.remoteUrl : image && image.url ? image.url : URL.createObjectURL(image)
|
||||
}
|
||||
alt={`Vorschau ${index + 1}`}
|
||||
/>
|
||||
|
||||
|
|
@ -128,8 +132,8 @@ function ImagePreviewGallery({ images, onRemoveImage, onReorderImages }) {
|
|||
{index + 1}
|
||||
</div>
|
||||
|
||||
<div className={classes.fileName} title={`${image.name} (${formatFileSize(image.size)})`}>
|
||||
{image.name} • {formatFileSize(image.size)}
|
||||
<div className={classes.fileName} title={`${image.name || image.originalName || ''} ${image.size ? ' • ' + formatFileSize(image.size) : ''}`}>
|
||||
{image.name || image.originalName || 'Bild'}{image.size ? ' • ' + formatFileSize(image.size) : ''}
|
||||
</div>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,57 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import './Css/ModerationPage.css';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
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 '../ComponentUtils/Css/Background.css';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
container: {
|
||||
paddingTop: '20px',
|
||||
paddingBottom: '40px',
|
||||
minHeight: '80vh'
|
||||
},
|
||||
card: {
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
||||
padding: '20px',
|
||||
marginBottom: '20px'
|
||||
},
|
||||
headerText: {
|
||||
fontFamily: 'roboto',
|
||||
fontWeight: '400',
|
||||
fontSize: '28px',
|
||||
textAlign: 'center',
|
||||
marginBottom: '10px',
|
||||
color: '#333333'
|
||||
},
|
||||
subheaderText: {
|
||||
fontFamily: 'roboto',
|
||||
fontWeight: '300',
|
||||
fontSize: '16px',
|
||||
color: '#666666',
|
||||
textAlign: 'center',
|
||||
marginBottom: '30px'
|
||||
},
|
||||
actionButtons: {
|
||||
display: 'flex',
|
||||
gap: '15px',
|
||||
justifyContent: 'center',
|
||||
marginTop: '20px',
|
||||
flexWrap: 'wrap'
|
||||
}
|
||||
});
|
||||
|
||||
const GroupImagesPage = () => {
|
||||
const classes = useStyles();
|
||||
const { groupId } = useParams();
|
||||
const history = useHistory();
|
||||
const [group, setGroup] = useState(null);
|
||||
|
|
@ -10,6 +59,10 @@ const GroupImagesPage = () => {
|
|||
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
|
||||
|
|
@ -22,6 +75,24 @@ const GroupImagesPage = () => {
|
|||
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 {
|
||||
|
|
@ -31,6 +102,7 @@ const GroupImagesPage = () => {
|
|||
|
||||
const handleChange = (field, value) => {
|
||||
setGroup({ ...group, [field]: value });
|
||||
setMetadata(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
|
|
@ -55,11 +127,11 @@ const GroupImagesPage = () => {
|
|||
throw new Error(body.message || 'Speichern fehlgeschlagen');
|
||||
}
|
||||
|
||||
alert('Gruppe erfolgreich aktualisiert');
|
||||
Swal.fire({ icon: 'success', title: 'Gruppe erfolgreich aktualisiert', timer: 1500, showConfirmButton: false });
|
||||
history.push('/moderation');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert('Fehler beim Speichern: ' + e.message);
|
||||
Swal.fire({ icon: 'error', title: 'Fehler beim Speichern', text: e.message });
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
@ -71,53 +143,59 @@ const GroupImagesPage = () => {
|
|||
const res = await fetch(`/groups/${groupId}/images/${imageId}`, { method: 'DELETE' });
|
||||
if (!res.ok) throw new Error('Löschen fehlgeschlagen');
|
||||
// Aktualisiere lokale Ansicht
|
||||
setGroup({ ...group, images: group.images.filter(img => img.id !== imageId), imageCount: group.imageCount - 1 });
|
||||
alert('Bild gelöscht');
|
||||
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);
|
||||
alert('Fehler beim Löschen des Bildes');
|
||||
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));
|
||||
};
|
||||
|
||||
if (loading) return <div className="moderation-loading">Lade Gruppe...</div>;
|
||||
if (error) return <div className="moderation-error">{error}</div>;
|
||||
if (!group) return <div className="moderation-error">Gruppe nicht gefunden</div>;
|
||||
|
||||
return (
|
||||
<div className="group-images-page">
|
||||
<h1>Gruppe bearbeiten</h1>
|
||||
<div className="allContainer">
|
||||
<Navbar />
|
||||
|
||||
<div className="group-edit-form">
|
||||
<label>Titel</label>
|
||||
<input value={group.title || ''} onChange={e => handleChange('title', e.target.value)} />
|
||||
<Container maxWidth="lg" className={classes.container}>
|
||||
<Card className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography className={classes.headerText}>Gruppe bearbeiten</Typography>
|
||||
<Typography className={classes.subheaderText}>{group.title || ''}</Typography>
|
||||
|
||||
<label>Beschreibung</label>
|
||||
<textarea value={group.description || ''} onChange={e => handleChange('description', e.target.value)} />
|
||||
<ImagePreviewGallery images={selectedImages} onRemoveImage={handleRemoveImage} />
|
||||
|
||||
<label>Jahr</label>
|
||||
<input value={group.year || ''} onChange={e => handleChange('year', e.target.value)} />
|
||||
{selectedImages.length > 0 && (
|
||||
<>
|
||||
<DescriptionInput metadata={metadata} onMetadataChange={setMetadata} />
|
||||
|
||||
<label>Ersteller</label>
|
||||
<input value={group.name || ''} onChange={e => handleChange('name', e.target.value)} />
|
||||
<div className={classes.actionButtons}>
|
||||
<Button variant="outlined" onClick={() => history.push('/moderation')}>↩ Zurück</Button>
|
||||
<Button color="primary" variant="contained" onClick={handleSave} disabled={saving}>{saving ? 'Speichern...' : 'Speichern'}</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="edit-actions">
|
||||
<button className="btn btn-secondary" onClick={() => history.push('/moderation')}>↩ Zurück</button>
|
||||
<button className="btn btn-primary" onClick={handleSave} disabled={saving}>{saving ? 'Speichern...' : 'Speichern'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Container>
|
||||
|
||||
<div className="images-grid">
|
||||
{group.images.map(image => (
|
||||
<div key={image.id} className="image-item">
|
||||
<img src={`/download/${image.fileName}`} alt={image.originalName} className="modal-image" />
|
||||
<div className="image-actions">
|
||||
<span className="image-name">{image.originalName}</span>
|
||||
<button className="btn btn-danger btn-sm" onClick={() => handleDeleteImage(image.id)}>🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="footerContainer"><Footer /></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user