Commit Graph

13 Commits

Author SHA1 Message Date
0e4a2d7396 feat(record-export): migrate client/berth/interest summaries to react-pdf
Phase 1 / commits 7-9 of 14 — bundled because all three record exports
share the same conversion pattern and call sites.

Templates:
  client-summary.tsx      header + KV grid for client, contacts table
                          with primary badge, yacht table, interests
                          table with stage/category, recent activity
                          table
  berth-spec.tsx          header + status badge, overview KV grid,
                          dimensions KV grid (with min markers), pricing
                          & tenure KV grid, infrastructure KV grid,
                          waiting list table with priority badges,
                          maintenance log table
  interest-summary.tsx    header + stage badge, status KV grid, client
                          KV, optional yacht/berth sections, milestones
                          KV grid, recent timeline table

record-export.tsx (renamed .ts -> .tsx for JSX):
  - swap generatePdf(...) calls for renderPdf(<…Pdf … />) calls
  - inject port logo via resolvePortLogo()
  - shape data into typed template props (Drizzle returns are passed
    through deliberately so the template controls its own type surface)

Drops two latent bugs the old templates carried:
  - client.nationality was read as a property but the schema field is
    nationalityIso — old PDFs always showed "—" for nationality
  - interest.notes was read but the interests table doesn't have a
    notes column (interest_berths does) — old PDFs always showed "No
    notes"
Both fields are now sourced correctly (or omitted) in the new templates.

Old pdfme files deleted (3 templates). API routes that import
exportClientPdf/exportBerthPdf/exportInterestPdf unchanged.

Tests:
  tests/unit/record-export-templates.test.tsx (4 tests): each template
  renders to valid PDF bytes with representative data, plus a minimal-
  input path for the berth spec.

1317/1317 vitest green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:59:05 +02:00
90fbb66709 feat(reports): migrate 4 reports from pdfme to react-pdf
Phase 1 / commits 3-6 of 14 — bundled because every report follows the
same conversion pattern (coordinate-stuffed pdfme template -> JSX brand
kit). Each report now has a real header (logo + port name), structured
KeyValueGrid for summary stats, a chart (BarChart / FunnelChart / PieChart
/ LineChart-ready), and a DataTable for detail rows.

Templates:
  activity-report.tsx   bar chart of events-per-day, summary KPIs, top
                        actions table, recent-events table (50 rows)
  revenue-report.tsx    bar chart of revenue per stage, breakdown table
                        with totals row, currency-aware formatting
  pipeline-report.tsx   funnel chart of interests per stage, top interests
                        table, win rate / cycle KPIs
  occupancy-report.tsx  donut pie of berth status mix, status breakdown
                        table with percentages, occupancy rate KPI

reports.service.tsx (renamed .ts -> .tsx for JSX):
  - swap REPORT_TYPE_MAP `template`/`buildInputs` for a single `render`
    function returning a typed react-pdf element
  - inject port logo via resolvePortLogo() and pass through to every
    template through a ReportContext object
  - keep the existing job queue / storage / file-row / socket-emit
    flow intact — only the inner PDF-bytes generation changed

Old pdfme files deleted (4 templates). buildStoragePath / files-table
insert / notifications / status updates all unchanged.

Tests:
  tests/unit/report-templates.test.tsx (5 tests): each report renders
  to valid PDF bytes given a representative seed-style fixture; empty
  data path doesn't throw.

1313/1313 vitest green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:55:07 +02:00
6517e014a6 feat(branding): port logo upload pipeline for internal PDFs
Phase 1 / commit 2 of 14 — adds the admin-facing logo upload that the
brand-kit Header pulls in for every internal-only PDF.

Server pipeline (src/lib/services/logo.service.ts):
  - magic-byte format check via sharp metadata
  - rejects animated/multi-frame inputs
  - SVGs sanitized via svgo preset-default + post-pass regex check
    (rejects <script>, on*=, javascript:, external href, <foreignObject>),
    then rasterized to PNG at 300 DPI
  - HEIC/HEIF/AVIF/WEBP all auto-converted to PNG by sharp
  - optional crop coords applied server-side (bounds-checked first)
  - auto-trim near-white borders
  - resize so longest edge <= 1200px, sRGB, palette-PNG
  - rejects undersized output (< 200px any side) or > 1MB
  - atomic system_settings upsert; soft-archives prior file row + storage object

API:
  GET    /api/v1/admin/branding/logo            current logo metadata
  POST   /api/v1/admin/branding/logo            multipart upload + crop
  DELETE /api/v1/admin/branding/logo            clear; future PDFs fall back
                                                 to port-name text header
  GET    /api/v1/admin/branding/logo/sample-pdf renders branding-sample.tsx
                                                 with the current logo so
                                                 admins can spot-check
                                                 letterboxing in real shell

UI:
  src/components/admin/branding/pdf-logo-uploader.tsx
    - react-image-crop with Wide 3:1 / Square 1:1 / Freeform aspect toggle
    - file picker accepts PNG/JPEG/WEBP/SVG/HEIC/HEIF/AVIF (up to 5 MB)
    - dark-band preview swatch shows how the logo lands in the header
    - post-upload warnings panel surfaces every server-side normalization
      (resized, trimmed, JPEG no-alpha warning, SVG rasterized, etc.)
    - "Test with sample PDF" button streams a real PDF for spot-check
    - "Remove" tears down the file + storage object + setting
  Wired into the existing /admin/branding settings page beneath the
  Identity and Email-branding cards.

Audit:
  Two new AuditAction enum values added: branding.logo.uploaded and
  branding.logo.archived. Captured per upload + per archived prior logo.

Tests:
  tests/unit/logo-service.test.ts (11 tests): sharp pipeline happy path,
  undersized rejection, empty/oversized rejection, non-image rejection,
  out-of-bounds crop rejection, in-bounds crop, SVG rasterization, SVG
  with embedded script rejection, SVG with external href rejection,
  JPEG-with-no-alpha warning collection.

1308/1308 vitest green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:51:49 +02:00
4b9743a594 audit: 33-agent comprehensive audit + critical fixes
Full team audit run, all reports verbatim in docs/AUDIT-2026-05-12.md
(5900+ lines, 30+ critical findings). Already-fixed this commit:
- permission-overrides PUT: self-target block + RolePermissions allow-list + cross-tenant guard
- /api/auth/resolve-identifier: rate-limit + synthetic miss-email kill enumeration
- admin email-change: rotates account.accountId + revokes sessions
- middleware: token-gated email confirm/cancel routes whitelisted
- NAV_CATALOG: 10 dead-link sweeps to existing /admin/<x> targets

Feature work landing same commit: optional username sign-in
(migration 0054), per-user permission overrides (0055) with three-state
matrix tabbed inside UserForm, user disable button, role + outcome +
stage label normalisation across the platform, admin email-change
with auto-notification template.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:52:35 +02:00
0e8feb1073 chore: prettier format pass on branch files
Auto-format all files modified during the documents-hub-split feature
branch that were not yet aligned with the project's Prettier config
(single quotes, semicolons, trailing commas).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 13:01:47 +02:00
43191659e6 feat(currency): sweep remaining concat call sites to formatCurrency
Builds on the centralised formatter shipped in ee2da8f. Replaces
\`\${currency} \${amount}\` style concatenations across the dashboard
revenue tooltip, command-search invoice/expense fallback labels,
expense-duplicate banner, and the invoice + expense PDF templates.
Drops the duplicate \`currencySymbol\` helper inside expense-pdf.service
in favour of the shared util; the two PDF helpers (renderReceiptHeader,
addReceiptErrorPage) now take a currency code instead of a pre-rendered
symbol so the formatter is the single source for spacing + thousands
separators. Also re-runs Prettier on the files where the prior commit
shipped without it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:35:34 +02:00
Matt Ciaccio
8699f81879 chore(style): codebase em-dash sweep + minor layout polish
Some checks failed
Build & Push Docker Images / lint (push) Failing after 1m18s
Build & Push Docker Images / build-and-push (push) Has been skipped
Replaces every em-dash and en-dash with regular ASCII hyphens
across comments, JSX strings, and dev-facing logs. Mostly cosmetic
but stops the inconsistent mix that crept in over the last few
months (some files used em-dashes in comments, others didn't,
some used both).

Bundles two small dashboard-layout tweaks that touch a couple of
already-modified files:
- (dashboard)/layout.tsx main padding goes from p-6 to pt-3 px-6
  pb-6 so page content sits closer to the topbar.
- Sidebar now receives the ports list it needs for the footer
  port switcher.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 22:57:01 +02:00
Matt Ciaccio
49d92234dd fix(test): align stage names with consolidated pipeline enum
Followup to 886119c (refactor(sales): consolidate pipeline stages) — the
runtime enum was renamed but a few test fixtures and PDF report templates
still referenced the legacy names, leaving them broken at the type level
(36 tsc errors before this fix).

Renames in this commit:
  visited        -> in_communication (alerts test) / removed (PDF reports)
  signed_eoi_nda -> eoi_signed
  contract       -> contract_signed (interests test) / contract_sent (factory)

Affected files: pipeline-report, revenue-report, makeCreateInterestInput
factory, alerts-engine, pipeline-transitions, interest-scoring.

Verification: tsc clean, 858/858 vitest passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:14:04 +02:00
Matt Ciaccio
0ed401d083 refactor(clients): drop deprecated yacht/company/proxy columns
PR 13: now that all reads are migrated to the dedicated yacht / company
/ membership entities, drop the columns that mirrored them on `clients`:
companyName, isProxy, proxyType, actualOwnerName, relationshipNotes,
yachtName, yachtLength{Ft,M}, yachtWidth{Ft,M}, yachtDraft{Ft,M},
berthSizeDesired.

Migration `0008_loud_ikaris.sql` issues the destructive ALTER TABLE
DROP COLUMN statements. Run `pnpm db:push` (or the migration runner) to
apply.

Caller cleanup (zero behavioral change to remaining flows):

- Drops the legacy `generateEoi` flow entirely (route, service function,
  pdfme template, validator schema). The dual-path generate-and-sign
  service from PR 11 has fully replaced it; the route was no longer
  wired to the UI.
- `clients.service`: company-name search column / WHERE / audit value
  removed; search now ranks by full name only.
- `interests.service`: `resolveLeadCategory` reads dimensions from
  `yachts` via `interest.yachtId` instead of the dropped
  `client.yachtLength{Ft,M}`.
- `record-export`: client-summary now lists yachts via owner-side
  lookup (direct + active company memberships); interest-summary fetches
  yacht via `interest.yachtId`. Both PDF templates updated to read
  yacht details from the new entity.
- `client-detail-header`, `client-picker`, `command-search`,
  `search-result-item`, `use-search` hook, `types/domain.ts`,
  `search.service` — drop the companyName badge / sub-label / typed
  field everywhere it was rendered or fetched.
- `ai.ts` worker: drop the company / yacht context lines from the
  prompt (will be re-added later sourced from the new entities).
- `validators/interests.ts`: remove the deprecated public-form flat
  yacht/company fields. The route already ignores them.
- `factories.ts`: drop the `isProxy: false` default.

Tests: 652/652 green; type-check clean. The
`security-sensitive-data` tests use `companyName` / `isProxy` as
arbitrary record keys for a generic util — left unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 13:57:54 +02:00
Matt Ciaccio
7ef7b9bb5f feat(eoi): seed Standard EOI in-app template per port
Adds a new per-port document_templates row of type 'eoi' containing an
HTML EOI / Letter of Intent body with {{section.field}} merge tokens
that mirror the EoiContext shape. Enables the in-app pdfme PDF path as
an alternative to the Documenso template flow.

- New getStandardEoiTemplateHtml() returns the Letter-sized HTML body
  with Applicant / Yacht / Owner / Berth / Interest / Signatures blocks
- STANDARD_EOI_MERGE_FIELDS exported for resolveTemplate wiring (11.4)
- seed-data.ts inserts one document_templates row per port inside the
  existing withTransaction block, between ownership transfers and
  interests, using SEED_USER_ID for audit consistency

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:13:51 +02:00
082d4f20e3 Fix all TypeScript errors: restore proper types and typed route casts
Some checks failed
Build & Push Docker Images / lint (push) Successful in 1m16s
Build & Push Docker Images / build-and-push (push) Failing after 4m42s
- Restore `as any` casts for Next.js typedRoutes on dynamic routes
- Use proper types for PDF templates, invoice/expense data, DB schema
- Fix PgColumn casts in sort helpers for expenses/invoices
- Add null guards for optional port/client in record-export
- Fix vitest config (remove invalid poolOptions)
- Lint: 0 errors, TypeScript: 0 errors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:29:55 +01:00
4c20bcffcd Fix all ESLint errors: remove unused imports, replace any types
Some checks failed
Build & Push Docker Images / lint (push) Failing after 1m10s
Build & Push Docker Images / build-and-push (push) Has been skipped
Build & Push Docker Images / deploy (push) Has been skipped
- Remove ~60 unused imports and variables across 88 files
- Replace ~80 `any` type annotations with proper types (unknown,
  Record<string, unknown>, or specific types)
- Prefix unused callback args with underscore
- Fix unescaped JSX entities
- Lint now passes cleanly (0 errors, 2 intentional img warnings)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:06:18 +01:00
67d7e6e3d5 Initial commit: Port Nimara CRM (Layers 0-4)
Some checks failed
Build & Push Docker Images / build-and-push (push) Has been cancelled
Build & Push Docker Images / deploy (push) Has been cancelled
Build & Push Docker Images / lint (push) Has been cancelled
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00