diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index 2ed1741..0cb88f4 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -163,12 +163,12 @@ Drawer selection → update_drug_selection → app-state store → load_pathway_ ## Phase 3: Core Callbacks ### 3.1 Reference data loading + filter state management -- [ ] Create `dash_app/callbacks/filters.py`: +- [x] Create `dash_app/callbacks/filters.py`: - `load_reference_data` callback: fires on page load, calls `queries.load_initial_data()`, populates `reference-data` store + header indicators - `update_app_state` callback: fires when chart-type toggle or date dropdowns change, computes `date_filter_id` (e.g., `"all_6mo"`), updates `app-state` store - Chart type toggle: use `callback_context` to determine which button was clicked, set active class via `className` -- [ ] Create `dash_app/callbacks/__init__.py` with `register_callbacks(app)` that imports and registers all callback modules -- [ ] Wire `register_callbacks(app)` in `app.py` +- [x] Create `dash_app/callbacks/__init__.py` with `register_callbacks(app)` that imports and registers all callback modules +- [x] Wire `register_callbacks(app)` in `app.py` - **Checkpoint**: Page loads reference data, filter dropdowns update app-state store (verify via browser dev tools → dcc.Store) ### 3.2 Pathway data loading callback diff --git a/dash_app/app.py b/dash_app/app.py index 9eaaca3..9f32a3a 100644 --- a/dash_app/app.py +++ b/dash_app/app.py @@ -27,6 +27,7 @@ app.layout = dmc.MantineProvider( }), dcc.Store(id="chart-data", storage_type="memory"), dcc.Store(id="reference-data", storage_type="session"), + dcc.Location(id="url", refresh=False), # Page structure make_header(), @@ -43,4 +44,8 @@ app.layout = dmc.MantineProvider( ], ) +from dash_app.callbacks import register_callbacks + +register_callbacks(app) + server = app.server diff --git a/dash_app/callbacks/__init__.py b/dash_app/callbacks/__init__.py index e69de29..e3255c5 100644 --- a/dash_app/callbacks/__init__.py +++ b/dash_app/callbacks/__init__.py @@ -0,0 +1,8 @@ +"""Callback registration — imports all callback modules and wires them to the app.""" + + +def register_callbacks(app): + """Register all Dash callbacks with the app instance.""" + from dash_app.callbacks.filters import register_filter_callbacks + + register_filter_callbacks(app) diff --git a/dash_app/callbacks/filters.py b/dash_app/callbacks/filters.py new file mode 100644 index 0000000..0f89199 --- /dev/null +++ b/dash_app/callbacks/filters.py @@ -0,0 +1,77 @@ +"""Callbacks for reference data loading and filter state management.""" +from dash import Input, Output, State, callback, ctx, no_update + + +def register_filter_callbacks(app): + """Register reference data loading and filter state callbacks.""" + + @app.callback( + Output("reference-data", "data"), + Output("header-record-count", "children"), + Output("header-last-updated", "children"), + Input("url", "pathname"), # fires once on page load + ) + def load_reference_data(_pathname): + """Load reference data once on page load.""" + from dash_app.data.queries import load_initial_data + + ref = load_initial_data() + total = ref.get("total_records", 0) + record_text = f"{total:,} records" if total else "Data loaded" + last_updated = ref.get("last_updated", "") + updated_text = last_updated[:10] if last_updated else "Unknown" + + return ref, record_text, updated_text + + @app.callback( + Output("app-state", "data"), + Output("chart-type-directory", "className"), + Output("chart-type-indication", "className"), + Input("chart-type-directory", "n_clicks"), + Input("chart-type-indication", "n_clicks"), + Input("filter-initiated", "value"), + Input("filter-last-seen", "value"), + State("app-state", "data"), + ) + def update_app_state( + _dir_clicks, _ind_clicks, initiated, last_seen, current_state + ): + """Update app-state when chart type toggle or date filters change.""" + if not current_state: + current_state = { + "chart_type": "directory", + "initiated": "all", + "last_seen": "6mo", + "date_filter_id": "all_6mo", + "selected_drugs": [], + "selected_directorates": [], + } + + triggered_id = ctx.triggered_id + + # Determine chart type from toggle pills + chart_type = current_state.get("chart_type", "directory") + if triggered_id == "chart-type-directory": + chart_type = "directory" + elif triggered_id == "chart-type-indication": + chart_type = "indication" + + # Compute date_filter_id from dropdown values + date_filter_id = f"{initiated}_{last_seen}" + + # Build updated state + updated_state = { + **current_state, + "chart_type": chart_type, + "initiated": initiated, + "last_seen": last_seen, + "date_filter_id": date_filter_id, + } + + # Toggle pill CSS classes + base = "toggle-pill" + active = f"{base} toggle-pill--active" + dir_class = active if chart_type == "directory" else base + ind_class = active if chart_type == "indication" else base + + return updated_state, dir_class, ind_class