diff --git a/src/assets/css/style.css b/src/assets/css/style.css index adab228..b42c0f3 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -354,15 +354,17 @@ table.cell-table th { position: relative; width: 180px; margin: 0 0 10px 0; + display: none; } +/* .show-hide::before { background: inherit; content: "Colonnes affichées"; position: absolute; left: 5px; right: 5px; -} + #column_list .show-hide, #table .show-hide { @@ -371,7 +373,7 @@ table.cell-table th { .show-hide-menu-item > input { margin-right: 10px; -} +} */ /* Tooltips */ .dash-tooltip, diff --git a/src/figures.py b/src/figures.py index 2562093..e7b97cb 100644 --- a/src/figures.py +++ b/src/figures.py @@ -6,10 +6,10 @@ import plotly.graph_objects as go import polars as pl from dash import dash_table, dcc, html -from src.utils import data_schema, format_number +from src.utils import data_schema, df, format_number -def get_map_count_marches(df: pl.DataFrame): +def get_map_count_marches(): lf = df.lazy() lf = lf.with_columns( pl.col("lieuExecution_code").str.head(2).str.zfill(2).alias("Département") @@ -31,16 +31,16 @@ def get_map_count_marches(df: pl.DataFrame): for f in departements["features"]: f["id"] = f["properties"]["code"] - df = lf.collect(engine="streaming") + df_map = lf.collect(engine="streaming") fig = px.choropleth( - df, + df_map, geojson=departements, locations="Département", color="uid", color_continuous_scale="Reds", title="Nombres de marchés attribués par département (lieu d'exécution)", - range_color=(df["uid"].min(), df["uid"].max()), + range_color=(df_map["uid"].min(), df_map["uid"].max()), labels={"uid": "Marchés attribués"}, scope="europe", width=900, @@ -414,3 +414,57 @@ def get_duplicate_matrix() -> html.Div: dcc.Graph(figure=fig), ] ) + + +def make_column_picker(page: str): + 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="native", + sort_action="none", + style_cell={ + "textAlign": "left", + }, + columns=[ + { + "name": "Nom", + "id": "name", + }, + { + "name": "Description", + "id": "description", + }, + ], + style_cell_conditional=[ + { + "if": {"column_id": "description"}, + "minWidth": "450px", + "overflow": "hidden", + "lineHeight": "18px", + "whiteSpace": "normal", + } + ], + page_action="none", + dtid=f"{page}_column_list", + ), + ) + + return table diff --git a/src/pages/acheteur.py b/src/pages/acheteur.py index 6f5e5e2..1ca9044 100644 --- a/src/pages/acheteur.py +++ b/src/pages/acheteur.py @@ -1,11 +1,13 @@ import datetime +import dash_bootstrap_components as dbc import polars as pl from dash import Input, Output, State, callback, dcc, html, register_page from src.callbacks import get_top_org_table -from src.figures import DataTable, point_on_map +from src.figures import DataTable, make_column_picker, point_on_map from src.utils import ( + columns, df, df_acheteurs, filter_table_data, @@ -53,6 +55,7 @@ datatable = html.Div( layout = [ dcc.Store(id="acheteur_data", storage_type="memory"), + dcc.Store(id="acheteur-hidden-columns", storage_type="memory"), dcc.Location(id="acheteur_url", refresh="callback-nav"), html.Div( children=[ @@ -133,6 +136,12 @@ layout = [ children=[ html.Div( [ + # Bouton modal des colonnes affichées + dbc.Button( + "Colonnes affichées", + id="acheteur_columns_open", + className="column_list", + ), html.P("lignes", id="acheteur_nb_rows"), html.Button( "Téléchargement désactivé au-delà de 65 000 lignes", @@ -144,6 +153,30 @@ layout = [ ], className="table-menu", ), + dbc.Modal( + [ + dbc.ModalHeader( + dbc.ModalTitle("Choix des colonnes à afficher") + ), + dbc.ModalBody( + id="acheteur_columns_body", + children=make_column_picker("acheteur"), + ), + dbc.ModalFooter( + dbc.Button( + "Fermer", + id="acheteur_columns_close", + className="ms-auto", + n_clicks=0, + ) + ), + ], + id="acheteur_columns", + is_open=False, + fullscreen="md-down", + scrollable=True, + size="xl", + ), datatable, ], ), @@ -333,3 +366,52 @@ def download_filtered_acheteur_data( return dcc.send_bytes( to_bytes, filename=f"decp_filtrées_{acheteur_nom}_{date}.xlsx" ) + + +@callback( + Output("acheteur-hidden-columns", "data", allow_duplicate=True), + Input("acheteur_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("acheteur_datatable", "hidden_columns", allow_duplicate=True), + Input( + "acheteur-hidden-columns", + "data", + ), + prevent_initial_call=True, +) +def store_hidden_columns(hidden_columns): + return hidden_columns + + +@callback( + Output("acheteur_column_list", "selected_rows"), + Input("acheteur_datatable", "hidden_columns"), + State("acheteur_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("acheteur_columns", "is_open"), + Input("acheteur_columns_open", "n_clicks"), + Input("acheteur_columns_close", "n_clicks"), + State("acheteur_columns", "is_open"), +) +def toggle_acheteur_columns(click_open, click_close, is_open): + if click_open or click_close: + return not is_open + return is_open diff --git a/src/pages/tableau.py b/src/pages/tableau.py index 0eb16e1..967d9ff 100644 --- a/src/pages/tableau.py +++ b/src/pages/tableau.py @@ -19,10 +19,10 @@ from dash import ( register_page, ) +from figures import make_column_picker from src.figures import DataTable from src.utils import ( columns, - data_schema, df, filter_table_data, get_default_hidden_columns, @@ -63,66 +63,6 @@ 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="native", - 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"), @@ -199,6 +139,7 @@ layout = [ children=[ html.Div( [ + # Modal du mode d'emploi dbc.Button("Mode d'emploi", id="tableau_help_open"), dbc.Modal( [ @@ -246,7 +187,7 @@ layout = [ ##### Partager une vue - Une vue est un ensemble de filtres, de tris et de choix de colonnes que vous avez appliqué. Cliquez sur l'icône drawing pour copier une adresse Web qui reproduit la vue courante à l'identique : en la collant dans la barre d'adresse d'un navigateur, vous ouvrez la vue Tableau avec les mêmes paramètres. + Une vue est un ensemble de filtres, de tris et de choix de colonnes que vous avez appliqués. Cliquez sur l'icône drawing pour copier une adresse Web qui reproduit la vue courante à l'identique : en la collant dans la barre d'adresse d'un navigateur, vous ouvrez la vue Tableau avec les mêmes paramètres. Pratique pour partager une vue avec un·e collègue, sur les réseaux sociaux, ou la sauvegarder pour plus tard. @@ -277,6 +218,7 @@ layout = [ scrollable=True, size="lg", ), + # Bouton modal des colonnes affichées dbc.Button( "Colonnes affichées", id="tableau_columns_open", @@ -300,7 +242,8 @@ layout = [ [ dbc.ModalHeader(dbc.ModalTitle("Choix des colonnes à afficher")), dbc.ModalBody( - id="tableau_columns_body", children=make_tableau_columns_table() + id="tableau_columns_body", + children=make_column_picker("tableau"), ), dbc.ModalFooter( dbc.Button( @@ -502,7 +445,7 @@ def toggle_tableau_help(click_open, click_close, is_open): @callback( Output("tableau-hidden-columns", "data", allow_duplicate=True), - Input("column_list", "selected_rows"), + Input("tableau_column_list", "selected_rows"), prevent_initial_call=True, ) def update_hidden_columns_from_checkboxes(selected_columns): @@ -527,9 +470,9 @@ def store_hidden_columns(hidden_columns): @callback( - Output("column_list", "selected_rows"), + Output("tableau_column_list", "selected_rows"), Input("table", "hidden_columns"), - State("column_list", "selected_rows"), # pour éviter la boucle infinie + State("tableau_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 diff --git a/src/pages/titulaire.py b/src/pages/titulaire.py index 84ec606..939dd96 100644 --- a/src/pages/titulaire.py +++ b/src/pages/titulaire.py @@ -1,11 +1,13 @@ import datetime +import dash_bootstrap_components as dbc import polars as pl from dash import Input, Output, State, callback, dcc, html, register_page from src.callbacks import get_top_org_table -from src.figures import DataTable, point_on_map +from src.figures import DataTable, make_column_picker, point_on_map from src.utils import ( + columns, df, df_titulaires, filter_table_data, @@ -53,6 +55,7 @@ datatable = html.Div( layout = [ dcc.Store(id="titulaire_data", storage_type="memory"), + dcc.Store(id="titulaire-hidden-columns", storage_type="memory"), dcc.Location(id="titulaire_url", refresh="callback-nav"), html.Div( children=[ @@ -132,6 +135,12 @@ layout = [ children=[ html.Div( [ + # Bouton modal des colonnes affichées + dbc.Button( + "Colonnes affichées", + id="titulaire_columns_open", + className="column_list", + ), html.P("lignes", id="titulaire_nb_rows"), html.Button( "Téléchargement désactivé au-delà de 65 000 lignes", @@ -142,6 +151,30 @@ layout = [ ], className="table-menu", ), + dbc.Modal( + [ + dbc.ModalHeader( + dbc.ModalTitle("Choix des colonnes à afficher") + ), + dbc.ModalBody( + id="titulaire_columns_body", + children=make_column_picker("titulaire"), + ), + dbc.ModalFooter( + dbc.Button( + "Fermer", + id="titulaire_columns_close", + className="ms-auto", + n_clicks=0, + ) + ), + ], + id="titulaire_columns", + is_open=False, + fullscreen="md-down", + scrollable=True, + size="xl", + ), datatable, ], ), @@ -346,3 +379,52 @@ def download_filtered_titulaire_data( return dcc.send_bytes( to_bytes, filename=f"decp_filtrées_{titulaire_nom}_{date}.xlsx" ) + + +@callback( + Output("titulaire-hidden-columns", "data", allow_duplicate=True), + Input("titulaire_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("titulaire_datatable", "hidden_columns", allow_duplicate=True), + Input( + "titulaire-hidden-columns", + "data", + ), + prevent_initial_call=True, +) +def store_hidden_columns(hidden_columns): + return hidden_columns + + +@callback( + Output("titulaire_column_list", "selected_rows"), + Input("titulaire_datatable", "hidden_columns"), + State("titulaire_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("titulaire_columns", "is_open"), + Input("titulaire_columns_open", "n_clicks"), + Input("titulaire_columns_close", "n_clicks"), + State("titulaire_columns", "is_open"), +) +def toggle_titulaire_columns(click_open, click_close, is_open): + if click_open or click_close: + return not is_open + return is_open