Files
pn-new-crm/src/components/berths/berth-columns.tsx

580 lines
18 KiB
TypeScript
Raw Normal View History

'use client';
feat(berths): manual status catch-up wizard + reconciliation queue (#67) Wires the long-dormant berths.status_override_mode column into a closed loop so reps can reconcile berths flipped to under_offer/sold without a backing interest. Phase 1 — Status source tracking: - updateBerthStatus() stamps 'manual' on every user-facing write - berth-rules-engine.ts stamps 'automated' on auto-rule writes - new clearBerthOverride() helper nulls the field and stamps the reason "Reconciled via interest <id>" — only the wizard calls it Phase 2 — Visual indicator: - Amber "Manual" chip on berth-list rows where statusOverrideMode='manual' AND no active linked interest (the candidates for catch-up) Phase 3 — Reconciliation queue: - new service listManualReconcileBerths() with cross-port-safe NOT-EXISTS against activeInterestsWhere - GET /api/v1/berths/reconcile-queue - new page /[portSlug]/admin/berths/reconcile listing the queue, each row linking to the catch-up wizard Phase 4 — Catch-up wizard: - POST /api/v1/berths/[id]/reconcile orchestrates create-client (optional quick-create), create-interest with primary berth link, and clearBerthOverride — composed via existing service helpers - <CatchUpWizard> dialog: existing-client or quick-create, optional yacht link, stage picker scoped to the current berth status, with contract auto-setting outcome=won Phase 5 — Entry points: - sidebar Admin > "Reconcile berths" link - berth-list row action menu shows "Catch up…" on flagged rows Doc upload + payment recording (spec phases 4.4 / 4.5) are deferred — once the interest exists, the rep uses the standard interest detail page surfaces for those follow-ups. The wizard's MVP responsibility is to take a manual berth to "interest exists, override cleared" in one round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:55:22 +02:00
import { useState } from 'react';
import { type ColumnDef } from '@tanstack/react-table';
feat(berths): manual status catch-up wizard + reconciliation queue (#67) Wires the long-dormant berths.status_override_mode column into a closed loop so reps can reconcile berths flipped to under_offer/sold without a backing interest. Phase 1 — Status source tracking: - updateBerthStatus() stamps 'manual' on every user-facing write - berth-rules-engine.ts stamps 'automated' on auto-rule writes - new clearBerthOverride() helper nulls the field and stamps the reason "Reconciled via interest <id>" — only the wizard calls it Phase 2 — Visual indicator: - Amber "Manual" chip on berth-list rows where statusOverrideMode='manual' AND no active linked interest (the candidates for catch-up) Phase 3 — Reconciliation queue: - new service listManualReconcileBerths() with cross-port-safe NOT-EXISTS against activeInterestsWhere - GET /api/v1/berths/reconcile-queue - new page /[portSlug]/admin/berths/reconcile listing the queue, each row linking to the catch-up wizard Phase 4 — Catch-up wizard: - POST /api/v1/berths/[id]/reconcile orchestrates create-client (optional quick-create), create-interest with primary berth link, and clearBerthOverride — composed via existing service helpers - <CatchUpWizard> dialog: existing-client or quick-create, optional yacht link, stage picker scoped to the current berth status, with contract auto-setting outcome=won Phase 5 — Entry points: - sidebar Admin > "Reconcile berths" link - berth-list row action menu shows "Catch up…" on flagged rows Doc upload + payment recording (spec phases 4.4 / 4.5) are deferred — once the interest exists, the rep uses the standard interest detail page surfaces for those follow-ups. The wizard's MVP responsibility is to take a manual berth to "interest exists, override cleared" in one round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:55:22 +02:00
import { MoreHorizontal, Pencil, Activity, RefreshCw } from 'lucide-react';
import { useRouter, useParams } from 'next/navigation';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { TagBadge } from '@/components/shared/tag-badge';
import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill';
import { formatCurrency } from '@/lib/utils/currency';
import { InlineEditableField } from '@/components/shared/inline-editable-field';
import { apiFetch } from '@/lib/api/client';
import { usePermissions } from '@/hooks/use-permissions';
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
import { mooringLetterDot } from './mooring-letter-tone';
feat: round 2 — stage prompts, berth header, EOI inline edit, measurement units Berth surfaces - New compact mooring-chip header (colored plate + status pill, dock-label in tooltip) replaces the redundant "Berth B1 / Sold / B DOCK" stack - Berth list gains a "Latest deal stage" column showing the most-advanced pipeline stage of any active linked interest (server-aggregated, ranks by PIPELINE_STAGES index) - "Linked prospect" Select on the status-change dialog rebuilt as a Command combobox: search, recent-first sort, stage-coloured pills Pipeline UX - Reverting an interest to Open with linked berths now prompts: keep the links, unlink and reset, or cancel. Silent when no berths are linked - Activity feed + entity-activity feed normalise enum field values via STAGE_LABELS / formatSource: "deposit_10pct → contract_sent" reads as "10% Deposit → Contract Sent" EOI generate dialog - Inline-editable rows for client name, nationality (country combobox), and yacht name — pencil affordance saves directly via clients/yachts PATCH - Replaces the single "Edit on client's page" link with two contextual links framed by short copy explaining what's inline vs what needs the canonical page - Backend EoiContext now includes client.id + yacht.id so the dialog can PATCH without an extra round-trip Company form - New "Connections" section lets the rep attach members (clients) and yachts during create. Yacht attach uses the existing transfer endpoint so audit log + ownership history capture the change - Inline "+ New client" / "+ New yacht" buttons open the canonical forms stacked over the company sheet - After save, the form chains to a yacht pull-in prompt (if any attached client owns yachts not yet linked) and an optional "Create interest" step pre-filled with the first attached client Admin - /admin landing gains a searchable index — typed query flattens groups into a result list matching label + description + group title - "Documenso & EOI" card relabelled to "EOI signing service" (consistent with the user-facing language rename from round 1) Measurement units (migration 0053) - interests gains desired_*_m columns + desired_*_unit discriminators so the rep's literal entry (ft OR m) is preserved verbatim instead of being reconstructed from a single canonical column on every render - yachts + berths gain matching *_unit columns alongside their existing ft + m pairs; defaults to 'ft' so legacy rows still render normally - Interest form POST/PATCH now sends both ft + m + unit; computed m is derived from the ft canonical to keep the recommender SQL unchanged Misc - Active-deals tile + topbar type their Link href as `Route` instead of `any` - Unused REPORT_TYPE_LABELS const dropped from generate-report-form - Test fixtures (fill-eoi-form, documenso-payload, public-berths) updated to include the new id + unit fields on the EoiContext / Berth shapes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:28:22 +02:00
import { stageBadgeClass, stageLabel } from '@/lib/constants';
feat(berths): manual status catch-up wizard + reconciliation queue (#67) Wires the long-dormant berths.status_override_mode column into a closed loop so reps can reconcile berths flipped to under_offer/sold without a backing interest. Phase 1 — Status source tracking: - updateBerthStatus() stamps 'manual' on every user-facing write - berth-rules-engine.ts stamps 'automated' on auto-rule writes - new clearBerthOverride() helper nulls the field and stamps the reason "Reconciled via interest <id>" — only the wizard calls it Phase 2 — Visual indicator: - Amber "Manual" chip on berth-list rows where statusOverrideMode='manual' AND no active linked interest (the candidates for catch-up) Phase 3 — Reconciliation queue: - new service listManualReconcileBerths() with cross-port-safe NOT-EXISTS against activeInterestsWhere - GET /api/v1/berths/reconcile-queue - new page /[portSlug]/admin/berths/reconcile listing the queue, each row linking to the catch-up wizard Phase 4 — Catch-up wizard: - POST /api/v1/berths/[id]/reconcile orchestrates create-client (optional quick-create), create-interest with primary berth link, and clearBerthOverride — composed via existing service helpers - <CatchUpWizard> dialog: existing-client or quick-create, optional yacht link, stage picker scoped to the current berth status, with contract auto-setting outcome=won Phase 5 — Entry points: - sidebar Admin > "Reconcile berths" link - berth-list row action menu shows "Catch up…" on flagged rows Doc upload + payment recording (spec phases 4.4 / 4.5) are deferred — once the interest exists, the rep uses the standard interest detail page surfaces for those follow-ups. The wizard's MVP responsibility is to take a manual berth to "interest exists, override cleared" in one round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:55:22 +02:00
import { CatchUpWizard } from '@/components/berths/catch-up-wizard';
import { ActiveInterestsPopover } from '@/components/berths/active-interests-popover';
export type BerthRow = {
id: string;
mooringNumber: string;
area: string | null;
status: string;
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
// Dimensions (both units; row falls back when one is null)
lengthFt: string | null;
widthFt: string | null;
draftFt: string | null;
lengthM: string | null;
widthM: string | null;
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
draftM: string | null;
widthIsMinimum: boolean | null;
// Capacity
nominalBoatSize: string | null;
nominalBoatSizeM: string | null;
waterDepth: string | null;
waterDepthM: string | null;
waterDepthIsMinimum: boolean | null;
// Pontoon details (NocoDB)
sidePontoon: string | null;
mooringType: string | null;
cleatType: string | null;
cleatCapacity: string | null;
bollardType: string | null;
bollardCapacity: string | null;
access: string | null;
bowFacing: string | null;
berthApproved: boolean | null;
// Power
powerCapacity: string | null;
voltage: string | null;
// Pricing
price: string | null;
priceCurrency: string;
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
weeklyRateHighUsd: string | null;
weeklyRateLowUsd: string | null;
dailyRateHighUsd: string | null;
dailyRateLowUsd: string | null;
pricingValidUntil: string | null;
// Tenure
tenureType: string;
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
tenureYears: number | null;
tenureStartDate: string | null;
tenureEndDate: string | null;
tags: Array<{ id: string; name: string; color: string }>;
feat: round 2 — stage prompts, berth header, EOI inline edit, measurement units Berth surfaces - New compact mooring-chip header (colored plate + status pill, dock-label in tooltip) replaces the redundant "Berth B1 / Sold / B DOCK" stack - Berth list gains a "Latest deal stage" column showing the most-advanced pipeline stage of any active linked interest (server-aggregated, ranks by PIPELINE_STAGES index) - "Linked prospect" Select on the status-change dialog rebuilt as a Command combobox: search, recent-first sort, stage-coloured pills Pipeline UX - Reverting an interest to Open with linked berths now prompts: keep the links, unlink and reset, or cancel. Silent when no berths are linked - Activity feed + entity-activity feed normalise enum field values via STAGE_LABELS / formatSource: "deposit_10pct → contract_sent" reads as "10% Deposit → Contract Sent" EOI generate dialog - Inline-editable rows for client name, nationality (country combobox), and yacht name — pencil affordance saves directly via clients/yachts PATCH - Replaces the single "Edit on client's page" link with two contextual links framed by short copy explaining what's inline vs what needs the canonical page - Backend EoiContext now includes client.id + yacht.id so the dialog can PATCH without an extra round-trip Company form - New "Connections" section lets the rep attach members (clients) and yachts during create. Yacht attach uses the existing transfer endpoint so audit log + ownership history capture the change - Inline "+ New client" / "+ New yacht" buttons open the canonical forms stacked over the company sheet - After save, the form chains to a yacht pull-in prompt (if any attached client owns yachts not yet linked) and an optional "Create interest" step pre-filled with the first attached client Admin - /admin landing gains a searchable index — typed query flattens groups into a result list matching label + description + group title - "Documenso & EOI" card relabelled to "EOI signing service" (consistent with the user-facing language rename from round 1) Measurement units (migration 0053) - interests gains desired_*_m columns + desired_*_unit discriminators so the rep's literal entry (ft OR m) is preserved verbatim instead of being reconstructed from a single canonical column on every render - yachts + berths gain matching *_unit columns alongside their existing ft + m pairs; defaults to 'ft' so legacy rows still render normally - Interest form POST/PATCH now sends both ft + m + unit; computed m is derived from the ft canonical to keep the recommender SQL unchanged Misc - Active-deals tile + topbar type their Link href as `Route` instead of `any` - Unused REPORT_TYPE_LABELS const dropped from generate-report-form - Test fixtures (fill-eoi-form, documenso-payload, public-berths) updated to include the new id + unit fields on the EoiContext / Berth shapes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:28:22 +02:00
/** Most-advanced pipeline stage among the berth's active interests. Null
* when no active interest is linked. Read-only; computed server-side. */
latestInterestStage?: string | null;
/** Count of non-terminal, non-archived interests linked to this berth.
* Drives the "Active interests" column + the demand sort. */
activeInterestCount?: number;
feat(berths): manual status catch-up wizard + reconciliation queue (#67) Wires the long-dormant berths.status_override_mode column into a closed loop so reps can reconcile berths flipped to under_offer/sold without a backing interest. Phase 1 — Status source tracking: - updateBerthStatus() stamps 'manual' on every user-facing write - berth-rules-engine.ts stamps 'automated' on auto-rule writes - new clearBerthOverride() helper nulls the field and stamps the reason "Reconciled via interest <id>" — only the wizard calls it Phase 2 — Visual indicator: - Amber "Manual" chip on berth-list rows where statusOverrideMode='manual' AND no active linked interest (the candidates for catch-up) Phase 3 — Reconciliation queue: - new service listManualReconcileBerths() with cross-port-safe NOT-EXISTS against activeInterestsWhere - GET /api/v1/berths/reconcile-queue - new page /[portSlug]/admin/berths/reconcile listing the queue, each row linking to the catch-up wizard Phase 4 — Catch-up wizard: - POST /api/v1/berths/[id]/reconcile orchestrates create-client (optional quick-create), create-interest with primary berth link, and clearBerthOverride — composed via existing service helpers - <CatchUpWizard> dialog: existing-client or quick-create, optional yacht link, stage picker scoped to the current berth status, with contract auto-setting outcome=won Phase 5 — Entry points: - sidebar Admin > "Reconcile berths" link - berth-list row action menu shows "Catch up…" on flagged rows Doc upload + payment recording (spec phases 4.4 / 4.5) are deferred — once the interest exists, the rep uses the standard interest detail page surfaces for those follow-ups. The wizard's MVP responsibility is to take a manual berth to "interest exists, override cleared" in one round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:55:22 +02:00
/** #67: source of the last status write. 'manual' when a human set it
* via the API; 'automated' when a berth-rule fired; null on rows that
* haven't been touched since seed. The reconciliation surface treats
* 'manual' + no latestInterestStage as a row needing catch-up. */
statusOverrideMode?: string | null;
};
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
/**
* Toggleable columns for the berth list ColumnPicker. Heavy NocoDB
* fields default to hidden; reps can switch them on per-table-view.
* `mooringNumber` is intentionally omitted from this list - it's the
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
* primary identifier and always visible.
*/
export const BERTH_COLUMN_OPTIONS: Array<{ id: string; label: string }> = [
{ id: 'area', label: 'Area' },
{ id: 'status', label: 'Status' },
feat: round 2 — stage prompts, berth header, EOI inline edit, measurement units Berth surfaces - New compact mooring-chip header (colored plate + status pill, dock-label in tooltip) replaces the redundant "Berth B1 / Sold / B DOCK" stack - Berth list gains a "Latest deal stage" column showing the most-advanced pipeline stage of any active linked interest (server-aggregated, ranks by PIPELINE_STAGES index) - "Linked prospect" Select on the status-change dialog rebuilt as a Command combobox: search, recent-first sort, stage-coloured pills Pipeline UX - Reverting an interest to Open with linked berths now prompts: keep the links, unlink and reset, or cancel. Silent when no berths are linked - Activity feed + entity-activity feed normalise enum field values via STAGE_LABELS / formatSource: "deposit_10pct → contract_sent" reads as "10% Deposit → Contract Sent" EOI generate dialog - Inline-editable rows for client name, nationality (country combobox), and yacht name — pencil affordance saves directly via clients/yachts PATCH - Replaces the single "Edit on client's page" link with two contextual links framed by short copy explaining what's inline vs what needs the canonical page - Backend EoiContext now includes client.id + yacht.id so the dialog can PATCH without an extra round-trip Company form - New "Connections" section lets the rep attach members (clients) and yachts during create. Yacht attach uses the existing transfer endpoint so audit log + ownership history capture the change - Inline "+ New client" / "+ New yacht" buttons open the canonical forms stacked over the company sheet - After save, the form chains to a yacht pull-in prompt (if any attached client owns yachts not yet linked) and an optional "Create interest" step pre-filled with the first attached client Admin - /admin landing gains a searchable index — typed query flattens groups into a result list matching label + description + group title - "Documenso & EOI" card relabelled to "EOI signing service" (consistent with the user-facing language rename from round 1) Measurement units (migration 0053) - interests gains desired_*_m columns + desired_*_unit discriminators so the rep's literal entry (ft OR m) is preserved verbatim instead of being reconstructed from a single canonical column on every render - yachts + berths gain matching *_unit columns alongside their existing ft + m pairs; defaults to 'ft' so legacy rows still render normally - Interest form POST/PATCH now sends both ft + m + unit; computed m is derived from the ft canonical to keep the recommender SQL unchanged Misc - Active-deals tile + topbar type their Link href as `Route` instead of `any` - Unused REPORT_TYPE_LABELS const dropped from generate-report-form - Test fixtures (fill-eoi-form, documenso-payload, public-berths) updated to include the new id + unit fields on the EoiContext / Berth shapes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:28:22 +02:00
{ id: 'latestInterestStage', label: 'Latest deal stage' },
{ id: 'activeInterestCount', label: 'Active interests' },
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
{ id: 'sidePontoon', label: 'Side / Pontoon' },
{ id: 'dimensions', label: 'Dimensions' },
{ id: 'nominalBoatSize', label: 'Nominal boat size' },
{ id: 'waterDepth', label: 'Water depth' },
{ id: 'mooringType', label: 'Mooring type' },
{ id: 'cleat', label: 'Cleat (type · capacity)' },
{ id: 'bollard', label: 'Bollard (type · capacity)' },
{ id: 'access', label: 'Access' },
{ id: 'bowFacing', label: 'Bow facing' },
{ id: 'berthApproved', label: 'Approved' },
{ id: 'power', label: 'Power (kW · V)' },
{ id: 'price', label: 'Price' },
{ id: 'rates', label: 'Daily / Weekly rates' },
{ id: 'pricingValidUntil', label: 'Pricing valid until' },
{ id: 'tenure', label: 'Tenure' },
{ id: 'tags', label: 'Tags' },
];
/** Hidden by default - power-users turn them on via the picker. */
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
export const BERTH_DEFAULT_HIDDEN: string[] = [
'tenure',
'sidePontoon',
'nominalBoatSize',
'waterDepth',
'mooringType',
'cleat',
'bollard',
'access',
'bowFacing',
'berthApproved',
'power',
'rates',
'pricingValidUntil',
];
const BERTH_STATUS_PILL: Record<string, StatusPillStatus> = {
available: 'available',
under_offer: 'under_offer',
sold: 'sold',
};
const BERTH_STATUS_LABELS: Record<string, string> = {
available: 'Available',
under_offer: 'Under Offer',
sold: 'Sold',
};
function StatusBadge({ status }: { status: string }) {
return (
<StatusPill status={BERTH_STATUS_PILL[status] ?? 'pending'}>
{BERTH_STATUS_LABELS[status] ?? status}
</StatusPill>
);
}
feat(berths): manual status catch-up wizard + reconciliation queue (#67) Wires the long-dormant berths.status_override_mode column into a closed loop so reps can reconcile berths flipped to under_offer/sold without a backing interest. Phase 1 — Status source tracking: - updateBerthStatus() stamps 'manual' on every user-facing write - berth-rules-engine.ts stamps 'automated' on auto-rule writes - new clearBerthOverride() helper nulls the field and stamps the reason "Reconciled via interest <id>" — only the wizard calls it Phase 2 — Visual indicator: - Amber "Manual" chip on berth-list rows where statusOverrideMode='manual' AND no active linked interest (the candidates for catch-up) Phase 3 — Reconciliation queue: - new service listManualReconcileBerths() with cross-port-safe NOT-EXISTS against activeInterestsWhere - GET /api/v1/berths/reconcile-queue - new page /[portSlug]/admin/berths/reconcile listing the queue, each row linking to the catch-up wizard Phase 4 — Catch-up wizard: - POST /api/v1/berths/[id]/reconcile orchestrates create-client (optional quick-create), create-interest with primary berth link, and clearBerthOverride — composed via existing service helpers - <CatchUpWizard> dialog: existing-client or quick-create, optional yacht link, stage picker scoped to the current berth status, with contract auto-setting outcome=won Phase 5 — Entry points: - sidebar Admin > "Reconcile berths" link - berth-list row action menu shows "Catch up…" on flagged rows Doc upload + payment recording (spec phases 4.4 / 4.5) are deferred — once the interest exists, the rep uses the standard interest detail page surfaces for those follow-ups. The wizard's MVP responsibility is to take a manual berth to "interest exists, override cleared" in one round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:55:22 +02:00
/**
* #67 Phase 2: small amber chip beside the status pill flagging rows
* whose status was set manually and has no backing interest. These are
* the candidates for the catch-up wizard - the rep flipped a berth to
feat(berths): manual status catch-up wizard + reconciliation queue (#67) Wires the long-dormant berths.status_override_mode column into a closed loop so reps can reconcile berths flipped to under_offer/sold without a backing interest. Phase 1 — Status source tracking: - updateBerthStatus() stamps 'manual' on every user-facing write - berth-rules-engine.ts stamps 'automated' on auto-rule writes - new clearBerthOverride() helper nulls the field and stamps the reason "Reconciled via interest <id>" — only the wizard calls it Phase 2 — Visual indicator: - Amber "Manual" chip on berth-list rows where statusOverrideMode='manual' AND no active linked interest (the candidates for catch-up) Phase 3 — Reconciliation queue: - new service listManualReconcileBerths() with cross-port-safe NOT-EXISTS against activeInterestsWhere - GET /api/v1/berths/reconcile-queue - new page /[portSlug]/admin/berths/reconcile listing the queue, each row linking to the catch-up wizard Phase 4 — Catch-up wizard: - POST /api/v1/berths/[id]/reconcile orchestrates create-client (optional quick-create), create-interest with primary berth link, and clearBerthOverride — composed via existing service helpers - <CatchUpWizard> dialog: existing-client or quick-create, optional yacht link, stage picker scoped to the current berth status, with contract auto-setting outcome=won Phase 5 — Entry points: - sidebar Admin > "Reconcile berths" link - berth-list row action menu shows "Catch up…" on flagged rows Doc upload + payment recording (spec phases 4.4 / 4.5) are deferred — once the interest exists, the rep uses the standard interest detail page surfaces for those follow-ups. The wizard's MVP responsibility is to take a manual berth to "interest exists, override cleared" in one round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:55:22 +02:00
* "Under Offer" or "Sold" without ever creating the matching deal.
*/
function ManualBadge() {
return (
<span
chore(audit-drain): rip out next-intl, RTL lint, sweeps, polish Drain the long-tail audit queue captured in alpha-uat-master.md. - next-intl ripped out (zero useTranslations callers ever existed): package.json, next.config.ts plugin wrap, src/i18n/, messages/, and the layout NextIntlClientProvider all gone; <html lang="en"> hardcoded. - RTL lint nudge added: warn-only no-restricted-syntax on physical Tailwind utilities (ml-/mr-/pl-/pr-/text-left/text-right/border-l/ border-r/rounded-l-/rounded-r-) inside JSX className literals. Existing ~1,000 sites grandfathered; new code trends toward logical. - Icon-only button accessibility lint: jsx-a11y/control-has-associated- label enabled at warn; 4 empty <th>/<td> action placeholders gain sr-only labels. - Currency: SUPPORTED_CURRENCIES drops the hardcoded English labels; new currencyLabel(code, locale?) helper resolves via Intl.DisplayNames. CurrencySelect + settings-manager migrated. - Date locale sweep: 7 surfaces flip from toLocaleString('en-GB'|'en-US') to toLocaleString(undefined, ...) so dates honour runtime locale. - Dialog/Sheet width: 10 document/EOI/entity-form dialogs gain a lg:max-w-4xl or lg:max-w-5xl step so wide desktops get breathing room. - PaymentsSection collapsed-bar: slim one-line bar showing "Payments - Not received yet" or "Payments - \$X received - N payments - Expand"; per-interest collapse state persists in localStorage; the RecordPayment flow auto-expands. - muted-foreground opacity sweep: 10 text-bearing text-muted-foreground/{60,70,80} hits dropped to plain text-muted-foreground for AA contrast on muted bg. Icon-only (aria-hidden) opacity hits left as-is. - Micro-type bump: text-[10px] and text-[11px] -> text-xs (12px) across 87 files in src/components + src/app. Pure mechanical sweep. - Audit-doc cleanup: alpha-uat-master.md stale 2026-05-25 summary rewritten with cumulative state through today. Items genuinely still open are now a short long-tail list. - New docs/marketing-site-followups.md: Umami Phase 4a/3/5, email pixel E2E verification, and website-cutover work parked here so they don't get lost in the CRM audit doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:48:46 +02:00
className="inline-flex items-center rounded-full border border-amber-300 bg-amber-50 px-1.5 py-0.5 text-xs font-medium uppercase tracking-wide text-amber-800"
title="Status set manually with no backing interest - needs catch-up"
feat(berths): manual status catch-up wizard + reconciliation queue (#67) Wires the long-dormant berths.status_override_mode column into a closed loop so reps can reconcile berths flipped to under_offer/sold without a backing interest. Phase 1 — Status source tracking: - updateBerthStatus() stamps 'manual' on every user-facing write - berth-rules-engine.ts stamps 'automated' on auto-rule writes - new clearBerthOverride() helper nulls the field and stamps the reason "Reconciled via interest <id>" — only the wizard calls it Phase 2 — Visual indicator: - Amber "Manual" chip on berth-list rows where statusOverrideMode='manual' AND no active linked interest (the candidates for catch-up) Phase 3 — Reconciliation queue: - new service listManualReconcileBerths() with cross-port-safe NOT-EXISTS against activeInterestsWhere - GET /api/v1/berths/reconcile-queue - new page /[portSlug]/admin/berths/reconcile listing the queue, each row linking to the catch-up wizard Phase 4 — Catch-up wizard: - POST /api/v1/berths/[id]/reconcile orchestrates create-client (optional quick-create), create-interest with primary berth link, and clearBerthOverride — composed via existing service helpers - <CatchUpWizard> dialog: existing-client or quick-create, optional yacht link, stage picker scoped to the current berth status, with contract auto-setting outcome=won Phase 5 — Entry points: - sidebar Admin > "Reconcile berths" link - berth-list row action menu shows "Catch up…" on flagged rows Doc upload + payment recording (spec phases 4.4 / 4.5) are deferred — once the interest exists, the rep uses the standard interest detail page surfaces for those follow-ups. The wizard's MVP responsibility is to take a manual berth to "interest exists, override cleared" in one round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:55:22 +02:00
>
Manual
</span>
);
}
function ActionsCell({ row }: { row: { original: BerthRow } }) {
const router = useRouter();
const params = useParams<{ portSlug: string }>();
const berth = row.original;
feat(berths): manual status catch-up wizard + reconciliation queue (#67) Wires the long-dormant berths.status_override_mode column into a closed loop so reps can reconcile berths flipped to under_offer/sold without a backing interest. Phase 1 — Status source tracking: - updateBerthStatus() stamps 'manual' on every user-facing write - berth-rules-engine.ts stamps 'automated' on auto-rule writes - new clearBerthOverride() helper nulls the field and stamps the reason "Reconciled via interest <id>" — only the wizard calls it Phase 2 — Visual indicator: - Amber "Manual" chip on berth-list rows where statusOverrideMode='manual' AND no active linked interest (the candidates for catch-up) Phase 3 — Reconciliation queue: - new service listManualReconcileBerths() with cross-port-safe NOT-EXISTS against activeInterestsWhere - GET /api/v1/berths/reconcile-queue - new page /[portSlug]/admin/berths/reconcile listing the queue, each row linking to the catch-up wizard Phase 4 — Catch-up wizard: - POST /api/v1/berths/[id]/reconcile orchestrates create-client (optional quick-create), create-interest with primary berth link, and clearBerthOverride — composed via existing service helpers - <CatchUpWizard> dialog: existing-client or quick-create, optional yacht link, stage picker scoped to the current berth status, with contract auto-setting outcome=won Phase 5 — Entry points: - sidebar Admin > "Reconcile berths" link - berth-list row action menu shows "Catch up…" on flagged rows Doc upload + payment recording (spec phases 4.4 / 4.5) are deferred — once the interest exists, the rep uses the standard interest detail page surfaces for those follow-ups. The wizard's MVP responsibility is to take a manual berth to "interest exists, override cleared" in one round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:55:22 +02:00
const [catchUpOpen, setCatchUpOpen] = useState(false);
const isManualUnreconciled = berth.statusOverrideMode === 'manual' && !berth.latestInterestStage;
return (
feat(berths): manual status catch-up wizard + reconciliation queue (#67) Wires the long-dormant berths.status_override_mode column into a closed loop so reps can reconcile berths flipped to under_offer/sold without a backing interest. Phase 1 — Status source tracking: - updateBerthStatus() stamps 'manual' on every user-facing write - berth-rules-engine.ts stamps 'automated' on auto-rule writes - new clearBerthOverride() helper nulls the field and stamps the reason "Reconciled via interest <id>" — only the wizard calls it Phase 2 — Visual indicator: - Amber "Manual" chip on berth-list rows where statusOverrideMode='manual' AND no active linked interest (the candidates for catch-up) Phase 3 — Reconciliation queue: - new service listManualReconcileBerths() with cross-port-safe NOT-EXISTS against activeInterestsWhere - GET /api/v1/berths/reconcile-queue - new page /[portSlug]/admin/berths/reconcile listing the queue, each row linking to the catch-up wizard Phase 4 — Catch-up wizard: - POST /api/v1/berths/[id]/reconcile orchestrates create-client (optional quick-create), create-interest with primary berth link, and clearBerthOverride — composed via existing service helpers - <CatchUpWizard> dialog: existing-client or quick-create, optional yacht link, stage picker scoped to the current berth status, with contract auto-setting outcome=won Phase 5 — Entry points: - sidebar Admin > "Reconcile berths" link - berth-list row action menu shows "Catch up…" on flagged rows Doc upload + payment recording (spec phases 4.4 / 4.5) are deferred — once the interest exists, the rep uses the standard interest detail page surfaces for those follow-ups. The wizard's MVP responsibility is to take a manual berth to "interest exists, override cleared" in one round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:55:22 +02:00
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
aria-label={`Row actions for berth ${berth.mooringNumber ?? ''}`.trim()}
feat(berths): manual status catch-up wizard + reconciliation queue (#67) Wires the long-dormant berths.status_override_mode column into a closed loop so reps can reconcile berths flipped to under_offer/sold without a backing interest. Phase 1 — Status source tracking: - updateBerthStatus() stamps 'manual' on every user-facing write - berth-rules-engine.ts stamps 'automated' on auto-rule writes - new clearBerthOverride() helper nulls the field and stamps the reason "Reconciled via interest <id>" — only the wizard calls it Phase 2 — Visual indicator: - Amber "Manual" chip on berth-list rows where statusOverrideMode='manual' AND no active linked interest (the candidates for catch-up) Phase 3 — Reconciliation queue: - new service listManualReconcileBerths() with cross-port-safe NOT-EXISTS against activeInterestsWhere - GET /api/v1/berths/reconcile-queue - new page /[portSlug]/admin/berths/reconcile listing the queue, each row linking to the catch-up wizard Phase 4 — Catch-up wizard: - POST /api/v1/berths/[id]/reconcile orchestrates create-client (optional quick-create), create-interest with primary berth link, and clearBerthOverride — composed via existing service helpers - <CatchUpWizard> dialog: existing-client or quick-create, optional yacht link, stage picker scoped to the current berth status, with contract auto-setting outcome=won Phase 5 — Entry points: - sidebar Admin > "Reconcile berths" link - berth-list row action menu shows "Catch up…" on flagged rows Doc upload + payment recording (spec phases 4.4 / 4.5) are deferred — once the interest exists, the rep uses the standard interest detail page surfaces for those follow-ups. The wizard's MVP responsibility is to take a manual berth to "interest exists, override cleared" in one round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:55:22 +02:00
onClick={(e) => e.stopPropagation()}
>
<MoreHorizontal className="h-4 w-4" aria-hidden />
<span className="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
router.push(`/${params.portSlug}/berths/${berth.id}`);
}}
>
<Activity className="mr-2 h-4 w-4" aria-hidden />
View details
</DropdownMenuItem>
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
router.push(`/${params.portSlug}/berths/${berth.id}?edit=true`);
}}
>
<Pencil className="mr-2 h-4 w-4" aria-hidden />
Edit
</DropdownMenuItem>
{isManualUnreconciled ? (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
setCatchUpOpen(true);
}}
>
<RefreshCw className="mr-2 h-4 w-4" aria-hidden />
Catch up
</DropdownMenuItem>
) : null}
</DropdownMenuContent>
</DropdownMenu>
{isManualUnreconciled ? (
<CatchUpWizard
berthId={catchUpOpen ? berth.id : null}
open={catchUpOpen}
onOpenChange={setCatchUpOpen}
/>
) : null}
</>
);
}
function ActiveInterestsCell({ berthId, count }: { berthId: string; count: number }) {
const params = useParams<{ portSlug: string }>();
const portSlug = params?.portSlug ?? '';
return <ActiveInterestsPopover berthId={berthId} portSlug={portSlug} count={count} />;
}
/**
* Price column cell. Reps with the `berths.update_prices` permission get
* a click-to-edit inline field saves go through the focused price-only
* route so non-`edit` roles can retune pricing without unlocking the rest
* of the berth schema. Click stops bubbling so the row's navigate-to-
* detail handler doesn't fire while the rep is editing.
*/
function PriceCell({
berthId,
price,
currency,
}: {
berthId: string;
price: string | null;
currency: string;
}) {
const { can } = usePermissions();
const qc = useQueryClient();
const display = price ? (formatCurrency(price, currency, { maxFractionDigits: 0 }) ?? '-') : null;
const mutation = useMutation({
mutationFn: async (next: number | null) =>
apiFetch(`/api/v1/berths/${berthId}/price`, {
method: 'PATCH',
body: { price: next },
}),
onSuccess: () => {
void qc.invalidateQueries({ queryKey: ['berths'] });
},
});
if (!can('berths', 'update_prices')) {
return <span>{display ?? '-'}</span>;
}
return (
<div onClick={(e) => e.stopPropagation()} className="inline-flex">
<InlineEditableField
value={price ?? null}
displayValue={display}
emptyText="-"
placeholder="Enter price"
onSave={async (next) => {
const parsed = next === null || next.trim() === '' ? null : Number(next);
if (parsed !== null && (!Number.isFinite(parsed) || parsed < 0)) {
throw new Error('Price must be a positive number');
}
await mutation.mutateAsync(parsed);
}}
/>
</div>
);
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
}
function joinNonNull(parts: Array<string | null | undefined>, sep = ' · '): string {
return parts.filter((p): p is string => Boolean(p)).join(sep);
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
}
feat(uat-batch): Group C Berth list features (3 new ships + 1 verified) C20–C23 from the 2026-05-21 plan. Shipped now: C21 Dimensions ft/m column toggle persisted to user prefs. `TablePreferences.dimensionUnit` ('ft' | 'm') added to the user- profiles JSONB. `useTablePreferences` returns `dimensionUnit` + `setDimensionUnit` alongside hidden/density. New `getBerthColumns(unit)` factory rewrites the dimensions / nominalBoatSize / waterDepth cells when ft is requested (waterDepth converts on-the-fly from the canonical meters column at 3.2808 ft/m). Berth-list toolbar gains a small ft/m toggle button next to the density toggle. C22 ft/m switching on Berth Requirements rows. `interest-tabs.tsx` Berth-requirements section now honours `interest.desiredLengthUnit`. Labels flip to "(m)" when set; value reads from `desired*M` columns; on save, both the chosen- unit and the canonical counterpart columns are PATCHed (3.28084 ratio) so downstream surfaces (recommender, EOI merge fields) stay in lockstep. `InterestPatchField` widened with `desired*M` variants. C23 Berth list bulk-edit affordance. New `POST /api/v1/berths/bulk` (mirror of /interests/bulk): discriminated union of `change_status` / `change_tenure_type` / `add_tag` / `remove_tag` / `archive`, 500-id cap, per-row failure reporting, single `berths.edit` permission gate (no separate `archive` perm exists on berths today). Status mutations route through `updateBerthStatus` so under-offer / sold transitions still trigger the primary interest_berths auto-link + the rules-engine evaluation. BerthList toolbar wires `bulkActions` on the DataTable — Change status (Select dialog), Change tenure (permanent / fixed-term), Add tag, Remove tag, Archive (destructive + confirmation). Each dialog uses the same `bulkMutation` so toast + cache-invalidation behaviour is consistent across actions. Already shipped (verified): C20 Berth list rates / pricing valid columns hidden by default — already in `BERTH_DEFAULT_HIDDEN`. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:22:30 +02:00
/**
* Static column list rendered in metric units (the historical default).
* Most callers should use `getBerthColumns(unit)` instead, which lets the
* berth-list toolbar toggle render imperial when the rep prefers feet.
*/
export const berthColumns: ColumnDef<BerthRow, unknown>[] = [
{
accessorKey: 'mooringNumber',
header: 'Mooring #',
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
cell: ({ row }) => {
const dot = mooringLetterDot(row.original.mooringNumber);
return (
<span className="inline-flex items-center gap-2 font-medium">
{dot && <span className={`inline-block size-2 rounded-full ${dot}`} aria-hidden />}
{row.original.mooringNumber}
</span>
);
},
},
{
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
id: 'area',
accessorKey: 'area',
header: 'Area',
cell: ({ row }) => row.original.area ?? '-',
},
{
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
id: 'status',
accessorKey: 'status',
header: 'Status',
feat(berths): manual status catch-up wizard + reconciliation queue (#67) Wires the long-dormant berths.status_override_mode column into a closed loop so reps can reconcile berths flipped to under_offer/sold without a backing interest. Phase 1 — Status source tracking: - updateBerthStatus() stamps 'manual' on every user-facing write - berth-rules-engine.ts stamps 'automated' on auto-rule writes - new clearBerthOverride() helper nulls the field and stamps the reason "Reconciled via interest <id>" — only the wizard calls it Phase 2 — Visual indicator: - Amber "Manual" chip on berth-list rows where statusOverrideMode='manual' AND no active linked interest (the candidates for catch-up) Phase 3 — Reconciliation queue: - new service listManualReconcileBerths() with cross-port-safe NOT-EXISTS against activeInterestsWhere - GET /api/v1/berths/reconcile-queue - new page /[portSlug]/admin/berths/reconcile listing the queue, each row linking to the catch-up wizard Phase 4 — Catch-up wizard: - POST /api/v1/berths/[id]/reconcile orchestrates create-client (optional quick-create), create-interest with primary berth link, and clearBerthOverride — composed via existing service helpers - <CatchUpWizard> dialog: existing-client or quick-create, optional yacht link, stage picker scoped to the current berth status, with contract auto-setting outcome=won Phase 5 — Entry points: - sidebar Admin > "Reconcile berths" link - berth-list row action menu shows "Catch up…" on flagged rows Doc upload + payment recording (spec phases 4.4 / 4.5) are deferred — once the interest exists, the rep uses the standard interest detail page surfaces for those follow-ups. The wizard's MVP responsibility is to take a manual berth to "interest exists, override cleared" in one round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:55:22 +02:00
cell: ({ row }) => {
const r = row.original;
const isManualUnreconciled = r.statusOverrideMode === 'manual' && !r.latestInterestStage;
return (
<div className="inline-flex items-center gap-1.5">
<StatusBadge status={r.status} />
{isManualUnreconciled ? <ManualBadge /> : null}
</div>
);
},
},
feat: round 2 — stage prompts, berth header, EOI inline edit, measurement units Berth surfaces - New compact mooring-chip header (colored plate + status pill, dock-label in tooltip) replaces the redundant "Berth B1 / Sold / B DOCK" stack - Berth list gains a "Latest deal stage" column showing the most-advanced pipeline stage of any active linked interest (server-aggregated, ranks by PIPELINE_STAGES index) - "Linked prospect" Select on the status-change dialog rebuilt as a Command combobox: search, recent-first sort, stage-coloured pills Pipeline UX - Reverting an interest to Open with linked berths now prompts: keep the links, unlink and reset, or cancel. Silent when no berths are linked - Activity feed + entity-activity feed normalise enum field values via STAGE_LABELS / formatSource: "deposit_10pct → contract_sent" reads as "10% Deposit → Contract Sent" EOI generate dialog - Inline-editable rows for client name, nationality (country combobox), and yacht name — pencil affordance saves directly via clients/yachts PATCH - Replaces the single "Edit on client's page" link with two contextual links framed by short copy explaining what's inline vs what needs the canonical page - Backend EoiContext now includes client.id + yacht.id so the dialog can PATCH without an extra round-trip Company form - New "Connections" section lets the rep attach members (clients) and yachts during create. Yacht attach uses the existing transfer endpoint so audit log + ownership history capture the change - Inline "+ New client" / "+ New yacht" buttons open the canonical forms stacked over the company sheet - After save, the form chains to a yacht pull-in prompt (if any attached client owns yachts not yet linked) and an optional "Create interest" step pre-filled with the first attached client Admin - /admin landing gains a searchable index — typed query flattens groups into a result list matching label + description + group title - "Documenso & EOI" card relabelled to "EOI signing service" (consistent with the user-facing language rename from round 1) Measurement units (migration 0053) - interests gains desired_*_m columns + desired_*_unit discriminators so the rep's literal entry (ft OR m) is preserved verbatim instead of being reconstructed from a single canonical column on every render - yachts + berths gain matching *_unit columns alongside their existing ft + m pairs; defaults to 'ft' so legacy rows still render normally - Interest form POST/PATCH now sends both ft + m + unit; computed m is derived from the ft canonical to keep the recommender SQL unchanged Misc - Active-deals tile + topbar type their Link href as `Route` instead of `any` - Unused REPORT_TYPE_LABELS const dropped from generate-report-form - Test fixtures (fill-eoi-form, documenso-payload, public-berths) updated to include the new id + unit fields on the EoiContext / Berth shapes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:28:22 +02:00
{
id: 'latestInterestStage',
header: 'Latest deal stage',
enableSorting: true,
feat: round 2 — stage prompts, berth header, EOI inline edit, measurement units Berth surfaces - New compact mooring-chip header (colored plate + status pill, dock-label in tooltip) replaces the redundant "Berth B1 / Sold / B DOCK" stack - Berth list gains a "Latest deal stage" column showing the most-advanced pipeline stage of any active linked interest (server-aggregated, ranks by PIPELINE_STAGES index) - "Linked prospect" Select on the status-change dialog rebuilt as a Command combobox: search, recent-first sort, stage-coloured pills Pipeline UX - Reverting an interest to Open with linked berths now prompts: keep the links, unlink and reset, or cancel. Silent when no berths are linked - Activity feed + entity-activity feed normalise enum field values via STAGE_LABELS / formatSource: "deposit_10pct → contract_sent" reads as "10% Deposit → Contract Sent" EOI generate dialog - Inline-editable rows for client name, nationality (country combobox), and yacht name — pencil affordance saves directly via clients/yachts PATCH - Replaces the single "Edit on client's page" link with two contextual links framed by short copy explaining what's inline vs what needs the canonical page - Backend EoiContext now includes client.id + yacht.id so the dialog can PATCH without an extra round-trip Company form - New "Connections" section lets the rep attach members (clients) and yachts during create. Yacht attach uses the existing transfer endpoint so audit log + ownership history capture the change - Inline "+ New client" / "+ New yacht" buttons open the canonical forms stacked over the company sheet - After save, the form chains to a yacht pull-in prompt (if any attached client owns yachts not yet linked) and an optional "Create interest" step pre-filled with the first attached client Admin - /admin landing gains a searchable index — typed query flattens groups into a result list matching label + description + group title - "Documenso & EOI" card relabelled to "EOI signing service" (consistent with the user-facing language rename from round 1) Measurement units (migration 0053) - interests gains desired_*_m columns + desired_*_unit discriminators so the rep's literal entry (ft OR m) is preserved verbatim instead of being reconstructed from a single canonical column on every render - yachts + berths gain matching *_unit columns alongside their existing ft + m pairs; defaults to 'ft' so legacy rows still render normally - Interest form POST/PATCH now sends both ft + m + unit; computed m is derived from the ft canonical to keep the recommender SQL unchanged Misc - Active-deals tile + topbar type their Link href as `Route` instead of `any` - Unused REPORT_TYPE_LABELS const dropped from generate-report-form - Test fixtures (fill-eoi-form, documenso-payload, public-berths) updated to include the new id + unit fields on the EoiContext / Berth shapes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:28:22 +02:00
cell: ({ row }) => {
const s = row.original.latestInterestStage;
if (!s) return <span className="text-muted-foreground">-</span>;
return (
<span
className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${stageBadgeClass(s)}`}
>
{stageLabel(s)}
</span>
);
},
},
{
id: 'activeInterestCount',
accessorKey: 'activeInterestCount',
header: 'Active interests',
cell: ({ row }) => (
<ActiveInterestsCell
berthId={row.original.id}
count={row.original.activeInterestCount ?? 0}
/>
),
},
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
{
id: 'sidePontoon',
header: 'Side / Pontoon',
enableSorting: false,
cell: ({ row }) => row.original.sidePontoon ?? '-',
},
{
id: 'dimensions',
header: 'Dimensions',
enableSorting: false,
cell: ({ row }) => {
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
const { lengthM, widthM, draftM, widthIsMinimum } = row.original;
if (!lengthM && !widthM) return '-';
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
const widthLabel = widthM ? `${widthIsMinimum ? '≥' : ''}${widthM}m` : '?';
const base = `${lengthM ?? '?'}m × ${widthLabel}`;
return draftM ? `${base} (draft ${draftM}m)` : base;
},
},
{
id: 'nominalBoatSize',
header: 'Boat size',
enableSorting: false,
cell: ({ row }) => {
const m = row.original.nominalBoatSizeM;
const ft = row.original.nominalBoatSize;
if (!m && !ft) return '-';
return m ? `${m}m` : `${ft}ft`;
},
},
{
id: 'waterDepth',
header: 'Water depth',
enableSorting: false,
cell: ({ row }) => {
const { waterDepthM, waterDepthIsMinimum } = row.original;
if (!waterDepthM) return '-';
return `${waterDepthIsMinimum ? '≥' : ''}${waterDepthM}m`;
},
},
{
id: 'mooringType',
header: 'Mooring type',
enableSorting: false,
cell: ({ row }) => row.original.mooringType ?? '-',
},
{
id: 'cleat',
header: 'Cleat',
enableSorting: false,
cell: ({ row }) => joinNonNull([row.original.cleatType, row.original.cleatCapacity]) || '-',
},
{
id: 'bollard',
header: 'Bollard',
enableSorting: false,
cell: ({ row }) => joinNonNull([row.original.bollardType, row.original.bollardCapacity]) || '-',
},
{
id: 'access',
header: 'Access',
enableSorting: false,
cell: ({ row }) => row.original.access ?? '-',
},
{
id: 'bowFacing',
header: 'Bow facing',
enableSorting: false,
cell: ({ row }) => row.original.bowFacing ?? '-',
},
{
id: 'berthApproved',
header: 'Approved',
enableSorting: false,
cell: ({ row }) => (row.original.berthApproved ? 'Yes' : 'No'),
},
{
id: 'power',
header: 'Power',
enableSorting: false,
cell: ({ row }) => {
const kw = row.original.powerCapacity;
const v = row.original.voltage;
if (!kw && !v) return '-';
return joinNonNull([kw ? `${kw}kW` : null, v ? `${v}V` : null]);
},
},
{
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
id: 'price',
accessorKey: 'price',
header: 'Price',
cell: ({ row }) => (
<PriceCell
berthId={row.original.id}
price={row.original.price}
currency={row.original.priceCurrency}
/>
),
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
},
{
id: 'rates',
header: 'Rates (USD)',
enableSorting: false,
cell: ({ row }) => {
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
const { dailyRateLowUsd, dailyRateHighUsd, weeklyRateLowUsd, weeklyRateHighUsd } =
row.original;
const daily =
dailyRateLowUsd && dailyRateHighUsd
? `${dailyRateLowUsd}${dailyRateHighUsd}/d`
: dailyRateLowUsd
? `${dailyRateLowUsd}/d`
: null;
const weekly =
weeklyRateLowUsd && weeklyRateHighUsd
? `${weeklyRateLowUsd}${weeklyRateHighUsd}/wk`
: weeklyRateLowUsd
? `${weeklyRateLowUsd}/wk`
: null;
return joinNonNull([daily, weekly]) || '-';
},
},
{
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul Major interest workflow expansion driven by the rapid-fire UX session. EOI / Contract / Reservation tabs replace the generic Documents tab when the deal is at the relevant stage — workspace pattern with active-doc hero, signing progress, paper-signed upload, and history strip. Stage- conditional visibility wired through interest-tabs.tsx so the tab set shrinks/expands as the deal moves through the pipeline. Contact log: per-interaction structured log (channel/direction/summary/ optional follow-up reminder). New `interest_contact_log` table + service + tab UI (timeline with channel-coded icons + compose dialog). auto-creates a reminder when followUpAt is set. Berth Interest milestone: first milestone in the OverviewTab's pipeline strip, completes the moment any berth is linked via the junction. Drives the "have we captured what they want?" sanity check for general_interest leads before they move to EOI. Stage-conditional milestones: past phases collapse into a one-liner strip, current phase expands, future phases hide behind a "Show upcoming" toggle. Inline stage picker now defers reason capture to an override-confirm view (only required for illegal transitions, not the default flow). Notes blob → threaded: dropped `interests.notes` column entirely; the threaded `interest_notes` table is the single source of truth. Latest- note teaser on Overview links into the dedicated Notes tab. Polymorphic notes service gains aggregated client view (unions client + interest + yacht notes with source chips and group-by-source toggle). Berth interest list overhaul: - Configurable columns via ColumnPicker (18 toggleable, 5 default-on) - Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2) - Per-letter row tinting via colored left-border accent + dot in cell - Documents tab merged Files (single attachments section) Topbar improvements: - Always-visible back arrow on detail pages (path depth > 2) - Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can push their entity hierarchy (Clients › Mary Smith › Interest › B17) - Tighter spacing, softer separators, 160px crumb truncation DataTable upgrades: - Page-size selector with All option (validator cap raised to 1000) - getRowClassName slot for per-row styling (used by berth tinting) - Fixed Radix SelectItem crash on empty-string values via __any__ sentinel (was crashing every list page that opened a select filter) Interest list: - Configurable columns picker - Stage cell clickable into detail - TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons - Save view moved into ColumnPicker menu; Views button hidden when no views are saved - Pipeline kanban board endpoint at /api/v1/interests/board with minimal projection, 5000-row cap + truncated banner, filter pass-through Mobile chrome + sidebar collapse removed (always-expanded design choice). User management lists super-admins (was inner-joined on user_port_roles which excluded global super-admins). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:59:28 +02:00
id: 'pricingValidUntil',
header: 'Pricing valid',
enableSorting: false,
cell: ({ row }) => row.original.pricingValidUntil ?? '-',
},
{
id: 'tenure',
accessorKey: 'tenureType',
header: 'Tenure',
cell: ({ row }) => (row.original.tenureType === 'permanent' ? 'Permanent' : 'Fixed Term'),
},
{
id: 'tags',
header: 'Tags',
enableSorting: false,
cell: ({ row }) => {
const { tags } = row.original;
if (!tags || tags.length === 0) return null;
return (
<div className="flex flex-wrap gap-1">
{tags.slice(0, 3).map((tag) => (
<TagBadge key={tag.id} name={tag.name} color={tag.color} />
))}
{tags.length > 3 && (
<span className="text-xs text-muted-foreground">+{tags.length - 3}</span>
)}
</div>
);
},
},
{
id: 'actions',
header: '',
enableSorting: false,
size: 48,
cell: ({ row }) => <ActionsCell row={row} />,
},
];
feat(uat-batch): Group C Berth list features (3 new ships + 1 verified) C20–C23 from the 2026-05-21 plan. Shipped now: C21 Dimensions ft/m column toggle persisted to user prefs. `TablePreferences.dimensionUnit` ('ft' | 'm') added to the user- profiles JSONB. `useTablePreferences` returns `dimensionUnit` + `setDimensionUnit` alongside hidden/density. New `getBerthColumns(unit)` factory rewrites the dimensions / nominalBoatSize / waterDepth cells when ft is requested (waterDepth converts on-the-fly from the canonical meters column at 3.2808 ft/m). Berth-list toolbar gains a small ft/m toggle button next to the density toggle. C22 ft/m switching on Berth Requirements rows. `interest-tabs.tsx` Berth-requirements section now honours `interest.desiredLengthUnit`. Labels flip to "(m)" when set; value reads from `desired*M` columns; on save, both the chosen- unit and the canonical counterpart columns are PATCHed (3.28084 ratio) so downstream surfaces (recommender, EOI merge fields) stay in lockstep. `InterestPatchField` widened with `desired*M` variants. C23 Berth list bulk-edit affordance. New `POST /api/v1/berths/bulk` (mirror of /interests/bulk): discriminated union of `change_status` / `change_tenure_type` / `add_tag` / `remove_tag` / `archive`, 500-id cap, per-row failure reporting, single `berths.edit` permission gate (no separate `archive` perm exists on berths today). Status mutations route through `updateBerthStatus` so under-offer / sold transitions still trigger the primary interest_berths auto-link + the rules-engine evaluation. BerthList toolbar wires `bulkActions` on the DataTable — Change status (Select dialog), Change tenure (permanent / fixed-term), Add tag, Remove tag, Archive (destructive + confirmation). Each dialog uses the same `bulkMutation` so toast + cache-invalidation behaviour is consistent across actions. Already shipped (verified): C20 Berth list rates / pricing valid columns hidden by default — already in `BERTH_DEFAULT_HIDDEN`. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:22:30 +02:00
/**
* Returns a copy of `berthColumns` with the dimension-bearing cells
* rewritten to render in the requested unit. Used by `BerthList` so the
* column-header toggle can flip the rendering globally without each
* cell renderer reading a context.
*
* Imperial columns assume the canonical `*Ft` columns are populated
* (true by default - the import pipeline + bulk-add wizard write both,
feat(uat-batch): Group C Berth list features (3 new ships + 1 verified) C20–C23 from the 2026-05-21 plan. Shipped now: C21 Dimensions ft/m column toggle persisted to user prefs. `TablePreferences.dimensionUnit` ('ft' | 'm') added to the user- profiles JSONB. `useTablePreferences` returns `dimensionUnit` + `setDimensionUnit` alongside hidden/density. New `getBerthColumns(unit)` factory rewrites the dimensions / nominalBoatSize / waterDepth cells when ft is requested (waterDepth converts on-the-fly from the canonical meters column at 3.2808 ft/m). Berth-list toolbar gains a small ft/m toggle button next to the density toggle. C22 ft/m switching on Berth Requirements rows. `interest-tabs.tsx` Berth-requirements section now honours `interest.desiredLengthUnit`. Labels flip to "(m)" when set; value reads from `desired*M` columns; on save, both the chosen- unit and the canonical counterpart columns are PATCHed (3.28084 ratio) so downstream surfaces (recommender, EOI merge fields) stay in lockstep. `InterestPatchField` widened with `desired*M` variants. C23 Berth list bulk-edit affordance. New `POST /api/v1/berths/bulk` (mirror of /interests/bulk): discriminated union of `change_status` / `change_tenure_type` / `add_tag` / `remove_tag` / `archive`, 500-id cap, per-row failure reporting, single `berths.edit` permission gate (no separate `archive` perm exists on berths today). Status mutations route through `updateBerthStatus` so under-offer / sold transitions still trigger the primary interest_berths auto-link + the rules-engine evaluation. BerthList toolbar wires `bulkActions` on the DataTable — Change status (Select dialog), Change tenure (permanent / fixed-term), Add tag, Remove tag, Archive (destructive + confirmation). Each dialog uses the same `bulkMutation` so toast + cache-invalidation behaviour is consistent across actions. Already shipped (verified): C20 Berth list rates / pricing valid columns hidden by default — already in `BERTH_DEFAULT_HIDDEN`. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:22:30 +02:00
* and the inline editor in yacht-tabs.tsx auto-fills the counterpart).
* Rows with only the metric counterpart fall through to `?` for that
* dimension; the cell still renders so the rep sees what's set.
*/
export function getBerthColumns(unit: 'ft' | 'm'): ColumnDef<BerthRow, unknown>[] {
if (unit === 'm') return berthColumns;
return berthColumns.map((col) => {
if (col.id === 'dimensions') {
return {
...col,
cell: ({ row }) => {
const { lengthFt, widthFt, draftFt, widthIsMinimum } = row.original;
if (!lengthFt && !widthFt) return '-';
const widthLabel = widthFt ? `${widthIsMinimum ? '≥' : ''}${widthFt}ft` : '?';
const base = `${lengthFt ?? '?'}ft × ${widthLabel}`;
return draftFt ? `${base} (draft ${draftFt}ft)` : base;
},
};
}
if (col.id === 'nominalBoatSize') {
return {
...col,
cell: ({ row }) => {
const ft = row.original.nominalBoatSize;
const m = row.original.nominalBoatSizeM;
if (!ft && !m) return '-';
return ft ? `${ft}ft` : `${m}m`;
},
};
}
if (col.id === 'waterDepth') {
// Water depth lacks a stored `*Ft` column today; convert from meters
// on the fly when the rep prefers ft. 1m = 3.2808ft (canonical
// ratio used in yacht-dimensions.ts).
return {
...col,
cell: ({ row }) => {
const { waterDepthM, waterDepthIsMinimum } = row.original;
if (!waterDepthM) return '-';
const ft = Number(waterDepthM) * 3.2808;
return `${waterDepthIsMinimum ? '≥' : ''}${ft.toFixed(1)}ft`;
},
};
}
return col;
});
}