From 7e0c851063dabcb77e59b62e9d8202253fd4f572 Mon Sep 17 00:00:00 2001 From: Andrew Charlwood Date: Fri, 6 Feb 2026 21:51:56 +0000 Subject: [PATCH] feat: add global filter sub-header bar (Task 10.4) Extract chart type toggle and date filter dropdowns from filter_bar.py into a new sub-header component. Sub-header is fixed-position below the main header, visible across both views. Filter bar now contains only drug/trust/directorate buttons for Patient Pathways view. --- IMPLEMENTATION_PLAN.md | 14 ++--- dash_app/app.py | 2 + dash_app/assets/nhs.css | 39 +++++++++++- dash_app/components/filter_bar.py | 100 ++++++------------------------ dash_app/components/sub_header.py | 83 +++++++++++++++++++++++++ 5 files changed, 148 insertions(+), 90 deletions(-) create mode 100644 dash_app/components/sub_header.py diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index 9bb6789..d044fdb 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -494,13 +494,13 @@ Additionally: KPI row removed, fraction KPIs moved to header, global filter sub- - **Checkpoint**: Header shows fraction KPIs, KPI row is gone, header looks clean with design from 10.1 ✓ ### 10.4 Global filter sub-header bar -- [ ] Extract date filter dropdowns + chart type toggle from `filter_bar.py` into a new sub-header component (or restyle existing filter_bar) -- [ ] Style as a prominent bar directly below the main header — visually distinct per design from 10.1 -- [ ] Remove drug/trust/directorate filter buttons from this bar (they move to Patient Pathways view only — see 10.7) -- [ ] Ensure sub-header is constant across both views (Patient Pathways and Trust Comparison) -- [ ] Date filter and chart type toggle changes update `app-state` globally (triggering updates in whichever view is active) -- [ ] Update CSS per design from 10.1 -- **Checkpoint**: Global sub-header renders below main header, date/chart-type controls work, visible in both views +- [x] Extract date filter dropdowns + chart type toggle from `filter_bar.py` into a new sub-header component (or restyle existing filter_bar) +- [x] Style as a prominent bar directly below the main header — visually distinct per design from 10.1 +- [x] Remove drug/trust/directorate filter buttons from this bar (they move to Patient Pathways view only — see 10.7) +- [x] Ensure sub-header is constant across both views (Patient Pathways and Trust Comparison) +- [x] Date filter and chart type toggle changes update `app-state` globally (triggering updates in whichever view is active) +- [x] Update CSS per design from 10.1 +- **Checkpoint**: Global sub-header renders below main header, date/chart-type controls work, visible in both views ✓ ### 10.5 Patient Pathways view — reduce to Icicle + Sankey - [ ] Create a Patient Pathways view component (or update chart_card.py) with only 2 tabs: Icicle, Sankey diff --git a/dash_app/app.py b/dash_app/app.py index 0ab7b2a..c37e8e5 100644 --- a/dash_app/app.py +++ b/dash_app/app.py @@ -3,6 +3,7 @@ from dash import Dash, html, dcc import dash_mantine_components as dmc from dash_app.components.header import make_header +from dash_app.components.sub_header import make_sub_header from dash_app.components.sidebar import make_sidebar from dash_app.components.filter_bar import make_filter_bar from dash_app.components.chart_card import make_chart_card @@ -35,6 +36,7 @@ app.layout = dmc.MantineProvider( # Page structure make_header(), + make_sub_header(), make_sidebar(), make_modals(), html.Main( diff --git a/dash_app/assets/nhs.css b/dash_app/assets/nhs.css index fd16936..f3de3be 100644 --- a/dash_app/assets/nhs.css +++ b/dash_app/assets/nhs.css @@ -155,12 +155,47 @@ body { font-size: 12px; color: var(--nhs-mid-grey); } +/* ── Global Filter Sub-Header ── */ +.sub-header { + position: fixed; + top: 56px; + left: var(--sidebar-w); + right: 0; + z-index: 150; + height: 44px; + background: #E8F0FE; + border-bottom: 1px solid #C5D4E8; + display: flex; + align-items: center; + padding: 0 24px; + gap: 16px; +} +.sub-header__group { + display: flex; + align-items: center; + gap: 8px; +} +.sub-header__label { + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--nhs-dark-blue); + white-space: nowrap; + opacity: 0.6; +} +.sub-header__divider { + width: 1px; + height: 24px; + background: rgba(0, 48, 135, 0.15); +} + /* ── Main Content ── */ .main { margin-left: var(--sidebar-w); - margin-top: 56px; + margin-top: 100px; padding: 24px; - min-height: calc(100vh - 56px); + min-height: calc(100vh - 100px); display: flex; flex-direction: column; gap: 20px; } diff --git a/dash_app/components/filter_bar.py b/dash_app/components/filter_bar.py index 05794d1..b3b2faf 100644 --- a/dash_app/components/filter_bar.py +++ b/dash_app/components/filter_bar.py @@ -1,88 +1,17 @@ -"""Filter bar component — chart type toggle, date filters, and modal trigger buttons.""" -from dash import html, dcc +"""Filter bar component — drug, trust, and directorate filter buttons. + +View-specific controls for Patient Pathways. Global controls (chart type +toggle, date filters) live in sub_header.py. +""" +from dash import html def make_filter_bar(): - """Return a filter bar with chart type toggle, date dropdowns, and filter buttons. - - Filter buttons open modals for drug, trust, and directorate selection. - Each button shows a selection count badge (updated via callbacks). - """ + """Return a filter bar with drug, trust, and directorate filter buttons.""" return html.Section( className="filter-bar", **{"aria-label": "Filters"}, children=[ - # Chart type toggle - html.Div( - className="filter-bar__group", - children=[ - html.Span("View", className="filter-bar__label"), - html.Div( - className="toggle-pills", - role="radiogroup", - **{"aria-label": "Chart view type"}, - children=[ - html.Button( - "By Directory", - id="chart-type-directory", - className="toggle-pill toggle-pill--active", - role="radio", - n_clicks=0, - **{"aria-checked": "true"}, - ), - html.Button( - "By Indication", - id="chart-type-indication", - className="toggle-pill", - role="radio", - n_clicks=0, - **{"aria-checked": "false"}, - ), - ], - ), - ], - ), - # Divider - html.Div(className="filter-bar__divider"), - # Initiated filter - html.Div( - className="filter-bar__group", - children=[ - html.Span("Initiated", className="filter-bar__label"), - dcc.Dropdown( - id="filter-initiated", - options=[ - {"label": "All years", "value": "all"}, - {"label": "Last 2 years", "value": "2yr"}, - {"label": "Last 1 year", "value": "1yr"}, - ], - value="all", - clearable=False, - searchable=False, - className="filter-dropdown", - ), - ], - ), - # Last seen filter - html.Div( - className="filter-bar__group", - children=[ - html.Span("Last seen", className="filter-bar__label"), - dcc.Dropdown( - id="filter-last-seen", - options=[ - {"label": "Last 6 months", "value": "6mo"}, - {"label": "Last 12 months", "value": "12mo"}, - ], - value="6mo", - clearable=False, - searchable=False, - className="filter-dropdown", - ), - ], - ), - # Divider before filter buttons - html.Div(className="filter-bar__divider"), # Filter trigger buttons html.Div( className="filter-bar__group", @@ -90,7 +19,10 @@ def make_filter_bar(): html.Button( children=[ "Drugs", - html.Span(id="drug-count-badge", className="filter-btn__badge filter-btn__badge--hidden"), + html.Span( + id="drug-count-badge", + className="filter-btn__badge filter-btn__badge--hidden", + ), ], id="open-drug-modal", className="filter-btn", @@ -99,7 +31,10 @@ def make_filter_bar(): html.Button( children=[ "Trusts", - html.Span(id="trust-count-badge", className="filter-btn__badge filter-btn__badge--hidden"), + html.Span( + id="trust-count-badge", + className="filter-btn__badge filter-btn__badge--hidden", + ), ], id="open-trust-modal", className="filter-btn", @@ -108,7 +43,10 @@ def make_filter_bar(): html.Button( children=[ "Directorates", - html.Span(id="directorate-count-badge", className="filter-btn__badge filter-btn__badge--hidden"), + html.Span( + id="directorate-count-badge", + className="filter-btn__badge filter-btn__badge--hidden", + ), ], id="open-directorate-modal", className="filter-btn", diff --git a/dash_app/components/sub_header.py b/dash_app/components/sub_header.py new file mode 100644 index 0000000..459b89f --- /dev/null +++ b/dash_app/components/sub_header.py @@ -0,0 +1,83 @@ +"""Global filter sub-header — chart type toggle + date filter dropdowns. + +Fixed bar below the main header, constant across both views. +""" +from dash import html, dcc + + +def make_sub_header(): + """Return the global filter sub-header bar.""" + return html.Div( + className="sub-header", + children=[ + # Chart type toggle + html.Div( + className="sub-header__group", + children=[ + html.Span("View", className="sub-header__label"), + html.Div( + className="toggle-pills", + role="radiogroup", + **{"aria-label": "Chart view type"}, + children=[ + html.Button( + "By Directory", + id="chart-type-directory", + className="toggle-pill toggle-pill--active", + role="radio", + n_clicks=0, + **{"aria-checked": "true"}, + ), + html.Button( + "By Indication", + id="chart-type-indication", + className="toggle-pill", + role="radio", + n_clicks=0, + **{"aria-checked": "false"}, + ), + ], + ), + ], + ), + # Divider + html.Div(className="sub-header__divider"), + # Initiated filter + html.Div( + className="sub-header__group", + children=[ + html.Span("Initiated", className="sub-header__label"), + dcc.Dropdown( + id="filter-initiated", + options=[ + {"label": "All years", "value": "all"}, + {"label": "Last 2 years", "value": "2yr"}, + {"label": "Last 1 year", "value": "1yr"}, + ], + value="all", + clearable=False, + searchable=False, + className="filter-dropdown", + ), + ], + ), + # Last seen filter + html.Div( + className="sub-header__group", + children=[ + html.Span("Last seen", className="sub-header__label"), + dcc.Dropdown( + id="filter-last-seen", + options=[ + {"label": "Last 6 months", "value": "6mo"}, + {"label": "Last 12 months", "value": "12mo"}, + ], + value="6mo", + clearable=False, + searchable=False, + className="filter-dropdown", + ), + ], + ), + ], + )