'use client'; import { Activity, MoreHorizontal, Pencil } from 'lucide-react'; import { useRouter, useParams } from 'next/navigation'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { TagBadge } from '@/components/shared/tag-badge'; import { ListCard, ListCardAvatar, ListCardMeta } from '@/components/shared/list-card'; import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill'; import { formatCurrency } from '@/lib/utils/currency'; import type { BerthRow } from './berth-columns'; import { mooringLetterDot } from './mooring-letter-tone'; const STATUS_LABELS: Record = { available: 'Available', under_offer: 'Under Offer', sold: 'Sold', }; const BERTH_STATUS_PILL: Record = { available: 'available', under_offer: 'under_offer', sold: 'sold', }; interface BerthCardProps { berth: BerthRow; } export function BerthCard({ berth }: BerthCardProps) { const router = useRouter(); const params = useParams<{ portSlug: string }>(); const portSlug = params?.portSlug ?? ''; const statusLabel = STATUS_LABELS[berth.status] ?? berth.status; const statusPill = BERTH_STATUS_PILL[berth.status] ?? 'pending'; // Accent stripe groups visually by dock (A-row, B-row, ...). Status is // already conveyed by the pill below, so the stripe is dock-keyed. const accentClass = mooringLetterDot(berth.mooringNumber) ?? 'bg-slate-300'; // Dimensions string — Length × Width × Draft (each segment is optional). // The avatar already conveys the mooring number, so this becomes the // primary "what is this berth" line. const dimParts: string[] = []; if (berth.lengthM) dimParts.push(`${berth.lengthM}m`); if (berth.widthM) dimParts.push(`${berth.widthM}m`); if (berth.draftM) dimParts.push(`${berth.draftM}m draft`); const dimText = dimParts.length > 0 ? dimParts.join(' × ') : null; // Recommended boat size — the most rep-actionable signal in a glance // ("can my client's yacht park here?"). Tenure was previously here but // dropped: tenure is set per EOI/contract, not per berth, so showing // it as a berth property was misleading. let boatCapacityText: string | null = null; if (berth.nominalBoatSizeM) { boatCapacityText = `Fits up to ${berth.nominalBoatSizeM}m`; } else if (berth.nominalBoatSize) { boatCapacityText = `Fits up to ${berth.nominalBoatSize}ft`; } // Water depth — operational; matters for deep-keel yachts. let waterDepthText: string | null = null; if (berth.waterDepthM) { const prefix = berth.waterDepthIsMinimum ? '≥ ' : ''; waterDepthText = `${prefix}${berth.waterDepthM}m deep`; } // Power label: combine capacity + voltage when both present. let powerText: string | null = null; if (berth.powerCapacity && berth.voltage) { powerText = `${berth.powerCapacity}A / ${berth.voltage}V`; } else if (berth.powerCapacity) { powerText = `${berth.powerCapacity}A`; } else if (berth.voltage) { powerText = `${berth.voltage}V`; } // Secondary meta: boat-capacity · water-depth · price · power. All // optional; order favours the highest-utility scan signals first. const metaParts: string[] = []; if (boatCapacityText) metaParts.push(boatCapacityText); if (waterDepthText) metaParts.push(waterDepthText); if (berth.price) metaParts.push(formatCurrency(berth.price, berth.priceCurrency, { maxFractionDigits: 0 })); if (powerText) metaParts.push(powerText); const tags = berth.tags ?? []; return ( { e.stopPropagation(); router.push(`/${portSlug}/berths/${berth.id}`); }} > View details { e.stopPropagation(); router.push(`/${portSlug}/berths/${berth.id}?edit=true`); }} > Edit } >
{/* The mooring number IS the avatar — recognisable at a glance (A1, B12, …) and eliminates the duplicate berth-number heading that previously sat to the right of an anchor icon. */}
{/* Primary line: dimensions (L × W × Draft). The avatar already carries the area letter, so this slot becomes the "what fits here" answer. Falls back gracefully when dimensions aren't recorded yet. */}

{dimText ?? No dimensions}

{/* Meta line: tenure · price · power. All optional. */} {metaParts.length > 0 ? (
{metaParts.map((part, i) => ( {i > 0 ? · : null} {part} ))}
) : null} {/* Status pill + tags */}
{statusLabel} {tags.slice(0, 2).map((tag) => ( ))} {tags.length > 2 ? ( +{tags.length - 2} ) : null}
); }