feat: Refonte de la checklist de préparation avec gestion des manquants et des containers

Cette mise à jour refond entièrement l'interface et la logique de la checklist de préparation d'événement. Elle introduit la notion d'équipements "manquants", une gestion visuelle des containers et de leur contenu, et une logique plus fine pour le suivi des quantités et des statuts à chaque étape.

**Features et Améliorations :**

-   **Gestion des Équipements Manquants :**
    -   Le modèle `EventEquipment` a été enrichi pour tracer si un équipement est manquant à chaque étape (`isMissingAtPreparation`, `isMissingAtLoading`, etc.).
    -   Un équipement non validé lors de la confirmation d'une étape est désormais marqué comme "manquant" pour les étapes suivantes.
    -   Les équipements qui étaient manquants à l'étape précédente sont maintenant visuellement mis en évidence avec une bordure et une icône orange, et une confirmation est demandée pour les valider.

-   **Refonte de la Checklist (UI/UX) :**
    -   **Groupement par Container :** La checklist affiche désormais les containers comme des en-têtes de groupe. Les équipements qu'ils contiennent sont listés en dessous, avec une indentation visuelle.
    -   **Validation Groupée :** Il est possible de valider tous les équipements d'un container en un seul clic sur l'en-tête du container.
    -   **Nouveau Widget `ContainerChecklistItem` :** Créé pour afficher un container et ses équipements enfants dans la checklist.
    -   **Refonte de `EquipmentChecklistItem` :** Le widget a été entièrement revu pour un design plus clair, une meilleure gestion des états (validé, manquant), et un affichage compact pour les équipements enfants.

-   **Logique de Suivi Améliorée :**
    -   **Quantités par Étape :** Le modèle `EventEquipment` et l'interface de préparation permettent maintenant de suivre les quantités réelles à chaque étape (`quantityAtPreparation`, `quantityAtLoading`, etc.), au lieu d'une seule quantité de retour.
    -   **Marquage Automatique des "Perdus" :** À l'étape finale du retour, un équipement qui était présent au départ mais qui est maintenant manquant sera automatiquement marqué avec le statut "lost" dans la base de données.
    -   **Flux de Validation :** Le processus de confirmation distingue désormais la validation de tous les équipements et la confirmation de l'état actuel (y compris les manquants).

-   **Export ICS Enrichi :**
    -   L'export ICS inclut désormais les noms résolus des utilisateurs (main d'œuvre) pour plus de clarté, en plus des détails de l'événement.
    -   Le contenu généré mentionne la version de l'application.
This commit is contained in:
ElPoyo
2026-01-15 12:05:37 +01:00
parent b30ae0f10a
commit 60d0e1c6c4
10 changed files with 885 additions and 336 deletions

View File

@@ -1,173 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:em2rp/models/equipment_model.dart';
/// Widget pour afficher un équipement avec checkbox de validation
class EquipmentChecklistItem extends StatelessWidget {
final EquipmentModel equipment;
final bool isValidated;
final ValueChanged<bool> onValidate;
final bool isReturnMode;
final int? quantity;
final int? returnedQuantity;
final ValueChanged<int>? onReturnedQuantityChanged;
const EquipmentChecklistItem({
super.key,
required this.equipment,
required this.isValidated,
required this.onValidate,
this.isReturnMode = false,
this.quantity,
this.returnedQuantity,
this.onReturnedQuantityChanged,
});
bool get _isConsumable =>
equipment.category == EquipmentCategory.consumable ||
equipment.category == EquipmentCategory.cable;
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
elevation: isValidated ? 0 : 2,
color: isValidated ? Colors.green.shade50 : Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(
color: isValidated ? Colors.green : Colors.grey.shade300,
width: isValidated ? 2 : 1,
),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Checkbox de validation
Checkbox(
value: isValidated,
onChanged: (value) => onValidate(value ?? false),
activeColor: Colors.green,
),
const SizedBox(width: 12),
// Icône de l'équipement
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: equipment.category.color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: equipment.category.getIcon(
size: 24,
color: equipment.category.color,
),
),
const SizedBox(width: 12),
// Informations de l'équipement
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Nom/ID
Text(
equipment.id,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
const SizedBox(height: 4),
// Marque/Modèle
if (equipment.brand != null || equipment.model != null)
Text(
'${equipment.brand ?? ''} ${equipment.model ?? ''}'.trim(),
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade700,
),
),
const SizedBox(height: 4),
// Quantité assignée (consommables uniquement)
if (_isConsumable && quantity != null)
Text(
'Quantité assignée : $quantity',
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade600,
fontWeight: FontWeight.w500,
),
),
// Champ de quantité retournée (mode retour + consommables)
if (isReturnMode && _isConsumable && onReturnedQuantityChanged != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Row(
children: [
Text(
'Quantité retournée :',
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade700,
),
),
const SizedBox(width: 8),
SizedBox(
width: 80,
child: TextField(
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 8,
),
hintText: quantity?.toString() ?? '0',
),
controller: TextEditingController(
text: returnedQuantity?.toString() ?? quantity?.toString() ?? '0',
),
onChanged: (value) {
final intValue = int.tryParse(value) ?? 0;
if (onReturnedQuantityChanged != null) {
onReturnedQuantityChanged!(intValue);
}
},
),
),
],
),
),
],
),
),
// Icône de statut
if (isValidated)
const Icon(
Icons.check_circle,
color: Colors.green,
size: 28,
),
],
),
),
);
}
}