'use client'; import { useEffect, useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { cn } from '@/lib/utils'; import { type DetailTab } from '@/components/shared/detail-layout'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { EntityActivityFeed } from '@/components/shared/entity-activity-feed'; import { InlineEditableField } from '@/components/shared/inline-editable-field'; import { InlineTagEditor } from '@/components/shared/inline-tag-editor'; import { apiFetch } from '@/lib/api/client'; import { formatCurrency } from '@/lib/utils/currency'; import { BERTH_ACCESS_OPTIONS, BERTH_BOLLARD_CAPACITIES, BERTH_BOLLARD_TYPES, BERTH_BOW_FACING_OPTIONS, BERTH_CLEAT_CAPACITIES, BERTH_CLEAT_TYPES, BERTH_MOORING_TYPES, BERTH_SIDE_PONTOON_OPTIONS, toSelectOptions, } from '@/lib/constants'; import { BerthReservationsTab } from './berth-reservations-tab'; import { BerthInterestsTab } from './berth-interests-tab'; import { BerthInterestPulse } from './berth-interest-pulse'; import { BerthDocumentsTab } from './berth-documents-tab'; import { BerthDealDocumentsTab } from './berth-deal-documents-tab'; type BerthData = { id: string; mooringNumber: string; area: string | null; status: string; lengthFt: string | null; lengthM: string | null; widthFt: string | null; widthM: string | null; draftFt: string | null; draftM: string | null; widthIsMinimum: boolean | null; nominalBoatSize: string | null; nominalBoatSizeM: string | null; waterDepth: string | null; waterDepthM: string | null; waterDepthIsMinimum: boolean | null; sidePontoon: string | null; powerCapacity: string | null; voltage: string | null; mooringType: string | null; cleatType: string | null; cleatCapacity: string | null; bollardType: string | null; bollardCapacity: string | null; access: string | null; price: string | null; priceCurrency: string; bowFacing: string | null; berthApproved: boolean | null; tenureType: string; tenureYears: number | null; tenureStartDate: string | null; tenureEndDate: string | null; statusLastChangedReason: string | null; statusLastModified: string | null; tags: Array<{ id: string; name: string; color: string }>; }; /** * Compact ft/m segmented control for the Specifications card. Two * tappable pills with `min-h-[36px]` for an Apple-HIG-friendly touch * target. The active option gets the brand primary background; the * other reads as muted. */ function UnitToggle({ value, onChange }: { value: 'ft' | 'm'; onChange: (v: 'ft' | 'm') => void }) { return (
{(['ft', 'm'] as const).map((opt) => ( ))}
); } function SpecRow({ label, value }: { label: string; value: React.ReactNode }) { if (!value && value !== 0 && value !== false) return null; // Mobile-first: stack vertically with label on top so long values // (e.g. "206.69 ft / 62.99 m") never clip at the right edge. // From `sm` (>=640px) up: switch to the original two-column layout. return (
{label} {value}
); } function useBerthPatch(berthId: string) { const qc = useQueryClient(); return useMutation({ mutationFn: async (patch: Record) => apiFetch(`/api/v1/berths/${berthId}`, { method: 'PATCH', body: patch, }), onSuccess: () => { qc.invalidateQueries({ queryKey: ['berths', berthId] }); qc.invalidateQueries({ queryKey: ['berths'] }); }, }); } /** * Editable spec row. Wraps SpecRow with InlineEditableField for fields * the operator commonly tweaks (length, width, draft, side pontoon, etc). * Read-only fields (mooringNumber, area) keep using plain SpecRow. * * Numeric fields are stored as strings in the schema (postgres NUMERIC); * the `numeric` flag tells us to parse before sending and display "-" when * blank. */ function EditableSpec({ label, value, displayValue, field, patch, numeric = false, suffix, selectOptions, linkedUnit, }: { label: string; value: string | null; /** Optional formatted version for display only (currency, percent, * unit-suffixed). The edit input still works against the raw `value`. */ displayValue?: string | null; field: string; patch: ReturnType; numeric?: boolean; suffix?: string; /** When provided, the inline editor uses a `