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:
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user