diff --git a/src/pages/etapes.py b/src/pages/etapes.py index c6737d4..526e611 100644 --- a/src/pages/etapes.py +++ b/src/pages/etapes.py @@ -17,6 +17,141 @@ register_page( image_url=META_CONTENT["image_url"], ) + +def _lane(*bars): + """Une ligne d'étape : fond segmenté en 5 + barres positionnées.""" + return html.Div( + className="etapes-lane", + children=[ + html.Div( + className="etapes-segs", + children=[html.Div() for _ in range(5)], + ), + *bars, + ], + ) + + +def _bar(label, color, style): + base = {"backgroundColor": color} + base.update(style) + return html.Div(label, className="etapes-bar", style=base) + + +def build_chart(): + return html.Div( + className="etapes-chart-scroll", + children=html.Div( + className="etapes-chart", + children=[ + # En-tête : coin vide + 5 marqueurs de seuils + html.Div(className="etapes-corner"), + html.Div( + className="etapes-xhead", + children=[ + html.Div("0 €", className="etapes-xcell"), + html.Div( + [html.Strong("40 000 €"), "seuil DECP"], + className="etapes-xcell", + ), + html.Div( + [html.Strong("90 000 €"), "publicité"], + className="etapes-xcell", + ), + html.Div( + [html.Strong("140 k€ / 216 k€"), "seuils formalisés (UE)"], + className="etapes-xcell", + ), + html.Div( + [html.Strong("5,404 M€"), "travaux (UE)"], + className="etapes-xcell", + ), + ], + ), + # Programmation + html.Div("Programmation", className="etapes-stage"), + _lane( + _bar( + "Approch — sourcing / préinformation (non réglementaire)", + "#7c5cff", + {"left": "2%", "right": "2%"}, + ), + ), + # Publicité (appel d'offres) + html.Div( + ["Publicité ", html.Small("(appel d'offres)")], + className="etapes-stage", + ), + _lane( + _bar( + "Journaux d'annonces légales", + "#f79009", + {"left": "40%", "right": "40%", "top": "6px", "height": "20px"}, + ), + _bar( + "BOAMP", + "#1570ef", + {"left": "40%", "right": "2%", "top": "28px", "height": "20px"}, + ), + _bar( + "JOUE — avis de marché", + "#0e9384", + {"left": "60%", "right": "2%", "top": "6px", "height": "20px"}, + ), + ), + # Attribution + html.Div("Attribution", className="etapes-stage"), + _lane( + _bar( + "DECP — données essentielles", + "#12b76a", + {"left": "20%", "right": "2%", "top": "6px", "height": "20px"}, + ), + _bar( + "JOUE — avis d'attribution", + "#0e9384", + {"left": "60%", "right": "2%", "top": "28px", "height": "20px"}, + ), + ), + # Contrat (vide) + html.Div("Contrat", className="etapes-stage"), + html.Div( + "— aucune donnée publiée aujourd'hui —", + className="etapes-lane etapes-empty", + ), + # Paiement (vide) + html.Div("Paiement", className="etapes-stage"), + html.Div( + "— aucune donnée publiée aujourd'hui —", + className="etapes-lane etapes-empty", + ), + ], + ), + ) + + +def build_legend(): + items = [ + ("Approch", "#7c5cff"), + ("Journaux d'annonces légales", "#f79009"), + ("BOAMP", "#1570ef"), + ("JOUE", "#0e9384"), + ("DECP", "#12b76a"), + ] + return html.Div( + className="etapes-legend", + children=[ + html.Span( + [ + html.I(style={"backgroundColor": color}), + label, + ] + ) + for label, color in items + ], + ) + + layout = html.Div( className="container", children=[ @@ -28,7 +163,8 @@ layout = html.Div( "principales publications de données par **étape** (de haut en " "bas) et par **seuil** (de gauche à droite, en euros hors taxes)." ), - # Le graphique sera inséré ici en Task 2 + build_chart(), + build_legend(), dcc.Markdown( "**À noter :** l'axe horizontal n'est pas linéaire — les seuils " "sont espacés régulièrement pour rester lisibles. Les étapes "