import "package:flutter/material.dart"; import "package:google_fonts/google_fonts.dart"; import "../demo_identity.dart"; import "../services/responsable_service.dart"; import "../theme/ensup_colors.dart"; import "../widgets/brand_corners.dart"; import "../widgets/ensup_top_bar.dart"; class ResponsableScreen extends StatefulWidget { const ResponsableScreen({super.key}); @override State createState() => _ResponsableScreenState(); } class _ResponsableScreenState extends State { late Future<_ResponsableData> _future = _charger(); int _onglet = 0; bool _transitionEnCours = false; Future<_ResponsableData> _charger() async { final dashboard = await ResponsableService.dashboard(); final emprunts = await ResponsableService.emprunts(); final materiels = await ResponsableService.materiels(); final anomalies = await ResponsableService.anomalies(); final notifications = await ResponsableService.notifications(); final historique = await ResponsableService.historique(); return _ResponsableData( dashboard: dashboard, emprunts: emprunts, materiels: materiels, anomalies: anomalies, notifications: notifications, historique: historique, ); } void _rafraichir() { setState(() => _future = _charger()); } Future _changerStatut(Map anomalie) async { final prochain = _prochainStatut(anomalie["statut"] as String?); if (prochain == null || _transitionEnCours) { return; } setState(() => _transitionEnCours = true); try { await ResponsableService.changerStatutAnomalie( anomalie["id"] as int, prochain, ); _rafraichir(); } catch (error) { if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(error.toString()))); } finally { if (mounted) { setState(() => _transitionEnCours = false); } } } String? _prochainStatut(String? statut) { return switch (statut) { "DETECTEE" => "EN_COURS_TRAITEMENT", "EN_COURS_TRAITEMENT" => "RESOLUE", "RESOLUE" => "CLOTUREE", _ => null, }; } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: EnsupColors.soft, body: Stack( children: [ Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 1320, maxHeight: 860), 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: "EME Responsable", campusTag: DemoIdentity.loginCampusTag, userName: "Karim Benali", userInitials: "KB", onBack: () => Navigator.of(context).pop(), ), Expanded( child: FutureBuilder<_ResponsableData>( future: _future, builder: (context, snapshot) { if (snapshot.connectionState != ConnectionState.done) { return const Center( child: CircularProgressIndicator(), ); } if (snapshot.hasError) { return _Erreur( message: snapshot.error.toString(), onRetry: _rafraichir, ); } return _contenu(snapshot.data!); }, ), ), ], ), ), ), ), const Positioned.fill(child: BrandCorners()), ], ), ); } Widget _contenu(_ResponsableData data) { final vues = [ _VueDashboard(data: data), _VueListe(titre: "Emprunts", items: data.emprunts, builder: _empruntTile), _VueListe(titre: "Stock", items: data.materiels, builder: _materielTile), _VueListe( titre: "Anomalies", items: data.anomalies, builder: _anomalieTile, ), _VueListe( titre: "Notifications", items: data.notifications, builder: _notificationTile, ), _VueListe( titre: "Historique", items: data.historique, builder: _historiqueTile, ), ]; return Column( children: [ _Onglets( index: _onglet, labels: const [ "Dashboard", "Emprunts", "Stock", "Anomalies", "Notifications", "Historique", ], onChanged: (index) => setState(() => _onglet = index), ), Expanded(child: vues[_onglet]), ], ); } Widget _empruntTile(Map item) { final etudiant = Map.from(item["etudiant"] as Map); final materiel = Map.from(item["materiel"] as Map); return _InfoTile( icon: Icons.assignment_outlined, title: "${materiel["nom"]}", subtitle: "${etudiant["prenom"]} ${etudiant["nom"]} · retour prévu ${_date(item["dateRetourPrevue"])}", trailing: _Badge("${item["statut"]}"), ); } Widget _materielTile(Map item) { final categorie = Map.from(item["categorie"] as Map); return _InfoTile( icon: Icons.inventory_2_outlined, title: "${item["nom"]}", subtitle: "${item["reference"]} · ${categorie["nom"]}", trailing: _Badge("${item["statut"]}"), ); } Widget _anomalieTile(Map item) { final materiel = Map.from(item["materiel"] as Map); final prochain = _prochainStatut(item["statut"] as String?); return _InfoTile( icon: Icons.report_problem_outlined, title: "${item["type"]} · ${materiel["nom"]}", subtitle: "${item["description"]}", trailing: prochain == null ? _Badge("${item["statut"]}") : FilledButton( onPressed: _transitionEnCours ? null : () => _changerStatut(item), child: Text(prochain), ), ); } Widget _notificationTile(Map item) { return _InfoTile( icon: item["lu"] == true ? Icons.notifications_none : Icons.notifications_active_outlined, title: "${item["titre"]}", subtitle: "${item["message"]}", trailing: _Badge(item["lu"] == true ? "LUE" : "NON LUE"), ); } Widget _historiqueTile(Map item) { final utilisateur = Map.from(item["utilisateur"] as Map); return _InfoTile( icon: Icons.history, title: "${item["action"]}", subtitle: "${_date(item["dateAction"])} · ${utilisateur["prenom"]} ${utilisateur["nom"]}", trailing: const Icon(Icons.chevron_right, color: EnsupColors.muted), ); } String _date(Object? value) { if (value == null) return "-"; final parsed = DateTime.tryParse(value.toString()); if (parsed == null) return value.toString(); return "${parsed.day.toString().padLeft(2, "0")}/" "${parsed.month.toString().padLeft(2, "0")}/${parsed.year}"; } } class _ResponsableData { const _ResponsableData({ required this.dashboard, required this.emprunts, required this.materiels, required this.anomalies, required this.notifications, required this.historique, }); final Map dashboard; final List> emprunts; final List> materiels; final List> anomalies; final List> notifications; final List> historique; } class _VueDashboard extends StatelessWidget { const _VueDashboard({required this.data}); final _ResponsableData data; @override Widget build(BuildContext context) { final kpis = Map.from(data.dashboard["kpis"] as Map); return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Wrap( spacing: 14, runSpacing: 14, children: [ _KpiCard( label: "Matériels", value: _total(kpis["materiels"]), icon: Icons.inventory_2_outlined, ), _KpiCard( label: "Emprunts", value: _total(kpis["emprunts"]), icon: Icons.assignment_outlined, ), _KpiCard( label: "Anomalies", value: _total(kpis["anomalies"]), icon: Icons.report_problem_outlined, ), _KpiCard( label: "Notifications", value: "${kpis["notificationsNonLues"] ?? 0}", icon: Icons.notifications_outlined, ), ], ), ); } String _total(Object? repartition) { final map = Map.from(repartition as Map); return "${map["total"] ?? 0}"; } } class _VueListe extends StatelessWidget { const _VueListe({ required this.titre, required this.items, required this.builder, }); final String titre; final List> items; final Widget Function(Map) builder; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "$titre (${items.length})", style: GoogleFonts.darkerGrotesque( fontSize: 26, fontWeight: FontWeight.w900, color: EnsupColors.text, ), ), const SizedBox(height: 14), Expanded( child: ListView.separated( itemCount: items.length, separatorBuilder: (_, _) => const SizedBox(height: 10), itemBuilder: (_, index) => builder(items[index]), ), ), ], ), ); } } class _Onglets extends StatelessWidget { const _Onglets({ required this.index, required this.labels, required this.onChanged, }); final int index; final List labels; final ValueChanged onChanged; @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.fromLTRB(20, 16, 20, 8), decoration: const BoxDecoration( color: EnsupColors.soft, border: Border(bottom: BorderSide(color: EnsupColors.line)), ), child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: List.generate(labels.length, (i) { final selected = i == index; return Padding( padding: const EdgeInsets.only(right: 8), child: ChoiceChip( selected: selected, label: Text(labels[i]), onSelected: (_) => onChanged(i), ), ); }), ), ), ); } } class _KpiCard extends StatelessWidget { const _KpiCard({ required this.label, required this.value, required this.icon, }); final String label; final String value; final IconData icon; @override Widget build(BuildContext context) { return Container( width: 210, padding: const EdgeInsets.all(18), decoration: BoxDecoration( border: Border.all(color: EnsupColors.line), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon(icon, color: EnsupColors.blue3), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( value, style: GoogleFonts.darkerGrotesque( fontSize: 30, fontWeight: FontWeight.w900, ), ), Text( label, style: GoogleFonts.titilliumWeb( fontSize: 13, color: EnsupColors.muted, ), ), ], ), ], ), ); } } class _InfoTile extends StatelessWidget { const _InfoTile({ required this.icon, required this.title, required this.subtitle, required this.trailing, }); final IconData icon; final String title; final String subtitle; final Widget trailing; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: EnsupColors.line), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon(icon, color: EnsupColors.teal), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: GoogleFonts.darkerGrotesque( fontSize: 19, fontWeight: FontWeight.w800, ), ), Text(subtitle, maxLines: 2, overflow: TextOverflow.ellipsis), ], ), ), const SizedBox(width: 12), trailing, ], ), ); } } class _Badge extends StatelessWidget { const _Badge(this.label); final String label; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: EnsupColors.blue3.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(999), ), child: Text( label, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w700), ), ); } } class _Erreur extends StatelessWidget { const _Erreur({required this.message, required this.onRetry}); final String message; final VoidCallback onRetry; @override Widget build(BuildContext context) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(message, textAlign: TextAlign.center), const SizedBox(height: 12), OutlinedButton.icon( onPressed: onRetry, icon: const Icon(Icons.refresh), label: const Text("Réessayer"), ), ], ), ); } }