feat: add two-view architecture with sidebar navigation (Task 10.2)
- Add active_view and selected_comparison_directorate to app-state - Sidebar: rename to Patient Pathways + add Trust Comparison nav item - View container pattern: two view divs toggled by active_view - Navigation callback: sidebar clicks switch views + update active state - Trust Comparison placeholder landing page with tc-landing structure
This commit is contained in:
@@ -469,16 +469,16 @@ Additionally: KPI row removed, fraction KPIs moved to header, global filter sub-
|
|||||||
- **Checkpoint**: Design mockups/specifications ready for all 5 areas above
|
- **Checkpoint**: Design mockups/specifications ready for all 5 areas above
|
||||||
|
|
||||||
### 10.2 State management + sidebar restructure
|
### 10.2 State management + sidebar restructure
|
||||||
- [ ] Add `active_view` to `app-state`: `"patient-pathways"` (default) or `"trust-comparison"`
|
- [x] Add `active_view` to `app-state`: `"patient-pathways"` (default) or `"trust-comparison"`
|
||||||
- [ ] Add `selected_comparison_directorate` to `app-state`: `null` (landing page) or directorate name
|
- [x] Add `selected_comparison_directorate` to `app-state`: `null` (landing page) or directorate name
|
||||||
- [ ] Update `dash_app/components/sidebar.py`:
|
- [x] Update `dash_app/components/sidebar.py`:
|
||||||
- Rename "Pathway Overview" → "Patient Pathways"
|
- Rename "Pathway Overview" → "Patient Pathways"
|
||||||
- Add "Trust Comparison" nav item below it
|
- Add "Trust Comparison" nav item below it
|
||||||
- Active state tracks `active_view`
|
- Active state tracks `active_view`
|
||||||
- [ ] Add callback: sidebar clicks → update `active_view` in app-state
|
- [x] Add callback: sidebar clicks → update `active_view` in app-state
|
||||||
- [ ] Main content area switches between Patient Pathways view and Trust Comparison view based on `active_view`
|
- [x] Main content area switches between Patient Pathways view and Trust Comparison view based on `active_view`
|
||||||
- [ ] Date filter + chart type toggle remain in global sub-header (visible in both views)
|
- [x] Date filter + chart type toggle remain in global sub-header (visible in both views)
|
||||||
- **Checkpoint**: Sidebar switches between two views, active state highlights correctly, app starts without errors
|
- **Checkpoint**: Sidebar switches between two views, active state highlights correctly, app starts without errors ✓
|
||||||
|
|
||||||
### 10.3 Header redesign — remove KPI row, add fraction KPIs
|
### 10.3 Header redesign — remove KPI row, add fraction KPIs
|
||||||
- [ ] Remove `dash_app/components/kpi_row.py` (or gut it)
|
- [ ] Remove `dash_app/components/kpi_row.py` (or gut it)
|
||||||
|
|||||||
+51
-3
@@ -26,6 +26,8 @@ app.layout = dmc.MantineProvider(
|
|||||||
"selected_drugs": [],
|
"selected_drugs": [],
|
||||||
"selected_directorates": [],
|
"selected_directorates": [],
|
||||||
"selected_trusts": [],
|
"selected_trusts": [],
|
||||||
|
"active_view": "patient-pathways",
|
||||||
|
"selected_comparison_directorate": None,
|
||||||
}),
|
}),
|
||||||
dcc.Store(id="chart-data", storage_type="memory"),
|
dcc.Store(id="chart-data", storage_type="memory"),
|
||||||
dcc.Store(id="reference-data", storage_type="session"),
|
dcc.Store(id="reference-data", storage_type="session"),
|
||||||
@@ -39,9 +41,55 @@ app.layout = dmc.MantineProvider(
|
|||||||
html.Main(
|
html.Main(
|
||||||
className="main",
|
className="main",
|
||||||
children=[
|
children=[
|
||||||
make_kpi_row(),
|
# View container — switched by active_view in app-state
|
||||||
make_filter_bar(),
|
html.Div(
|
||||||
make_chart_card(),
|
id="view-container",
|
||||||
|
children=[
|
||||||
|
# Patient Pathways view (default, visible)
|
||||||
|
html.Div(
|
||||||
|
id="patient-pathways-view",
|
||||||
|
children=[
|
||||||
|
make_kpi_row(),
|
||||||
|
make_filter_bar(),
|
||||||
|
make_chart_card(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
# Trust Comparison view (hidden initially)
|
||||||
|
html.Div(
|
||||||
|
id="trust-comparison-view",
|
||||||
|
style={"display": "none"},
|
||||||
|
children=[
|
||||||
|
html.Div(
|
||||||
|
className="tc-landing",
|
||||||
|
id="trust-comparison-landing",
|
||||||
|
children=[
|
||||||
|
html.Div(
|
||||||
|
className="tc-landing__header",
|
||||||
|
children=[
|
||||||
|
html.H2(
|
||||||
|
"Trust Comparison",
|
||||||
|
className="tc-landing__title",
|
||||||
|
),
|
||||||
|
html.P(
|
||||||
|
"Select a directorate to compare drug usage across trusts.",
|
||||||
|
className="tc-landing__desc",
|
||||||
|
id="tc-landing-desc",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
html.Div(
|
||||||
|
className="tc-landing__grid",
|
||||||
|
id="tc-landing-grid",
|
||||||
|
children=[
|
||||||
|
# Populated by callback in later task
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
make_footer(),
|
make_footer(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ def register_callbacks(app):
|
|||||||
from dash_app.callbacks.chart import register_chart_callbacks
|
from dash_app.callbacks.chart import register_chart_callbacks
|
||||||
from dash_app.callbacks.kpi import register_kpi_callbacks
|
from dash_app.callbacks.kpi import register_kpi_callbacks
|
||||||
from dash_app.callbacks.modals import register_modal_callbacks
|
from dash_app.callbacks.modals import register_modal_callbacks
|
||||||
|
from dash_app.callbacks.navigation import register_navigation_callbacks
|
||||||
|
|
||||||
register_filter_callbacks(app)
|
register_filter_callbacks(app)
|
||||||
register_chart_callbacks(app)
|
register_chart_callbacks(app)
|
||||||
register_kpi_callbacks(app)
|
register_kpi_callbacks(app)
|
||||||
register_modal_callbacks(app)
|
register_modal_callbacks(app)
|
||||||
|
register_navigation_callbacks(app)
|
||||||
|
|||||||
@@ -73,13 +73,15 @@ def register_filter_callbacks(app):
|
|||||||
Input("filter-last-seen", "value"),
|
Input("filter-last-seen", "value"),
|
||||||
Input("all-drugs-chips", "value"),
|
Input("all-drugs-chips", "value"),
|
||||||
Input("trust-chips", "value"),
|
Input("trust-chips", "value"),
|
||||||
|
Input("nav-patient-pathways", "n_clicks"),
|
||||||
|
Input("nav-trust-comparison", "n_clicks"),
|
||||||
State("app-state", "data"),
|
State("app-state", "data"),
|
||||||
)
|
)
|
||||||
def update_app_state(
|
def update_app_state(
|
||||||
_dir_clicks, _ind_clicks, initiated, last_seen, selected_drugs,
|
_dir_clicks, _ind_clicks, initiated, last_seen, selected_drugs,
|
||||||
selected_trusts, current_state
|
selected_trusts, _nav_pp_clicks, _nav_tc_clicks, current_state
|
||||||
):
|
):
|
||||||
"""Update app-state when chart type toggle, date filters, drug chips, or trust chips change."""
|
"""Update app-state when chart type toggle, date filters, drug/trust chips, or sidebar nav change."""
|
||||||
if not current_state:
|
if not current_state:
|
||||||
current_state = {
|
current_state = {
|
||||||
"chart_type": "directory",
|
"chart_type": "directory",
|
||||||
@@ -89,6 +91,8 @@ def register_filter_callbacks(app):
|
|||||||
"selected_drugs": [],
|
"selected_drugs": [],
|
||||||
"selected_directorates": [],
|
"selected_directorates": [],
|
||||||
"selected_trusts": [],
|
"selected_trusts": [],
|
||||||
|
"active_view": "patient-pathways",
|
||||||
|
"selected_comparison_directorate": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
triggered_id = ctx.triggered_id
|
triggered_id = ctx.triggered_id
|
||||||
@@ -100,6 +104,13 @@ def register_filter_callbacks(app):
|
|||||||
elif triggered_id == "chart-type-indication":
|
elif triggered_id == "chart-type-indication":
|
||||||
chart_type = "indication"
|
chart_type = "indication"
|
||||||
|
|
||||||
|
# Determine active view from sidebar nav
|
||||||
|
active_view = current_state.get("active_view", "patient-pathways")
|
||||||
|
if triggered_id == "nav-patient-pathways":
|
||||||
|
active_view = "patient-pathways"
|
||||||
|
elif triggered_id == "nav-trust-comparison":
|
||||||
|
active_view = "trust-comparison"
|
||||||
|
|
||||||
# Compute date_filter_id from dropdown values
|
# Compute date_filter_id from dropdown values
|
||||||
date_filter_id = f"{initiated}_{last_seen}"
|
date_filter_id = f"{initiated}_{last_seen}"
|
||||||
|
|
||||||
@@ -112,12 +123,13 @@ def register_filter_callbacks(app):
|
|||||||
"date_filter_id": date_filter_id,
|
"date_filter_id": date_filter_id,
|
||||||
"selected_drugs": selected_drugs or [],
|
"selected_drugs": selected_drugs or [],
|
||||||
"selected_trusts": selected_trusts or [],
|
"selected_trusts": selected_trusts or [],
|
||||||
|
"active_view": active_view,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Toggle pill CSS classes
|
# Toggle pill CSS classes
|
||||||
base = "toggle-pill"
|
base = "toggle-pill"
|
||||||
active = f"{base} toggle-pill--active"
|
active_cls = f"{base} toggle-pill--active"
|
||||||
dir_class = active if chart_type == "directory" else base
|
dir_class = active_cls if chart_type == "directory" else base
|
||||||
ind_class = active if chart_type == "indication" else base
|
ind_class = active_cls if chart_type == "indication" else base
|
||||||
|
|
||||||
return updated_state, dir_class, ind_class
|
return updated_state, dir_class, ind_class
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"""Callbacks for view switching between Patient Pathways and Trust Comparison."""
|
||||||
|
from dash import Input, Output
|
||||||
|
|
||||||
|
|
||||||
|
def register_navigation_callbacks(app):
|
||||||
|
"""Register view switching callbacks."""
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output("patient-pathways-view", "style"),
|
||||||
|
Output("trust-comparison-view", "style"),
|
||||||
|
Output("nav-patient-pathways", "className"),
|
||||||
|
Output("nav-trust-comparison", "className"),
|
||||||
|
Input("app-state", "data"),
|
||||||
|
)
|
||||||
|
def switch_view(app_state):
|
||||||
|
"""Show/hide views and update sidebar active state based on active_view."""
|
||||||
|
if not app_state:
|
||||||
|
return {}, {"display": "none"}, "sidebar__item sidebar__item--active", "sidebar__item"
|
||||||
|
|
||||||
|
view = app_state.get("active_view", "patient-pathways")
|
||||||
|
show = {}
|
||||||
|
hide = {"display": "none"}
|
||||||
|
active_cls = "sidebar__item sidebar__item--active"
|
||||||
|
inactive_cls = "sidebar__item"
|
||||||
|
|
||||||
|
if view == "patient-pathways":
|
||||||
|
return show, hide, active_cls, inactive_cls
|
||||||
|
else:
|
||||||
|
return hide, show, inactive_cls, active_cls
|
||||||
@@ -19,6 +19,7 @@ def _svg_icon(svg_body):
|
|||||||
# SVG icon bodies (Feather-style)
|
# 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"/>',
|
||||||
|
"compare": '<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"/>',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -28,15 +29,20 @@ def make_sidebar():
|
|||||||
className="sidebar",
|
className="sidebar",
|
||||||
**{"aria-label": "Main navigation"},
|
**{"aria-label": "Main navigation"},
|
||||||
children=[
|
children=[
|
||||||
# Overview section
|
|
||||||
html.Div(
|
html.Div(
|
||||||
className="sidebar__section",
|
className="sidebar__section",
|
||||||
children=[
|
children=[
|
||||||
html.Div("Overview", className="sidebar__label"),
|
html.Div("Analysis", className="sidebar__label"),
|
||||||
_sidebar_item("Pathway Overview", "pathway", active=True),
|
_sidebar_item(
|
||||||
|
"Patient Pathways", "pathway",
|
||||||
|
active=True, item_id="nav-patient-pathways",
|
||||||
|
),
|
||||||
|
_sidebar_item(
|
||||||
|
"Trust Comparison", "compare",
|
||||||
|
active=False, item_id="nav-trust-comparison",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
# Footer
|
|
||||||
html.Div(
|
html.Div(
|
||||||
className="sidebar__footer",
|
className="sidebar__footer",
|
||||||
children=[
|
children=[
|
||||||
|
|||||||
Reference in New Issue
Block a user