// components/Dashboard.tsx // Server component that renders the dashboard surface for the // registry overview. All data is passed in via props from the page // (which is responsible for the PocketBase calls). // // Sections, top to bottom: // 1. KPI strip — five tiles across the top // 2. Status distribution — single progress-bar showing how the // company portfolio splits across the four status buckets // 3. Onboarding activity — last 5 submissions, with a CTA into // the inbox // 4. Recently updated — last 5 companies, last 5 directors // 5. Quick links — replaces the old prose "What you can do here" // with actionable links to the most useful filters // // All numbers are pre-formatted in the parent so this component // stays free of formatting concerns. import Link from "next/link"; import StatCard from "@/components/StatCard"; import StatusBadge from "@/components/StatusBadge"; import OverviewSearch, { type OverviewSearchItem, } from "@/components/OverviewSearch"; import { formatDate } from "@/lib/format"; import type { YsterbaardCompany, YsterbaardDirector, } from "@/lib/types"; export interface DashboardProps { kpis: { directors: { total: number; activeRoles: number; clients: number }; companies: { total: number; active: number; inProcess: number; inactive: number; deregistered: number; clients: number; }; }; statusDistribution: ReadonlyArray<{ key: "active" | "in_process" | "inactive" | "deregistered"; label: string; count: number; }>; recentCompanies: YsterbaardCompany[]; recentDirectors: YsterbaardDirector[]; searchIndex: OverviewSearchItem[]; } export default function Dashboard({ kpis, statusDistribution, recentCompanies, recentDirectors, searchIndex, }: DashboardProps) { const totalStatuses = statusDistribution.reduce( (sum, b) => sum + b.count, 0 ); return ( <> {/* ───── 0. Global tax / CK number search ─────────────────────── */} {/* ───── 1. KPI strip ─────────────────────────────────────────── */}
{/* ───── 2. Status distribution ───────────────────────────────── */}

Company status distribution

See all →
{/* Stacked bar */}
{statusDistribution.map((b) => { const width = totalStatuses === 0 ? 0 : (b.count / totalStatuses) * 100; if (width === 0) return null; return (
); })}
{/* Legend rows */}
    {statusDistribution.map((b) => (
  • {b.count.toLocaleString()}
  • ))}
{/* ───── 3. Recently updated — companies + directors ──────────── */}
{/* Companies */}

Recent companies

All →
{recentCompanies.length === 0 ? (
No companies in registry yet.
) : (
    {recentCompanies.map((c) => (
  • {c.enterprise_name || "—"}

    {formatDate(c.last_checked)}

    {c.enterprise_no}

  • ))}
)}
{/* Directors */}

Recent directors

All →
{recentDirectors.length === 0 ? (
No directors in registry yet.
) : (
    {recentDirectors.map((d) => (
  • {d.first_names} {d.surname}

    {d.id_no}

    {formatDate(d.last_checked)}
  • ))}
)}
{/* ───── 5. Quick links ───────────────────────────────────────── */}

Quick filters

); } /* ─────────────────────────────────────────────────────────────────────── */ function QuickLink({ href, label, hint, cta = false, }: { href: string; label: string; hint: string; cta?: boolean; }) { return ( {label} {hint} ); } function pct(part: number, whole: number): string { if (whole === 0) return "0"; return ((part / whole) * 100).toFixed(0); } function barColor(key: DashboardProps["statusDistribution"][number]["key"]): string { switch (key) { case "active": return "bg-[color:var(--success)]"; case "in_process": return "bg-[color:var(--warn)]"; case "inactive": return "bg-border"; case "deregistered": return "bg-[color:var(--danger)]"; } } function dotColor(key: DashboardProps["statusDistribution"][number]["key"]): string { switch (key) { case "active": return "bg-[color:var(--success)]"; case "in_process": return "bg-[color:var(--warn)]"; case "inactive": return "bg-border"; case "deregistered": return "bg-[color:var(--danger)]"; } }