Files
pn-new-crm/src/components/layout/breadcrumbs.tsx
Matt 67d7e6e3d5
Some checks failed
Build & Push Docker Images / build-and-push (push) Has been cancelled
Build & Push Docker Images / deploy (push) Has been cancelled
Build & Push Docker Images / lint (push) Has been cancelled
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00

130 lines
3.6 KiB
TypeScript

'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { Fragment } from 'react';
import { ChevronRight } from 'lucide-react';
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb';
import { usePortContext } from '@/providers/port-provider';
// Human-readable labels for route segments
const SEGMENT_LABELS: Record<string, string> = {
dashboard: 'Dashboard',
clients: 'Clients',
interests: 'Interests',
berths: 'Berths',
documents: 'Documents',
files: 'Files',
expenses: 'Expenses',
invoices: 'Invoices',
email: 'Email',
reminders: 'Reminders',
settings: 'Settings',
admin: 'Administration',
reports: 'Reports',
new: 'New',
edit: 'Edit',
profile: 'Profile',
};
function formatSegment(segment: string): string {
return SEGMENT_LABELS[segment] ?? segment.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
}
export function Breadcrumbs() {
const pathname = usePathname();
const { currentPort, currentPortSlug } = usePortContext();
// Split pathname and filter empty segments
const rawSegments = pathname.split('/').filter(Boolean);
// Remove the portSlug segment from display
const segments = currentPortSlug
? rawSegments.filter((seg) => seg !== currentPortSlug)
: rawSegments;
if (segments.length === 0) {
return (
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbPage className="text-foreground font-medium">
{currentPort?.name ?? 'Port Nimara CRM'}
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
);
}
// Build href for each segment
const crumbs = segments.map((segment, index) => {
const segmentsUpToHere = rawSegments.slice(0, rawSegments.indexOf(segment, index) + 1);
const href = '/' + segmentsUpToHere.join('/');
const label = formatSegment(segment);
const isLast = index === segments.length - 1;
return { label, href, isLast };
});
return (
<Breadcrumb>
<BreadcrumbList>
{currentPort && (
<>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link
href={`/${currentPortSlug}/dashboard` as any}
className="text-muted-foreground hover:text-foreground transition-colors"
>
{currentPort.name}
</Link>
</BreadcrumbLink>
</BreadcrumbItem>
{crumbs.length > 0 && (
<BreadcrumbSeparator>
<ChevronRight className="w-3 h-3" />
</BreadcrumbSeparator>
)}
</>
)}
{crumbs.map((crumb, index) => (
<Fragment key={crumb.href}>
<BreadcrumbItem>
{crumb.isLast ? (
<BreadcrumbPage className="font-medium text-foreground">
{crumb.label}
</BreadcrumbPage>
) : (
<BreadcrumbLink asChild>
<Link
href={crumb.href as any}
className="text-muted-foreground hover:text-foreground transition-colors"
>
{crumb.label}
</Link>
</BreadcrumbLink>
)}
</BreadcrumbItem>
{!crumb.isLast && (
<BreadcrumbSeparator>
<ChevronRight className="w-3 h-3" />
</BreadcrumbSeparator>
)}
</Fragment>
))}
</BreadcrumbList>
</Breadcrumb>
);
}