Phase 5 cleanup
This commit is contained in:
parent
5536acd67d
commit
76a516a417
136 changed files with 6322 additions and 1985 deletions
|
|
@ -1,33 +1,259 @@
|
|||
import Link from 'next/link';
|
||||
'use client';
|
||||
|
||||
const navItems = [
|
||||
{ label: 'Dashboard', href: '/dashboard' },
|
||||
{ label: 'Medicines', href: '/medicines' },
|
||||
{ label: 'Settings', href: '/settings' },
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Icon } from '@/components/ui/Icon';
|
||||
import type { IconName } from '@/components/ui/Icon';
|
||||
import { Avatar } from '@/components/ui/Avatar';
|
||||
import { useApi } from '@/lib/useApi';
|
||||
|
||||
interface NavItem {
|
||||
id: string;
|
||||
label: string;
|
||||
href: string;
|
||||
icon: IconName;
|
||||
section?: string;
|
||||
badge?: number;
|
||||
}
|
||||
|
||||
const NAV: NavItem[] = [
|
||||
{ id: 'dashboard', label: 'Dashboard', href: '/dashboard', icon: 'dashboard' },
|
||||
{
|
||||
id: 'cabinet',
|
||||
label: 'Cabinet',
|
||||
href: '/medicines/cabinet',
|
||||
icon: 'cabinet',
|
||||
section: 'Medicines',
|
||||
},
|
||||
{
|
||||
id: 'schedule',
|
||||
label: 'Schedule & Log',
|
||||
href: '/medicines/schedule',
|
||||
icon: 'clock',
|
||||
section: 'Medicines',
|
||||
},
|
||||
{
|
||||
id: 'regimens',
|
||||
label: 'Regimens',
|
||||
href: '/medicines/regimens',
|
||||
icon: 'list',
|
||||
section: 'Medicines',
|
||||
},
|
||||
{
|
||||
id: 'organizer',
|
||||
label: 'Pill Organizer',
|
||||
href: '/medicines/organizer',
|
||||
icon: 'calendar',
|
||||
section: 'Medicines',
|
||||
},
|
||||
{
|
||||
id: 'library',
|
||||
label: 'Library',
|
||||
href: '/medicines/library',
|
||||
icon: 'pill',
|
||||
section: 'Medicines',
|
||||
},
|
||||
{
|
||||
id: 'refills',
|
||||
label: 'Shopping list',
|
||||
href: '/refills',
|
||||
icon: 'refresh',
|
||||
section: 'Medicines',
|
||||
},
|
||||
{ id: 'purchases', label: 'Purchases', href: '/purchases', icon: 'truck', section: 'Medicines' },
|
||||
{ id: 'prices', label: 'Prices', href: '/medicine-prices', icon: 'tag', section: 'Medicines' },
|
||||
{ id: 'stores', label: 'Stores', href: '/stores', icon: 'store', section: 'Medicines' },
|
||||
{
|
||||
id: 'activity',
|
||||
label: 'Activity & Spend',
|
||||
href: '/medicines/activity',
|
||||
icon: 'trend',
|
||||
section: 'Medicines',
|
||||
},
|
||||
{ id: 'settings', label: 'Settings', href: '/settings', icon: 'settings' },
|
||||
];
|
||||
|
||||
function groupNav(items: NavItem[]): [string, NavItem[]][] {
|
||||
const map = new Map<string, NavItem[]>();
|
||||
for (const item of items) {
|
||||
const key = item.section ?? '__root__';
|
||||
if (!map.has(key)) map.set(key, []);
|
||||
map.get(key)!.push(item);
|
||||
}
|
||||
return Array.from(map.entries());
|
||||
}
|
||||
|
||||
export function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
const { profile } = useApi();
|
||||
const displayName = profile?.displayName ?? 'User';
|
||||
const groups = groupNav(NAV);
|
||||
|
||||
return (
|
||||
<aside className="flex w-64 flex-col border-r bg-white">
|
||||
<div className="flex h-16 items-center border-b px-6">
|
||||
<Link href="/dashboard" className="text-xl font-bold text-primary-700">
|
||||
MeshiTrack
|
||||
</Link>
|
||||
<aside
|
||||
style={{
|
||||
background: 'var(--bg-elev)',
|
||||
borderRight: '1px solid var(--border)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
height: '100vh',
|
||||
width: 248,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{/* Brand */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 10,
|
||||
alignItems: 'center',
|
||||
padding: '18px 18px 14px',
|
||||
borderBottom: '1px solid var(--border)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 34,
|
||||
height: 34,
|
||||
borderRadius: 10,
|
||||
display: 'grid',
|
||||
placeItems: 'center',
|
||||
background: 'var(--brand-soft)',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<rect x="3" y="3" width="18" height="18" rx="5" fill="var(--brand)" />
|
||||
<path
|
||||
d="M7 15l3-6 2 4 2-3 3 5"
|
||||
stroke="var(--brand-ink)"
|
||||
strokeWidth="1.8"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: 'var(--font-display)',
|
||||
fontSize: 17,
|
||||
fontWeight: 600,
|
||||
letterSpacing: '-0.02em',
|
||||
color: 'var(--ink-strong)',
|
||||
}}
|
||||
>
|
||||
MeshiTrack
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav className="flex-1 overflow-y-auto p-4">
|
||||
<ul className="space-y-1">
|
||||
{navItems.map((item) => (
|
||||
<li key={item.href}>
|
||||
<Link
|
||||
href={item.href}
|
||||
className="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 transition-colors"
|
||||
|
||||
{/* Navigation */}
|
||||
<nav
|
||||
style={{ flex: 1, overflowY: 'auto', padding: '10px 10px 16px' }}
|
||||
aria-label="Main navigation"
|
||||
>
|
||||
{groups.map(([section, items]) => (
|
||||
<div key={section} style={{ marginBottom: 14 }}>
|
||||
{section !== '__root__' && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: 10,
|
||||
fontWeight: 600,
|
||||
letterSpacing: '0.08em',
|
||||
textTransform: 'uppercase',
|
||||
color: 'var(--ink-faint)',
|
||||
padding: '10px 10px 6px',
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{section}
|
||||
</div>
|
||||
)}
|
||||
{items.map((item) => {
|
||||
const isActive = pathname === item.href || pathname.startsWith(item.href + '/');
|
||||
return (
|
||||
<Link
|
||||
key={item.id}
|
||||
href={item.href}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
width: '100%',
|
||||
padding: '7px 10px',
|
||||
borderRadius: 'var(--r-sm)',
|
||||
fontSize: 13,
|
||||
color: isActive ? 'var(--brand-soft-ink)' : 'var(--ink-muted)',
|
||||
background: isActive ? 'var(--brand-soft)' : 'transparent',
|
||||
fontWeight: isActive ? 500 : 400,
|
||||
transition: 'background 0.1s, color 0.1s',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name={item.icon}
|
||||
size={16}
|
||||
style={{ opacity: isActive ? 1 : 0.8, flexShrink: 0 }}
|
||||
/>
|
||||
<span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{item.label}
|
||||
</span>
|
||||
{item.badge != null && (
|
||||
<span
|
||||
style={{
|
||||
background: 'var(--danger-soft)',
|
||||
color: 'var(--danger)',
|
||||
fontSize: 10,
|
||||
fontWeight: 600,
|
||||
padding: '1px 6px',
|
||||
borderRadius: 8,
|
||||
minWidth: 18,
|
||||
textAlign: 'center',
|
||||
fontVariantNumeric: 'tabular-nums',
|
||||
}}
|
||||
>
|
||||
{item.badge}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Footer */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
padding: '12px 14px',
|
||||
borderTop: '1px solid var(--border)',
|
||||
}}
|
||||
>
|
||||
<Avatar name={displayName} size={32} />
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontWeight: 500,
|
||||
color: 'var(--ink-strong)',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{displayName}
|
||||
</div>
|
||||
<div style={{ fontSize: 11, color: 'var(--ink-muted)' }}>Household owner</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue