feat: Refactor event equipment management with advanced selection and conflict detection

This commit introduces a complete overhaul of how equipment is assigned to events, focusing on an enhanced user experience, advanced selection capabilities, and robust conflict detection.

**Key Features & Enhancements:**

-   **Advanced Equipment Selection UI (`EquipmentSelectionDialog`):**
    -   New full-screen dialog to select equipment and containers ("boîtes") for an event.
    -   Hierarchical view showing containers and a flat list of all individual equipment.
    -   Real-time search and filtering by equipment category.
    -   Side panel summarizing the current selection and providing recommendations for containers based on selected equipment.
    -   Supports quantity selection for consumables and cables.

-   **Conflict Detection & Management (`EventAvailabilityService`):**
    -   A new service (`EventAvailabilityService`) checks for equipment availability against other events based on the selected date range.
    -   The selection dialog visually highlights equipment and containers with scheduling conflicts (e.g., already used, partially unavailable).
    -   A dedicated conflict resolution dialog (`EquipmentConflictDialog`) appears if conflicting items are selected, allowing the user to either remove them or force the assignment.

-   **Integrated Event Form (`EventAssignedEquipmentSection`):**
    -   The event creation/editing form now includes a new section for managing assigned equipment.
    -   It clearly displays assigned containers and standalone equipment, showing the composition of each container.
    -   Integrates the new selection dialog, ensuring all assignments are checked for conflicts before being saved.

-   **Event Preparation & Return Workflow (`EventPreparationPage`):**
    -   New page (`EventPreparationPage`) for managing the check-out (preparation) and check-in (return) of equipment for an event.
    -   Provides a checklist of all assigned equipment.
    -   Users can validate each item, with options to "validate all" or finalize with missing items.
    -   Includes a dialog (`MissingEquipmentDialog`) to handle discrepancies.
    -   Supports tracking returned quantities for consumables.

**Data Model and Other Changes:**

-   The `EventModel` now includes `assignedContainers` to explicitly link containers to an event.
-   `EquipmentAssociatedEventsSection` on the equipment detail page is now functional, displaying current, upcoming, and past events for that item.
-   Added deployment and versioning scripts (`scripts/deploy.js`, `scripts/increment_version.js`, `scripts/toggle_env.js`) to automate the release process.
-   Introduced an application version display in the main drawer (`AppVersion`).
This commit is contained in:
ElPoyo
2025-11-30 20:33:03 +01:00
parent e59e3e6316
commit 08f046c89c
31 changed files with 4955 additions and 46 deletions

View File

@@ -0,0 +1,226 @@
import 'package:flutter/material.dart';
import 'package:em2rp/utils/colors.dart';
/// Dialog de succès avec animation de camion
class PreparationSuccessDialog extends StatefulWidget {
final bool isReturnMode;
const PreparationSuccessDialog({
super.key,
this.isReturnMode = false,
});
@override
State<PreparationSuccessDialog> createState() => _PreparationSuccessDialogState();
}
class _PreparationSuccessDialogState extends State<PreparationSuccessDialog>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _truckAnimation;
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2500),
vsync: this,
);
// Animation du camion qui part (translation)
_truckAnimation = Tween<double>(
begin: 0.0,
end: 1.5,
).animate(CurvedAnimation(
parent: _controller,
curve: const Interval(0.3, 1.0, curve: Curves.easeInBack),
));
// Animation de fade out
_fadeAnimation = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(CurvedAnimation(
parent: _controller,
curve: const Interval(0.7, 1.0, curve: Curves.easeOut),
));
// Animation de scale pour le check
_scaleAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.3, curve: Curves.elasticOut),
));
_controller.forward();
// Auto-fermer après l'animation
Future.delayed(const Duration(milliseconds: 2500), () {
if (mounted) {
Navigator.of(context).pop();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
elevation: 0,
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Opacity(
opacity: _fadeAnimation.value,
child: Container(
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Animation du check qui pop
Transform.scale(
scale: _scaleAnimation.value,
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
),
child: const Icon(
Icons.check,
color: Colors.white,
size: 50,
),
),
),
const SizedBox(height: 24),
// Texte
Text(
widget.isReturnMode
? 'Retour validé !'
: 'Préparation validée !',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.green,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
// Animation du camion avec pneus qui crissent
SizedBox(
height: 100,
child: Stack(
children: [
// Traces de pneus (lignes qui apparaissent)
if (_truckAnimation.value > 0.1)
Positioned(
left: 0,
right: MediaQuery.of(context).size.width * 0.3,
bottom: 30,
child: CustomPaint(
painter: TireMarksPainter(
progress: (_truckAnimation.value - 0.1).clamp(0.0, 1.0),
),
),
),
// Camion qui part
Positioned(
left: MediaQuery.of(context).size.width * _truckAnimation.value - 100,
bottom: 20,
child: Transform.rotate(
angle: _truckAnimation.value > 0.5 ? -0.1 : 0,
child: const Icon(
Icons.local_shipping,
size: 60,
color: AppColors.rouge,
),
),
),
],
),
),
const SizedBox(height: 16),
Text(
widget.isReturnMode
? 'Le matériel est de retour au dépôt'
: 'Le matériel est prêt pour l\'événement',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
textAlign: TextAlign.center,
),
],
),
),
);
},
),
);
}
}
/// Custom painter pour dessiner les traces de pneus
class TireMarksPainter extends CustomPainter {
final double progress;
TireMarksPainter({required this.progress});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.grey.shade400
..strokeWidth = 2
..style = PaintingStyle.stroke;
final dashWidth = 10.0;
final dashSpace = 5.0;
final maxWidth = size.width * progress;
// Dessiner deux lignes de traces (pour les deux roues)
for (var i = 0; i < 2; i++) {
final y = i * 15.0;
var startX = 0.0;
while (startX < maxWidth) {
final endX = (startX + dashWidth).clamp(0.0, maxWidth);
canvas.drawLine(
Offset(startX, y),
Offset(endX, y),
paint,
);
startX += dashWidth + dashSpace;
}
}
}
@override
bool shouldRepaint(TireMarksPainter oldDelegate) {
return oldDelegate.progress != progress;
}
}