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>
This commit is contained in:
Matt Ciaccio
2026-05-04 22:57:01 +02:00
parent d62822c284
commit 8699f81879
225 changed files with 844 additions and 845 deletions

View File

@@ -70,7 +70,7 @@ export function getClientColumns({
enableSorting: false,
cell: ({ row }) => {
const primary = row.original.contacts?.find((c) => c.isPrimary);
if (!primary) return <span className="text-muted-foreground"></span>;
if (!primary) return <span className="text-muted-foreground">-</span>;
return (
<span className="text-sm">
<span className="text-muted-foreground capitalize">{primary.channel}: </span>
@@ -86,7 +86,7 @@ export function getClientColumns({
cell: ({ getValue }) => {
const iso = getValue() as string | null;
return (
<span className="text-muted-foreground">{iso ? getCountryName(iso, 'en') : ''}</span>
<span className="text-muted-foreground">{iso ? getCountryName(iso, 'en') : '-'}</span>
);
},
},
@@ -96,7 +96,7 @@ export function getClientColumns({
header: 'Source',
cell: ({ getValue }) => {
const source = getValue() as string | null;
if (!source) return <span className="text-muted-foreground"></span>;
if (!source) return <span className="text-muted-foreground">-</span>;
return (
<Badge variant="outline" className="capitalize text-xs">
{SOURCE_LABELS[source] ?? source}
@@ -111,7 +111,7 @@ export function getClientColumns({
cell: ({ row }) => {
const c = row.original.yachtCount ?? 0;
return c === 0 ? (
<span className="text-muted-foreground"></span>
<span className="text-muted-foreground">-</span>
) : (
<Badge variant="secondary" className="text-xs">
{c}
@@ -126,7 +126,7 @@ export function getClientColumns({
cell: ({ row }) => {
const c = row.original.companyCount ?? 0;
return c === 0 ? (
<span className="text-muted-foreground"></span>
<span className="text-muted-foreground">-</span>
) : (
<Badge variant="secondary" className="text-xs">
{c}
@@ -140,7 +140,7 @@ export function getClientColumns({
enableSorting: false,
cell: ({ row }) => {
const clientTags = row.original.tags ?? [];
if (clientTags.length === 0) return <span className="text-muted-foreground"></span>;
if (clientTags.length === 0) return <span className="text-muted-foreground">-</span>;
return (
<div className="flex flex-wrap gap-1">
{clientTags.slice(0, 3).map((tag) => (

View File

@@ -33,7 +33,7 @@ interface ClientCompaniesTabProps {
function formatSince(startDate: string | Date): string {
const d = typeof startDate === 'string' ? new Date(startDate) : startDate;
if (Number.isNaN(d.getTime())) return '';
if (Number.isNaN(d.getTime())) return '-';
return format(d, 'MMM d, yyyy');
}
@@ -87,7 +87,7 @@ export function ClientCompaniesTab({ clientId: _clientId, companies }: ClientCom
Primary
</Badge>
) : (
<span className="text-muted-foreground"></span>
<span className="text-muted-foreground">-</span>
)}
</TableCell>
<TableCell className="text-muted-foreground text-sm">

View File

@@ -169,7 +169,7 @@ export function ClientDetailHeader({ client }: ClientDetailHeaderProps) {
)}
</div>
{/* Top-right: archive/restore as a small icon button destructive
{/* Top-right: archive/restore as a small icon button - destructive
action sits out of the primary action flow. */}
<button
type="button"

View File

@@ -150,7 +150,7 @@ export function ClientForm({ open, onOpenChange, client, onUseExistingClient }:
</SheetHeader>
<form onSubmit={handleSubmit((data) => mutation.mutate(data))} className="space-y-6 py-6">
{/* Dedup suggestion only on the create path. Watches the
{/* Dedup suggestion - only on the create path. Watches the
live form values for email / phone / name and surfaces
an existing client when one matches. The user can
attach the new interest to that client instead of

View File

@@ -180,7 +180,7 @@ function InterestPreviewDrawer({
}) {
// Pin the most recently selected interest so the drawer stays populated
// during the close-animation tail (Vaul keeps the content mounted ~250ms
// after `open=false`). Conditional setState is safe here the guard
// after `open=false`). Conditional setState is safe here - the guard
// ensures it only fires when the prop actually changes to a new row.
const [pinned, setPinned] = useState<ClientInterestRow | null>(interest);
if (interest && interest !== pinned) setPinned(interest);
@@ -243,7 +243,7 @@ function InterestPreviewDrawer({
</DrawerHeader>
<div className="space-y-5 overflow-y-auto px-4 pb-4">
{/* Pipeline-stepper segmented bar the same primitive used on the
{/* Pipeline-stepper segmented bar - the same primitive used on the
row card, so the at-a-glance progress hint is consistent
across surfaces. */}
{stage ? (
@@ -255,7 +255,7 @@ function InterestPreviewDrawer({
</div>
) : null}
{/* Milestones three sections matching the full interest detail
{/* Milestones - three sections matching the full interest detail
page (EOI / Deposit / Contract). Done-state is derived from
the pipeline stage so seed data without per-step dates still
renders correctly. The full milestone columns + per-step
@@ -308,7 +308,7 @@ function InterestPreviewDrawer({
</div>
</section>
{/* Compact key/value pairs lead category, source, last contact,
{/* Compact key/value pairs - lead category, source, last contact,
activity. Each row collapses cleanly when its value is
missing so the drawer scales from sparse seed data to full
records without empty placeholders. */}

View File

@@ -106,8 +106,8 @@ function lastActivityLabel(interests: ClientInterestRow[]): string | null {
interface PipelineSummaryProps {
clientId: string;
/**
* `hero` single-line pulse for the detail header (highest active stage only).
* `panel` compact list of every active interest, for the Overview tab.
* `hero` - single-line pulse for the detail header (highest active stage only).
* `panel` - compact list of every active interest, for the Overview tab.
*/
variant?: 'hero' | 'panel';
}

View File

@@ -74,9 +74,9 @@ export function ClientYachtsTab({ clientId: _clientId, yachts }: ClientYachtsTab
</Link>
</TableCell>
<TableCell>
{y.lengthFt && y.widthFt ? `${y.lengthFt} × ${y.widthFt} ft` : ''}
{y.lengthFt && y.widthFt ? `${y.lengthFt} × ${y.widthFt} ft` : '-'}
</TableCell>
<TableCell>{y.hullNumber ?? ''}</TableCell>
<TableCell>{y.hullNumber ?? '-'}</TableCell>
<TableCell className="capitalize">{y.status.replace('_', ' ')}</TableCell>
</TableRow>
))}

View File

@@ -225,10 +225,10 @@ function ContactRow({
{/* Bottom / right: tag + actions.
Two layers of hiding compose here:
(a) phoneEditing when the phone editor is open, hide the entire
(a) phoneEditing - when the phone editor is open, hide the entire
action cluster (tag + star + trash) so the user can focus on
the form without chips fighting for space.
(b) contact.value when the value is empty (stale import row,
(b) contact.value - when the value is empty (stale import row,
aborted edit), hide just the tag + Make-primary star;
neither makes sense without a value. The trash icon stays
so the user can clean up the empty entry.

View File

@@ -63,7 +63,7 @@ export function DedupSuggestionPanel({
useEffect(() => {
const t = setTimeout(() => {
setDebounced({ email: email ?? '', phone: phone ?? '', name: name ?? '' });
// Clear the dismissed flag when inputs change the user typed
// Clear the dismissed flag when inputs change - the user typed
// something new, so the prior dismissal no longer applies.
setDismissed(false);
}, 300);
@@ -83,7 +83,7 @@ export function DedupSuggestionPanel({
return apiFetch<{ data: MatchData[] }>(`/api/v1/clients/match-candidates?${params}`);
},
enabled: hasSomething && !dismissed,
// Same query is fine to cache for a minute moves are slow at this layer.
// Same query is fine to cache for a minute - moves are slow at this layer.
staleTime: 60_000,
});
@@ -120,7 +120,7 @@ export function DedupSuggestionPanel({
<p className="text-sm font-semibold leading-tight">
{isHigh
? 'This looks like an existing client'
: 'Possible match check before creating'}
: 'Possible match - check before creating'}
</p>
<div className="mt-2 rounded-md border bg-background/80 p-2.5">
<div className="flex items-center gap-2">

View File

@@ -74,7 +74,7 @@ export function GdprExportButton({ clientId }: { clientId: string }) {
},
}),
onSuccess: () => {
toast.success('Export queued refresh in ~30 seconds');
toast.success('Export queued - refresh in ~30 seconds');
qc.invalidateQueries({ queryKey });
setEmailOverride('');
},
@@ -128,7 +128,7 @@ export function GdprExportButton({ clientId }: { clientId: string }) {
Email the bundle when ready
</Label>
<p className="text-xs text-muted-foreground">
Sends a 7-day signed download link to the client&apos;s primary email or to the
Sends a 7-day signed download link to the client&apos;s primary email - or to the
override below.
</p>
{emailToClient ? (