Merge branch 'feature/mode_demploi_modal' into dev
This commit is contained in:
@@ -27,18 +27,50 @@ b {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 36px 0 20px 0;
|
||||
}
|
||||
|
||||
/* Base Button Styles */
|
||||
/* Base Button Styles
|
||||
button {
|
||||
font-weight: 400;
|
||||
background-color: #fff;
|
||||
border-radius: 3px;
|
||||
appearance: auto;
|
||||
border: solid var(--primary-color) 1px;
|
||||
/* couleur thème foncée */
|
||||
} */
|
||||
|
||||
button.btn.btn-primary,
|
||||
button.show-hide {
|
||||
display: block;
|
||||
border-radius: 3px;
|
||||
outline: 0;
|
||||
color: #fff;
|
||||
border: 0;
|
||||
height: 30px;
|
||||
padding-top: 2px;
|
||||
background-image: linear-gradient(
|
||||
rgb(209, 96, 73),
|
||||
rgb(179, 56, 33) 26%,
|
||||
rgb(159, 36, 22)
|
||||
);
|
||||
}
|
||||
|
||||
button.btn.btn-primary:hover,
|
||||
button.show-hide:hover {
|
||||
background-image: linear-gradient(
|
||||
rgb(239, 126, 103),
|
||||
rgb(209, 86, 63) 26%,
|
||||
rgb(189, 66, 52)
|
||||
);
|
||||
}
|
||||
|
||||
button[disabled] {
|
||||
@@ -160,12 +192,13 @@ p.version > a {
|
||||
.table-menu {
|
||||
font-size: 16px;
|
||||
margin: 12px 0 12px 0;
|
||||
height: 36px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.table-menu > * {
|
||||
margin: 8px 16px 8px 0;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#source_table {
|
||||
@@ -213,11 +246,11 @@ table.cell-table th {
|
||||
font-family: "Inter", sans-serif;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
padding-right: 12px;
|
||||
padding: 2px 12px 4px 2px;
|
||||
border: 1px solid rgb(179, 56, 33) !important;
|
||||
background-color: rgb(179, 56, 33);
|
||||
border-bottom: none !important;
|
||||
height: 34px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.dash-table-container
|
||||
|
||||
+17
-38
@@ -6,7 +6,7 @@ import plotly.graph_objects as go
|
||||
import polars as pl
|
||||
from dash import dash_table, dcc, html
|
||||
|
||||
from src.utils import format_number
|
||||
from src.utils import data_schema, format_number
|
||||
|
||||
|
||||
def get_map_count_marches(df: pl.DataFrame):
|
||||
@@ -91,11 +91,8 @@ def get_yearly_statistics(statistics, today_str) -> html.Div:
|
||||
page_size=10,
|
||||
sort_action="none",
|
||||
filter_action="none",
|
||||
style_header={
|
||||
"border": "solid 1px rgb(179, 56, 33)",
|
||||
"backgroundColor": "rgb(179, 56, 33)",
|
||||
"color": "white",
|
||||
},
|
||||
style_header={"fontFamily": "Inter", "fontSize": "16px"},
|
||||
style_cell={"fontFamily": "Inter", "fontSize": "16px"},
|
||||
)
|
||||
|
||||
return html.Div(children=table, className="marches_table")
|
||||
@@ -210,11 +207,8 @@ def get_sources_tables(source_path) -> html.Div:
|
||||
],
|
||||
sort_action="native",
|
||||
markdown_options={"html": True},
|
||||
style_header={
|
||||
"border": "solid 1px rgb(179, 56, 33)",
|
||||
"backgroundColor": "rgb(179, 56, 33)",
|
||||
"color": "white",
|
||||
},
|
||||
style_header={"fontFamily": "Inter", "fontSize": "16px"},
|
||||
style_cell={"fontFamily": "Inter", "fontSize": "16px"},
|
||||
)
|
||||
|
||||
return html.Div(children=datatable)
|
||||
@@ -295,20 +289,18 @@ class DataTable(dash_table.DataTable):
|
||||
"lineHeight": "18px",
|
||||
"whiteSpace": "normal",
|
||||
},
|
||||
{
|
||||
"if": {"column_id": "montant"},
|
||||
"textAlign": "right",
|
||||
},
|
||||
{
|
||||
"if": {"column_id": "dureeMois"},
|
||||
"textAlign": "right",
|
||||
},
|
||||
{
|
||||
"if": {"column_id": "titulaire_distance"},
|
||||
"textAlign": "right",
|
||||
},
|
||||
]
|
||||
|
||||
for key in data_schema.keys():
|
||||
field = data_schema[key]
|
||||
if field["type"] in ["number", "integer"]:
|
||||
rule = {
|
||||
"if": {"column_id": field["name"]},
|
||||
"textAlign": "right",
|
||||
# "fontFamily": "Fira Code",
|
||||
}
|
||||
style_cell_conditional.append(rule)
|
||||
|
||||
# Initialisation de la classe parente avec les arguments
|
||||
super().__init__(
|
||||
id=dtid,
|
||||
@@ -330,6 +322,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"},
|
||||
tooltip_duration=8000,
|
||||
tooltip_delay=350,
|
||||
hidden_columns=hidden_columns,
|
||||
@@ -360,13 +354,6 @@ def get_duplicate_matrix() -> html.Div:
|
||||
|
||||
Passez votre souris sur une case pour avoir les pourcentages exacts. À noter que ces statistiques sont produites avant le dédoublonnement qui a lieu avant la publication en Open Data et sur ce site.""")
|
||||
|
||||
# Assuming result_df is your DataFrame with structure:
|
||||
# | sourceDataset | unique | dataset1 | dataset2 | dataset3 |
|
||||
# |---------------|--------|----------|----------|----------|
|
||||
# | dataset1 | 0.8 | | 0.15 | 0.2 |
|
||||
# | dataset2 | 0.75 | 0.15 | | 0.12 |
|
||||
# | dataset3 | 0.85 | 0.2 | 0.12 | |
|
||||
|
||||
# Extract data
|
||||
z_data = result_df.select(pl.all().exclude("sourceDataset")).fill_null(0).to_numpy()
|
||||
x_labels = result_df.columns[1:] # columns after "sourceDataset"
|
||||
@@ -378,12 +365,6 @@ def get_duplicate_matrix() -> html.Div:
|
||||
z=z_data,
|
||||
x=x_labels,
|
||||
y=y_labels,
|
||||
# colorscale=[
|
||||
# [0, "white"], # 0% → white
|
||||
# [0.10, "lightblue"], # 1% → light blue (soft start)
|
||||
# [0.50, "steelblue"], # 50% → medium blue
|
||||
# [1, "darkblue"], # 100% → dark blue
|
||||
# ],
|
||||
colorscale=[
|
||||
[0.0, "white"], # 0% → white
|
||||
[0.10, "lightsalmon"], # 10% → light warm tone
|
||||
@@ -391,8 +372,6 @@ def get_duplicate_matrix() -> html.Div:
|
||||
],
|
||||
zmin=0,
|
||||
zmax=1,
|
||||
# texttemplate="%{z:.0%}", # Format as percentage
|
||||
# textfont={"size": 10, "color": "black"}, # Smaller font
|
||||
hoverongaps=False,
|
||||
showscale=True,
|
||||
hovertemplate=(
|
||||
|
||||
@@ -71,7 +71,7 @@ layout = [
|
||||
className="org_year",
|
||||
children=dcc.Dropdown(
|
||||
id="acheteur_year",
|
||||
options=["Toutes"]
|
||||
options=["Toutes les années"]
|
||||
+ [
|
||||
str(year)
|
||||
for year in range(
|
||||
@@ -109,6 +109,7 @@ layout = [
|
||||
html.Button(
|
||||
"Téléchargement au format Excel",
|
||||
id="btn-download-data-acheteur",
|
||||
className="btn btn-primary",
|
||||
),
|
||||
dcc.Download(id="download-data-acheteur"),
|
||||
],
|
||||
@@ -136,6 +137,7 @@ layout = [
|
||||
html.Button(
|
||||
"Téléchargement désactivé au-delà de 65 000 lignes",
|
||||
id="btn-download-filtered-data-acheteur",
|
||||
className="btn btn-primary",
|
||||
disabled=True,
|
||||
),
|
||||
dcc.Download(id="acheteur-download-filtered-data"),
|
||||
@@ -229,7 +231,7 @@ def get_acheteur_marches_data(url, acheteur_year: str) -> tuple:
|
||||
acheteur_siret = url.split("/")[-1]
|
||||
lff = df.lazy()
|
||||
lff = lff.filter(pl.col("acheteur_id") == acheteur_siret)
|
||||
if acheteur_year and acheteur_year != "Toutes":
|
||||
if acheteur_year and acheteur_year != "Toutes les années":
|
||||
acheteur_year = int(acheteur_year)
|
||||
lff = lff.filter(pl.col("dateNotification").dt.year() == acheteur_year)
|
||||
lff = lff.sort(["dateNotification", "uid"], descending=True, nulls_last=True)
|
||||
@@ -290,7 +292,7 @@ def download_acheteur_data(
|
||||
|
||||
def to_bytes(buffer):
|
||||
df_to_download.write_excel(
|
||||
buffer, worksheet="DECP" if annee in ["Toutes", None] else annee
|
||||
buffer, worksheet="DECP" if annee in ["Toutes les années", None] else annee
|
||||
)
|
||||
|
||||
date = datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
|
||||
|
||||
@@ -46,20 +46,20 @@ layout = html.Div(
|
||||
"width": "500px",
|
||||
"border": "1px solid #ccc",
|
||||
"borderRight": "none",
|
||||
"borderRadius": "4px 0 0 4px",
|
||||
"borderRadius": "3px 0 0 3px",
|
||||
"padding": "5px 10px",
|
||||
"outline": "none",
|
||||
"height": "34px",
|
||||
},
|
||||
),
|
||||
html.Button(
|
||||
"🔍",
|
||||
"=>",
|
||||
id="search-button",
|
||||
className="btn btn-primary",
|
||||
style={
|
||||
"border": "1px solid #ccc",
|
||||
"borderRadius": "0 4px 4px 0",
|
||||
"borderRadius": "0 3px 3px 0",
|
||||
"marginLeft": "0",
|
||||
"backgroundColor": "#f0f0f0",
|
||||
"cursor": "pointer",
|
||||
"height": "auto", # Ensure it matches input height if necessary, often relying on padding/line-height
|
||||
},
|
||||
),
|
||||
|
||||
+40
-15
@@ -4,6 +4,7 @@ import urllib.parse
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
import dash_bootstrap_components as dbc
|
||||
import polars as pl
|
||||
from dash import (
|
||||
ClientsideFunction,
|
||||
@@ -125,14 +126,15 @@ layout = [
|
||||
style={"maxWidth": "1000px"},
|
||||
),
|
||||
html.Div(
|
||||
html.Details(
|
||||
children=[
|
||||
html.Summary(
|
||||
html.H4("Mode d'emploi", style={"textDecoration": "underline"}),
|
||||
),
|
||||
dcc.Markdown(
|
||||
dangerously_allow_html=True,
|
||||
children=f"""
|
||||
[
|
||||
dbc.Button("Mode d'emploi", id="tableau_help_open"),
|
||||
dbc.Modal(
|
||||
[
|
||||
dbc.ModalHeader(dbc.ModalTitle("Header")),
|
||||
dbc.ModalBody(
|
||||
dcc.Markdown(
|
||||
dangerously_allow_html=True,
|
||||
children=f"""
|
||||
##### Définition des colonnes
|
||||
|
||||
Pour voir la définition d'une colonne, passez votre souris sur son en-tête.
|
||||
@@ -144,11 +146,9 @@ layout = [
|
||||
- Champs textuels : la recherche retourne les valeurs qui contiennent le texte recherché et n'est pas sensible à la casse (majuscules/minuscules).
|
||||
- Exemple : `rennes` retourne "RENNES METROPOLE".
|
||||
- Les guillemets simples (apostrophe du 4) doivent être prédédées d'une barre oblique (AltGr + 8). Exemple : `services d\\\'assurances`
|
||||
- Lorsque vous ouvrez une URL de vue (voir "Partager une vue" plus bas), le format équivalent `icontains rennes` est utilisé. Mais dans vos filtres pas besoin de taper `icontains` !
|
||||
- Champs numériques (Durée en mois, Montant, ...) : vous pouvez...
|
||||
- soit taper un nombre pour trouver les valeurs strictement égales. Exemple : `12` ne retourne que des 12
|
||||
- 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.
|
||||
- lorsque vous ouvrez une URL de vue (voir "Partager une vue" plus bas), le format équivalent `i<` ou `i>` est utilisé, mais c'est un bug : vous n'avez pas besoin de taper le `i` pour appliquer ce filtre.
|
||||
- Champs date (Date de notification, ...) : vous pouvez également utiliser **>** ou **<**. Exemples :
|
||||
- `< 2024-01-31` pour "avant le 31 janvier 2024"
|
||||
- `2024` pour "en 2024", `> 2022` pour "à partir de 2022".
|
||||
@@ -188,10 +188,24 @@ layout = [
|
||||
(informations, marchés attribués/remportés, etc.)
|
||||
|
||||
""",
|
||||
),
|
||||
],
|
||||
id="instructions",
|
||||
),
|
||||
),
|
||||
),
|
||||
dbc.ModalFooter(
|
||||
dbc.Button(
|
||||
"Close",
|
||||
id="tableau_help_close",
|
||||
className="ms-auto",
|
||||
n_clicks=0,
|
||||
)
|
||||
),
|
||||
],
|
||||
id="tableau_help",
|
||||
is_open=False,
|
||||
fullscreen="md-down",
|
||||
scrollable=True,
|
||||
size="lg",
|
||||
),
|
||||
],
|
||||
id="header",
|
||||
),
|
||||
# html.Div(
|
||||
@@ -210,7 +224,7 @@ layout = [
|
||||
html.P("lignes", id="nb_rows"),
|
||||
html.Div(id="copy-container"),
|
||||
dcc.Input(id="share-url", readOnly=True, style={"display": "none"}),
|
||||
html.Button(
|
||||
dbc.Button(
|
||||
"Téléchargement désactivé au-delà de 65 000 lignes",
|
||||
id="btn-download-data",
|
||||
disabled=True,
|
||||
@@ -389,3 +403,14 @@ def show_confirmation(n_clicks):
|
||||
style={"color": "green", "fontWeight": "bold", "marginLeft": "10px"},
|
||||
)
|
||||
return no_update
|
||||
|
||||
|
||||
@callback(
|
||||
Output("tableau_help", "is_open"),
|
||||
[Input("tableau_help_open", "n_clicks"), Input("tableau_help_close", "n_clicks")],
|
||||
[State("tableau_help", "is_open")],
|
||||
)
|
||||
def toggle_tableau_help(click_open, click_close, is_open):
|
||||
if click_open or click_close:
|
||||
return not is_open
|
||||
return is_open
|
||||
|
||||
@@ -71,7 +71,7 @@ layout = [
|
||||
className="org_year",
|
||||
children=dcc.Dropdown(
|
||||
id="titulaire_year",
|
||||
options=["Toutes"]
|
||||
options=["Toutes les années"]
|
||||
+ [
|
||||
str(year)
|
||||
for year in range(
|
||||
@@ -235,7 +235,7 @@ def get_titulaire_marches_data(url, titulaire_year: str) -> tuple:
|
||||
(pl.col("titulaire_id") == titulaire_siret)
|
||||
& (pl.col("titulaire_typeIdentifiant") == "SIRET")
|
||||
)
|
||||
if titulaire_year and titulaire_year != "Toutes":
|
||||
if titulaire_year and titulaire_year != "Toutes les années":
|
||||
lff = lff.filter(
|
||||
pl.col("dateNotification").cast(pl.String).str.starts_with(titulaire_year)
|
||||
)
|
||||
@@ -305,7 +305,7 @@ def download_titulaire_data(
|
||||
|
||||
def to_bytes(buffer):
|
||||
df_to_download.write_excel(
|
||||
buffer, worksheet="DECP" if annee in ["Toutes", None] else annee
|
||||
buffer, worksheet="DECP" if annee in ["Toutes les années", None] else annee
|
||||
)
|
||||
|
||||
date = datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
|
||||
|
||||
+7
-13
@@ -284,7 +284,7 @@ def filter_table_data(
|
||||
lff: pl.LazyFrame, filter_query: str, filter_source: str
|
||||
) -> pl.LazyFrame:
|
||||
_schema = lff.collect_schema()
|
||||
track_search(f"{filter_source}: {filter_query}")
|
||||
track_search(filter_query, filter_source)
|
||||
filtering_expressions = filter_query.split(" && ")
|
||||
for filter_part in filtering_expressions:
|
||||
col_name, operator, filter_value = split_filter_part(filter_part)
|
||||
@@ -464,21 +464,15 @@ def get_data_schema() -> dict:
|
||||
return new_schema
|
||||
|
||||
|
||||
def track_search(query):
|
||||
if (
|
||||
len(query) >= 4
|
||||
and os.getenv("DEVELOPMENT").lower != "true"
|
||||
and os.getenv("MATOMO_DOMAIN")
|
||||
):
|
||||
if os.getenv("DEVELOPMENT").lower() == "true":
|
||||
url = "https://test.decp.info"
|
||||
else:
|
||||
url = "https://decp.info"
|
||||
def track_search(query, category):
|
||||
if len(query) >= 4 and not development and os.getenv("MATOMO_DOMAIN"):
|
||||
url = "https://decp.info"
|
||||
params = {
|
||||
"idsite": os.getenv("MATOMO_ID_SITE"),
|
||||
"url": url,
|
||||
"rec": "1",
|
||||
"action_name": "front_page_search",
|
||||
"action_name": "search" if category == "home_page_search" else "filter",
|
||||
"search_cat": category,
|
||||
"rand": uuid.uuid4().hex,
|
||||
"apiv": "1",
|
||||
"h": localtime().tm_hour,
|
||||
@@ -506,7 +500,7 @@ def search_org(dff: pl.DataFrame, query: str, org_type: str) -> pl.DataFrame:
|
||||
return dff.select(pl.lit(False).alias("matches"))
|
||||
|
||||
# Enregistrement des recherche dans Matomo
|
||||
track_search(query)
|
||||
track_search(query, "home_page_search")
|
||||
|
||||
# Normalize query
|
||||
normalized_query = unidecode(query.strip()).upper()
|
||||
|
||||
Reference in New Issue
Block a user