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
@@ -2,7 +2,9 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:em2rp/models/event_model.dart';
import 'package:em2rp/providers/event_provider.dart';
import 'package:em2rp/providers/local_user_provider.dart';
import 'package:em2rp/views/event_preparation_page.dart';
import 'package:em2rp/services/api_service.dart';
import 'package:em2rp/utils/colors.dart';
/// Boutons de préparation et retour d'événement
@@ -52,16 +54,16 @@ class _EventPreparationButtonsState extends State<EventPreparationButtons> {
IconData buttonIcon;
bool isCompleted = false;
if (prep != PreparationStatus.completed) {
if (prep != PreparationStatus.completed && prep != PreparationStatus.completedWithMissing) {
buttonText = 'Préparation dépôt';
buttonIcon = Icons.inventory_2;
} else if (loading != LoadingStatus.completed) {
} else if (loading != LoadingStatus.completed && loading != LoadingStatus.completedWithMissing) {
buttonText = 'Chargement aller';
buttonIcon = Icons.local_shipping;
} else if (unloading != UnloadingStatus.completed) {
} else if (unloading != UnloadingStatus.completed && unloading != UnloadingStatus.completedWithMissing) {
buttonText = 'Chargement retour';
buttonIcon = Icons.unarchive;
} else if (returnStatus != ReturnStatus.completed) {
} else if (returnStatus != ReturnStatus.completed && returnStatus != ReturnStatus.completedWithMissing) {
buttonText = 'Retour dépôt';
buttonIcon = Icons.assignment_return;
} else {
@@ -131,9 +133,9 @@ class _EventPreparationButtonsState extends State<EventPreparationButtons> {
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.green, width: 1),
),
child: Row(
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
children: [
Icon(Icons.check_circle, color: Colors.green, size: 20),
SizedBox(width: 8),
Text(
@@ -147,9 +149,163 @@ class _EventPreparationButtonsState extends State<EventPreparationButtons> {
),
),
),
// Bouton de retour en arrière si au moins une étape est commencée/validée
if (prep != PreparationStatus.notStarted) ...[
const SizedBox(height: 8),
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: OutlinedButton.icon(
onPressed: () => _showRollbackDialog(context, event),
icon: const Icon(Icons.undo, size: 18),
label: const Text('Revenir à une étape précédente'),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.orange[800],
side: BorderSide(color: Colors.orange[300]!),
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
),
),
),
),
],
],
),
);
}
Future<void> _showRollbackDialog(BuildContext context, EventModel event) async {
final prep = event.preparationStatus ?? PreparationStatus.notStarted;
final loading = event.loadingStatus ?? LoadingStatus.notStarted;
final unloading = event.unloadingStatus ?? UnloadingStatus.notStarted;
final returnStatus = event.returnStatus ?? ReturnStatus.notStarted;
final List<Map<String, String>> steps = [];
if (prep == PreparationStatus.completed || prep == PreparationStatus.completedWithMissing) {
steps.add({'key': 'PREPARATION', 'label': 'Préparation dépôt'});
}
if (loading == LoadingStatus.completed || loading == LoadingStatus.completedWithMissing) {
steps.add({'key': 'LOADING', 'label': 'Chargement aller'});
}
if (unloading == UnloadingStatus.completed || unloading == UnloadingStatus.completedWithMissing) {
steps.add({'key': 'UNLOADING', 'label': 'Chargement retour'});
}
if (returnStatus == ReturnStatus.completed || returnStatus == ReturnStatus.completedWithMissing) {
steps.add({'key': 'RETURN', 'label': 'Retour dépôt'});
}
if (steps.isEmpty) return;
final String? selectedStep = await showDialog<String>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Row(
children: [
Icon(Icons.undo, color: Colors.orange),
SizedBox(width: 8),
Text('Revenir en arrière'),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Sélectionnez l\'étape à laquelle vous souhaitez revenir :'),
const SizedBox(height: 12),
...steps.map((step) {
return ListTile(
leading: const Icon(Icons.arrow_back, color: AppColors.rouge),
title: Text(step['label']!),
onTap: () => Navigator.of(context).pop(step['key']),
);
}),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Annuler'),
),
],
);
},
);
if (selectedStep != null && context.mounted) {
final confirm = await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Confirmer le retour en arrière'),
content: const Text(
'Êtes-vous sûr de vouloir revenir à cette étape ?\n\n'
'Toutes les validations des étapes ultérieures seront effacées, '
'et si le retour était terminé, les stocks restaurés seront annulés.'
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(true),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.rouge,
),
child: const Text('Confirmer'),
),
],
);
},
);
if (confirm == true && context.mounted) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const Center(child: CircularProgressIndicator());
},
);
try {
final apiService = FirebaseFunctionsApiService();
await apiService.call('rollbackEventStep', {
'eventId': event.id,
'targetStep': selectedStep,
});
if (context.mounted) {
final eventProvider = Provider.of<EventProvider>(context, listen: false);
final userProvider = Provider.of<LocalUserProvider>(context, listen: false);
if (userProvider.currentUser != null) {
await eventProvider.refreshEvents(userProvider.currentUser!.uid);
}
Navigator.of(context).pop(); // Fermer le loader
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Retour en arrière effectué avec succès'),
backgroundColor: Colors.green,
),
);
}
} catch (e) {
if (context.mounted) {
Navigator.of(context).pop(); // Fermer le loader
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur : $e'),
backgroundColor: Colors.red,
),
);
}
}
}
}
}
}