Tableau de choix des colonnes aussi sur les pages acheteur et titulaire

This commit is contained in:
Colin Maudry
2026-01-31 17:38:54 +01:00
parent 1561cb3c9a
commit 41a832d7be
5 changed files with 238 additions and 75 deletions
+4 -2
View File
@@ -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,
+59 -5
View File
@@ -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
+83 -1
View File
@@ -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
+9 -66
View File
@@ -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 <img src="/assets/copy.svg" alt="drawing" width="20"/> 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 <img src="/assets/copy.svg" alt="drawing" width="20"/> 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
+83 -1
View File
@@ -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