feat(interests): add yacht-picker to interest form

This commit is contained in:
Matt Ciaccio
2026-04-24 15:36:27 +02:00
parent f9cb8003b5
commit bcf4c1f797

View File

@@ -18,18 +18,8 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetFooter,
} from '@/components/ui/sheet';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetFooter } from '@/components/ui/sheet';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import {
Command,
CommandEmpty,
@@ -41,6 +31,7 @@ import {
import { Checkbox } from '@/components/ui/checkbox';
import { Separator } from '@/components/ui/separator';
import { TagPicker } from '@/components/shared/tag-picker';
import { YachtPicker } from '@/components/yachts/yacht-picker';
import { apiFetch } from '@/lib/api/client';
import { useEntityOptions } from '@/hooks/use-entity-options';
import { createInterestSchema, type CreateInterestInput } from '@/lib/validators/interests';
@@ -71,6 +62,7 @@ interface InterestFormProps {
id: string;
clientId: string;
clientName?: string | null;
yachtId?: string | null;
berthId?: string | null;
berthMooringNumber?: string | null;
pipelineStage: string;
@@ -101,6 +93,7 @@ export function InterestForm({ open, onOpenChange, interest }: InterestFormProps
resolver: zodResolver(createInterestSchema),
defaultValues: {
clientId: '',
yachtId: undefined,
pipelineStage: 'open',
reminderEnabled: false,
tagIds: [],
@@ -111,26 +104,34 @@ export function InterestForm({ open, onOpenChange, interest }: InterestFormProps
const reminderEnabled = watch('reminderEnabled');
const selectedClientId = watch('clientId');
const selectedBerthId = watch('berthId');
const selectedYachtId = watch('yachtId');
const { options: clientOptions, isLoading: clientsLoading, setSearch: setClientSearch } =
useEntityOptions({
endpoint: '/api/v1/clients/options',
labelKey: 'fullName',
});
const {
options: clientOptions,
isLoading: clientsLoading,
setSearch: setClientSearch,
} = useEntityOptions({
endpoint: '/api/v1/clients/options',
labelKey: 'fullName',
});
const { options: berthOptions, isLoading: berthsLoading, setSearch: setBerthSearch } =
useEntityOptions({
endpoint: '/api/v1/berths/options',
labelKey: 'mooringNumber',
});
const {
options: berthOptions,
isLoading: berthsLoading,
setSearch: setBerthSearch,
} = useEntityOptions({
endpoint: '/api/v1/berths/options',
labelKey: 'mooringNumber',
});
useEffect(() => {
if (interest && open) {
reset({
clientId: interest.clientId,
yachtId: interest.yachtId ?? undefined,
berthId: interest.berthId ?? undefined,
pipelineStage: interest.pipelineStage as typeof PIPELINE_STAGES[number],
leadCategory: interest.leadCategory as typeof LEAD_CATEGORIES[number] | undefined,
pipelineStage: interest.pipelineStage as (typeof PIPELINE_STAGES)[number],
leadCategory: interest.leadCategory as (typeof LEAD_CATEGORIES)[number] | undefined,
source: interest.source ?? undefined,
notes: interest.notes ?? undefined,
reminderEnabled: interest.reminderEnabled ?? false,
@@ -140,6 +141,7 @@ export function InterestForm({ open, onOpenChange, interest }: InterestFormProps
} else if (!interest && open) {
reset({
clientId: '',
yachtId: undefined,
pipelineStage: 'open',
reminderEnabled: false,
tagIds: [],
@@ -178,10 +180,7 @@ export function InterestForm({ open, onOpenChange, interest }: InterestFormProps
<SheetTitle>{isEdit ? 'Edit Interest' : 'New Interest'}</SheetTitle>
</SheetHeader>
<form
onSubmit={handleSubmit((data) => mutation.mutate(data))}
className="space-y-6 py-6"
>
<form onSubmit={handleSubmit((data) => mutation.mutate(data))} className="space-y-6 py-6">
{/* Client */}
<div className="space-y-4">
<h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wide">
@@ -202,16 +201,13 @@ export function InterestForm({ open, onOpenChange, interest }: InterestFormProps
)}
disabled={isEdit}
>
{selectedClient?.label ?? (interest?.clientName ?? 'Select client...')}
{selectedClient?.label ?? interest?.clientName ?? 'Select client...'}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[400px] p-0">
<Command>
<CommandInput
placeholder="Search clients..."
onValueChange={setClientSearch}
/>
<CommandInput placeholder="Search clients..." onValueChange={setClientSearch} />
<CommandList>
<CommandEmpty>
{clientsLoading ? 'Loading...' : 'No clients found.'}
@@ -258,16 +254,13 @@ export function InterestForm({ open, onOpenChange, interest }: InterestFormProps
!selectedBerthId && 'text-muted-foreground',
)}
>
{selectedBerth?.label ?? (interest?.berthMooringNumber ?? 'Select berth...')}
{selectedBerth?.label ?? interest?.berthMooringNumber ?? 'Select berth...'}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[400px] p-0">
<Command>
<CommandInput
placeholder="Search berths..."
onValueChange={setBerthSearch}
/>
<CommandInput placeholder="Search berths..." onValueChange={setBerthSearch} />
<CommandList>
<CommandEmpty>
{berthsLoading ? 'Loading...' : 'No berths found.'}
@@ -312,6 +305,24 @@ export function InterestForm({ open, onOpenChange, interest }: InterestFormProps
</PopoverContent>
</Popover>
</div>
<div className="space-y-2">
<Label>Yacht</Label>
<YachtPicker
value={selectedYachtId ?? null}
onChange={(id) => setValue('yachtId', id ?? undefined)}
ownerFilter={
selectedClientId ? { type: 'client', id: selectedClientId } : undefined
}
disabled={!selectedClientId}
placeholder={selectedClientId ? 'Select yacht...' : 'Select a client first'}
/>
<p className="text-xs text-muted-foreground">
Required before the interest can leave the &quot;Open&quot; stage.
</p>
{/* TODO: also include company-owned yachts where client is a member — requires autocomplete owner=any|company filter */}
{/* TODO: add "Add new yacht" inline shortcut (requires YachtForm integration) */}
</div>
</div>
<Separator />
@@ -326,7 +337,9 @@ export function InterestForm({ open, onOpenChange, interest }: InterestFormProps
<Label>Stage</Label>
<Select
value={watch('pipelineStage') ?? 'open'}
onValueChange={(v) => setValue('pipelineStage', v as typeof PIPELINE_STAGES[number])}
onValueChange={(v) =>
setValue('pipelineStage', v as (typeof PIPELINE_STAGES)[number])
}
>
<SelectTrigger>
<SelectValue placeholder="Select stage" />
@@ -346,7 +359,10 @@ export function InterestForm({ open, onOpenChange, interest }: InterestFormProps
<Select
value={watch('leadCategory') ?? ''}
onValueChange={(v) =>
setValue('leadCategory', v ? v as typeof LEAD_CATEGORIES[number] : undefined)
setValue(
'leadCategory',
v ? (v as (typeof LEAD_CATEGORIES)[number]) : undefined,
)
}
>
<SelectTrigger>
@@ -427,18 +443,11 @@ export function InterestForm({ open, onOpenChange, interest }: InterestFormProps
{/* Tags */}
<div className="space-y-2">
<Label>Tags</Label>
<TagPicker
selectedIds={tagIds}
onChange={(ids) => setValue('tagIds', ids)}
/>
<TagPicker selectedIds={tagIds} onChange={(ids) => setValue('tagIds', ids)} />
</div>
<SheetFooter>
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
>
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button type="submit" disabled={isSubmitting || mutation.isPending}>