chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged: - Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances) - country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk after the per-subpath dynamic-import approach silently failed in webpack) - Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index, redirects (ocr to ai, reports to dashboard, invitations to users), docs/admin-ia-proposal.md - Per-template email tester (registry + endpoint + UI on Email admin page) - Cancel-document mode picker (delete-from-Documenso vs keep-for-audit) - Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers - Customize-widgets per-region sortables at xl+ (charts/rails/feed); single flat sortable below xl when the layout stacks; per-viewport saved orders - Audit doc updates capturing each shipped item - Lint fixes: react-compiler immutability in DonutChart (reduce instead of let-reassign), set-state-in-effect disables in CountryFlag and UploadForSigning preview-bytes effect, unused 'confirm' destructures in interest contract + reservation tabs, unescaped apostrophe in test-template card copy
This commit is contained in:
415
docs/admin-ia-proposal.md
Normal file
415
docs/admin-ia-proposal.md
Normal file
@@ -0,0 +1,415 @@
|
||||
# Admin IA — Audit + Proposed Regrouping
|
||||
|
||||
**Status:** Phase 1 (proposal + decisions) — captured 2026-05-22 from B3 #10. Open questions resolved in section 7; final IA reflected in section 8. Phase 2 (execution) is mechanical from here.
|
||||
|
||||
## Resolved decisions (2026-05-22)
|
||||
|
||||
User answered the 5 open questions from section 7:
|
||||
|
||||
1. **Forms + Document Templates** → moved to **Sales workflow** (not "Content"). Both are workflow inputs, not abstract content.
|
||||
2. **Webhooks** → keep as its own thing; **new "Integrations" domain** is the right home (Webhooks + Documenso + Website analytics + AI all belong together as "external system + provider config").
|
||||
3. **AI configuration** → keep a dedicated `/admin/ai` panel that consolidates every AI feature in one place; lives under the new **Integrations** domain.
|
||||
4. **`/admin/reports`** → **DELETE entirely** (confirmed duplicative — the dashboard already renders Pipeline funnel + Berth occupancy + KPI cards via widgets). Redirect to `/[portSlug]/dashboard`.
|
||||
5. **`/admin/settings`** (generic KV editor) → keep visible to all admins under System & observability.
|
||||
|
||||
**Net effect:** 7 domains instead of 6; 3 pages deleted (ocr, invitations, reports) instead of 2. Final IA in section 8.
|
||||
|
||||
---
|
||||
|
||||
**Goal:** today's 41 admin pages are organically grown and discoverability is poor (test-email lives on Branding, an SMTP test on Email, an OCR-settings duplicate exists on both `/admin/ai` and `/admin/ocr`, etc.). Below: page-by-page inventory + a recommended IA in 6 domains.
|
||||
|
||||
**Out of scope here:** the actual file moves, route redirects, and nav updates. That's Phase 2 (~4–6h once the IA below is locked).
|
||||
|
||||
---
|
||||
|
||||
## 1. Page-by-page inventory (current state, 41 pages)
|
||||
|
||||
Sorted alphabetically. Each row: what the page renders today + its current admin-sections-browser group.
|
||||
|
||||
| Route | What it renders | Current group |
|
||||
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
|
||||
| `/admin` | `<AdminSectionsBrowser>` — landing tile grid grouped into 5 categories | — |
|
||||
| `/admin/ai` | `<RegistryDrivenForm>` (ai master controls + provider credentials) + `<OcrSettingsForm>` + AI-suggestions card | Operations |
|
||||
| `/admin/audit` | `<AuditLogList>` — full mutation log search | Data Quality |
|
||||
| `/admin/backup` | `<BackupAdminPanel>` — backup posture (read-only) | Operations |
|
||||
| `/admin/berths/bulk-add` | `<BulkAddBerthsWizard>` — generate berth rows in bulk | (not in landing browser) |
|
||||
| `/admin/berths/reconcile` | `<ReconcileQueue>` — review berths missing required fields | (not in landing browser) |
|
||||
| `/admin/branding` | `<RegistryDrivenForm sections={['branding']}>` (identity) + email branding form + `<PdfLogoUploader>` + `<EmailPreviewCard>` | Configuration |
|
||||
| `/admin/brochures` | `<BrochuresAdminPanel>` — upload/version port brochures | (not in landing browser) |
|
||||
| `/admin/custom-fields` | `<CustomFieldsManager>` — per-entity custom-field definitions | Content |
|
||||
| `/admin/documenso` | `<RegistryDrivenForm>` (api creds, signers, templates, behavior) + `<DocumensoTestButton>` + `<TemplateSyncButton>` + `<EmbeddedSigningCard>` | Configuration ("EOI signing service") |
|
||||
| `/admin/duplicates` | `<DuplicatesReviewQueue>` — suspected-duplicate clients | Data Quality |
|
||||
| `/admin/email` | `<RegistryDrivenForm>` (from address + smtp) + `<SmtpTestSendCard>` + `<TestTemplateCard>` (new) + `<SalesEmailConfigCard>` + `<EmailRoutingCard>` | Configuration |
|
||||
| `/admin/email-templates` | `<EmailTemplatesAdmin>` — subject-line overrides per transactional template | Content |
|
||||
| `/admin/errors` | error-event list (system errors) | (not in landing browser) |
|
||||
| `/admin/errors/codes` | error-code catalog reference | (not in landing browser) |
|
||||
| `/admin/errors/[requestId]` | single error-event detail | (not in landing browser) |
|
||||
| `/admin/forms` | `<FormTemplateList>` — public inquiry/intake form schemas | Content |
|
||||
| `/admin/import` | CSV import wizard | Data Quality ("Bulk Import") |
|
||||
| `/admin/inquiries` | `<InquiryInbox>` — public-site submissions awaiting triage | Data Quality |
|
||||
| `/admin/invitations` | (empty body — comment says merged into `/admin/users` 2026-05-21) | (not in landing browser) |
|
||||
| `/admin/monitoring` | `<SystemMonitoringDashboard>` — BullMQ queue health | Operations |
|
||||
| `/admin/monitoring/[queueName]` | `<QueueDetailTable>` — single-queue drill-down | (not in landing browser) |
|
||||
| `/admin/ocr` | `<OcrSettingsForm>` — **DUPLICATES the same form on `/admin/ai`** | (not in landing browser) |
|
||||
| `/admin/onboarding` | `<OnboardingChecklist>` — cross-page setup checklist for new ports | Operations |
|
||||
| `/admin/pipeline-rules` | per-trigger berth-rules editor + `<RegistryDrivenForm sections={['pipeline.auto-advance']}>` | Configuration ("Pipeline auto-advance") |
|
||||
| `/admin/ports` | `<PortList>` — manage marinas (super-admin only) | Operations |
|
||||
| `/admin/pulse` | `<RegistryDrivenForm sections={['pulse']}>` — pulse chip tuning | Configuration |
|
||||
| `/admin/qualification-criteria` | `<QualificationCriteriaAdmin>` — lead-qualification rubric | Operations |
|
||||
| `/admin/reminders` | `<RegistryDrivenForm sections={['reminders.defaults','reminders.digest']}>` | Configuration |
|
||||
| `/admin/reports` | `<ReportsDashboard>` — saved analytics + ad-hoc queries | Operations |
|
||||
| `/admin/residential-stages` | `<ResidentialStagesAdmin>` + stage-template registry form | Operations |
|
||||
| `/admin/roles` | `<RoleList>` — role/permission matrix | Access |
|
||||
| `/admin/sends` | `<SendsLog>` — brochure + per-berth PDF send retries | Data Quality |
|
||||
| `/admin/settings` | `<SettingsManager>` — generic system_settings KV editor (escape hatch) | Configuration ("System Settings") |
|
||||
| `/admin/storage` | `<StorageAdminPanel>` — storage backend selector + migration | Operations |
|
||||
| `/admin/tags` | `<TagList>` — color-coded tags per entity | Content |
|
||||
| `/admin/templates` | `<TemplateList>` — PDF + email document templates (merge-field-driven) | Content |
|
||||
| `/admin/templates/[id]/editor` | per-template editor (PDF + email body) | (not in landing browser) |
|
||||
| `/admin/users` | `<UserList>` + `<InvitationsManager>` (tabs, merged 2026-05-21) | Access |
|
||||
| `/admin/vocabularies` | `<VocabulariesManager>` — admin-editable enum lists | Content |
|
||||
| `/admin/webhooks` | `<WebhookForm>` + `<WebhookDeliveryLog>` + `<WebhookSecretDisplay>` | Configuration |
|
||||
| `/admin/website-analytics` | Umami creds form + `<UmamiTestButton>` | Operations |
|
||||
|
||||
---
|
||||
|
||||
## 2. Issues identified
|
||||
|
||||
### 2.1 Duplicates
|
||||
|
||||
1. **`/admin/ocr` duplicates `/admin/ai`** — same `<OcrSettingsForm>` is mounted on both. The AI page is the source of truth (it also has the master AI switch + provider creds + AI-suggestions config). **Recommendation: delete `/admin/ocr`** + add a redirect.
|
||||
2. **`/admin/invitations` is dead** — the page body is empty (per the comment, merged into `/admin/users` 2026-05-21). **Recommendation: delete the route** + add a redirect to `/admin/users?tab=invitations`.
|
||||
|
||||
### 2.2 Misplaced cards
|
||||
|
||||
1. **`<EmailPreviewCard>` is on Branding but tests email rendering** — overlap with the new per-template tester on `/admin/email`. **Recommendation: KEEP on Branding** (it's a one-click "does the email LOOK right with current logo/colors?" affordance — that's a branding-validation concern, not a delivery test). Add a sibling link "→ Test individual templates" pointing at `/admin/email`.
|
||||
2. **`<SalesEmailConfigCard>` is on `/admin/email`** — correct home, but it's structurally identical to the noreply SMTP card above it (just a second mailbox). **Recommendation: keep but reformat** so both mailboxes are in matching cards stacked, with a shared "Test send" footer per mailbox.
|
||||
3. **`<EmailRoutingCard>` is on `/admin/email`** — actually it's a routing-rule editor (when X event fires, route through Y mailbox). Conceptually closer to a workflow rule than a credentials setting. **Recommendation: keep on Email** for now (the routing IS about email plumbing) but cross-link from Workflows since changing the rule changes behaviour.
|
||||
|
||||
### 2.3 Inconsistent naming
|
||||
|
||||
1. **"Documenso & EOI"** page title implies EOI lives separately — but EOI generation is one of multiple Documenso flows. **Recommendation: rename to "Signing service (Documenso)"**.
|
||||
2. **"Bulk Import"** vs `/admin/import` — fine, but the page should explicitly say "Data import" (matches the page title `<PageHeader title="Data import">`).
|
||||
3. **"Send Log"** vs `/admin/sends` — fine; consider renaming the route slug to `/admin/send-log` for clarity, but that costs cross-references.
|
||||
|
||||
### 2.4 Pages not in the admin-sections-browser tile grid
|
||||
|
||||
A bunch of pages exist as routes but aren't surfaced on `/admin`:
|
||||
|
||||
- `/admin/berths/bulk-add`, `/admin/berths/reconcile` — only reachable from deep links inside the Berths page
|
||||
- `/admin/brochures`
|
||||
- `/admin/email-templates`, `/admin/tags`, `/admin/vocabularies`, `/admin/custom-fields`, `/admin/forms` — actually these ARE in the browser under "Content", verified
|
||||
- `/admin/qualification-criteria`, `/admin/residential-stages` — under Operations
|
||||
- `/admin/errors`, `/admin/errors/codes`, `/admin/errors/[requestId]`
|
||||
- `/admin/ocr` (duplicate, recommended for deletion)
|
||||
- `/admin/invitations` (dead, recommended for deletion)
|
||||
|
||||
**Recommendation:** surface every active page on `/admin` (no hidden surfaces — discoverability matters for admins). Move `/admin/berths/bulk-add` + `/admin/berths/reconcile` to a new "Berths admin" landing card.
|
||||
|
||||
### 2.5 Categories that don't quite fit
|
||||
|
||||
- **"Content"** is doing too much heavy lifting — it lumps tag color picker (visual), vocab enum lists (config), form templates (workflow), and document templates (mail merge). These are all things admins _tune_ but their cognitive shape is different.
|
||||
- **"Data Quality"** mixes inbound queues (Inquiry Inbox) with cleanup utilities (Duplicates, Bulk Import) — those serve different daily-workflows.
|
||||
- **"Operations"** is the catch-all for "anything observability or infra-shaped" but also has things that are pure setup (AI configuration, residential pipeline stages).
|
||||
|
||||
---
|
||||
|
||||
## 3. Proposed IA — 6 domains, 38 pages
|
||||
|
||||
Two pages deleted (`/admin/ocr`, `/admin/invitations`), one moved out of admin entirely (`/admin/reports` — see below), one new sub-area (`/admin/berths`). Net page count: 41 → 38.
|
||||
|
||||
### Domain 1. **Brand & Communication** (5 pages)
|
||||
|
||||
_Everything about how outbound looks + which channel it ships on._
|
||||
|
||||
| Page | Action | Notes |
|
||||
| ------------------------ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `/admin/branding` | KEEP | Logo, colors, app name, email header/footer HTML, the visual "does it look right?" tester. |
|
||||
| `/admin/email` | KEEP | SMTP creds (noreply + sales), routing, per-template tester, SMTP connectivity probe. |
|
||||
| `/admin/email-templates` | KEEP | Subject-line overrides per transactional template. Stays separate from `/admin/email` because the audience is "copywriter" vs "ops". |
|
||||
| `/admin/documenso` | RENAME → "Signing service" | API creds, signer identities, templates, behaviour. Page title currently says "Documenso & EOI" — drop "& EOI" (EOI is one of many doc types). |
|
||||
| `/admin/webhooks` | KEEP | Outbound webhook subscriptions + delivery log. Sits here because webhooks are an outbound-comms channel, same conceptual bucket as email. |
|
||||
|
||||
### Domain 2. **Sales workflow** (7 pages)
|
||||
|
||||
_How the pipeline behaves end-to-end — triggers, scoring, templates._
|
||||
|
||||
| Page | Action | Notes |
|
||||
| ------------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| `/admin/pipeline-rules` | KEEP | Berth-rules engine triggers + auto-advance. |
|
||||
| `/admin/pulse` | KEEP | Deal Pulse chip tuning. |
|
||||
| `/admin/reminders` | KEEP | Default reminder behaviour + digest window. |
|
||||
| `/admin/qualification-criteria` | MOVE FROM "Operations" → here | Lead-qualification rubric — clearly a sales-workflow concern. |
|
||||
| `/admin/residential-stages` | MOVE FROM "Operations" → here | Residential pipeline shape. Same domain as the standard pipeline rules. |
|
||||
| `/admin/forms` | MOVE FROM "Content" → here | Form templates drive lead intake — workflow input, not "content". |
|
||||
| `/admin/templates` | MOVE FROM "Content" → here | Document templates carry merge fields tied to the pipeline (EOI, reservation, contract). These ARE pipeline artefacts. |
|
||||
|
||||
### Domain 3. **Catalog** (4 pages)
|
||||
|
||||
_Tenant-defined data shapes — values that get attached to records._
|
||||
|
||||
| Page | Action | Notes |
|
||||
| ---------------------- | -------------------------- | ---------------------------------------------------------------------------- |
|
||||
| `/admin/vocabularies` | KEEP | Admin-editable enum lists (berth_side_pontoon_options, lead_category, etc.). |
|
||||
| `/admin/tags` | KEEP | Color tags. |
|
||||
| `/admin/custom-fields` | KEEP | Per-entity field definitions. |
|
||||
| `/admin/brochures` | MOVE FROM ungrouped → here | Brochure assets are catalog artefacts (per-port versioned PDFs). |
|
||||
|
||||
### Domain 4. **Identity & access** (3 pages)
|
||||
|
||||
_Who can use the system and what they can do._
|
||||
|
||||
| Page | Action | Notes |
|
||||
| -------------- | ------ | -------------------------------------------- |
|
||||
| `/admin/users` | KEEP | Active users + invitations (already merged). |
|
||||
| `/admin/roles` | KEEP | Role/permission matrix. |
|
||||
| `/admin/ports` | KEEP | Super-admin only; per-port management. |
|
||||
|
||||
### Domain 5. **Inbox & data quality** (6 pages)
|
||||
|
||||
_Stuff that lands in admin queues + tools to clean up data._
|
||||
|
||||
| Page | Action | Notes |
|
||||
| ------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| `/admin/inquiries` | KEEP | Public-site form submissions. |
|
||||
| `/admin/sends` | KEEP | Brochure + per-berth-PDF send retries. |
|
||||
| `/admin/duplicates` | KEEP | Suspected-duplicate review queue. |
|
||||
| `/admin/import` | KEEP | CSV imports. |
|
||||
| `/admin/berths` | NEW INDEX | Landing page that surfaces the two berth-admin tools below. |
|
||||
| `/admin/berths/bulk-add` | MOVE FROM ungrouped → keep route, surface via /admin/berths | Bulk berth row generator. |
|
||||
| `/admin/berths/reconcile` | MOVE FROM ungrouped → keep route, surface via /admin/berths | Berth-pdf reconciliation queue. |
|
||||
|
||||
(Counted as one Berths entry on the landing tile + the two existing routes as sub-pages.)
|
||||
|
||||
### Domain 6. **System & observability** (10 pages)
|
||||
|
||||
_Infra, observability, escape hatches._
|
||||
|
||||
| Page | Action | Notes |
|
||||
| ------------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `/admin/audit` | KEEP | Mutation audit log. |
|
||||
| `/admin/monitoring` | KEEP | BullMQ queue health. |
|
||||
| `/admin/monitoring/[queueName]` | KEEP | Single-queue detail. |
|
||||
| `/admin/errors` | SURFACE on landing | Error-event list (currently hidden from `/admin` tile grid). |
|
||||
| `/admin/errors/codes` | KEEP as sub-page | Linked from `/admin/errors`. |
|
||||
| `/admin/errors/[requestId]` | KEEP as sub-page | Linked from `/admin/errors`. |
|
||||
| `/admin/backup` | KEEP | Backup posture. |
|
||||
| `/admin/storage` | KEEP | Storage backend selector + migration. |
|
||||
| `/admin/website-analytics` | KEEP | Umami creds. |
|
||||
| `/admin/ai` | KEEP | AI config (master switch, providers, OCR settings, suggestions). |
|
||||
| `/admin/settings` | KEEP | Generic KV editor (escape hatch for advanced flags). Stays in this domain because it's an admin-debug surface, not a normal-day setting. |
|
||||
| `/admin/onboarding` | KEEP, FLOATS | Cross-cutting setup checklist. Stays accessible from `/admin` landing but doesn't belong in any single domain — it links to many. |
|
||||
|
||||
### Out of admin entirely
|
||||
|
||||
| Page | Action | Rationale |
|
||||
| ---------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `/admin/reports` | MOVE OUT → `/[portSlug]/reports` | Reports are an end-user feature, not admin config. Today it lives in admin only because it's permission-gated; should be a top-level nav item with the same permission gate. Defer to a follow-up; for the IA pass, just stop surfacing it on `/admin`. |
|
||||
|
||||
### Deleted
|
||||
|
||||
| Page | Action | Rationale |
|
||||
| -------------------- | ----------------------------- | -------------------------------------------- |
|
||||
| `/admin/ocr` | DELETE + 301 → `/admin/ai` | Duplicate of `/admin/ai`. |
|
||||
| `/admin/invitations` | DELETE + 301 → `/admin/users` | Empty page; functionality merged 2026-05-21. |
|
||||
|
||||
---
|
||||
|
||||
## 4. Misplaced cards / sub-section moves
|
||||
|
||||
These are smaller-grained moves _within_ the new IA — cards that should change page even though the routes stay put.
|
||||
|
||||
1. **`<EmailPreviewCard>` (currently on `/admin/branding`)** → KEEP on Branding (visual brand check); add a "→ Test individual templates" link pointing at `/admin/email#test-template`.
|
||||
2. **`<EmailRoutingCard>` (currently on `/admin/email`)** → KEEP on Email; cross-link from a "Routing rules" subsection of the new Workflow domain.
|
||||
3. **`<TemplateSyncButton>` (currently on `/admin/documenso`)** → KEEP; consider surfacing duplicate on `/admin/templates` (since "Sync from Documenso" populates template IDs there).
|
||||
4. **`<OnboardingChecklist>`** → consider exposing a slim version as a banner on `/admin` landing for ports that haven't completed setup.
|
||||
|
||||
---
|
||||
|
||||
## 5. Proposed `/admin` landing tile groups
|
||||
|
||||
The `admin-sections-browser.tsx` array should be rebuilt to match the 6 domains above. Sketch:
|
||||
|
||||
```ts
|
||||
const SECTIONS: AdminSection[] = [
|
||||
{
|
||||
title: 'Brand & Communication',
|
||||
description: 'How outbound looks and which channels it ships on.',
|
||||
items: ['branding', 'email', 'email-templates', 'documenso', 'webhooks'],
|
||||
},
|
||||
{
|
||||
title: 'Sales workflow',
|
||||
description: 'Pipeline behaviour, scoring, document + form templates.',
|
||||
items: [
|
||||
'pipeline-rules',
|
||||
'pulse',
|
||||
'reminders',
|
||||
'qualification-criteria',
|
||||
'residential-stages',
|
||||
'forms',
|
||||
'templates',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Catalog',
|
||||
description: 'Tenant-defined enums, tags, custom fields, and brochures.',
|
||||
items: ['vocabularies', 'tags', 'custom-fields', 'brochures'],
|
||||
},
|
||||
{
|
||||
title: 'Identity & access',
|
||||
description: 'Who can use the system and what they can do.',
|
||||
items: ['users', 'roles', 'ports'],
|
||||
},
|
||||
{
|
||||
title: 'Inbox & data quality',
|
||||
description: 'Admin queues + cleanup tools.',
|
||||
items: ['inquiries', 'sends', 'duplicates', 'import', 'berths'],
|
||||
},
|
||||
{
|
||||
title: 'System & observability',
|
||||
description: 'Infra, observability, escape hatches.',
|
||||
items: [
|
||||
'audit',
|
||||
'monitoring',
|
||||
'errors',
|
||||
'backup',
|
||||
'storage',
|
||||
'website-analytics',
|
||||
'ai',
|
||||
'settings',
|
||||
],
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
Onboarding checklist surfaces above the grid (or as a banner on incomplete ports), not as a tile.
|
||||
|
||||
---
|
||||
|
||||
## 6. Phase 2 execution plan (~4–6h)
|
||||
|
||||
Once the above IA is approved (or amended), the migration is mechanical:
|
||||
|
||||
1. **Update `admin-sections-browser.tsx`** to the 6-domain shape above. (~30 min)
|
||||
2. **Delete `/admin/ocr`** + add `redirect()` to `/admin/ai`. (~10 min)
|
||||
3. **Delete `/admin/invitations`** + add `redirect()` to `/admin/users`. (~10 min)
|
||||
4. **Rename "Documenso & EOI"** → "Signing service" (page title + landing label). (~5 min)
|
||||
5. **Create `/admin/berths/page.tsx`** index that surfaces bulk-add + reconcile. (~30 min)
|
||||
6. **Move `/admin/reports` out of admin** — touches sidebar nav + landing browser + permission docs. Defer to its own task if scope creeps. (~1h)
|
||||
7. **Cross-link cards** per section 4 (EmailPreviewCard → /admin/email link, etc.). (~30 min)
|
||||
8. **Smoke pass** — click every tile, confirm every page loads, every redirect lands. (~30 min)
|
||||
9. **Audit doc update** — mark B3 #10 SHIPPED in `alpha-uat-master.md`. (~10 min)
|
||||
|
||||
Total: ~4 h plus ~1 h for the Reports move if we include it.
|
||||
|
||||
---
|
||||
|
||||
## 7. Open questions (resolved)
|
||||
|
||||
| # | Question | Decision |
|
||||
| --- | ---------------------------------------- | ------------------------------------------------------------------------------- |
|
||||
| 1 | Forms + Document Templates placement | Moved to **Sales workflow** (not Content) |
|
||||
| 2 | Webhooks placement | **New "Integrations" domain** (webhooks + documenso + website-analytics + ai) |
|
||||
| 3 | AI configuration placement | Keep dedicated `/admin/ai` panel; lives under **Integrations** |
|
||||
| 4 | `/admin/reports` | **DELETE entirely** (duplicates dashboard); redirect to `/[portSlug]/dashboard` |
|
||||
| 5 | `/admin/settings` (KV editor) visibility | Keep visible to all admins under **System & observability** |
|
||||
|
||||
---
|
||||
|
||||
## 8. Final IA — 7 domains, 38 pages
|
||||
|
||||
After resolutions. Three pages deleted (`/admin/ocr`, `/admin/invitations`, `/admin/reports`); one new sub-area (`/admin/berths` index); one new domain (Integrations) split out from Brand & Communication.
|
||||
|
||||
### Domain 1. **Brand & Communication** (3 pages)
|
||||
|
||||
_How outbound LOOKS — visual and copy._
|
||||
|
||||
- `/admin/branding` — logo, colors, app name, email shell HTML, EmailPreviewCard (visual check)
|
||||
- `/admin/email` — SMTP creds (noreply + sales), routing, per-template tester, SMTP probe
|
||||
- `/admin/email-templates` — subject-line + copy overrides per transactional template
|
||||
|
||||
### Domain 2. **Sales workflow** (7 pages)
|
||||
|
||||
_How the pipeline BEHAVES — triggers, scoring, templates._
|
||||
|
||||
- `/admin/pipeline-rules` — berth-rules engine + auto-advance
|
||||
- `/admin/pulse` — Deal Pulse chip tuning
|
||||
- `/admin/reminders` — default behaviour + digest
|
||||
- `/admin/qualification-criteria` — lead-scoring rubric
|
||||
- `/admin/residential-stages` — residential pipeline shape
|
||||
- `/admin/forms` — lead intake form templates (moved from Content)
|
||||
- `/admin/templates` — document templates with merge fields (moved from Content)
|
||||
|
||||
### Domain 3. **Catalog** (4 pages)
|
||||
|
||||
_Tenant-defined data shapes that attach to records._
|
||||
|
||||
- `/admin/vocabularies` — admin-editable enum lists
|
||||
- `/admin/tags` — color tags
|
||||
- `/admin/custom-fields` — per-entity field definitions
|
||||
- `/admin/brochures` — per-port versioned PDF assets
|
||||
|
||||
### Domain 4. **Identity & access** (3 pages)
|
||||
|
||||
- `/admin/users` — active users + invitations (merged)
|
||||
- `/admin/roles` — role/permission matrix
|
||||
- `/admin/ports` — super-admin only, per-port management
|
||||
|
||||
### Domain 5. **Inbox & data quality** (5 pages, 1 sub-index)
|
||||
|
||||
_Admin queues + cleanup tools._
|
||||
|
||||
- `/admin/inquiries` — public-site submissions
|
||||
- `/admin/sends` — outbound send retry log
|
||||
- `/admin/duplicates` — duplicate-client review queue
|
||||
- `/admin/import` — CSV imports
|
||||
- `/admin/berths` — **NEW** index page surfacing the two existing sub-tools:
|
||||
- `/admin/berths/bulk-add` (bulk row generator)
|
||||
- `/admin/berths/reconcile` (berth-pdf reconciliation queue)
|
||||
|
||||
### Domain 6. **Integrations** (4 pages) — NEW DOMAIN
|
||||
|
||||
_External-system + provider configuration._
|
||||
|
||||
- `/admin/documenso` — signing service (rename from "Documenso & EOI" → "Signing service")
|
||||
- `/admin/webhooks` — outbound subscriptions + delivery log
|
||||
- `/admin/website-analytics` — Umami creds
|
||||
- `/admin/ai` — dedicated AI panel consolidating master switch + provider creds + OCR settings + AI-suggestions config
|
||||
|
||||
### Domain 7. **System & observability** (7 pages + 1 floating)
|
||||
|
||||
_Infra, observability, escape hatches._
|
||||
|
||||
- `/admin/audit` — mutation audit log
|
||||
- `/admin/monitoring` — BullMQ queue health (+ `/admin/monitoring/[queueName]` sub-page)
|
||||
- `/admin/errors` — error-event list (+ `/admin/errors/codes` + `/admin/errors/[requestId]`)
|
||||
- `/admin/backup` — backup posture
|
||||
- `/admin/storage` — storage backend selector + migration
|
||||
- `/admin/settings` — generic KV editor (escape hatch)
|
||||
- `/admin/onboarding` — cross-cutting setup checklist (floats above the grid for incomplete ports)
|
||||
|
||||
### Deleted
|
||||
|
||||
| Page | Action | Rationale |
|
||||
| -------------------- | -------------------------------------- | ---------------------------------------------------- |
|
||||
| `/admin/ocr` | DELETE + 301 → `/admin/ai` | Duplicate of `/admin/ai`'s OcrSettingsForm |
|
||||
| `/admin/invitations` | DELETE + 301 → `/admin/users` | Empty page; merged into `/admin/users` on 2026-05-21 |
|
||||
| `/admin/reports` | DELETE + 301 → `/[portSlug]/dashboard` | Three widgets all already on the dashboard |
|
||||
|
||||
---
|
||||
|
||||
## 9. Phase 2 execution plan (~4-5 h)
|
||||
|
||||
Updated to reflect the resolved decisions. Reports move-out becomes a delete (simpler).
|
||||
|
||||
1. **Update `admin-sections-browser.tsx`** to the 7-domain shape above. (~45 min — 7 groups, ~30 tiles)
|
||||
2. **Delete `/admin/ocr`** + add `redirect()` to `/admin/ai`. (~10 min)
|
||||
3. **Delete `/admin/invitations`** + add `redirect()` to `/admin/users`. (~10 min)
|
||||
4. **Delete `/admin/reports`** + add `redirect()` to `/[portSlug]/dashboard`. (~10 min) + remove from sidebar nav + landing browser. (~15 min)
|
||||
5. **Rename `/admin/documenso`** page title → "Signing service" (page title + landing tile label). (~5 min)
|
||||
6. **Create `/admin/berths/page.tsx`** index page surfacing bulk-add + reconcile sub-tools. (~30 min)
|
||||
7. **Cross-link `<EmailPreviewCard>`** on Branding to add a "→ Test individual templates" link pointing at `/admin/email#test-template`. (~10 min)
|
||||
8. **Smoke pass** — click every tile on the new `/admin` landing, confirm every page loads, every redirect lands. (~30 min)
|
||||
9. **Update `alpha-uat-master.md`** Bucket 3 #10 → SHIPPED with this proposal's commit hash. (~5 min)
|
||||
|
||||
Total: ~3.5-4 h.
|
||||
@@ -146,6 +146,9 @@ _Copy tweaks, alignment, single-prop edits, obvious typos._
|
||||
- Service docstring updated to cite the verified v3 endpoint behaviour + the flat-shape rationale so the next reader doesn't repeat the v1-nested mistake.
|
||||
- `tsc --noEmit` clean. Verified live: dashboard tile + website-analytics page both render 2,081 pageviews / 726 visitors / 872 visits / 457 bounces over 30d (the real numbers from analytics.portnimara.com). Fixed in this session.
|
||||
16. **Revenue Breakdown widget removed end-to-end** — _src/components/dashboard/{revenue-breakdown-chart.tsx (deleted), widget-registry.tsx, use-analytics.ts}_, _src/app/api/v1/analytics/route.ts_, _src/lib/services/analytics.service.ts_, _tests/integration/analytics-service.test.ts_ — the "Revenue Breakdown" tile (bar chart of invoice totals by status × currency) wasn't aligned with how the org uses invoicing (no client-facing invoicing through the system — only employee expense-sheet PDFs for trip reimbursement) and was redundant once the Pipeline Value tile shipped with a weighted forecast + per-stage breakdown. Removed: widget file, dynamic import, registry entry, `useRevenue` hook, `RevenueBreakdownData` type, `MetricBase` union member, `ALL_METRICS` entry, `SnapshotData` union member, `getRevenueBreakdown` + `computeRevenueBreakdown` service functions, `refreshSnapshotsForPort` revenue branch, route dictionary entry, integration test. `RevenueReportPdf` (separate code path for the reports module) intentionally kept. `tsc --noEmit` clean. Fixed in this session.
|
||||
17. **Finish CountryFlag rollout — table + filter surfaces** — _src/components/shared/country-flag.tsx (shipped this session)_ + _src/components/clients/client-columns.tsx:173_ (nationality column cell — currently renders bare ISO code; should prefix with `<CountryFlag>`) + _src/components/clients/client-filters.tsx_ (nationality filter pill — render flag next to selected country name) + _src/components/yachts/yacht-form.tsx_ (flag the yacht's country if surfaced anywhere outside the CountryCombobox transitive path) + audit any remaining `flagEmoji` or `0x1f1e6` codepoint references with `rg -n "0x1f1e6\|flagEmoji"` → expected 0 hits. Shipped this session: country-combobox + inline-country-field + addresses-editor (replaced existing emoji glyphs, which never rendered on Windows) and added flags to clients-by-country-widget / client-card / client-detail-header / website-analytics realtime + sessions + session-detail. Library: `country-flag-icons` (MIT, ~1-2 KB per flag, dynamically imported on first render, cached). Effort: ~30 min for the remaining surfaces. Captured 2026-05-22 from UAT.
|
||||
- **SHIPPED in this session:** client-columns nationality cell now renders flag + name. client-filters is a free-text input (no rendered chip surface to flag). No yacht country rendering exists outside CountryCombobox. Final `rg "0x1f1e6\|flagEmoji"` returns 0 hits.
|
||||
- **Follow-up fix in this session:** the original `CountryFlag` used a template-string dynamic import (`import('country-flag-icons/string/3x2/${code}')`), which silently fails in Next.js's webpack because the package's `exports` field gates each subpath. Symptom: every flag rendered as the muted placeholder box. Replaced with a single lazy `import('country-flag-icons/string/3x2')` that loads the whole index once (~1.6 MB raw / ~400 KB gzip, single chunk shared across the app), caches on a module-level promise, and lookups become synchronous after first render.
|
||||
|
||||
---
|
||||
|
||||
@@ -521,6 +524,11 @@ _Component refactors, multi-file edits, single-service tweaks, new validators._
|
||||
3. **Pipeline Value tile expanded with per-stage breakdown** — _src/components/dashboard/pipeline-value-tile.tsx_, _src/lib/services/dashboard.service.ts_ — replaced the single-number KPI with a richer card: gross headline + weighted forecast on top, per-stage rows below (label · mini bar · gross value · count + close-probability), and a footnote when default stage weights are in use. Service `getRevenueForecast` extended to return `grossValue`, `weight`, `totalGrossValue`, and `dealsMissingPrice` alongside the existing weighted shape; the tile pulls from `/kpis` (for gross + currency + activeInterests) and `/forecast` (for breakdown). Per-stage warning chip surfaces when berths are missing a `price` so a silently undercounted gross is visible (full coverage → "berth price missing", partial → "N of M missing price"). Leadership can now see how much of the headline is near-close vs speculative. Fixed in this session.
|
||||
4. **"How weighted forecast works" info popover on the Pipeline Value tile** — _src/components/dashboard/pipeline-value-tile.tsx_ — added an `Info` icon next to the description that opens a `Popover` (click or hover) explaining the close-probability model + showing the per-stage weight table (live from `/forecast`, fallback to `STAGE_WEIGHTS` constant) + a note about whether default or per-port weights are in use. Fixed in this session.
|
||||
5. **Bulk + inline berth price editing — backend complete** — _src/lib/db/schema/users.ts_, _src/lib/db/seed-permissions.ts_, _src/components/admin/roles/role-form.tsx_, _src/components/admin/users/user-permission-matrix.tsx_, _src/app/api/v1/admin/users/[id]/permission-overrides/route.ts_, _src/lib/validators/berths.ts_, _src/lib/services/berths.service.ts_, _src/app/api/v1/berths/[id]/price/route.ts_, _src/app/api/v1/berths/bulk-update-prices/route.ts_, _tests/helpers/factories.ts_ — new `berths.update_prices` permission carved out from generic `berths.edit` so sales reps can update prices without exposing the full edit surface. Permission seeded on for super_admin/director/sales_manager/sales_agent, off for viewer/residential_partner. New validators (`updateBerthPriceSchema`, `bulkUpdateBerthPricesSchema` capped at 500/batch), services (`updateBerthPrice`, `bulkUpdateBerthPrices`, both transactional + per-row audited with `fieldChanged='price'` + realtime `berth:updated` + webhook fan-out), and routes (`PATCH /api/v1/berths/[id]/price`, `POST /api/v1/berths/bulk-update-prices`). UI shipping in a follow-up — see Features bucket #1. Fixed in this session.
|
||||
6. **Cancel-document: choose delete-from-Documenso vs keep-for-audit** — _src/lib/services/documents.service.ts (cancelDocument)_ + _src/lib/services/documenso-client.ts (voidDocument)_ + every cancel-document UI surface (interest reservation tab, contract tab, EOI cancel dialog, send-document dialog admin actions, etc.) — today cancel always fires `DELETE /api/v2/envelope/{id}` (or v1 equivalent), which unclogs the Documenso instance but loses the upstream audit trail. UX ask: present the rep with an explicit choice on cancel: (a) **Delete upstream** (current behaviour — frees the Documenso slot, history rendered from CRM `documents` row only) or (b) **Keep for audit** (local row → `status='cancelled'`, no DELETE call; rep can later reopen on Documenso for forensics). Default to (a). Plumb a `cancelMode: 'delete' | 'keep_remote'` param through `cancelDocument` + the route handler; only call `documensoVoid` when mode === 'delete'. ~1-1.5h: service param + UI radio in the existing confirm-cancel dialog + audit-doc-status reflection in the cancelled-doc badge ("Cancelled, kept on Documenso" when keep_remote). Captured 2026-05-22.
|
||||
7. **Document signature reminders: drop rate-limit when automatic** — _src/components/interests/interest-reservation-tab.tsx:740_ ("Reminders are rate-limited (max once per 7 days per signer)") + the underlying remind-signer service. Today both manual and scheduled-auto reminders share the same 7-days-per-signer throttle. The cap is right for manual clicks (avoids harassment) but breaks the auto-cadence cron: if the rules engine wants to nudge a stale signer every 3 days, it gets swallowed. Plumb a `triggeredBy: 'manual' | 'auto'` flag from the caller and skip the rate-limit when `auto` (the cron's own cadence is the throttle). Manual UI keeps the 7-day cap. ~30-45 min: service param + cron caller + UI copy update ("Reminders are rate-limited for manual sends — automatic follow-ups run on the configured cadence"). Captured 2026-05-22.
|
||||
8. **EOI tab: add upload-draft-then-place-fields option (parity with Contract / Reservation)** — _src/components/interests/interest-eoi-tab.tsx_ (no `UploadForSigningDialog` mount yet) + _src/app/api/v1/interests/[id]/upload-for-signing/route.ts_ (`documentTypeSchema` is locked to `'contract' | 'reservation_agreement'`) + _src/lib/services/custom-document-upload.service.ts_ (`CustomDocumentType` union, `targetStage` switch, `dateContractSent` / `reservationDocStatus` branch). EOI currently has two paths — template-generated (`EoiGenerateDialog`, `/template/{id}/generate-document`) and external paper-signed upload (`ExternalEoiUploadDialog`, `markExternallySigned`) — but no upload-draft-then-drag-fields flow like Contract/Reservation. Reps with a bespoke EOI PDF have to either generate from template (loses custom layout) or mark-as-external (no signing). Fix shape: extend the union to `'eoi'`, add an EOI branch to the stage-advance + doc-status switch (`pipelineStage='eoi_sent'`, `eoiDocStatus='sent'`, `dateEoiSent`), wire `<UploadForSigningDialog documentType="eoi">` into the EOI tab next to the existing "Generate EOI" CTA. Effort: ~2-3h including the route validator bump, service branch, UI mount, and a smoke playwright run. Captured 2026-05-22.
|
||||
9. **Surface per-signer copyable signing URLs on every Documenso-driven doc** — _src/components/interests/interest-eoi-tab.tsx_, _src/components/interests/interest-reservation-tab.tsx_, _src/components/interests/interest-contract-tab.tsx_, _src/components/documents/signing-details-dialog.tsx_, _src/components/shared/send-document-dialog.tsx_ — once a document has been created in Documenso, each signer's `signingUrl` is already stored on the `document_signers` row (returned by `/api/v1/documents/{id}/signers`). Today the rep sees Pending / Invited badges but no way to grab a specific signer's signing URL for QA or manual delivery. Add a "Copy signing link" button next to each signer row across every signing-doc tab (EOI / Reservation / Contract / SigningDetails / SendDocument admin panel). Behaviour: button is disabled when `signingUrl` is null (Documenso hadn't returned a URL yet — e.g. send-mode failure); on click, copy to clipboard via `navigator.clipboard.writeText`, toast "Signing link copied" + the truncated URL. Useful for: smoke-testing the signing flow without spamming the rep's inbox, manually pasting a link into a custom email or Slack DM when the auto-send mode failed, and for sales reps who want to QA the look of the page before the customer touches it. ~30-45 min, all UI surface work — backend already exposes the data. Captured 2026-05-22.
|
||||
10. **Per-template "Send a test" tester for every transactional email the system emits** — _src/components/admin/branding/email-preview-card.tsx_ (current sample-only tester), _src/lib/email/templates/_ (all template files), _src/app/api/v1/admin/branding/email-preview/route.ts_ (current endpoint), new endpoint `/api/v1/admin/email/test-template`. Today the admin can send ONE generic branded shell from Branding, plus an SMTP-connectivity ping from Email — but no way to fire a specific template (password reset, EOI invitation, signing reminder, GDPR export ready, portal activation, reminder digest, bounce-back notice, …) to a designated address. Add a per-template tester card: dropdown of every registered template (read from a central template registry exposing `id, label, sampleProps`), recipient email input, "Send test" button. Backend route renders the selected template with realistic sample props (port branding, fake but plausible client/yacht/EOI), pipes through the same sender helper as the real flow, returns delivery status. Goes on the Email admin page next to the existing SMTP test card. Effort: ~2-3h (registry + endpoint + card + sample-prop fixtures for each template). Captured 2026-05-22.
|
||||
|
||||
---
|
||||
|
||||
@@ -676,6 +684,12 @@ _New UI surfaces, new endpoints, schema migrations, multi-step flows._
|
||||
- **(a) Pre-flight config-shape errors at known integration boundaries** — _src/lib/services/documenso-client.ts_, _src/lib/services/storage/\*_, _src/lib/email/_, _src/lib/services/imap-bounce-poller.ts_, IMAP, SMS providers, payment gateways, etc. — when a call would fail because admin/env config is empty or unparseable, raise a typed `CodedError` _before_ the network call with an operator-facing message like `"Documenso is not configured for {portName}. Open Admin → Documenso settings to enter the API key, or set DOCUMENSO_API_KEY in env."` Include the offending setting key + port name. The `documenso-client` `resolveCreds()` is the canonical example to template from — others (IMAP, S3, SMTP, Stripe etc.) should follow the same pattern.
|
||||
- **(b) User-facing error-message audit** — _src/lib/errors.ts_, all `try/catch` blocks in `src/app/api/*`, all `toastError` consumers in `src/components/*` — scan for `errorResponse(err)` paths that return generic "Something went wrong" / status codes only, and enrich with: (i) the operation that failed ("EOI generation", "Send invoice", "Upload file"), (ii) the likely cause (config missing, permission denied, conflict, etc.), (iii) the next step (where to fix it). Especially important for setting-driven features (email send accounts, storage backends, Documenso config, webhook secrets) where the real cause is one config field off-screen. The error catalog in `src/lib/errors.ts` already supports `CodedError` with operator-friendly `userMessage` — most call sites just need to populate it.
|
||||
- Total scope: probably a 1-2 day audit + remediation pass. Out-of-scope items to consider during the pass: a per-port "Integrations health" admin page that probes each external integration and shows green/red with the same diagnostic copy.
|
||||
9. **Universal "upload file → optionally place signing fields"** — _src/components/documents/upload-for-signing-dialog.tsx_ (the existing place-fields step) + _src/components/documents/new-document-menu.tsx_ (Documents Hub upload), _src/components/documents/documents-hub.tsx_ (root + folder upload), _src/components/files/file-upload-zone.tsx_ (the shared dropzone), _src/components/clients/_ + _yachts/_ + _companies/_ document-tab upload surfaces — every modal where a PDF can land should expose an optional "Send for signature?" toggle that swaps the regular file-upload for the field-placement wizard. Avoids the re-upload friction the user currently hits when an arbitrary doc needs signatures. Shape: extract a `<DocumensoFieldPlacementStep>` from `UploadForSigningDialog`, mount it conditionally after the dropzone in every upload modal, and route through `/upload-for-signing` when fields are placed (skip it when only a plain file is uploaded). Backend: extend `CustomDocumentType` to accept `'generic'` (no pipeline-stage advance, no doc-status flip — just files + documents row in `sent` status). Effort: ~8-12h. Captured 2026-05-22.
|
||||
10. **Comprehensive admin-settings IA audit + regroup** — _src/app/(dashboard)/[portSlug]/admin/_ — 41 admin pages today, organically grown: `ai`, `audit`, `backup`, `berths/bulk-add`, `berths/reconcile`, `branding`, `brochures`, `custom-fields`, `documenso`, `duplicates`, `email-templates`, `email`, `errors`, `forms`, `import`, `inquiries`, `invitations`, `monitoring`, `ocr`, `onboarding`, `pipeline-rules`, `ports`, `pulse`, `qualification-criteria`, `reminders`, `reports`, `residential-stages`, `roles`, `sends`, `settings`, `storage`, `tags`, `templates`, `users`, `vocabularies`, `webhooks`, `website-analytics`. Settings are scattered — e.g. test-email lives on Branding, SMTP test on Email, password-reset copy probably in `email-templates`, but the rep has to guess. Audit each page for: (a) what settings live there now, (b) which settings logically belong elsewhere ("right home" test — Documenso send mode currently lives on Documenso, makes sense; per-port email signature would make more sense under Branding than Email), (c) duplicates (vocabularies vs custom-fields vs qualification-criteria overlap on enum tuning). Then propose a regrouped IA — likely fewer top-level pages with clear domain headers (Configuration → Branding, Email, Documenso, Storage, Webhooks; Workflows → Pipeline rules, Reminders, Auto-stage advancement; Catalog → Vocabularies, Tags, Custom fields, Qualification criteria; Operations → Monitoring, Pulse, Audit log, Errors, Backup; Data → Import, Duplicates, Bulk berth tools; Identity → Users, Roles, Invitations, Onboarding). Pair with a new admin index page that groups by domain instead of a flat alphabetical list. Effort: ~1.5-2 days — audit pass + IA proposal review + actual file moves + nav updates + redirect shims for old URLs. Captured 2026-05-22.
|
||||
- **SHIPPED in this session (Phase 1 + Phase 2):** Full audit + proposal at `docs/admin-ia-proposal.md`. Final IA = 7 domains, 38 pages (down from 41 via three deletes). `admin-sections-browser.tsx` rewritten to the new domain shape (Brand & Communication, Sales workflow, Catalog, Identity & access, Inbox & data quality, Integrations, System & observability). Deleted with redirects: `/admin/ocr` → `/admin/ai`, `/admin/reports` → `/[portSlug]/dashboard`, `/admin/invitations` → `/admin/users` (this last one was already a redirect). Renamed: "Documenso & EOI" → "Signing service (Documenso)". New: `/admin/berths` index page surfacing bulk-add + reconcile sub-tools (which were previously discoverable only via deep links). `<EmailPreviewCard>` on Branding cross-links to `/admin/email` per-template tester. Search-nav-catalog updated (ocr entry removed, berths entry added). tsc clean.
|
||||
11. **B3 #9 follow-up — UI wiring for universal upload-with-fields** — _src/components/documents/upload-for-signing-dialog.tsx_ (`<FieldPlacementStep>` lives inside this monolith — needs extraction into a standalone component the other upload modals can mount conditionally), _src/components/documents/new-document-menu.tsx_ + _src/components/documents/documents-hub.tsx_ + _src/components/files/file-upload-zone.tsx_ + entity-tab upload sites (client/yacht/company doc tabs). **Backend foundations SHIPPED 2026-05-22**: `CustomDocumentType` union now includes `'generic'`; `uploadDocumentForSigning` skips pipeline-stage advance + doc-status flip when generic; route validator accepts the new value; storage path category routes to `signed-source/`. **UI half deferred** to a paired session — needs careful surgery to each upload modal to add the "Send for signature?" toggle + mount the extracted field-placement step. Effort for UI wiring: ~5-7h. Captured 2026-05-22.
|
||||
12. **Time-period PDF report + chart rendering + deeper data** — _src/lib/pdf/reports/dashboard-report.tsx_, _src/lib/services/dashboard-report-data.service.ts_, _src/lib/pdf/reports/types.ts_, new _src/lib/pdf/reports/charts.tsx_, _src/components/reports/export-dashboard-pdf-button.tsx_ (date-range picker). Today's PDF report ignores dateFrom/dateTo for most sections and renders every chart-style widget as a table. User wants: (a) **time-range filter** that scopes EVERY section to a chosen window — new clients in the window, new interests in the window, active interests touching the window, in-progress berths (sold/under-offer transitions in the window), pipeline counts at the start vs end of window, etc.; (b) **chart rendering** — react-pdf supports SVG, so build small SVG generators (`<PipelineFunnel data>`, `<OccupancyTimeline data>`, `<SourceMixDonut data>`) inline OR pre-render via vega-lite/d3-node to PNG and embed; (c) **deeper data per section** — add berths-in-flight (status changes within window), client+interest cohort tables, contact-cadence histogram, document-signing throughput. Shape: extend `DashboardReportData` with `window: {from, to}` and new sub-sections; extend the export-PDF dialog to take a date-range; route handler propagates the window to every per-section resolver. Effort: ~8-12h depending on chart-rendering approach (inline SVG is ~6h, vega-lite pre-render is ~10h with a worker round-trip). Captured 2026-05-22.
|
||||
- **SHIPPED in this session:** Catalog expanded from 5 ids to 25 — chart variants (pipeline funnel bar, berth status donut, source conversion bar, lead source donut, occupancy timeline line) + period cohorts (new clients/interests, berths sold, deposits received, documents/contracts signed) + value views (pipeline value breakdown, revenue forecast, avg sales cycle, berth demand, country distribution, deal pulse distribution, recent activity). Hand-rolled SVG chart primitives in `src/lib/pdf/reports/charts.tsx` (HorizontalBarChart, DonutChart, LineChart) using @react-pdf/renderer's native Svg/Path/Rect support. Export-dialog grew a date-range picker with Last-30/90-days quick presets, defaults to last 30 days. Route + service plumbing carries dateFrom/dateTo. 11 of 16 pending resolvers landed (new_clients_period, new_interests_period, berths_sold_period via audit log, deposits_received_period, signed_documents_period, contracts_signed_period, berth_demand_ranking, lead_source_donut, client_country_distribution, recent_activity, pipeline_value_breakdown, revenue_forecast, avg_sales_cycle). Still pending (in this session's PENDING_RESOLVER_IDS set): stage_conversion_rates, occupancy_timeline_chart (needs daily buckets), inquiry_inbox_summary, reminders_summary, deal_pulse_distribution (requires the pulse service's dynamic computation, not a simple column query — left as follow-up). Also shipped: PDF logo absolutize for server-side fetch (was empty because @react-pdf/renderer can't fetch path-only URLs server-side), "Dashboard report" → "Report" default name, section-orphan fix (`wrap={false}` + `minPresenceAhead`).
|
||||
|
||||
---
|
||||
|
||||
@@ -825,6 +839,13 @@ _Functional defects. Tag each with `[critical|high|medium|low]` prefix._
|
||||
- **Effort:** ~10 min for the move + verify (no code change, just file relocation + manual click-through). Captured 2026-05-21 from UAT.
|
||||
- **SHIPPED in 2d57417:** route relocated via `git mv` to `src/app/public/supplemental-info/[token]/page.tsx`. URL `/public/supplemental-info/<token>` unchanged (route groups don't affect URLs). Sweep of `src/app/(portal)/` confirmed no other public token routes were similarly nested.
|
||||
12. **[high] Command-search quick-create buttons routed to dead `/new` pages** — _src/components/search/command-search.tsx_ — ZeroState "New client/yacht/company" buttons pushed `/<entity>/new?name=…` which matched the `[id]` dynamic segment and rendered the entity-not-found page. Fixed by switching to `/<entity>?create=1&prefill_name=…` (the existing `useCreateFromUrl` convention) + adding `prefill` prop support to `YachtForm` + `CompanyForm` and wiring `prefill_name` reads in their list components. Now correctly pops the create sheet pre-filled. Fixed in this session.
|
||||
13. **[high] Dashboard widget cross-group reorder silently ignored by the Customize modal** — _src/components/dashboard/customize-widgets-menu.tsx:113-136_ vs _src/components/dashboard/dashboard-shell.tsx:88-90_ — the Customize modal exposes a single flat `SortableContext` over ALL visible widgets, so a rep can drag (e.g.) "My Reminders" (rail) above "Pipeline Funnel" (chart). The new order persists correctly (`setOrder(...)` → `dashboardWidgetOrder` PATCH → optimistic cache update), and `visibleWidgets` recomputes sorted by rank. BUT the shell then re-buckets `visibleWidgets.filter(w => w.group === 'chart' | 'rail' | 'feed')` into three independent slots before rendering — so any cross-group reorder leaves the dashboard visually unchanged. Intra-group reorders DO work (within charts column, within rails aside, within feed). User-perceived bug: "rearranging apps in the customize modal still does not change the order of them."
|
||||
- **Decision needed** before fixing — two viable directions:
|
||||
- **(a) Flatten the dashboard layout** to a single ordered grid (drop the chart/rail/feed bucketing). Honour the rep's exact order across the whole page. Implementation: replace the three-block layout in DashboardShell with one auto-fit grid + per-widget span hints on the registry (`{ colSpan: 1 | 2 | 'full' }`); rails would naturally widen to their hinted column count, feed becomes a `col-span-full` row. Bigger UI surgery, but most honest semantics.
|
||||
- **(b) Scope the Customize modal sortable to per-group sub-lists.** Render three SortableContexts ("Charts", "Rails", "Feed") inside the modal, each with its own drag handles. Cross-group moves disallowed (or shown as a toggle to move a widget between groups). Smaller code change but loses the flexibility the current UI implies.
|
||||
- **Recommended:** (b) for the short-term fix (matches the actual rendering reality), with (a) parked as a v2 follow-up after we see whether reps actually want the flat layout.
|
||||
- **Effort:** ~30-45 min for (b); ~3-4 h for (a) including registry schema bump + responsive layout audit. Captured 2026-05-22 from UAT.
|
||||
- **SHIPPED in this session:** combined approach. At xl viewports the Customize modal renders three region-scoped sortables (Charts / Side rail / Activity) — matches the actual side-by-side dashboard layout. Below xl where the dashboard stacks all three regions into one visual column, the modal renders a single flat sortable so the rep can drag across regions freely. Plus per-viewport saved orders: `userPreferences.dashboardWidgetOrder` (xl/desktop) + new `dashboardWidgetOrderMobile` (stacked), so reps can customize each layout independently. The hook auto-picks the right field based on viewport.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user