Files
pn-new-crm/src/components/shared/notes-list.tsx

427 lines
16 KiB
TypeScript
Raw Normal View History

'use client';
fix(compiler): React Compiler safety triage — 5 categories cleared Cleared 4 rule buckets (37 violations, including 5 real bugs) and silenced 1 informational bucket from the Next 16 / react-hooks v7 upgrade. Cleared rules promoted from `warn` back to `error` so new regressions block CI. Real bug fixes: - `interest-contact-log-tab.tsx`: `useMemo` used for side effects (5 setState calls inside a memo body); converted to `useEffect`. - `PieChart.tsx`: cumulative `let angle` mutation in a render-phase `map`; converted to `reduce` so the slice array is built without re-assignment. - `documents-hub.tsx`: `useMemo(() => ({ count: 0 }))` used as a mutable drag counter; converted to `useRef`. - `notes-list.tsx`: `Date.now()` read during render for note-edit countdown (impure) → pinned to a `now` state ticked every 30s. - `onboarding-checklist.tsx` / `user-profile.tsx` / `user-settings.tsx`: `useEffect(() => void load(), [])` with the `load` function declared AFTER the effect — relied on hoisting, trips Compiler's "access before declared" rule. Declared inside the effect. Pattern fixes (intentional cache-via-ref → state or layout-effect): - 6 `ref.current = x` writes during render moved into layout effects (`use-realtime-invalidation`, `settings-form-card`, `inbox`). - 3 `ref.current` reads during render (search totals cache, scanner file ref) rewritten to backed-by-state. - `use-is-mobile.ts` rewritten on `useSyncExternalStore` to avoid the SSR-then-rehydrate setState dance. - `use-notifications.ts` rewritten to write socket pushes directly into the React Query cache via `setQueryData`, removing a local state mirror. Rule config (`eslint.config.mjs`): - `react-hooks/purity` → error (was warn, cleared) - `react-hooks/set-state-in-render` → error (was warn, cleared) - `react-hooks/immutability` → error (was warn, cleared) - `react-hooks/refs` → error (was warn, cleared) - `react-hooks/incompatible-library` → off (informational only) - `react-hooks/set-state-in-effect` → warn (51 remaining, all the useEffect→fetch→setState data-fetch pattern; migration to useQuery tracked in BACKLOG) Verified: tsc clean, eslint 0 errors / 69 warnings (down from 105), vitest 1315/1315, next build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:14:16 +02:00
import { useEffect, useState } from 'react';
feat(uat-polish): live-UAT round — dialog widths, recommender polish, inline create, tenancy + notes plumbing Compendium of polish + small-fix work captured during the 2026-05-26 live UAT session. Every change has a corresponding entry in docs/superpowers/audits/active-uat.md with file:line evidence + root cause + alternatives considered. Dialog primitive width - DialogContent default bumped from sm:max-w-lg (512px) to sm:max-w-xl + lg:max-w-3xl so every consumer gets a sane desktop default. Confirm dialogs override DOWN, content-heavy dialogs override UP. - FilePreviewDialog full-viewport via w-[min(95vw,1400px)] + h-[85vh] so PDFs render at usable width on real desktops. Recommender card - Heat badge now a Popover with the score (X/100), the formula in plain English, the four component breakdowns (recency / furthest stage / interest count / EOI count), and a pointer to the admin weight tuning page. - Area letter span dropped from the card header - mooring number already prefixes it. - BerthRecommenderPanel + the dedicated "Berth Recommendations" tab both hidden when interest.desiredLengthFt is null. The empty guidance card was reading as noise. interest-tabs.tsx computes hasDesiredDims once and gates the inline mount + tab strip spread off it. BerthPicker - Drop area suffix from row labels. Mooring number already carries the area letter prefix; group heading conveys the same context. Same fix flows to every BerthPicker consumer (tenancy create/renew/transfer, interest form, linked-berths picker). CreateDocumentWizard - DOCUMENT_TYPE_LABELS constant added to constants.ts. Wizard reads from the map instead of naive replace(/_/g, ' '): "EOI", "Contract", "NDA", "Reservation Agreement", "Other". - "Other" option surfaces a hint pointing the rep at the Title field so they describe what the doc actually is. InterestForm inline client + yacht create - ClientForm gains an onCreated(clientId) callback. Mutation returns { id } in create mode so onSuccess can forward. - InterestForm renders an "Add new" Button next to the Client label (create mode only - hidden on edit), opens ClientForm, auto- selects the new client into the draft. Mirrors the existing inline yacht-create pattern. - Reset path includes source: 'manual' alongside the other create- mode defaults; the manual flow was dropping back to a blank source dropdown on reopen. Tenancy list - ClientTenanciesTab activeTenancies query now includes status IN ('pending', 'active'). Was filtering to active-only; pending rows from manual create + webhook auto-create were invisible on the client detail's Tenancies tab. - TenancyList rows are now keyboard- and click-navigable to the tenancy detail page (Enter/Space included). Inner links + buttons stop propagation so per-cell navigation works. NotesList source badge - Aggregated-mode source badge ("Yacht / Test Yacht") is now a Link to the source entity's detail page. New sourceLinkFor helper centralises the URL mapping across clients/companies/yachts/ interests + residential variants. Yacht transfer audit log - transferOwnership emits a distinct 'transfer' AuditAction (added to AuditAction union in src/lib/audit.ts) with old/new owner names resolved at write time. EntityActivityFeed renders "Matt transferred owner to Jane Smith" instead of "Matt updated this record." formatValueForField unwraps the { name } shape so the audit_logs Record<string, unknown> typing stays clean. - yacht-transfer-dialog copy: dropped "atomic" jargon. Reads "The change is logged in the audit history" instead. Companies autocomplete - /api/v1/companies/autocomplete now returns the 10 most-recently- updated companies when the query string is empty. Was returning []. CompanyPicker popover opens with results to scan instead of a blank dropdown. DocumentsHub FlatFolderListing - Uploaded files (the files table) now merge into the documents table view via a parallel /api/v1/files?folderId=X query + client-side merge into a unified row list. listFiles service honours the folderId filter that was already accepted by the validator. New renderFileRow renders file rows with an "Uploaded file" type pill + "Stored" status pill, links the filename to the download URL. Existing FolderDropZone invalidation covers the new query, so drag-drop and New-document-menu uploads refresh the list without a page reload. - FlatFolderListing wrapped in a vertically-spaced container so subfolders / search row / list have consistent gap. - Per-row chevron only renders when totalSigners > 0; empty placeholder column kept so grid alignment doesn't jump. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 20:07:45 +02:00
import Link from 'next/link';
import { useParams } from 'next/navigation';
fix(uat-batch-1): wave-1 blocker bugs — supplemental gate, file FK, downloads, search dedup, notes stale, expense form, vocab Surgical fixes for the 7 UAT blockers that prevent productive forward testing. Each item has a corresponding entry in alpha-uat-master.md. - supplemental-info route relocated out of (portal) so it bypasses the isPortalDisabledGlobally() kill-switch. URL unchanged. - file upload service derives client_id/company_id/yacht_id from (entityType, entityId) when not explicitly passed, so interest-tab uploads no longer land with client_id=NULL and stay visible in the Attachments list. - triggerBlobDownload / triggerUrlDownload helpers in src/lib/utils attach the anchor to the DOM before click so Chromium honours the download attribute; 7 sites refactored, file-named downloads stop arriving as bare UUIDs. - search-nav-catalog dedupes by href at the result-collection layer so the same href can no longer surface twice in the command-K dropdown (kills the React duplicate-key warning); /admin/templates entries merged into a single richer-keyword variant. - NotesList gains a parentInvalidateKey prop, wired through all five callers (interest, client, yacht, company, residential client/ interest) so the Overview "Latest note" teaser refreshes when a note is added in the Notes tab. - expense-form-dialog: setValue('receiptFileIds') / setValue( 'noReceiptAcknowledged') on upload/clear/checkbox so the schema-level refine sees the field and Create stops silently no-op'ing on submit. - bulk-add-berths-wizard: side-pontoon dropdown now reads through useVocabulary('berth_side_pontoon_options') instead of a wrong local enum ('Port', 'Starboard', 'Bow', 'Stern') — wizard data now matches the rest of the platform + honours admin-editable per-port overrides. tsc clean. 1419/1419 vitest. lint clean on touched files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:50:58 +02:00
import { useQuery, useMutation, useQueryClient, type QueryKey } from '@tanstack/react-query';
import { formatDistanceToNow } from 'date-fns';
import { Lock, Pencil, Trash2, Send, Loader2 } from 'lucide-react';
import { useAutoAnimate } from '@formkit/auto-animate/react';
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { apiFetch } from '@/lib/api/client';
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing- progress redesign + env-to-admin migration + dev-mode banner) with the 2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW). CRITICAL (3): - C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths no longer silently drop interest links - C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed - C-03 generic PATCH /interests/[id] no longer accepts pipelineStage — callers must go through /stage with the override-guard chain HIGH (14/15): - H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across interests/documents/reservations/reminders/invoices (migration 0070) - H-02 login page reads ?redirect= param with same-origin guard - H-03 CRM invite token moves to URL fragment so it never lands in nginx access logs / Referer headers - H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4) - H-05 toggleAccount writes an audit row - H-06 upsertSetting masks any value whose key ends with _encrypted - H-07 archiveClient cascade fires per-interest audit rows - H-08 createSalesTransporter applies SMTP_TIMEOUTS - H-09 AppShell stable children — viewport flip across breakpoint no longer destroys in-progress form drafts - H-10 portal documents page swaps Unicode glyph status icons for Lucide CheckCircle2/XCircle/Circle + aria-labels - H-12 list components swap alert(...) for toast.warning(...) - H-13 5 icon-only buttons gain aria-label - H-14 parseBody treats empty bodies as {} - H-15 admin layout renders a 403 panel instead of silent bounce - H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet MEDIUM (28+): - M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE WHEREs across custom-fields, notes (all 6 entity types x update + delete), client-contacts, yacht ownerClient lookup, webhook reads - M-D01 documents-hub realtime event-name typo (file:created -> uploaded) - M-EM01 portal-auth emails thread through portId - M-EM02 sendEmail accepts cc/bcc params - M-EM04 notification_digest catalog key - M-IN01 portal presigned download URLs use 4h TTL - M-IN02 OpenAI client lazy-instantiated - M-IN04 stale pdfme refs updated to pdf-lib AcroForm - M-IN05 umami.testConnection returns tagged union - M-L01 reservations tenure_type unified with berths - M-L02 report-generators canonicalize stage values - M-AU01 audit log placeholder copy fixed - M-AU04 outcome_set / outcome_cleared distinct audit verbs - M-NEW-2 activity feed entity name+type separator - M-R01 portal allowlist narrowed + portal_session backstop in proxy - M-SC02 companies archived partial index - M-SC04 audit_logs.searchText documented as DB-managed - M-S01 storage_s3_access_key_encrypted admin field - M-U01 audit log empty state uses <EmptyState> - M-U09 invoice delete dialog -> <AlertDialog> - M-U10 toast.success on ClientForm + InterestForm create/edit - M-U11 settings-form-card logo preview alt text - M-U14 mobile topbar title on clients/yachts/interests/berths - M-U15 Invoices in mobile More-sheet LOW (6/8): - L-AU01 severity defaults for security-relevant verbs - L-AU02 +13 missing actions in admin audit filter - L-AU03 +7 missing entity types in admin audit filter - L-AU04 dead listAuditLogs stubbed - L-D02 CLAUDE.md Owner-wins chain tightened Bonus — Document detail polish (#67 partial, 3/6 deliverables): - state-aware action button per signer - watcher Add UI with display-name resolution - cleanSignerName cleanup Prior session work bundled in: - Documenso v2 webhook + envelope-ID normalization + sequential signing - SigningProgress UI redesign (avatars, per-signer state, timestamps) - env->admin settings registry + RegistryDrivenForm + encrypted creds - Embedded-signing card + Test connection + setup help - Dev-mode EMAIL_REDIRECT_TO banner - Pipeline rules admin page - Sales email config card - Audit log details Sheet - EOI tab: Finalising badge, absolute timestamps, sequential indicator - Notes pipeline_stage_at_creation (migration 0069) - Documenso numeric ID dual-key webhook (migration 0068) - Dimensions criterion copy (migration 0067) Tests: 1374/1374 vitest pass. tsc clean. lint clean. See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and the user-input items still pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:28:50 +02:00
import { stageLabel } from '@/lib/constants';
type NoteSource =
| 'client'
| 'interest'
| 'yacht'
| 'company'
| 'residential_client'
| 'residential_interest';
interface Note {
id: string;
content: string;
authorId: string;
authorName?: string;
isLocked: boolean;
createdAt: string;
updatedAt: 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
/** Aggregated-mode only: which child entity this note came from. */
source?: NoteSource;
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
sourceId?: string;
sourceLabel?: string;
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing- progress redesign + env-to-admin migration + dev-mode banner) with the 2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW). CRITICAL (3): - C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths no longer silently drop interest links - C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed - C-03 generic PATCH /interests/[id] no longer accepts pipelineStage — callers must go through /stage with the override-guard chain HIGH (14/15): - H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across interests/documents/reservations/reminders/invoices (migration 0070) - H-02 login page reads ?redirect= param with same-origin guard - H-03 CRM invite token moves to URL fragment so it never lands in nginx access logs / Referer headers - H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4) - H-05 toggleAccount writes an audit row - H-06 upsertSetting masks any value whose key ends with _encrypted - H-07 archiveClient cascade fires per-interest audit rows - H-08 createSalesTransporter applies SMTP_TIMEOUTS - H-09 AppShell stable children — viewport flip across breakpoint no longer destroys in-progress form drafts - H-10 portal documents page swaps Unicode glyph status icons for Lucide CheckCircle2/XCircle/Circle + aria-labels - H-12 list components swap alert(...) for toast.warning(...) - H-13 5 icon-only buttons gain aria-label - H-14 parseBody treats empty bodies as {} - H-15 admin layout renders a 403 panel instead of silent bounce - H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet MEDIUM (28+): - M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE WHEREs across custom-fields, notes (all 6 entity types x update + delete), client-contacts, yacht ownerClient lookup, webhook reads - M-D01 documents-hub realtime event-name typo (file:created -> uploaded) - M-EM01 portal-auth emails thread through portId - M-EM02 sendEmail accepts cc/bcc params - M-EM04 notification_digest catalog key - M-IN01 portal presigned download URLs use 4h TTL - M-IN02 OpenAI client lazy-instantiated - M-IN04 stale pdfme refs updated to pdf-lib AcroForm - M-IN05 umami.testConnection returns tagged union - M-L01 reservations tenure_type unified with berths - M-L02 report-generators canonicalize stage values - M-AU01 audit log placeholder copy fixed - M-AU04 outcome_set / outcome_cleared distinct audit verbs - M-NEW-2 activity feed entity name+type separator - M-R01 portal allowlist narrowed + portal_session backstop in proxy - M-SC02 companies archived partial index - M-SC04 audit_logs.searchText documented as DB-managed - M-S01 storage_s3_access_key_encrypted admin field - M-U01 audit log empty state uses <EmptyState> - M-U09 invoice delete dialog -> <AlertDialog> - M-U10 toast.success on ClientForm + InterestForm create/edit - M-U11 settings-form-card logo preview alt text - M-U14 mobile topbar title on clients/yachts/interests/berths - M-U15 Invoices in mobile More-sheet LOW (6/8): - L-AU01 severity defaults for security-relevant verbs - L-AU02 +13 missing actions in admin audit filter - L-AU03 +7 missing entity types in admin audit filter - L-AU04 dead listAuditLogs stubbed - L-D02 CLAUDE.md Owner-wins chain tightened Bonus — Document detail polish (#67 partial, 3/6 deliverables): - state-aware action button per signer - watcher Add UI with display-name resolution - cleanSignerName cleanup Prior session work bundled in: - Documenso v2 webhook + envelope-ID normalization + sequential signing - SigningProgress UI redesign (avatars, per-signer state, timestamps) - env->admin settings registry + RegistryDrivenForm + encrypted creds - Embedded-signing card + Test connection + setup help - Dev-mode EMAIL_REDIRECT_TO banner - Pipeline rules admin page - Sales email config card - Audit log details Sheet - EOI tab: Finalising badge, absolute timestamps, sequential indicator - Notes pipeline_stage_at_creation (migration 0069) - Documenso numeric ID dual-key webhook (migration 0068) - Dimensions criterion copy (migration 0067) Tests: 1374/1374 vitest pass. tsc clean. lint clean. See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and the user-input items still pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:28:50 +02:00
/** Pipeline stage the linked interest was at when the note was authored.
* Only populated for interest notes - drives the small stage chip. */
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing- progress redesign + env-to-admin migration + dev-mode banner) with the 2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW). CRITICAL (3): - C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths no longer silently drop interest links - C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed - C-03 generic PATCH /interests/[id] no longer accepts pipelineStage — callers must go through /stage with the override-guard chain HIGH (14/15): - H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across interests/documents/reservations/reminders/invoices (migration 0070) - H-02 login page reads ?redirect= param with same-origin guard - H-03 CRM invite token moves to URL fragment so it never lands in nginx access logs / Referer headers - H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4) - H-05 toggleAccount writes an audit row - H-06 upsertSetting masks any value whose key ends with _encrypted - H-07 archiveClient cascade fires per-interest audit rows - H-08 createSalesTransporter applies SMTP_TIMEOUTS - H-09 AppShell stable children — viewport flip across breakpoint no longer destroys in-progress form drafts - H-10 portal documents page swaps Unicode glyph status icons for Lucide CheckCircle2/XCircle/Circle + aria-labels - H-12 list components swap alert(...) for toast.warning(...) - H-13 5 icon-only buttons gain aria-label - H-14 parseBody treats empty bodies as {} - H-15 admin layout renders a 403 panel instead of silent bounce - H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet MEDIUM (28+): - M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE WHEREs across custom-fields, notes (all 6 entity types x update + delete), client-contacts, yacht ownerClient lookup, webhook reads - M-D01 documents-hub realtime event-name typo (file:created -> uploaded) - M-EM01 portal-auth emails thread through portId - M-EM02 sendEmail accepts cc/bcc params - M-EM04 notification_digest catalog key - M-IN01 portal presigned download URLs use 4h TTL - M-IN02 OpenAI client lazy-instantiated - M-IN04 stale pdfme refs updated to pdf-lib AcroForm - M-IN05 umami.testConnection returns tagged union - M-L01 reservations tenure_type unified with berths - M-L02 report-generators canonicalize stage values - M-AU01 audit log placeholder copy fixed - M-AU04 outcome_set / outcome_cleared distinct audit verbs - M-NEW-2 activity feed entity name+type separator - M-R01 portal allowlist narrowed + portal_session backstop in proxy - M-SC02 companies archived partial index - M-SC04 audit_logs.searchText documented as DB-managed - M-S01 storage_s3_access_key_encrypted admin field - M-U01 audit log empty state uses <EmptyState> - M-U09 invoice delete dialog -> <AlertDialog> - M-U10 toast.success on ClientForm + InterestForm create/edit - M-U11 settings-form-card logo preview alt text - M-U14 mobile topbar title on clients/yachts/interests/berths - M-U15 Invoices in mobile More-sheet LOW (6/8): - L-AU01 severity defaults for security-relevant verbs - L-AU02 +13 missing actions in admin audit filter - L-AU03 +7 missing entity types in admin audit filter - L-AU04 dead listAuditLogs stubbed - L-D02 CLAUDE.md Owner-wins chain tightened Bonus — Document detail polish (#67 partial, 3/6 deliverables): - state-aware action button per signer - watcher Add UI with display-name resolution - cleanSignerName cleanup Prior session work bundled in: - Documenso v2 webhook + envelope-ID normalization + sequential signing - SigningProgress UI redesign (avatars, per-signer state, timestamps) - env->admin settings registry + RegistryDrivenForm + encrypted creds - Embedded-signing card + Test connection + setup help - Dev-mode EMAIL_REDIRECT_TO banner - Pipeline rules admin page - Sales email config card - Audit log details Sheet - EOI tab: Finalising badge, absolute timestamps, sequential indicator - Notes pipeline_stage_at_creation (migration 0069) - Documenso numeric ID dual-key webhook (migration 0068) - Dimensions criterion copy (migration 0067) Tests: 1374/1374 vitest pass. tsc clean. lint clean. See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and the user-input items still pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:28:50 +02:00
pipelineStageAtCreation?: string | null;
}
type NotesEntityType =
| 'clients'
| 'interests'
| 'yachts'
| 'companies'
| 'residential_clients'
| 'residential_interests';
/** Maps the entity-type the list is rendered for to the `source` value
* the aggregator uses when a note came from THAT entity itself
* (vs. a related entity). Used to decide whether a note is editable
* in-place or read-only with an "Open source" affordance. */
const SELF_SOURCE: Record<NotesEntityType, NoteSource | null> = {
clients: 'client',
yachts: 'yacht',
companies: 'company',
residential_clients: 'residential_client',
// Aggregate-mode is only meaningful for the entities above. Interests
// and residential_interests are leaf nodes - there's nothing to roll
// up to them.
interests: null,
residential_interests: null,
};
const AGGREGATABLE: ReadonlySet<NotesEntityType> = new Set<NotesEntityType>([
'clients',
'yachts',
'companies',
'residential_clients',
]);
const SOURCE_BADGE_CLASS: Record<NoteSource, string> = {
client: 'bg-violet-100 text-violet-900',
interest: 'bg-blue-100 text-blue-900',
yacht: 'bg-emerald-100 text-emerald-900',
company: 'bg-amber-100 text-amber-900',
residential_client: 'bg-violet-100 text-violet-900',
residential_interest: 'bg-blue-100 text-blue-900',
};
const SOURCE_LABEL: Record<NoteSource, string> = {
client: 'Client',
interest: 'Interest',
yacht: 'Yacht',
company: 'Company',
residential_client: 'Resident',
residential_interest: 'Inquiry',
};
interface NotesListProps {
entityType: NotesEntityType;
entityId: string;
currentUserId?: 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
/**
* Aggregate-on-read: union the entity's own notes with notes from
* related entities (interests, owned yachts / company yachts, owner
* client). Cross-source notes render with a source chip and are
* read-only here - open the source entity's page to edit.
*
* Supported for entityType in {clients, yachts, companies,
* residential_clients}. Ignored for interests / residential_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
*/
aggregate?: boolean;
fix(uat-batch-1): wave-1 blocker bugs — supplemental gate, file FK, downloads, search dedup, notes stale, expense form, vocab Surgical fixes for the 7 UAT blockers that prevent productive forward testing. Each item has a corresponding entry in alpha-uat-master.md. - supplemental-info route relocated out of (portal) so it bypasses the isPortalDisabledGlobally() kill-switch. URL unchanged. - file upload service derives client_id/company_id/yacht_id from (entityType, entityId) when not explicitly passed, so interest-tab uploads no longer land with client_id=NULL and stay visible in the Attachments list. - triggerBlobDownload / triggerUrlDownload helpers in src/lib/utils attach the anchor to the DOM before click so Chromium honours the download attribute; 7 sites refactored, file-named downloads stop arriving as bare UUIDs. - search-nav-catalog dedupes by href at the result-collection layer so the same href can no longer surface twice in the command-K dropdown (kills the React duplicate-key warning); /admin/templates entries merged into a single richer-keyword variant. - NotesList gains a parentInvalidateKey prop, wired through all five callers (interest, client, yacht, company, residential client/ interest) so the Overview "Latest note" teaser refreshes when a note is added in the Notes tab. - expense-form-dialog: setValue('receiptFileIds') / setValue( 'noReceiptAcknowledged') on upload/clear/checkbox so the schema-level refine sees the field and Create stops silently no-op'ing on submit. - bulk-add-berths-wizard: side-pontoon dropdown now reads through useVocabulary('berth_side_pontoon_options') instead of a wrong local enum ('Port', 'Starboard', 'Bow', 'Stern') — wizard data now matches the rest of the platform + honours admin-editable per-port overrides. tsc clean. 1419/1419 vitest. lint clean on touched files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:50:58 +02:00
/**
* Optional parent-entity query key to invalidate alongside the notes
* query on create/update/delete. The parent entity detail typically
* hydrates a `recentNote` / `notesCount` teaser that goes stale after
* a note mutation; passing the detail's query key here keeps it in
* sync without a hard refresh.
*/
parentInvalidateKey?: QueryKey;
}
const NOTE_EDIT_WINDOW_MS = 15 * 60 * 1000; // 15 minutes
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
/** Sort by source then chronologically inside each source.
* Used by the aggregated view's "Group by source" toggle. */
function sortByGroup(notes: Note[]): Note[] {
const sourceOrder: Record<string, number> = {
client: 0,
company: 1,
yacht: 2,
interest: 3,
residential_client: 0,
residential_interest: 1,
};
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
return [...notes].sort((a, b) => {
const aRank = sourceOrder[a.source ?? ''] ?? 99;
const bRank = sourceOrder[b.source ?? ''] ?? 99;
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
if (aRank !== bRank) return aRank - bRank;
const aLabel = a.sourceLabel ?? '';
const bLabel = b.sourceLabel ?? '';
if (aLabel !== bLabel) return aLabel.localeCompare(bLabel);
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
});
}
feat(uat-polish): live-UAT round — dialog widths, recommender polish, inline create, tenancy + notes plumbing Compendium of polish + small-fix work captured during the 2026-05-26 live UAT session. Every change has a corresponding entry in docs/superpowers/audits/active-uat.md with file:line evidence + root cause + alternatives considered. Dialog primitive width - DialogContent default bumped from sm:max-w-lg (512px) to sm:max-w-xl + lg:max-w-3xl so every consumer gets a sane desktop default. Confirm dialogs override DOWN, content-heavy dialogs override UP. - FilePreviewDialog full-viewport via w-[min(95vw,1400px)] + h-[85vh] so PDFs render at usable width on real desktops. Recommender card - Heat badge now a Popover with the score (X/100), the formula in plain English, the four component breakdowns (recency / furthest stage / interest count / EOI count), and a pointer to the admin weight tuning page. - Area letter span dropped from the card header - mooring number already prefixes it. - BerthRecommenderPanel + the dedicated "Berth Recommendations" tab both hidden when interest.desiredLengthFt is null. The empty guidance card was reading as noise. interest-tabs.tsx computes hasDesiredDims once and gates the inline mount + tab strip spread off it. BerthPicker - Drop area suffix from row labels. Mooring number already carries the area letter prefix; group heading conveys the same context. Same fix flows to every BerthPicker consumer (tenancy create/renew/transfer, interest form, linked-berths picker). CreateDocumentWizard - DOCUMENT_TYPE_LABELS constant added to constants.ts. Wizard reads from the map instead of naive replace(/_/g, ' '): "EOI", "Contract", "NDA", "Reservation Agreement", "Other". - "Other" option surfaces a hint pointing the rep at the Title field so they describe what the doc actually is. InterestForm inline client + yacht create - ClientForm gains an onCreated(clientId) callback. Mutation returns { id } in create mode so onSuccess can forward. - InterestForm renders an "Add new" Button next to the Client label (create mode only - hidden on edit), opens ClientForm, auto- selects the new client into the draft. Mirrors the existing inline yacht-create pattern. - Reset path includes source: 'manual' alongside the other create- mode defaults; the manual flow was dropping back to a blank source dropdown on reopen. Tenancy list - ClientTenanciesTab activeTenancies query now includes status IN ('pending', 'active'). Was filtering to active-only; pending rows from manual create + webhook auto-create were invisible on the client detail's Tenancies tab. - TenancyList rows are now keyboard- and click-navigable to the tenancy detail page (Enter/Space included). Inner links + buttons stop propagation so per-cell navigation works. NotesList source badge - Aggregated-mode source badge ("Yacht / Test Yacht") is now a Link to the source entity's detail page. New sourceLinkFor helper centralises the URL mapping across clients/companies/yachts/ interests + residential variants. Yacht transfer audit log - transferOwnership emits a distinct 'transfer' AuditAction (added to AuditAction union in src/lib/audit.ts) with old/new owner names resolved at write time. EntityActivityFeed renders "Matt transferred owner to Jane Smith" instead of "Matt updated this record." formatValueForField unwraps the { name } shape so the audit_logs Record<string, unknown> typing stays clean. - yacht-transfer-dialog copy: dropped "atomic" jargon. Reads "The change is logged in the audit history" instead. Companies autocomplete - /api/v1/companies/autocomplete now returns the 10 most-recently- updated companies when the query string is empty. Was returning []. CompanyPicker popover opens with results to scan instead of a blank dropdown. DocumentsHub FlatFolderListing - Uploaded files (the files table) now merge into the documents table view via a parallel /api/v1/files?folderId=X query + client-side merge into a unified row list. listFiles service honours the folderId filter that was already accepted by the validator. New renderFileRow renders file rows with an "Uploaded file" type pill + "Stored" status pill, links the filename to the download URL. Existing FolderDropZone invalidation covers the new query, so drag-drop and New-document-menu uploads refresh the list without a page reload. - FlatFolderListing wrapped in a vertically-spaced container so subfolders / search row / list have consistent gap. - Per-row chevron only renders when totalSigners > 0; empty placeholder column kept so grid alignment doesn't jump. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 20:07:45 +02:00
/** Resolve the detail-page URL for a note's source entity so the
* aggregated-mode source badge can navigate the rep to that record. */
function sourceLinkFor(portSlug: string, source: NoteSource, sourceId: string): string | null {
if (!portSlug) return null;
switch (source) {
case 'client':
return `/${portSlug}/clients/${sourceId}`;
case 'company':
return `/${portSlug}/companies/${sourceId}`;
case 'yacht':
return `/${portSlug}/yachts/${sourceId}`;
case 'interest':
return `/${portSlug}/interests/${sourceId}`;
case 'residential_client':
return `/${portSlug}/residential/clients/${sourceId}`;
case 'residential_interest':
return `/${portSlug}/residential/interests/${sourceId}`;
default:
return null;
}
}
fix(uat-batch-1): wave-1 blocker bugs — supplemental gate, file FK, downloads, search dedup, notes stale, expense form, vocab Surgical fixes for the 7 UAT blockers that prevent productive forward testing. Each item has a corresponding entry in alpha-uat-master.md. - supplemental-info route relocated out of (portal) so it bypasses the isPortalDisabledGlobally() kill-switch. URL unchanged. - file upload service derives client_id/company_id/yacht_id from (entityType, entityId) when not explicitly passed, so interest-tab uploads no longer land with client_id=NULL and stay visible in the Attachments list. - triggerBlobDownload / triggerUrlDownload helpers in src/lib/utils attach the anchor to the DOM before click so Chromium honours the download attribute; 7 sites refactored, file-named downloads stop arriving as bare UUIDs. - search-nav-catalog dedupes by href at the result-collection layer so the same href can no longer surface twice in the command-K dropdown (kills the React duplicate-key warning); /admin/templates entries merged into a single richer-keyword variant. - NotesList gains a parentInvalidateKey prop, wired through all five callers (interest, client, yacht, company, residential client/ interest) so the Overview "Latest note" teaser refreshes when a note is added in the Notes tab. - expense-form-dialog: setValue('receiptFileIds') / setValue( 'noReceiptAcknowledged') on upload/clear/checkbox so the schema-level refine sees the field and Create stops silently no-op'ing on submit. - bulk-add-berths-wizard: side-pontoon dropdown now reads through useVocabulary('berth_side_pontoon_options') instead of a wrong local enum ('Port', 'Starboard', 'Bow', 'Stern') — wizard data now matches the rest of the platform + honours admin-editable per-port overrides. tsc clean. 1419/1419 vitest. lint clean on touched files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:50:58 +02:00
export function NotesList({
entityType,
entityId,
currentUserId,
aggregate,
parentInvalidateKey,
}: NotesListProps) {
const queryClient = useQueryClient();
feat(uat-polish): live-UAT round — dialog widths, recommender polish, inline create, tenancy + notes plumbing Compendium of polish + small-fix work captured during the 2026-05-26 live UAT session. Every change has a corresponding entry in docs/superpowers/audits/active-uat.md with file:line evidence + root cause + alternatives considered. Dialog primitive width - DialogContent default bumped from sm:max-w-lg (512px) to sm:max-w-xl + lg:max-w-3xl so every consumer gets a sane desktop default. Confirm dialogs override DOWN, content-heavy dialogs override UP. - FilePreviewDialog full-viewport via w-[min(95vw,1400px)] + h-[85vh] so PDFs render at usable width on real desktops. Recommender card - Heat badge now a Popover with the score (X/100), the formula in plain English, the four component breakdowns (recency / furthest stage / interest count / EOI count), and a pointer to the admin weight tuning page. - Area letter span dropped from the card header - mooring number already prefixes it. - BerthRecommenderPanel + the dedicated "Berth Recommendations" tab both hidden when interest.desiredLengthFt is null. The empty guidance card was reading as noise. interest-tabs.tsx computes hasDesiredDims once and gates the inline mount + tab strip spread off it. BerthPicker - Drop area suffix from row labels. Mooring number already carries the area letter prefix; group heading conveys the same context. Same fix flows to every BerthPicker consumer (tenancy create/renew/transfer, interest form, linked-berths picker). CreateDocumentWizard - DOCUMENT_TYPE_LABELS constant added to constants.ts. Wizard reads from the map instead of naive replace(/_/g, ' '): "EOI", "Contract", "NDA", "Reservation Agreement", "Other". - "Other" option surfaces a hint pointing the rep at the Title field so they describe what the doc actually is. InterestForm inline client + yacht create - ClientForm gains an onCreated(clientId) callback. Mutation returns { id } in create mode so onSuccess can forward. - InterestForm renders an "Add new" Button next to the Client label (create mode only - hidden on edit), opens ClientForm, auto- selects the new client into the draft. Mirrors the existing inline yacht-create pattern. - Reset path includes source: 'manual' alongside the other create- mode defaults; the manual flow was dropping back to a blank source dropdown on reopen. Tenancy list - ClientTenanciesTab activeTenancies query now includes status IN ('pending', 'active'). Was filtering to active-only; pending rows from manual create + webhook auto-create were invisible on the client detail's Tenancies tab. - TenancyList rows are now keyboard- and click-navigable to the tenancy detail page (Enter/Space included). Inner links + buttons stop propagation so per-cell navigation works. NotesList source badge - Aggregated-mode source badge ("Yacht / Test Yacht") is now a Link to the source entity's detail page. New sourceLinkFor helper centralises the URL mapping across clients/companies/yachts/ interests + residential variants. Yacht transfer audit log - transferOwnership emits a distinct 'transfer' AuditAction (added to AuditAction union in src/lib/audit.ts) with old/new owner names resolved at write time. EntityActivityFeed renders "Matt transferred owner to Jane Smith" instead of "Matt updated this record." formatValueForField unwraps the { name } shape so the audit_logs Record<string, unknown> typing stays clean. - yacht-transfer-dialog copy: dropped "atomic" jargon. Reads "The change is logged in the audit history" instead. Companies autocomplete - /api/v1/companies/autocomplete now returns the 10 most-recently- updated companies when the query string is empty. Was returning []. CompanyPicker popover opens with results to scan instead of a blank dropdown. DocumentsHub FlatFolderListing - Uploaded files (the files table) now merge into the documents table view via a parallel /api/v1/files?folderId=X query + client-side merge into a unified row list. listFiles service honours the folderId filter that was already accepted by the validator. New renderFileRow renders file rows with an "Uploaded file" type pill + "Stored" status pill, links the filename to the download URL. Existing FolderDropZone invalidation covers the new query, so drag-drop and New-document-menu uploads refresh the list without a page reload. - FlatFolderListing wrapped in a vertically-spaced container so subfolders / search row / list have consistent gap. - Per-row chevron only renders when totalSigners > 0; empty placeholder column kept so grid alignment doesn't jump. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 20:07:45 +02:00
const routeParams = useParams<{ portSlug: string }>();
const portSlug = routeParams?.portSlug ?? '';
fix(uat-batch-1): wave-1 blocker bugs — supplemental gate, file FK, downloads, search dedup, notes stale, expense form, vocab Surgical fixes for the 7 UAT blockers that prevent productive forward testing. Each item has a corresponding entry in alpha-uat-master.md. - supplemental-info route relocated out of (portal) so it bypasses the isPortalDisabledGlobally() kill-switch. URL unchanged. - file upload service derives client_id/company_id/yacht_id from (entityType, entityId) when not explicitly passed, so interest-tab uploads no longer land with client_id=NULL and stay visible in the Attachments list. - triggerBlobDownload / triggerUrlDownload helpers in src/lib/utils attach the anchor to the DOM before click so Chromium honours the download attribute; 7 sites refactored, file-named downloads stop arriving as bare UUIDs. - search-nav-catalog dedupes by href at the result-collection layer so the same href can no longer surface twice in the command-K dropdown (kills the React duplicate-key warning); /admin/templates entries merged into a single richer-keyword variant. - NotesList gains a parentInvalidateKey prop, wired through all five callers (interest, client, yacht, company, residential client/ interest) so the Overview "Latest note" teaser refreshes when a note is added in the Notes tab. - expense-form-dialog: setValue('receiptFileIds') / setValue( 'noReceiptAcknowledged') on upload/clear/checkbox so the schema-level refine sees the field and Create stops silently no-op'ing on submit. - bulk-add-berths-wizard: side-pontoon dropdown now reads through useVocabulary('berth_side_pontoon_options') instead of a wrong local enum ('Port', 'Starboard', 'Bow', 'Stern') — wizard data now matches the rest of the platform + honours admin-editable per-port overrides. tsc clean. 1419/1419 vitest. lint clean on touched files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:50:58 +02:00
const invalidateAll = () => {
queryClient.invalidateQueries({ queryKey });
if (parentInvalidateKey) {
queryClient.invalidateQueries({ queryKey: parentInvalidateKey });
}
};
const [newNote, setNewNote] = useState('');
const [editingId, setEditingId] = useState<string | null>(null);
const [editContent, setEditContent] = useState('');
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 [groupBySource, setGroupBySource] = useState(false);
fix(compiler): React Compiler safety triage — 5 categories cleared Cleared 4 rule buckets (37 violations, including 5 real bugs) and silenced 1 informational bucket from the Next 16 / react-hooks v7 upgrade. Cleared rules promoted from `warn` back to `error` so new regressions block CI. Real bug fixes: - `interest-contact-log-tab.tsx`: `useMemo` used for side effects (5 setState calls inside a memo body); converted to `useEffect`. - `PieChart.tsx`: cumulative `let angle` mutation in a render-phase `map`; converted to `reduce` so the slice array is built without re-assignment. - `documents-hub.tsx`: `useMemo(() => ({ count: 0 }))` used as a mutable drag counter; converted to `useRef`. - `notes-list.tsx`: `Date.now()` read during render for note-edit countdown (impure) → pinned to a `now` state ticked every 30s. - `onboarding-checklist.tsx` / `user-profile.tsx` / `user-settings.tsx`: `useEffect(() => void load(), [])` with the `load` function declared AFTER the effect — relied on hoisting, trips Compiler's "access before declared" rule. Declared inside the effect. Pattern fixes (intentional cache-via-ref → state or layout-effect): - 6 `ref.current = x` writes during render moved into layout effects (`use-realtime-invalidation`, `settings-form-card`, `inbox`). - 3 `ref.current` reads during render (search totals cache, scanner file ref) rewritten to backed-by-state. - `use-is-mobile.ts` rewritten on `useSyncExternalStore` to avoid the SSR-then-rehydrate setState dance. - `use-notifications.ts` rewritten to write socket pushes directly into the React Query cache via `setQueryData`, removing a local state mirror. Rule config (`eslint.config.mjs`): - `react-hooks/purity` → error (was warn, cleared) - `react-hooks/set-state-in-render` → error (was warn, cleared) - `react-hooks/immutability` → error (was warn, cleared) - `react-hooks/refs` → error (was warn, cleared) - `react-hooks/incompatible-library` → off (informational only) - `react-hooks/set-state-in-effect` → warn (51 remaining, all the useEffect→fetch→setState data-fetch pattern; migration to useQuery tracked in BACKLOG) Verified: tsc clean, eslint 0 errors / 69 warnings (down from 105), vitest 1315/1315, next build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:14:16 +02:00
// Wall-clock 'now' ticked every 30s so the per-note "Xm left to edit"
// countdown decrements on screen. Reading `Date.now()` directly inside
// render is impure (different value every call); pinning to a state
// value means React Compiler can memoize cleanly.
const [now, setNow] = useState(() => Date.now());
useEffect(() => {
const id = setInterval(() => setNow(Date.now()), 30_000);
return () => clearInterval(id);
}, []);
const aggregateOn = !!aggregate && AGGREGATABLE.has(entityType);
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 baseEndpoint = `/api/v1/${entityType}/${entityId}/notes`;
const listEndpoint = aggregateOn ? `${baseEndpoint}?aggregate=true` : baseEndpoint;
const queryKey = [entityType, entityId, 'notes', aggregateOn ? 'aggregated' : 'own'];
// Smooth animation when notes are added / edited / deleted - replaces
// the abrupt re-render with a per-row fade/slide.
const [animateRef] = useAutoAnimate<HTMLDivElement>();
const { data: notes = [], isLoading } = useQuery<Note[]>({
queryKey,
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
queryFn: () => apiFetch<{ data: Note[] }>(listEndpoint).then((r) => r.data),
});
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
// Mutations always target the parent entity (client). Aggregated
// notes from interests/yachts are read-only here - the rep edits
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
// them on the source entity's page (we surface a "Open source" link
// below). Keeping mutations against `baseEndpoint` keeps the POST
// route handler clean.
const createMutation = useMutation({
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
mutationFn: (content: string) => apiFetch(baseEndpoint, { method: 'POST', body: { content } }),
onSuccess: () => {
fix(uat-batch-1): wave-1 blocker bugs — supplemental gate, file FK, downloads, search dedup, notes stale, expense form, vocab Surgical fixes for the 7 UAT blockers that prevent productive forward testing. Each item has a corresponding entry in alpha-uat-master.md. - supplemental-info route relocated out of (portal) so it bypasses the isPortalDisabledGlobally() kill-switch. URL unchanged. - file upload service derives client_id/company_id/yacht_id from (entityType, entityId) when not explicitly passed, so interest-tab uploads no longer land with client_id=NULL and stay visible in the Attachments list. - triggerBlobDownload / triggerUrlDownload helpers in src/lib/utils attach the anchor to the DOM before click so Chromium honours the download attribute; 7 sites refactored, file-named downloads stop arriving as bare UUIDs. - search-nav-catalog dedupes by href at the result-collection layer so the same href can no longer surface twice in the command-K dropdown (kills the React duplicate-key warning); /admin/templates entries merged into a single richer-keyword variant. - NotesList gains a parentInvalidateKey prop, wired through all five callers (interest, client, yacht, company, residential client/ interest) so the Overview "Latest note" teaser refreshes when a note is added in the Notes tab. - expense-form-dialog: setValue('receiptFileIds') / setValue( 'noReceiptAcknowledged') on upload/clear/checkbox so the schema-level refine sees the field and Create stops silently no-op'ing on submit. - bulk-add-berths-wizard: side-pontoon dropdown now reads through useVocabulary('berth_side_pontoon_options') instead of a wrong local enum ('Port', 'Starboard', 'Bow', 'Stern') — wizard data now matches the rest of the platform + honours admin-editable per-port overrides. tsc clean. 1419/1419 vitest. lint clean on touched files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:50:58 +02:00
invalidateAll();
setNewNote('');
},
});
const updateMutation = useMutation({
mutationFn: ({ noteId, content }: { noteId: string; content: 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
apiFetch(`${baseEndpoint}/${noteId}`, { method: 'PATCH', body: { content } }),
onSuccess: () => {
fix(uat-batch-1): wave-1 blocker bugs — supplemental gate, file FK, downloads, search dedup, notes stale, expense form, vocab Surgical fixes for the 7 UAT blockers that prevent productive forward testing. Each item has a corresponding entry in alpha-uat-master.md. - supplemental-info route relocated out of (portal) so it bypasses the isPortalDisabledGlobally() kill-switch. URL unchanged. - file upload service derives client_id/company_id/yacht_id from (entityType, entityId) when not explicitly passed, so interest-tab uploads no longer land with client_id=NULL and stay visible in the Attachments list. - triggerBlobDownload / triggerUrlDownload helpers in src/lib/utils attach the anchor to the DOM before click so Chromium honours the download attribute; 7 sites refactored, file-named downloads stop arriving as bare UUIDs. - search-nav-catalog dedupes by href at the result-collection layer so the same href can no longer surface twice in the command-K dropdown (kills the React duplicate-key warning); /admin/templates entries merged into a single richer-keyword variant. - NotesList gains a parentInvalidateKey prop, wired through all five callers (interest, client, yacht, company, residential client/ interest) so the Overview "Latest note" teaser refreshes when a note is added in the Notes tab. - expense-form-dialog: setValue('receiptFileIds') / setValue( 'noReceiptAcknowledged') on upload/clear/checkbox so the schema-level refine sees the field and Create stops silently no-op'ing on submit. - bulk-add-berths-wizard: side-pontoon dropdown now reads through useVocabulary('berth_side_pontoon_options') instead of a wrong local enum ('Port', 'Starboard', 'Bow', 'Stern') — wizard data now matches the rest of the platform + honours admin-editable per-port overrides. tsc clean. 1419/1419 vitest. lint clean on touched files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:50:58 +02:00
invalidateAll();
setEditingId(null);
},
});
const deleteMutation = useMutation({
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
mutationFn: (noteId: string) => apiFetch(`${baseEndpoint}/${noteId}`, { method: 'DELETE' }),
fix(uat-batch-1): wave-1 blocker bugs — supplemental gate, file FK, downloads, search dedup, notes stale, expense form, vocab Surgical fixes for the 7 UAT blockers that prevent productive forward testing. Each item has a corresponding entry in alpha-uat-master.md. - supplemental-info route relocated out of (portal) so it bypasses the isPortalDisabledGlobally() kill-switch. URL unchanged. - file upload service derives client_id/company_id/yacht_id from (entityType, entityId) when not explicitly passed, so interest-tab uploads no longer land with client_id=NULL and stay visible in the Attachments list. - triggerBlobDownload / triggerUrlDownload helpers in src/lib/utils attach the anchor to the DOM before click so Chromium honours the download attribute; 7 sites refactored, file-named downloads stop arriving as bare UUIDs. - search-nav-catalog dedupes by href at the result-collection layer so the same href can no longer surface twice in the command-K dropdown (kills the React duplicate-key warning); /admin/templates entries merged into a single richer-keyword variant. - NotesList gains a parentInvalidateKey prop, wired through all five callers (interest, client, yacht, company, residential client/ interest) so the Overview "Latest note" teaser refreshes when a note is added in the Notes tab. - expense-form-dialog: setValue('receiptFileIds') / setValue( 'noReceiptAcknowledged') on upload/clear/checkbox so the schema-level refine sees the field and Create stops silently no-op'ing on submit. - bulk-add-berths-wizard: side-pontoon dropdown now reads through useVocabulary('berth_side_pontoon_options') instead of a wrong local enum ('Port', 'Starboard', 'Bow', 'Stern') — wizard data now matches the rest of the platform + honours admin-editable per-port overrides. tsc clean. 1419/1419 vitest. lint clean on touched files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:50:58 +02:00
onSuccess: () => invalidateAll(),
});
function canEdit(note: Note): boolean {
if (note.authorId !== currentUserId) return false;
if (note.isLocked) return false;
// Aggregated view: only notes from THIS entity itself are editable
// in-place. Notes pulled in from related entities (e.g. interests
// surfaced under a client) must be edited on the source page so the
// owning entity's timeline records the change.
const selfSource = SELF_SOURCE[entityType];
if (aggregateOn && note.source && note.source !== selfSource) return false;
fix(compiler): React Compiler safety triage — 5 categories cleared Cleared 4 rule buckets (37 violations, including 5 real bugs) and silenced 1 informational bucket from the Next 16 / react-hooks v7 upgrade. Cleared rules promoted from `warn` back to `error` so new regressions block CI. Real bug fixes: - `interest-contact-log-tab.tsx`: `useMemo` used for side effects (5 setState calls inside a memo body); converted to `useEffect`. - `PieChart.tsx`: cumulative `let angle` mutation in a render-phase `map`; converted to `reduce` so the slice array is built without re-assignment. - `documents-hub.tsx`: `useMemo(() => ({ count: 0 }))` used as a mutable drag counter; converted to `useRef`. - `notes-list.tsx`: `Date.now()` read during render for note-edit countdown (impure) → pinned to a `now` state ticked every 30s. - `onboarding-checklist.tsx` / `user-profile.tsx` / `user-settings.tsx`: `useEffect(() => void load(), [])` with the `load` function declared AFTER the effect — relied on hoisting, trips Compiler's "access before declared" rule. Declared inside the effect. Pattern fixes (intentional cache-via-ref → state or layout-effect): - 6 `ref.current = x` writes during render moved into layout effects (`use-realtime-invalidation`, `settings-form-card`, `inbox`). - 3 `ref.current` reads during render (search totals cache, scanner file ref) rewritten to backed-by-state. - `use-is-mobile.ts` rewritten on `useSyncExternalStore` to avoid the SSR-then-rehydrate setState dance. - `use-notifications.ts` rewritten to write socket pushes directly into the React Query cache via `setQueryData`, removing a local state mirror. Rule config (`eslint.config.mjs`): - `react-hooks/purity` → error (was warn, cleared) - `react-hooks/set-state-in-render` → error (was warn, cleared) - `react-hooks/immutability` → error (was warn, cleared) - `react-hooks/refs` → error (was warn, cleared) - `react-hooks/incompatible-library` → off (informational only) - `react-hooks/set-state-in-effect` → warn (51 remaining, all the useEffect→fetch→setState data-fetch pattern; migration to useQuery tracked in BACKLOG) Verified: tsc clean, eslint 0 errors / 69 warnings (down from 105), vitest 1315/1315, next build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:14:16 +02:00
const elapsed = now - new Date(note.createdAt).getTime();
return elapsed < NOTE_EDIT_WINDOW_MS;
}
function getTimeRemaining(note: Note): string | null {
fix(compiler): React Compiler safety triage — 5 categories cleared Cleared 4 rule buckets (37 violations, including 5 real bugs) and silenced 1 informational bucket from the Next 16 / react-hooks v7 upgrade. Cleared rules promoted from `warn` back to `error` so new regressions block CI. Real bug fixes: - `interest-contact-log-tab.tsx`: `useMemo` used for side effects (5 setState calls inside a memo body); converted to `useEffect`. - `PieChart.tsx`: cumulative `let angle` mutation in a render-phase `map`; converted to `reduce` so the slice array is built without re-assignment. - `documents-hub.tsx`: `useMemo(() => ({ count: 0 }))` used as a mutable drag counter; converted to `useRef`. - `notes-list.tsx`: `Date.now()` read during render for note-edit countdown (impure) → pinned to a `now` state ticked every 30s. - `onboarding-checklist.tsx` / `user-profile.tsx` / `user-settings.tsx`: `useEffect(() => void load(), [])` with the `load` function declared AFTER the effect — relied on hoisting, trips Compiler's "access before declared" rule. Declared inside the effect. Pattern fixes (intentional cache-via-ref → state or layout-effect): - 6 `ref.current = x` writes during render moved into layout effects (`use-realtime-invalidation`, `settings-form-card`, `inbox`). - 3 `ref.current` reads during render (search totals cache, scanner file ref) rewritten to backed-by-state. - `use-is-mobile.ts` rewritten on `useSyncExternalStore` to avoid the SSR-then-rehydrate setState dance. - `use-notifications.ts` rewritten to write socket pushes directly into the React Query cache via `setQueryData`, removing a local state mirror. Rule config (`eslint.config.mjs`): - `react-hooks/purity` → error (was warn, cleared) - `react-hooks/set-state-in-render` → error (was warn, cleared) - `react-hooks/immutability` → error (was warn, cleared) - `react-hooks/refs` → error (was warn, cleared) - `react-hooks/incompatible-library` → off (informational only) - `react-hooks/set-state-in-effect` → warn (51 remaining, all the useEffect→fetch→setState data-fetch pattern; migration to useQuery tracked in BACKLOG) Verified: tsc clean, eslint 0 errors / 69 warnings (down from 105), vitest 1315/1315, next build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:14:16 +02:00
const elapsed = now - new Date(note.createdAt).getTime();
const remaining = NOTE_EDIT_WINDOW_MS - elapsed;
if (remaining <= 0) return null;
const mins = Math.ceil(remaining / 60000);
return `${mins}m left to edit`;
}
return (
<div className="space-y-4">
{/* Create note form */}
<div className="space-y-2">
<Textarea
placeholder="Add a note..."
value={newNote}
onChange={(e) => setNewNote(e.target.value)}
rows={3}
/>
<div className="flex justify-end">
<Button
size="sm"
disabled={!newNote.trim() || createMutation.isPending}
onClick={() => createMutation.mutate(newNote.trim())}
>
{createMutation.isPending ? (
<Loader2 className="mr-1.5 h-4 w-4 animate-spin" aria-hidden />
) : (
<Send className="mr-1.5 h-4 w-4" aria-hidden />
)}
Add Note
</Button>
</div>
</div>
{/* Aggregated-mode controls - sort toggle. Only renders when
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
* aggregation is on and there's actually content to group. */}
{aggregateOn && notes.length > 0 && (
<div className="flex items-center justify-end gap-2 text-xs text-muted-foreground">
<span>View:</span>
<button
type="button"
className={!groupBySource ? 'font-medium text-foreground' : 'hover:text-foreground'}
onClick={() => setGroupBySource(false)}
>
Chronological
</button>
<span>·</span>
<button
type="button"
className={groupBySource ? 'font-medium text-foreground' : 'hover:text-foreground'}
onClick={() => setGroupBySource(true)}
>
Group by source
</button>
</div>
)}
{/* Notes list */}
{isLoading ? (
<div className="text-center py-8 text-muted-foreground">Loading notes...</div>
) : notes.length === 0 ? (
<div className="text-center py-8 text-muted-foreground">No notes yet</div>
) : (
<div ref={animateRef} className="space-y-3">
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
{(groupBySource ? sortByGroup(notes) : notes).map((note) => (
<div key={note.id} className="flex gap-3 p-3 rounded-lg border bg-card">
<Avatar className="h-8 w-8 shrink-0">
<AvatarFallback className="text-xs">
{(note.authorName ?? 'U').charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0 space-y-1">
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
<div className="flex items-center gap-2 text-sm flex-wrap">
<span className="font-medium">{note.authorName ?? 'User'}</span>
<span className="text-muted-foreground">
{formatDistanceToNow(new Date(note.createdAt), { addSuffix: true })}
</span>
{aggregateOn &&
note.source &&
note.source !== SELF_SOURCE[entityType] &&
feat(uat-polish): live-UAT round — dialog widths, recommender polish, inline create, tenancy + notes plumbing Compendium of polish + small-fix work captured during the 2026-05-26 live UAT session. Every change has a corresponding entry in docs/superpowers/audits/active-uat.md with file:line evidence + root cause + alternatives considered. Dialog primitive width - DialogContent default bumped from sm:max-w-lg (512px) to sm:max-w-xl + lg:max-w-3xl so every consumer gets a sane desktop default. Confirm dialogs override DOWN, content-heavy dialogs override UP. - FilePreviewDialog full-viewport via w-[min(95vw,1400px)] + h-[85vh] so PDFs render at usable width on real desktops. Recommender card - Heat badge now a Popover with the score (X/100), the formula in plain English, the four component breakdowns (recency / furthest stage / interest count / EOI count), and a pointer to the admin weight tuning page. - Area letter span dropped from the card header - mooring number already prefixes it. - BerthRecommenderPanel + the dedicated "Berth Recommendations" tab both hidden when interest.desiredLengthFt is null. The empty guidance card was reading as noise. interest-tabs.tsx computes hasDesiredDims once and gates the inline mount + tab strip spread off it. BerthPicker - Drop area suffix from row labels. Mooring number already carries the area letter prefix; group heading conveys the same context. Same fix flows to every BerthPicker consumer (tenancy create/renew/transfer, interest form, linked-berths picker). CreateDocumentWizard - DOCUMENT_TYPE_LABELS constant added to constants.ts. Wizard reads from the map instead of naive replace(/_/g, ' '): "EOI", "Contract", "NDA", "Reservation Agreement", "Other". - "Other" option surfaces a hint pointing the rep at the Title field so they describe what the doc actually is. InterestForm inline client + yacht create - ClientForm gains an onCreated(clientId) callback. Mutation returns { id } in create mode so onSuccess can forward. - InterestForm renders an "Add new" Button next to the Client label (create mode only - hidden on edit), opens ClientForm, auto- selects the new client into the draft. Mirrors the existing inline yacht-create pattern. - Reset path includes source: 'manual' alongside the other create- mode defaults; the manual flow was dropping back to a blank source dropdown on reopen. Tenancy list - ClientTenanciesTab activeTenancies query now includes status IN ('pending', 'active'). Was filtering to active-only; pending rows from manual create + webhook auto-create were invisible on the client detail's Tenancies tab. - TenancyList rows are now keyboard- and click-navigable to the tenancy detail page (Enter/Space included). Inner links + buttons stop propagation so per-cell navigation works. NotesList source badge - Aggregated-mode source badge ("Yacht / Test Yacht") is now a Link to the source entity's detail page. New sourceLinkFor helper centralises the URL mapping across clients/companies/yachts/ interests + residential variants. Yacht transfer audit log - transferOwnership emits a distinct 'transfer' AuditAction (added to AuditAction union in src/lib/audit.ts) with old/new owner names resolved at write time. EntityActivityFeed renders "Matt transferred owner to Jane Smith" instead of "Matt updated this record." formatValueForField unwraps the { name } shape so the audit_logs Record<string, unknown> typing stays clean. - yacht-transfer-dialog copy: dropped "atomic" jargon. Reads "The change is logged in the audit history" instead. Companies autocomplete - /api/v1/companies/autocomplete now returns the 10 most-recently- updated companies when the query string is empty. Was returning []. CompanyPicker popover opens with results to scan instead of a blank dropdown. DocumentsHub FlatFolderListing - Uploaded files (the files table) now merge into the documents table view via a parallel /api/v1/files?folderId=X query + client-side merge into a unified row list. listFiles service honours the folderId filter that was already accepted by the validator. New renderFileRow renders file rows with an "Uploaded file" type pill + "Stored" status pill, links the filename to the download URL. Existing FolderDropZone invalidation covers the new query, so drag-drop and New-document-menu uploads refresh the list without a page reload. - FlatFolderListing wrapped in a vertically-spaced container so subfolders / search row / list have consistent gap. - Per-row chevron only renders when totalSigners > 0; empty placeholder column kept so grid alignment doesn't jump. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 20:07:45 +02:00
note.sourceLabel &&
(() => {
// Source badge links to the originating entity so reps
// can pivot from "this note about a linked yacht" to
// the yacht detail page directly. Falls back to a
// plain span when no sourceId is present (rare; aggregator
// returns it for every materialised note).
const sourceHref = note.sourceId
? sourceLinkFor(portSlug, note.source, note.sourceId)
: null;
const className = `inline-flex items-center rounded-full px-1.5 py-0.5 text-xs font-medium ${SOURCE_BADGE_CLASS[note.source]} ${sourceHref ? 'hover:opacity-80 transition-opacity' : ''}`;
const body = `${SOURCE_LABEL[note.source]} · ${note.sourceLabel}`;
const title = `Open this ${note.source}`;
return sourceHref ? (
<Link
href={sourceHref as never}
className={className}
title={title}
onClick={(e) => e.stopPropagation()}
>
{body}
</Link>
) : (
<span className={className} title={`From ${note.source}`}>
{body}
</span>
);
})()}
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing- progress redesign + env-to-admin migration + dev-mode banner) with the 2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW). CRITICAL (3): - C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths no longer silently drop interest links - C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed - C-03 generic PATCH /interests/[id] no longer accepts pipelineStage — callers must go through /stage with the override-guard chain HIGH (14/15): - H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across interests/documents/reservations/reminders/invoices (migration 0070) - H-02 login page reads ?redirect= param with same-origin guard - H-03 CRM invite token moves to URL fragment so it never lands in nginx access logs / Referer headers - H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4) - H-05 toggleAccount writes an audit row - H-06 upsertSetting masks any value whose key ends with _encrypted - H-07 archiveClient cascade fires per-interest audit rows - H-08 createSalesTransporter applies SMTP_TIMEOUTS - H-09 AppShell stable children — viewport flip across breakpoint no longer destroys in-progress form drafts - H-10 portal documents page swaps Unicode glyph status icons for Lucide CheckCircle2/XCircle/Circle + aria-labels - H-12 list components swap alert(...) for toast.warning(...) - H-13 5 icon-only buttons gain aria-label - H-14 parseBody treats empty bodies as {} - H-15 admin layout renders a 403 panel instead of silent bounce - H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet MEDIUM (28+): - M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE WHEREs across custom-fields, notes (all 6 entity types x update + delete), client-contacts, yacht ownerClient lookup, webhook reads - M-D01 documents-hub realtime event-name typo (file:created -> uploaded) - M-EM01 portal-auth emails thread through portId - M-EM02 sendEmail accepts cc/bcc params - M-EM04 notification_digest catalog key - M-IN01 portal presigned download URLs use 4h TTL - M-IN02 OpenAI client lazy-instantiated - M-IN04 stale pdfme refs updated to pdf-lib AcroForm - M-IN05 umami.testConnection returns tagged union - M-L01 reservations tenure_type unified with berths - M-L02 report-generators canonicalize stage values - M-AU01 audit log placeholder copy fixed - M-AU04 outcome_set / outcome_cleared distinct audit verbs - M-NEW-2 activity feed entity name+type separator - M-R01 portal allowlist narrowed + portal_session backstop in proxy - M-SC02 companies archived partial index - M-SC04 audit_logs.searchText documented as DB-managed - M-S01 storage_s3_access_key_encrypted admin field - M-U01 audit log empty state uses <EmptyState> - M-U09 invoice delete dialog -> <AlertDialog> - M-U10 toast.success on ClientForm + InterestForm create/edit - M-U11 settings-form-card logo preview alt text - M-U14 mobile topbar title on clients/yachts/interests/berths - M-U15 Invoices in mobile More-sheet LOW (6/8): - L-AU01 severity defaults for security-relevant verbs - L-AU02 +13 missing actions in admin audit filter - L-AU03 +7 missing entity types in admin audit filter - L-AU04 dead listAuditLogs stubbed - L-D02 CLAUDE.md Owner-wins chain tightened Bonus — Document detail polish (#67 partial, 3/6 deliverables): - state-aware action button per signer - watcher Add UI with display-name resolution - cleanSignerName cleanup Prior session work bundled in: - Documenso v2 webhook + envelope-ID normalization + sequential signing - SigningProgress UI redesign (avatars, per-signer state, timestamps) - env->admin settings registry + RegistryDrivenForm + encrypted creds - Embedded-signing card + Test connection + setup help - Dev-mode EMAIL_REDIRECT_TO banner - Pipeline rules admin page - Sales email config card - Audit log details Sheet - EOI tab: Finalising badge, absolute timestamps, sequential indicator - Notes pipeline_stage_at_creation (migration 0069) - Documenso numeric ID dual-key webhook (migration 0068) - Dimensions criterion copy (migration 0067) Tests: 1374/1374 vitest pass. tsc clean. lint clean. See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and the user-input items still pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:28:50 +02:00
{/* Pipeline-stage stamp: shows what stage the linked
interest was at when the note was authored. Lets a
rep trace how the deal's notes evolved (concerns
raised at qualified vs after reservation). Only
populated for interest notes from 2026-05-15+. */}
{note.pipelineStageAtCreation && (
<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 bg-indigo-50 px-1.5 py-0.5 text-xs font-medium text-indigo-900"
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing- progress redesign + env-to-admin migration + dev-mode banner) with the 2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW). CRITICAL (3): - C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths no longer silently drop interest links - C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed - C-03 generic PATCH /interests/[id] no longer accepts pipelineStage — callers must go through /stage with the override-guard chain HIGH (14/15): - H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across interests/documents/reservations/reminders/invoices (migration 0070) - H-02 login page reads ?redirect= param with same-origin guard - H-03 CRM invite token moves to URL fragment so it never lands in nginx access logs / Referer headers - H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4) - H-05 toggleAccount writes an audit row - H-06 upsertSetting masks any value whose key ends with _encrypted - H-07 archiveClient cascade fires per-interest audit rows - H-08 createSalesTransporter applies SMTP_TIMEOUTS - H-09 AppShell stable children — viewport flip across breakpoint no longer destroys in-progress form drafts - H-10 portal documents page swaps Unicode glyph status icons for Lucide CheckCircle2/XCircle/Circle + aria-labels - H-12 list components swap alert(...) for toast.warning(...) - H-13 5 icon-only buttons gain aria-label - H-14 parseBody treats empty bodies as {} - H-15 admin layout renders a 403 panel instead of silent bounce - H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet MEDIUM (28+): - M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE WHEREs across custom-fields, notes (all 6 entity types x update + delete), client-contacts, yacht ownerClient lookup, webhook reads - M-D01 documents-hub realtime event-name typo (file:created -> uploaded) - M-EM01 portal-auth emails thread through portId - M-EM02 sendEmail accepts cc/bcc params - M-EM04 notification_digest catalog key - M-IN01 portal presigned download URLs use 4h TTL - M-IN02 OpenAI client lazy-instantiated - M-IN04 stale pdfme refs updated to pdf-lib AcroForm - M-IN05 umami.testConnection returns tagged union - M-L01 reservations tenure_type unified with berths - M-L02 report-generators canonicalize stage values - M-AU01 audit log placeholder copy fixed - M-AU04 outcome_set / outcome_cleared distinct audit verbs - M-NEW-2 activity feed entity name+type separator - M-R01 portal allowlist narrowed + portal_session backstop in proxy - M-SC02 companies archived partial index - M-SC04 audit_logs.searchText documented as DB-managed - M-S01 storage_s3_access_key_encrypted admin field - M-U01 audit log empty state uses <EmptyState> - M-U09 invoice delete dialog -> <AlertDialog> - M-U10 toast.success on ClientForm + InterestForm create/edit - M-U11 settings-form-card logo preview alt text - M-U14 mobile topbar title on clients/yachts/interests/berths - M-U15 Invoices in mobile More-sheet LOW (6/8): - L-AU01 severity defaults for security-relevant verbs - L-AU02 +13 missing actions in admin audit filter - L-AU03 +7 missing entity types in admin audit filter - L-AU04 dead listAuditLogs stubbed - L-D02 CLAUDE.md Owner-wins chain tightened Bonus — Document detail polish (#67 partial, 3/6 deliverables): - state-aware action button per signer - watcher Add UI with display-name resolution - cleanSignerName cleanup Prior session work bundled in: - Documenso v2 webhook + envelope-ID normalization + sequential signing - SigningProgress UI redesign (avatars, per-signer state, timestamps) - env->admin settings registry + RegistryDrivenForm + encrypted creds - Embedded-signing card + Test connection + setup help - Dev-mode EMAIL_REDIRECT_TO banner - Pipeline rules admin page - Sales email config card - Audit log details Sheet - EOI tab: Finalising badge, absolute timestamps, sequential indicator - Notes pipeline_stage_at_creation (migration 0069) - Documenso numeric ID dual-key webhook (migration 0068) - Dimensions criterion copy (migration 0067) Tests: 1374/1374 vitest pass. tsc clean. lint clean. See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and the user-input items still pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:28:50 +02:00
title="Pipeline stage when note was authored"
>
@ {stageLabel(note.pipelineStageAtCreation)}
</span>
)}
{note.isLocked && <Lock className="h-3 w-3 text-muted-foreground" aria-hidden />}
{canEdit(note) && (
feat(platform): residential module + admin UI + reliability fixes Residential platform - New schema: residentialClients, residentialInterests (separate from marina/yacht clients) with migration 0010 - Service layer with CRUD + audit + sockets + per-port portal toggle - v1 + public API routes (/api/v1/residential/*, /api/public/residential-inquiries) - List + detail pages with inline editing for clients and interests - Per-user residentialAccess toggle on userPortRoles (migration 0011) - Permission keys: residential_clients, residential_interests - Sidebar nav + role form integration - Smoke spec covering page loads, UI create flow, public endpoint Admin & shared UI - Admin → Forms (form templates CRUD) with validators + service - Notification preferences page (in-app + email per type) - Email composition + accounts list + threads view - Branded auth shell shared across CRM + portal auth surfaces - Inline editing extended to yacht/company/interest detail pages - InlineTagEditor + per-entity tags endpoints (yachts, companies) - Notes service polymorphic across clients/interests/yachts/companies - Client list columns: yachtCount + companyCount badges - Reservation file-download via presigned URL (replaces stale <a href>) Route handler refactor - Extracted yachts/companies/berths reservation handlers to sibling handlers.ts files (Next.js 15 route.ts only allows specific exports) Reliability fixes - apiFetch double-stringify bug fixed across 13 components (apiFetch already JSON.stringifies its body; passing a stringified body produced double-encoded JSON which failed zod validation) - SocketProvider gated behind useSyncExternalStore-based mount check to avoid useSession() SSR crashes under React 19 + Next 15 - apiFetch falls back to URL-pathname → port-id resolution when the Zustand store hasn't hydrated yet (fresh contexts, e2e tests) - CRM invite flow (schema, service, route, email, dev script) - Dashboard route → [portSlug]/dashboard/page.tsx + redirect - Document the dev-server restart-after-migration gotcha in CLAUDE.md Tests - 5-case residential smoke spec - Integration test updates for new service signatures Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:54:32 +02:00
<span className="text-xs text-muted-foreground">{getTimeRemaining(note)}</span>
)}
</div>
{editingId === note.id ? (
<div className="space-y-2">
<Textarea
value={editContent}
onChange={(e) => setEditContent(e.target.value)}
rows={3}
/>
<div className="flex gap-2">
<Button
size="sm"
disabled={!editContent.trim() || updateMutation.isPending}
onClick={() =>
updateMutation.mutate({ noteId: note.id, content: editContent.trim() })
}
>
Save
</Button>
<Button size="sm" variant="ghost" onClick={() => setEditingId(null)}>
Cancel
</Button>
</div>
</div>
) : (
<p className="text-sm whitespace-pre-wrap">{note.content}</p>
)}
</div>
{canEdit(note) && editingId !== note.id && (
<div className="flex gap-1 shrink-0">
<Button
variant="ghost"
size="icon"
className="h-7 w-7"
onClick={() => {
setEditingId(note.id);
setEditContent(note.content);
}}
>
<Pencil className="h-3.5 w-3.5" aria-hidden />
</Button>
<Button
variant="ghost"
size="icon"
className="h-7 w-7 text-destructive"
onClick={() => deleteMutation.mutate(note.id)}
>
<Trash2 className="h-3.5 w-3.5" aria-hidden />
</Button>
</div>
)}
</div>
))}
</div>
)}
</div>
);
}