refactor: restructure sidebar with chart views, remove placeholder items (Task 7.3)

- Remove non-functional sidebar items: Cost Analysis, Export Data
- Remove filter trigger items: Drug/Trust/Directory Selection, Indications
- Add Chart Views section: Icicle Chart (active), Sankey Diagram (disabled), Timeline (disabled)
- Remove tab row from chart_card.py (chart view selection now in sidebar)
- Remove open_drawer callback (sidebar no longer has filter triggers)
- Add .sidebar__item--disabled CSS class
This commit is contained in:
Andrew Charlwood
2026-02-06 15:29:53 +00:00
parent 74c243c8a2
commit 7aa49b0d6b
5 changed files with 37 additions and 79 deletions
+5
View File
@@ -100,6 +100,11 @@ body {
color: var(--nhs-blue);
font-weight: 600;
}
.sidebar__item--disabled {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
}
.sidebar__item svg { width: 18px; height: 18px; flex-shrink: 0; }
.sidebar__icon { width: 18px; height: 18px; flex-shrink: 0; }
.sidebar__footer {
+7 -13
View File
@@ -1,20 +1,14 @@
"""Callbacks for the drug browser drawer: open/close, drug selection, fragment matching, clear."""
"""Callbacks for drug selection: fragment matching and clear filters.
The open_drawer callback was removed in Task 7.3 (sidebar restructure) because
the sidebar no longer has filter trigger items. Task 7.4 will replace the drawer
with modals opened from the filter bar.
"""
from dash import Input, Output, State, ctx, no_update, ALL
def register_drawer_callbacks(app):
"""Register drawer-related callbacks."""
@app.callback(
Output("drug-drawer", "opened"),
Input("sidebar-drug-selection", "n_clicks"),
Input("sidebar-indications", "n_clicks"),
Input("sidebar-trust-selection", "n_clicks"),
prevent_initial_call=True,
)
def open_drawer(_drug_clicks, _indication_clicks, _trust_clicks):
"""Open the drawer when sidebar Drug Selection, Indications, or Trust Selection is clicked."""
return True
"""Register drug/trust selection callbacks (fragment matching + clear)."""
@app.callback(
Output("all-drugs-chips", "value"),
+2 -29
View File
@@ -1,4 +1,4 @@
"""Chart card component — header, tabs, and dcc.Graph for icicle chart."""
"""Chart card component — header and dcc.Graph for icicle chart."""
from dash import html, dcc
@@ -7,8 +7,8 @@ def make_chart_card():
Contains:
- Header with title and dynamic subtitle (hierarchy label)
- Tab row (Icicle active, Sankey and Timeline as disabled placeholders)
- dcc.Loading wrapper around dcc.Graph for loading spinner
Chart view selection (icicle/sankey/timeline) is in the sidebar.
"""
return html.Section(
className="chart-card",
@@ -33,33 +33,6 @@ def make_chart_card():
),
],
),
# Tab row
html.Div(
className="chart-card__tabs",
role="tablist",
children=[
html.Button(
"Icicle",
className="chart-tab chart-tab--active",
role="tab",
**{"aria-selected": "true"},
),
html.Button(
"Sankey",
className="chart-tab",
role="tab",
disabled=True,
**{"aria-selected": "false"},
),
html.Button(
"Timeline",
className="chart-tab",
role="tab",
disabled=True,
**{"aria-selected": "false"},
),
],
),
# Chart area with loading spinner
dcc.Loading(
type="circle",
+15 -29
View File
@@ -5,11 +5,7 @@ from dash import html
def _svg_icon(svg_body):
"""Wrap an SVG body string into an html.Img using a data URI.
This avoids needing dash-svg or dangerouslySetInnerHTML.
The SVG icons are copied from 01_nhs_classic.html.
"""
"""Wrap an SVG body string into an html.Img using a data URI."""
svg = (
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" '
f'fill="none" stroke="currentColor" stroke-width="2">{svg_body}</svg>'
@@ -20,15 +16,12 @@ def _svg_icon(svg_body):
)
# SVG icon bodies from 01_nhs_classic.html
# SVG icon bodies (Feather-style)
_ICONS = {
"pathway": '<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>',
"drug": '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/>',
"trust": '<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/>',
"directory": '<path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/>',
"indication": '<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14,2 14,8 20,8"/>',
"cost": '<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>',
"export": '<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>',
"icicle": '<rect x="3" y="3" width="18" height="4" rx="1"/><rect x="3" y="10" width="10" height="4" rx="1"/><rect x="3" y="17" width="6" height="4" rx="1"/>',
"sankey": '<path d="M6 3v18"/><path d="M18 3v18"/><path d="M6 8c6 0 6 5 12 5"/><path d="M6 16c4 0 4-3 12-3"/>',
"timeline": '<line x1="3" y1="12" x2="21" y2="12"/><circle cx="6" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="18" cy="12" r="2"/>',
}
@@ -38,31 +31,22 @@ def make_sidebar():
className="sidebar",
**{"aria-label": "Main navigation"},
children=[
# Analysis section
# Overview section
html.Div(
className="sidebar__section",
children=[
html.Div("Analysis", className="sidebar__label"),
html.Div("Overview", className="sidebar__label"),
_sidebar_item("Pathway Overview", "pathway", active=True),
_sidebar_item(
"Drug Selection", "drug", item_id="sidebar-drug-selection"
),
_sidebar_item(
"Trust Selection", "trust", item_id="sidebar-trust-selection"
),
_sidebar_item("Directory Selection", "directory"),
_sidebar_item(
"Indications", "indication", item_id="sidebar-indications"
),
],
),
# Reports section
# Chart views section
html.Div(
className="sidebar__section",
children=[
html.Div("Reports", className="sidebar__label"),
_sidebar_item("Cost Analysis", "cost"),
_sidebar_item("Export Data", "export"),
html.Div("Chart Views", className="sidebar__label"),
_sidebar_item("Icicle Chart", "icicle", active=True),
_sidebar_item("Sankey Diagram", "sankey", disabled=True),
_sidebar_item("Timeline", "timeline", disabled=True),
],
),
# Footer
@@ -78,11 +62,13 @@ def make_sidebar():
)
def _sidebar_item(label, icon_key, active=False, item_id=None):
def _sidebar_item(label, icon_key, active=False, disabled=False, item_id=None):
"""Create a single sidebar navigation item."""
class_name = "sidebar__item"
if active:
class_name += " sidebar__item--active"
if disabled:
class_name += " sidebar__item--disabled"
props = {"className": class_name}
if item_id: