Admin Panel
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-09-29 14:57:37 +07:00
parent 04fbddba13
commit 8c6dad2a31
94 changed files with 4864 additions and 1151 deletions
+58 -396
View File
@@ -1,386 +1,11 @@
import type { Project, ProjectProgram, ProjectSector } from '@/types';
export type { Project, ProjectProgram, ProjectSector } from '@/types';
/**
* PAJ's IT project track record. Project and client names are official
* Indonesian names, so they are shown as-is in both languages.
* Portfolio helpers. The projects themselves are managed in the admin panel
* and arrive as a page prop, already sorted newest first.
*/
export type ProjectSector = 'government' | 'private' | 'soe';
/** Long-running programs highlighted as featured case studies. */
export type ProjectProgram = 'price-monitoring' | 'investment' | 'erp';
export type Project = {
title: string;
client: string;
year: number;
sector: ProjectSector;
program?: ProjectProgram;
/** Optional detail of what the project covered. */
scope?: string;
};
const government: Omit<Project, 'sector'>[] = [
{
title: 'Penyusunan dan Pembuatan SOP Direktorat Data dan Informasi',
client: 'Kementerian Investasi / Badan Koordinasi Penanaman Modal (BKPM)',
year: 2025,
program: 'investment',
},
{
title: 'Perencanaan Peta Jalan Direktorat Data dan Informasi TA 2024',
client: 'Kementerian Investasi / Badan Koordinasi Penanaman Modal (BKPM)',
year: 2024,
program: 'investment',
},
{
title: 'Modul Pelayanan Metrologi Legal Submodul Verifikasi (Sistem Informasi Ketertelusuran Standar (SIKS))',
client: 'Kementerian Perdagangan',
year: 2024,
},
{
title: 'Penyediaan Data Harga dan Pasokan Cabe dan Bawang di Pasar Induk Wilayah Barat',
client: 'Kementerian Perdagangan',
year: 2024,
program: 'price-monitoring',
},
{
title: 'Pemeliharaan Sistem Pelaporan Harga (SP2KP), Pusat Data dan Sistem Informasi',
client: 'Kementerian Perdagangan',
year: 2023,
program: 'price-monitoring',
},
{
title: 'Penyediaan Data Harga dan Pasokan Cabe dan Bawang di Pasar Induk Wilayah Barat',
client: 'Kementerian Perdagangan',
year: 2023,
program: 'price-monitoring',
},
{
title: 'Pembangunan Aplikasi Sertifikasi Prasarana Perkeretaapian Dengan Memanfaatkan Teknologi Informasi',
client: 'Kementerian Perhubungan',
year: 2022,
},
{
title: 'Pengembangan Aplikasi LDH',
client: 'Badan Informasi Geospasial (BIG)',
year: 2022,
program: 'erp',
scope: 'OpenERP Community: manajemen dokumen dan custom development',
},
{
title: 'Perawatan Sistem Pelaporan Harga',
client: 'Kementerian Perdagangan',
year: 2022,
program: 'price-monitoring',
},
{
title: 'Pengembangan Aplikasi Sistem Informasi Ketertelusuran Standar',
client: 'Kementerian Perdagangan',
year: 2021,
},
{
title: 'Pengembangan Modul Master Data Aplikasi SIREBEKA (Sistem Informasi Remunerasi Berbasis Kinerja)',
client: 'Kementerian Perhubungan',
year: 2021,
},
{
title: 'Pengelolaan Sistem Pelaporan Harga',
client: 'Kementerian Perdagangan',
year: 2020,
program: 'price-monitoring',
},
{
title: 'Pengadaan Metrologi Aplikasi Mobile: Pendataan dan Pemetaan Kondisi UTTP dan BDKT dalam Kerangka Pengawasan Kemetrologian',
client: 'Kementerian Perdagangan',
year: 2020,
},
{
title: 'Penyediaan Data Harga dan Pasokan Cabe dan Bawang di Pasar Induk Wilayah Barat',
client: 'Kementerian Perdagangan',
year: 2020,
program: 'price-monitoring',
},
{
title: 'Penyediaan Data Harga dan Pasokan Cabe dan Bawang di Pasar Induk Wilayah Timur',
client: 'Kementerian Perdagangan',
year: 2020,
program: 'price-monitoring',
},
{
title: 'Perawatan Sistem Pelaporan Harga',
client: 'Kementerian Perdagangan',
year: 2020,
program: 'price-monitoring',
},
{
title: 'Perancangan Antar Muka Sistem Informasi Ketertelusuran Standar',
client: 'Kementerian Perdagangan',
year: 2019,
},
{
title: 'Pengembangan dan Pemeliharaan Portal NSWI TA 2019',
client: 'Badan Koordinasi Penanaman Modal (BKPM)',
year: 2019,
program: 'investment',
},
{
title: 'Pengembangan Aplikasi Website Intranet',
client: 'Kementerian Perdagangan',
year: 2018,
},
{
title: 'Pemantauan Harga Bapok 34 Ibukota Provinsi pada Sabtu atau Minggu, Libur Nasional dan HBKN, Pasar Kab/Kota, Daerah TTT dan Pasar Ritel Modern Tahun 2018',
client: 'Kementerian Perdagangan',
year: 2018,
program: 'price-monitoring',
},
{
title: 'Pengembangan Business Intelligence (BI) TA 2018',
client: 'Badan Koordinasi Penanaman Modal (BKPM)',
year: 2018,
program: 'investment',
},
{
title: 'Pengembangan Dashboard Produk Wisata MICE',
client: 'Kementerian Pariwisata',
year: 2017,
},
{
title: 'Pembuatan Sistem Informasi Manajemen Harga dan Pasokan',
client: 'Kementerian Perdagangan',
year: 2017,
program: 'price-monitoring',
},
{
title: 'Penerapan Model Pembangunan dan Revitalisasi Pasar Rakyat Dengan Pendekatan Teknologi Informasi Berbasis Mobile',
client: 'Kementerian Perdagangan',
year: 2017,
},
{
title: 'Penyusunan Pemutakhiran (Updating) Cetak Biru Pengembangan Sistem Pelayanan Informasi dan Perizinan',
client: 'Badan Koordinasi Penanaman Modal (BKPM)',
year: 2017,
program: 'investment',
},
{
title: 'Pemantauan Stok dan Pasokan Barang Kebutuhan Pokok 100 Titik di Pasar Rakyat (Tradisional), Pasar Induk dan Sentra Produksi',
client: 'Kementerian Perdagangan',
year: 2017,
program: 'price-monitoring',
},
{
title: 'Verifikasi Pelaporan Harga Barang Pokok pada SP2KP',
client: 'Kementerian Perdagangan',
year: 2016,
program: 'price-monitoring',
},
{
title: 'Pengembangan Program Aplikasi Harga Kabupaten Kota',
client: 'Kementerian Perdagangan',
year: 2016,
program: 'price-monitoring',
},
{
title: 'Updating Program Penilaian Pasar',
client: 'Kementerian Perdagangan',
year: 2016,
},
{
title: 'Pengembangan Sistem Aplikasi Jaringan Informasi Dokumentasi Hukum',
client: 'Badan Informasi Geospasial (BIG)',
year: 2016,
program: 'erp',
scope: 'OpenERP Community: manajemen dokumen dan custom development',
},
{
title: 'Verifikasi Pelaporan Harga Barang Pokok pada Sistem Pemantauan Pasar Kebutuhan Pokok (SP2KP)',
client: 'Kementerian Perdagangan',
year: 2016,
program: 'price-monitoring',
},
{
title: 'Pekerjaan Pengembangan Program Aplikasi Harga Kabupaten Kota',
client: 'Kementerian Perdagangan',
year: 2016,
program: 'price-monitoring',
},
{
title: 'Pembuatan Software pada Kegiatan Penguatan Regional Climate Change Center',
client: 'Badan Meteorologi Klimatologi dan Geofisika (BMKG)',
year: 2015,
},
{
title: 'Kajian Tata Kelola Teknologi Informasi BKPM TA 2014',
client: 'Badan Koordinasi Penanaman Modal (BKPM)',
year: 2014,
program: 'investment',
},
{
title: 'Sistem Informasi Monitoring Updating Harga dan Pasokan Bawang Merah',
client: 'Kementerian Perdagangan',
year: 2014,
program: 'price-monitoring',
},
{
title: 'Pemetaan Indikasi dan Variabel Penentu Kenaikan Harga Bahan Pokok (Komoditas Cabe Merah, Bawang Merah dan Beras)',
client: 'Kementerian Perdagangan',
year: 2014,
program: 'price-monitoring',
},
{
title: 'Pembangunan Sistem Informasi Pelayanan Dokumen Hukum',
client: 'Badan Informasi Geospasial (BIG)',
year: 2014,
program: 'erp',
scope: 'OpenERP Community: manajemen dokumen dan custom development',
},
{
title: 'Pendampingan Helpdesk Implementasi Sistem Pelayanan Informasi dan Perizinan Investasi Secara Elektronik (SPIPISE)',
client: 'Badan Koordinasi Penanaman Modal (BKPM)',
year: 2013,
program: 'investment',
},
{
title: 'Pengadaan Jasa Konsultasi Pekerjaan Pembangunan Sub Sistem Bidang Ketahanan Lembaga Usaha Ekonomi',
client: 'Kementerian Perdagangan',
year: 2012,
},
{
title: 'Pendampingan Helpdesk Implementasi Sistem Pelayanan Informasi dan Perizinan Investasi Secara Elektronik (SPIPISE)',
client: 'Badan Koordinasi Penanaman Modal (BKPM)',
year: 2012,
program: 'investment',
},
{
title: 'Pembangunan Aplikasi Jaringan Dokumentasi dan Informasi Hukum',
client: 'Badan Informasi Geospasial (BIG)',
year: 2012,
program: 'erp',
scope: 'OpenERP Community: manajemen dokumen dan custom development',
},
{
title: 'Pengembangan Portal National Single Window for Investment (NSWI) untuk Mendukung SPIPISE BKPM',
client: 'Badan Koordinasi Penanaman Modal (BKPM)',
year: 2010,
program: 'investment',
},
{
title: 'Pembuatan Database Eksportir Importir, Perusahaan, Asosiasi dan Pelaku Industri Kreatif serta Lembaga Pendidikan Formal/Non Formal',
client: 'Departemen Perdagangan Republik Indonesia',
year: 2009,
},
{
title: 'Pembangunan Sistem Informasi Tracking Promosi Investasi Tahun Anggaran 2006',
client: 'Badan Koordinasi Penanaman Modal (BKPM)',
year: 2006,
program: 'investment',
},
];
const privateSector: Omit<Project, 'sector'>[] = [
{
title: 'Implementasi Odoo ERP',
client: 'Marawah Beach Club',
year: 2022,
program: 'erp',
scope: 'Odoo Enterprise: POS, event, pembelian, penjualan, website, dan custom development',
},
{
title: 'Enhancement Manajemen Sistem',
client: 'PT Cipta Kridatama',
year: 2022,
},
{
title: 'Implementasi OpenERP Manufacturing System',
client: 'CV Mujur Jaya Abadi',
year: 2015,
program: 'erp',
scope: 'OpenERP Community: akunting, manufaktur, pembelian, penjualan, inventory, dan custom development',
},
{
title: 'Pengembangan Reswara Integrated Management System',
client: 'PT Reswara Minergi Hartama',
year: 2013,
},
{
title: 'Kegiatan Pengembangan dan Operasionalisasi Sistem Pemantauan Pasar Kebutuhan Pokok (SP2KP)',
client: 'PT LAPI ITB (Bapokstra)',
year: 2013,
program: 'price-monitoring',
},
{
title: 'Pengembangan Aplikasi GIS Pelaporan Online Harga',
client: 'PT DKN',
year: 2011,
program: 'price-monitoring',
},
{
title: 'Pengembangan Portal Ekonomi Kreatif Indonesia (www.indonesiakreatif.net)',
client: 'CV Multi Sarana Putra',
year: 2010,
},
{
title: 'Pengembangan Cetak Biru Teknologi Informasi Pusat Pengendalian Online Harga dan Stok',
client: 'PT Pranala Nitisala',
year: 2010,
program: 'price-monitoring',
},
{
title: 'Pembuatan Sistem Informasi Monitoring Penerimaan dan Pengeluaran BBM di Setiap Fasilitas Rantai Pasok BBM',
client: 'KSO LAPI – Pranala',
year: 2008,
},
{
title: 'Validasi dan Koreksi Data Akunting',
client: 'PT Lautan Berlian Utama Motor',
year: 2008,
},
{
title: 'Pengembangan, Pemutakhiran dan Pemeliharaan Website Tahun Anggaran 2007',
client: 'CV Multi Sarana Putra',
year: 2007,
},
{
title: 'Pembuatan Compact Disc (CD) Profil Wilayah, Sektor dan Komoditi',
client: 'PT Qualita Prima Pariwara',
year: 2007,
},
{
title: 'Pengembangan Aplikasi Titis Sampurna Accounting Information System',
client: 'PT Titis Sampurna',
year: 2006,
program: 'erp',
},
{
title: 'Jasa Konsultasi Pengembangan Sistem Informasi Eksekutif Keuangan',
client: 'CV Multi Sarana Putra',
year: 2006,
},
{
title: 'Desain Sistem Informasi Museum Bank Indonesia',
client: 'PT Global Media Inline',
year: 2006,
},
];
const soe: Omit<Project, 'sector'>[] = [
{
title: 'Pembangunan Sistem Aplikasi',
client: 'PT Sucofindo (Persero)',
year: 2015,
},
{
title: 'Jasa Pemeliharaan Sistem Merpati Operation System Infrastructure (MOSI), Mobile Flight Log (MFL), Schedule Messaging System (SMS)',
client: 'PT Merpati Nusantara Airlines',
year: 2006,
},
];
const newestFirst = (a: Project, b: Project) => b.year - a.year;
export const projects: Project[] = [
...government.map((p) => ({ ...p, sector: 'government' as const })),
...privateSector.map((p) => ({ ...p, sector: 'private' as const })),
...soe.map((p) => ({ ...p, sector: 'soe' as const })),
].sort(newestFirst);
/** Different names used over the years for the same institution. */
const clientAliases: Record<string, string> = {
@@ -392,11 +17,8 @@ const clientAliases: Record<string, string> = {
const clientName = (project: Project) =>
clientAliases[project.client] ?? project.client;
/** Number of distinct clients across all projects. */
export const clientCount = new Set(projects.map(clientName)).size;
/** Distinct clients, most frequent first. */
const rankClients = (items: Project[]) => {
export const rankClients = (items: Project[]) => {
const counts = new Map<string, number>();
items.forEach((project) => {
@@ -409,24 +31,64 @@ const rankClients = (items: Project[]) => {
.map(([name]) => name);
};
export const clientsInSector = (sector: ProjectSector) =>
rankClients(projects.filter((project) => project.sector === sector));
export const bySector = (items: Project[], sector: ProjectSector) =>
items.filter((project) => project.sector === sector);
/** Clients with the most projects across every sector. */
export const topClients = (limit: number) =>
rankClients(projects).slice(0, limit);
export const projectsBySector = (sector: ProjectSector) =>
projects.filter((project) => project.sector === sector);
export const projectsInProgram = (program: ProjectProgram) =>
projects.filter((project) => project.program === program);
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;
}