From 78534000a087f58aec2704d21ecbccc45322f356 Mon Sep 17 00:00:00 2001 From: Colin Maudry Date: Tue, 30 Jun 2026 14:33:28 +0200 Subject: [PATCH] =?UTF-8?q?refactor(tableau):=20barre=20d'outils=20compact?= =?UTF-8?q?e=20sur=20une=20ligne=20+=20l=C3=A9gende=20d'aide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Barre d'outils repensée au-dessus du tableau : - boutons compacts (size=sm) sur une seule rangée + ligne d'infos discrète en dessous (2 lignes au total sur desktop) - libellés explicites (Sauvegarder la vue, Partager la vue, Télécharger (Excel)), espacement homogène, couleurs conformes à la charte (neutre=secondary, Réinitialiser=danger outline) - Mode d'emploi rejeté en fin de barre via CSS order (DOM/callbacks intacts) - .table-menu (3 autres pages) laissée intacte Téléchargement bloqué (>65 000 lignes) : la raison s'affiche en clair dans la ligne d'infos (fiable cross-browser) plutôt que dans une infobulle sur bouton désactivé. Override scopé au tableau via update_table ; acheteur/ titulaire non touchés. Mode d'emploi : légende en tête listant chaque bouton reproduit (inerte) en face de sa fonction. Co-Authored-By: Claude Opus 4.8 --- src/assets/css/style.css | 49 ++++++++++++++ src/pages/tableau.py | 139 +++++++++++++++++++++++++++++++++------ 2 files changed, 169 insertions(+), 19 deletions(-) diff --git a/src/assets/css/style.css b/src/assets/css/style.css index 34b6e38..2c50d97 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -320,6 +320,55 @@ input[type="email"] { margin: 8px 16px 8px 0; } +/* Barre d'outils du tableau (/tableau) : une rangée d'actions compactes + au-dessus d'une ligne d'infos discrète. .table-menu (autres pages) intacte. */ +.table-toolbar { + display: flex; + flex-wrap: wrap; + align-items: center; + margin: 14px 0 6px 0; +} + +.table-toolbar > * { + margin: 0 12px 8px 0; +} + +/* Mode d'emploi : aide auxiliaire, rejetée en fin de barre sans toucher au DOM */ +.table-toolbar #tableau_help_open { + order: 1; +} + +.table-meta { + margin: 0 0 16px 0; + color: #666; + font-size: 14px; +} + +/* Raison du téléchargement bloqué : visible (pas une infobulle), mise en avant + sobrement avec la couleur terracotta de la marque. */ +.table-meta .dl-hint { + color: #b33821; +} + +/* Légende des boutons en tête du mode d'emploi : boutons reproduits à + l'identique mais inertes, alignés en face de leur fonction. */ +.help-legend-table { + border-collapse: collapse; +} + +.help-legend-table td { + padding: 4px 16px 4px 0; + vertical-align: middle; +} + +.help-legend-btn { + white-space: nowrap; +} + +.help-legend .btn { + pointer-events: none; +} + #source_table { margin-bottom: 25px; } diff --git a/src/pages/tableau.py b/src/pages/tableau.py index 6187949..a1eda4a 100644 --- a/src/pages/tableau.py +++ b/src/pages/tableau.py @@ -78,6 +78,62 @@ DATATABLE = html.Div( ), ) + +def _help_button_legend(): + """Légende en tête du mode d'emploi : chaque bouton de la barre d'outils, + reproduit à l'identique (mais inerte), en face de sa fonction.""" + rows = [ + ( + dbc.Button("Colonnes", color="secondary", size="sm"), + "Choisir les colonnes affichées.", + ), + ( + dbc.Button("Sauvegarder la vue", color="secondary", size="sm"), + "Enregistrer les filtres, tris et colonnes actuels sous un nom (abonnés).", + ), + ( + dbc.Button("Mes vues ▾", color="secondary", size="sm"), + "Rouvrir une vue que vous avez enregistrée (abonnés).", + ), + ( + dbc.Button("Partager la vue", color="secondary", size="sm"), + "Copier l'adresse de la vue actuelle pour la partager ou la conserver.", + ), + ( + dbc.Button("Télécharger (Excel)", color="secondary", size="sm"), + "Télécharger les données filtrées et triées au format Excel.", + ), + ( + dbc.Button("Réinitialiser", color="danger", outline=True, size="sm"), + "Supprimer tous les filtres et tris.", + ), + ( + dbc.Button("Mode d'emploi", color="secondary", outline=True, size="sm"), + "Ouvrir cette aide.", + ), + ] + return html.Div( + className="help-legend", + children=[ + html.P("Les boutons de la barre d'outils", className="fw-bold mb-2"), + html.Table( + className="help-legend-table", + children=html.Tbody( + [ + html.Tr( + [ + html.Td(btn, className="help-legend-btn"), + html.Td(desc, className="help-legend-desc"), + ] + ) + for btn, desc in rows + ] + ), + ), + ], + ) + + layout = [ dcc.Location(id="tableau_url", refresh=False), dcc.Store(id="filter-cleanup-trigger-tableau"), @@ -160,14 +216,23 @@ layout = [ html.Div( [ # Modal du mode d'emploi - dbc.Button("Mode d'emploi", id="tableau_help_open"), + dbc.Button( + "Mode d'emploi", + id="tableau_help_open", + color="secondary", + outline=True, + size="sm", + ), dbc.Modal( [ dbc.ModalHeader(dbc.ModalTitle("Mode d'emploi")), dbc.ModalBody( - dcc.Markdown( - dangerously_allow_html=True, - children=f""" + [ + _help_button_legend(), + html.Hr(), + dcc.Markdown( + dangerously_allow_html=True, + children=f""" ##### Définition des colonnes Pour voir la définition d'une colonne, passez votre souris sur son en-tête. @@ -226,7 +291,8 @@ layout = [ (informations, marchés attribués/remportés, etc.) """, - ), + ), + ], ), dbc.ModalFooter( dbc.Button( @@ -245,8 +311,10 @@ layout = [ ), # Bouton modal des colonnes affichées dbc.Button( - "Choisir les colonnes", + "Colonnes", id="tableau_columns_open", + color="secondary", + size="sm", className="column_list", title="Choisir les colonnes à afficher et masquer", ), @@ -258,11 +326,15 @@ layout = [ dbc.Button( "Sauvegarder la vue", id="btn-save-view", + color="secondary", + size="sm", title="Enregistrer les filtres, tris et colonnes actuels sous un nom", ), dbc.DropdownMenu( id="saved-views-menu", label="Mes vues", + color="secondary", + size="sm", children=[], className="d-inline-block", ), @@ -294,24 +366,35 @@ layout = [ ), ], ), - html.P("lignes", id="nb_rows"), html.Div(id="copy-container"), dcc.Input(id="share-url", readOnly=True, style={"display": "none"}), dbc.Button( - "Téléchargement désactivé au-delà de 65 000 lignes", + "Télécharger (Excel)", id="btn-download-data", + color="secondary", + size="sm", disabled=True, ), dcc.Download(id="download-data"), dcc.Store(id="filtered_data", storage_type="memory"), - html.P("Données mises à jour le " + str(update_date)), dbc.Button( - "Remettre à zéro", - title="Supprime tous les filtres et les tris. Autrement ils sont conservés même si vous fermez la page.", + "Réinitialiser", id="btn-tableau-reset", + color="danger", + outline=True, + size="sm", + title="Supprime tous les filtres et les tris. Autrement ils sont conservés même si vous fermez la page.", ), ], - className="table-menu", + className="table-toolbar", + ), + html.Div( + className="table-meta", + children=[ + html.Span(id="nb_rows"), + html.Span(" · Données mises à jour le " + str(update_date)), + html.Span(id="download-hint", className="dl-hint"), + ], ), dbc.Modal( [ @@ -351,6 +434,7 @@ layout = [ Output("btn-download-data", "children"), Output("btn-download-data", "title"), Output("filter-cleanup-trigger-tableau", "data", allow_duplicate=True), + Output("download-hint", "children"), Input("tableau_url", "href"), Input("tableau_datatable", "page_current"), Input("tableau_datatable", "page_size"), @@ -360,13 +444,29 @@ layout = [ prevent_initial_call=True, ) def update_table(href, page_current, page_size, filter_query, sort_by, data_timestamp): - # if ctx.triggered_id != "url": - # search_params = None - # else: - # search_params = urllib.parse.parse_qs(search_params.lstrip("?")) - return prepare_table_data( - None, data_timestamp, filter_query, page_current, page_size, sort_by, "tableau" + result = list( + prepare_table_data( + None, + data_timestamp, + filter_query, + page_current, + page_size, + sort_by, + "tableau", + ) ) + # Libellé court et constant ; la raison d'un éventuel blocage est affichée + # en clair dans la ligne d'infos (fiable cross-browser, contrairement à une + # infobulle sur bouton désactivé). index 5 = disabled, 6 = children, 7 = title. + result[6] = "Télécharger (Excel)" + download_blocked_too_many = result[5] and result[7] + download_hint = ( + " · Filtrez sous 65 000 lignes pour activer le téléchargement" + if download_blocked_too_many + else "" + ) + result.append(download_hint) + return tuple(result) @callback( @@ -491,7 +591,8 @@ def sync_url_and_reset_button(filter_query, sort_by, hidden_columns, href): children=[ dbc.Button( "Partager la vue", - className="btn btn-primary", + color="secondary", + size="sm", title="Copier l'adresse de cette vue (filtres, tris, choix de colonnes) pour la partager.", ) ],