feat: add dmc.Drawer drug browser with directorate cards and drug chips (Task 4.1)
This commit is contained in:
@@ -202,7 +202,7 @@ Drawer selection → update_drug_selection → app-state store → load_pathway_
|
||||
## Phase 4: Directorate Card Browser
|
||||
|
||||
### 4.1 dmc.Drawer layout
|
||||
- [ ] Create `dash_app/components/drawer.py` — `make_drawer()` function:
|
||||
- [x] Create `dash_app/components/drawer.py` — `make_drawer()` function:
|
||||
- `dmc.Drawer(id="drug-drawer", position="right", size="480px")`
|
||||
- **Top section**: "All Drugs" card — flat alphabetical list of all drug names from pathway_nodes level 3
|
||||
- Each drug as a `dmc.Chip` or clickable badge, ID pattern: `{"type": "drug-chip", "index": drug_name}`
|
||||
|
||||
@@ -8,6 +8,7 @@ from dash_app.components.kpi_row import make_kpi_row
|
||||
from dash_app.components.filter_bar import make_filter_bar
|
||||
from dash_app.components.chart_card import make_chart_card
|
||||
from dash_app.components.footer import make_footer
|
||||
from dash_app.components.drawer import make_drawer
|
||||
|
||||
app = Dash(
|
||||
__name__,
|
||||
@@ -32,6 +33,7 @@ app.layout = dmc.MantineProvider(
|
||||
# Page structure
|
||||
make_header(),
|
||||
make_sidebar(),
|
||||
make_drawer(),
|
||||
html.Main(
|
||||
className="main",
|
||||
children=[
|
||||
|
||||
@@ -254,6 +254,33 @@ body {
|
||||
.chart-tab:hover:not(.chart-tab--active) { color: var(--nhs-dark-grey); }
|
||||
.chart-tab:focus-visible { box-shadow: inset 0 0 0 3px var(--nhs-yellow); }
|
||||
|
||||
/* ── Drug Browser Drawer ── */
|
||||
.drawer-section {
|
||||
padding: 4px 0;
|
||||
}
|
||||
.drawer-section-title {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.drawer-chips-wrap {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.drawer-chips-wrap .mantine-Chip-label {
|
||||
font-family: 'Source Sans 3', Arial, sans-serif;
|
||||
}
|
||||
.drawer-drug-badge {
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.drawer-drug-badge:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
.drawer-directorate-accordion {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.drawer-clear-btn {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* ── Footer ── */
|
||||
.page-footer {
|
||||
background: var(--nhs-pale-grey);
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
"""
|
||||
Drug browser drawer component using Dash Mantine Components.
|
||||
|
||||
Provides a right-side drawer with:
|
||||
- "All Drugs" section: flat alphabetical list of all drugs from pathway_nodes
|
||||
- Directorate cards: grouped by PrimaryDirectorate from DimSearchTerm.csv,
|
||||
with Accordion items per Search_Term containing drug fragment chips
|
||||
- "Clear Filters" button at the bottom
|
||||
"""
|
||||
|
||||
from dash import html
|
||||
import dash_mantine_components as dmc
|
||||
|
||||
from dash_app.data.card_browser import build_directorate_tree, get_all_drugs
|
||||
|
||||
|
||||
def _make_drug_chips(drugs: list[str]) -> dmc.ChipGroup:
|
||||
"""Create a ChipGroup with multiple selection for the 'All Drugs' section."""
|
||||
return dmc.ChipGroup(
|
||||
id="all-drugs-chips",
|
||||
multiple=True,
|
||||
value=[],
|
||||
children=[
|
||||
dmc.Chip(drug, value=drug, size="xs")
|
||||
for drug in drugs
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def _make_directorate_card(directorate: str, indications: dict[str, list[str]]) -> dmc.AccordionItem:
|
||||
"""
|
||||
Create an AccordionItem for a single directorate.
|
||||
|
||||
Each indication becomes a panel with drug fragment badges inside.
|
||||
"""
|
||||
panels = []
|
||||
for search_term, fragments in indications.items():
|
||||
panels.append(
|
||||
dmc.AccordionItem(
|
||||
value=f"{directorate}|{search_term}",
|
||||
children=[
|
||||
dmc.AccordionControl(
|
||||
search_term.title(),
|
||||
className="drawer-indication",
|
||||
),
|
||||
dmc.AccordionPanel(
|
||||
dmc.Group(
|
||||
gap="xs",
|
||||
children=[
|
||||
dmc.Badge(
|
||||
frag,
|
||||
id={"type": "drug-fragment", "index": f"{directorate}|{frag}"},
|
||||
variant="light",
|
||||
size="sm",
|
||||
className="drawer-drug-badge",
|
||||
style={"cursor": "pointer"},
|
||||
)
|
||||
for frag in fragments
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
return dmc.AccordionItem(
|
||||
value=directorate,
|
||||
children=[
|
||||
dmc.AccordionControl(
|
||||
dmc.Group(
|
||||
gap="xs",
|
||||
children=[
|
||||
dmc.Text(directorate.title(), fw=600, size="sm"),
|
||||
dmc.Badge(
|
||||
str(len(indications)),
|
||||
size="xs",
|
||||
variant="light",
|
||||
color="gray",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
dmc.AccordionPanel(
|
||||
dmc.Accordion(
|
||||
variant="separated",
|
||||
children=panels,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def make_drawer():
|
||||
"""
|
||||
Build the drug browser drawer component.
|
||||
|
||||
Returns a dmc.Drawer that will be opened/closed via callbacks in Phase 4.2.
|
||||
"""
|
||||
drugs = get_all_drugs()
|
||||
directorate_tree = build_directorate_tree()
|
||||
|
||||
# All Drugs section
|
||||
all_drugs_section = html.Div(
|
||||
className="drawer-section",
|
||||
children=[
|
||||
dmc.Text("All Drugs", fw=700, size="sm", className="drawer-section-title"),
|
||||
dmc.Text(
|
||||
f"{len(drugs)} drugs from pathway data",
|
||||
size="xs",
|
||||
c="dimmed",
|
||||
),
|
||||
html.Div(
|
||||
className="drawer-chips-wrap",
|
||||
children=_make_drug_chips(drugs),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
# Directorate cards section
|
||||
directorate_items = [
|
||||
_make_directorate_card(directorate, indications)
|
||||
for directorate, indications in directorate_tree.items()
|
||||
]
|
||||
|
||||
directorate_section = html.Div(
|
||||
className="drawer-section",
|
||||
children=[
|
||||
dmc.Text("By Directorate", fw=700, size="sm", className="drawer-section-title"),
|
||||
dmc.Text(
|
||||
f"{len(directorate_tree)} directorates \u00b7 {sum(len(v) for v in directorate_tree.values())} indications",
|
||||
size="xs",
|
||||
c="dimmed",
|
||||
),
|
||||
dmc.Accordion(
|
||||
variant="separated",
|
||||
children=directorate_items,
|
||||
className="drawer-directorate-accordion",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
# Clear filters button
|
||||
clear_button = dmc.Button(
|
||||
"Clear All Filters",
|
||||
id="clear-drug-filters",
|
||||
variant="outline",
|
||||
color="red",
|
||||
fullWidth=True,
|
||||
className="drawer-clear-btn",
|
||||
)
|
||||
|
||||
return dmc.Drawer(
|
||||
id="drug-drawer",
|
||||
opened=False,
|
||||
position="right",
|
||||
size="480px",
|
||||
title=dmc.Text("Drug & Indication Browser", fw=700, size="lg"),
|
||||
children=[
|
||||
dmc.ScrollArea(
|
||||
h="calc(100vh - 140px)",
|
||||
children=dmc.Stack(
|
||||
gap="md",
|
||||
children=[
|
||||
all_drugs_section,
|
||||
dmc.Divider(),
|
||||
directorate_section,
|
||||
clear_button,
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
Reference in New Issue
Block a user