import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../router/app_router.dart'; import '../theme/app_colors.dart'; import '../theme/machine_status_theme.dart'; import '../../features/establishments/domain/establishment.dart'; /// Légende des statuts machines. class MachineStatusLegend extends StatelessWidget { const MachineStatusLegend({super.key}); @override Widget build(BuildContext context) { return Wrap( spacing: 12, runSpacing: 6, children: [ _LegendDot(color: AppColors.machineAvailable, label: 'Libre'), _LegendDot(color: AppColors.machineRunning, label: 'En cours'), _LegendDot(color: AppColors.machineReserved, label: 'Réservée'), _LegendDot(color: AppColors.machineOffline, label: 'Indisponible'), ], ); } } class _LegendDot extends StatelessWidget { const _LegendDot({required this.color, required this.label}); final Color color; final String label; @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 8, height: 8, decoration: BoxDecoration(color: color, shape: BoxShape.circle), ), const SizedBox(width: 5), Text(label, style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontSize: 12)), ], ); } } /// Carte machine en grille — vue d'ensemble type WashOnline. class MachineGridCard extends StatelessWidget { const MachineGridCard({ super.key, required this.machine, required this.onTap, }); final Machine machine; final VoidCallback onTap; @override Widget build(BuildContext context) { final statusColor = MachineStatusTheme.color(machine.status); final canStart = MachineStatusTheme.canStart(machine.status); return Material( color: AppColors.surface, elevation: canStart ? 2 : 0, shadowColor: statusColor.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(20), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(20), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), border: Border.all( color: canStart ? statusColor.withValues(alpha: 0.35) : AppColors.divider, width: canStart ? 1.5 : 1, ), ), padding: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 36, height: 36, decoration: BoxDecoration( color: statusColor.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(10), ), child: Icon( MachineStatusTheme.iconForType(machine.type), color: statusColor, size: 20, ), ), const Spacer(), _StatusPill(status: machine.status), ], ), const SizedBox(height: 10), Text( machine.name, maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.titleMedium?.copyWith(fontSize: 14), ), const SizedBox(height: 2), Text( machine.typeLabel, style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontSize: 11), ), if (canStart) ...[ const Spacer(), Text( 'Démarrer', style: TextStyle( color: statusColor, fontSize: 11, fontWeight: FontWeight.w600, ), ), ], ], ), ), ), ); } } class _StatusPill extends StatelessWidget { const _StatusPill({required this.status}); final String status; @override Widget build(BuildContext context) { final color = MachineStatusTheme.color(status); return Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), decoration: BoxDecoration( color: color.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(6), ), child: Text( MachineStatusTheme.label(status), style: TextStyle(color: color, fontSize: 10, fontWeight: FontWeight.w700), ), ); } } /// Statistiques rapides du parc machines. class MachineStatsRow extends StatelessWidget { const MachineStatsRow({super.key, required this.machines}); final List machines; @override Widget build(BuildContext context) { final available = machines.where((m) => m.status == 'available').length; final running = machines.where((m) => m.status == 'running').length; final reserved = machines.where((m) => m.status == 'reserved').length; return Row( children: [ Expanded(child: _StatBox(value: '$available', label: 'Libres', color: AppColors.machineAvailable)), const SizedBox(width: 8), Expanded(child: _StatBox(value: '$running', label: 'En cours', color: AppColors.machineRunning)), const SizedBox(width: 8), Expanded(child: _StatBox(value: '$reserved', label: 'Réservées', color: AppColors.machineReserved)), ], ); } } class _StatBox extends StatelessWidget { const _StatBox({required this.value, required this.label, required this.color}); final String value; final String label; final Color color; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8), decoration: BoxDecoration( color: color.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(10), ), child: Column( children: [ Text(value, style: TextStyle(color: color, fontSize: 20, fontWeight: FontWeight.w700)), Text(label, style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontSize: 11)), ], ), ); } } /// Actions rapides — démarrage en quelques clics. class QuickActionsRow extends StatelessWidget { const QuickActionsRow({super.key}); @override Widget build(BuildContext context) { return Row( children: [ Expanded( child: _QuickAction( icon: Icons.qr_code_scanner_rounded, label: 'Scanner', color: AppColors.primary, onTap: () => context.push(AppRoutes.washScan), ), ), const SizedBox(width: 10), Expanded( child: _QuickAction( icon: Icons.event_available_outlined, label: 'Réserver', color: AppColors.machineReserved, onTap: () => context.go(AppRoutes.bookings), ), ), const SizedBox(width: 10), Expanded( child: _QuickAction( icon: Icons.add_card_outlined, label: 'Recharger', color: AppColors.secondary, onTap: () => context.go(AppRoutes.wallet), ), ), ], ); } } class _QuickAction extends StatelessWidget { const _QuickAction({ required this.icon, required this.label, required this.color, required this.onTap, }); final IconData icon; final String label; final Color color; final VoidCallback onTap; @override Widget build(BuildContext context) { return Material( color: AppColors.surface, elevation: 1, shadowColor: Colors.black.withValues(alpha: 0.06), borderRadius: BorderRadius.circular(12), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.symmetric(vertical: 14), child: Column( children: [ Icon(icon, color: color, size: 26), const SizedBox(height: 6), Text(label, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)), ], ), ), ), ); } } /// Bannière lavage en cours. class ActiveWashBanner extends StatelessWidget { const ActiveWashBanner({ super.key, required this.machineName, required this.onTap, }); final String machineName; final VoidCallback onTap; @override Widget build(BuildContext context) { return Material( color: AppColors.primary, borderRadius: BorderRadius.circular(14), elevation: 2, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(14), child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Container( width: 44, height: 44, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(12), ), child: const Icon(Icons.local_laundry_service, color: Colors.white), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Lavage en cours', style: TextStyle(color: Colors.white70, fontSize: 12), ), Text( machineName, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w600, fontSize: 16, ), ), ], ), ), const Icon(Icons.chevron_right, color: Colors.white), ], ), ), ), ); } } /// Bottom sheet détail machine + actions. class MachineActionSheet { static Future show( BuildContext context, { required Machine machine, required VoidCallback onStart, VoidCallback? onReserve, }) { final statusColor = MachineStatusTheme.color(machine.status); final canStart = MachineStatusTheme.canStart(machine.status); return showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (context) => Padding( padding: EdgeInsets.fromLTRB(20, 16, 20, 20 + MediaQuery.of(context).padding.bottom), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: Container( width: 40, height: 4, decoration: BoxDecoration( color: AppColors.divider, borderRadius: BorderRadius.circular(2), ), ), ), const SizedBox(height: 20), Row( children: [ Container( width: 52, height: 52, decoration: BoxDecoration( color: statusColor.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(14), ), child: Icon( MachineStatusTheme.iconForType(machine.type), color: statusColor, size: 28, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(machine.name, style: Theme.of(context).textTheme.titleMedium), Text(machine.typeLabel, style: Theme.of(context).textTheme.bodyMedium), ], ), ), _StatusPill(status: machine.status), ], ), const SizedBox(height: 20), if (canStart) ...[ ElevatedButton.icon( onPressed: () { Navigator.pop(context); onStart(); }, icon: const Icon(Icons.play_arrow_rounded), label: const Text('Démarrer maintenant'), ), const SizedBox(height: 10), OutlinedButton.icon( onPressed: () { Navigator.pop(context); onReserve?.call(); }, icon: const Icon(Icons.event_outlined), label: const Text('Réserver un créneau'), ), ] else Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: statusColor.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(12), ), child: Text( machine.status == 'running' ? 'Cette machine est en cours d\'utilisation.' : machine.status == 'reserved' ? 'Machine réservée — elle sera disponible sur votre créneau.' : 'Machine indisponible pour le moment.', textAlign: TextAlign.center, style: TextStyle(color: statusColor, fontWeight: FontWeight.w500), ), ), ], ), ), ); } }