WellNuo/web/components/Layout/Breadcrumbs.tsx
Sergei 1628501e75 Add Layout components for web application
Implemented responsive layout system with:
- Header: Top navigation with profile menu and mobile hamburger
- Sidebar: Desktop-only navigation sidebar (lg and above)
- Breadcrumbs: Auto-generated navigation breadcrumbs
- Layout: Main wrapper component with configurable options

Features:
- Responsive design (mobile, tablet, desktop)
- Active route highlighting
- User profile integration via auth store
- Click-outside dropdown closing
- Comprehensive test coverage (49 tests passing)

Updated (main) layout to use new Layout component system.
Updated dashboard page to work with new layout structure.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-31 18:20:13 -08:00

145 lines
4.0 KiB
TypeScript

'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useMemo } from 'react';
/**
* Breadcrumb item type
*/
interface BreadcrumbItem {
label: string;
href?: string;
}
/**
* Route label mappings
* Maps route segments to human-readable labels
*/
const routeLabels: Record<string, string> = {
dashboard: 'Dashboard',
beneficiaries: 'Loved Ones',
sensors: 'Sensors',
settings: 'Settings',
profile: 'Profile',
'add-sensor': 'Add Sensor',
subscription: 'Subscription',
equipment: 'Equipment',
share: 'Share Access',
};
/**
* Breadcrumbs Component
*
* Displays navigation breadcrumbs based on current route.
* Automatically generates breadcrumb trail from pathname.
*
* Features:
* - Auto-generated from URL path
* - Clickable navigation links
* - Current page highlighted
* - Responsive (hidden on mobile)
*
* @example
* ```tsx
* <Breadcrumbs />
* ```
*
* Route examples:
* - /dashboard -> Dashboard
* - /beneficiaries/123 -> Loved Ones / John Doe
* - /beneficiaries/123/sensors -> Loved Ones / John Doe / Sensors
*/
export function Breadcrumbs() {
const pathname = usePathname();
const breadcrumbs = useMemo(() => {
// Don't show breadcrumbs on auth pages
if (pathname.startsWith('/login') || pathname.startsWith('/verify-otp')) {
return [];
}
const segments = pathname.split('/').filter(Boolean);
const items: BreadcrumbItem[] = [];
// Build breadcrumb trail
let currentPath = '';
segments.forEach((segment, index) => {
currentPath += `/${segment}`;
// Check if segment is a dynamic ID (numeric)
const isId = /^\d+$/.test(segment);
if (isId) {
// For IDs, use a placeholder or fetch the actual name
// In production, you would fetch the beneficiary name here
items.push({
label: `#${segment}`,
href: currentPath,
});
} else {
// Use mapped label or capitalize segment
const label = routeLabels[segment] || segment.charAt(0).toUpperCase() + segment.slice(1);
items.push({
label,
href: currentPath,
});
}
});
return items;
}, [pathname]);
// Don't render if no breadcrumbs
if (breadcrumbs.length === 0) {
return null;
}
return (
<nav aria-label="Breadcrumb" className="hidden md:block">
<ol className="flex items-center gap-2 text-sm">
{/* Home icon */}
<li>
<Link
href="/dashboard"
className="text-slate-500 hover:text-slate-700"
aria-label="Home"
>
<svg className="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" />
</svg>
</Link>
</li>
{/* Breadcrumb items */}
{breadcrumbs.map((item, index) => {
const isLast = index === breadcrumbs.length - 1;
return (
<li key={item.href} className="flex items-center gap-2">
{/* Separator */}
<svg className="h-4 w-4 text-slate-400" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clipRule="evenodd" />
</svg>
{/* Link or current page */}
{isLast ? (
<span className="font-medium text-slate-900" aria-current="page">
{item.label}
</span>
) : (
<Link
href={item.href || '#'}
className="text-slate-500 hover:text-slate-700"
>
{item.label}
</Link>
)}
</li>
);
})}
</ol>
</nav>
);
}