feat: add KPI row, filter bar, and chart card components (Task 2.2)

This commit is contained in:
Andrew Charlwood
2026-02-06 13:20:42 +00:00
parent 5ebe75ad13
commit 307563bb31
7 changed files with 296 additions and 8 deletions
+74
View File
@@ -0,0 +1,74 @@
"""Chart card component — header, tabs, and dcc.Graph for icicle chart."""
from dash import html, dcc
def make_chart_card():
"""Return a chart card matching 01_nhs_classic.html structure.
Contains:
- Header with title and dynamic subtitle (hierarchy label)
- Tab row (Icicle active, Sankey and Timeline as disabled placeholders)
- dcc.Graph for the Plotly icicle figure
"""
return html.Section(
className="chart-card",
**{"aria-label": "Patient pathway chart"},
children=[
# Card header
html.Div(
className="chart-card__header",
children=[
html.Div(
children=[
html.Div(
"Patient Pathway Visualization",
className="chart-card__title",
),
html.Div(
"Trust \u2192 Directorate \u2192 Drug \u2192 Patient Pathway",
className="chart-card__subtitle",
id="chart-subtitle",
),
]
),
],
),
# 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
dcc.Graph(
id="pathway-chart",
style={"minHeight": "500px", "flex": "1"},
config={
"displayModeBar": True,
"displaylogo": False,
"modeBarButtonsToRemove": ["lasso2d", "select2d"],
},
),
],
)
+89
View File
@@ -0,0 +1,89 @@
"""Filter bar component — chart type toggle + date filter dropdowns."""
from dash import html, dcc
def make_filter_bar():
"""Return a filter bar matching 01_nhs_classic.html structure.
Contains:
- Chart type toggle pills (By Directory / By Indication)
- Initiated dropdown (All years, Last 2 years, Last 1 year)
- Last seen dropdown (Last 6 months, Last 12 months)
Drug/directorate filters are in the drawer (Phase 4), not here.
"""
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",
),
],
),
],
)
+46
View File
@@ -0,0 +1,46 @@
"""KPI row component — 4 metric cards with callback-updatable values."""
from dash import html
def make_kpi_row():
"""Return a section with 4 KPI cards matching 01_nhs_classic.html structure."""
return html.Section(
className="kpi-row",
**{"aria-label": "Key performance indicators"},
children=[
_kpi_card("Unique Patients", "kpi-patients", "", "across all trusts"),
_kpi_card("Drug Types", "kpi-drugs", "", "high-cost drugs tracked"),
_kpi_card("Total Cost", "kpi-cost", "", "current period spend"),
_kpi_card(
"Indication Match",
"kpi-match",
"",
"GP diagnosis confirmed",
modifier="kpi-card--green",
),
],
)
def _kpi_card(label, value_id, default_value, sub_text, modifier=None):
"""Build a single KPI card.
Args:
label: uppercase label text
value_id: HTML id for the value span (for callback Output)
default_value: initial display value before callbacks fire
sub_text: description below the value
modifier: optional CSS modifier class (e.g. 'kpi-card--green')
"""
card_class = "kpi-card"
if modifier:
card_class += f" {modifier}"
return html.Div(
className=card_class,
children=[
html.Div(label, className="kpi-card__label"),
html.Div(default_value, className="kpi-card__value", id=value_id),
html.Div(sub_text, className="kpi-card__sub"),
],
)