Workflow-App/Workflow/app/page.tsx

151 lines
4.7 KiB
TypeScript
Raw Normal View History

// app/page.tsx
// Dashboard / landing. Server-rendered summary tiles + sections
// driven by direct PocketBase calls via lib/pb.ts.
//
// The page itself stays thin — it loads the data and passes it to
// components/Dashboard.tsx, which owns the visual layout. This
// keeps PB error handling in one place and lets the dashboard
// component stay testable in isolation.
//
// All PB queries run in parallel (Promise.all) to keep first paint
// under a single round-trip.
import { fetchCollection } from "@/lib/pb";
import type {
YsterbaardCompany,
YsterbaardDirector,
} from "@/lib/types";
import { deriveCompanyStatus } from "@/lib/format";
import Dashboard from "@/components/Dashboard";
import PageHeader from "@/components/PageHeader";
import type { OverviewSearchItem } from "@/components/OverviewSearch";
export const dynamic = "force-dynamic";
async function loadDashboardData() {
// Parallel PB calls. Each one is bounded (perPage=500 or 5).
const [
directorsCount,
companiesCount,
companiesFull,
directorsFull,
recentCompanies,
recentDirectors,
// Overview search index — only the fields the global search bar
// matches against (CK no + every SARS / UIF / COID number).
searchIndex,
] = await Promise.all([
fetchCollection<YsterbaardDirector>("ysterbaard_directors", {
page: 1,
perPage: 1,
fields: "id",
}),
fetchCollection<YsterbaardCompany>("ysterbaard_companies", {
page: 1,
perPage: 1,
fields: "id",
}),
// Full sweep for status rollups + client counts.
fetchCollection<YsterbaardCompany>("ysterbaard_companies", {
page: 1,
perPage: 500,
fields:
"id,enterprise_no,enterprise_name,enterprise_status,is_active,is_client",
}),
// Directors sweep for client count + active-role count.
fetchCollection<YsterbaardDirector>("ysterbaard_directors", {
page: 1,
perPage: 500,
fields: "id,total_companies,is_client",
}),
fetchCollection<YsterbaardCompany>("ysterbaard_companies", {
page: 1,
perPage: 5,
sort: "-last_checked",
fields:
"id,enterprise_no,enterprise_name,enterprise_status,is_active,is_client,last_checked",
}),
fetchCollection<YsterbaardDirector>("ysterbaard_directors", {
page: 1,
perPage: 5,
sort: "-last_checked",
fields:
"id,id_no,first_names,surname,total_companies,is_client,last_checked",
}),
fetchCollection<OverviewSearchItem>("ysterbaard_companies", {
page: 1,
perPage: 500,
fields:
"id,enterprise_no,enterprise_name,sars_tax_number,sars_paye_no,sars_vat_no,uif_registration_number,compensation_fund_registration_number",
}),
]);
// Company rollups
const rollup = companiesFull.items.reduce(
(acc, c) => {
const bucket = deriveCompanyStatus(c.enterprise_status, c.is_active);
if (bucket === "active") acc.active += 1;
else if (bucket === "inactive") acc.inactive += 1;
else if (bucket === "in_process") acc.in_process += 1;
else if (bucket === "deregistered") acc.deregistered += 1;
if (c.is_client) acc.clients += 1;
return acc;
},
{ active: 0, inactive: 0, in_process: 0, deregistered: 0, clients: 0 }
);
// Director rollups
const directorRollup = directorsFull.items.reduce(
(acc, d) => {
if ((d.total_companies ?? 0) > 0) acc.activeRoles += 1;
if (d.is_client) acc.clients += 1;
return acc;
},
{ activeRoles: 0, clients: 0 }
);
return {
kpis: {
directors: {
total: directorsCount.totalItems,
activeRoles: directorRollup.activeRoles,
clients: directorRollup.clients,
},
companies: {
total: companiesCount.totalItems,
active: rollup.active,
inProcess: rollup.in_process,
inactive: rollup.inactive,
deregistered: rollup.deregistered,
clients: rollup.clients,
},
},
statusDistribution: [
{ key: "active", label: "In business", count: rollup.active },
{ key: "in_process", label: "In process", count: rollup.in_process },
{ key: "inactive", label: "Inactive", count: rollup.inactive },
{ key: "deregistered", label: "Deregistered", count: rollup.deregistered },
] as const,
recentCompanies: recentCompanies.items,
recentDirectors: recentDirectors.items,
searchIndex: searchIndex.items,
};
}
export default async function DashboardPage() {
const data = await loadDashboardData();
return (
<>
<PageHeader
eyebrow="Overview"
title="Registry dashboard"
subtitle="Live counts from PocketBase. Click into a section for the full searchable list."
action={null}
/>
<Dashboard {...data} />
</>
);
}