Files
EM2_ERP/em2rp/lib/views/widgets/event/equipment_conflict_dialog.dart

321 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import 'package:em2rp/services/event_availability_service.dart';
import 'package:em2rp/utils/colors.dart';
import 'package:intl/intl.dart';
/// Dialog affichant les conflits de disponibilité du matériel
class EquipmentConflictDialog extends StatefulWidget {
final Map<String, List<AvailabilityConflict>> conflicts;
const EquipmentConflictDialog({
super.key,
required this.conflicts,
});
@override
State<EquipmentConflictDialog> createState() => _EquipmentConflictDialogState();
}
class _EquipmentConflictDialogState extends State<EquipmentConflictDialog> {
final Set<String> _removedEquipmentIds = {};
int get totalConflicts => widget.conflicts.values.fold(0, (sum, list) => sum + list.length);
int get remainingConflicts => widget.conflicts.entries
.where((entry) => !_removedEquipmentIds.contains(entry.key))
.fold(0, (sum, entry) => sum + entry.value.length);
/// Retourne l'icône appropriée selon le type de conflit
IconData _getIconForConflict(AvailabilityConflict conflict) {
switch (conflict.type) {
case ConflictType.containerFullyUsed:
case ConflictType.containerPartiallyUsed:
return Icons.inventory_2;
case ConflictType.insufficientQuantity:
return Icons.production_quantity_limits;
case ConflictType.equipmentUnavailable:
return Icons.block;
}
}
@override
Widget build(BuildContext context) {
final dateFormat = DateFormat('dd/MM/yyyy');
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Container(
constraints: const BoxConstraints(maxWidth: 700, maxHeight: 700),
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// En-tête avec icône warning
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.orange.shade100,
shape: BoxShape.circle,
),
child: Icon(
Icons.warning_amber_rounded,
size: 32,
color: Colors.orange.shade700,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Conflits de disponibilité détectés',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'$remainingConflicts conflit(s) sur $totalConflicts équipement(s)',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade700,
),
),
],
),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.of(context).pop('cancel'),
),
],
),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 8),
// Liste des conflits
Flexible(
child: ListView.builder(
shrinkWrap: true,
itemCount: widget.conflicts.length,
itemBuilder: (context, index) {
final entry = widget.conflicts.entries.elementAt(index);
final equipmentId = entry.key;
final conflictsList = entry.value;
final isRemoved = _removedEquipmentIds.contains(equipmentId);
if (conflictsList.isEmpty) return const SizedBox.shrink();
final firstConflict = conflictsList.first;
return Opacity(
opacity: isRemoved ? 0.4 : 1.0,
child: Card(
margin: const EdgeInsets.only(bottom: 12),
elevation: isRemoved ? 0 : 2,
color: isRemoved ? Colors.grey.shade200 : null,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Nom de l'équipement
Row(
children: [
Icon(
_getIconForConflict(firstConflict),
color: isRemoved ? Colors.grey : AppColors.rouge,
size: 20,
),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
firstConflict.equipmentName,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
decoration: isRemoved ? TextDecoration.lineThrough : null,
),
),
// Message de conflit spécifique
if (firstConflict.conflictMessage.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
firstConflict.conflictMessage,
style: TextStyle(
fontSize: 13,
color: Colors.orange.shade700,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
if (isRemoved)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(12),
),
child: const Text(
'RETIRÉ',
style: TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 12),
// Liste des événements en conflit
...conflictsList.map((conflict) {
return Padding(
padding: const EdgeInsets.only(bottom: 8, left: 28),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.event,
size: 16,
color: Colors.grey.shade600,
),
const SizedBox(width: 6),
Expanded(
child: Text(
conflict.conflictingEvent.name,
style: TextStyle(
fontWeight: FontWeight.w500,
color: Colors.grey.shade800,
),
),
),
],
),
const SizedBox(height: 4),
Padding(
padding: const EdgeInsets.only(left: 22),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${dateFormat.format(conflict.conflictingEvent.startDateTime)}${dateFormat.format(conflict.conflictingEvent.endDateTime)}',
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade600,
),
),
Text(
'Chevauchement : ${conflict.overlapDays} jour(s)',
style: TextStyle(
fontSize: 12,
color: Colors.orange.shade700,
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
);
}).toList(),
// Boutons d'action par équipement
if (!isRemoved)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Row(
children: [
const Spacer(),
OutlinedButton.icon(
onPressed: () {
setState(() {
_removedEquipmentIds.add(equipmentId);
});
},
icon: const Icon(Icons.remove_circle_outline, size: 16),
label: const Text('Retirer'),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.red,
side: const BorderSide(color: Colors.red),
),
),
],
),
),
],
),
),
),
);
},
),
),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 16),
// Boutons d'action globaux
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => Navigator.of(context).pop('cancel'),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
),
child: const Text('Annuler tout'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: remainingConflicts == 0
? () => Navigator.of(context).pop('force_removed')
: () => Navigator.of(context).pop('force_all'),
style: ElevatedButton.styleFrom(
backgroundColor: remainingConflicts == 0 ? Colors.green : Colors.orange,
padding: const EdgeInsets.symmetric(vertical: 14),
),
child: Text(
remainingConflicts == 0
? 'Valider sans les retirés'
: 'Forcer malgré les conflits',
style: const TextStyle(color: Colors.white),
),
),
),
],
),
],
),
),
);
}
}