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:
2026-05-23 00:52:59 +02:00
parent 43719b49e9
commit 221ae5784e
749 changed files with 7440 additions and 3118 deletions

View File

@@ -72,7 +72,7 @@ const STATUS_TONE: Record<string, BadgeTone> = {
};
function dim(ft?: string | null, m?: string | null, minimum?: boolean | null): string {
if (!ft && !m) return '';
if (!ft && !m) return '-';
const parts = [ft ? `${ft}ft` : null, m ? `${m}m` : null].filter(Boolean);
return `${parts.join(' / ')}${minimum ? ' (min)' : ''}`;
}
@@ -95,23 +95,23 @@ export function BerthSpecPdf({
maintenance,
}: BerthSpecPdfProps) {
const status = (berth.status ?? 'available').toLowerCase();
const docMeta = `Mooring ${berth.mooringNumber ?? ''}${berth.area ? ` · ${berth.area}` : ''}`;
const docMeta = `Mooring ${berth.mooringNumber ?? '-'}${berth.area ? ` · ${berth.area}` : ''}`;
return (
<DocumentShell
portName={portName}
docTitle={`Berth Spec ${berth.mooringNumber ?? ''}`}
docTitle={`Berth Spec - ${berth.mooringNumber ?? '-'}`}
docMeta={docMeta}
logoBuffer={logoBuffer}
>
<Section title="Overview">
<KeyValueGrid
rows={[
{ label: 'Mooring', value: berth.mooringNumber ?? '' },
{ label: 'Area', value: berth.area ?? '' },
{ label: 'Mooring', value: berth.mooringNumber ?? '-' },
{ label: 'Area', value: berth.area ?? '-' },
{ label: 'Status', value: status.replace('_', ' ') },
{ label: 'Nominal boat size', value: berth.nominalBoatSize ?? '' },
{ label: 'Bow facing', value: berth.bowFacing ?? '' },
{ label: 'Side pontoon', value: berth.sidePontoon ?? '' },
{ label: 'Nominal boat size', value: berth.nominalBoatSize ?? '-' },
{ label: 'Bow facing', value: berth.bowFacing ?? '-' },
{ label: 'Side pontoon', value: berth.sidePontoon ?? '-' },
]}
/>
<Badge
@@ -142,9 +142,9 @@ export function BerthSpecPdf({
rows={[
{ label: 'Price', value: fmtPrice(berth.price, berth.priceCurrency) },
{ label: 'Tenure type', value: berth.tenureType ?? 'permanent' },
{ label: 'Tenure years', value: berth.tenureYears ?? '' },
{ label: 'Tenure start', value: berth.tenureStartDate ?? '' },
{ label: 'Tenure end', value: berth.tenureEndDate ?? '' },
{ label: 'Tenure years', value: berth.tenureYears ?? '-' },
{ label: 'Tenure start', value: berth.tenureStartDate ?? '-' },
{ label: 'Tenure end', value: berth.tenureEndDate ?? '-' },
]}
/>
</Section>
@@ -152,26 +152,26 @@ export function BerthSpecPdf({
<Section title="Infrastructure">
<KeyValueGrid
rows={[
{ label: 'Mooring type', value: berth.mooringType ?? '' },
{ label: 'Mooring type', value: berth.mooringType ?? '-' },
{
label: 'Power',
value: berth.powerCapacity
? `${berth.powerCapacity}${berth.voltage ? ` / ${berth.voltage}V` : ''}`
: '',
: '-',
},
{
label: 'Cleat',
value: berth.cleatType
? `${berth.cleatType}${berth.cleatCapacity ? ` (${berth.cleatCapacity})` : ''}`
: '',
: '-',
},
{
label: 'Bollard',
value: berth.bollardType
? `${berth.bollardType}${berth.bollardCapacity ? ` (${berth.bollardCapacity})` : ''}`
: '',
: '-',
},
{ label: 'Access', value: berth.access ?? '' },
{ label: 'Access', value: berth.access ?? '-' },
]}
/>
</Section>
@@ -182,15 +182,15 @@ export function BerthSpecPdf({
>
<DataTable<BerthSpecWaitingRow>
columns={[
{ header: '#', flex: 0.5, render: (w) => String(w.position ?? '') },
{ header: '#', flex: 0.5, render: (w) => String(w.position ?? '-') },
{ header: 'Client', flex: 3, render: (w) => w.clientName },
{
header: 'Priority',
flex: 1,
render: (w) =>
w.priority === 'high' ? <Badge text="High" tone="warning" /> : (w.priority ?? ''),
w.priority === 'high' ? <Badge text="High" tone="warning" /> : (w.priority ?? '-'),
},
{ header: 'Notes', flex: 3, render: (w) => w.notes ?? '' },
{ header: 'Notes', flex: 3, render: (w) => w.notes ?? '-' },
]}
rows={waitingList}
emptyMessage="No clients on waiting list."
@@ -200,9 +200,9 @@ export function BerthSpecPdf({
<Section title={`Maintenance log (${maintenance.length})`}>
<DataTable<BerthSpecMaintenanceRow>
columns={[
{ header: 'Date', flex: 1.5, render: (m) => m.performedDate ?? '' },
{ header: 'Category', flex: 1.5, render: (m) => m.category ?? '' },
{ header: 'Description', flex: 4, render: (m) => m.description ?? '' },
{ header: 'Date', flex: 1.5, render: (m) => m.performedDate ?? '-' },
{ header: 'Category', flex: 1.5, render: (m) => m.category ?? '-' },
{ header: 'Description', flex: 4, render: (m) => m.description ?? '-' },
{
header: 'Cost',
flex: 1.5,

View File

@@ -48,7 +48,7 @@ export interface ClientSummaryPdfProps {
}
function fmtDate(d: Date | string | null | undefined): string {
if (!d) return '';
if (!d) return '-';
return new Date(d).toISOString().slice(0, 10);
}
@@ -58,7 +58,7 @@ function yachtDims(y: YachtRow): string {
y.widthFt ? `${y.widthFt}ft beam` : null,
y.draftFt ? `${y.draftFt}ft draft` : null,
].filter(Boolean);
return parts.length ? parts.join(' · ') : '';
return parts.length ? parts.join(' · ') : '-';
}
export function ClientSummaryPdf({
@@ -85,9 +85,9 @@ export function ClientSummaryPdf({
<Section title="Client">
<KeyValueGrid
rows={[
{ label: 'Full name', value: client.fullName ?? '' },
{ label: 'Nationality', value: client.nationality ?? '' },
{ label: 'Source', value: client.source ?? '' },
{ label: 'Full name', value: client.fullName ?? '-' },
{ label: 'Nationality', value: client.nationality ?? '-' },
{ label: 'Source', value: client.source ?? '-' },
{ label: 'Added', value: fmtDate(client.createdAt) },
]}
/>
@@ -109,7 +109,7 @@ export function ClientSummaryPdf({
header: 'Primary',
flex: 1,
align: 'center',
render: (c) => (c.isPrimary ? <Badge text="Yes" tone="success" /> : ''),
render: (c) => (c.isPrimary ? <Badge text="Yes" tone="success" /> : '-'),
},
]}
rows={contacts}
@@ -132,8 +132,8 @@ export function ClientSummaryPdf({
<DataTable<InterestRow>
columns={[
{ header: 'Stage', flex: 2, render: (i) => i.pipelineStage ?? 'open' },
{ header: 'Berth', flex: 1, render: (i) => i.berthMooringNumber ?? '' },
{ header: 'Category', flex: 2, render: (i) => i.leadCategory ?? '' },
{ header: 'Berth', flex: 1, render: (i) => i.berthMooringNumber ?? '-' },
{ header: 'Category', flex: 2, render: (i) => i.leadCategory ?? '-' },
{ header: 'Created', flex: 1.5, render: (i) => fmtDate(i.createdAt) },
]}
rows={interests}
@@ -147,7 +147,7 @@ export function ClientSummaryPdf({
{ header: 'When', flex: 1.5, render: (a) => fmtDate(a.createdAt) },
{ header: 'Action', flex: 1.5, render: (a) => a.action },
{ header: 'Entity', flex: 1.5, render: (a) => a.entityType },
{ header: 'Field', flex: 1.5, render: (a) => a.fieldChanged ?? '' },
{ header: 'Field', flex: 1.5, render: (a) => a.fieldChanged ?? '-' },
]}
rows={activity}
emptyMessage="No recent activity."

View File

@@ -61,7 +61,7 @@ const STAGE_TONE: Record<string, BadgeTone> = {
};
function fmt(d: Date | string | null | undefined): string {
if (!d) return '';
if (!d) return '-';
return new Date(d).toISOString().slice(0, 10);
}
@@ -88,33 +88,33 @@ export function InterestSummaryPdf({
<KeyValueGrid
rows={[
{ label: 'Pipeline stage', value: stage.replace('_', ' ') },
{ label: 'Lead category', value: interest.leadCategory ?? '' },
{ label: 'Source', value: interest.source ?? '' },
{ label: 'EOI status', value: interest.eoiStatus ?? '' },
{ label: 'Contract status', value: interest.contractStatus ?? '' },
{ label: 'Deposit status', value: interest.depositStatus ?? '' },
{ label: 'Lead category', value: interest.leadCategory ?? '-' },
{ label: 'Source', value: interest.source ?? '-' },
{ label: 'EOI status', value: interest.eoiStatus ?? '-' },
{ label: 'Contract status', value: interest.contractStatus ?? '-' },
{ label: 'Deposit status', value: interest.depositStatus ?? '-' },
]}
/>
<Badge text={stage.replace('_', ' ').toUpperCase()} tone={STAGE_TONE[stage] ?? 'neutral'} />
</Section>
<Section title="Client">
<KeyValueGrid rows={[{ label: 'Name', value: client.fullName ?? '' }]} />
<KeyValueGrid rows={[{ label: 'Name', value: client.fullName ?? '-' }]} />
</Section>
{yacht ? (
<Section title="Yacht">
<KeyValueGrid
rows={[
{ label: 'Name', value: yacht.name ?? '' },
{ label: 'Name', value: yacht.name ?? '-' },
{
label: 'Length',
value: yacht.lengthFt
? `${yacht.lengthFt}ft${yacht.lengthM ? ` / ${yacht.lengthM}m` : ''}`
: '',
: '-',
},
{ label: 'Beam', value: yacht.widthFt ? `${yacht.widthFt}ft` : '' },
{ label: 'Draft', value: yacht.draftFt ? `${yacht.draftFt}ft` : '' },
{ label: 'Beam', value: yacht.widthFt ? `${yacht.widthFt}ft` : '-' },
{ label: 'Draft', value: yacht.draftFt ? `${yacht.draftFt}ft` : '-' },
]}
/>
</Section>
@@ -124,16 +124,16 @@ export function InterestSummaryPdf({
<Section title="Primary berth">
<KeyValueGrid
rows={[
{ label: 'Mooring', value: berth.mooringNumber ?? '' },
{ label: 'Area', value: berth.area ?? '' },
{ label: 'Length', value: berth.lengthFt ? `${berth.lengthFt}ft` : '' },
{ label: 'Mooring', value: berth.mooringNumber ?? '-' },
{ label: 'Area', value: berth.area ?? '-' },
{ label: 'Length', value: berth.lengthFt ? `${berth.lengthFt}ft` : '-' },
{
label: 'Price',
value: berth.price
? `${berth.priceCurrency ?? 'USD'} ${Number(berth.price).toLocaleString()}`
: '',
: '-',
},
{ label: 'Status', value: berth.status ?? '' },
{ label: 'Status', value: berth.status ?? '-' },
]}
/>
</Section>
@@ -162,9 +162,9 @@ export function InterestSummaryPdf({
}>
columns={[
{ header: 'When', flex: 1.5, render: (e) => fmt(e.createdAt) },
{ header: 'Action', flex: 2, render: (e) => e.action ?? '' },
{ header: 'Entity', flex: 1.5, render: (e) => e.entityType ?? '' },
{ header: 'Field', flex: 1.5, render: (e) => e.fieldChanged ?? '' },
{ header: 'Action', flex: 2, render: (e) => e.action ?? '-' },
{ header: 'Entity', flex: 1.5, render: (e) => e.entityType ?? '-' },
{ header: 'Field', flex: 1.5, render: (e) => e.fieldChanged ?? '-' },
]}
rows={timeline}
emptyMessage="No timeline events."

View File

@@ -38,7 +38,7 @@ export function ParentCompanyExpensePdf({
}: ParentCompanyExpensePdfProps) {
const meta =
dateFrom || dateTo
? `Range: ${dateFrom ?? ''}${dateTo ?? 'today'} · ${rows.length} entries`
? `Range: ${dateFrom ?? '-'}${dateTo ?? 'today'} · ${rows.length} entries`
: `${rows.length} entries`;
return (
@@ -64,7 +64,7 @@ export function ParentCompanyExpensePdf({
{
label: 'Currency conversion',
value:
'EUR exchange rate unavailable at generation time amounts shown at 1:1 USD:EUR fallback.',
'EUR exchange rate unavailable at generation time - amounts shown at 1:1 USD:EUR fallback.',
},
]}
layout="stacked"

View File

@@ -65,7 +65,7 @@ function busiestDay(logs: RowShape[]): string {
bestCount = count;
}
}
return best ? `${best} (${bestCount})` : '';
return best ? `${best} (${bestCount})` : '-';
}
export function ActivityReportPdf({
@@ -76,10 +76,10 @@ export function ActivityReportPdf({
dateTo,
}: ActivityReportPdfProps) {
const topActions = topEntries(data.summary, 5);
const topAction = topActions[0]?.key ?? '';
const topAction = topActions[0]?.key ?? '-';
const meta =
dateFrom || dateTo
? `Range: ${dateFrom ?? ''}${dateTo ?? 'today'} · ${data.logs.length} events`
? `Range: ${dateFrom ?? '-'}${dateTo ?? 'today'} · ${data.logs.length} events`
: `Last 30 days · ${data.logs.length} events`;
const chartData = bucketByDay(data.logs);
@@ -127,7 +127,7 @@ export function ActivityReportPdf({
},
{ header: 'Action', flex: 1.5, render: (r) => r.action },
{ header: 'Entity', flex: 1.5, render: (r) => r.entityType },
{ header: 'User', flex: 1.5, render: (r) => r.userId ?? '' },
{ header: 'User', flex: 1.5, render: (r) => r.userId ?? '-' },
]}
rows={tableRows}
emptyMessage="No activity in the selected period."

View File

@@ -77,7 +77,7 @@ export function OccupancyReportPdf({ portName, logoBuffer, data }: OccupancyRepo
flex: 1,
align: 'right',
render: (r) =>
data.totalBerths > 0 ? `${((r.count / data.totalBerths) * 100).toFixed(1)}%` : '',
data.totalBerths > 0 ? `${((r.count / data.totalBerths) * 100).toFixed(1)}%` : '-',
},
]}
rows={entries.map(([status, count]) => ({ status, count }))}

View File

@@ -54,7 +54,7 @@ export function PipelineReportPdf({ portName, logoBuffer, data }: PipelineReport
{ label: 'Completed', value: completed.toLocaleString() },
{
label: 'Top stage',
value: topStage ? `${stageLabel(topStage[0])} (${topStage[1]})` : '',
value: topStage ? `${stageLabel(topStage[0])} (${topStage[1]})` : '-',
},
{ label: 'Cancelled / Lost', value: cancelled.toLocaleString() },
]}
@@ -74,7 +74,7 @@ export function PipelineReportPdf({ portName, logoBuffer, data }: PipelineReport
header: 'Berth price',
flex: 2,
align: 'right',
render: (r) => (r.berthPrice ? Number(r.berthPrice).toLocaleString() : ''),
render: (r) => (r.berthPrice ? Number(r.berthPrice).toLocaleString() : '-'),
},
]}
rows={data.topInterests}

View File

@@ -52,7 +52,7 @@ export function RevenueReportPdf({
const totalCompleted = Number(data.totalCompleted);
const totalForecast = Number(data.totalForecast);
const subtotal = rows.reduce((s, r) => s + r.amount, 0);
const meta = dateFrom || dateTo ? `Range: ${dateFrom ?? ''}${dateTo ?? 'today'}` : 'All time';
const meta = dateFrom || dateTo ? `Range: ${dateFrom ?? '-'}${dateTo ?? 'today'}` : 'All time';
return (
<DocumentShell