// 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("ysterbaard_directors", { page: 1, perPage: 1, fields: "id", }), fetchCollection("ysterbaard_companies", { page: 1, perPage: 1, fields: "id", }), // Full sweep for status rollups + client counts. fetchCollection("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("ysterbaard_directors", { page: 1, perPage: 500, fields: "id,total_companies,is_client", }), fetchCollection("ysterbaard_companies", { page: 1, perPage: 5, sort: "-last_checked", fields: "id,enterprise_no,enterprise_name,enterprise_status,is_active,is_client,last_checked", }), fetchCollection("ysterbaard_directors", { page: 1, perPage: 5, sort: "-last_checked", fields: "id,id_no,first_names,surname,total_companies,is_client,last_checked", }), fetchCollection("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 ( <> ); }