Files
HighCostDrugsDemo/dash_app/callbacks/modals.py
T
Andrew Charlwood f2c5b2645e refactor: replace dmc.Drawer with dmc.Modal for filter selection (Task 7.4 + 7.5)
- Created 3 separate modals: Drug Selection (lg), Trust Selection (sm),
  Directorate Browser (xl) with centered overlay
- Added filter trigger buttons to filter bar with count badges
- Added "Clear All" button in filter bar for global filter reset
- Per-modal clear buttons for drugs and trusts
- Preserved all existing selection logic (same component IDs)
- Deleted drawer.py component and callbacks (replaced by modals.py)
- Updated CSS: filter-btn styles, modal chip/badge styles
2026-02-06 15:42:48 +00:00

137 lines
4.7 KiB
Python

"""Callbacks for filter selection modals: open/close, fragment matching, clear, count badges."""
from dash import Input, Output, State, ctx, no_update, ALL
def register_modal_callbacks(app):
"""Register all modal-related callbacks."""
# ── Open modals from filter bar buttons ──
@app.callback(
Output("drug-modal", "opened"),
Input("open-drug-modal", "n_clicks"),
prevent_initial_call=True,
)
def open_drug_modal(_n):
return True
@app.callback(
Output("trust-modal", "opened"),
Input("open-trust-modal", "n_clicks"),
prevent_initial_call=True,
)
def open_trust_modal(_n):
return True
@app.callback(
Output("directorate-modal", "opened"),
Input("open-directorate-modal", "n_clicks"),
prevent_initial_call=True,
)
def open_directorate_modal(_n):
return True
# ── Fragment matching + clear ──
@app.callback(
Output("all-drugs-chips", "value"),
Output("trust-chips", "value"),
Input({"type": "drug-fragment", "index": ALL}, "n_clicks"),
Input("clear-drug-filters", "n_clicks"),
Input("clear-drug-selection", "n_clicks"),
Input("clear-trust-selection", "n_clicks"),
Input("clear-all-filters", "n_clicks"),
State("all-drugs-chips", "value"),
State("trust-chips", "value"),
State("reference-data", "data"),
prevent_initial_call=True,
)
def handle_selection_actions(
fragment_clicks, _clear_all_clicks, _clear_drug_clicks,
_clear_trust_clicks, _clear_global_clicks,
current_drugs, current_trusts, ref_data
):
"""Handle fragment clicks, per-modal clears, and global clear."""
triggered = ctx.triggered_id
# Global clear (filter bar) or directorate modal clear
if triggered in ("clear-drug-filters", "clear-all-filters"):
return [], []
# Drug modal clear
if triggered == "clear-drug-selection":
return [], no_update
# Trust modal clear
if triggered == "clear-trust-selection":
return no_update, []
# Fragment badge click
if isinstance(triggered, dict) and triggered.get("type") == "drug-fragment":
if not any(n for n in (fragment_clicks or []) if n):
return no_update, no_update
fragment_key = triggered["index"]
fragment = fragment_key.rsplit("|", 1)[-1] if "|" in fragment_key else fragment_key
all_drugs = (ref_data or {}).get("available_drugs", [])
matching_drugs = [
drug for drug in all_drugs
if fragment.upper() in drug.upper()
]
if not matching_drugs:
return no_update, no_update
current = set(current_drugs or [])
all_selected = all(d in current for d in matching_drugs)
if all_selected:
updated = current - set(matching_drugs)
else:
updated = current | set(matching_drugs)
return sorted(updated), no_update
return no_update, no_update
# ── Count badges in filter bar ──
@app.callback(
Output("drug-count-badge", "children"),
Output("drug-count-badge", "className"),
Output("trust-count-badge", "children"),
Output("trust-count-badge", "className"),
Output("drug-modal-count", "children"),
Output("trust-modal-count", "children"),
Input("all-drugs-chips", "value"),
Input("trust-chips", "value"),
State("reference-data", "data"),
)
def update_count_badges(selected_drugs, selected_trusts, ref_data):
"""Update selection count badges in filter bar and modal headers."""
drug_count = len(selected_drugs or [])
trust_count = len(selected_trusts or [])
all_drugs = (ref_data or {}).get("available_drugs", [])
all_trusts = (ref_data or {}).get("available_trusts", [])
total_drugs = len(all_drugs) if all_drugs else 42
total_trusts = len(all_trusts) if all_trusts else 7
hidden = "filter-btn__badge filter-btn__badge--hidden"
visible = "filter-btn__badge"
drug_badge_text = str(drug_count) if drug_count else ""
drug_badge_class = visible if drug_count else hidden
trust_badge_text = str(trust_count) if trust_count else ""
trust_badge_class = visible if trust_count else hidden
drug_modal_text = f"{drug_count} of {total_drugs} selected"
trust_modal_text = f"{trust_count} of {total_trusts} selected"
return (
drug_badge_text, drug_badge_class,
trust_badge_text, trust_badge_class,
drug_modal_text, trust_modal_text,
)