'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 `