feat(tableau): grille AG Grid + datasource getRows server-side (#41)

Remplace le composant DataTable et le callback update_table de tableau.py
par la grille dash-ag-grid (mode ligne infini) et un callback get_rows_tableau
piloté par getRowsRequest/getRowsResponse, plus un callback update_meta qui
synchronise nb_rows/btn-download-data/download-hint via un dcc.Store du total.

Les autres callbacks de la page (sélecteur de colonnes, vues sauvegardées,
export, restauration URL, aide) référencent encore les anciens
id/props (tableau_datatable, hidden_columns, filter_query, sort_by) et seront
mis à jour dans les tâches suivantes (8-11) du plan de migration #41.
This commit is contained in:
Colin Maudry
2026-07-10 08:11:06 +02:00
parent 97e1d0b5de
commit 4c1c1cc987
+34 -48
View File
@@ -21,11 +21,12 @@ from dash import (
from flask_login import current_user
from src.db import query_marches, schema
from src.figures import DataTable, make_column_picker
from src.figures import ag_grid, make_column_picker
from src.pages._compte_shell import current_user_has_subscription
from src.saved_views import db as saved_views_db
from src.saved_views import ui as saved_views_ui
from src.utils import get_data_update_timestamp, logger
from src.utils.grid import fetch_grid_page, grid_column_defs
from src.utils.seo import META_CONTENT
from src.utils.table import (
COLUMNS,
@@ -33,7 +34,6 @@ from src.utils.table import (
filter_table_data,
get_default_hidden_columns,
invert_columns,
prepare_table_data,
sort_table_data,
write_styled_excel,
)
@@ -64,17 +64,8 @@ register_page(
DATATABLE = html.Div(
className="marches_table",
children=DataTable(
dtid="tableau_datatable",
persisted_props=["filter_query", "sort_by"],
persistence_type="local",
persistence=True,
page_size=20,
page_action="custom",
filter_action="custom",
sort_action="custom",
hidden_columns=[],
columns=[{"id": col, "name": col} for col in schema.names()],
children=ag_grid(
"tableau_grid", grid_column_defs(get_default_hidden_columns("tableau"))
),
)
@@ -139,6 +130,7 @@ layout = [
dcc.Store(id="filter-cleanup-trigger-tableau"),
dcc.Store(id="tableau-hidden-columns", storage_type="local"),
dcc.Store(id="tableau-table"),
dcc.Store(id="tableau-total"),
html.Script(
type="application/ld+json",
id="dataset_jsonld",
@@ -434,48 +426,42 @@ layout = [
@callback(
Output("tableau_datatable", "data"),
Output("tableau_datatable", "columns"),
Output("tableau_datatable", "tooltip_header"),
Output("tableau_datatable", "data_timestamp"),
Output("nb_rows", "children"),
Output("btn-download-data", "disabled"),
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"),
Input("tableau_datatable", "filter_query"),
Input("tableau_datatable", "sort_by"),
State("tableau_datatable", "data_timestamp"),
Output("tableau_grid", "getRowsResponse"),
Output("tableau-total", "data"),
Input("tableau_grid", "getRowsRequest"),
prevent_initial_call=True,
)
def update_table(href, page_current, page_size, filter_query, sort_by, data_timestamp):
result = list(
prepare_table_data(
None,
data_timestamp,
filter_query,
page_current,
page_size,
sort_by,
"tableau",
def get_rows_tableau(request):
if request is None:
return no_update, no_update
filter_model = request.get("filterModel") or None
sort_model = request.get("sortModel") or None
if filter_model:
track_search(json.dumps(filter_model), "tableau")
rows, total = fetch_grid_page(
filter_model,
sort_model,
request.get("startRow", 0),
request.get("endRow", 100),
)
return {"rowData": rows, "rowCount": total}, total
@callback(
Output("nb_rows", "children"),
Output("btn-download-data", "disabled"),
Output("download-hint", "children"),
Input("tableau-total", "data"),
)
# 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 = (
def update_meta(total):
total = total or 0
too_many = total > 65000
hint = (
" · Filtrez sous 65 000 lignes pour activer le téléchargement"
if download_blocked_too_many
if too_many
else ""
)
result.append(download_hint)
return tuple(result)
return f"{total} lignes", too_many, hint
@callback(