Files
serv_benchmark/docs/FEATURE_PRIMARY_PHOTO_TOGGLE.md
Gilles Soulier c67befc549 addon
2026-01-05 16:08:01 +01:00

8.5 KiB
Executable File
Raw Blame History

Fonctionnalité : Icône cliquable pour photo principale

🎯 Objectif

Ajouter une icône cliquable en bas à gauche de chaque photo dans la galerie pour permettre de définir facilement quelle photo sera utilisée comme vignette principale (thumbnail).

Implémentation

1. Interface utilisateur

Fichier : frontend/js/peripheral-detail.js (lignes 108-112)

<button class="photo-primary-toggle ${photo.is_primary ? 'active' : ''}"
        onclick="setPrimaryPhoto(${photo.id})"
        title="${photo.is_primary ? 'Photo principale' : 'Définir comme photo principale'}">
    <i class="fas fa-${photo.is_primary ? 'check-circle' : 'circle'}"></i>
</button>

Icônes :

  • circle (non cochée) - Photo normale
  • check-circle (cochée) - Photo principale

2. Style CSS

Fichier : frontend/css/peripherals.css (lignes 764-803)

/* Photo Primary Toggle */
.photo-primary-toggle {
    position: absolute;
    bottom: 8px;
    left: 8px;
    background: rgba(0, 0, 0, 0.7);
    border: 2px solid #666;
    border-radius: 50%;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.2s ease;
    color: #999;
    font-size: 16px;
    z-index: 10;
}

.photo-primary-toggle:hover {
    background: rgba(0, 0, 0, 0.85);
    border-color: #66d9ef;
    color: #66d9ef;
    transform: scale(1.1);
}

.photo-primary-toggle.active {
    background: rgba(102, 217, 239, 0.2);
    border-color: #66d9ef;
    color: #66d9ef;
}

.photo-primary-toggle.active:hover {
    background: rgba(102, 217, 239, 0.3);
}

.photo-item {
    position: relative;
}

Caractéristiques :

  • Position : Coin inférieur gauche de chaque photo
  • Taille : 32×32px, bouton rond
  • États : normal (gris), hover (bleu), active (bleu clair)
  • Effet : Scale 1.1 au hover
  • Z-index élevé pour rester au-dessus de l'image

3. Fonction JavaScript

Fichier : frontend/js/peripheral-detail.js (lignes 239-252)

// Set photo as primary
async function setPrimaryPhoto(photoId) {
    try {
        await apiRequest(`/peripherals/${peripheralId}/photos/${photoId}/set-primary`, {
            method: 'POST'
        });

        showSuccess('Photo principale définie');
        loadPhotos(); // Reload to update icons
    } catch (error) {
        console.error('Error setting primary photo:', error);
        showError('Erreur lors de la définition de la photo principale');
    }
}

Processus :

  1. Appel API POST pour définir la photo comme principale
  2. Message de succès
  3. Rechargement de la galerie pour mettre à jour les icônes

4. Endpoint API Backend

Fichier : backend/app/api/endpoints/peripherals.py (lignes 370-396)

@router.post("/{peripheral_id}/photos/{photo_id}/set-primary", status_code=200)
def set_primary_photo(
    peripheral_id: int,
    photo_id: int,
    db: Session = Depends(get_peripherals_db)
):
    """Set a photo as primary (thumbnail)"""
    # Get the photo
    photo = db.query(PeripheralPhoto).filter(
        PeripheralPhoto.id == photo_id,
        PeripheralPhoto.peripheral_id == peripheral_id
    ).first()

    if not photo:
        raise HTTPException(status_code=404, detail="Photo not found")

    # Unset all other primary photos for this peripheral
    db.query(PeripheralPhoto).filter(
        PeripheralPhoto.peripheral_id == peripheral_id,
        PeripheralPhoto.id != photo_id
    ).update({"is_primary": False})

    # Set this photo as primary
    photo.is_primary = True
    db.commit()

    return {"message": "Photo set as primary", "photo_id": photo_id}

Logique :

  1. Vérifie que la photo existe et appartient au périphérique
  2. Retire le flag is_primary de toutes les autres photos
  3. Définit is_primary=True pour la photo sélectionnée
  4. Garantit qu'une seule photo est principale à la fois

🎨 Rendu visuel

Galerie de photos

┌─────────────┐  ┌─────────────┐  ┌─────────────┐
│             │  │             │  │             │
│   Photo 1   │  │   Photo 2   │  │   Photo 3   │
│             │  │             │  │             │
│ ⭕          │  │ ✅          │  │ ⭕          │
│  [🗑️]      │  │  [🗑️]      │  │  [🗑️]      │
│ ★ Principale│  │             │  │             │
└─────────────┘  └─────────────┘  └─────────────┘

Légende :

  • = Icône ronde grise (non sélectionnée)
  • = Icône check bleu cyan (sélectionnée)
  • ★ = Badge "Principale" (affiché en haut)
  • 🗑️ = Bouton supprimer (en haut à droite)

États de l'icône

État Apparence Couleur Comportement
Normal Circle Gris #999 Cliquable
Hover Circle agrandie Bleu #66d9ef Scale 1.1
Active Check-circle Bleu #66d9ef Fond bleu clair
Active + Hover Check-circle Bleu plus clair Rétroaction visuelle

🔄 Flux d'utilisation

1. User voit la galerie de photos
   ↓
2. Chaque photo affiche une icône ⭕/✅ en bas à gauche
   ↓
3. User clique sur une icône ⭕ (non sélectionnée)
   ↓
4. setPrimaryPhoto(photoId) appelé
   │  ├─> POST /api/peripherals/{id}/photos/{photo_id}/set-primary
   │  └─> Backend met à jour is_primary
   ↓
5. Base de données mise à jour
   │  ├─> Ancienne photo principale : is_primary = false
   │  └─> Nouvelle photo : is_primary = true
   ↓
6. Success
   │  ├─> Message "Photo principale définie"
   │  ├─> Galerie rechargée
   │  └─> Icônes mises à jour (✅ sur la nouvelle, ⭕ sur les autres)

📊 Règles métier

Contraintes

  1. Une seule photo principale par périphérique

    • Définie automatiquement lors de la sélection
    • Les autres sont désélectionnées automatiquement
  2. Photo principale = Vignette

    • Utilisée dans les listes de périphériques
    • Affichée comme aperçu principal
  3. Upload avec is_primary

    • Possibilité de cocher lors de l'upload
    • Si cochée, retire le flag des autres

Avantages

  • Interface intuitive : Un clic pour changer
  • Visuel clair : États bien différenciés
  • Feedback immédiat : Message + rechargement
  • Cohérence : Une seule photo principale garantie

🧪 Tests

Test manuel

  1. Ouvrir page détail : Aller sur /peripheral-detail.html?id=3
  2. Observer les icônes : Voir ou en bas à gauche de chaque photo
  3. Hover sur icône : Vérifier effet scale + changement couleur
  4. Cliquer icône non cochée :
    • Message "Photo principale définie"
    • Icône devient
    • Autres icônes deviennent
  5. Vérifier badge : Badge "★ Principale" sur la photo cochée

Test API

# Définir photo ID 5 comme principale pour périphérique 3
curl -X POST "http://10.0.0.50:8007/api/peripherals/3/photos/5/set-primary" \
  -H "X-API-Token: YOUR_TOKEN"

Résultat attendu :

{
  "message": "Photo set as primary",
  "photo_id": 5
}

Vérification base de données

docker exec linux_benchtools_backend python3 -c "
import sqlite3
conn = sqlite3.connect('/app/data/peripherals.db')
cursor = conn.cursor()
cursor.execute('SELECT id, filename, is_primary FROM peripheral_photos WHERE peripheral_id = 3')
for row in cursor.fetchall():
    print(f'Photo {row[0]}: {row[1]} - Primary: {row[2]}')
"

Résultat attendu :

Photo 4: image1.png - Primary: 0
Photo 5: image2.png - Primary: 1  ← Une seule
Photo 6: image3.png - Primary: 0

📝 Fichiers modifiés

Frontend

  • frontend/js/peripheral-detail.js - Ajout bouton + fonction setPrimaryPhoto()
  • frontend/css/peripherals.css - Style .photo-primary-toggle

Backend

  • backend/app/api/endpoints/peripherals.py - Endpoint POST set-primary

Documentation

  • docs/FEATURE_PRIMARY_PHOTO_TOGGLE.md - Cette documentation

💡 Améliorations futures possibles

  • Drag & drop pour réorganiser les photos
  • Double-clic sur photo pour la définir comme principale
  • Raccourci clavier (ex: P pour Primary)
  • Animation de transition entre photos principales
  • Preview de la vignette avant validation

Date : 31 décembre 2025 Statut : Implémenté et fonctionnel Impact : Interface intuitive pour choisir la photo principale