(() => {
const API_BASE_URL = (window.DASHBOARD_API_BASE_URL || "http://127.0.0.1:3000/api").replace(/\/$/, "");
if (!window.L) {
console.error("Leaflet failed to load.");
return;
}
const state = {
mapFilter: "central",
tab: "all",
selectedAreaKey: null,
selectedOwnerKey: null,
search: "",
sortBy: "waste",
modalRequestId: 0,
modal: {
areaType: "region",
areaKey: null,
ownerName: "",
page: 1,
pageSize: 25,
search: "",
ownerType: "",
severity: "",
priorityOnly: false,
},
};
const dom = {
kpi: document.getElementById("kpi"),
mapRoot: document.getElementById("map"),
mapFilters: document.getElementById("mf"),
tabs: document.getElementById("tabs"),
legend: document.getElementById("legend"),
sidebarContent: document.getElementById("sbc"),
modal: document.getElementById("rupModal"),
modalTop: document.getElementById("modalTop"),
modalBody: document.getElementById("modalBody"),
};
if (Object.values(dom).some((element) => !element)) {
console.error("Dashboard shell is incomplete.");
return;
}
const FILTERS = [
{ key: "central", label: "Kementerian/Lembaga" },
{ key: "provinsi", label: "Pemprov" },
{ key: "kabkota", label: "Pemkot" },
{ key: "other", label: "Others" },
];
const TABS = [
{ key: "all", label: "Semua" },
{ key: "kabupaten", label: "Kabupaten" },
{ key: "kota", label: "Kota" },
];
const SEVERITY_FILTERS = [
{ key: "", label: "Semua Severity" },
{ key: "low", label: "Low" },
{ key: "med", label: "Medium" },
{ key: "high", label: "High" },
{ key: "absurd", label: "Absurd" },
];
let dashboardData = null;
let regionsByKey = new Map();
let provincesByKey = new Map();
let map = null;
let geoLayer = null;
function escapeHtml(value) {
return String(value)
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function escapeAttr(value) {
return String(value)
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
}
function escapeJsString(value) {
return String(value)
.replace(/\\/g, "\\\\")
.replace(/'/g, "\\'")
.replace(/\r/g, "\\r")
.replace(/\n/g, "\\n");
}
function jsArg(value) {
if (typeof value === "boolean") {
return value ? "true" : "false";
}
if (typeof value === "number") {
return String(value);
}
return `'${escapeJsString(value)}'`;
}
function actionCall(action, ...args) {
return escapeAttr(`dashboardActions.${action}(${args.map(jsArg).join(",")})`);
}
function actionExpr(expression) {
return escapeAttr(expression);
}
function normalizeSourceId(sourceId) {
if (sourceId === null || sourceId === undefined) {
return null;
}
const normalized = String(sourceId).trim();
if (!/^\d+$/.test(normalized)) {
return null;
}
const parsed = Number(normalized);
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
return null;
}
return String(parsed);
}
function buildInaprocUrl(sourceId) {
const kode = normalizeSourceId(sourceId);
return kode ? `https://data.inaproc.id/rup?kode=${encodeURIComponent(kode)}` : null;
}
function isProvinceView() {
return state.mapFilter === "provinsi";
}
function isCentralOwnerMode() {
return state.mapFilter === "central";
}
function currentAreaType() {
return isProvinceView() ? "province" : "region";
}
function formatCompactCurrency(value) {
const amount = Number(value) || 0;
const abs = Math.abs(amount);
if (abs >= 1e12) return `${(amount / 1e12).toFixed(amount % 1e12 === 0 ? 0 : 1)} T`;
if (abs >= 1e9) return `${(amount / 1e9).toFixed(amount % 1e9 === 0 ? 0 : 1)} B`;
if (abs >= 1e6) return `${(amount / 1e6).toFixed(amount % 1e6 === 0 ? 0 : 1)} M`;
if (abs >= 1e3) return `${(amount / 1e3).toFixed(amount % 1e3 === 0 ? 0 : 1)} K`;
return `${amount.toFixed(0)}`;
}
function formatCurrencyLong(value) {
const number = Math.round(Number(value) || 0);
return `Rp ${number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".")}`;
}
function formatNumber(value) {
const number = Math.round(Number(value) || 0);
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}
function formatDecimal(value) {
const amount = Number(value) || 0;
return amount % 1 === 0 ? formatNumber(amount) : amount.toFixed(2).replace(".", ",");
}
function ownerTypeLabel(value) {
if (value === "central") return "Kementerian/Lembaga";
if (value === "provinsi") return "Pemprov";
if (value === "kabkota") return "Pemkot";
if (value === "other") return "Others";
return "Tidak diketahui";
}
function ownerTypeCount(area, ownerType) {
return Number(area && area.ownerMix ? area.ownerMix[ownerType] : 0) || 0;
}
function ownerMixSummary(area) {
return `K/L ${formatNumber(ownerTypeCount(area, "central"))} | Pemprov ${formatNumber(
ownerTypeCount(area, "provinsi")
)} | Pemkot ${formatNumber(ownerTypeCount(area, "kabkota"))} | Others ${formatNumber(
ownerTypeCount(area, "other")
)}`;
}
function areaOwnerSummary(area) {
return `${activeSidebarOwnerLabel()} saja`;
}
function areaBadgeLabel(area) {
if (area.regionType === "Provinsi") return "Prov.";
if (area.regionType === "Kota") return "Kota";
return "Kab.";
}
function areaBadgeClass(area) {
return area.regionType === "Kota" ? "bk" : "bp";
}
function areaSecondaryLine(area) {
return isProvinceView() ? "Hanya paket Pemprov" : area.provinceName;
}
function severityColor(severity) {
if (severity === "absurd") return "var(--rose)";
if (severity === "high") return "var(--brick)";
if (severity === "med") return "var(--olive)";
return "var(--steel)";
}
function severityLabel(severity) {
if (severity === "absurd") return "Absurd";
if (severity === "high") return "High";
if (severity === "med") return "Medium";
return "Low";
}
function totalAreaMetrics(area) {
return {
totalPackages: Number(area?.totalPackages) || 0,
totalPriorityPackages: Number(area?.totalPriorityPackages) || 0,
totalPotentialWaste: Number(area?.totalPotentialWaste) || 0,
totalBudget: Number(area?.totalBudget) || 0,
};
}
function getActiveSidebarOwnerKey() {
return isProvinceView() ? "provinsi" : state.mapFilter;
}
function activeSidebarOwnerLabel() {
return ownerTypeLabel(getActiveSidebarOwnerKey());
}
function getAreaMetricsForOwner(area, ownerKey) {
if (!area) {
return totalAreaMetrics(null);
}
const metrics = area.ownerMetrics && area.ownerMetrics[ownerKey];
if (metrics) {
return {
totalPackages: Number(metrics.totalPackages) || 0,
totalPriorityPackages: Number(metrics.totalPriorityPackages) || 0,
totalPotentialWaste: Number(metrics.totalPotentialWaste) || 0,
totalBudget: Number(metrics.totalBudget) || 0,
};
}
if (isProvinceView() && ownerKey === "provinsi") {
return totalAreaMetrics(area);
}
return {
totalPackages: ownerTypeCount(area, ownerKey),
totalPriorityPackages: 0,
totalPotentialWaste: 0,
totalBudget: 0,
};
}
function getSidebarAreaMetrics(area) {
const ownerKey = getActiveSidebarOwnerKey();
return ownerKey ? getAreaMetricsForOwner(area, ownerKey) : totalAreaMetrics(area);
}
function renderSeverityFilterOptions(selectedValue) {
return SEVERITY_FILTERS.map(
(filter) =>
`${escapeHtml(
filter.label
)} `
).join("");
}
function getOwnerCardKey(ownerType, ownerName) {
return `${ownerType}::${ownerName}`;
}
function getAreaKey(area, areaType = currentAreaType()) {
return areaType === "province" ? area.provinceKey : area.regionKey;
}
function getAreaByKey(areaType, areaKey) {
return (areaType === "province" ? provincesByKey : regionsByKey).get(areaKey) || null;
}
function getActiveAreaByKey(areaKey) {
return getAreaByKey(currentAreaType(), areaKey);
}
function getActiveAreas() {
return isProvinceView() ? dashboardData.provinceView.provinces : dashboardData.regions;
}
function getCentralOwnersForSidebar() {
return dashboardData && dashboardData.ownerLists && Array.isArray(dashboardData.ownerLists.central)
? dashboardData.ownerLists.central
: [];
}
function getActiveGeo() {
return isProvinceView() ? dashboardData.provinceView.geo : dashboardData.geo;
}
function getActiveLegend() {
return isProvinceView() ? dashboardData.provinceView.legend : dashboardData.legend;
}
function getFeatureAreaKey(feature) {
return isProvinceView() ? feature.properties.provinceKey : feature.properties.regionKey;
}
function ensureMapStatus() {
let status = document.getElementById("mapStatus");
if (!status) {
status = document.createElement("div");
status.id = "mapStatus";
status.className = "map-status";
dom.mapRoot.parentElement.appendChild(status);
}
return status;
}
function setMapStatus(message, isError) {
const status = ensureMapStatus();
status.className = `map-status${isError ? " error" : ""}`;
status.textContent = message;
}
function clearMapStatus() {
const status = document.getElementById("mapStatus");
if (status) {
status.remove();
}
}
function renderKpiCards(cards) {
dom.kpi.innerHTML = cards
.map(
(item) =>
`
${escapeHtml(item.label)}
${escapeHtml(
item.value
)}
${escapeHtml(item.sublabel)}
`
)
.join("");
}
function renderSidebarMessage(message, isError) {
dom.sidebarContent.innerHTML = `${escapeHtml(message)}
`;
}
function renderModalState(title, message, isError) {
dom.modalTop.innerHTML =
`${escapeHtml(title)} Audit paket pengadaan · TA 2026
` +
`
✕ Tutup
`;
dom.modalBody.innerHTML = `${escapeHtml(message)}
`;
}
function renderBootstrapLoading() {
renderKpiCards([
{ label: "Total Potensi Pemborosan", value: "...", sublabel: "Menghitung agregat audit" },
{ label: "Paket Prioritas Audit", value: "...", sublabel: "Memuat daftar area" },
{ label: "Total Pagu Teraudit", value: "...", sublabel: "Menyiapkan peta kab/kota dan provinsi" },
{ label: "Paket Terpetakan", value: "...", sublabel: "Memeriksa cakupan lokasi" },
]);
renderSidebarMessage("Memuat audit pengadaan per area...", false);
setMapStatus("Memuat peta audit...", false);
}
function renderBootstrapError(error) {
renderKpiCards([
{ label: "Total Potensi Pemborosan", value: "-", sublabel: "Backend belum siap" },
{ label: "Paket Prioritas Audit", value: "-", sublabel: "Periksa ingest hasil analyze" },
{ label: "Total Pagu Teraudit", value: "-", sublabel: "Ulangi db:reset bila perlu" },
{ label: "Paket Terpetakan", value: "-", sublabel: "Map belum dapat dibuat" },
]);
renderSidebarMessage(`Gagal memuat dashboard audit: ${error}`, true);
setMapStatus(`Gagal memuat dashboard audit: ${error}`, true);
}
function formatFetchError(error) {
return error instanceof Error ? error.message : String(error);
}
async function fetchJson(path) {
const response = await fetch(`${API_BASE_URL}${path}`);
const text = await response.text();
let payload = null;
if (text) {
try {
payload = JSON.parse(text);
} catch (_error) {
throw new Error(`Invalid JSON response from ${path}`);
}
}
if (!response.ok) {
throw new Error(payload && payload.error ? payload.error : `Request failed (${response.status})`);
}
return payload;
}
function normalizeDashboardData(payload) {
if (!payload || typeof payload !== "object") {
throw new Error("Bootstrap payload tidak valid.");
}
return {
summary: payload.summary || {
totalPackages: 0,
totalPriorityPackages: 0,
totalPotentialWaste: 0,
totalBudget: 0,
unmappedPackages: 0,
multiLocationPackages: 0,
},
legend: payload.legend || { zeroColor: "#243155", ranges: [] },
geo: payload.geo || { type: "FeatureCollection", features: [] },
regions: Array.isArray(payload.regions) ? payload.regions : [],
provinceView: {
legend: (payload.provinceView && payload.provinceView.legend) || { zeroColor: "#243155", ranges: [] },
geo: (payload.provinceView && payload.provinceView.geo) || { type: "FeatureCollection", features: [] },
provinces:
payload.provinceView && Array.isArray(payload.provinceView.provinces) ? payload.provinceView.provinces : [],
},
ownerLists: {
central: payload.ownerLists && Array.isArray(payload.ownerLists.central) ? payload.ownerLists.central : [],
},
};
}
function getLegendColor(value) {
const legend = getActiveLegend();
if (!legend) {
return "#243155";
}
if (!value || value <= 0) {
return legend.zeroColor || "#243155";
}
const range = (legend.ranges || []).find((item) => value >= item.min && value <= item.max);
return range ? range.color : legend.ranges[legend.ranges.length - 1]?.color || "#a83c2e";
}
function areaMatchesCurrentView(area) {
if (!area) {
return false;
}
if (isProvinceView()) {
return area.totalPackages > 0;
}
if (state.tab === "kabupaten" && area.regionType !== "Kabupaten") {
return false;
}
if (state.tab === "kota" && area.regionType !== "Kota") {
return false;
}
if (FILTERS.some((filter) => filter.key === state.mapFilter)) {
return ownerTypeCount(area, state.mapFilter) > 0;
}
return true;
}
function getFilteredAreasForSidebar() {
let areas = getActiveAreas().filter((area) => areaMatchesCurrentView(area));
if (state.search) {
const query = state.search.toLowerCase();
const activeOwnerQuery = activeSidebarOwnerLabel().toLowerCase();
areas = areas.filter((area) => {
const matchesName = area.displayName.toLowerCase().includes(query) || area.provinceName.toLowerCase().includes(query);
if (isProvinceView()) {
return matchesName;
}
return matchesName || activeOwnerQuery.includes(query);
});
}
const metricsByAreaKey = new Map(areas.map((area) => [getAreaKey(area), getSidebarAreaMetrics(area)]));
const sorters = {
waste: (left, right) =>
metricsByAreaKey.get(getAreaKey(right)).totalPotentialWaste - metricsByAreaKey.get(getAreaKey(left)).totalPotentialWaste,
priority: (left, right) =>
metricsByAreaKey.get(getAreaKey(right)).totalPriorityPackages - metricsByAreaKey.get(getAreaKey(left)).totalPriorityPackages,
packages: (left, right) =>
metricsByAreaKey.get(getAreaKey(right)).totalPackages - metricsByAreaKey.get(getAreaKey(left)).totalPackages,
budget: (left, right) =>
metricsByAreaKey.get(getAreaKey(right)).totalBudget - metricsByAreaKey.get(getAreaKey(left)).totalBudget,
};
return areas.sort((left, right) => {
const primary = (sorters[state.sortBy] || sorters.waste)(left, right);
return primary !== 0 ? primary : left.displayName.localeCompare(right.displayName, "id");
});
}
function getFilteredOwnersForSidebar() {
let owners = getCentralOwnersForSidebar().slice();
if (state.search) {
const query = state.search.toLowerCase();
owners = owners.filter((owner) => owner.ownerName.toLowerCase().includes(query));
}
const sorters = {
waste: (left, right) => right.totalPotentialWaste - left.totalPotentialWaste,
priority: (left, right) => right.totalPriorityPackages - left.totalPriorityPackages,
packages: (left, right) => right.totalPackages - left.totalPackages,
budget: (left, right) => right.totalBudget - left.totalBudget,
};
return owners.sort((left, right) => {
const primary = (sorters[state.sortBy] || sorters.waste)(left, right);
return primary !== 0 ? primary : left.ownerName.localeCompare(right.ownerName, "id");
});
}
function renderKpis() {
const summary = dashboardData.summary;
const mappedPackages = summary.totalPackages - summary.unmappedPackages;
renderKpiCards([
{
label: "Total Potensi Pemborosan",
value: `Rp ${formatCompactCurrency(summary.totalPotentialWaste)}`,
sublabel: "Nilai nasional raw, tanpa duplikasi multi-lokasi",
},
{
label: "Paket Prioritas Audit",
value: formatNumber(summary.totalPriorityPackages),
sublabel: `${formatNumber(summary.totalPackages)} paket teraudit`,
},
{
label: "Total Pagu Teraudit",
value: `Rp ${formatCompactCurrency(summary.totalBudget)}`,
sublabel: "Akumulasi pagu dari seluruh artifact audit",
},
{
label: "Paket Terpetakan",
value: `${formatNumber(mappedPackages)} / ${formatNumber(summary.totalPackages)}`,
sublabel: `${formatNumber(summary.unmappedPackages)} unmapped | ${formatNumber(summary.multiLocationPackages)} multi-lokasi`,
},
]);
}
function renderLegend() {
const legend = getActiveLegend();
const title = isProvinceView()
? "Potensi Pemborosan Paket Pemprov per Provinsi"
: "Potensi Pemborosan per Kab/Kota";
const zeroLabel = isProvinceView() ? "Tidak ada paket pemprov terdeteksi" : "Tidak ada potensi terdeteksi";
const note = isProvinceView()
? "Agregasi provinsi mendeduplikasi paket multi-kab/kota di provinsi yang sama."
: "Map region menghitung penuh paket multi-lokasi, sehingga agregat region bisa lebih besar dari KPI nasional.";
const rows = [
`${escapeHtml(title)}
`,
`
${escapeHtml(
zeroLabel
)}
`,
];
(legend.ranges || []).forEach((range) => {
rows.push(
`
Rp ${escapeHtml(
formatCompactCurrency(range.min)
)} – Rp ${escapeHtml(formatCompactCurrency(range.max))}
`
);
});
rows.push(`${escapeHtml(note)}
`);
dom.legend.innerHTML = rows.join("");
}
function renderFilterChips() {
dom.mapFilters.innerHTML = FILTERS.map(
(filter) =>
`${escapeHtml(
filter.label
)}
`
).join("");
}
function renderTabs() {
const provinceView = isProvinceView();
const centralOwnerMode = isCentralOwnerMode();
dom.tabs.innerHTML = TABS.map((tab) => {
const active = provinceView || centralOwnerMode ? tab.key === "all" : tab.key === state.tab;
const disabled = (provinceView || centralOwnerMode) && tab.key !== "all";
return `${escapeHtml(tab.label)} `;
}).join("");
}
function sortControl() {
const placeholder = isCentralOwnerMode()
? "Cari kementerian/lembaga..."
: isProvinceView()
? "Cari provinsi..."
: "Cari kabupaten/kota...";
return (
`🔍
` +
`Urutkan ` +
`Potensi Pemborosan ` +
`Paket Prioritas ` +
`Total Paket ` +
`Total Pagu ` +
`
`
);
}
function renderOwnerSidebarContent() {
const owners = getFilteredOwnersForSidebar();
if (!owners.length) {
dom.sidebarContent.innerHTML =
sortControl() + `Tidak ada kementerian/lembaga yang cocok dengan filter saat ini.
`;
return;
}
const maxWaste = Math.max(...owners.map((owner) => owner.totalPotentialWaste), 1);
dom.sidebarContent.innerHTML =
sortControl() +
owners
.map((owner, index) => {
const selectedClass =
state.selectedOwnerKey === getOwnerCardKey(owner.ownerType, owner.ownerName) ? " a" : "";
return (
`` +
`
#${index + 1} ${escapeHtml(
owner.ownerName
)}
K/L
` +
`
Kementerian/Lembaga
` +
`
Rp ${escapeHtml(formatCompactCurrency(owner.totalPotentialWaste))} · ${escapeHtml(
formatNumber(owner.totalPriorityPackages)
)} prioritas
` +
`
` +
`
Total Paket: ${escapeHtml(
formatNumber(owner.totalPackages)
)}
Severity High: ${escapeHtml(
formatNumber(owner.severityCounts.high)
)}
` +
`
Severity Absurd ${escapeHtml(formatNumber(owner.severityCounts.absurd))}
` +
`
Pagu Teraudit ${escapeHtml(
`Rp ${formatCompactCurrency(owner.totalBudget)}`
)}
` +
`
`
);
})
.join("");
}
function renderSidebarContent() {
if (!dashboardData) {
renderSidebarMessage("Data dashboard belum tersedia.", true);
return;
}
if (isCentralOwnerMode()) {
renderOwnerSidebarContent();
return;
}
const areas = getFilteredAreasForSidebar();
if (!areas.length) {
dom.sidebarContent.innerHTML =
sortControl() +
`Tidak ada ${escapeHtml(
isProvinceView() ? "provinsi" : "region"
)} yang cocok dengan filter saat ini.
`;
return;
}
const areaEntries = areas.map((area) => ({
area,
metrics: getSidebarAreaMetrics(area),
}));
const maxWaste = Math.max(...areaEntries.map(({ metrics }) => metrics.totalPotentialWaste), 1);
const ownerLabel = activeSidebarOwnerLabel();
dom.sidebarContent.innerHTML =
sortControl() +
areaEntries
.map(({ area, metrics }, index) => {
const areaKey = getAreaKey(area);
const selectedClass = state.selectedAreaKey === areaKey ? " a" : "";
return (
`` +
`
#${index + 1} ${escapeHtml(
area.displayName
)}
${escapeHtml(areaBadgeLabel(area))}
` +
`
${escapeHtml(areaSecondaryLine(area))}
` +
`
Rp ${escapeHtml(formatCompactCurrency(metrics.totalPotentialWaste))} · ${escapeHtml(
formatNumber(metrics.totalPriorityPackages)
)} prioritas
` +
`
` +
`
Total Paket: ${escapeHtml(
formatNumber(metrics.totalPackages)
)}
Pemilik: ${escapeHtml(ownerLabel)}
` +
`
${escapeHtml(areaOwnerSummary(area))}
` +
`
Pagu Teraudit ${escapeHtml(
`Rp ${formatCompactCurrency(metrics.totalBudget)}`
)}
` +
`
`
);
})
.join("");
}
function featureStyle(feature) {
const areaKey = getFeatureAreaKey(feature);
const area = getActiveAreaByKey(areaKey);
const visible = areaMatchesCurrentView(area);
const selected = state.selectedAreaKey === areaKey;
return {
fillColor: area ? getLegendColor(area.totalPotentialWaste) : "#243155",
fillOpacity: selected ? 0.72 : visible ? 0.52 : 0.08,
color: selected ? "#f0d8a8" : "rgba(181,168,130,0.2)",
weight: selected ? 2.1 : 0.8,
opacity: visible ? 0.85 : 0.2,
};
}
function popupHtml(area) {
if (!area) {
return `Belum ada data
`;
}
if (isProvinceView()) {
return (
`${escapeHtml(area.displayName)}
` +
`` +
`Potensi Pemborosan Rp ${escapeHtml(
formatCompactCurrency(area.totalPotentialWaste)
)}
` +
`Paket Prioritas ${escapeHtml(
formatNumber(area.totalPriorityPackages)
)}
` +
`Total Paket ${escapeHtml(
formatNumber(area.totalPackages)
)}
` +
`Total Pagu ${escapeHtml(
formatCompactCurrency(area.totalBudget)
)}
` +
`Severity High ${escapeHtml(
formatNumber(area.severityCounts.high)
)}
` +
``
);
}
return (
`${escapeHtml(area.displayName)}
` +
`` +
`Potensi Pemborosan Rp ${escapeHtml(
formatCompactCurrency(area.totalPotentialWaste)
)}
` +
`Paket Prioritas ${escapeHtml(
formatNumber(area.totalPriorityPackages)
)}
` +
`Total Paket ${escapeHtml(
formatNumber(area.totalPackages)
)}
` +
`Kementerian/Lembaga ${escapeHtml(
formatNumber(ownerTypeCount(area, "central"))
)}
` +
`Pemprov ${escapeHtml(
formatNumber(ownerTypeCount(area, "provinsi"))
)}
` +
`Pemkot ${escapeHtml(
formatNumber(ownerTypeCount(area, "kabkota"))
)}
` +
`Others ${escapeHtml(
formatNumber(ownerTypeCount(area, "other"))
)}
` +
``
);
}
function ensureMap() {
if (map) {
return;
}
map = L.map(dom.mapRoot, {
center: [-2.5, 118],
zoom: 5,
minZoom: 4,
maxZoom: 12,
attributionControl: false,
});
L.tileLayer("https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png", {
subdomains: "abcd",
maxZoom: 19,
}).addTo(map);
L.tileLayer("https://{s}.basemaps.cartocdn.com/dark_only_labels/{z}/{x}/{y}{r}.png", {
subdomains: "abcd",
maxZoom: 19,
opacity: 0.35,
}).addTo(map);
}
function renderGeoLayer(fitToBounds) {
ensureMap();
if (geoLayer) {
geoLayer.remove();
geoLayer = null;
}
const geo = getActiveGeo();
if (!geo || !Array.isArray(geo.features) || !geo.features.length) {
setMapStatus("Tidak ada geometri untuk mode peta saat ini.", true);
return;
}
geoLayer = L.geoJSON(geo, {
style: featureStyle,
onEachFeature: (feature, layer) => {
const areaKey = getFeatureAreaKey(feature);
const area = getActiveAreaByKey(areaKey);
layer.bindPopup(popupHtml(area), { maxWidth: 320 });
layer.on({
mouseover: (event) => {
if (!area) {
return;
}
event.target.setStyle({
weight: 1.8,
color: "#f0d8a8",
fillOpacity: Math.min(featureStyle(feature).fillOpacity + 0.16, 0.85),
});
event.target.bringToFront();
},
mouseout: (event) => {
geoLayer.resetStyle(event.target);
},
click: () => {
openAreaModal(areaKey);
},
});
},
}).addTo(map);
const bounds = geoLayer.getBounds();
if (fitToBounds && bounds.isValid()) {
map.fitBounds(bounds.pad(isProvinceView() ? 0.08 : 0.05));
}
clearMapStatus();
}
function initMap() {
renderGeoLayer(true);
}
function refreshMapStyles() {
if (geoLayer) {
geoLayer.setStyle(featureStyle);
}
}
function renderPackageTableRows(items) {
return items.length
? items
.map((item) => {
const packageUrl = buildInaprocUrl(item.sourceId);
return (
`` +
`${escapeHtml(String(item.sourceId || item.id))} ` +
`${escapeHtml(item.packageName)} ` +
`${escapeHtml(item.ownerName)}
${escapeHtml(
ownerTypeLabel(item.ownerType)
)}
` +
`${escapeHtml(item.satker || "-")}
${escapeHtml(
item.locationRaw || "-"
)}
` +
`${escapeHtml(item.budget === null ? "-" : formatCurrencyLong(item.budget))} ` +
`${escapeHtml(
severityLabel(item.audit.severity)
)} ` +
`${escapeHtml(item.audit.reason || "-")} ` +
` `
);
})
.join("")
: `Tidak ada paket untuk filter saat ini. `;
}
function renderPagination(pagination) {
return (
``
);
}
function renderRegionModalContent(payload) {
const region = payload.region;
const rowsHtml = renderPackageTableRows(payload.items);
dom.modalTop.innerHTML =
`${escapeHtml(region.displayName)} ${escapeHtml(
`${region.provinceName} | Audit paket pengadaan TA 2026`
)}
` +
`
${escapeHtml(
region.regionType
)} ✕ Tutup
` +
`` +
`
Potensi Pemborosan
Rp ${escapeHtml(
formatCompactCurrency(region.totalPotentialWaste)
)}
` +
`
Paket Prioritas
${escapeHtml(
formatNumber(region.totalPriorityPackages)
)}
` +
`
Total Paket
${escapeHtml(
formatNumber(region.totalPackages)
)}
` +
`
Total Pagu
Rp ${escapeHtml(
formatCompactCurrency(region.totalBudget)
)}
`;
dom.modalBody.innerHTML =
`` +
`
Kementerian/Lembaga ${escapeHtml(
formatNumber(ownerTypeCount(region, "central"))
)}
` +
`
Pemprov ${escapeHtml(
formatNumber(ownerTypeCount(region, "provinsi"))
)}
` +
`
Pemkot ${escapeHtml(
formatNumber(ownerTypeCount(region, "kabkota"))
)}
` +
`
Others ${escapeHtml(
formatNumber(ownerTypeCount(region, "other"))
)}
` +
`
Severity High ${escapeHtml(formatNumber(region.severityCounts.high))}
` +
`
Severity Absurd ${escapeHtml(
formatNumber(region.severityCounts.absurd)
)}
` +
`
` +
`` +
` ` +
`` +
`Semua Pemilik Kementerian/Lembaga ` +
`Pemprov Pemkot Others ` +
`${renderSeverityFilterOptions(
state.modal.severity
)} ` +
` Hanya prioritas ` +
`
` +
`Menampilkan ${escapeHtml(formatNumber(payload.items.length))} dari ${escapeHtml(
formatNumber(payload.pagination.totalItems)
)} paket pada area ini
` +
`ID Nama Paket Pemilik Satker / Lokasi Pagu Severity Alasan ${rowsHtml}
` +
renderPagination(payload.pagination);
}
function renderProvinceModalContent(payload) {
const province = payload.province;
const rowsHtml = renderPackageTableRows(payload.items);
dom.modalTop.innerHTML =
`${escapeHtml(province.displayName)} Paket pemprov pada provinsi ini · TA 2026
` +
`
Provinsi ✕ Tutup
` +
`` +
`
Potensi Pemborosan
Rp ${escapeHtml(
formatCompactCurrency(province.totalPotentialWaste)
)}
` +
`
Paket Prioritas
${escapeHtml(
formatNumber(province.totalPriorityPackages)
)}
` +
`
Total Paket Pemprov
${escapeHtml(
formatNumber(province.totalPackages)
)}
` +
`
Total Pagu
Rp ${escapeHtml(
formatCompactCurrency(province.totalBudget)
)}
`;
dom.modalBody.innerHTML =
`` +
`
Paket Flagged ${escapeHtml(
formatNumber(province.totalFlaggedPackages)
)}
` +
`
Severity Medium ${escapeHtml(
formatNumber(province.severityCounts.med)
)}
` +
`
Severity High ${escapeHtml(
formatNumber(province.severityCounts.high)
)}
` +
`
Severity Absurd ${escapeHtml(
formatNumber(province.severityCounts.absurd)
)}
` +
`
Avg Risk Score ${escapeHtml(
formatDecimal(province.avgRiskScore)
)}
` +
`
Max Risk Score ${escapeHtml(
formatNumber(province.maxRiskScore)
)}
` +
`
` +
`` +
` ` +
`${renderSeverityFilterOptions(
state.modal.severity
)} ` +
` Hanya prioritas ` +
`
` +
`Menampilkan ${escapeHtml(formatNumber(payload.items.length))} dari ${escapeHtml(
formatNumber(payload.pagination.totalItems)
)} paket pemprov pada provinsi ini
` +
`ID Nama Paket Pemilik Satker / Lokasi Pagu Severity Alasan ${rowsHtml}
` +
renderPagination(payload.pagination);
}
function renderOwnerModalContent(payload) {
const owner = payload.owner;
const rowsHtml = renderPackageTableRows(payload.items);
dom.modalTop.innerHTML =
`${escapeHtml(owner.ownerName)} ${escapeHtml(
`${ownerTypeLabel(owner.ownerType)} | Audit paket nasional TA 2026`
)}
` +
`
K/L ✕ Tutup
` +
`` +
`
Potensi Pemborosan
Rp ${escapeHtml(
formatCompactCurrency(owner.totalPotentialWaste)
)}
` +
`
Paket Prioritas
${escapeHtml(
formatNumber(owner.totalPriorityPackages)
)}
` +
`
Total Paket
${escapeHtml(
formatNumber(owner.totalPackages)
)}
` +
`
Total Pagu
Rp ${escapeHtml(
formatCompactCurrency(owner.totalBudget)
)}
`;
dom.modalBody.innerHTML =
`` +
`
Paket Flagged ${escapeHtml(
formatNumber(owner.totalFlaggedPackages)
)}
` +
`
Severity Medium ${escapeHtml(
formatNumber(owner.severityCounts.med)
)}
` +
`
Severity High ${escapeHtml(
formatNumber(owner.severityCounts.high)
)}
` +
`
Severity Absurd ${escapeHtml(
formatNumber(owner.severityCounts.absurd)
)}
` +
`
` +
`` +
` ` +
`${renderSeverityFilterOptions(
state.modal.severity
)} ` +
` Hanya prioritas ` +
`
` +
`Menampilkan ${escapeHtml(formatNumber(payload.items.length))} dari ${escapeHtml(
formatNumber(payload.pagination.totalItems)
)} paket pada pemilik ini
` +
`ID Nama Paket Pemilik Satker / Lokasi Pagu Severity Alasan ${rowsHtml}
` +
renderPagination(payload.pagination);
}
function renderModalContent(payload) {
if (state.modal.areaType === "owner") {
renderOwnerModalContent(payload);
return;
}
if (state.modal.areaType === "province") {
renderProvinceModalContent(payload);
return;
}
renderRegionModalContent(payload);
}
async function loadAreaPackages() {
if (
(state.modal.areaType === "owner" && (!state.modal.ownerType || !state.modal.ownerName)) ||
(state.modal.areaType !== "owner" && !state.modal.areaKey)
) {
return;
}
state.modalRequestId += 1;
const requestId = state.modalRequestId;
renderModalState(
state.modal.areaType === "owner" ? "Memuat pemilik..." : "Memuat area...",
state.modal.areaType === "owner"
? "Mengambil paket dari pemilik terpilih..."
: "Mengambil paket dari backend audit...",
false
);
const params = new URLSearchParams({
page: String(state.modal.page),
pageSize: String(state.modal.pageSize),
});
if (state.modal.search) {
params.set("search", state.modal.search);
}
if (state.modal.areaType === "region" && state.modal.ownerType) {
params.set("ownerType", state.modal.ownerType);
}
if (state.modal.severity) {
params.set("severity", state.modal.severity);
}
if (state.modal.priorityOnly) {
params.set("priorityOnly", "true");
}
const path =
state.modal.areaType === "owner"
? (() => {
params.set("ownerType", state.modal.ownerType);
params.set("ownerName", state.modal.ownerName);
return `/owners/packages?${params.toString()}`;
})()
: state.modal.areaType === "province"
? `/provinces/${encodeURIComponent(state.modal.areaKey)}/packages?${params.toString()}`
: `/regions/${encodeURIComponent(state.modal.areaKey)}/packages?${params.toString()}`;
try {
const payload = await fetchJson(path);
if (requestId !== state.modalRequestId) {
return;
}
renderModalContent(payload);
} catch (error) {
if (requestId !== state.modalRequestId) {
return;
}
renderModalState("Gagal memuat paket", formatFetchError(error), true);
}
}
function openAreaModal(areaKey) {
state.selectedAreaKey = areaKey;
state.selectedOwnerKey = null;
state.modal = {
areaType: currentAreaType(),
areaKey,
ownerName: "",
page: 1,
pageSize: 25,
search: "",
ownerType: "",
severity: "",
priorityOnly: false,
};
refreshMapStyles();
renderSidebarContent();
dom.modal.classList.add("open");
document.body.style.overflow = "hidden";
loadAreaPackages();
}
function openOwnerModal(ownerName, ownerType) {
state.selectedAreaKey = null;
state.selectedOwnerKey = getOwnerCardKey(ownerType, ownerName);
state.modal = {
areaType: "owner",
areaKey: null,
ownerName,
page: 1,
pageSize: 25,
search: "",
ownerType,
severity: "",
priorityOnly: false,
};
refreshMapStyles();
renderSidebarContent();
dom.modal.classList.add("open");
document.body.style.overflow = "hidden";
loadAreaPackages();
}
function closeRegionModal() {
state.modalRequestId += 1;
state.modal = {
areaType: currentAreaType(),
areaKey: null,
ownerName: "",
page: 1,
pageSize: 25,
search: "",
ownerType: "",
severity: "",
priorityOnly: false,
};
dom.modal.classList.remove("open");
document.body.style.overflow = "";
}
function setSearch(value) {
state.search = value;
renderSidebarContent();
}
function setSort(value) {
state.sortBy = value;
renderSidebarContent();
}
function setTab(value) {
if (isProvinceView() || isCentralOwnerMode()) {
state.tab = "all";
renderTabs();
return;
}
state.tab = value;
refreshMapStyles();
renderTabs();
renderSidebarContent();
}
function setMapFilter(value) {
const wasProvinceView = isProvinceView();
const wasCentralOwnerMode = isCentralOwnerMode();
state.mapFilter = value;
const viewChanged = wasProvinceView !== isProvinceView();
const centralOwnerModeChanged = wasCentralOwnerMode !== isCentralOwnerMode();
if (viewChanged) {
state.tab = "all";
state.selectedAreaKey = null;
state.selectedOwnerKey = null;
closeRegionModal();
renderLegend();
renderFilterChips();
renderTabs();
renderSidebarContent();
renderGeoLayer(true);
return;
}
if (centralOwnerModeChanged) {
state.tab = "all";
state.selectedAreaKey = null;
state.selectedOwnerKey = null;
if (state.modal.areaType === "owner" && !isCentralOwnerMode()) {
closeRegionModal();
}
}
refreshMapStyles();
renderFilterChips();
renderTabs();
renderSidebarContent();
}
function setModalSearch(value) {
state.modal.search = value;
state.modal.page = 1;
loadAreaPackages();
}
function setModalOwnerType(value) {
if (state.modal.areaType === "province" || state.modal.areaType === "owner") {
return;
}
state.modal.ownerType = value;
state.modal.page = 1;
loadAreaPackages();
}
function setModalSeverity(value) {
state.modal.severity = value;
state.modal.page = 1;
loadAreaPackages();
}
function setModalPriorityOnly(value) {
state.modal.priorityOnly = Boolean(value);
state.modal.page = 1;
loadAreaPackages();
}
function changeModalPage(page) {
state.modal.page = page;
loadAreaPackages();
}
function openPackageDetail(sourceId) {
const url = buildInaprocUrl(sourceId);
if (!url) {
return;
}
window.open(url, "_blank", "noopener,noreferrer");
}
function handlePackageRowKeydown(event, sourceId) {
if (!event) {
return;
}
if (event.key !== "Enter" && event.key !== " " && event.key !== "Spacebar") {
return;
}
event.preventDefault();
openPackageDetail(sourceId);
}
function bindEvents() {
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
closeRegionModal();
}
});
dom.modal.addEventListener("click", (event) => {
if (event.target === dom.modal) {
closeRegionModal();
}
});
}
async function bootstrap() {
renderBootstrapLoading();
try {
dashboardData = normalizeDashboardData(await fetchJson("/bootstrap"));
regionsByKey = new Map(dashboardData.regions.map((region) => [region.regionKey, region]));
provincesByKey = new Map(dashboardData.provinceView.provinces.map((province) => [province.provinceKey, province]));
renderKpis();
renderLegend();
initMap();
renderFilterChips();
renderTabs();
renderSidebarContent();
} catch (error) {
renderBootstrapError(formatFetchError(error));
}
}
window.dashboardActions = {
changeModalPage,
closeRegionModal,
handlePackageRowKeydown,
openAreaModal,
openOwnerModal,
openPackageDetail,
setMapFilter,
setModalOwnerType,
setModalPriorityOnly,
setModalSearch,
setModalSeverity,
setSearch,
setSort,
setTab,
};
bindEvents();
bootstrap();
})();