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:
@@ -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 2–3 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` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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)"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user