diff --git a/src/pages/_compte_shell.py b/src/pages/_compte_shell.py index 7769bf9..46a6bd3 100644 --- a/src/pages/_compte_shell.py +++ b/src/pages/_compte_shell.py @@ -29,6 +29,12 @@ SECTIONS = [ "href": "/compte/filtres", "require_subscription": True, }, + { + "key": "vues", + "label": "Mes vues", + "href": "/compte/vues", + "require_subscription": True, + }, { "key": "siret", "label": "Mon SIRET", diff --git a/src/pages/compte_vues.py b/src/pages/compte_vues.py new file mode 100644 index 0000000..d14ee73 --- /dev/null +++ b/src/pages/compte_vues.py @@ -0,0 +1,103 @@ +import dash_bootstrap_components as dbc +from dash import ( + ALL, + Input, + Output, + State, + callback, + ctx, + html, + no_update, + register_page, +) +from flask_login import current_user + +from src.pages._compte_shell import account_guard, account_shell +from src.saved_views import db as saved_views_db +from src.saved_views import ui as saved_views_ui + +register_page( + __name__, + path="/compte/vues", + title="Mes vues | decp.info", + name="Mes vues", + description="Gérez vos vues enregistrées du tableau des marchés.", +) + + +def _content(): + views = saved_views_db.list_views(current_user.id, "tableau") + return html.Div( + [ + html.H2("Mes vues"), + html.P( + "Les vues que vous enregistrez depuis le Tableau apparaissent ici. " + "Cliquez sur « Ouvrir » pour appliquer une vue." + ), + html.Div(saved_views_ui.views_table(views), id="vues-list"), + dbc.Modal( + id="vue-rename-modal", + is_open=False, + children=[ + dbc.ModalHeader(dbc.ModalTitle("Renommer la vue")), + dbc.ModalBody( + dbc.Input(id="vue-rename-input", type="text"), + ), + dbc.ModalFooter( + dbc.Button("Renommer", id="vue-rename-confirm", color="primary") + ), + ], + ), + dbc.Input(id="vue-rename-id", type="hidden"), + ] + ) + + +def layout(**_): + guard = account_guard("/compte/vues", require_subscription=True) + if guard is not None: + return guard + return account_shell("vues", _content()) + + +@callback( + Output("vues-list", "children"), + Input({"type": "vue-delete", "index": ALL}, "n_clicks"), + prevent_initial_call=True, +) +def delete_view(n_clicks): + if not ctx.triggered_id or not any(n_clicks): + return no_update + saved_views_db.delete(ctx.triggered_id["index"], current_user.id) + views = saved_views_db.list_views(current_user.id, "tableau") + return saved_views_ui.views_table(views) + + +@callback( + Output("vue-rename-modal", "is_open"), + Output("vue-rename-id", "value"), + Input({"type": "vue-rename-open", "index": ALL}, "n_clicks"), + Input("vue-rename-confirm", "n_clicks"), + State("vue-rename-modal", "is_open"), + prevent_initial_call=True, +) +def toggle_rename_modal(_open, _confirm, is_open): + if isinstance(ctx.triggered_id, dict) and any(_open): + return True, str(ctx.triggered_id["index"]) + return False, no_update + + +@callback( + Output("vues-list", "children", allow_duplicate=True), + Input("vue-rename-confirm", "n_clicks"), + State("vue-rename-id", "value"), + State("vue-rename-input", "value"), + prevent_initial_call=True, +) +def rename_view(_n, view_id, new_name): + clean = saved_views_ui.clean_view_name(new_name) + if not view_id or not clean: + return no_update + saved_views_db.rename(int(view_id), current_user.id, clean) + views = saved_views_db.list_views(current_user.id, "tableau") + return saved_views_ui.views_table(views) diff --git a/tests/saved_views/test_compte_vues.py b/tests/saved_views/test_compte_vues.py new file mode 100644 index 0000000..eaec535 --- /dev/null +++ b/tests/saved_views/test_compte_vues.py @@ -0,0 +1,17 @@ +from src.pages import _compte_shell as shell + + +def test_vues_section_is_gated_subscription(): + section = next(s for s in shell.SECTIONS if s["key"] == "vues") + assert section["href"] == "/compte/vues" + assert section["require_subscription"] is True + + +def test_vues_hidden_without_subscription(): + keys = {s["key"] for s in shell.visible_sections(has_subscription=False)} + assert "vues" not in keys + + +def test_vues_visible_with_subscription(): + keys = {s["key"] for s in shell.visible_sections(has_subscription=True)} + assert "vues" in keys