"use client"; import { format } from "date-fns"; import { ChevronRight } from "lucide-react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import React from "react"; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, } from "~/components/ui/breadcrumb"; import { Skeleton } from "~/components/ui/skeleton"; import { getRouteLabel } from "~/lib/pluralize"; import { api } from "~/trpc/react"; function isUUID(str: string) { return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test( str, ); } // Special segment labels const SPECIAL_SEGMENTS: Record = { new: "New", edit: "Edit", import: "Import", export: "Export", dashboard: "Dashboard", entries: "All entries", "time-clock": "Time clock", }; import { cn } from "~/lib/utils"; export function DashboardBreadcrumbs({ className }: { className?: string }) { const pathname = usePathname(); const segments = pathname.split("/").filter(Boolean); // Determine resource type and ID from path const resourceType = segments[1]; // e.g., 'clients', 'invoices', 'businesses' const resourceId = segments[2] && isUUID(segments[2]) ? segments[2] : undefined; // const action = segments[3]; // e.g., 'edit' // Fetch client data if needed const { data: client, isLoading: clientLoading } = api.clients.getById.useQuery( { id: resourceId ?? "" }, { enabled: resourceType === "clients" && !!resourceId }, ); // Fetch invoice data if needed const { data: invoice, isLoading: invoiceLoading } = api.invoices.getById.useQuery( { id: resourceId ?? "" }, { enabled: resourceType === "invoices" && !!resourceId }, ); // Fetch business data if needed const { data: business, isLoading: businessLoading } = api.businesses.getById.useQuery( { id: resourceId ?? "" }, { enabled: resourceType === "businesses" && !!resourceId }, ); // Generate breadcrumb items based on pathname const breadcrumbs = React.useMemo(() => { const items = []; for (let i = 0; i < segments.length; i++) { const segment = segments[i]; const path = `/${segments.slice(0, i + 1).join("/")}`; // Skip dashboard segment as it's always shown as root if (segment === "dashboard") continue; let label: string | React.ReactElement = ""; let shouldShow = true; // Handle UUID segments if (segment && isUUID(segment)) { // Determine which resource we're looking at const prevSegment = segments[i - 1]; if (prevSegment === "clients") { if (clientLoading) { label = ; } else if (client) { label = client.name; } } else if (prevSegment === "invoices") { if (invoiceLoading) { label = ; } else if (invoice) { label = format(new Date(invoice.issueDate), "MMM dd, yyyy"); } } else if (prevSegment === "businesses") { if (businessLoading) { label = ; } else if (business) { label = business.name; } } } // Handle action segments (edit, new, etc.) else if (segment && SPECIAL_SEGMENTS[segment]) { // Don't show 'edit' as the last breadcrumb when we have the resource name if (segment === "edit" && i === segments.length - 1 && resourceId) { shouldShow = false; } else { label = SPECIAL_SEGMENTS[segment]; } } // Handle resource segments (clients, invoices, etc.) else if (segment) { // Use plural form for list pages, singular when there's a specific ID const nextSegment = segments[i + 1]; const isListPage = !nextSegment || (!isUUID(nextSegment) && nextSegment !== "new"); label = getRouteLabel(segment, isListPage); } if (shouldShow && label) { items.push({ label, href: path, isLast: i === segments.length - 1, }); } } return items; }, [ segments, client, invoice, business, clientLoading, invoiceLoading, businessLoading, resourceId, ]); if (breadcrumbs.length === 0) return null; return ( Dashboard {breadcrumbs.map((crumb, index) => ( {crumb.isLast ? ( {crumb.label} ) : ( {crumb.label} )} ))} ); }