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:
@@ -37,10 +37,34 @@ export interface InterestRow {
|
||||
dateDepositReceived?: string | null;
|
||||
eoiStatus?: string | null;
|
||||
outcome?: string | null;
|
||||
/** Imperial; nullable. Recommender treats nulls as "no constraint" on
|
||||
* that axis. Rendered as a compact "60×18×6 ft" string in the list. */
|
||||
desiredLengthFt?: string | number | null;
|
||||
desiredWidthFt?: string | number | null;
|
||||
desiredDraftFt?: string | number | null;
|
||||
notesCount?: number;
|
||||
tags?: Array<{ id: string; name: string; color: string }>;
|
||||
}
|
||||
|
||||
function formatDim(value: string | number | null | undefined): string {
|
||||
if (value === null || value === undefined || value === '') return '?';
|
||||
const n = typeof value === 'number' ? value : parseFloat(value);
|
||||
if (!Number.isFinite(n)) return '?';
|
||||
return Number.isInteger(n) ? String(n) : n.toFixed(1);
|
||||
}
|
||||
|
||||
function formatDesiredSize(row: InterestRow): string | null {
|
||||
const { desiredLengthFt, desiredWidthFt, desiredDraftFt } = row;
|
||||
if (
|
||||
(desiredLengthFt === null || desiredLengthFt === undefined || desiredLengthFt === '') &&
|
||||
(desiredWidthFt === null || desiredWidthFt === undefined || desiredWidthFt === '') &&
|
||||
(desiredDraftFt === null || desiredDraftFt === undefined || desiredDraftFt === '')
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return `${formatDim(desiredLengthFt)}×${formatDim(desiredWidthFt)}×${formatDim(desiredDraftFt)} ft`;
|
||||
}
|
||||
|
||||
const SOURCE_LABELS: Record<string, string> = {
|
||||
website: 'Website',
|
||||
manual: 'Manual',
|
||||
@@ -134,6 +158,16 @@ export function getInterestColumns({
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'desiredSize',
|
||||
header: 'Berth size desired',
|
||||
enableSorting: false,
|
||||
cell: ({ row }) => {
|
||||
const label = formatDesiredSize(row.original);
|
||||
if (!label) return <span className="text-muted-foreground">-</span>;
|
||||
return <span className="text-sm tabular-nums">{label}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'pipelineStage',
|
||||
accessorKey: 'pipelineStage',
|
||||
|
||||
Reference in New Issue
Block a user