feat: Display biomarkers by category in collapsible sections on the dashboard, including new UI styles and backend category_id in the API response.

This commit is contained in:
2025-12-19 21:12:02 +05:30
parent d08f63f50c
commit 10e6d2c58a
3 changed files with 145 additions and 4 deletions

View File

@@ -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<User | null>(null)
@@ -15,6 +30,9 @@ export function Dashboard() {
const [theme, setTheme] = useState(() =>
document.documentElement.getAttribute('data-theme') || 'light'
)
const [categories, setCategories] = useState<Category[]>([])
const [biomarkers, setBiomarkers] = useState<Biomarker[]>([])
const [expandedCategories, setExpandedCategories] = useState<Set<number>>(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 <div style={{ padding: 'var(--space-lg)' }}>Loading...</div>
}
@@ -86,8 +129,8 @@ export function Dashboard() {
</div>
</header>
<main>
{/* User Welcome Card */}
<div className="card" style={{ marginBottom: 'var(--space-lg)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div>
@@ -102,6 +145,7 @@ export function Dashboard() {
</div>
</div>
{/* Admin Section */}
{user.role === 'admin' && (
<div className="card" style={{ marginBottom: 'var(--space-lg)' }}>
<h3 style={{ marginBottom: 'var(--space-sm)' }}>Admin</h3>
@@ -114,9 +158,70 @@ export function Dashboard() {
</div>
)}
<p className="text-secondary text-sm">
Dashboard coming soon...
</p>
{/* Biomarker Categories */}
<h2 style={{ marginBottom: 'var(--space-md)' }}>Biomarkers</h2>
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-sm)' }}>
{categories.map(category => {
const categoryBiomarkers = getBiomarkersForCategory(category.id)
const isExpanded = expandedCategories.has(category.id)
return (
<div key={category.id} className="card" style={{ padding: 0 }}>
{/* Category Header - Clickable */}
<button
className="collapsible-header"
onClick={() => toggleCategory(category.id)}
style={{
width: '100%',
padding: 'var(--space-md)',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
background: 'none',
border: 'none',
cursor: 'pointer',
textAlign: 'left',
color: 'var(--text-primary)',
}}
>
<div>
<span style={{ fontSize: '16px', fontWeight: 600 }}>{category.name}</span>
<span className="text-secondary text-sm" style={{ marginLeft: 'var(--space-sm)' }}>
({categoryBiomarkers.length} biomarkers)
</span>
</div>
<span style={{ fontSize: '18px', transition: 'transform 0.2s', transform: isExpanded ? 'rotate(180deg)' : 'rotate(0)' }}>
</span>
</button>
{/* Biomarkers List - Collapsible */}
{isExpanded && (
<div style={{ borderTop: '1px solid var(--border)', padding: 'var(--space-sm)' }}>
{categoryBiomarkers.length === 0 ? (
<p className="text-secondary text-sm" style={{ padding: 'var(--space-sm)' }}>
No biomarkers in this category
</p>
) : (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--space-xs)' }}>
{categoryBiomarkers.map(biomarker => (
<div
key={biomarker.id}
className="biomarker-chip"
title={`${biomarker.name} (${biomarker.unit})`}
>
<span className="biomarker-name">{biomarker.name}</span>
<span className="biomarker-unit">{biomarker.unit}</span>
</div>
))}
</div>
)}
</div>
)}
</div>
)
})}
</div>
</main>
</div>
)