Files
web-probindo/resources/js/data/projects.ts
T
afriadirahman 8c6dad2a31
tests / ci (push) Canceled after 0s
Admin Panel
2026-09-29 14:57:37 +07:00

95 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Project, ProjectProgram, ProjectSector } from '@/types';
export type { Project, ProjectProgram, ProjectSector } from '@/types';
/**
* Portfolio helpers. The projects themselves are managed in the admin panel
* and arrive as a page prop, already sorted newest first.
*/
/** Different names used over the years for the same institution. */
const clientAliases: Record<string, string> = {
'Kementerian Investasi / Badan Koordinasi Penanaman Modal (BKPM)':
'Badan Koordinasi Penanaman Modal (BKPM)',
'Departemen Perdagangan Republik Indonesia': 'Kementerian Perdagangan',
};
const clientName = (project: Project) =>
clientAliases[project.client] ?? project.client;
/** Distinct clients, most frequent first. */
export const rankClients = (items: Project[]) => {
const counts = new Map<string, number>();
items.forEach((project) => {
const name = clientName(project);
counts.set(name, (counts.get(name) ?? 0) + 1);
});
return [...counts.entries()]
.sort((a, b) => b[1] - a[1])
.map(([name]) => name);
};
export const bySector = (items: Project[], sector: ProjectSector) =>
items.filter((project) => project.sector === sector);
export const inProgram = (items: Project[], program: ProjectProgram) =>
items.filter((project) => project.program === program);
/** e.g. "2006 – 2025", or a single year when all projects share it. */
export const yearRange = (items: Project[]) => {
if (items.length === 0) {
return '–';
}
const years = items.map((project) => project.year);
const first = Math.min(...years);
const last = Math.max(...years);
return first === last ? `${first}` : `${first} – ${last}`;
};
/** All portfolio numbers and lists derived from one set of projects. */
export const portfolio = (projects: Project[]) => ({
projects,
projectCount: projects.length,
clientCount: rankClients(projects).length,
projectsBySector: (sector: ProjectSector) => bySector(projects, sector),
projectsInProgram: (program: ProjectProgram) =>
inProgram(projects, program),
clientsInSector: (sector: ProjectSector) =>
rankClients(bySector(projects, sector)),
topClients: (limit: number) => rankClients(projects).slice(0, limit),
});
export type Portfolio = ReturnType<typeof portfolio>;
/** Fills `{projects}` and `{clients}` placeholders in content strings. */
export const withCounts = (text: string, stats: Portfolio) =>
text
.replaceAll('{projects}', String(stats.projectCount))
.replaceAll('{clients}', String(stats.clientCount));
/** Applies `withCounts` to every string in a content object. */
export function fillCounts<T>(value: T, stats: Portfolio): T {
if (typeof value === 'string') {
return withCounts(value, stats) as T;
}
if (Array.isArray(value)) {
return value.map((item: unknown) => fillCounts(item, stats)) as T;
}
if (value && typeof value === 'object') {
return Object.fromEntries(
Object.entries(value).map(([key, item]) => [
key,
fillCounts(item, stats),
]),
) as T;
}
return value;
}