manager-app · proposition d'interface

Refonte Configuration & Sections

Le blanc de l'écran actuel ne vient pas de la mise en page de configuration_detail_screen.dart : il vient de quatre composants partagés qui imposent des pastilles pleine largeur, des labels centrés et des listes horizontales à hauteur fixe. On les corrige une fois, et les 13 types de section en héritent.

Écran Configuration Écran Section 3 gabarits pour 13 types 2 dialogues Tokens de constants.dart
Rien à inventer : le système existe

constants.dart porte déjà un système complet — kSurface2, kInk2, kLine, kSpace1kSpace8, kRadiusInput 5 / kRadiusCard 8 / kRadiusShell 10 / kRadiusPill, kTitleCard, kLabelField, kTextHint. confirmation_dialog.dart et resource_picker.dart l'appliquent déjà.

Ces deux écrans sont simplement antérieurs à ce système. Il n'y a donc pas de direction visuelle à trancher ici : toutes les valeurs des maquettes ci-dessous sont reprises de constants.dart, et là où une première version de cette page avait inventé un rayon ou une capitale, c'est le token qui a gagné.

01

Ce qui fabrique le blanc

Quatre causes, toutes dans des composants réutilisés par tous les écrans d'édition.

Cause
Effet observé
Correction
Le champ texte est une pastille pleine largeur text_field_container.dart — width: double.infinity, radius 29, margin 10px
« Config test » (11 caractères) occupe une barre grise de 1 400 px de large et 60 px de haut. Le compteur 11/50 se retrouve à un mètre du texte.
Champ à largeur intrinsèque (rang de 12 colonnes), radius 8, bordure 1 px au lieu d'un fond plein.
Le label est à gauche, dans un Row string_input_container.dart · resource_input_container.dart
Chaque ligne a une colonne de label d'une largeur différente : rien ne s'aligne verticalement, et le champ démarre à un endroit imprévisible.
Label au-dessus, capitales 10,5 px lettrées. Une grille, une seule gouttière.
Deux composants se centrent eux-mêmes mainAxisAlignment: MainAxisAlignment.center
« Titre affiché », « Langues » et « Hors ligne » flottent au milieu de la carte, désolidarisés des champs au-dessus.
Aucun composant ne décide de son alignement : c'est le parent qui place.
Les sections sont une liste horizontale à hauteur fixe section_reorderList.dart — height: size.height * 0.3, cartes 150×150
30 % de la hauteur d'écran réservés quoi qu'il arrive, cartes carrées trop grandes, et les sections au-delà de la 8e sont hors champ à droite sans indice visuel.
Grille responsive auto-fill minmax(146px, 1fr), hauteur pilotée par le contenu, réordonnancement conservé.
02

Le champ, avant et après

C'est la brique qui décide de la densité de tous les écrans. Elle se corrige dans trois fichiers.

Aujourd'hui
Identifiant :
Config test 11/50
Titre affiché: Config Test3 / 3 langues traduites
Deux champs, deux alignements, 210 px de hauteur. Le libellé « Identifiant » est répété par le titre de carte juste au-dessus.
Proposé
Config test11/50
Nom interne, jamais affiché aux visiteurs.
Config Test FR NL EN edit
3 / 3 langues traduites.
Deux champs sur une ligne, 92 px de hauteur, une seule ligne de base. Le badge de traduction porte l'information à la place d'un sous-titre gris tronqué.
03

Édition d'une configuration

Trois cartes empilées deviennent une colonne de travail et un rail latéral. Les actions quittent la barre flottante du bas pour rejoindre l'en-tête.

Pas de bento à ce niveau

Le placement bento ne concerne pas cet écran : gridColSpan / gridRowSpan vivent sur AppConfigurationLink, donc sur le lien entre une instance d'application et une configuration. Les tuiles de la grille bento sont les configurations de l'accueil, réglées dans app_configuration_link_screen.dart via CardShapeSelector.

À l'intérieur d'une configuration, les sections n'ont ni span ni grille : elles ne portent qu'un order, et le visiteur les voit en liste verticale de cartes pleine largeur (SectionList.tsx, SectionCard à 160 px, accent alterné). La grille de la maquette ci-dessous est donc un outil de réordonnancement, pas une prévisualisation.

Et pas d'aperçu visiteur non plus

Un aperçu ne se justifie que là où la décision d'édition est invisible dans le formulaire — les spans bento, le HTML traduit. Ici l'ordre se lit dans la grille, l'image est en vignette, les langues sont des pastilles : l'aperçu ne redessinerait, en plus petit, que ce qui est déjà à l'écran. En échange il faudrait maintenir un troisième moteur de rendu en Dart, alors que le bento est déjà dupliqué entre myinfomate_layout et le CSS Grid de visitapp-web — et un aperçu qui dérive du vrai rendu est pire que pas d'aperçu. Le rail droit porte donc un bouton d'ouverture et le QR : le rendu réel, sans rien à synchroniser.

manager.myinfomate.be · Configurations / Config test
arrow_back
Config test edit
Créée le 22/12/2023 · 8 sections · 3 langues
download deleteSupprimer Annuler checkEnregistrer
tuneRéglages
Config test11/50
Config Test3/3edit
FRNLENDE expand_more
Mode hors ligneLe contenu est téléchargé au premier lancement.
dashboardSectionsaddAjouter
searchRechercher une section… Tous · 8 Contenu Carte Interactif
1drag_indicator
Vue panoramique
collectionsSlider
2drag_indicator
Plan du parc
location_onMap
3question_answerdrag_indicator
Quiz des collections
question_answerQuiz
4drag_indicator
Parcours famille
routeParcours
5calendar_monthdrag_indicator
Agenda du mois
calendar_monthAgenda
6picture_as_pdfMasquéedrag_indicator
Brochure 2026
picture_as_pdfPDF
7sunnydrag_indicator
Météo Liège
sunnyMétéo
addAjouter
une section
imageHabillage
check_circletelepherique.jpgmore_horiz
check_circleunov-loader.svgmore_horiz
shareDiffusion
app.myinfomate.be/musee-liege/6d41f0…
open_in_newOuvrir content_copyLien
Le lien ouvre le rendu réel : rien à maquetter, rien qui puisse dériver.
Ce qui change — L'en-tête porte le contexte (8 sections, 3 langues) et les actions, ce qui supprime la barre flottante du bas et rend 60 px au contenu. La colonne principale contient les deux choses qu'on vient vraiment modifier ; le rail droit regroupe les médias et la diffusion. La carte « Identifiant » devient « Réglages » : le titre d'une carte ne répète plus le nom du champ qu'elle contient.
04

Édition d'une section

Même squelette, même grille, même rail. Seul le contenu de la carte « Configuration <type> » change d'un type à l'autre.

manager.myinfomate.be · Config test / Quiz des collections
arrow_back question_answerQuiz
Quiz des collections edit
Section 3 sur 8 · créée le 14/03/2026
picture_as_pdf deleteSupprimer Annuler checkEnregistrer
tuneRéglages
Quiz des collections20/50
Testez vos connaissances2/3edit
Balise iBeaconDéclenche la section à l'approche du visiteur.
question_answerQuestions6 questions · glisser pour réordonner
drag_indicator1Quel peintre…
drag_indicator2En quelle année…
drag_indicator3Que représente…
drag_indicator4Combien de salles…
drag_indicator5Qui a financé…
drag_indicator6Où se trouve…
addNouvelle question
Question 2Modifiée à l'instant
En quelle année le musée a-t-il ouvert ?3/3edit
10unfold_more
1897drag_indicator
1905drag_indicator
1923drag_indicator
1951drag_indicator
addAjouter une réponse
imageHabillage
add_photo_alternateChoisir dans la médiathèque
shareDiffusion
…/musee-liege/6d41f0/sections/a17c9b
open_in_newOuvrir downloadQR
Le QR imprimé pointe cette section. Identifiant : a17c9b…
Ce qui change — Le QR code et l'identifiant quittent leur carte pleine largeur pour le rail droit, sous la même forme que sur l'écran Configuration : ils servent au setup et à l'impression, pas à chaque édition. Les questions, aujourd'hui une liste horizontale de 150×150 avec une modale par question, deviennent une liste verticale et un panneau d'édition en place — le réordonnancement reste du glisser-déposer.
05

Trois gabarits, pas treize maquettes

Réponse à ta question : non. Les 13 types se rangent dans trois formes, et c'est la forme qui se dessine.

Pourquoi trois suffisent

Les configs par type ne diffèrent que par leurs champs, pas par leur structure : slider_config, menu_config, quizz_config, article_config, map_config, agenda_config, event_config et parcours_config font tous rigoureusement la même chose — une liste réordonnable + un bouton vert flottant + une modale d'édition, chacun avec sa propre hauteur en MediaQuery.size * 0.x. Une seule brique CollectionEditor les remplace tous les huit.

gabarit A

Champs simples

Un à quatre champs dans la carte de type. Rien à dessiner de plus : la grille de 12 colonnes de l'écran suffit.

WebVidéoMétéo
gabarit B

Éditeur de collection

Liste ordonnée d'éléments + édition en place. Maquetté ci-dessus avec le Quiz ; l'élément édité change, la mécanique non.

SliderMenuQuizArticlePDFAgendaParcours
gabarit C

Éditeur avec canevas

Gabarit B, plus un canevas de travail à droite : la carte, la grille du puzzle, le plan d'événement. Le canevas prend la place du rail latéral, qui repasse au-dessus du canevas.

MapÉvénementJeu
TypeGabaritContenu de la carte de typeDette actuelle
WebAURL
MétéoAVilleChamp marqué isUrl à tort
VidéoASource (YouTube / Vimeo / ressource) + URLDéjà refait, sert de référence
PDFBListe de fichiers traduitsBloc figé 70 × 440 px centré
SliderBContenus ordonnés : image, titre, descriptionListe horizontale height: 300
MenuBSous-sections ordonnéesListe horizontale 0.35 × hauteur
ArticleBContenus + audio + lecture autoListe horizontale height: 250
QuizBQuestions, réponses, points, scoresLe plus dense : 4 zones à hauteur fixe
AgendaBÉvénements datésModale par événement
ParcoursBÉtapes, mode de progressionÉditeur dédié de 785 lignes à raccorder
MapCPoints géo, catégories, images par point753 lignes, pas d'aperçu carte à l'édition
ÉvénementCBlocs de programme, annotations sur planTrois modales imbriquées
JeuCImage, messages, lignes × colonnesTabBar dans un SizedBox(height: 650)
06

Les dialogues

29 fichiers ouvrent un dialogue. Il n'en faut pas 29 maquettes : dix disparaissent ou fusionnent, la coquille est déjà tranchée, et deux seulement portent une vraie décision de design.

La coquille existe déjà — ne pas en inventer une deuxième

confirmation_dialog.dart l'a fixée, et resource_picker.dart la suit : rayon kRadiusShell, bordure kLine d'1 px, pied en bande kSurface2 séparé par un filet kLineSoft, un TextButton « Annuler » à 13 px et un FilledButton en pastille kRadiusPill qui passe au rouge quand l'action détruit. Les maquettes ci-dessous ne proposent rien de neuf sur ce plan : elles appliquent cette coquille.

S · 420 Confirmation, renommage — un message et deux boutons.
M · 512 Ajout d'une ressource, création — un formulaire court.
L · 780 Traduction, choix du type — plusieurs zones à comparer.

Le modal de traduction

C'est l'interface la plus utilisée de l'app : elle s'ouvre depuis chaque titre et chaque contenu de chaque type. Aujourd'hui elle empile un éditeur Quill par langue les uns sous les autres, puis pose deux boutons de 370 × 70 px centrés dans le flux du contenu — « Appliquer à toutes les langues » et « Traduire avec l'IA ». Sur trois langues, l'action IA est sous la ligne de flottaison.

Titre affiché
Section « Quiz des collections »
2 / 3 traduites close
FR
NL
EN
FR Testez vos connaissances
format_bold format_italic format_underlined format_list_bulleted format_list_numbered link format_clear
Test uw kennis
18 / 60 auto_awesome Proposé par l'IA — à relire
content_copyCopier le FR partout auto_awesomeTraduire les 2 manquantes 42 crédits restants Annuler Enregistrer
⚠️ Le piège d'implémentation : le cycle de vie des contrôleurs Quill

Aujourd'hui ça marche parce que l'empilement vertical monte toutes les langues en même temps : _controllers est une Map<String, QuillController> construite une seule fois dans initState (translation_input_container.dart:52) et libérée seulement dans dispose (198-202). Aucun éditeur n'est jamais démonté en cours de route.

Le rail de langues casse cette propriété si on l'implémente naïvement. N'afficher que la langue sélectionnée en reconstruisant l'éditeur à chaque bascule, c'est créer et détruire des QuillController en boucle — exactement le chemin fragile. La règle : construire la map une fois pour toutes les langues, comme aujourd'hui, et basculer avec un IndexedStack ou un Offstage — jamais en rebâtissant l'éditeur. Et surtout pas de TabBarView : il construit paresseusement et peut lâcher ses enfants hors écran sans AutomaticKeepAliveClientMixin.

Le code garde la trace d'une tentative dans ce sens : translation_tab.dart existe, en TabBar + TabBarView — mais son corps est un TextFormField, pas du Quill, et son appel est en commentaire dans multi_input_modal.dart ligne 46-51. Une version à onglets a été essayée puis abandonnée.

Deux chemins déjà existants libèrent et recréent les contrôleurs, et ce sont eux qu'il faut retester en priorité après la refonte : la traduction par IA (ligne 142) et « appliquer à toutes les langues » (ligne 183). Ils remplacent l'entrée de la map ; une référence gardée ailleurs deviendrait caduque.

Ce qui change — Les deux gros boutons quittent le contenu pour le pied : ce sont des actions, pas du texte. Le rail de gauche donne l'état des trois langues d'un coup d'œil — pleine, vide, ou à relire — au lieu de le faire deviner en scrollant. Et la langue source reste affichée au-dessus de l'éditeur : on traduit depuis une référence, c'est le geste réel. Le quota IA passe à côté du bouton qu'il conditionne, plus sous le formulaire.

Le choix du type de section

C'est la première interface que voit un nouveau client, et c'est aujourd'hui un menu déroulant de 13 entrées dans un SizedBox(height: 250), avec le champ du nom enfermé dans un height: 100. Un menu déroulant demande de connaître les noms avant de choisir ; une grille les montre.

Nouvelle section
Configuration « Config test »
close
Salle des maquettes19/50
Contenu
collectionsSliderSuite d'images légendées
articleArticleTexte, images, audio
picture_as_pdfPDFDocuments à consulter
ondemand_videoVidéoYouTube, Vimeo, fichier
webWebPage externe intégrée
Lieux et déplacements
location_onMapPlan avec points d'intérêt
routeParcoursÉtapes guidées, énigmes
eventÉvénementProgramme et plan du site
Interactif
question_answerQuizQuestions et scores
sports_esportsJeuPuzzle d'image
appsMenuRegroupe des sous-sections
Informations pratiques
calendar_monthAgendaÉvénements datés
sunnyMétéoPrévisions d'une ville
Le type ne pourra plus être changé après la création. Annuler addCréer la section
Ce qui change — Les 13 types sont visibles, regroupés par famille, avec une ligne qui dit ce qu'ils font : le client choisit sans avoir à connaître le vocabulaire du produit. Les icônes sont celles de getSectionIcon, déjà écrites — la grille ne coûte que la mise en page. Et l'avertissement qui compte (le type est définitif) est là où il sert, à côté du bouton qui valide.

Le reste de l'inventaire

DialogueSortPourquoi
8 × showNewOrUpdate*suppriméQuestion de quiz, image de slider, point géo, catégorie, fichier PDF, événement d'agenda, bloc de programme, annotation de plan — tous absorbés par le panneau d'édition en place du gabarit B.
TraductionmaquettéL'interface la plus utilisée de l'app. Ci-dessus.
Nouvelle sectionmaquettéPremière impression client. Ci-dessus.
Nouvelle configurationalignéFormulaire court : la coquille M et la grille de champs suffisent, rien à décider.
ConfirmationfaitDéjà repris, et c'est lui la référence.
Ajouter une ressourcefaitMaquette dédiée du 03/09, déjà publiée à part.
Sélecteur de ressourcecadre seulIl embarque ResourcesScreen : son contenu appartient au lot Médiathèque (v1-mediatheque-plan.md §3.3, « inchangé si possible »). Seul son cadre est à reprendre — voir ci-dessous.
Traduction sans HTML
multi_input_modal.dart
fusionnéLe jumeau du modal de traduction. multi_string_input_container appelle l'un ou l'autre selon isHTML : même travail, deux implémentations (560 px de large, une zone de 500 × 200, des boutons de 180 px). La maquette ci-dessus couvre les deux — une fois fusionnés en un seul, la barre de format en moins quand le champ n'accepte pas de HTML.
Point géo, géométrie
geoloc_ / geometry_input_container.dart
absorbéDeux AlertDialog de 0,75 × largeur pour poser un point ou tracer une zone : c'est précisément ce que le canevas du gabarit C met à plat dans l'écran. Placer un point sur une carte enfermée dans une modale est le geste le plus contraint qu'on puisse offrir.
Fichiers PDF, catégories de carte, sélecteur de couleuralignéTrois corps de dialogue honnêtes dans trois cadres cassés (0,65 × 0,7 d'écran, boutons de 180 × 70). Ils héritent de la coquille sans rien décider. Au passage : pdf_file_input_container et category_input_container sont un copier-coller l'un de l'autre — mêmes dimensions, même structure — sauf que le second a ses libellés en dur (« Annuler », « Valider » au lieu des traductions) et un onGetResult(result) commenté. À vérifier : une catégorie modifiée ne remonte peut-être pas à la volée.
Éditeur de parcours
guided_path_editor.dart
à part785 lignes avec ses propres dialogues. Il a déjà sa maquette (sectionparcours-refonte-flux.html) : à raccorder, pas à redessiner.
Lien d'application, device kiosk, utilisateurs, clés API, audit, guide IA, menu principal, notificationsalignéHors périmètre de cette refonte, mais héritent de la coquille et des boutons dès qu'on y touche.
Le sélecteur de ressource : le cadre, pas le contenu

showSelectResourceModal encastre l'écran Médiathèque entier dans un AlertDialog. Son contenu a déjà deux sources de vérité — v1-mediatheque-plan.md §3.3 et la maquette « Ajouter une ressource » — et je n'en ajoute pas une troisième. En revanche son cadre a un vrai défaut, pas seulement un défaut de goût.

Le débordement. Un AlertDialog s'insère avec 24 px de marge verticale : il dispose de hauteur − 48. Le contenu en demande 0,85 × hauteur, plus un titre, plus une ligne d'actions de 70 px — soit environ 895 px demandés pour 852 disponibles sur un écran de 900 de haut. C'est la raison d'être du SingleChildScrollView qui l'entoure, et la conséquence est un défilement dans un défilement : on fait glisser le dialogue au lieu de la grille de médias.

Ce qui change, et rien de plus. Coquille standard avec en-tête à la place du title: Center(Text(…)), kRadiusShell au lieu du rayon 20, le bouton « Annuler » de 180 × 70 px descendu dans le pied — il est seul, dans un Row en spaceEvenly, puisque la sélection se fait au clic sur la vignette. La hauteur figée cède la place à un corps souple sous un maxHeight : plus de débordement, plus de double défilement. Et showValues, lignes 64-78, est du code mort — comme la copie qu'en garde multi_input_modal.dart ligne 186 : son unique appel, ligne 53, est en commentaire. Les deux peuvent partir.

Ce qui ne change pas, volontairement : la largeur. Le size.width * 0.85 est conservé tel quel. La règle du rail de facettes, en §3.3 du plan Médiathèque, est écrite sur cette hypothèse — « elle fait déjà size.width * 0.85, et un rail de 194 px y est acceptable ». Rétrécir la modale invaliderait une décision déjà codée dans un autre lot. On corrige donc la hauteur et la coquille, pas la largeur.

Sélectionner une image
Champ « Image principale » — Config test
Images seulement close
TYPE
USAGE
CONFIGURATION
image
image
image
image
lockContenu inchangé — c'est ResourcesScreen, il appartient au lot Médiathèque.
Un clic sur une vignette sélectionne et referme. Annuler
Seul le cadre est dessiné. L'en-tête, le pied et les contraintes de taille sont à moi ; le rail de facettes et la grille sont schématisés à dessein — ils sont déjà spécifiés ailleurs et déjà codés. Le pied ne porte qu'« Annuler », puisqu'il n'y a rien à valider : la sélection se fait au clic. La largeur reste celle d'aujourd'hui.
07

Où ça se code, dans l'ordre

Les trois premiers fichiers portent l'essentiel du gain visuel — ils sont importés par tous les écrans d'édition, y compris ceux qui ne sont pas dans cette proposition.

1Components/text_field_container.dart
Components/rounded_input_field.dart
Sortir le width: double.infinity et le radius 29. Bordure 1 px, radius 8, marges rendues au parent. Tous les écrans changent d'un coup — à vérifier au passage sur Ressources et Instances.
2Components/string_input_container.dart
Components/multi_string_input_container.dart
Components/resource_input_container.dart
Components/check_input_container.dart
Label au-dessus du champ, suppression des MainAxisAlignment.center et des tailles en dur (width: 200, height: 54, la case à cocher de 50 px). Le composant ne décide plus de sa largeur ni de sa position.
3Screens/Configurations/section_reorderList.dart
Screens/Configurations/listView_card_section.dart
Grille responsive au lieu de la liste horizontale, hauteur pilotée par le contenu, carte au format 16/9 avec badge d'ordre, poignée et type. Le bouton vert flottant devient une tuile « Ajouter » dans la grille.
4Screens/Configurations/configuration_detail_screen.dart
Screens/Configurations/Section/section_detail_screen.dart
Grille à deux colonnes, actions remontées dans l'en-tête, suppression du _buildFooter. Le _cardQR devient le panneau « Diffusion » du rail droit.
5Components/multi_string_input_html_modal.dart
Components/translation_input_container.dart
Screens/Configurations/new_section_popup.dart
Screens/Resources/select_resource_modal.dart
Les dialogues : coquille standard, boutons sortis du contenu vers le pied, grille de types à la place du menu déroulant. Le sélecteur de ressource ne reçoit que son cadre.
6Components/collection_editor.dart (à créer)
La brique du gabarit B, puis migration des huit configs qui la dupliquent — et suppression des huit dialogues showNewOrUpdate*. C'est le plus gros morceau, et le seul qui puisse s'étaler dans le temps : chaque type migré est indépendant.
Ce n'est que du visuel — sauf sur deux points

Aucune donnée, aucune migration, aucun contrat d'API n'est touché, et tout est réversible par git. Le risque n'est pas la gravité, c'est la portée : les lots 1 et 2 changent des composants importés par une trentaine d'écrans, et manager-app n'a que trois tests, aucun sur ces composants. La vérification est donc manuelle.

Deux pièges concrets. D'abord, retirer une pastille pleine largeur change la hauteur des lignes — et il reste des conteneurs à hauteur figée autour (height: 70, height: 100, SizedBox(height: 250)) : ce qui rentrait juste peut déborder, et un débordement Flutter est visible, pas silencieux. Ensuite, ces composants ne sont pas purement décoratifs : dans section_detail_screen.dart, MultiStringInputContainer.onGetResult déclenche un save(true, …) à la ligne 270, et la case iBeacon un save(false, …) à la ligne 381. Il y a un chemin d'écriture câblé dans le composant — à ne pas perdre ni dédoubler en le refactorisant.