# Fonctionnalité : Miniatures dans la liste des périphériques

## 🎯 Objectif

Afficher les miniatures (thumbnails) de 48px dans la liste des périphériques au lieu de l'icône générique.

**Comportement** :
- Si le périphérique a une photo principale → Afficher la miniature
- Si pas de photo → Afficher l'icône `<i class="fas fa-microchip"></i>`
- Si l'image ne charge pas (erreur) → Fallback vers l'icône

---

## ✅ Implémentation

### 1. Backend - Schéma API

**Fichier** : `backend/app/schemas/peripheral.py` (ligne 150)

**Modification** : Ajout du champ `thumbnail_url` au schéma `PeripheralSummary`

```python
class PeripheralSummary(BaseModel):
    """Summary schema for peripheral lists"""
    id: int
    nom: str
    type_principal: str
    sous_type: Optional[str]
    marque: Optional[str]
    modele: Optional[str]
    etat: str
    rating: float
    prix: Optional[float]
    en_pret: bool
    is_complete_device: bool
    quantite_disponible: int
    thumbnail_url: Optional[str] = None  # ← Nouveau champ

    class Config:
        from_attributes = True
```

**Résultat** : L'API retourne maintenant l'URL du thumbnail pour chaque périphérique dans la liste.

---

### 2. Backend - Service

**Fichier** : `backend/app/services/peripheral_service.py` (lignes 179-210)

**Modification** : Récupération de la photo principale et construction de l'URL

```python
# Import PeripheralPhoto here to avoid circular import
from app.models.peripheral import PeripheralPhoto

# Convert to summary
items = []
for p in peripherals:
    # Get primary photo thumbnail
    thumbnail_url = None
    primary_photo = db.query(PeripheralPhoto).filter(
        PeripheralPhoto.peripheral_id == p.id,
        PeripheralPhoto.is_primary == True
    ).first()

    if primary_photo and primary_photo.thumbnail_path:
        # Convert file path to URL
        thumbnail_url = primary_photo.thumbnail_path.replace('/app/uploads/', '/uploads/')

    items.append(PeripheralSummary(
        id=p.id,
        nom=p.nom,
        type_principal=p.type_principal,
        sous_type=p.sous_type,
        marque=p.marque,
        modele=p.modele,
        etat=p.etat or "Inconnu",
        rating=p.rating or 0.0,
        prix=p.prix,
        en_pret=p.en_pret or False,
        is_complete_device=p.is_complete_device or False,
        quantite_disponible=p.quantite_disponible or 0,
        thumbnail_url=thumbnail_url  # ← Ajout du thumbnail
    ))
```

**Logique** :
1. Pour chaque périphérique, requête SQL pour trouver la photo avec `is_primary = True`
2. Si une photo principale existe et a un `thumbnail_path` :
   - Convertir le chemin serveur (`/app/uploads/...`) en URL web (`/uploads/...`)
3. Sinon : `thumbnail_url = None`

**Exemple de résultat API** :
```json
{
  "items": [
    {
      "id": 6,
      "nom": "USB Receiver",
      "thumbnail_url": "/uploads/peripherals/photos/6/thumbnail/logitechreceiver_thumb_20251231_101254.png"
    },
    {
      "id": 5,
      "nom": "Flash Card Reader/Writer",
      "thumbnail_url": null
    }
  ],
  "total": 46,
  "page": 1,
  "page_size": 10,
  "total_pages": 5
}
```

---

### 3. Frontend - JavaScript

**Fichier** : `frontend/js/peripherals.js` (lignes 325-329)

**Modification** : Affichage conditionnel de l'image ou de l'icône

```javascript
<div class="peripheral-photo">
    ${p.thumbnail_url
        ? `<img src="${escapeHtml(p.thumbnail_url)}" alt="${escapeHtml(p.nom)}" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
           <i class="fas fa-microchip" style="display:none;"></i>`
        : `<i class="fas fa-microchip"></i>`
    }
</div>
```

**Logique** :
- **Si `thumbnail_url` existe** :
  - Afficher `<img>` avec l'URL du thumbnail
  - Ajouter un `onerror` handler : si l'image ne charge pas, cacher l'image et afficher l'icône
  - Icône en `display:none` par défaut (visible seulement en cas d'erreur)

- **Si pas de `thumbnail_url`** :
  - Afficher directement l'icône `<i class="fas fa-microchip"></i>`

**Cas gérés** :
1. ✅ Périphérique avec photo → Image affichée
2. ✅ Périphérique sans photo → Icône affichée
3. ✅ Image qui ne charge pas (404, erreur réseau) → Fallback vers icône

---

### 4. Frontend - CSS

**Fichier** : `frontend/css/peripherals.css` (lignes 156-176)

**Modification** : Style pour conteneur et images

```css
.peripheral-photo {
    width: 50px;
    height: 50px;
    background: #232323;
    border: 1px solid #3e3d32;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #66d9ef;
    font-size: 1.5rem;
    overflow: hidden;  /* ← Ajouté */
}

.peripheral-photo img {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain;  /* ← Conserve ratio */
}
```

**Caractéristiques** :
- **Conteneur** : 50×50px, fond sombre, bordure, centré
- **Image** :
  - `max-width/max-height: 100%` → Ne dépasse jamais le conteneur
  - `width/height: auto` → Préserve le ratio d'aspect
  - `object-fit: contain` → L'image entière est visible sans déformation
  - Centré grâce au `display: flex` du parent

**Rendu visuel** :

```
┌─────────────────────────────────────────────────────┐
│ Nom                    │ Type    │ Photo            │
├─────────────────────────────────────────────────────┤
│ USB Receiver           │ USB     │ [🖼️ Thumbnail] │
│ Flash Card Reader      │ Stockage│ [💾 Icône]     │
│ ConBee II              │ USB     │ [🖼️ Thumbnail] │
│ CS9711Fingprint        │ USB     │ [🖼️ Thumbnail] │
│ TL-WN823N              │ Réseau  │ [💾 Icône]     │
└─────────────────────────────────────────────────────┘
```

---

## 📊 Exemples

### Exemple 1 : Périphérique avec photo

**API Response** :
```json
{
  "id": 6,
  "nom": "USB Receiver",
  "thumbnail_url": "/uploads/peripherals/photos/6/thumbnail/logitechreceiver_thumb_20251231_101254.png"
}
```

**HTML généré** :
```html
<div class="peripheral-photo">
    <img src="/uploads/peripherals/photos/6/thumbnail/logitechreceiver_thumb_20251231_101254.png"
         alt="USB Receiver"
         onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
    <i class="fas fa-microchip" style="display:none;"></i>
</div>
```

**Rendu** : Image du thumbnail (48px de large, ratio conservé)

---

### Exemple 2 : Périphérique sans photo

**API Response** :
```json
{
  "id": 5,
  "nom": "Flash Card Reader/Writer",
  "thumbnail_url": null
}
```

**HTML généré** :
```html
<div class="peripheral-photo">
    <i class="fas fa-microchip"></i>
</div>
```

**Rendu** : Icône générique de puce électronique

---

### Exemple 3 : Image qui ne charge pas (erreur)

**Scénario** : Le fichier thumbnail est supprimé mais l'URL existe en base

**API Response** :
```json
{
  "id": 7,
  "nom": "Peripheral Test",
  "thumbnail_url": "/uploads/peripherals/photos/7/thumbnail/deleted.png"
}
```

**HTML généré** :
```html
<div class="peripheral-photo">
    <img src="/uploads/peripherals/photos/7/thumbnail/deleted.png"
         alt="Peripheral Test"
         onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
    <i class="fas fa-microchip" style="display:none;"></i>
</div>
```

**Comportement** :
1. Navigateur tente de charger l'image
2. Image introuvable → Event `onerror` déclenché
3. JavaScript cache l'image (`display:none`)
4. JavaScript affiche l'icône (`display:flex`)

**Rendu final** : Icône générique (fallback automatique)

---

## 🔄 Flux complet

```
1. User charge page /peripherals.html
   ↓
2. JavaScript appelle GET /api/peripherals/?page=1&page_size=10
   ↓
3. Backend service list_peripherals()
   │  ├─> Query périphériques (avec pagination)
   │  └─> Pour chaque périphérique:
   │       ├─> Query photo principale (is_primary=True)
   │       └─> Si photo existe: thumbnail_url = "/uploads/..."
   ↓
4. API retourne JSON avec items[].thumbnail_url
   ↓
5. JavaScript génère HTML du tableau
   │  ├─> Si thumbnail_url → <img>
   │  └─> Sinon → <i class="fas fa-microchip">
   ↓
6. Navigateur affiche la liste
   │  ├─> Charge les images (si URLs présentes)
   │  └─> Affiche icônes (si pas d'URL ou erreur)
```

---

## 📝 Fichiers modifiés

### Backend
| Fichier | Lignes | Modification |
|---------|--------|--------------|
| `backend/app/schemas/peripheral.py` | 150 | Ajout `thumbnail_url: Optional[str] = None` |
| `backend/app/services/peripheral_service.py` | 179-210 | Query photo principale + construction URL |

### Frontend
| Fichier | Lignes | Modification |
|---------|--------|--------------|
| `frontend/js/peripherals.js` | 325-329 | Affichage conditionnel image/icône |
| `frontend/css/peripherals.css` | 170-176 | Style pour `img` dans `.peripheral-photo` |

---

## 🧪 Tests

### Test 1 : API retourne thumbnail_url

```bash
curl -s "http://localhost:8007/api/peripherals/?page=1&page_size=2" | \
  python3 -c "import sys, json; data=json.load(sys.stdin); print(data['items'][0].get('thumbnail_url'))"
```

**Résultat attendu** :
```
/uploads/peripherals/photos/6/thumbnail/logitechreceiver_thumb_20251231_101254.png
```

### Test 2 : Plusieurs périphériques avec/sans photos

```bash
curl -s "http://localhost:8007/api/peripherals/?page=1&page_size=10" | \
  python3 -c "
import sys, json
data = json.load(sys.stdin)
for item in data['items'][:5]:
    thumb = item.get('thumbnail_url', 'None')
    print(f'{item[\"nom\"][:30]:30} → {thumb}')
"
```

**Résultat attendu** :
```
USB Receiver                   → /uploads/peripherals/photos/6/thumbnail/logitechreceiver_thumb_20251231_101254.png
Flash Card Reader/Writer       → None
ConBee II                      → /uploads/peripherals/photos/4/thumbnail/conbee2_thumb_20251231_101147.png
CS9711Fingprint                → /uploads/peripherals/photos/3/thumbnail/csfingerprint_thumb_20251231_101537.png
TL-WN823N v2/v3                → None
```

### Test 3 : Affichage visuel

1. Ouvrir `http://10.0.0.50:8087/peripherals.html`
2. Vérifier la colonne "Photo" :
   - ✅ Les périphériques avec photos affichent la miniature
   - ✅ Les périphériques sans photos affichent l'icône puce
   - ✅ Les images sont bien dimensionnées (max 50×50px)

---

## 💡 Améliorations futures

- [ ] Cache des requêtes thumbnail (éviter N+1 queries)
- [ ] Lazy loading des images (`loading="lazy"`)
- [ ] Preview au survol (hover) de la miniature
- [ ] Optimisation : JOIN au lieu de requête séparée par périphérique
- [ ] Placeholder animé pendant chargement de l'image

---

**Date** : 31 décembre 2025
**Statut** : ✅ Implémenté et testé
**Impact** : Affichage des miniatures dans la liste des périphériques avec fallback automatique vers icône
