From 10e6d2c58a80a76e194724f42a27731e639ff8d9 Mon Sep 17 00:00:00 2001 From: abhishekbhakat Date: Fri, 19 Dec 2025 21:12:02 +0530 Subject: [PATCH] feat: Display biomarkers by category in collapsible sections on the dashboard, including new UI styles and backend `category_id` in the API response. --- backend/src/handlers/biomarkers.rs | 2 + frontend/src/index.css | 34 +++++++++ frontend/src/pages/Dashboard.tsx | 113 ++++++++++++++++++++++++++++- 3 files changed, 145 insertions(+), 4 deletions(-) diff --git a/backend/src/handlers/biomarkers.rs b/backend/src/handlers/biomarkers.rs index ac7f5b1..f28ee4b 100644 --- a/backend/src/handlers/biomarkers.rs +++ b/backend/src/handlers/biomarkers.rs @@ -43,6 +43,7 @@ pub struct ReferenceRuleResponse { #[derive(Serialize)] pub struct BiomarkerListItem { pub id: i32, + pub category_id: i32, pub name: String, pub test_category: String, pub unit: String, @@ -62,6 +63,7 @@ pub async fn list_biomarkers( .into_iter() .map(|b| BiomarkerListItem { id: b.id, + category_id: b.category_id, name: b.name, test_category: b.test_category, unit: b.unit, diff --git a/frontend/src/index.css b/frontend/src/index.css index 23eb461..3341371 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -386,4 +386,38 @@ select.input { .btn-danger:hover { background-color: color-mix(in srgb, var(--indicator-critical) 10%, transparent); +} + +/* Collapsible header */ +.collapsible-header:hover { + background-color: var(--bg-secondary) !important; +} + +/* Biomarker chips */ +.biomarker-chip { + display: inline-flex; + align-items: center; + gap: var(--space-xs); + padding: var(--space-xs) var(--space-sm); + background-color: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: var(--radius-md); + font-size: 13px; + cursor: default; + transition: background-color 0.15s; +} + +.biomarker-chip:hover { + background-color: color-mix(in srgb, var(--accent) 10%, var(--bg-secondary)); + border-color: var(--accent); +} + +.biomarker-name { + font-weight: 500; + color: var(--text-primary); +} + +.biomarker-unit { + color: var(--text-secondary); + font-size: 11px; } \ No newline at end of file diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 0c36c42..4569e52 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -8,6 +8,21 @@ interface User { role: string } +interface Category { + id: number + name: string + description: string | null +} + +interface Biomarker { + id: number + category_id: number + name: string + test_category: string + unit: string + methodology: string | null +} + export function Dashboard() { const navigate = useNavigate() const [user, setUser] = useState(null) @@ -15,6 +30,9 @@ export function Dashboard() { const [theme, setTheme] = useState(() => document.documentElement.getAttribute('data-theme') || 'light' ) + const [categories, setCategories] = useState([]) + const [biomarkers, setBiomarkers] = useState([]) + const [expandedCategories, setExpandedCategories] = useState>(new Set()) useEffect(() => { // First get auth info, then fetch full user profile for name @@ -36,6 +54,15 @@ export function Dashboard() { }) .catch(() => navigate('/login')) .finally(() => setLoading(false)) + + // Fetch categories and biomarkers + Promise.all([ + fetch('/api/categories', { credentials: 'include' }).then(r => r.json()), + fetch('/api/biomarkers', { credentials: 'include' }).then(r => r.json()), + ]).then(([cats, bms]) => { + setCategories(cats) + setBiomarkers(bms) + }) }, [navigate]) const handleLogout = async () => { @@ -53,6 +80,22 @@ export function Dashboard() { setTheme(newTheme) } + const toggleCategory = (categoryId: number) => { + setExpandedCategories(prev => { + const next = new Set(prev) + if (next.has(categoryId)) { + next.delete(categoryId) + } else { + next.add(categoryId) + } + return next + }) + } + + const getBiomarkersForCategory = (categoryId: number) => { + return biomarkers.filter(b => b.category_id === categoryId) + } + if (loading) { return
Loading...
} @@ -86,8 +129,8 @@ export function Dashboard() { -
+ {/* User Welcome Card */}
@@ -102,6 +145,7 @@ export function Dashboard() {
+ {/* Admin Section */} {user.role === 'admin' && (

Admin

@@ -114,9 +158,70 @@ export function Dashboard() {
)} -

- Dashboard coming soon... -

+ {/* Biomarker Categories */} +

Biomarkers

+
+ {categories.map(category => { + const categoryBiomarkers = getBiomarkersForCategory(category.id) + const isExpanded = expandedCategories.has(category.id) + + return ( +
+ {/* Category Header - Clickable */} + + + {/* Biomarkers List - Collapsible */} + {isExpanded && ( +
+ {categoryBiomarkers.length === 0 ? ( +

+ No biomarkers in this category +

+ ) : ( +
+ {categoryBiomarkers.map(biomarker => ( +
+ {biomarker.name} + {biomarker.unit} +
+ ))} +
+ )} +
+ )} +
+ ) + })} +
)