diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index 913598d..b107898 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -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 --- diff --git a/dash_app/assets/nhs.css b/dash_app/assets/nhs.css index 050c50b..d6c7e2c 100644 --- a/dash_app/assets/nhs.css +++ b/dash_app/assets/nhs.css @@ -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 { diff --git a/dash_app/callbacks/drawer.py b/dash_app/callbacks/drawer.py index 65e2a51..49f7b50 100644 --- a/dash_app/callbacks/drawer.py +++ b/dash_app/callbacks/drawer.py @@ -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"), diff --git a/dash_app/components/chart_card.py b/dash_app/components/chart_card.py index 067bbd2..fb3043b 100644 --- a/dash_app/components/chart_card.py +++ b/dash_app/components/chart_card.py @@ -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", diff --git a/dash_app/components/sidebar.py b/dash_app/components/sidebar.py index 42326ca..47d49fd 100644 --- a/dash_app/components/sidebar.py +++ b/dash_app/components/sidebar.py @@ -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_body}' @@ -20,15 +16,12 @@ def _svg_icon(svg_body): ) -# SVG icon bodies from 01_nhs_classic.html +# SVG icon bodies (Feather-style) _ICONS = { "pathway": '', - "drug": '', - "trust": '', - "directory": '', - "indication": '', - "cost": '', - "export": '', + "icicle": '', + "sankey": '', + "timeline": '', } @@ -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: