feat: implement equipment and container loading rollback functionality with corresponding backend cloud functions

This commit is contained in:
ElPoyo
2026-05-27 22:04:46 +02:00
parent 64a9fe382a
commit faff06e4df
15 changed files with 660 additions and 514 deletions
@@ -12,7 +12,7 @@ enum ChecklistStep {
}
/// Widget pour afficher un équipement dans une checklist de préparation/retour
class EquipmentChecklistItem extends StatelessWidget {
class EquipmentChecklistItem extends StatefulWidget {
final EquipmentModel equipment;
final EventEquipment eventEquipment;
final ChecklistStep step;
@@ -34,92 +34,120 @@ class EquipmentChecklistItem extends StatelessWidget {
this.wasMissingBefore = false,
});
/// Retourne la quantité actuelle selon l'étape
@override
State<EquipmentChecklistItem> createState() => _EquipmentChecklistItemState();
}
class _EquipmentChecklistItemState extends State<EquipmentChecklistItem> {
late TextEditingController _quantityController;
int _getCurrentQuantity() {
switch (step) {
switch (widget.step) {
case ChecklistStep.preparation:
return eventEquipment.quantityAtPreparation ?? eventEquipment.quantity;
return widget.eventEquipment.quantityAtPreparation ?? widget.eventEquipment.quantity;
case ChecklistStep.loading:
return eventEquipment.quantityAtLoading ?? eventEquipment.quantityAtPreparation ?? eventEquipment.quantity;
return widget.eventEquipment.quantityAtLoading ?? widget.eventEquipment.quantityAtPreparation ?? widget.eventEquipment.quantity;
case ChecklistStep.unloading:
return eventEquipment.quantityAtUnloading ?? eventEquipment.quantityAtLoading ?? eventEquipment.quantity;
return widget.eventEquipment.quantityAtUnloading ?? widget.eventEquipment.quantityAtLoading ?? widget.eventEquipment.quantityAtPreparation ?? widget.eventEquipment.quantity;
case ChecklistStep.return_:
return eventEquipment.quantityAtReturn ?? eventEquipment.quantityAtUnloading ?? eventEquipment.quantity;
return widget.eventEquipment.quantityAtReturn ?? widget.eventEquipment.quantityAtUnloading ?? widget.eventEquipment.quantityAtLoading ?? widget.eventEquipment.quantityAtPreparation ?? widget.eventEquipment.quantity;
}
}
@override
void initState() {
super.initState();
_quantityController = TextEditingController(text: _getCurrentQuantity().toString());
}
@override
void didUpdateWidget(covariant EquipmentChecklistItem oldWidget) {
super.didUpdateWidget(oldWidget);
final currentQty = _getCurrentQuantity();
final controllerQty = int.tryParse(_quantityController.text);
if (controllerQty != currentQty) {
_quantityController.text = currentQty.toString();
}
}
@override
void dispose() {
_quantityController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final hasQuantity = equipment.hasQuantity;
final hasQuantity = widget.equipment.hasQuantity;
// Déterminer la quantité actuelle selon l'étape
final int currentQuantity = _getCurrentQuantity();
return Padding(
padding: EdgeInsets.only(
left: isChild ? 32.0 : 0.0, // Indentation pour les enfants
left: widget.isChild ? 32.0 : 0.0, // Indentation pour les enfants
top: 4.0,
bottom: 4.0,
),
child: Card(
margin: EdgeInsets.zero,
elevation: isChild ? 0 : 1, // Pas d'élévation pour les enfants
color: wasMissingBefore
elevation: widget.isChild ? 0 : 1, // Pas d'élévation pour les enfants
color: widget.wasMissingBefore
? Colors.orange.shade50
: (isChild ? Colors.grey.shade50 : Colors.white),
: (widget.isChild ? Colors.grey.shade50 : Colors.white),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(
color: wasMissingBefore
color: widget.wasMissingBefore
? Colors.orange
: (isValidated ? Colors.green : Colors.grey.shade300),
width: (isValidated || wasMissingBefore) ? 2 : 1,
: (widget.isValidated ? Colors.green : Colors.grey.shade300),
width: (widget.isValidated || widget.wasMissingBefore) ? 2 : 1,
),
),
child: ListTile(
dense: isChild, // Plus compact pour les enfants
dense: widget.isChild, // Plus compact pour les enfants
contentPadding: EdgeInsets.symmetric(
horizontal: isChild ? 8.0 : 16.0,
vertical: isChild ? 4.0 : 8.0,
horizontal: widget.isChild ? 8.0 : 16.0,
vertical: widget.isChild ? 4.0 : 8.0,
),
leading: Container(
width: isChild ? 32 : 40,
height: isChild ? 32 : 40,
width: widget.isChild ? 32 : 40,
height: widget.isChild ? 32 : 40,
decoration: BoxDecoration(
color: wasMissingBefore
color: widget.wasMissingBefore
? Colors.orange.shade100
: (isValidated ? Colors.green.shade100 : Colors.grey.shade100),
: (widget.isValidated ? Colors.green.shade100 : Colors.grey.shade100),
borderRadius: BorderRadius.circular(8),
),
child: IconButton(
icon: Icon(
wasMissingBefore
widget.wasMissingBefore
? Icons.warning
: (isValidated ? Icons.check_circle : Icons.radio_button_unchecked),
color: wasMissingBefore
: (widget.isValidated ? Icons.check_circle : Icons.radio_button_unchecked),
color: widget.wasMissingBefore
? Colors.orange
: (isValidated ? Colors.green : Colors.grey),
size: isChild ? 18 : 24,
: (widget.isValidated ? Colors.green : Colors.grey),
size: widget.isChild ? 18 : 24,
),
onPressed: onToggle,
onPressed: widget.onToggle,
padding: EdgeInsets.zero,
),
),
title: Text(
equipment.name,
widget.equipment.name,
style: TextStyle(
fontWeight: isChild ? FontWeight.w500 : FontWeight.w600,
fontSize: isChild ? 13 : 15,
decoration: isValidated ? TextDecoration.lineThrough : null,
color: isValidated ? Colors.grey : null,
fontWeight: widget.isChild ? FontWeight.w500 : FontWeight.w600,
fontSize: widget.isChild ? 13 : 15,
decoration: widget.isValidated ? TextDecoration.lineThrough : null,
color: widget.isValidated ? Colors.grey : null,
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (equipment.model != null)
if (widget.equipment.model != null)
Text(
equipment.model!,
widget.equipment.model!,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
@@ -127,12 +155,12 @@ class EquipmentChecklistItem extends StatelessWidget {
),
// Indicateur si manquant à l'étape précédente
if (wasMissingBefore)
if (widget.wasMissingBefore)
Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Row(
children: [
Icon(Icons.warning_amber, size: 14, color: Colors.orange),
const Icon(Icons.warning_amber, size: 14, color: Colors.orange),
const SizedBox(width: 4),
Text(
'Était manquant à l\'étape précédente',
@@ -151,7 +179,7 @@ class EquipmentChecklistItem extends StatelessWidget {
const SizedBox(height: 6),
Row(
children: [
Text(
const Text(
'Quantité : ',
style: TextStyle(
fontSize: 12,
@@ -159,11 +187,11 @@ class EquipmentChecklistItem extends StatelessWidget {
color: AppColors.bleuFonce,
),
),
if (onQuantityChanged != null)
if (widget.onQuantityChanged != null)
SizedBox(
width: 60,
child: TextFormField(
initialValue: currentQuantity.toString(),
controller: _quantityController,
keyboardType: TextInputType.number,
style: const TextStyle(fontSize: 12),
decoration: InputDecoration(
@@ -175,14 +203,14 @@ class EquipmentChecklistItem extends StatelessWidget {
),
onChanged: (value) {
final qty = int.tryParse(value) ?? currentQuantity;
onQuantityChanged!(qty);
widget.onQuantityChanged!(qty);
},
),
)
else
Text(
currentQuantity.toString(),
style: TextStyle(
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: AppColors.bleuFonce,
@@ -193,16 +221,16 @@ class EquipmentChecklistItem extends StatelessWidget {
],
],
),
trailing: isValidated
trailing: widget.isValidated
? Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.green.shade100,
borderRadius: BorderRadius.circular(12),
),
child: Row(
child: const Row(
mainAxisSize: MainAxisSize.min,
children: const [
children: [
Icon(Icons.check, size: 16, color: Colors.green),
SizedBox(width: 4),
Text(