import "package:flutter/material.dart"; import "package:google_fonts/google_fonts.dart"; import "../demo_identity.dart"; import "../theme/ensup_colors.dart"; import "../models/emprunt_item.dart"; import "../models/anomalie_retour.dart"; import "../models/checklist_element_item.dart"; import "../services/emprunt_service.dart"; import "../widgets/ensup_top_bar.dart"; import "../widgets/brand_corners.dart"; import "../widgets/etat_checklist.dart"; import "../widgets/app_message.dart"; import "resultat_retour_screen.dart"; class ChecklistRetourScreen extends StatefulWidget { const ChecklistRetourScreen({super.key, required this.emprunt}); final EmpruntItem emprunt; @override State createState() => _ChecklistRetourScreenState(); } class _ChecklistRetourScreenState extends State { late final List _checklistDepart = widget.emprunt.checklistDepart.isNotEmpty ? widget.emprunt.checklistDepart : (widget.emprunt.materiel.accessoires.isEmpty ? [ ChecklistElementItem( accessoireId: null, nom: widget.emprunt.materiel.nom, etat: "PRESENT", quantite: 1, ), ] : widget.emprunt.materiel.accessoires .map( (nom) => ChecklistElementItem( accessoireId: null, nom: nom, etat: "PRESENT", quantite: 1, ), ) .toList()); late final List _elements = _checklistDepart .map((element) => element.nom) .toList(); late final List _etatsInitiales = _checklistDepart .map((element) => _etatDepuisApi(element.etat)) .toList(); late final List _etats = List.from( _etatsInitiales, ); final TextEditingController _commentaireController = TextEditingController(); bool _envoiEnCours = false; bool _alerteEnCours = false; bool _alerteEnvoyee = false; @override void dispose() { _commentaireController.dispose(); super.dispose(); } Future _valider() async { setState(() => _envoiEnCours = true); final anomalies = []; for (var i = 0; i < _etats.length; i++) { if (_etats[i] != _etatsInitiales[i]) { anomalies.add( AnomalieRetour( element: _elements[i], etatRetour: "${_etatLabel(_etatsInitiales[i])} → ${_etatLabel(_etats[i])}", ), ); } } try { final resultat = await EmpruntService.restituerEmprunt( empruntId: widget.emprunt.id, commentaireRetour: _commentaireController.text, elements: _elements.asMap().entries.map((entry) { final elementDepart = _checklistDepart[entry.key]; return { if (elementDepart.accessoireId != null) "accessoireId": elementDepart.accessoireId, "nomElement": entry.value, "etat": _etatApi(_etats[entry.key]), "quantiteConstatee": _quantiteConstatee(entry.key), }; }).toList(), ); if (!mounted) { return; } final anomaliesAffichees = resultat.statut == "CLOTURE" ? [] : anomalies; Navigator.of(context).push( MaterialPageRoute( builder: (_) => ResultatRetourScreen( emprunt: widget.emprunt.copyWith(statut: resultat.statut), anomalies: anomaliesAffichees, ), ), ); } catch (error) { if (!mounted) { return; } AppMessage.erreur(context, error); } finally { if (mounted) { setState(() => _envoiEnCours = false); } } } Future _changerEtat(int index, EtatChecklist etat) async { setState(() => _etats[index] = etat); if (_alerteEnvoyee || etat == _etatsInitiales[index]) { return; } setState(() { _alerteEnvoyee = true; _alerteEnCours = true; }); try { await EmpruntService.signalerTentativeRetour( empruntId: widget.emprunt.id, nomElement: _elements[index], etatInitial: _etatApi(_etatsInitiales[index]), etatDemande: _etatApi(etat), ); if (!mounted) return; AppMessage.alerte( context, "Le responsable a été alerté de cette tentative de modification.", titre: "Changement signalé", ); } catch (error) { if (!mounted) return; setState(() => _alerteEnvoyee = false); AppMessage.erreur(context, error); } finally { if (mounted) { setState(() => _alerteEnCours = false); } } } int _quantiteConstatee(int index) { if (_etats[index] == EtatChecklist.absent) { return 0; } final quantiteDepart = _checklistDepart[index].quantite; return quantiteDepart > 0 ? quantiteDepart : 1; } static EtatChecklist _etatDepuisApi(String etat) { return switch (etat) { "ABSENT" => EtatChecklist.absent, "DETERIORE" => EtatChecklist.deteriore, _ => EtatChecklist.present, }; } String _etatApi(EtatChecklist etat) { switch (etat) { case EtatChecklist.present: return "PRESENT"; case EtatChecklist.absent: return "ABSENT"; case EtatChecklist.deteriore: return "DETERIORE"; } } String _etatLabel(EtatChecklist etat) { return switch (etat) { EtatChecklist.present => "Présent", EtatChecklist.absent => "Absent", EtatChecklist.deteriore => "Détérioré", }; } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: EnsupColors.soft, body: Stack( children: [ Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 1280, maxHeight: 820), child: Container( margin: const EdgeInsets.all(24), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: EnsupColors.text.withValues(alpha: 0.14), blurRadius: 60, offset: const Offset(0, 18), ), ], ), clipBehavior: Clip.antiAlias, child: Column( children: [ EnsupTopBar( brandText: "Checklist de retour", campusTag: DemoIdentity.campusTag, userName: DemoIdentity.userName, userInitials: DemoIdentity.userInitials, onBack: () => Navigator.of(context).pop(), ), Expanded( child: SingleChildScrollView( padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 26, ), child: LayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth < 780) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _colonneGauche(), const SizedBox(height: 22), _colonneDroite(), ], ); } return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(flex: 3, child: _colonneGauche()), const SizedBox(width: 20), Expanded(flex: 2, child: _colonneDroite()), ], ); }, ), ), ), ], ), ), ), ), const Positioned.fill(child: BrandCorners()), ], ), ); } Widget _colonneGauche() { final materiel = widget.emprunt.materiel; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( color: EnsupColors.soft, borderRadius: BorderRadius.circular(12), border: Border.all( color: EnsupColors.teal.withValues(alpha: 0.15), width: 1.5, ), ), child: Row( children: [ Container( width: 38, height: 38, decoration: BoxDecoration( color: EnsupColors.teal.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(9), ), child: Icon(materiel.icon, color: EnsupColors.teal, size: 18), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( materiel.nom, style: GoogleFonts.darkerGrotesque( fontWeight: FontWeight.w800, fontSize: 16, ), ), Text( "Emprunté le ${widget.emprunt.dateEmprunt}", style: GoogleFonts.titilliumWeb( fontSize: 12, color: EnsupColors.muted, ), ), ], ), ), ], ), ), const SizedBox(height: 16), ChecklistTableau( accessoires: _elements, etats: _etats, onChanged: _changerEtat, ), ], ); } Widget _colonneDroite() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ChecklistInstructions( titre: "État validé au départ", texte: "La restitution reprend automatiquement l'état validé au départ : " "${_checklistDepart.map((element) => "${element.nom} ${_etatLabel(_etatDepuisApi(element.etat)).toLowerCase()}").join(" · ")}. " "Toute modification alerte immédiatement un responsable.", ), const SizedBox(height: 14), ChecklistCommentaire( controller: _commentaireController, hint: "Commentaire optionnel...", ), const SizedBox(height: 14), SizedBox( width: double.infinity, child: FilledButton.icon( onPressed: _envoiEnCours || _alerteEnCours ? null : _valider, icon: _envoiEnCours || _alerteEnCours ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : const Icon(Icons.check, size: 18), label: Text( _alerteEnCours ? "Alerte en cours..." : _envoiEnCours ? "Enregistrement..." : "Valider le retour", style: GoogleFonts.darkerGrotesque( fontWeight: FontWeight.w700, fontSize: 16, ), ), style: FilledButton.styleFrom( backgroundColor: EnsupColors.blue3, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), ), ), ], ); } }