diff --git a/src/assets/css/style.css b/src/assets/css/style.css index 6417467..adab228 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -194,7 +194,7 @@ p.version > a { margin: 12px 0 12px 0; height: 50px; display: flex; - align-items: baseline; + align-items: center; } .table-menu > * { @@ -240,7 +240,11 @@ table.cell-table th { .dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner - th.dash-header { + th.dash-header, +.dash-table-container + .dash-spreadsheet-container + .dash-spreadsheet-inner + th.dash-select-header { margin: 0; color: white; font-family: "Inter", sans-serif; @@ -258,7 +262,13 @@ table.cell-table th { .dash-spreadsheet-inner table.cell-table tr:first-of-type - th.dash-header:first-of-type { + th.dash-header:first-of-type, +.dash-table-container + .dash-spreadsheet-container + .dash-spreadsheet-inner + table.cell-table + tr:first-of-type + th.dash-select-header:first-of-type { border-top-left-radius: 3px !important; } @@ -354,6 +364,11 @@ table.cell-table th { right: 5px; } +#column_list .show-hide, +#table .show-hide { + display: none; +} + .show-hide-menu-item > input { margin-right: 10px; } diff --git a/src/figures.py b/src/figures.py index a505576..2562093 100644 --- a/src/figures.py +++ b/src/figures.py @@ -258,10 +258,12 @@ class DataTable(dash_table.DataTable): page_action: Literal["native", "custom", "none"] = "native", sort_action: Literal["native", "custom", "none"] = "native", filter_action: Literal["native", "custom", "none"] = "native", + style_cell_conditional: list | None = None, + style_cell: dict | None = None, **kwargs, ): # Styles de base - style_cell_conditional = [ + style_cell_conditional_common = [ { "if": {"column_id": "objet"}, "minWidth": "350px", @@ -291,6 +293,8 @@ class DataTable(dash_table.DataTable): }, ] + style_cell_common = {"fontFamily": "Inter", "fontSize": "16px"} + for key in data_schema.keys(): field = data_schema[key] if field["type"] in ["number", "integer"]: @@ -299,7 +303,16 @@ class DataTable(dash_table.DataTable): "textAlign": "right", # "fontFamily": "Fira Code", } - style_cell_conditional.append(rule) + style_cell_conditional_common.append(rule) + + style_cell_conditional = ( + style_cell_conditional or [] + ) + style_cell_conditional_common + if style_cell: + style_cell.update(style_cell_common) + else: + style_cell = style_cell_common + style_header = style_cell # Initialisation de la classe parente avec les arguments super().__init__( @@ -322,8 +335,8 @@ class DataTable(dash_table.DataTable): style_cell_conditional=style_cell_conditional, data_timestamp=0, markdown_options={"html": True}, - style_header={"fontFamily": "Inter", "fontSize": "16px"}, - style_cell={"fontFamily": "Inter", "fontSize": "16px"}, + style_header=style_header, + style_cell=style_cell, tooltip_duration=8000, tooltip_delay=350, hidden_columns=hidden_columns, diff --git a/src/pages/tableau.py b/src/pages/tableau.py index 20edcf1..e6049bb 100644 --- a/src/pages/tableau.py +++ b/src/pages/tableau.py @@ -21,6 +21,8 @@ from dash import ( from src.figures import DataTable from src.utils import ( + columns, + data_schema, df, filter_table_data, get_default_hidden_columns, @@ -61,9 +63,70 @@ datatable = html.Div( ), ) + +def make_tableau_columns_table(): + table_data = [] + table_columns = [ + { + "id": col, + "name": data_schema[col]["title"], + "description": data_schema[col]["description"], + } + for col in df.columns + ] + for column in table_columns: + new_column = { + "id": column["id"], + "name": column["name"], + "description": data_schema[column["id"]]["description"], + } + table_data.append(new_column) + + table = ( + DataTable( + row_selectable="multi", + data=table_data, + filter_action="none", + sort_action="none", + style_cell={ + "textAlign": "left", + }, + columns=[ + { + "name": "Nom", + "id": "name", + }, + { + "name": "Description", + "id": "description", + }, + { + "name": "column_id", + "id": "id", + }, + ], + hidden_columns=["id"], + style_cell_conditional=[ + { + "if": {"column_id": "description"}, + "minWidth": "450px", + "overflow": "hidden", + "lineHeight": "18px", + "whiteSpace": "normal", + } + ], + page_action="none", + dtid="column_list", + ), + ) + + return table + + layout = [ dcc.Location(id="tableau_url", refresh=False), dcc.Store(id="filter-cleanup-trigger"), + dcc.Store(id="tableau-hidden-columns"), html.Script( type="application/ld+json", id="dataset_jsonld", @@ -192,7 +255,7 @@ layout = [ ), dbc.ModalFooter( dbc.Button( - "Close", + "Fermer", id="tableau_help_close", className="ms-auto", n_clicks=0, @@ -221,6 +284,11 @@ layout = [ children=[ html.Div( [ + dbc.Button( + "Colonnes affichées", + id="tableau_columns_open", + className="column_list", + ), html.P("lignes", id="nb_rows"), html.Div(id="copy-container"), dcc.Input(id="share-url", readOnly=True, style={"display": "none"}), @@ -235,6 +303,27 @@ layout = [ ], className="table-menu", ), + dbc.Modal( + [ + dbc.ModalHeader(dbc.ModalTitle("Choix des colonnes à afficher")), + dbc.ModalBody( + id="tableau_columns_body", children=make_tableau_columns_table() + ), + dbc.ModalFooter( + dbc.Button( + "Fermer", + id="tableau_columns_close", + className="ms-auto", + n_clicks=0, + ) + ), + ], + id="tableau_columns", + is_open=False, + fullscreen="md-down", + scrollable=True, + size="xl", + ), datatable, ], ), @@ -297,7 +386,7 @@ def download_data(n_clicks, filter_query, sort_by, hidden_columns: list = None): @callback( Output("table", "filter_query"), Output("table", "sort_by"), - Output("table", "hidden_columns"), + Output("tableau-hidden-columns", "data"), Output("tableau_url", "search", allow_duplicate=True), Output("filter-cleanup-trigger", "data"), Input("tableau_url", "search"), @@ -368,9 +457,9 @@ def sync_url_and_reset_button(filter_query, sort_by, hidden_columns, href): params["tris"] = json.dumps(sort_by) if hidden_columns: - columns = invert_columns(hidden_columns) - columns = ",".join(columns) - params["colonnes"] = columns + table_columns = invert_columns(hidden_columns) + table_columns = ",".join(table_columns) + params["colonnes"] = table_columns query_string = urllib.parse.urlencode(params) full_url = f"{base_url}?{query_string}" if query_string else base_url @@ -414,3 +503,52 @@ def toggle_tableau_help(click_open, click_close, is_open): if click_open or click_close: return not is_open return is_open + + +@callback( + Output("tableau-hidden-columns", "data", allow_duplicate=True), + Input("column_list", "selected_rows"), + prevent_initial_call=True, +) +def update_hidden_columns_from_checkboxes(selected_columns): + if selected_columns: + selected_columns = [columns[i] for i in selected_columns] + hidden_columns = [col for col in columns if col not in selected_columns] + return hidden_columns + else: + return [] + + +@callback( + Output("table", "hidden_columns", allow_duplicate=True), + Input( + "tableau-hidden-columns", + "data", + ), + prevent_initial_call=True, +) +def store_hidden_columns(hidden_columns): + return hidden_columns + + +@callback( + Output("column_list", "selected_rows"), + Input("table", "hidden_columns"), + State("column_list", "selected_rows"), # pour éviter la boucle infinie +) +def update_checkboxes_from_hidden_columns(hidden_cols, current_checkboxes): + # Show all columns that are NOT hidden + visible_cols = [columns.index(col) for col in columns if col not in hidden_cols] + return visible_cols + + +@callback( + Output("tableau_columns", "is_open"), + Input("tableau_columns_open", "n_clicks"), + Input("tableau_columns_close", "n_clicks"), + State("tableau_columns", "is_open"), +) +def toggle_tableau_columns(click_open, click_close, is_open): + if click_open or click_close: + return not is_open + return is_open diff --git a/src/utils.py b/src/utils.py index 0cf908f..57385fd 100644 --- a/src/utils.py +++ b/src/utils.py @@ -2,6 +2,7 @@ import json import logging import os import uuid +from collections import OrderedDict from time import localtime, sleep import polars as pl @@ -456,7 +457,7 @@ def get_data_schema() -> dict: else: raise Exception(f"Chemin vers le schéma invalide: {path}") - new_schema = {} + new_schema = OrderedDict() for col in original_schema["fields"]: new_schema[col["name"]] = col @@ -757,3 +758,4 @@ meta_content = { ), } data_schema = get_data_schema() +columns = df.columns