diff --git a/eme-frontend/lib/screens/responsable_screen.dart b/eme-frontend/lib/screens/responsable_screen.dart index 2623629..0b9578e 100644 --- a/eme-frontend/lib/screens/responsable_screen.dart +++ b/eme-frontend/lib/screens/responsable_screen.dart @@ -2238,6 +2238,57 @@ class _PanelEmptyState extends StatelessWidget { } } +class _TableauAvecDefilementHorizontal extends StatefulWidget { + const _TableauAvecDefilementHorizontal({ + required this.largeurMinimale, + required this.child, + }); + + final double largeurMinimale; + final Widget child; + + @override + State<_TableauAvecDefilementHorizontal> createState() => + _TableauAvecDefilementHorizontalState(); +} + +class _TableauAvecDefilementHorizontalState + extends State<_TableauAvecDefilementHorizontal> { + final ScrollController _controleur = ScrollController(); + + @override + void dispose() { + _controleur.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Scrollbar( + controller: _controleur, + thumbVisibility: true, + trackVisibility: true, + interactive: true, + thickness: 8, + radius: const Radius.circular(4), + scrollbarOrientation: ScrollbarOrientation.bottom, + notificationPredicate: (notification) => + notification.metrics.axis == Axis.horizontal, + child: SingleChildScrollView( + controller: _controleur, + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.only(bottom: 14), + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: widget.largeurMinimale), + child: widget.child, + ), + ), + ), + ); + } +} + class _VueEmpruntsTableau extends StatelessWidget { const _VueEmpruntsTableau({ required this.items, @@ -2556,56 +2607,51 @@ class _VueEmpruntsTableau extends StatelessWidget { color: Colors.white, border: Border.all(color: EnsupColors.line), ), - child: SingleChildScrollView( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: ConstrainedBox( - constraints: BoxConstraints(minWidth: largeurDisponible), - child: DataTable( - sortColumnIndex: _sortColumnIndex, - sortAscending: ordre == "asc", - headingRowColor: WidgetStatePropertyAll( - EnsupColors.soft.withValues(alpha: 0.8), - ), - headingTextStyle: GoogleFonts.titilliumWeb( - fontSize: 12, - fontWeight: FontWeight.w700, - color: EnsupColors.text2, - ), - dataTextStyle: GoogleFonts.titilliumWeb( - fontSize: 12, - color: EnsupColors.text, - ), - columns: [ - const DataColumn(label: Text("N°")), - DataColumn( - label: const Text("Matériel"), - onSort: (_, _) => onTri("materiel"), - ), - DataColumn( - label: const Text("Étudiant"), - onSort: (_, _) => onTri("etudiant"), - ), - DataColumn( - label: const Text("Date d’emprunt"), - onSort: (_, _) => onTri("dateEmprunt"), - ), - DataColumn( - label: const Text("Retour prévu"), - onSort: (_, _) => onTri("dateRetourPrevue"), - ), - DataColumn( - label: const Text("Retour réel"), - onSort: (_, _) => onTri("dateRetourReelle"), - ), - DataColumn( - label: const Text("Statut"), - onSort: (_, _) => onTri("statut"), - ), - ], - rows: items.map(_ligneTableau).toList(), - ), + child: _TableauAvecDefilementHorizontal( + largeurMinimale: largeurDisponible, + child: DataTable( + sortColumnIndex: _sortColumnIndex, + sortAscending: ordre == "asc", + headingRowColor: WidgetStatePropertyAll( + EnsupColors.soft.withValues(alpha: 0.8), ), + headingTextStyle: GoogleFonts.titilliumWeb( + fontSize: 12, + fontWeight: FontWeight.w700, + color: EnsupColors.text2, + ), + dataTextStyle: GoogleFonts.titilliumWeb( + fontSize: 12, + color: EnsupColors.text, + ), + columns: [ + const DataColumn(label: Text("N°")), + DataColumn( + label: const Text("Matériel"), + onSort: (_, _) => onTri("materiel"), + ), + DataColumn( + label: const Text("Étudiant"), + onSort: (_, _) => onTri("etudiant"), + ), + DataColumn( + label: const Text("Date d’emprunt"), + onSort: (_, _) => onTri("dateEmprunt"), + ), + DataColumn( + label: const Text("Retour prévu"), + onSort: (_, _) => onTri("dateRetourPrevue"), + ), + DataColumn( + label: const Text("Retour réel"), + onSort: (_, _) => onTri("dateRetourReelle"), + ), + DataColumn( + label: const Text("Statut"), + onSort: (_, _) => onTri("statut"), + ), + ], + rows: items.map(_ligneTableau).toList(), ), ), ), @@ -3098,55 +3144,50 @@ class _VueMaterielsTableau extends StatelessWidget { color: Colors.white, border: Border.all(color: EnsupColors.line), ), - child: SingleChildScrollView( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: ConstrainedBox( - constraints: BoxConstraints(minWidth: largeurDisponible), - child: DataTable( - sortColumnIndex: _sortColumnIndex, - sortAscending: ordre == "asc", - headingRowColor: WidgetStatePropertyAll( - EnsupColors.soft.withValues(alpha: 0.8), - ), - headingTextStyle: GoogleFonts.titilliumWeb( - fontSize: 12, - fontWeight: FontWeight.w700, - color: EnsupColors.text2, - ), - dataTextStyle: GoogleFonts.titilliumWeb( - fontSize: 12, - color: EnsupColors.text, - ), - columns: [ - DataColumn( - label: const Text("Référence"), - onSort: (_, _) => onTri("reference"), - ), - DataColumn( - label: const Text("Matériel"), - onSort: (_, _) => onTri("nom"), - ), - DataColumn( - label: const Text("Catégorie"), - onSort: (_, _) => onTri("categorie"), - ), - DataColumn( - label: const Text("Marque / modèle"), - onSort: (_, _) => onTri("marque"), - ), - DataColumn( - label: const Text("État"), - onSort: (_, _) => onTri("etatGeneral"), - ), - DataColumn( - label: const Text("Statut"), - onSort: (_, _) => onTri("statut"), - ), - ], - rows: items.map(_ligneTableau).toList(), - ), + child: _TableauAvecDefilementHorizontal( + largeurMinimale: largeurDisponible, + child: DataTable( + sortColumnIndex: _sortColumnIndex, + sortAscending: ordre == "asc", + headingRowColor: WidgetStatePropertyAll( + EnsupColors.soft.withValues(alpha: 0.8), ), + headingTextStyle: GoogleFonts.titilliumWeb( + fontSize: 12, + fontWeight: FontWeight.w700, + color: EnsupColors.text2, + ), + dataTextStyle: GoogleFonts.titilliumWeb( + fontSize: 12, + color: EnsupColors.text, + ), + columns: [ + DataColumn( + label: const Text("Référence"), + onSort: (_, _) => onTri("reference"), + ), + DataColumn( + label: const Text("Matériel"), + onSort: (_, _) => onTri("nom"), + ), + DataColumn( + label: const Text("Catégorie"), + onSort: (_, _) => onTri("categorie"), + ), + DataColumn( + label: const Text("Marque / modèle"), + onSort: (_, _) => onTri("marque"), + ), + DataColumn( + label: const Text("État"), + onSort: (_, _) => onTri("etatGeneral"), + ), + DataColumn( + label: const Text("Statut"), + onSort: (_, _) => onTri("statut"), + ), + ], + rows: items.map(_ligneTableau).toList(), ), ), ), @@ -3626,52 +3667,47 @@ class _VueAnomaliesTableau extends StatelessWidget { color: Colors.white, border: Border.all(color: EnsupColors.line), ), - child: SingleChildScrollView( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: ConstrainedBox( - constraints: BoxConstraints(minWidth: largeurDisponible), - child: DataTable( - sortColumnIndex: _sortColumnIndex, - sortAscending: ordre == "asc", - headingRowColor: WidgetStatePropertyAll( - EnsupColors.soft.withValues(alpha: 0.8), - ), - headingTextStyle: GoogleFonts.titilliumWeb( - fontSize: 12, - fontWeight: FontWeight.w700, - color: EnsupColors.text2, - ), - dataTextStyle: GoogleFonts.titilliumWeb( - fontSize: 12, - color: EnsupColors.text, - ), - columns: [ - DataColumn( - label: const Text("Détection"), - onSort: (_, _) => onTri("dateDetection"), - ), - DataColumn( - label: const Text("Anomalie"), - onSort: (_, _) => onTri("type"), - ), - DataColumn( - label: const Text("Matériel"), - onSort: (_, _) => onTri("materiel"), - ), - DataColumn( - label: const Text("Étudiant"), - onSort: (_, _) => onTri("etudiant"), - ), - DataColumn( - label: const Text("Statut"), - onSort: (_, _) => onTri("statut"), - ), - const DataColumn(label: Text("Action")), - ], - rows: items.map(_ligneTableau).toList(), - ), + child: _TableauAvecDefilementHorizontal( + largeurMinimale: largeurDisponible, + child: DataTable( + sortColumnIndex: _sortColumnIndex, + sortAscending: ordre == "asc", + headingRowColor: WidgetStatePropertyAll( + EnsupColors.soft.withValues(alpha: 0.8), ), + headingTextStyle: GoogleFonts.titilliumWeb( + fontSize: 12, + fontWeight: FontWeight.w700, + color: EnsupColors.text2, + ), + dataTextStyle: GoogleFonts.titilliumWeb( + fontSize: 12, + color: EnsupColors.text, + ), + columns: [ + DataColumn( + label: const Text("Détection"), + onSort: (_, _) => onTri("dateDetection"), + ), + DataColumn( + label: const Text("Anomalie"), + onSort: (_, _) => onTri("type"), + ), + DataColumn( + label: const Text("Matériel"), + onSort: (_, _) => onTri("materiel"), + ), + DataColumn( + label: const Text("Étudiant"), + onSort: (_, _) => onTri("etudiant"), + ), + DataColumn( + label: const Text("Statut"), + onSort: (_, _) => onTri("statut"), + ), + const DataColumn(label: Text("Action")), + ], + rows: items.map(_ligneTableau).toList(), ), ), ), @@ -4250,52 +4286,47 @@ class _VueHistoriqueTableau extends StatelessWidget { color: Colors.white, border: Border.all(color: EnsupColors.line), ), - child: SingleChildScrollView( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: ConstrainedBox( - constraints: BoxConstraints(minWidth: largeurDisponible), - child: DataTable( - sortColumnIndex: _sortColumnIndex, - sortAscending: ordre == "asc", - headingRowColor: WidgetStatePropertyAll( - EnsupColors.soft.withValues(alpha: 0.8), - ), - headingTextStyle: GoogleFonts.titilliumWeb( - fontSize: 12, - fontWeight: FontWeight.w700, - color: EnsupColors.text2, - ), - dataTextStyle: GoogleFonts.titilliumWeb( - fontSize: 12, - color: EnsupColors.text, - ), - columns: [ - DataColumn( - label: const Text("Date"), - onSort: (_, _) => onTri("dateAction"), - ), - DataColumn( - label: const Text("Action"), - onSort: (_, _) => onTri("action"), - ), - DataColumn( - label: const Text("Utilisateur"), - onSort: (_, _) => onTri("utilisateur"), - ), - DataColumn( - label: const Text("Matériel"), - onSort: (_, _) => onTri("materiel"), - ), - DataColumn( - label: const Text("Emprunt"), - onSort: (_, _) => onTri("emprunt"), - ), - const DataColumn(label: Text("Point d’opération")), - ], - rows: items.map(_ligneTableau).toList(), - ), + child: _TableauAvecDefilementHorizontal( + largeurMinimale: largeurDisponible, + child: DataTable( + sortColumnIndex: _sortColumnIndex, + sortAscending: ordre == "asc", + headingRowColor: WidgetStatePropertyAll( + EnsupColors.soft.withValues(alpha: 0.8), ), + headingTextStyle: GoogleFonts.titilliumWeb( + fontSize: 12, + fontWeight: FontWeight.w700, + color: EnsupColors.text2, + ), + dataTextStyle: GoogleFonts.titilliumWeb( + fontSize: 12, + color: EnsupColors.text, + ), + columns: [ + DataColumn( + label: const Text("Date"), + onSort: (_, _) => onTri("dateAction"), + ), + DataColumn( + label: const Text("Action"), + onSort: (_, _) => onTri("action"), + ), + DataColumn( + label: const Text("Utilisateur"), + onSort: (_, _) => onTri("utilisateur"), + ), + DataColumn( + label: const Text("Matériel"), + onSort: (_, _) => onTri("materiel"), + ), + DataColumn( + label: const Text("Emprunt"), + onSort: (_, _) => onTri("emprunt"), + ), + const DataColumn(label: Text("Point d’opération")), + ], + rows: items.map(_ligneTableau).toList(), ), ), ), diff --git a/review.md b/review.md index 20d3165..2806fa5 100644 --- a/review.md +++ b/review.md @@ -525,6 +525,14 @@ En terminal interactif (VS Code), `migrate dev` fonctionne normalement (taper `y - La confirmation reste enregistrée, le message de succès reste visible et les données de supervision sont rechargées normalement. - Vérifications réussies : `dart analyze` sans erreur et serveur Flutter opérationnel sur le port 5000. +### Étape 55 — Défilement horizontal visible des tableaux responsable +- Les tableaux Emprunts, Stock matériel, Anomalies et Historique restent accessibles sur les écrans de largeur intermédiaire. +- Un composant partagé gère le défilement horizontal avec son propre contrôleur Flutter. +- La barre horizontale est visible, interactive et positionnée sous le contenu de chaque tableau. +- Les dernières colonnes, notamment les statuts et les actions, peuvent être atteintes sans modifier la disposition sur grand écran. +- Les listes compactes mobiles existantes sont conservées sous leurs seuils responsifs. +- Vérifications réussies : les quatre tableaux utilisent le composant partagé, `dart analyze` est sans erreur et le serveur Flutter répond sur le port 5000. + --- -*Dernière mise à jour : 2026-07-24 — Rafraîchissement après décision responsable corrigé.* +*Dernière mise à jour : 2026-07-24 — Défilement horizontal des tableaux responsable ajouté.*