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
+8 -8
View File
@@ -286,12 +286,12 @@ Drawer selection → update_drug_selection → app-state store → load_pathway_
- **Checkpoint**: Drug selection filters chart without "multiple implied roots" error.
### 7.3 Restructure sidebar: move chart views to sidebar, remove placeholder items
- [ ] **Remove** from sidebar: "Cost Analysis" and "Export Data" items (no functionality behind them)
- [ ] **Remove** from sidebar: "Drug Selection", "Trust Selection", "Directory Selection", "Indications" items (filters moving to top bar — see 7.5)
- [ ] **Add** to sidebar: chart view buttons — "Icicle Chart" (active), "Sankey Diagram" (disabled), "Timeline" (disabled). These replace the tab row currently in chart_card.py.
- [ ] **Keep**: "Pathway Overview" as the top active item
- [ ] Update sidebar IDs and callback wiring. The chart type toggle pills (By Directory / By Indication) stay in the filter bar — they're data filters, not view selectors.
- [ ] Remove the tab row from `chart_card.py` since chart view selection moves to sidebar
- [x] **Remove** from sidebar: "Cost Analysis" and "Export Data" items (no functionality behind them)
- [x] **Remove** from sidebar: "Drug Selection", "Trust Selection", "Directory Selection", "Indications" items (filters moving to top bar — see 7.5)
- [x] **Add** to sidebar: chart view buttons — "Icicle Chart" (active), "Sankey Diagram" (disabled), "Timeline" (disabled). These replace the tab row currently in chart_card.py.
- [x] **Keep**: "Pathway Overview" as the top active item
- [x] Update sidebar IDs and callback wiring. The chart type toggle pills (By Directory / By Indication) stay in the filter bar — they're data filters, not view selectors.
- [x] Remove the tab row from `chart_card.py` since chart view selection moves to sidebar
- **Checkpoint**: Sidebar shows chart view options, no placeholder items, app runs without errors.
### 7.4 Replace dmc.Drawer with dmc.Modal for filter selection
@@ -335,8 +335,8 @@ All tasks marked `[x]` AND:
- [x] "Clear Filters" resets all selections
- [x] KPIs update dynamically (patients, drugs, cost)
- [x] No Reflex imports in `dash_app/`
- [ ] No duplicate component ID errors on first load
- [ ] Sidebar shows chart views (icicle/sankey/timeline), not filter triggers
- [x] No duplicate component ID errors on first load
- [x] Sidebar shows chart views (icicle/sankey/timeline), not filter triggers
- [ ] Filter bar has drug/trust/directorate trigger buttons with selection count badges
---
+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: