IMP: Editierfunktion für Gruppen ergänzt

This commit is contained in:
Matthias Lotz 2025-10-19 18:15:34 +02:00
parent 81c17c1b30
commit 566eb3aed6
6 changed files with 237 additions and 101 deletions

View File

@ -466,87 +466,3 @@ function SlideshowPage() {
---
## 🚀 Deployment-Überlegungen
### Datei-Größe Limits
```javascript
// Backend-Konfiguration erweitern
app.use(fileUpload({
limits: {
fileSize: 50 * 1024 * 1024, // 50MB pro Datei
files: 20 // Max 20 Dateien pro Upload
},
}));
```
### Speicher-Management
- **Cleanup-Job**: Alte Upload-Gruppen nach X Tagen löschen
- **Komprimierung**: Automatische Bildkomprimierung für große Dateien
- **CDN-Integration**: Für bessere Performance bei vielen Bildern
### Sicherheit
- **File-Type Validation**: Nur erlaubte Bildformate
- **Virus-Scanning**: Optional für Produktionsumgebung
- **Rate Limiting**: Upload-Beschränkungen pro IP/User
---
## 📈 Erweiterungs-Möglichkeiten (Zukunft)
### Erweiterte Features
- **Benutzer-Accounts**: Upload-Gruppen Benutzern zuordnen
- **Tagging-System**: Bilder mit Tags versehen
- **Sharing**: Upload-Gruppen per Link teilen
- **Export**: Slideshow als Video oder PDF exportieren
### Slideshow-Features
- **Autoplay**: Automatischer Bildwechsel
- **Übergangs-Effekte**: Fade, Slide, etc.
- **Hintergrundmusik**: Audio-Upload für Slideshows
- **Vollbild-Modus**: Immersive Slideshow-Erfahrung
### Admin-Features
- **Upload-Statistiken**: Dashboard mit Nutzungsmetriken
- **Content-Moderation**: Gemeldete Inhalte prüfen
- **Bulk-Operations**: Mehrere Gruppen gleichzeitig verwalten
---
## ⚡ Quick-Start Implementierung
Für einen schnellen Proof-of-Concept (2-3 Stunden):
1. **Backend**: Erweitere `/upload` Route für Array-Handling
2. **Frontend**: Ändere bestehende Dropzone auf `multiple: true`
3. **Einfache Galerie**: Zeige alle Bilder einer "Session" an
4. **Basis-Slideshow**: Einfache Vor/Zurück-Navigation
Dies würde eine funktionale Basis schaffen, die später ausgebaut werden kann.
---
## 🎯 Erfolgskriterien
### Must-Have
- ✅ Mehrere Bilder gleichzeitig auswählen
- ✅ Beschreibungstext hinzufügen
- ✅ Upload als zusammengehörige Gruppe
- ✅ Einfache Slideshow-Anzeige
- ✅ Mobile-Kompatibilität
### Nice-to-Have
- 🎨 Drag & Drop Reihenfolge ändern
- 📊 Upload-Progress mit Details
- 🖼️ Thumbnail-Navigation in Slideshow
- 💾 Auto-Save der Beschreibung
- 🔄 Batch-Operations (alle entfernen, etc.)
### Future Features
- 👤 User-Management
- 🏷️ Tagging-System
- 📤 Export-Funktionen
- 🎵 Audio-Integration
---
**Fazit**: Die Erweiterung ist gut machbar und baut logisch auf der bestehenden Architektur auf. Der modulare Ansatz ermöglicht schrittweise Implementierung und spätere Erweiterungen.

57
ERWEITERUNG2.md Normal file
View File

@ -0,0 +1,57 @@
# Erweiterung 2: Zusätzliche Funktionen
## Backend
[ ] Erweiterung der API um die Funktion bestehende Daten zu editieren/aktualisieren
[ ] Integration eines Benachrichtigungssystems (E-Mail, Push-Benachrichtigungen) wenn eine neue Slideshow hochgeladen wurde
[ ] Implementierung eines Logging-Systems zur Nachverfolgung von Änderungen und Aktivitäten
# Frontend
[ ] Erweiterung der Benutzeroberfläche um eine Editierfunktion für bestehende Einträge in ModerationPage.js
[ ] In der angezeigten Gruppen soll statt Bilder ansehen Gruppe editieren stehen
[ ] Diese bestehende Ansicht (Bilder ansehen) soll als eigene Seite implementiert werden
## 🚀 Deployment-Überlegungen
### Speicher-Management
- **Komprimierung**: Automatische Bildkomprimierung für große Dateien
### Sicherheit
- **File-Type Validation**: Nur erlaubte Bildformate
- **Virus-Scanning**: Optional für Produktionsumgebung
---
## 📈 Erweiterungs-Möglichkeiten (Zukunft)
### Erweiterte Features
- **Benutzer-Accounts**: Upload-Gruppen Benutzern zuordnen
- **Export**: Slideshow als Video oder PDF exportieren
### Admin-Features
- **Bulk-Operations**: Mehrere Gruppen gleichzeitig verwalten (zum Beispiel löschen)
---
## 🎯 Erfolgskriterien
### Must-Have
- ✅ Mobile-Kompatibilität
### Nice-to-Have
- 🎨 Drag & Drop Reihenfolge ändern
- 📊 Upload-Progress mit Details
- 🖼️ Thumbnail-Navigation in Slideshow
- 🔄 Batch-Operations (alle entfernen, etc.)
### Future Features
- 👤 User-Management
- 🏷️ Tagging-System
- 📤 Export-Funktionen
- 🎵 Audio-Integration
---

View File

@ -137,6 +137,53 @@ router.patch('/groups/:groupId/approve', async (req, res) => {
}
});
// Gruppe bearbeiten (Metadaten aktualisieren)
router.patch('/groups/:groupId', async (req, res) => {
try {
const { groupId } = req.params;
// Erlaubte Felder zum Aktualisieren
const allowed = ['year', 'title', 'description', 'name'];
const updates = {};
for (const field of allowed) {
if (req.body[field] !== undefined) {
updates[field] = req.body[field];
}
}
if (Object.keys(updates).length === 0) {
return res.status(400).json({
error: 'Invalid request',
message: 'Keine gültigen Felder zum Aktualisieren angegeben'
});
}
const updated = await GroupRepository.updateGroup(groupId, updates);
if (!updated) {
return res.status(404).json({
error: 'Group not found',
message: `Gruppe mit ID ${groupId} wurde nicht gefunden`
});
}
res.json({
success: true,
message: 'Gruppe erfolgreich aktualisiert',
groupId: groupId,
updates: updates
});
} catch (error) {
console.error('Error updating group:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Fehler beim Aktualisieren der Gruppe',
details: error.message
});
}
});
// Einzelnes Bild löschen
router.delete('/groups/:groupId/images/:imageId', async (req, res) => {
try {

View File

@ -6,6 +6,7 @@ import UploadedImage from './Components/Pages/UploadedImagePage';
import MultiUploadPage from './Components/Pages/MultiUploadPage';
import SlideshowPage from './Components/Pages/SlideshowPage';
import GroupsOverviewPage from './Components/Pages/GroupsOverviewPage';
import GroupImagesPage from './Components/Pages/GroupImagesPage';
import ModerationPage from './Components/Pages/ModerationPage';
import FZF from './Components/Pages/404Page.js'
@ -16,7 +17,8 @@ function App() {
<Route path="/" exact component={MultiUploadPage} />
<Route path="/upload/:image_url" component={UploadedImage} />
<Route path="/slideshow" component={SlideshowPage} />
<Route path="/groups" component={GroupsOverviewPage} />
<Route path="/groups/:groupId" component={GroupImagesPage} />
<Route path="/groups" component={GroupsOverviewPage} />
<Route path="/moderation" component={ModerationPage} />
<Route component={FZF} />
</Switch>

View File

@ -0,0 +1,124 @@
import React, { useState, useEffect } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import './Css/ModerationPage.css';
const GroupImagesPage = () => {
const { groupId } = useParams();
const history = useHistory();
const [group, setGroup] = useState(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
loadGroup();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [groupId]);
const loadGroup = async () => {
try {
setLoading(true);
const res = await fetch(`/moderation/groups/${groupId}`);
if (!res.ok) throw new Error('Nicht gefunden');
const data = await res.json();
setGroup(data);
} catch (e) {
setError('Fehler beim Laden der Gruppe');
} finally {
setLoading(false);
}
};
const handleChange = (field, value) => {
setGroup({ ...group, [field]: value });
};
const handleSave = async () => {
if (!group) return;
setSaving(true);
try {
const payload = {
title: group.title,
description: group.description,
year: group.year,
name: group.name
};
const res = await fetch(`/groups/${groupId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.message || 'Speichern fehlgeschlagen');
}
alert('Gruppe erfolgreich aktualisiert');
history.push('/moderation');
} catch (e) {
console.error(e);
alert('Fehler beim Speichern: ' + e.message);
} finally {
setSaving(false);
}
};
const handleDeleteImage = async (imageId) => {
if (!window.confirm('Bild wirklich löschen?')) return;
try {
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');
} catch (e) {
console.error(e);
alert('Fehler beim Löschen des Bildes');
}
};
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="group-edit-form">
<label>Titel</label>
<input value={group.title || ''} onChange={e => handleChange('title', e.target.value)} />
<label>Beschreibung</label>
<textarea value={group.description || ''} onChange={e => handleChange('description', e.target.value)} />
<label>Jahr</label>
<input value={group.year || ''} onChange={e => handleChange('year', e.target.value)} />
<label>Ersteller</label>
<input value={group.name || ''} onChange={e => handleChange('name', e.target.value)} />
<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>
<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>
);
};
export default GroupImagesPage;

View File

@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import { Helmet } from 'react-helmet';
import { useHistory } from 'react-router-dom';
import './Css/ModerationPage.css';
const ModerationPage = () => {
@ -8,6 +9,7 @@ const ModerationPage = () => {
const [error, setError] = useState(null);
const [selectedGroup, setSelectedGroup] = useState(null);
const [showImages, setShowImages] = useState(false);
const history = useHistory();
useEffect(() => {
loadModerationGroups();
@ -127,21 +129,9 @@ const ModerationPage = () => {
}
};
const viewGroupImages = async (group) => {
try {
const response = await fetch(`/moderation/groups/${group.group_id}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setSelectedGroup(data);
setShowImages(true);
} catch (error) {
console.error('Fehler beim Laden der Bilder:', error);
alert('Fehler beim Laden der Bilder');
}
// Navigate to the dedicated group images page
const viewGroupImages = (group) => {
history.push(`/groups/${group.group_id}`);
};
if (loading) {
@ -267,7 +257,7 @@ const GroupCard = ({ group, onApprove, onViewImages, onDelete, isPending }) => {
className="btn btn-secondary"
onClick={() => onViewImages(group)}
>
👁 Bilder ansehen
Gruppe editieren
</button>
{isPending ? (