feat: helpers UI/validation des vues sauvegardées #95
This commit is contained in:
@@ -0,0 +1,69 @@
|
|||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
from dash import html
|
||||||
|
|
||||||
|
|
||||||
|
def bar_style(has_subscription: bool) -> dict:
|
||||||
|
return {} if has_subscription else {"display": "none"}
|
||||||
|
|
||||||
|
|
||||||
|
def clean_view_name(name: str | None) -> str:
|
||||||
|
return (name or "").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_view_to_save(
|
||||||
|
has_subscription: bool, name: str | None
|
||||||
|
) -> tuple[str | None, str | None]:
|
||||||
|
if not has_subscription:
|
||||||
|
return None, "Réservé aux abonné·es."
|
||||||
|
clean = clean_view_name(name)
|
||||||
|
if not clean:
|
||||||
|
return None, "Veuillez saisir un nom pour la vue."
|
||||||
|
return clean, None
|
||||||
|
|
||||||
|
|
||||||
|
def saved_views_items(views) -> list:
|
||||||
|
return [
|
||||||
|
dbc.DropdownMenuItem(view["name"], href=f"/tableau?{view['query']}")
|
||||||
|
for view in views
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _view_row(view) -> html.Div:
|
||||||
|
view_id = view["id"]
|
||||||
|
return html.Div(
|
||||||
|
className="saved-view-row d-flex align-items-center gap-2 mb-2",
|
||||||
|
children=[
|
||||||
|
html.Span(view["name"], className="flex-grow-1"),
|
||||||
|
dbc.Button(
|
||||||
|
"Ouvrir",
|
||||||
|
href=f"/tableau?{view['query']}",
|
||||||
|
color="link",
|
||||||
|
size="sm",
|
||||||
|
),
|
||||||
|
dbc.Button(
|
||||||
|
"Renommer",
|
||||||
|
id={"type": "vue-rename-open", "index": view_id},
|
||||||
|
color="secondary",
|
||||||
|
outline=True,
|
||||||
|
size="sm",
|
||||||
|
),
|
||||||
|
dbc.Button(
|
||||||
|
"Supprimer",
|
||||||
|
id={"type": "vue-delete", "index": view_id},
|
||||||
|
color="danger",
|
||||||
|
outline=True,
|
||||||
|
size="sm",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def views_table(views) -> html.Div:
|
||||||
|
if not views:
|
||||||
|
return html.Div(
|
||||||
|
html.P(
|
||||||
|
"Vous n'avez pas encore de vue enregistrée. "
|
||||||
|
"Créez-en une depuis le Tableau, bouton « Sauvegarder la vue »."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return html.Div([_view_row(v) for v in views])
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
from src.saved_views import ui
|
||||||
|
|
||||||
|
|
||||||
|
class _Row(dict):
|
||||||
|
"""Imite un sqlite3.Row : accès par clé."""
|
||||||
|
|
||||||
|
|
||||||
|
def _view(view_id, name, query):
|
||||||
|
return _Row(id=view_id, name=name, query=query)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bar_style_hidden_for_non_subscriber():
|
||||||
|
assert ui.bar_style(False) == {"display": "none"}
|
||||||
|
assert ui.bar_style(True) == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_clean_view_name_strips_and_empties():
|
||||||
|
assert ui.clean_view_name(" Ma vue ") == "Ma vue"
|
||||||
|
assert ui.clean_view_name(" ") == ""
|
||||||
|
assert ui.clean_view_name(None) == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_prepare_refuses_non_subscriber():
|
||||||
|
name, err = ui.prepare_view_to_save(False, "Ma vue")
|
||||||
|
assert name is None
|
||||||
|
assert err
|
||||||
|
|
||||||
|
|
||||||
|
def test_prepare_refuses_empty_name():
|
||||||
|
name, err = ui.prepare_view_to_save(True, " ")
|
||||||
|
assert name is None
|
||||||
|
assert err
|
||||||
|
|
||||||
|
|
||||||
|
def test_prepare_accepts_valid():
|
||||||
|
name, err = ui.prepare_view_to_save(True, " Ma vue ")
|
||||||
|
assert name == "Ma vue"
|
||||||
|
assert err is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_saved_views_items_build_links():
|
||||||
|
items = ui.saved_views_items(
|
||||||
|
[_view(1, "Vue A", "filtres=a"), _view(2, "Vue B", "tris=b")]
|
||||||
|
)
|
||||||
|
assert len(items) == 2
|
||||||
|
assert items[0].href == "/tableau?filtres=a"
|
||||||
|
assert items[0].children == "Vue A"
|
||||||
|
|
||||||
|
|
||||||
|
def test_views_table_empty_state():
|
||||||
|
out = ui.views_table([])
|
||||||
|
# un Div non vide (message d'état) sans item de suppression
|
||||||
|
assert out is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_views_table_lists_views():
|
||||||
|
out = ui.views_table([_view(1, "Vue A", "filtres=a")])
|
||||||
|
text = str(out)
|
||||||
|
assert "Vue A" in text
|
||||||
Reference in New Issue
Block a user