'use client'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import type { DetailTab } from '@/components/shared/detail-layout'; import { InlineEditableField } from '@/components/shared/inline-editable-field'; import { InlineCountryField } from '@/components/shared/inline-country-field'; import { InlineTimezoneField } from '@/components/shared/inline-timezone-field'; import { primaryTimezoneFor } from '@/lib/i18n/timezones'; import { InlineTagEditor } from '@/components/shared/inline-tag-editor'; import { NotesList } from '@/components/shared/notes-list'; import type { CountryCode } from '@/lib/i18n/countries'; import { ClientInterestsTab } from '@/components/clients/client-interests-tab'; import { ClientPipelineSummary } from '@/components/clients/client-pipeline-summary'; import { ClientYachtsTab } from '@/components/clients/client-yachts-tab'; import { ClientCompaniesTab } from '@/components/clients/client-companies-tab'; import { ClientReservationsTab } from '@/components/clients/client-reservations-tab'; import { ClientFilesTab } from '@/components/clients/client-files-tab'; import { ContactsEditor } from '@/components/clients/contacts-editor'; import { AddressesEditor, type Address } from '@/components/shared/addresses-editor'; import { EntityActivityFeed } from '@/components/shared/entity-activity-feed'; import { apiFetch } from '@/lib/api/client'; type ClientPatchField = | 'fullName' | 'nationality' | 'nationalityIso' | 'preferredContactMethod' | 'preferredLanguage' | 'timezone' | 'source' | 'sourceDetails'; const SOURCE_OPTIONS = [ { value: 'website', label: 'Website' }, { value: 'manual', label: 'Manual' }, { value: 'referral', label: 'Referral' }, { value: 'broker', label: 'Broker' }, { value: 'other', label: 'Other' }, ]; const CONTACT_METHOD_OPTIONS = [ { value: 'email', label: 'Email' }, { value: 'phone', label: 'Phone' }, { value: 'whatsapp', label: 'WhatsApp' }, ]; function useClientPatch(clientId: string) { const qc = useQueryClient(); return useMutation({ mutationFn: async (patch: Partial>) => { return apiFetch(`/api/v1/clients/${clientId}`, { method: 'PATCH', body: patch, }); }, onSuccess: () => { qc.invalidateQueries({ queryKey: ['clients', clientId] }); }, }); } function EditableRow({ label, children }: { label: string; children: React.ReactNode }) { return (
{label}
{children}
); } interface ClientTabsOptions { clientId: string; currentUserId?: string; client: { fullName: string; nationality?: string | null; nationalityIso?: string | null; preferredContactMethod?: string | null; preferredLanguage?: string | null; timezone?: string | null; source?: string | null; sourceDetails?: string | null; contacts?: Array<{ id: string; channel: string; value: string; valueE164?: string | null; valueCountry?: string | null; label?: string | null; isPrimary: boolean; }>; addresses?: Address[]; yachts: Array<{ id: string; name: string; hullNumber: string | null; registration: string | null; lengthFt: string | null; widthFt: string | null; status: string; }>; companies: Array<{ membershipId: string; role: string; isPrimary: boolean; startDate: string | Date; company: { id: string; name: string; legalName: string | null; status: string; }; }>; activeReservations: Array<{ id: string; berthId: string; yachtId: string; startDate: string | Date; tenureType: string; status: string; }>; interestCount?: number; noteCount?: number; tags?: Array<{ id: string; name: string; color: string }>; }; } function OverviewTab({ clientId, client, }: { clientId: string; client: ClientTabsOptions['client']; }) { const mutation = useClientPatch(clientId); const save = (field: ClientPatchField) => async (next: string | null) => { await mutation.mutateAsync({ [field]: next }); }; return (
{/* Personal Info */}

Personal Information

{ // Auto-default the timezone to the country's primary // zone when none is set yet — saves the rep a click // and matches what a marina actually wants for first // contact (London for GB, NYC for US, etc.). Only // fires when timezone is empty so we never clobber a // value the rep deliberately picked. const patch: { nationalityIso: string | null; timezone?: string | null } = { nationalityIso: iso, }; if (iso && !client.timezone) { const defaultTz = primaryTimezoneFor(iso as CountryCode); if (defaultTz) patch.timezone = defaultTz; } await mutation.mutateAsync(patch); }} data-testid="client-country-inline" /> { await mutation.mutateAsync({ timezone: tz }); }} data-testid="client-timezone-inline" />
{/* Contacts */}

Contact Details

{/* Source */}

Source

{/* Tags */}

Tags

); } export function getClientTabs({ clientId, currentUserId, client }: ClientTabsOptions): DetailTab[] { return [ { id: 'overview', label: 'Overview', content: , }, { id: 'interests', label: 'Interests', badge: client.interestCount, content: , }, { id: 'yachts', label: 'Yachts', badge: client.yachts.length, content: , }, { id: 'companies', label: 'Companies', badge: client.companies.length, content: , }, { id: 'reservations', label: 'Reservations', badge: client.activeReservations.length, content: ( ), }, { id: 'addresses', label: 'Addresses', badge: client.addresses?.length ?? 0, content: ( ), }, { id: 'notes', label: 'Notes', badge: client.noteCount, content: ( ), }, { id: 'files', label: 'Files', content: , }, { id: 'activity', label: 'Activity', content: ( ), }, ]; }