'use client'; import Link from 'next/link'; import { useParams } from 'next/navigation'; import { useQuery } from '@tanstack/react-query'; import { Globe } from 'lucide-react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Skeleton } from '@/components/ui/skeleton'; import { apiFetch } from '@/lib/api/client'; import { getCountryName } from '@/lib/i18n/countries'; import { cn } from '@/lib/utils'; interface ClientsByCountryRow { country: string; count: number; } interface ClientsByCountryResponse { data: ClientsByCountryRow[]; total: number; } /** * Compact ranked-list widget showing the per-country distribution of * non-archived clients. Designed to fit the rail tile footprint (no * external chart library); the mini-bar per row gives leadership an * at-a-glance feel for whether the book is concentrated or diverse. * * Each row deep-links to `/clients?country=` so the rep can drill * into a specific market. Country names render via the existing * locale-aware helper; unknown ISO codes fall back to the raw code. * * Variant (b) of the master-doc design — a true choropleth would need * a heavier viz lib (react-simple-maps + topojson) and pushes us to * the chart-library migration agenda. Variant (a) ships now; the * world-map variant can land alongside the recharts→ECharts pass. */ export function ClientsByCountryWidget({ limit = 8 }: { limit?: number } = {}) { const params = useParams<{ portSlug: string }>(); const portSlug = params?.portSlug ?? ''; const { data, isLoading } = useQuery({ queryKey: ['dashboard', 'clients-by-country'], queryFn: () => apiFetch('/api/v1/dashboard/clients-by-country'), staleTime: 60_000, }); if (isLoading) { return ( Clients by country Distribution of the active client book. {Array.from({ length: 4 }).map((_, i) => ( ))} ); } const rows = data?.data ?? []; const visibleRows = rows.slice(0, limit); const hiddenCount = Math.max(0, rows.length - limit); const maxCount = visibleRows.reduce((m, r) => Math.max(m, r.count), 0) || 1; return ( Clients by country {rows.length === 0 ? 'No clients with a country recorded yet.' : `${data?.total ?? rows.reduce((s, r) => s + r.count, 0)} client${rows.length === 1 ? '' : 's'} across ${rows.length} ${rows.length === 1 ? 'country' : 'countries'}.`} {rows.length === 0 ? (
Distribution will appear once clients capture a nationality.
) : (
    {visibleRows.map((row) => { const pct = (row.count / maxCount) * 100; const name = getCountryName(row.country) || row.country; return (
  1. {row.country} {name}
    {/* Mini bar — same `BerthHeatWidget` idiom: a thin background track with a coloured fill. The count sits on the right so the eye can read both the bar shape and the precise number. */}
    {row.count}
  2. ); })} {hiddenCount > 0 ? (
  3. + {hiddenCount} more {hiddenCount === 1 ? 'country' : 'countries'} not shown.
  4. ) : null}
)}
); }