diff --git a/src/components/berths/berth-card.tsx b/src/components/berths/berth-card.tsx index c2701956..1da8437b 100644 --- a/src/components/berths/berth-card.tsx +++ b/src/components/berths/berth-card.tsx @@ -14,6 +14,7 @@ import { TagBadge } from '@/components/shared/tag-badge'; import { ListCard, ListCardAvatar, ListCardMeta } from '@/components/shared/list-card'; import { cn } from '@/lib/utils'; import type { BerthRow } from './berth-columns'; +import { mooringLetterDot } from './mooring-letter-tone'; const STATUS_VARIANTS: Record = { available: 'bg-green-100 text-green-800 border-green-200', @@ -27,12 +28,6 @@ const STATUS_LABELS: Record = { sold: 'Sold', }; -const ACCENT_CLASS: Record = { - available: 'bg-emerald-400', - under_offer: 'bg-amber-400', - sold: 'bg-slate-400', -}; - function formatPrice(price: string, currency: string): string { try { return new Intl.NumberFormat('en-US', { @@ -57,7 +52,9 @@ export function BerthCard({ berth }: BerthCardProps) { const statusLabel = STATUS_LABELS[berth.status] ?? berth.status; const statusColor = STATUS_VARIANTS[berth.status] ?? 'bg-muted text-muted-foreground border-muted'; - const accentClass = ACCENT_CLASS[berth.status] ?? 'bg-slate-300'; + // Accent stripe groups visually by dock (A-row, B-row, ...). Status is + // already conveyed by the pill below, so the stripe is dock-keyed. + const accentClass = mooringLetterDot(berth.mooringNumber) ?? 'bg-slate-300'; // Dimensions string let dimText: string | null = null; diff --git a/src/components/berths/berth-detail-header.tsx b/src/components/berths/berth-detail-header.tsx index 514aceab..5c9ccdb7 100644 --- a/src/components/berths/berth-detail-header.tsx +++ b/src/components/berths/berth-detail-header.tsx @@ -27,6 +27,7 @@ import { Textarea } from '@/components/ui/textarea'; import { DetailHeaderStrip } from '@/components/shared/detail-header-strip'; import { PermissionGate } from '@/components/shared/permission-gate'; import { BerthForm } from './berth-form'; +import { mooringLetterDot } from './mooring-letter-tone'; import { apiFetch } from '@/lib/api/client'; import { toastError } from '@/lib/api/toast-error'; import { updateBerthStatusSchema, type UpdateBerthStatusInput } from '@/lib/validators/berths'; @@ -193,7 +194,15 @@ export function BerthDetailHeader({ berth }: BerthDetailHeaderProps) { {STATUS_LABELS[berth.status] ?? berth.status} - {berth.area &&

{berth.area}

} + {berth.area && ( +
+ + {berth.area} Dock + +
+ )}
diff --git a/src/components/berths/berth-documents-tab.tsx b/src/components/berths/berth-documents-tab.tsx index af1facd2..e5c3d490 100644 --- a/src/components/berths/berth-documents-tab.tsx +++ b/src/components/berths/berth-documents-tab.tsx @@ -162,6 +162,12 @@ export function BerthDocumentsTab({ berthId }: { berthId: string }) { return (
+

+ Berth-spec PDF: the dimensional drawing or surveyor sheet for this slip. + Versioned so a misparse can be rolled back. For documents tied to a + prospect on this berth (EOI, contract, etc.), open the matching deal + in the Interests tab. +

Current PDF diff --git a/src/components/berths/berth-filters.tsx b/src/components/berths/berth-filters.tsx index 00a820f2..75aeca79 100644 --- a/src/components/berths/berth-filters.tsx +++ b/src/components/berths/berth-filters.tsx @@ -1,5 +1,5 @@ import type { FilterDefinition } from '@/components/shared/filter-bar'; -import { BERTH_STATUSES } from '@/lib/constants'; +import { BERTH_AREAS, BERTH_STATUSES, toSelectOptions } from '@/lib/constants'; export const berthFilterDefinitions: FilterDefinition[] = [ { @@ -19,9 +19,9 @@ export const berthFilterDefinitions: FilterDefinition[] = [ }, { key: 'area', - label: 'Area', - type: 'text', - placeholder: 'Filter by area...', + label: 'Dock', + type: 'select', + options: toSelectOptions(BERTH_AREAS), }, { key: 'tenureType', diff --git a/src/components/berths/berth-form.tsx b/src/components/berths/berth-form.tsx index 853a0868..59857174 100644 --- a/src/components/berths/berth-form.tsx +++ b/src/components/berths/berth-form.tsx @@ -25,6 +25,7 @@ import { toastError } from '@/lib/api/toast-error'; import { updateBerthSchema, type UpdateBerthInput } from '@/lib/validators/berths'; import { BERTH_AREAS, + BERTH_BOW_FACING_OPTIONS, BERTH_SIDE_PONTOON_OPTIONS, BERTH_MOORING_TYPES, BERTH_CLEAT_TYPES, @@ -211,7 +212,11 @@ export function BerthForm({ berth, open, onOpenChange }: BerthFormProps) {
- + setValue('bowFacing', v)} + options={BERTH_BOW_FACING_OPTIONS} + />
diff --git a/src/components/berths/berth-tabs.tsx b/src/components/berths/berth-tabs.tsx index c3fba85c..26693efd 100644 --- a/src/components/berths/berth-tabs.tsx +++ b/src/components/berths/berth-tabs.tsx @@ -8,6 +8,17 @@ import { EntityActivityFeed } from '@/components/shared/entity-activity-feed'; import { InlineEditableField } from '@/components/shared/inline-editable-field'; import { InlineTagEditor } from '@/components/shared/inline-tag-editor'; import { apiFetch } from '@/lib/api/client'; +import { + BERTH_ACCESS_OPTIONS, + BERTH_BOLLARD_CAPACITIES, + BERTH_BOLLARD_TYPES, + BERTH_BOW_FACING_OPTIONS, + BERTH_CLEAT_CAPACITIES, + BERTH_CLEAT_TYPES, + BERTH_MOORING_TYPES, + BERTH_SIDE_PONTOON_OPTIONS, + toSelectOptions, +} from '@/lib/constants'; import { BerthReservationsTab } from './berth-reservations-tab'; import { BerthInterestsTab } from './berth-interests-tab'; import { BerthInterestPulse } from './berth-interest-pulse'; @@ -96,6 +107,8 @@ function EditableSpec({ patch, numeric = false, suffix, + selectOptions, + linkedUnit, }: { label: string; value: string | null; @@ -103,28 +116,54 @@ function EditableSpec({ patch: ReturnType; numeric?: boolean; suffix?: string; + /** When provided, the inline editor uses a `` `{value,label}` objects. */ +export function toSelectOptions( + values: T, +): Array<{ value: T[number]; label: T[number] }> { + return values.map((v) => ({ value: v, label: v })); +} + // ─── Lead Categories ───────────────────────────────────────────────────────── export const LEAD_CATEGORIES = ['general_interest', 'specific_qualified', 'hot_lead'] as const;