Project-Image-Uploader/FeatureRequests/done/FEATURE_PLAN-image-description.md

20 KiB

Feature Plan: Image Description (Bildbeschreibung)

Branch: feature/ImageDescription
Datum: 7. November 2025
Status: Implementiert (bereit für Testing)


📋 Übersicht

Implementierung einer individuellen Bildbeschreibung für jedes hochgeladene Bild. Benutzer können optional einen kurzen Text (max. 200 Zeichen) für jedes Bild hinzufügen, der dann in der Slideshow und in der GroupsOverviewPage angezeigt wird.

Hauptänderungen

  1. Button-Änderung: "Sort" Button wird durch "Edit" Button ersetzt in ImageGalleryCard.js
  2. Edit-Modus: Aktiviert Textfelder unter jedem Bild zur Eingabe von Bildbeschreibungen
  3. Datenbank-Erweiterung: Neues Feld image_description in der images Tabelle
  4. Backend-API: Neue Endpoints zum Speichern und Abrufen von Bildbeschreibungen
  5. Frontend-Integration: Edit-Modus in MultiUploadPage.js und ModerationGroupImagesPage.js
  6. Slideshow-Integration: Anzeige der Bildbeschreibungen während der Slideshow
  7. Groups-Overview: Anzeige von Bildbeschreibungen in der öffentlichen Übersicht

🎯 Anforderungen

Funktionale Anforderungen

  • Benutzer können für jedes Bild eine optionale Beschreibung eingeben
  • Maximale Länge: 200 Zeichen
  • Edit-Button ersetzt Sort-Button in Preview-Modus
  • Edit-Modus zeigt Textfelder unter allen Bildern gleichzeitig
  • Vorbefüllung mit Original-Dateinamen als Platzhalter
  • Funktioniert in MultiUploadPage.js und ModerationGroupImagesPage.js
  • NICHT in GroupsOverviewPage.js (nur Anzeige, kein Edit)
  • Bildbeschreibungen werden in Slideshow angezeigt
  • Bildbeschreibungen werden in GroupsOverviewPage angezeigt
  • Speicherung in Datenbank (persistente Speicherung)

Nicht-Funktionale Anforderungen

  • Performance: Keine merkbare Verzögerung beim Laden von Bildern
  • UX: Intuitive Bedienung des Edit-Modus
  • Mobile-Optimierung: Touch-freundliche Textfelder
  • Validierung: Client- und Server-seitige Längenbegrenzung
  • Backward-Kompatibilität: Bestehende Bilder ohne Beschreibung funktionieren weiterhin

🗄️ Datenbank-Schema Änderungen

Migration: 004_add_image_description.sql

-- Add image_description column to images table
ALTER TABLE images ADD COLUMN image_description TEXT;

-- Create index for better performance when filtering/searching
CREATE INDEX IF NOT EXISTS idx_images_description ON images(image_description);

Aktualisiertes Schema (images Tabelle)

CREATE TABLE images (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    group_id TEXT NOT NULL,
    file_name TEXT NOT NULL,
    original_name TEXT NOT NULL,
    file_path TEXT NOT NULL,
    upload_order INTEGER NOT NULL,
    file_size INTEGER,
    mime_type TEXT,
    preview_path TEXT,
    image_description TEXT,  -- ← NEU: Optional, max 200 Zeichen
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (group_id) REFERENCES groups(group_id) ON DELETE CASCADE
);

🔧 Backend-Änderungen

1. Datenbank-Migration

Datei: backend/src/database/migrations/004_add_image_description.sql

  • Fügt image_description Spalte zur images Tabelle hinzu
  • Erstellt Index für Performance-Optimierung

2. API-Erweiterungen

A) Bestehende Endpoints erweitern

POST /api/upload/batch

  • Akzeptiert imageDescriptions Array im Request Body
  • Format: [{ fileName: string, description: string }, ...]
  • Speichert Beschreibungen beim Upload

GET /api/groups/:groupId

  • Gibt image_description für jedes Bild zurück
  • Backward-kompatibel (null/leer für alte Bilder)

GET /moderation/groups/:groupId

  • Gibt image_description für jedes Bild zurück

B) Neue Endpoints

PATCH /groups/:groupId/images/:imageId

  • Aktualisiert image_description für einzelnes Bild
  • Payload: { image_description: string }
  • Validierung: Max 200 Zeichen

PATCH /groups/:groupId/images/batch-description

  • Aktualisiert mehrere Bildbeschreibungen auf einmal
  • Payload: { descriptions: [{ imageId: number, description: string }, ...] }
  • Effizienter als einzelne Requests

3. Repository & Service Layer

GroupRepository.js - Neue Methoden:

async updateImageDescription(imageId, description)
async updateBatchImageDescriptions(groupId, descriptions)
async getImagesByGroupId(groupId) // Erweitert um image_description

DatabaseManager.js - Query-Erweiterungen:

  • SELECT Queries inkludieren image_description
  • INSERT Queries akzeptieren image_description
  • UPDATE Queries für Beschreibungs-Updates

🎨 Frontend-Änderungen

1. ImageGalleryCard.js

Änderung: Button "Sort" → "Edit"

// ALT (Zeile 174-179):
<button 
    className="btn btn-secondary btn-sm" 
    disabled
>
    Sort
</button>

// NEU:
<button 
    className="btn btn-primary btn-sm"
    onClick={() => onEditMode?.(true)}
>
    ✏️ Edit
</button>

Neue Props:

  • onEditMode: Callback-Funktion zum Aktivieren des Edit-Modus
  • isEditMode: Boolean für aktuellen Edit-Status
  • imageDescription: String mit Bildbeschreibung
  • onDescriptionChange: Callback für Beschreibungsänderungen

Edit-Modus UI:

{isEditMode && mode === 'preview' && (
    <div className="image-description-edit">
        <textarea
            value={imageDescription || ''}
            onChange={(e) => onDescriptionChange(itemId, e.target.value)}
            placeholder={`Beschreibung für ${originalName}...`}
            maxLength={200}
            rows={2}
        />
        <span className="char-counter">
            {(imageDescription || '').length}/200
        </span>
    </div>
)}

2. ImageGallery.js

Neue Props durchreichen:

  • isEditMode
  • onEditMode
  • onDescriptionChange

Pass-through zu ImageGalleryCard:

<ImageGalleryCard
    // ... existing props
    isEditMode={isEditMode}
    onEditMode={onEditMode}
    imageDescription={item.imageDescription}
    onDescriptionChange={onDescriptionChange}
/>

3. MultiUploadPage.js

State-Erweiterung:

const [isEditMode, setIsEditMode] = useState(false);
const [imageDescriptions, setImageDescriptions] = useState({});

Neue Handler:

const handleDescriptionChange = (imageId, description) => {
    setImageDescriptions(prev => ({
        ...prev,
        [imageId]: description.slice(0, 200) // Enforce max length
    }));
};

const handleEditMode = (enabled) => {
    setIsEditMode(enabled);
};

Upload-Logik erweitern:

// In handleUpload()
const descriptionsArray = selectedImages.map(img => ({
    fileName: img.name,
    description: imageDescriptions[img.id] || ''
}));

const result = await uploadImageBatch(
    filesToUpload, 
    metadata,
    descriptionsArray // ← NEU
);

Edit-Mode Toggle:

{isEditMode && (
    <Box sx={{ textAlign: 'center', my: 2 }}>
        <Button
            variant="contained"
            color="success"
            onClick={() => setIsEditMode(false)}
        >
             Beschreibungen fertig
        </Button>
    </Box>
)}

4. ModerationGroupImagesPage.js

State-Erweiterung:

const [isEditMode, setIsEditMode] = useState(false);
const [imageDescriptions, setImageDescriptions] = useState({});

Load-Funktion erweitern:

// In loadGroup()
if (data.images && data.images.length > 0) {
    const mapped = data.images.map(img => ({ ... }));
    setSelectedImages(mapped);
    
    // Initialize descriptions from server
    const descriptions = {};
    data.images.forEach(img => {
        if (img.imageDescription) {
            descriptions[img.id] = img.imageDescription;
        }
    });
    setImageDescriptions(descriptions);
}

Save-Funktion erweitern:

const handleSaveDescriptions = async () => {
    try {
        const descriptions = Object.entries(imageDescriptions).map(([id, desc]) => ({
            imageId: parseInt(id),
            description: desc
        }));
        
        const res = await fetch(`/groups/${groupId}/images/batch-description`, {
            method: 'PATCH',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ descriptions })
        });
        
        if (!res.ok) throw new Error('Speichern fehlgeschlagen');
        
        Swal.fire({ 
            icon: 'success', 
            title: 'Beschreibungen gespeichert', 
            timer: 1500 
        });
        setIsEditMode(false);
    } catch (e) {
        Swal.fire({ icon: 'error', title: 'Fehler beim Speichern' });
    }
};

5. SlideshowPage.js

Erweitere Bild-Anzeige:

{currentImage && (
    <div className="slideshow-container">
        <img 
            src={getImageSrc(currentImage)} 
            alt={currentImage.originalName}
            className="slideshow-image"
        />
        
        {/* NEU: Beschreibung anzeigen */}
        {currentImage.imageDescription && (
            <div className="slideshow-description">
                <p>{currentImage.imageDescription}</p>
            </div>
        )}
        
        {/* Bestehende Metadaten */}
        <div className="slideshow-metadata">
            <h2>{currentGroup.title}</h2>
            {currentGroup.name && <p>{currentGroup.name}</p>}
        </div>
    </div>
)}

CSS für Slideshow-Beschreibung:

.slideshow-description {
    position: absolute;
    bottom: 100px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(0, 0, 0, 0.7);
    padding: 15px 30px;
    border-radius: 8px;
    max-width: 80%;
    text-align: center;
}

.slideshow-description p {
    color: white;
    font-size: 18px;
    margin: 0;
    line-height: 1.4;
}

6. GroupsOverviewPage.js

Keine Edit-Funktion, nur Anzeige

Beim Anzeigen einzelner Bilder einer Gruppe:

{image.imageDescription && (
    <p className="image-description">
        {image.imageDescription}
    </p>
)}

7. CSS-Erweiterungen

ImageGallery.css

/* Edit-Mode Textarea Styles */
.image-description-edit {
    padding: 10px;
    border-top: 1px solid #e0e0e0;
}

.image-description-edit textarea {
    width: 100%;
    padding: 8px;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-family: 'Roboto', sans-serif;
    font-size: 14px;
    resize: vertical;
    min-height: 50px;
}

.image-description-edit textarea:focus {
    outline: none;
    border-color: #4CAF50;
    box-shadow: 0 0 5px rgba(76, 175, 80, 0.3);
}

.image-description-edit .char-counter {
    display: block;
    text-align: right;
    font-size: 12px;
    color: #666;
    margin-top: 4px;
}

.image-description-edit .char-counter.limit-reached {
    color: #f44336;
    font-weight: bold;
}

/* Edit Button Styles */
.btn-edit-mode {
    background: linear-gradient(45deg, #2196F3 30%, #1976D2 90%);
    color: white;
    border: none;
}

.btn-edit-mode:hover {
    background: linear-gradient(45deg, #1976D2 30%, #2196F3 90%);
}

/* Display-only description */
.image-description-display {
    padding: 10px;
    border-top: 1px solid #e0e0e0;
    font-size: 14px;
    color: #555;
    font-style: italic;
}

🔄 Utils & Services

batchUpload.js

Signatur-Änderung:

// ALT:
export const uploadImageBatch = async (files, metadata) => { ... }

// NEU:
export const uploadImageBatch = async (files, metadata, descriptions = []) => {
    const formData = new FormData();
    
    // Files
    files.forEach(file => {
        formData.append('images', file);
    });
    
    // Metadata
    formData.append('metadata', JSON.stringify(metadata));
    
    // Descriptions (NEU)
    formData.append('descriptions', JSON.stringify(descriptions));
    
    // ... rest of upload logic
}

🧪 Testing-Strategie

Backend-Tests

  1. Datenbank-Migration

    • Migration läuft ohne Fehler
    • Spalte wird korrekt hinzugefügt
    • Bestehende Daten bleiben intakt
  2. API-Endpoints

    • Upload mit Beschreibungen funktioniert
    • Upload ohne Beschreibungen funktioniert (Backward-Kompatibilität)
    • Validierung: Max 200 Zeichen
    • Batch-Update funktioniert
    • Einzelnes Update funktioniert
    • GET Requests geben Beschreibungen zurück

Frontend-Tests

  1. MultiUploadPage.js

    • Edit-Button aktiviert Edit-Modus
    • Textfelder erscheinen unter allen Bildern
    • Zeichenzähler funktioniert
    • Max-Länge wird enforced
    • Upload sendet Beschreibungen mit
    • Platzhalter zeigt Dateinamen
  2. ModerationGroupImagesPage.js

    • Beschreibungen werden vom Server geladen
    • Edit-Modus aktivierbar
    • Speichern funktioniert
    • Optimistic Updates funktionieren
  3. SlideshowPage.js

    • Beschreibungen werden angezeigt
    • Layout ist responsive
    • Keine Beschreibung = kein Element
  4. GroupsOverviewPage.js

    • Beschreibungen werden angezeigt (falls vorhanden)
    • Kein Edit-Button sichtbar

Manuelle Tests

  • Upload mehrerer Bilder mit verschiedenen Beschreibungen
  • Upload ohne Beschreibungen
  • Bearbeiten bestehender Gruppen
  • Slideshow mit Beschreibungen testen
  • Mobile-Ansicht testen
  • Performance mit vielen Bildern testen

📝 Implementation TODO

Phase 1: Backend Foundation

  • Task 1.1: Datenbank-Migration erstellen

    • 004_add_image_description.sql erstellen
    • Migration in DatabaseManager.js registrieren
    • [ x Lokale DB testen
  • Task 1.2: Repository-Layer erweitern

    • updateImageDescription() in GroupRepository.js
    • updateBatchImageDescriptions() in GroupRepository.js
    • getImagesByGroupId() erweitern für image_description
  • Task 1.3: API-Routes implementieren

    • PATCH /groups/:groupId/images/:imageId in routes/groups.js
    • PATCH /groups/:groupId/images/batch-description in routes/groups.js
    • Validierung hinzufügen (max 200 Zeichen)
    • GET Routes erweitern (image_description returnen)
  • Task 1.4: Upload-Route erweitern

    • batchUpload.js Route akzeptiert descriptions Parameter
    • Speichere Beschreibungen beim Upload
    • Backward-Kompatibilität testen

Phase 2: Frontend Core Components

  • Task 2.1: ImageGalleryCard.js anpassen

    • "Sort" Button durch "Edit" Button ersetzen
    • Edit-Modus UI implementieren (Textarea)
    • Props hinzufügen: isEditMode, onEditMode, imageDescription, onDescriptionChange
    • Zeichenzähler implementieren
    • Validierung (max 200 Zeichen)
  • Task 2.2: ImageGallery.js erweitern

    • Neue Props durchreichen
    • Edit-Modus State-Management
  • Task 2.3: CSS-Styles hinzufügen

    • ImageGallery.css erweitern
    • Textarea-Styles
    • Zeichenzähler-Styles
    • Edit-Button-Styles
    • Mobile-Optimierung

Phase 3: Upload Flow Integration

  • Task 3.1: MultiUploadPage.js erweitern

    • State für Edit-Modus hinzufügen
    • State für Beschreibungen hinzufügen
    • Handler für Edit-Modus implementieren
    • Handler für Beschreibungsänderungen implementieren
    • Upload-Logik erweitern (Beschreibungen mitschicken)
    • Edit-Mode Toggle UI hinzufügen
  • Task 3.2: batchUpload.js erweitern

    • Funktionssignatur anpassen (descriptions Parameter)
    • FormData um Beschreibungen erweitern
    • Error-Handling

Phase 4: Moderation Integration

  • Task 4.1: ModerationGroupImagesPage.js erweitern
    • State für Edit-Modus hinzufügen
    • State für Beschreibungen hinzufügen
    • loadGroup() erweitern (Beschreibungen laden)
    • Handler für Beschreibungsänderungen implementieren
    • handleSaveDescriptions() implementieren
    • Edit-Mode Toggle UI hinzufügen
    • Optimistic Updates

Phase 5: Slideshow Integration

  • Task 5.1: SlideshowPage.js erweitern

    • Beschreibungs-Anzeige UI implementieren
    • CSS für Slideshow-Beschreibung
    • Responsive Design
    • Conditional Rendering (nur wenn Beschreibung vorhanden)
  • Task 5.2: Slideshow-Styles

    • .slideshow-description CSS
    • Overlay-Styling
    • Animation (optional)
    • Mobile-Ansicht

Phase 6: Groups Overview Integration

  • Task 6.1: GroupsOverviewPage.js erweitern
    • Beschreibungs-Anzeige bei Bilddetails
    • CSS für Beschreibungs-Display
    • Kein Edit-Button (nur Anzeige)

Phase 7: Testing & Refinement

  • Task 7.1: Backend-Tests

    • API-Endpoints testen
    • Datenbank-Migration testen
    • Validierung testen
  • Task 7.2: Frontend-Tests

    • Upload-Flow testen
    • Edit-Flow testen
    • Slideshow testen
    • Mobile-Tests
  • Task 7.3: Integration-Tests

    • End-to-End Upload-to-Slideshow Test
    • Edit in Moderation Test
    • Performance-Test mit vielen Bildern
  • Task 7.4: Bug Fixes & Polish

    • UI/UX Verbesserungen
    • Error-Handling verfeinern
    • Code-Cleanup

Phase 8: Documentation & Deployment

  • Task 8.1: README.md aktualisieren

    • Feature dokumentieren
    • API-Endpoints dokumentieren
    • Screenshots hinzufügen (optional)
  • Task 8.2: CHANGELOG.md erweitern

    • Feature hinzufügen
    • Breaking Changes auflisten (falls vorhanden)
  • Task 8.3: Migration Guide

    • Deployment-Anweisungen
    • Datenbank-Migration Schritte
  • Task 8.4: Final Testing

    • Production-Build testen
    • Docker-Container testen
    • Backup-Restore testen

🚀 Deployment-Plan

Schritte für Production Deployment

  1. Datenbank-Migration ausführen

    # Backup erstellen
    docker cp image-uploader-backend:/usr/src/app/src/data/db/image_uploader.db ./backup_before_migration.db
    
    # Migration ausführen
    docker exec -it image-uploader-backend npm run migrate
    
  2. Backend neu deployen

    docker compose -f docker/prod/docker-compose.yml up -d --build backend
    
  3. Frontend neu deployen

    docker compose -f docker/prod/docker-compose.yml up -d --build frontend
    
  4. Testen

    • Upload mit Beschreibungen
    • Slideshow mit Beschreibungen
    • Moderation Edit-Modus

Rollback-Plan

Falls Probleme auftreten:

# Stoppe Container
docker compose -f docker/prod/docker-compose.yml down

# Restore Datenbank-Backup
docker cp ./backup_before_migration.db image-uploader-backend:/usr/src/app/src/data/db/image_uploader.db

# Checkout previous version
git checkout main

# Rebuild & Restart
docker compose -f docker/prod/docker-compose.yml up -d --build

📊 Erfolgskriterien

  • Benutzer können Bildbeschreibungen beim Upload hinzufügen
  • Benutzer können Bildbeschreibungen in Moderation bearbeiten
  • Beschreibungen werden in Slideshow angezeigt
  • Beschreibungen werden in Groups-Overview angezeigt
  • Keine Performance-Einbußen
  • Mobile-freundlich
  • Backward-kompatibel mit bestehenden Uploads
  • Keine Breaking Changes für bestehende Features

🔮 Zukünftige Erweiterungen (Optional)

  • 🔄 Rich-Text Editor für Beschreibungen (Markdown?)
  • 🔄 Mehrsprachige Beschreibungen
  • 🔄 Auto-Vervollständigung basierend auf Bild-Metadaten (EXIF)
  • 🔄 KI-generierte Beschreibungen (Bild-Erkennung)
  • 🔄 Suche nach Bildern via Beschreibung
  • 🔄 Bulk-Edit für Beschreibungen (Regex-Replace, etc.)
  • 🔄 Export von Beschreibungen als CSV/JSON

📝 Notizen

  • Original-Dateinamen als Platzhalter nutzen für bessere UX
  • Validierung sowohl Client- als auch Server-seitig
  • Edit-Modus sollte klar visuell erkennbar sein
  • Speichern sollte optimistisch erfolgen (sofortiges Feedback)
  • Fehler-Handling mit User-freundlichen Nachrichten

Erstellt von: GitHub Copilot
Letzte Aktualisierung: 7. November 2025