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:
@@ -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,
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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 }))}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user