fix(gui): retours Dom GUI V6 — thème, Administration, Règles, aide

Cinq retours utilisateur sur l'exécutable Windows GUI V6.

- Thème : `_render()` vidait les widgets mais conservait le cache
  `_tab_frames`/`_visible_tab` → l'onglet Utilisation se vidait (TclError
  sur widget détruit) au changement de thème. Reset du cache dans
  `_render()` → onglet actif recréé proprement.
- Onglet principal « Configuration » → « Administration » (clé interne
  inchangée).
- Sous-onglet « Règles  2 » → « Règles » (le « 2 » était un badge non
  câblé).
- Actions de maquette non câblées (Partage Export/Import, Règles Nouvelle
  règle/Recharger/Tester/Fermer) désactivées + suffixe « (à venir) » via
  `_mockup_button` : plus aucune action morte qui semble fonctionner.
- Aide « ? » restaurée (façon V5) : `ui_kit.HelpButton`/`help_button`
  réutilisable ouvrant une fenêtre d'aide en français simple, posée sur
  Utilisation, Administration (Réglages/Masquage/Partage/Règles) et
  À propos. Partage : phrase visible + aide expliquant qu'on partage les
  réglages, jamais les documents patients.

`tests/unit/test_gui_v6_app_shell.py` : régression thème, libellés,
présence d'aide, navigation. 228 tests unit OK (0 régression), self-test
GUI V6 OK. V5/moteur/app_aivanov non touchés, aucune dépendance ajoutée.
Verdict Qwen requis avant push/build/diffusion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 16:39:53 +02:00
parent 13b79db417
commit 6a0a5811a5
6 changed files with 328 additions and 9 deletions

View File

@@ -24,7 +24,7 @@ _SUBTABS = [
("reg", "⚙️ Réglages"),
("msk", "🎭 Masquage"),
("shr", "🔄 Partage"),
("rul", "🛡️ Règles 2"),
("rul", "🛡️ Règles"),
]
_DETECTION_OPTIONS = [
@@ -62,6 +62,40 @@ _STOPWORDS = ["hospitalisation", "contrôle", "prescription"]
MANUAL_MASK_NONE_LABEL = "Aucun masque manuel"
# Textes d'aide « ? » (français simple, pour utilisateurs non informaticiens).
_HELP_REGLAGES = (
"Réglages de l'anonymisation.\n\n"
"• Profil métier : choisit un jeu de réglages adapté à votre service.\n"
"• Moteurs NER : les modèles qui détectent les noms et données personnelles.\n"
"• Données à détecter : ce qui sera masqué (noms, dates de naissance, etc.).\n"
"• Listes locales : vos termes à toujours masquer ou toujours conserver.\n\n"
"Tout fonctionne 100 % en local sur ce poste. Aucun document patient n'est envoyé sur Internet."
)
_HELP_MASQUAGE = (
"Masquage des documents.\n\n"
"• Couleur, style et marges du masque appliqué sur les PDF.\n"
"• Codes de remplacement affichés à la place des données ([NOM], [DATE_NAISSANCE]…).\n"
"• Masques de zones fixes : ouvrez l'éditeur pour dessiner les zones à masquer "
"(en-têtes, blocs identité) directement sur un PDF modèle, puis enregistrez un modèle réutilisable.\n\n"
"Le traitement reste local ; rien n'est envoyé sur Internet."
)
_HELP_PARTAGE = (
"À quoi sert le Partage ?\n\n"
"Il permet d'échanger les RÉGLAGES de l'application (listes de termes, règles, "
"style de masquage, modèle de masque) entre plusieurs postes, ou avec votre administrateur.\n\n"
"• Exporter : enregistre vos réglages dans un fichier .json à transmettre.\n"
"• Importer : fusionne des réglages reçus avec les vôtres.\n\n"
"IMPORTANT : seuls les réglages sont partagés. Vos documents patients ne sont JAMAIS "
"partagés ni envoyés sur Internet."
)
_HELP_REGLES = (
"Les Règles adaptent le moteur à votre établissement (ex. : toujours masquer un sigle, "
"toujours conserver un terme métier).\n\n"
"Chaque règle est validée avant d'être activée.\n\n"
"Cette section est en cours de finalisation : les actions marquées « à venir » "
"ne sont pas encore disponibles."
)
CONFIG_MOCKUP_SECTIONS = {
"reglages": [
"Profil métier",
@@ -198,6 +232,12 @@ class ConfigTab(ctk.CTkFrame):
def _build_reglages(self, parent) -> None:
p = self._p
self._section_intro(
parent,
"Choisissez ce que l'application doit détecter et masquer. Tout reste local.",
_HELP_REGLAGES,
"Les Réglages",
)
top = ctk.CTkFrame(
parent,
fg_color=p["card"],
@@ -308,6 +348,12 @@ class ConfigTab(ctk.CTkFrame):
def _build_masquage(self, parent) -> None:
p = self._p
self._section_intro(
parent,
"Apparence des masques et éditeur de zones fixes à masquer sur vos PDF.",
_HELP_MASQUAGE,
"Le Masquage",
)
top_cols = self._columns(parent, 3, gap=8, height=300)
pdf_opts = ui_kit.Card(top_cols[0], p, title="⬛ PDF")
@@ -423,19 +469,31 @@ class ConfigTab(ctk.CTkFrame):
def _build_partage(self, parent) -> None:
p = self._p
self._section_intro(
parent,
"Partagez vos réglages (jamais vos documents) entre postes ou avec l'administrateur.",
_HELP_PARTAGE,
"À quoi sert le Partage ?",
)
cols = self._columns(parent, 2, gap=8, height=180)
export = ui_kit.Card(cols[0], p, title="📤 Exporter la configuration")
export.pack(fill="both", expand=True)
self._note(export, "Listes locales, règles admin, style de masquage et template actif.")
ui_kit.secondary_button(export, p, "⬇ Exporter (.json)", command=lambda: None).pack(anchor="w", padx=12, pady=(0, 12))
self._mockup_button(export, "⬇ Exporter (.json)").pack(anchor="w", padx=12, pady=(0, 12))
import_card = ui_kit.Card(cols[1], p, title="📥 Importer une configuration")
import_card.pack(fill="both", expand=True)
self._note(import_card, "Fusionne la configuration reçue avec vos réglages locaux.")
ui_kit.secondary_button(import_card, p, "⬆ Importer (.json)", command=lambda: None).pack(anchor="w", padx=12, pady=(0, 12))
self._mockup_button(import_card, "⬆ Importer (.json)").pack(anchor="w", padx=12, pady=(0, 12))
def _build_regles(self, parent) -> None:
p = self._p
self._section_intro(
parent,
"Règles d'adaptation du moteur à votre établissement.",
_HELP_REGLES,
"Les Règles",
)
card = ui_kit.Card(parent, p, title="🛡️ Règles actives")
card.pack(fill="x", pady=(0, 8))
self._note(card, "Ces règles adaptent le moteur à votre établissement. Chaque règle est validée avant activation.")
@@ -451,8 +509,8 @@ class ConfigTab(ctk.CTkFrame):
self._rule_row(card, row)
actions = ctk.CTkFrame(card, fg_color="transparent")
actions.pack(fill="x", padx=12, pady=(8, 12))
ui_kit.primary_button(actions, p, "+ Nouvelle règle", command=lambda: None).pack(side="left", padx=(0, 8))
ui_kit.secondary_button(actions, p, "🔄 Recharger", command=lambda: None).pack(side="left")
self._mockup_button(actions, "+ Nouvelle règle", primary=True).pack(side="left", padx=(0, 8))
self._mockup_button(actions, "🔄 Recharger").pack(side="left")
sim = ui_kit.Card(parent, p, title="🧪 Testeur de règle")
sim.pack(fill="x")
@@ -462,8 +520,29 @@ class ConfigTab(ctk.CTkFrame):
txt.insert("1.0", "Compte rendu CHUXX, patient N° 1234567.")
btns = ctk.CTkFrame(sim, fg_color="transparent")
btns.pack(fill="x", padx=12, pady=(0, 12))
ui_kit.primary_button(btns, p, "▶ Tester", command=lambda: None).pack(side="left", padx=(0, 8))
ui_kit.secondary_button(btns, p, "✖ Fermer", command=lambda: None).pack(side="left")
self._mockup_button(btns, "▶ Tester", primary=True).pack(side="left", padx=(0, 8))
self._mockup_button(btns, "✖ Fermer").pack(side="left")
# -- helpers aide / maquette -----------------------------------------
def _section_intro(self, parent, sentence: str, help_text: str, help_title: str) -> None:
"""Ligne d'introduction d'une sous-section : phrase courte + bouton d'aide « ? »."""
p = self._p
intro = ctk.CTkFrame(parent, fg_color="transparent")
intro.pack(fill="x", pady=(0, 6))
ctk.CTkLabel(
intro, text=sentence, text_color=p["text_dim"], font=ui_kit.font(12), anchor="w", justify="left"
).pack(side="left", padx=(2, 6))
ui_kit.help_button(intro, p, help_text, title=help_title).pack(side="right", padx=2)
def _mockup_button(self, parent, text: str, primary: bool = False):
"""Bouton de maquette non câblé : désactivé + suffixe « (à venir) » pour
ne pas laisser croire qu'il fonctionne."""
p = self._p
factory = ui_kit.primary_button if primary else ui_kit.secondary_button
btn = factory(parent, p, f"{text} (à venir)", command=lambda: None)
btn.configure(state="disabled")
return btn
# -- callbacks réglages ----------------------------------------------
@@ -714,4 +793,4 @@ class ConfigTab(ctk.CTkFrame):
ctk.CTkLabel(row, text=target, width=210, anchor="w", text_color=p["primary"], font=ui_kit.font(12, "bold")).pack(side="left")
color = p["success"] if status == "Actif" else p["warning"]
ctk.CTkLabel(row, text=status, width=70, anchor="w", text_color=color, font=ui_kit.font(11, "bold")).pack(side="left")
ui_kit.secondary_button(row, p, "▶ Tester", command=lambda: None).pack(side="left")
self._mockup_button(row, "▶ Tester").pack(side="left")