feat(admin): inquiry inbox, send log, email-template overrides, reports dashboard, recommender keys, role-editor coverage; replace placeholder pages

Closes the bulk of audit-pass-#1 admin gaps in one batch.

New admin pages:
- /admin/inquiries reads website_submissions with filter chips for
  berth/residence/contact + payload viewer per row.
- /admin/sends reads document_sends with sent/failed filter chips and
  expandable body markdown; failures surface errorReason and any
  fallback-to-link reason from the SMTP retry.
- /admin/email-templates lets per-port admins override the subject of
  each transactional template (8 templates catalogued in
  template-catalog.ts). Body editing is a follow-on; portal_activation
  + portal_reset are wired to honor the override via loadSubjectOverride.
- /admin/reports replaces the "Coming in Layer 3" placeholder with a
  KPI dashboard: 4 KPI tiles, pipeline funnel bars, berth occupancy
  donut-bars, conversion %, refresh every 60s.
- backup/import/onboarding admin pages replace placeholders with
  actionable guidance: backup posture + planned features, available CLI
  imports + planned UI, ordered onboarding checklist linking to admin
  pages.

Existing pages widened:
- settings-manager exposes the 9 berth-recommender tunables that were
  previously code-only (recommender_*, heat_weight_*, fallthrough_*,
  tier_ladder_hide_late_stage).
- role-form covers all 19 RolePermissions schema groups; previously
  missing yachts/companies/memberships/reservations + missing
  documents.edit + files.edit checkboxes. snake_case residential
  labels replaced with friendly text.

portal-auth.service.ts now also writes audit_log rows for portal
invite, resend, activate, password-reset request, and reset (closes one
more audit-pass-#2 gap while we were touching the file).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-06 14:58:17 +02:00
parent 8cdee99310
commit c90876abad
22 changed files with 1703 additions and 54 deletions

View File

@@ -118,6 +118,77 @@ const KNOWN_SETTINGS: Array<{
approver: { name: 'Abbie May', email: 'sales@portnimara.com' },
},
},
// ─── Berth recommender (src/lib/services/berth-recommender.service.ts) ──────
{
key: 'recommender_max_oversize_pct',
label: 'Recommender — max oversize %',
description:
'Cap on how much larger a berth can be than the desired length/width/draft before it stops being suggested. Default 30.',
type: 'number',
defaultValue: 30,
},
{
key: 'recommender_top_n_default',
label: 'Recommender — default result count',
description: 'Default number of berth recommendations returned per request. Default 8.',
type: 'number',
defaultValue: 8,
},
{
key: 'fallthrough_policy',
label: 'Recommender — fall-through policy',
description:
'How berths re-enter the recommender after a lost deal. One of: immediate_with_heat, cooldown, never_auto_recommend.',
type: 'string',
defaultValue: 'immediate_with_heat',
},
{
key: 'fallthrough_cooldown_days',
label: 'Recommender — fall-through cooldown (days)',
description:
'Days a berth stays out of the recommender after a lost deal when the policy is `cooldown`. Default 30.',
type: 'number',
defaultValue: 30,
},
{
key: 'heat_weight_recency',
label: 'Heat weight — recency',
description: 'Weight given to how recently the prior interest fell through. Default 30.',
type: 'number',
defaultValue: 30,
},
{
key: 'heat_weight_furthest_stage',
label: 'Heat weight — furthest stage',
description:
'Weight given to how close the prior interest got to closing before falling through. Default 40.',
type: 'number',
defaultValue: 40,
},
{
key: 'heat_weight_interest_count',
label: 'Heat weight — historical interest count',
description:
'Weight given to how often this berth has attracted interest historically. Default 15.',
type: 'number',
defaultValue: 15,
},
{
key: 'heat_weight_eoi_count',
label: 'Heat weight — historical EOI count',
description:
'Weight given to how often interest in this berth has reached EOI signing. Default 15.',
type: 'number',
defaultValue: 15,
},
{
key: 'tier_ladder_hide_late_stage',
label: 'Recommender — hide late-stage tier',
description:
'Hide berths whose only active interests are late-stage (close to closing) from recommendations.',
type: 'boolean',
defaultValue: true,
},
];
export function SettingsManager() {