'use client'; import { Plus } from 'lucide-react'; import { useRouter } from 'next/navigation'; import type { Route } from 'next'; import type { ReactNode } from 'react'; import { useUIStore } from '@/stores/ui-store'; import { Button } from '@/components/ui/button'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Separator } from '@/components/ui/separator'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { BackButton } from '@/components/layout/back-button'; import { CommandSearch } from '@/components/search/command-search'; import { Inbox } from '@/components/layout/inbox'; import { UserMenu } from '@/components/layout/user-menu'; import type { Port } from '@/lib/db/schema/ports'; interface TopbarProps { ports: Port[]; user?: { name: string; email: string }; /** Optional leading slot rendered before the breadcrumbs on tablet * viewports - used by AppShell to mount a sidebar trigger button * (logo) when the sidebar is hidden behind a slide-over Sheet. */ leadingSlot?: ReactNode; } export function Topbar({ ports, user, leadingSlot }: TopbarProps) { const router = useRouter(); const currentPortSlug = useUIStore((s) => s.currentPortSlug); const base = currentPortSlug ? `/${currentPortSlug}` : ''; return ( // Three-column grid: smart back button left, search center, actions right. // The brand logo lives in the sidebar header so the topbar center is // dedicated to the global search bar. // // Grid is `auto auto 1fr` so the left + right columns size to their // actual content (back-button label on the left; New / Inbox / Avatar // on the right) and the search column soaks up the rest. // // Wayfinding model: the legacy breadcrumb chain was removed in favor // of a single contextual back button ("Back to Clients", "Back to // Sarah Doe"). Detail pages register their parent via // `useBreadcrumbHint` so the label is entity-aware; everything else // is URL-derived. See src/hooks/use-smart-back.ts.
{/* LEFT: optional sidebar trigger (tablet) + smart back button. Hard-capped width so the column never extends into the absolutely-positioned search bar's footprint. The cap is conservative on smaller widths to leave the search bar breathing room, more generous at xl. */}
{leadingSlot}
{/* CENTER (spacer): the search bar is absolutely positioned below so it anchors to true viewport center regardless of left/right column widths. This empty grid track keeps `auto 1fr auto` so the right column behaves the same as before. */}
{/* CENTER: global search, anchored to true viewport center. The topbar element starts AFTER the 256px sidebar at lg+, so `left: 50%` of the topbar lands sidebar/2 (=128px) right of the viewport center. We subtract that offset at lg+ so the search bar sits under the browser address bar; below lg the sidebar is hidden behind a Sheet and the topbar spans the full viewport, so plain `left: 50%` is already correct. Caps scale by viewport tier so the bar doesn't crowd the side columns. The previous max-w-2xl (672px) at xl ate so much of the topbar that the back-button column on the left got visually clipped by the search bar; tightened to max-w-xl so a "Back to Administration"-class label can render in full: base: max-w-md (28rem) lg: max-w-lg (32rem) xl: max-w-xl (36rem) The wrapper is pointer-events-none so it doesn't capture clicks meant for the left/right columns underneath; only the input itself receives pointer events. */}
{/* RIGHT: action row */}
{/* + New dropdown */} Create {/* Each item routes to the list page with ?create=1 so the relevant create sheet pops automatically (see useCreateFromUrl). The legacy `/clients/new`-style routes this menu used to push to landed on the dynamic detail page with id="new" and silently 404'd. */} {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} router.push(`${base}/clients?create=1` as any)}> New Client {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} router.push(`${base}/yachts?create=1` as any)}> New Yacht {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} router.push(`${base}/companies?create=1` as any)}> New Company {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} router.push(`${base}/interests?create=1` as any)}> New Interest {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} router.push(`${base}/expenses?create=1` as any)}> New Expense {/* /reminders 301s to /inbox#reminders (the merged page) and the server redirect strips the query string, so point straight at the new path. The Reminders section's useCreateFromUrl handler still picks up ?create=1. */} router.push(`${base}/inbox?create=1#reminders` as unknown as Route)} > New Reminder {/* Unified inbox - combines system alerts (operational) and personal notifications (user-targeted) into a single bell with two tabs. Replaces the previous AlertBell + NotificationBell pair. */} {/* User menu - single source of truth for the profile dropdown. Same component is mounted in the sidebar footer so the menu items (incl. port switcher) stay consistent across triggers. */} {(user?.name ?? 'U').slice(0, 1).toUpperCase()} } />
); }