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.
This commit is contained in:
Andrew Charlwood
2026-02-06 21:51:56 +00:00
parent 95f5125889
commit 7e0c851063
5 changed files with 148 additions and 90 deletions
+7 -7
View File
@@ -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 ✓ - **Checkpoint**: Header shows fraction KPIs, KPI row is gone, header looks clean with design from 10.1 ✓
### 10.4 Global filter sub-header bar ### 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) - [x] 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 - [x] 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) - [x] 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) - [x] 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) - [x] 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 - [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 - **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 ### 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 - [ ] Create a Patient Pathways view component (or update chart_card.py) with only 2 tabs: Icicle, Sankey
+2
View File
@@ -3,6 +3,7 @@ from dash import Dash, html, dcc
import dash_mantine_components as dmc import dash_mantine_components as dmc
from dash_app.components.header import make_header 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.sidebar import make_sidebar
from dash_app.components.filter_bar import make_filter_bar from dash_app.components.filter_bar import make_filter_bar
from dash_app.components.chart_card import make_chart_card from dash_app.components.chart_card import make_chart_card
@@ -35,6 +36,7 @@ app.layout = dmc.MantineProvider(
# Page structure # Page structure
make_header(), make_header(),
make_sub_header(),
make_sidebar(), make_sidebar(),
make_modals(), make_modals(),
html.Main( html.Main(
+37 -2
View File
@@ -155,12 +155,47 @@ body {
font-size: 12px; color: var(--nhs-mid-grey); 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 Content ── */
.main { .main {
margin-left: var(--sidebar-w); margin-left: var(--sidebar-w);
margin-top: 56px; margin-top: 100px;
padding: 24px; padding: 24px;
min-height: calc(100vh - 56px); min-height: calc(100vh - 100px);
display: flex; flex-direction: column; gap: 20px; display: flex; flex-direction: column; gap: 20px;
} }
+19 -81
View File
@@ -1,88 +1,17 @@
"""Filter bar component — chart type toggle, date filters, and modal trigger buttons.""" """Filter bar component — drug, trust, and directorate filter buttons.
from dash import html, dcc
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(): def make_filter_bar():
"""Return a filter bar with chart type toggle, date dropdowns, and filter buttons. """Return a filter bar with drug, trust, and directorate filter buttons."""
Filter buttons open modals for drug, trust, and directorate selection.
Each button shows a selection count badge (updated via callbacks).
"""
return html.Section( return html.Section(
className="filter-bar", className="filter-bar",
**{"aria-label": "Filters"}, **{"aria-label": "Filters"},
children=[ 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 # Filter trigger buttons
html.Div( html.Div(
className="filter-bar__group", className="filter-bar__group",
@@ -90,7 +19,10 @@ def make_filter_bar():
html.Button( html.Button(
children=[ children=[
"Drugs", "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", id="open-drug-modal",
className="filter-btn", className="filter-btn",
@@ -99,7 +31,10 @@ def make_filter_bar():
html.Button( html.Button(
children=[ children=[
"Trusts", "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", id="open-trust-modal",
className="filter-btn", className="filter-btn",
@@ -108,7 +43,10 @@ def make_filter_bar():
html.Button( html.Button(
children=[ children=[
"Directorates", "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", id="open-directorate-modal",
className="filter-btn", className="filter-btn",
+83
View File
@@ -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",
),
],
),
],
)