chore(format): apply prettier auto-formatting

Pre-commit hook reformatted these files after the substantive commits.
No semantic changes — markdown table alignment, list indentation, and
emphasis style normalisation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-09 04:11:54 +02:00
parent aad514a3bd
commit 502455ac04
6 changed files with 165 additions and 161 deletions

View File

@@ -15,20 +15,20 @@ message order where possible.
## Quick status snapshot — 2026-05-08 23:00 ## Quick status snapshot — 2026-05-08 23:00
| Wave | Topic | Status | | Wave | Topic | Status |
| --- | --- | --- | | --------- | ------------------------------------------ | -------------------------------------------------------------------------- |
| 1 | Small confident fixes | ✅ Done | | 1 | Small confident fixes | ✅ Done |
| 2 | Country dropdown unification + cmdk scroll | ✅ Done (country/nationality split deferred) | | 2 | Country dropdown unification + cmdk scroll | ✅ Done (country/nationality split deferred) |
| 3 | Berth field overhaul (NocoDB enums) | ✅ Done | | 3 | Berth field overhaul (NocoDB enums) | ✅ Done |
| 4 | Currency platform-wide | 🔴 Not started | | 4 | Currency platform-wide | 🔴 Not started |
| 5 | Configurable enums (admin Vocabularies) | 🔴 Not started | | 5 | Configurable enums (admin Vocabularies) | 🔴 Not started |
| 6 | Notes unification (aggregate-on-read) | 🔴 Not started | | 6 | Notes unification (aggregate-on-read) | 🔴 Not started |
| 7 | Clients / yachts / companies misc | 🟡 Partial (small bits done; large items deferred) | | 7 | Clients / yachts / companies misc | 🟡 Partial (small bits done; large items deferred) |
| 8 | Expenses revisit | 🔴 Not started | | 8 | Expenses revisit | 🔴 Not started |
| 9 | Interests + notifications | ✅ Done | | 9 | Interests + notifications | ✅ Done |
| 10 | Settings polish | ✅ Done (small bits) — schema-blocked items deferred | | 10 | Settings polish | ✅ Done (small bits) — schema-blocked items deferred |
| 11 | DEFERRED — group-discussion items | 🟣 Awaiting alignment | | 11 | DEFERRED — group-discussion items | 🟣 Awaiting alignment |
| **Bonus** | **Public berth feed (website map)** | ✅ Map data backfilled (117 rows). Field-parity gaps remain — see § Bonus. | | **Bonus** | **Public berth feed (website map)** | ✅ Map data backfilled (117 rows). Field-parity gaps remain — see § Bonus. |
Test status: `pnpm exec vitest run`**1185/1185 pass**. Test status: `pnpm exec vitest run`**1185/1185 pass**.
TS check: `pnpm exec tsc --noEmit`**clean**. TS check: `pnpm exec tsc --noEmit`**clean**.
@@ -83,7 +83,7 @@ Git: 23 files modified, 2 new files (no commits yet).
below (pipeline funnel, occupancy timeline, revenue breakdown, below (pipeline funnel, occupancy timeline, revenue breakdown,
lead source) and the activity feed remain. lead source) and the activity feed remain.
File: `src/components/dashboard/dashboard-shell.tsx`. File: `src/components/dashboard/dashboard-shell.tsx`.
3. **Per-dock color stripe on mobile berth cards** — was the *status* 3. **Per-dock color stripe on mobile berth cards** — was the _status_
color, which made every same-dock berth different. Now uses color, which made every same-dock berth different. Now uses
`mooringLetterDot()` so the stripe groups by dock letter; status `mooringLetterDot()` so the stripe groups by dock letter; status
conveyed by the existing pill below. conveyed by the existing pill below.
@@ -114,20 +114,20 @@ Git: 23 files modified, 2 new files (no commits yet).
Files: `src/components/layout/sidebar.tsx`, Files: `src/components/layout/sidebar.tsx`,
`src/components/layout/mobile/more-sheet.tsx`. `src/components/layout/mobile/more-sheet.tsx`.
10. **"Other" comm-channel UX hint** — when a contact's channel is 10. **"Other" comm-channel UX hint** — when a contact's channel is
`'other'`, the inline `Label` field switches its label/placeholder `'other'`, the inline `Label` field switches its label/placeholder
to "Specify" / "e.g. Telegram, Signal". to "Specify" / "e.g. Telegram, Signal".
File: `src/components/clients/client-form.tsx:289-302`. File: `src/components/clients/client-form.tsx:289-302`.
11. **End Membership wording** — renamed to "Remove from company" in 11. **End Membership wording** — renamed to "Remove from company" in
the company members tab dropdown. the company members tab dropdown.
File: `src/components/companies/company-members-tab.tsx:249`. File: `src/components/companies/company-members-tab.tsx:249`.
12. **Berth area filter → letter dropdown** — was free-text; now a 12. **Berth area filter → letter dropdown** — was free-text; now a
`<Select>` constrained to `A / B / C / D / E`. Label changed to `<Select>` constrained to `A / B / C / D / E`. Label changed to
"Dock" to match how the user refers to it. "Dock" to match how the user refers to it.
File: `src/components/berths/berth-filters.tsx`. File: `src/components/berths/berth-filters.tsx`.
13. **Yacht flag → CountryCombobox** — was a free-text 2-letter input 13. **Yacht flag → CountryCombobox** — was a free-text 2-letter input
(`placeholder="e.g. MT"`); now uses the same country picker as (`placeholder="e.g. MT"`); now uses the same country picker as
client / residential. client / residential.
File: `src/components/yachts/yacht-form.tsx`. File: `src/components/yachts/yacht-form.tsx`.
### Wave 2 — country dropdown unification ### Wave 2 — country dropdown unification
@@ -149,7 +149,7 @@ Git: 23 files modified, 2 new files (no commits yet).
floors so wide triggers get wide popovers. floors so wide triggers get wide popovers.
6. **DEFERRED: country/nationality split** on the client form — needs 6. **DEFERRED: country/nationality split** on the client form — needs
a Drizzle migration (`alter table clients add column country_iso a Drizzle migration (`alter table clients add column country_iso
text`) plus a copy-on-migrate of existing `nationality_iso` values. text`) plus a copy-on-migrate of existing `nationality_iso` values.
See § Wave 11 / pending — large. See § Wave 11 / pending — large.
### Wave 3 — berth field overhaul (NocoDB enums) ### Wave 3 — berth field overhaul (NocoDB enums)
@@ -210,6 +210,7 @@ Triggered by user prompt "ensure we are properly wired up to replace
the NocoDB table as the source of truth for the berth map". the NocoDB table as the source of truth for the berth map".
**State before audit:** **State before audit:**
- API endpoints existed (`/api/public/berths`, - API endpoints existed (`/api/public/berths`,
`/api/public/berths/[mooringNumber]`) — wiring fine. `/api/public/berths/[mooringNumber]`) — wiring fine.
- `src/lib/services/public-berths.ts` mapped the response shape to - `src/lib/services/public-berths.ts` mapped the response shape to
@@ -220,8 +221,9 @@ the NocoDB table as the source of truth for the berth map".
has no shapes to render. has no shapes to render.
**Action taken:** **Action taken:**
- Ran `pnpm tsx scripts/import-berths-from-nocodb.ts --apply - Ran `pnpm tsx scripts/import-berths-from-nocodb.ts --apply
--port-slug port-nimara` (after a clean dry-run). Result: --port-slug port-nimara` (after a clean dry-run). Result:
117 berths updated, 117 `berth_map_data` rows inserted. 117 berths updated, 117 `berth_map_data` rows inserted.
- Spot-checked the public API: `GET /api/public/berths` returns the - Spot-checked the public API: `GET /api/public/berths` returns the
correct shape with `Map Data` populated, byte-for-byte identical correct shape with `Map Data` populated, byte-for-byte identical
@@ -288,14 +290,16 @@ User chose option 1 ("aggregate on read") from the brainstorm. The
yacht notes into a single feed with `source` metadata. yacht notes into a single feed with `source` metadata.
Symmetric extensions to add: Symmetric extensions to add:
- `listForYachtAggregated` — yacht own notes + owner client notes - `listForYachtAggregated` — yacht own notes + owner client notes
+ linked interest notes. - linked interest notes.
- `listForCompanyAggregated` — company own notes + owned yacht notes - `listForCompanyAggregated` — company own notes + owned yacht notes
+ linked interest notes. - linked interest notes.
- `listForResidentialClientAggregated` — residential client notes - `listForResidentialClientAggregated` — residential client notes
+ residential interest notes. - residential interest notes.
UI: UI:
- `<NotesList entityType="…">` should render the source-label badge - `<NotesList entityType="…">` should render the source-label badge
(already implemented for clients — copy the pattern). (already implemented for clients — copy the pattern).
- Convert single-textarea spots to entry-list pattern: the - Convert single-textarea spots to entry-list pattern: the
@@ -310,11 +314,13 @@ UI:
### Wave 7: clients / yachts / companies misc ### Wave 7: clients / yachts / companies misc
Done in this session: Done in this session:
- **Yacht flag** → CountryCombobox (Wave 1). - **Yacht flag** → CountryCombobox (Wave 1).
- **End Membership** → "Remove from company" (Wave 1). - **End Membership** → "Remove from company" (Wave 1).
- **Berth Documents tab** explainer paragraph. - **Berth Documents tab** explainer paragraph.
Pending: Pending:
- **Status change modal — prospect picker**: when user changes berth - **Status change modal — prospect picker**: when user changes berth
status to `under_offer` or `sold`, surface an interest/prospect status to `under_offer` or `sold`, surface an interest/prospect
selector below the reason dropdown so the recorded reason can link selector below the reason dropdown so the recorded reason can link
@@ -338,8 +344,8 @@ Pending:
Chromium. User reported "doesn't open" on macOS — possibly a focus Chromium. User reported "doesn't open" on macOS — possibly a focus
/ window issue or a content-blocking extension. Need a real-machine / window issue or a content-blocking extension. Need a real-machine
repro to diagnose. The hidden `<input type="file" ref={fileInputRef}>` repro to diagnose. The hidden `<input type="file" ref={fileInputRef}>`
+ `fileInputRef.current?.click()` wiring is at - `fileInputRef.current?.click()` wiring is at
`user-settings.tsx:247-258`. `user-settings.tsx:247-258`.
- **Display name + first / last name fields** — current schema only - **Display name + first / last name fields** — current schema only
has `displayName`. Adding first/last requires a Drizzle migration on has `displayName`. Adding first/last requires a Drizzle migration on
`users` or `user_profiles` plus migration of existing data (split `users` or `user_profiles` plus migration of existing data (split
@@ -351,24 +357,24 @@ Pending:
### Wave Bonus follow-up — public berth feed field parity ### Wave Bonus follow-up — public berth feed field parity
Map data is now wired. Field gaps the website *might* consume but we Map data is now wired. Field gaps the website _might_ consume but we
don't expose: don't expose:
| NocoDB field | Currently in PublicBerth? | DB has it? | Notes | | NocoDB field | Currently in PublicBerth? | DB has it? | Notes |
| --- | --- | --- | --- | | ---------------------------- | ------------------------- | ---------------------------------- | ----------------------------------------------------------- |
| `Price` | ❌ | ✅ `berths.price` | Pricing-public is a policy decision. **Open question (#4)** | | `Price` | ❌ | ✅ `berths.price` | Pricing-public is a policy decision. **Open question (#4)** |
| `Berth Approved` | ❌ | ✅ `berths.berth_approved` | Boolean. Often used to gate "Sold" display | | `Berth Approved` | ❌ | ✅ `berths.berth_approved` | Boolean. Often used to gate "Sold" display |
| `Water Depth` | ❌ | ✅ `berths.water_depth` | Sometimes shown in tooltip | | `Water Depth` | ❌ | ✅ `berths.water_depth` | Sometimes shown in tooltip |
| `Width Is Minimum` | ❌ | ✅ `berths.width_is_minimum` | Modifier for "Width" display | | `Width Is Minimum` | ❌ | ✅ `berths.width_is_minimum` | Modifier for "Width" display |
| `Water Depth Is Minimum` | ❌ | ✅ `berths.water_depth_is_minimum` | ditto | | `Water Depth Is Minimum` | ❌ | ✅ `berths.water_depth_is_minimum` | ditto |
| `Length (Metric)` | ❌ | ✅ `berths.length_m` | Derivable. Website may consume | | `Length (Metric)` | ❌ | ✅ `berths.length_m` | Derivable. Website may consume |
| `Width (Metric)` | ❌ | ✅ `berths.width_m` | ditto | | `Width (Metric)` | ❌ | ✅ `berths.width_m` | ditto |
| `Draft (Metric)` | ❌ | ✅ `berths.draft_m` | ditto | | `Draft (Metric)` | ❌ | ✅ `berths.draft_m` | ditto |
| `Water Depth (Metric)` | ❌ | ✅ `berths.water_depth_m` | ditto | | `Water Depth (Metric)` | ❌ | ✅ `berths.water_depth_m` | ditto |
| `Nominal Boat Size (Metric)` | ❌ | ✅ `berths.nominal_boat_size_m` | ditto | | `Nominal Boat Size (Metric)` | ❌ | ✅ `berths.nominal_boat_size_m` | ditto |
| `CreatedAt` / `UpdatedAt` | ❌ | ✅ timestamps | Cache invalidation hints | | `CreatedAt` / `UpdatedAt` | ❌ | ✅ timestamps | Cache invalidation hints |
| `Interests` (count) | ❌ | derivable | Probably internal-only | | `Interests` (count) | ❌ | derivable | Probably internal-only |
| `Interested Parties` (count) | ❌ | derivable | Probably internal-only | | `Interested Parties` (count) | ❌ | derivable | Probably internal-only |
**Plan once questions are answered:** Add the chosen fields to **Plan once questions are answered:** Add the chosen fields to
`PublicBerth` interface in `src/lib/services/public-berths.ts`, the `PublicBerth` interface in `src/lib/services/public-berths.ts`, the
@@ -376,6 +382,7 @@ don't expose:
by which fields the website actually uses. by which fields the website actually uses.
**Other public-feed concerns to flag**: **Other public-feed concerns to flag**:
- **No archive flag**: when a berth is retired the public feed will - **No archive flag**: when a berth is retired the public feed will
still serve it. Need a `berths.archived_at` column + filter on the still serve it. Need a `berths.archived_at` column + filter on the
route. Plan §4.5 hinted at this. Not urgent. route. Plan §4.5 hinted at this. Not urgent.
@@ -404,6 +411,7 @@ berths inline (without leaving the form), plus a mini-recommender for
picking a berth at create time. picking a berth at create time.
Scope: Scope:
- "Existing yacht / new yacht" picker. - "Existing yacht / new yacht" picker.
- "Existing company / new company" picker. - "Existing company / new company" picker.
- "Open an interest with this client" affordance that wires through - "Open an interest with this client" affordance that wires through
@@ -417,6 +425,7 @@ Estimate fully before starting (likely 23 days).
### B. Documents section overhaul ### B. Documents section overhaul
User wants: User wants:
- Folders (create / delete / nested). - Folders (create / delete / nested).
- Sort + filter (by date, type, owner). - Sort + filter (by date, type, owner).
- Wider file-type allowlist (PDF + Office + image is current; expand). - Wider file-type allowlist (PDF + Office + image is current; expand).
@@ -433,6 +442,7 @@ Refactor of `documents.service.ts` plus a new folders schema
### C. Reports system ### C. Reports system
User asked for: User asked for:
- Defined report types (Pipeline summary / Revenue / Activity log / - Defined report types (Pipeline summary / Revenue / Activity log /
Berth occupancy) with documented data shape per type. Berth occupancy) with documented data shape per type.
- Test fixtures for visual QA. - Test fixtures for visual QA.
@@ -453,11 +463,13 @@ PDF pages via `pdf-lib.copyPages`. Depends on Wave 8 expense form work.
### E. Country / Nationality split on Client form ### E. Country / Nationality split on Client form
Client schema has only `nationalityIso`. User wants: Client schema has only `nationalityIso`. User wants:
- New `country_iso` column for *country of residence* (visible
- New `country_iso` column for _country of residence_ (visible
/ primary). / primary).
- Keep `nationality_iso` as an *optional* secondary field. - Keep `nationality_iso` as an _optional_ secondary field.
Requires: Requires:
- Drizzle migration (`alter table clients add column country_iso text`). - Drizzle migration (`alter table clients add column country_iso text`).
- Migrate existing data: copy `nationality_iso → country_iso` for - Migrate existing data: copy `nationality_iso → country_iso` for
every client (current value is more often country of residence in every client (current value is more often country of residence in
@@ -494,7 +506,7 @@ implementation. They're ordered by what unblocks the most work.
**Recommendation: one new page, grouped by domain.** Easier to **Recommendation: one new page, grouped by domain.** Easier to
discover, cleaner schema for the editor. discover, cleaner schema for the editor.
2. **Notification preferences placement (Wave 10)** — keep both the 2. **Notification preferences placement (Wave 10)** — keep both the
user-settings notifications panel *and* `/notifications/preferences`, user-settings notifications panel _and_ `/notifications/preferences`,
or collapse to one? **Recommendation: collapse to user-settings or collapse to one? **Recommendation: collapse to user-settings
only**, since that's where every other personal preference lives. only**, since that's where every other personal preference lives.
Keep the dedicated route as a redirect for back-compat links. Keep the dedicated route as a redirect for back-compat links.
@@ -543,7 +555,7 @@ implementation. They're ordered by what unblocks the most work.
every berth (NocoDB does). Confirmation that the live import we every berth (NocoDB does). Confirmation that the live import we
ran today populated mooring_type for all 117 records. (Spot- ran today populated mooring_type for all 117 records. (Spot-
checked: yes — the SQL traces show `mooring_type = "Side Pier / checked: yes — the SQL traces show `mooring_type = "Side Pier /
Med Mooring"` etc.) Med Mooring"` etc.)
--- ---
@@ -551,55 +563,55 @@ implementation. They're ordered by what unblocks the most work.
### Berth-related ### Berth-related
| Concern | File(s) | | Concern | File(s) |
| --- | --- | | ---------------------------------- | ---------------------------------------------------- |
| Canonical berth enums | `src/lib/constants.ts` (search `BERTH_`) | | Canonical berth enums | `src/lib/constants.ts` (search `BERTH_`) |
| Berth list ordering SQL | `src/lib/services/berths.service.ts:69-72` | | Berth list ordering SQL | `src/lib/services/berths.service.ts:69-72` |
| Berth detail inline edit | `src/components/berths/berth-tabs.tsx` | | Berth detail inline edit | `src/components/berths/berth-tabs.tsx` |
| Berth modal form | `src/components/berths/berth-form.tsx` | | Berth modal form | `src/components/berths/berth-form.tsx` |
| Berth area filter | `src/components/berths/berth-filters.tsx` | | Berth area filter | `src/components/berths/berth-filters.tsx` |
| Berth detail header / status modal | `src/components/berths/berth-detail-header.tsx:90` | | Berth detail header / status modal | `src/components/berths/berth-detail-header.tsx:90` |
| Berth Documents tab | `src/components/berths/berth-documents-tab.tsx` | | Berth Documents tab | `src/components/berths/berth-documents-tab.tsx` |
| Berth list query + sort | `src/lib/services/berths.service.ts:25-140` | | Berth list query + sort | `src/lib/services/berths.service.ts:25-140` |
| Berth import script | `scripts/import-berths-from-nocodb.ts` | | Berth import script | `scripts/import-berths-from-nocodb.ts` |
| Berth import service / parsers | `src/lib/services/berth-import.ts` | | Berth import service / parsers | `src/lib/services/berth-import.ts` |
| Public berth API route | `src/app/api/public/berths/route.ts` | | Public berth API route | `src/app/api/public/berths/route.ts` |
| Public berth single route | `src/app/api/public/berths/[mooringNumber]/route.ts` | | Public berth single route | `src/app/api/public/berths/[mooringNumber]/route.ts` |
| Public berth mapper | `src/lib/services/public-berths.ts` | | Public berth mapper | `src/lib/services/public-berths.ts` |
| Public berth tests | `tests/unit/services/public-berths.test.ts` | | Public berth tests | `tests/unit/services/public-berths.test.ts` |
| Berth seed snapshot | `src/lib/db/seed-data/berths.json` | | Berth seed snapshot | `src/lib/db/seed-data/berths.json` |
| Berth schema | `src/lib/db/schema/berths.ts` (incl. `berthMapData`) | | Berth schema | `src/lib/db/schema/berths.ts` (incl. `berthMapData`) |
### Other domains ### Other domains
| Concern | File(s) | | Concern | File(s) |
| --- | --- | | --------------------------------- | -------------------------------------------------------------------------------------- |
| Interest stage colors / legend | `src/components/interests/stage-legend.tsx` + `src/lib/constants.ts:STAGE_DOT` | | Interest stage colors / legend | `src/components/interests/stage-legend.tsx` + `src/lib/constants.ts:STAGE_DOT` |
| Mobile kanban toggle / fallback | `src/components/interests/interest-list.tsx` | | Mobile kanban toggle / fallback | `src/components/interests/interest-list.tsx` |
| Country / timezone autoset | `src/components/clients/client-form.tsx` + `src/components/settings/user-settings.tsx` | | Country / timezone autoset | `src/components/clients/client-form.tsx` + `src/components/settings/user-settings.tsx` |
| Phone input | `src/components/shared/phone-input.tsx` | | Phone input | `src/components/shared/phone-input.tsx` |
| Country combobox + scroll patch | `src/components/shared/country-combobox.tsx` + `src/components/ui/command.tsx` | | Country combobox + scroll patch | `src/components/shared/country-combobox.tsx` + `src/components/ui/command.tsx` |
| Sidebar Umami gate | `src/components/layout/sidebar.tsx` (search `umamiRequired`) | | Sidebar Umami gate | `src/components/layout/sidebar.tsx` (search `umamiRequired`) |
| Mobile More-sheet | `src/components/layout/mobile/more-sheet.tsx` | | Mobile More-sheet | `src/components/layout/mobile/more-sheet.tsx` |
| Notes service (aggregate-on-read) | `src/lib/services/notes.service.ts:130-242` | | Notes service (aggregate-on-read) | `src/lib/services/notes.service.ts:130-242` |
| Notes UI | `src/components/shared/notes-list.tsx` | | Notes UI | `src/components/shared/notes-list.tsx` |
| Settings manager (admin) | `src/components/admin/settings/settings-manager.tsx` | | Settings manager (admin) | `src/components/admin/settings/settings-manager.tsx` |
| User settings page | `src/components/settings/user-settings.tsx` | | User settings page | `src/components/settings/user-settings.tsx` |
| Status change dialog | `src/components/berths/berth-detail-header.tsx:90` | | Status change dialog | `src/components/berths/berth-detail-header.tsx:90` |
| Companies members tab | `src/components/companies/company-members-tab.tsx` | | Companies members tab | `src/components/companies/company-members-tab.tsx` |
| Yacht form | `src/components/yachts/yacht-form.tsx` | | Yacht form | `src/components/yachts/yacht-form.tsx` |
| Client form | `src/components/clients/client-form.tsx` | | Client form | `src/components/clients/client-form.tsx` |
### Infrastructure ### Infrastructure
| Concern | File(s) | | Concern | File(s) |
| --- | --- | | ------------------------------------------- | --------------------------------------------- |
| Drizzle config / migrations | `drizzle.config.ts`, `src/lib/db/migrations/` | | Drizzle config / migrations | `drizzle.config.ts`, `src/lib/db/migrations/` |
| `system_settings` table | `src/lib/db/schema/system.ts:128-147` | | `system_settings` table | `src/lib/db/schema/system.ts:128-147` |
| Permissions / `withAuth` / `withPermission` | `src/lib/api/helpers.ts` | | Permissions / `withAuth` / `withPermission` | `src/lib/api/helpers.ts` |
| Body parsing (always use `parseBody`) | `src/lib/api/route-helpers.ts` | | Body parsing (always use `parseBody`) | `src/lib/api/route-helpers.ts` |
| Storage backend abstraction | `src/lib/storage/` | | Storage backend abstraction | `src/lib/storage/` |
| Logger (pino) | `src/lib/logger.ts` | | Logger (pino) | `src/lib/logger.ts` |
--- ---

View File

@@ -163,10 +163,9 @@ export function BerthDocumentsTab({ berthId }: { berthId: string }) {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Berth-spec PDF: the dimensional drawing or surveyor sheet for this slip. Berth-spec PDF: the dimensional drawing or surveyor sheet for this slip. Versioned so a
Versioned so a misparse can be rolled back. For documents tied to a misparse can be rolled back. For documents tied to a prospect on this berth (EOI, contract,
prospect on this berth (EOI, contract, etc.), open the matching deal etc.), open the matching deal in the Interests tab.
in the Interests tab.
</p> </p>
<Card> <Card>
<CardHeader className="flex flex-row items-center justify-between pb-3"> <CardHeader className="flex flex-row items-center justify-between pb-3">

View File

@@ -245,7 +245,11 @@ function OverviewTab({ berth }: { berth: BerthData }) {
/> />
<SpecRow <SpecRow
label="Nominal Boat Size (m)" label="Nominal Boat Size (m)"
value={formatNominalBoatSize(berth.nominalBoatSize, berth.nominalBoatSizeM)?.split(' / ')[1] ?? null} value={
formatNominalBoatSize(berth.nominalBoatSize, berth.nominalBoatSizeM)?.split(
' / ',
)[1] ?? null
}
/> />
<EditableSpec <EditableSpec
label="Water Depth (ft)" label="Water Depth (ft)"

View File

@@ -4,12 +4,7 @@ import { Info } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { import { PIPELINE_STAGES, STAGE_LABELS, stageDotClass, type PipelineStage } from '@/lib/constants';
PIPELINE_STAGES,
STAGE_LABELS,
stageDotClass,
type PipelineStage,
} from '@/lib/constants';
/** /**
* Small popover that decodes the colored stripe on each interest card to * Small popover that decodes the colored stripe on each interest card to

View File

@@ -276,7 +276,11 @@ export function UserSettings() {
<Label htmlFor="settings-phone">Phone</Label> <Label htmlFor="settings-phone">Phone</Label>
<PhoneInput <PhoneInput
id="settings-phone" id="settings-phone"
value={phone ? ({ e164: phone, country: (country as never) ?? 'US' } as PhoneInputValue) : null} value={
phone
? ({ e164: phone, country: (country as never) ?? 'US' } as PhoneInputValue)
: null
}
onChange={(next) => setPhone(next.e164 ?? '')} onChange={(next) => setPhone(next.e164 ?? '')}
placeholder="555 0123" placeholder="555 0123"
/> />

View File

@@ -1,12 +1,12 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import { type DialogProps } from "@radix-ui/react-dialog" import { type DialogProps } from '@radix-ui/react-dialog';
import { Command as CommandPrimitive } from "cmdk" import { Command as CommandPrimitive } from 'cmdk';
import { Search } from "lucide-react" import { Search } from 'lucide-react';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
import { Dialog, DialogContent } from "@/components/ui/dialog" import { Dialog, DialogContent } from '@/components/ui/dialog';
const Command = React.forwardRef< const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>, React.ElementRef<typeof CommandPrimitive>,
@@ -15,13 +15,13 @@ const Command = React.forwardRef<
<CommandPrimitive <CommandPrimitive
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground", 'flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground',
className className,
)} )}
{...props} {...props}
/> />
)) ));
Command.displayName = CommandPrimitive.displayName Command.displayName = CommandPrimitive.displayName;
const CommandDialog = ({ children, ...props }: DialogProps) => { const CommandDialog = ({ children, ...props }: DialogProps) => {
return ( return (
@@ -32,8 +32,8 @@ const CommandDialog = ({ children, ...props }: DialogProps) => {
</Command> </Command>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
) );
} };
const CommandInput = React.forwardRef< const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>, React.ElementRef<typeof CommandPrimitive.Input>,
@@ -44,15 +44,15 @@ const CommandInput = React.forwardRef<
<CommandPrimitive.Input <CommandPrimitive.Input
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50", 'flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
className className,
)} )}
{...props} {...props}
/> />
</div> </div>
)) ));
CommandInput.displayName = CommandPrimitive.Input.displayName CommandInput.displayName = CommandPrimitive.Input.displayName;
const CommandList = React.forwardRef< const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>, React.ElementRef<typeof CommandPrimitive.List>,
@@ -64,30 +64,26 @@ const CommandList = React.forwardRef<
// event ourselves so the list scrolls regardless of focus state. // event ourselves so the list scrolls regardless of focus state.
<CommandPrimitive.List <CommandPrimitive.List
ref={ref} ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden overscroll-contain", className)} className={cn('max-h-[300px] overflow-y-auto overflow-x-hidden overscroll-contain', className)}
onWheel={(event) => { onWheel={(event) => {
onWheel?.(event) onWheel?.(event);
if (event.defaultPrevented) return if (event.defaultPrevented) return;
event.currentTarget.scrollTop += event.deltaY event.currentTarget.scrollTop += event.deltaY;
}} }}
{...props} {...props}
/> />
)) ));
CommandList.displayName = CommandPrimitive.List.displayName CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef< const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>, React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty> React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => ( >((props, ref) => (
<CommandPrimitive.Empty <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />
ref={ref} ));
className="py-6 text-center text-sm"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
const CommandGroup = React.forwardRef< const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>, React.ElementRef<typeof CommandPrimitive.Group>,
@@ -96,14 +92,14 @@ const CommandGroup = React.forwardRef<
<CommandPrimitive.Group <CommandPrimitive.Group
ref={ref} ref={ref}
className={cn( className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground", 'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground',
className className,
)} )}
{...props} {...props}
/> />
)) ));
CommandGroup.displayName = CommandPrimitive.Group.displayName CommandGroup.displayName = CommandPrimitive.Group.displayName;
const CommandSeparator = React.forwardRef< const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>, React.ElementRef<typeof CommandPrimitive.Separator>,
@@ -111,11 +107,11 @@ const CommandSeparator = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<CommandPrimitive.Separator <CommandPrimitive.Separator
ref={ref} ref={ref}
className={cn("-mx-1 h-px bg-border", className)} className={cn('-mx-1 h-px bg-border', className)}
{...props} {...props}
/> />
)) ));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
const CommandItem = React.forwardRef< const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>, React.ElementRef<typeof CommandPrimitive.Item>,
@@ -124,30 +120,24 @@ const CommandItem = React.forwardRef<
<CommandPrimitive.Item <CommandPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 'relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
className className,
)} )}
{...props} {...props}
/> />
)) ));
CommandItem.displayName = CommandPrimitive.Item.displayName CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({ const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<span <span
className={cn( className={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props} {...props}
/> />
) );
} };
CommandShortcut.displayName = "CommandShortcut" CommandShortcut.displayName = 'CommandShortcut';
export { export {
Command, Command,
@@ -159,4 +149,4 @@ export {
CommandItem, CommandItem,
CommandShortcut, CommandShortcut,
CommandSeparator, CommandSeparator,
} };