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. - **Checkpoint**: Drug selection filters chart without "multiple implied roots" error.
### 7.3 Restructure sidebar: move chart views to sidebar, remove placeholder items ### 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) - [x] **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) - [x] **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. - [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.
- [ ] **Keep**: "Pathway Overview" as the top active item - [x] **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. - [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.
- [ ] Remove the tab row from `chart_card.py` since chart view selection moves to sidebar - [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. - **Checkpoint**: Sidebar shows chart view options, no placeholder items, app runs without errors.
### 7.4 Replace dmc.Drawer with dmc.Modal for filter selection ### 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] "Clear Filters" resets all selections
- [x] KPIs update dynamically (patients, drugs, cost) - [x] KPIs update dynamically (patients, drugs, cost)
- [x] No Reflex imports in `dash_app/` - [x] No Reflex imports in `dash_app/`
- [ ] No duplicate component ID errors on first load - [x] No duplicate component ID errors on first load
- [ ] Sidebar shows chart views (icicle/sankey/timeline), not filter triggers - [x] Sidebar shows chart views (icicle/sankey/timeline), not filter triggers
- [ ] Filter bar has drug/trust/directorate trigger buttons with selection count badges - [ ] Filter bar has drug/trust/directorate trigger buttons with selection count badges
--- ---
+5
View File
@@ -100,6 +100,11 @@ body {
color: var(--nhs-blue); color: var(--nhs-blue);
font-weight: 600; 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__item svg { width: 18px; height: 18px; flex-shrink: 0; }
.sidebar__icon { width: 18px; height: 18px; flex-shrink: 0; } .sidebar__icon { width: 18px; height: 18px; flex-shrink: 0; }
.sidebar__footer { .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 from dash import Input, Output, State, ctx, no_update, ALL
def register_drawer_callbacks(app): def register_drawer_callbacks(app):
"""Register drawer-related callbacks.""" """Register drug/trust selection callbacks (fragment matching + clear)."""
@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
@app.callback( @app.callback(
Output("all-drugs-chips", "value"), 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 from dash import html, dcc
@@ -7,8 +7,8 @@ def make_chart_card():
Contains: Contains:
- Header with title and dynamic subtitle (hierarchy label) - 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 - dcc.Loading wrapper around dcc.Graph for loading spinner
Chart view selection (icicle/sankey/timeline) is in the sidebar.
""" """
return html.Section( return html.Section(
className="chart-card", 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 # Chart area with loading spinner
dcc.Loading( dcc.Loading(
type="circle", type="circle",
+15 -29
View File
@@ -5,11 +5,7 @@ from dash import html
def _svg_icon(svg_body): def _svg_icon(svg_body):
"""Wrap an SVG body string into an html.Img using a data URI. """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.
"""
svg = ( svg = (
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" ' 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>' 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 = { _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"/>', "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"/>', "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"/>',
"trust": '<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/>', "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"/>',
"directory": '<path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/>', "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"/>',
"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"/>',
} }
@@ -38,31 +31,22 @@ def make_sidebar():
className="sidebar", className="sidebar",
**{"aria-label": "Main navigation"}, **{"aria-label": "Main navigation"},
children=[ children=[
# Analysis section # Overview section
html.Div( html.Div(
className="sidebar__section", className="sidebar__section",
children=[ children=[
html.Div("Analysis", className="sidebar__label"), html.Div("Overview", className="sidebar__label"),
_sidebar_item("Pathway Overview", "pathway", active=True), _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( html.Div(
className="sidebar__section", className="sidebar__section",
children=[ children=[
html.Div("Reports", className="sidebar__label"), html.Div("Chart Views", className="sidebar__label"),
_sidebar_item("Cost Analysis", "cost"), _sidebar_item("Icicle Chart", "icicle", active=True),
_sidebar_item("Export Data", "export"), _sidebar_item("Sankey Diagram", "sankey", disabled=True),
_sidebar_item("Timeline", "timeline", disabled=True),
], ],
), ),
# Footer # 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.""" """Create a single sidebar navigation item."""
class_name = "sidebar__item" class_name = "sidebar__item"
if active: if active:
class_name += " sidebar__item--active" class_name += " sidebar__item--active"
if disabled:
class_name += " sidebar__item--disabled"
props = {"className": class_name} props = {"className": class_name}
if item_id: if item_id: