Files
pn-new-crm/src/components/clients/client-card.tsx

126 lines
4.3 KiB
TypeScript
Raw Normal View History

'use client';
import { Archive, MoreHorizontal, Pencil } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { TagBadge } from '@/components/shared/tag-badge';
import {
ListCard,
ListCardAvatar,
ListCardMeta,
deriveInitials,
} from '@/components/shared/list-card';
import { getCountryName } from '@/lib/i18n/countries';
import type { ClientRow } from './client-columns';
const SOURCE_LABELS: Record<string, string> = {
website: 'Website',
manual: 'Manual',
referral: 'Referral',
broker: 'Broker',
};
interface ClientCardProps {
client: ClientRow;
portSlug: string;
onEdit: (client: ClientRow) => void;
onArchive: (client: ClientRow) => void;
}
export function ClientCard({ client, portSlug, onEdit, onArchive }: ClientCardProps) {
const primary = client.contacts?.find((c) => c.isPrimary);
const nationality = client.nationalityIso ? getCountryName(client.nationalityIso, 'en') : null;
const sourceLabel = client.source ? (SOURCE_LABELS[client.source] ?? client.source) : null;
const yachtCount = client.yachtCount ?? 0;
const companyCount = client.companyCount ?? 0;
const tags = client.tags ?? [];
const meta = [nationality, sourceLabel].filter(Boolean) as string[];
const counts: string[] = [];
if (yachtCount > 0) counts.push(`${yachtCount} ${yachtCount === 1 ? 'yacht' : 'yachts'}`);
if (companyCount > 0)
counts.push(`${companyCount} ${companyCount === 1 ? 'company' : 'companies'}`);
return (
<ListCard
href={`/${portSlug}/clients/${client.id}`}
ariaLabel={`Client ${client.fullName}`}
actions={
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-9 w-9"
onClick={(e) => e.stopPropagation()}
aria-label={`Actions for ${client.fullName}`}
>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => onEdit(client)}>
<Pencil className="mr-2 h-3.5 w-3.5" />
Edit
</DropdownMenuItem>
<DropdownMenuItem className="text-destructive" onClick={() => onArchive(client)}>
<Archive className="mr-2 h-3.5 w-3.5" />
Archive
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
}
>
<div className="flex items-start gap-3">
<ListCardAvatar initials={deriveInitials(client.fullName)} />
<div className="min-w-0 flex-1">
<div className="flex items-start justify-between gap-2">
<h3 className="truncate text-base font-semibold tracking-tight text-foreground">
{client.fullName}
</h3>
<span aria-hidden className="block h-9 w-9 shrink-0" />
</div>
{primary ? (
<p className="truncate text-sm text-muted-foreground">{primary.value}</p>
) : null}
{meta.length > 0 ? (
<div className="mt-0.5 flex flex-wrap items-center gap-x-1.5 text-xs text-muted-foreground">
{meta.map((m, i) => (
<span key={m} className="inline-flex items-center gap-1">
{i > 0 ? <span aria-hidden>·</span> : null}
<ListCardMeta>{m}</ListCardMeta>
</span>
))}
</div>
) : null}
{counts.length > 0 ? (
<p className="mt-0.5 text-xs text-muted-foreground">{counts.join(' · ')}</p>
) : null}
{tags.length > 0 ? (
<div className="mt-2 flex flex-wrap gap-1">
{tags.slice(0, 2).map((tag) => (
<TagBadge key={tag.id} name={tag.name} color={tag.color} />
))}
{tags.length > 2 ? (
<span className="inline-flex items-center rounded-full bg-secondary px-2 py-0.5 text-xs text-secondary-foreground">
+{tags.length - 2}
</span>
) : null}
</div>
) : null}
</div>
</div>
</ListCard>
);
}