Files
pn-new-crm/src/components/yachts/yacht-card.tsx
Matt Ciaccio 8699f81879
Some checks failed
Build & Push Docker Images / lint (push) Failing after 1m18s
Build & Push Docker Images / build-and-push (push) Has been skipped
chore(style): codebase em-dash sweep + minor layout polish
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

143 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { Archive, Building2, Eye, MoreHorizontal, Pencil, Ship, User } from 'lucide-react';
import Link from 'next/link';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { ListCard, ListCardAvatar, ListCardMeta } from '@/components/shared/list-card';
import { cn } from '@/lib/utils';
import type { YachtRow } from './yacht-columns';
const STATUS_COLORS: Record<string, string> = {
active: 'bg-green-100 text-green-800 border-green-300',
retired: 'bg-gray-100 text-gray-800 border-gray-300',
sold_away: 'bg-amber-100 text-amber-800 border-amber-300',
};
const STATUS_LABELS: Record<string, string> = {
active: 'Active',
retired: 'Retired',
sold_away: 'Sold Away',
};
interface YachtCardProps {
yacht: YachtRow;
portSlug: string;
onEdit: (yacht: YachtRow) => void;
onArchive: (yacht: YachtRow) => void;
}
export function YachtCard({ yacht, portSlug, onEdit, onArchive }: YachtCardProps) {
const statusLabel = STATUS_LABELS[yacht.status] ?? yacht.status;
const statusColor = STATUS_COLORS[yacht.status] ?? 'bg-muted text-muted-foreground border-muted';
// Prefer metric dimensions; fall back to imperial
let dimText: string | null = null;
if (yacht.lengthM || yacht.widthM) {
const l = yacht.lengthM ?? '-';
const w = yacht.widthM ?? '-';
dimText = `${l}m × ${w}m`;
} else if (yacht.lengthFt || yacht.widthFt) {
const l = yacht.lengthFt ?? '-';
const w = yacht.widthFt ?? '-';
dimText = `${l}ft × ${w}ft`;
}
const metaParts: string[] = [];
if (dimText) metaParts.push(dimText);
if (yacht.hullNumber) metaParts.push(`Hull #${yacht.hullNumber}`);
const OwnerIcon = yacht.currentOwnerType === 'company' ? Building2 : User;
return (
<ListCard
href={`/${portSlug}/yachts/${yacht.id}`}
ariaLabel={`Yacht ${yacht.name}`}
actions={
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-9 w-9"
onClick={(e) => e.stopPropagation()}
aria-label={`Actions for ${yacht.name}`}
>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem asChild>
<Link href={`/${portSlug}/yachts/${yacht.id}`}>
<Eye className="mr-2 h-3.5 w-3.5" />
View
</Link>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onEdit(yacht)}>
<Pencil className="mr-2 h-3.5 w-3.5" />
Edit
</DropdownMenuItem>
<DropdownMenuItem className="text-destructive" onClick={() => onArchive(yacht)}>
<Archive className="mr-2 h-3.5 w-3.5" />
Archive
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
}
>
<div className="flex items-start gap-3">
<ListCardAvatar icon={<Ship className="h-5 w-5" />} />
<div className="min-w-0 flex-1">
{/* Title row + spacer for actions button */}
<div className="flex items-start justify-between gap-2">
<h3 className="truncate text-base font-semibold tracking-tight text-foreground">
{yacht.name}
</h3>
<span aria-hidden className="block h-9 w-9 shrink-0" />
</div>
{/* Owner subtitle */}
{yacht.currentOwnerName ? (
<p className="mt-0.5 inline-flex items-center gap-1 truncate text-sm text-muted-foreground">
<OwnerIcon className="h-3.5 w-3.5 shrink-0 text-muted-foreground/70" aria-hidden />
<span className="truncate">{yacht.currentOwnerName}</span>
</p>
) : null}
{/* Dimensions · Hull number */}
{metaParts.length > 0 ? (
<div className="mt-0.5 flex flex-wrap items-center gap-x-1.5 text-xs text-muted-foreground">
{metaParts.map((part, i) => (
<span key={part} className="inline-flex items-center gap-1">
{i > 0 ? <span aria-hidden>·</span> : null}
<ListCardMeta>{part}</ListCardMeta>
</span>
))}
</div>
) : null}
{/* Status pill */}
{yacht.status ? (
<div className="mt-1.5">
<span
className={cn(
'inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium',
statusColor,
)}
>
{statusLabel}
</span>
</div>
) : null}
</div>
</div>
</ListCard>
);
}