Merge branch 'feature/58_filter_url' into dev
This commit is contained in:
+116
-6
@@ -1,8 +1,10 @@
|
|||||||
|
import json
|
||||||
import os
|
import os
|
||||||
|
import urllib.parse
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import polars as pl
|
import polars as pl
|
||||||
from dash import Input, Output, State, callback, dcc, html, register_page
|
from dash import Input, Output, State, callback, dcc, html, no_update, register_page
|
||||||
|
|
||||||
from src.figures import DataTable
|
from src.figures import DataTable
|
||||||
from src.utils import (
|
from src.utils import (
|
||||||
@@ -38,18 +40,21 @@ datatable = html.Div(
|
|||||||
filter_action="custom",
|
filter_action="custom",
|
||||||
sort_action="custom",
|
sort_action="custom",
|
||||||
hidden_columns=get_default_hidden_columns(None),
|
hidden_columns=get_default_hidden_columns(None),
|
||||||
|
columns=[{"id": col, "name": col} for col in df.columns],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
layout = [
|
layout = [
|
||||||
|
dcc.Location(id="url", refresh=False),
|
||||||
html.Div(
|
html.Div(
|
||||||
html.Details(
|
html.Details(
|
||||||
children=[
|
children=[
|
||||||
html.Summary(
|
html.Summary(
|
||||||
html.H3("Mode d'emploi", style={"text-decoration": "underline"}),
|
html.H3("Mode d'emploi", style={"textDecoration": "underline"}),
|
||||||
),
|
),
|
||||||
dcc.Markdown(
|
dcc.Markdown(
|
||||||
"""
|
dangerously_allow_html=True,
|
||||||
|
children="""
|
||||||
##### Définition des colonnes
|
##### Définition des colonnes
|
||||||
|
|
||||||
Pour voir la définition d'une colonne, passez votre souris sur son en-tête.
|
Pour voir la définition d'une colonne, passez votre souris sur son en-tête.
|
||||||
@@ -59,10 +64,10 @@ layout = [
|
|||||||
Vous pouvez appliquer un filtre pour chaque colonne en entrant du texte sous le nom de la colonne, puis en tapant sur `Entrée`.
|
Vous pouvez appliquer un filtre pour chaque colonne en entrant du texte sous le nom de la colonne, puis en tapant sur `Entrée`.
|
||||||
|
|
||||||
- Champs textuels : la recherche est insensible à la casse (majuscules/minuscules) et retourne les valeurs qui contiennent
|
- Champs textuels : la recherche est insensible à la casse (majuscules/minuscules) et retourne les valeurs qui contiennent
|
||||||
le texte recherché. Exemple : `rennes` retourne "RENNES METROPOLE".
|
le texte recherché. Exemple : `rennes` retourne "RENNES METROPOLE". Lorsque vous ouvrez une URL de vue, le format équivalent `icontains rennes` est utilisé.
|
||||||
- Champs numériques : vous pouvez soit taper un nombre pour trouver les valeurs égales, soit le précéder de **>** ou **<** pour filtrer les valeurs supérieures ou inférieures. Exemple pour les offres reçues : `> 4` retourne les marchés ayant reçu plus de 4 offres.
|
- Champs numériques : vous pouvez soit taper un nombre pour trouver les valeurs égales, soit le précéder de **>** ou **<** pour filtrer les valeurs supérieures ou inférieures. Exemple pour les offres reçues : `> 4` retourne les marchés ayant reçu plus de 4 offres.
|
||||||
- Champs date : vous pouvez également utiliser **>** ou **<**. Exemples : `< 2024-01-31` pour "avant le 31 janvier 2024",
|
- Champs date : vous pouvez également utiliser **>** ou **<**. Exemples : `< 2024-01-31` pour "avant le 31 janvier 2024",
|
||||||
`2024` pour "en 2024", `> 2022` pour "à partir de 2022"
|
`2024` pour "en 2024", `> 2022` pour "à partir de 2022". Lorsque vous ouvrez une URL de vue, le format équivalent `i<` ou `i>` est utilisé.
|
||||||
|
|
||||||
Vous pouvez filtrer plusieurs colonnes à la fois. Vos filtres sont remis à zéro quand vous rafraîchissez la page.
|
Vous pouvez filtrer plusieurs colonnes à la fois. Vos filtres sont remis à zéro quand vous rafraîchissez la page.
|
||||||
|
|
||||||
@@ -70,6 +75,10 @@ layout = [
|
|||||||
|
|
||||||
Pour trier une colonne, utilisez les flèches grises à côté des noms de colonnes. Chaque clic change le tri dans cet ordre : tri ascendant, tri descendant, pas de tri.
|
Pour trier une colonne, utilisez les flèches grises à côté des noms de colonnes. Chaque clic change le tri dans cet ordre : tri ascendant, tri descendant, pas de tri.
|
||||||
|
|
||||||
|
##### Partager une vue
|
||||||
|
|
||||||
|
Une vue est un ensemble de filtres, de tris et de choix de colonnes que vous avez appliqué. Vous pouvez copier une adresse Web qui reproduit la vue courante à l'identique en cliquant sur l'icône <img src="/assets/copy.svg" alt="drawing" width="20"/>. En la collant dans la barre d'adresse d'un navigateur, vous ouvrez la vue Tableau avec les mêmes paramètres.
|
||||||
|
|
||||||
##### Télécharger le résultat
|
##### Télécharger le résultat
|
||||||
|
|
||||||
Vous pouvez télécharger le résultat de vos filtres et tris, pour les colonnes affichées, en cliquant sur **Télécharger au format Excel**.
|
Vous pouvez télécharger le résultat de vos filtres et tris, pour les colonnes affichées, en cliquant sur **Télécharger au format Excel**.
|
||||||
@@ -79,7 +88,7 @@ layout = [
|
|||||||
Les liens dans les colonnes Identifiant unique, Acheteur et Titulaire vous permettent de consulter une vue qui leur est dédiée
|
Les liens dans les colonnes Identifiant unique, Acheteur et Titulaire vous permettent de consulter une vue qui leur est dédiée
|
||||||
(informations, marchés attribués/remportés, etc.)
|
(informations, marchés attribués/remportés, etc.)
|
||||||
|
|
||||||
"""
|
""",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
id="instructions",
|
id="instructions",
|
||||||
@@ -100,6 +109,8 @@ layout = [
|
|||||||
html.Div(
|
html.Div(
|
||||||
[
|
[
|
||||||
html.P("lignes", id="nb_rows"),
|
html.P("lignes", id="nb_rows"),
|
||||||
|
html.Div(id="copy-container"),
|
||||||
|
dcc.Input(id="share-url", readOnly=True, style={"display": "none"}),
|
||||||
html.Button(
|
html.Button(
|
||||||
"Téléchargement désactivé au-delà de 65 000 lignes",
|
"Téléchargement désactivé au-delà de 65 000 lignes",
|
||||||
id="btn-download-data",
|
id="btn-download-data",
|
||||||
@@ -133,6 +144,10 @@ layout = [
|
|||||||
State("table", "data_timestamp"),
|
State("table", "data_timestamp"),
|
||||||
)
|
)
|
||||||
def update_table(page_current, page_size, filter_query, sort_by, data_timestamp):
|
def update_table(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(
|
return prepare_table_data(
|
||||||
None, data_timestamp, filter_query, page_current, page_size, sort_by
|
None, data_timestamp, filter_query, page_current, page_size, sort_by
|
||||||
)
|
)
|
||||||
@@ -164,3 +179,98 @@ def download_data(n_clicks, filter_query, sort_by, hidden_columns: list = None):
|
|||||||
|
|
||||||
date = datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
|
date = datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
|
||||||
return dcc.send_bytes(to_bytes, filename=f"decp_{date}.xlsx")
|
return dcc.send_bytes(to_bytes, filename=f"decp_{date}.xlsx")
|
||||||
|
|
||||||
|
|
||||||
|
@callback(
|
||||||
|
Output("table", "filter_query"),
|
||||||
|
Output("table", "sort_by"),
|
||||||
|
Output("table", "hidden_columns"),
|
||||||
|
Output("url", "search", allow_duplicate=True),
|
||||||
|
Input("url", "search"),
|
||||||
|
prevent_initial_call=True,
|
||||||
|
)
|
||||||
|
def restore_view_from_url(search):
|
||||||
|
if not search:
|
||||||
|
return no_update, no_update, no_update, no_update
|
||||||
|
|
||||||
|
params = urllib.parse.parse_qs(search.lstrip("?"))
|
||||||
|
print("params", params)
|
||||||
|
|
||||||
|
filter_query = no_update
|
||||||
|
sort_by = no_update
|
||||||
|
hidden_columns = no_update
|
||||||
|
|
||||||
|
if "filtres" in params:
|
||||||
|
filter_query = params["filtres"][0]
|
||||||
|
|
||||||
|
if "tris" in params:
|
||||||
|
try:
|
||||||
|
sort_by = json.loads(params["tris"][0])
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if "colonnes" in params:
|
||||||
|
try:
|
||||||
|
hidden_columns = json.loads(params["colonnes"][0])
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return filter_query, sort_by, hidden_columns, ""
|
||||||
|
|
||||||
|
|
||||||
|
@callback(
|
||||||
|
Output("share-url", "value"),
|
||||||
|
Output("copy-container", "children"),
|
||||||
|
Input("table", "filter_query"),
|
||||||
|
Input("table", "sort_by"),
|
||||||
|
Input("table", "hidden_columns"),
|
||||||
|
State("url", "href"),
|
||||||
|
)
|
||||||
|
def sync_url_and_reset_button(filter_query, sort_by, hidden_columns, href):
|
||||||
|
if not href:
|
||||||
|
return no_update, no_update
|
||||||
|
|
||||||
|
# Extract base URL (remove existing query params)
|
||||||
|
base_url = href.split("?")[0]
|
||||||
|
|
||||||
|
params = {}
|
||||||
|
if filter_query:
|
||||||
|
params["filtres"] = filter_query
|
||||||
|
|
||||||
|
if sort_by:
|
||||||
|
params["tris"] = json.dumps(sort_by)
|
||||||
|
|
||||||
|
if hidden_columns:
|
||||||
|
params["colonnes"] = json.dumps(hidden_columns)
|
||||||
|
|
||||||
|
query_string = urllib.parse.urlencode(params)
|
||||||
|
full_url = f"{base_url}?{query_string}" if query_string else base_url
|
||||||
|
|
||||||
|
copy_button = dcc.Clipboard(
|
||||||
|
id="btn-copy-url",
|
||||||
|
target_id="share-url",
|
||||||
|
title="Copier l'URL de cette vue",
|
||||||
|
style={
|
||||||
|
"display": "inline-block",
|
||||||
|
"fontSize": 20,
|
||||||
|
"verticalAlign": "top",
|
||||||
|
"cursor": "pointer",
|
||||||
|
},
|
||||||
|
className="fa fa-link",
|
||||||
|
)
|
||||||
|
|
||||||
|
return full_url, copy_button
|
||||||
|
|
||||||
|
|
||||||
|
@callback(
|
||||||
|
Output("copy-container", "children", allow_duplicate=True),
|
||||||
|
Input("btn-copy-url", "n_clicks", allow_optional=True),
|
||||||
|
prevent_initial_call=True,
|
||||||
|
)
|
||||||
|
def show_confirmation(n_clicks):
|
||||||
|
if n_clicks:
|
||||||
|
return html.Span(
|
||||||
|
"Adresse de la vue copiée",
|
||||||
|
style={"color": "green", "fontWeight": "bold", "marginLeft": "10px"},
|
||||||
|
)
|
||||||
|
return no_update
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ def split_filter_part(filter_part):
|
|||||||
["i<", "<"],
|
["i<", "<"],
|
||||||
["i>", ">"],
|
["i>", ">"],
|
||||||
["icontains", "contains"],
|
["icontains", "contains"],
|
||||||
|
# [" ", "contains"]
|
||||||
]
|
]
|
||||||
print("filter part", filter_part)
|
print("filter part", filter_part)
|
||||||
for operator_group in operators:
|
for operator_group in operators:
|
||||||
@@ -543,6 +544,7 @@ def prepare_table_data(
|
|||||||
:param page_current:
|
:param page_current:
|
||||||
:param page_size:
|
:param page_size:
|
||||||
:param sort_by:
|
:param sort_by:
|
||||||
|
:param search_params:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -555,6 +557,24 @@ def prepare_table_data(
|
|||||||
else:
|
else:
|
||||||
lff: pl.LazyFrame = df.lazy() # start from the original data
|
lff: pl.LazyFrame = df.lazy() # start from the original data
|
||||||
|
|
||||||
|
# if search_params:
|
||||||
|
# if "filtres" in search_params:
|
||||||
|
# filter_query = search_params["filtres"][0]
|
||||||
|
#
|
||||||
|
# if "tris" in search_params:
|
||||||
|
# try:
|
||||||
|
# sort_by = json.loads(search_params["tris"][0])
|
||||||
|
# except json.JSONDecodeError:
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
# if "colonnes" in search_params:
|
||||||
|
# try:
|
||||||
|
# hidden_columns = json.loads(search_params["colonnes"][0])
|
||||||
|
# print(hidden_columns)
|
||||||
|
# lff = lff.drop(hidden_columns)
|
||||||
|
# except json.JSONDecodeError:
|
||||||
|
# pass
|
||||||
|
|
||||||
# Application des filtres
|
# Application des filtres
|
||||||
if filter_query:
|
if filter_query:
|
||||||
lff = filter_table_data(lff, filter_query)
|
lff = filter_table_data(lff, filter_query)
|
||||||
|
|||||||
Reference in New Issue
Block a user