feat(interests): desired-dimension form fields + size-desired column

Surfaces the recommender inputs added in Phase 2a (interests
.desired_length_ft / desired_width_ft / desired_draft_ft) on the
two interfaces reps actually use:

- /interests list: new "Berth size desired" column rendered as a
  compact "60×18×6 ft" string. Cells with no dimensions show "-";
  partial dimensions render "?" for the missing axis (recommender
  treats null as "no constraint").
- New/Edit Interest form: three optional length/width/draft inputs
  with explanatory subhead. Empty submissions collapse to undefined
  so the API doesn't see "" -> numeric coercion errors.
- createInterestSchema gains the three optional desired-dim fields
  with a shared transform that coerces strings/numbers to a positive
  2-decimal numeric string for the postgres `numeric` column.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-05 02:49:01 +02:00
parent 57cbc9a506
commit 5b70e9b04b
3 changed files with 116 additions and 0 deletions

View File

@@ -66,6 +66,9 @@ interface InterestFormProps {
reminderEnabled?: boolean;
reminderDays?: number | null;
tags?: Array<{ id: string }>;
desiredLengthFt?: string | number | null;
desiredWidthFt?: string | number | null;
desiredDraftFt?: string | number | null;
};
}
@@ -131,6 +134,18 @@ export function InterestForm({ open, onOpenChange, defaultClientId, interest }:
reminderEnabled: interest.reminderEnabled ?? false,
reminderDays: interest.reminderDays ?? undefined,
tagIds: interest.tags?.map((t) => t.id) ?? [],
desiredLengthFt:
interest.desiredLengthFt === null || interest.desiredLengthFt === undefined
? undefined
: String(interest.desiredLengthFt),
desiredWidthFt:
interest.desiredWidthFt === null || interest.desiredWidthFt === undefined
? undefined
: String(interest.desiredWidthFt),
desiredDraftFt:
interest.desiredDraftFt === null || interest.desiredDraftFt === undefined
? undefined
: String(interest.desiredDraftFt),
});
} else if (!interest && open) {
reset({
@@ -394,6 +409,54 @@ export function InterestForm({ open, onOpenChange, defaultClientId, interest }:
<Separator />
{/* Desired berth dimensions (recommender inputs) */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wide">
Berth size desired
</h3>
<p className="text-xs text-muted-foreground">
Imperial. Optional - the recommender treats blank fields as no constraint on that
axis.
</p>
<div className="grid grid-cols-3 gap-3">
<div className="space-y-1">
<Label htmlFor="desiredLengthFt">Length (ft)</Label>
<Input
id="desiredLengthFt"
{...register('desiredLengthFt')}
type="number"
step="0.01"
min={0}
placeholder="e.g. 60"
/>
</div>
<div className="space-y-1">
<Label htmlFor="desiredWidthFt">Width (ft)</Label>
<Input
id="desiredWidthFt"
{...register('desiredWidthFt')}
type="number"
step="0.01"
min={0}
placeholder="e.g. 18"
/>
</div>
<div className="space-y-1">
<Label htmlFor="desiredDraftFt">Draft (ft)</Label>
<Input
id="desiredDraftFt"
{...register('desiredDraftFt')}
type="number"
step="0.01"
min={0}
placeholder="e.g. 6"
/>
</div>
</div>
</div>
<Separator />
{/* Notes */}
<div className="space-y-2">
<Label>Notes</Label>