bps-rw/Views/LaporanCapaian/Index.cshtml

422 lines
18 KiB
Plaintext

@{
ViewData["Title"] = "Capaian BPS RW DKI Jakarta";
}
<div class="breadcrumbs text-sm">
<ul>
<li class="text-gray-500"><a>Laporan</a></li>
<li>Capaian BPS RW DKI Jakarta</li>
</ul>
</div>
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
<div class="prose">
<h3 class="mb-2">Capaian BPS RW DKI Jakarta</h3>
</div>
<div class="flex justify-end items-center gap-2">
<span class="whitespace-nowrap text-sm font-medium text-gray-700">Filter Bulan:</span>
<input type="month" id="filterBulan" class="px-4 py-2 border bg-white rounded-md w-[180px] sm:w-[200px]"
min="2020-01" max="@DateTime.Now.ToString("yyyy-MM")" value="@DateTime.Now.ToString("yyyy-MM")" />
</div>
</div>
<div class="h-6"></div>
<!-- Tingkat Kepatuhan -->
<div class="bg-white rounded-sm shadow p-6">
<h3 class="text-lg font-semibold mb-4">Tingkat Kepatuhan dalam Menjalankan Instruksi Kepala Dinas</h3>
<div class="border border-gray-400 rounded-sm p-4 min-h-80 grid grid-cols-1 md:grid-cols-3 gap-6 items-start">
<!-- Pie Chart Total -->
<div class="flex flex-col items-center col-span-3 md:col-span-1">
<h4 class="font-medium text-sm text-center" id="titleTotalPjlp">Kepatuhan PJLP Pendamping</h4>
<div class="w-full h-[250px] flex items-center justify-center mt-2">
<canvas id="pieTotal"></canvas>
</div>
</div>
<div class="flex flex-col items-start col-span-3 md:col-span-2">
<h4 class="font-medium text-sm text-left">Kepatuhan PJLP Pendamping</h4>
<div class="w-full h-[250px] mt-2">
<canvas id="barWilayah"></canvas>
</div>
</div>
</div>
</div>
<!-- Detail Kepatuhan Per Wilayah -->
<div class="bg-white rounded-sm shadow p-6 mt-6">
<h3 class="text-lg font-semibold mb-4">Detail Kepatuhan PJLP Pendamping</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6" id="detailPjlpContainer">
</div>
</div>
<!-- Status Validasi SATPEL Oktober 2025 -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6" id="satpelContainer"></div>
<!-- Capaian Program Pendampingan -->
<div class="bg-white rounded-sm shadow p-6 mt-6">
<h3 class="text-lg font-semibold">Capaian Program Pendampingan BPS RW</h3>
<p class="text-sm text-gray-600 mb-4" id="descPeriodeRumah">Capaian Rumah memilah dalam pelaksanaan instruksi Kepala
Dinas Lingkungan Hidup</p>
<div class="border border-gray-400 rounded-sm p-4 min-h-80 grid grid-cols-1 md:grid-cols-3 gap-6 items-start">
<div class="flex flex-col items-center col-span-1 w-full">
<h4 class="font-medium text-sm text-center" id="titleTargetRumah">Total Target Rumah Tangga Memilah</h4>
<div class="w-full h-[250px] flex items-center justify-center mt-2">
<canvas id="pieRumah"></canvas>
</div>
</div>
<div class="flex flex-col items-start col-span-2">
<div class="w-full h-[250px] mt-2">
<canvas id="barRumahWilayah"></canvas>
</div>
</div>
</div>
</div>
<!-- Detail Rumah Memilah -->
<div class="bg-white rounded-sm shadow p-6 mt-6">
<h3 class="text-lg font-semibold mb-4">Detail Jumlah Rumah Memilah</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6" id="detailRumahContainer">
</div>
</div>
<script src="/lib/chart.js/chart.umd.js"></script>
<script src="/lib/chartjs-plugin-datalabels/chartjs-plugin-datalabels.min.js"></script>
<script>
Chart.register(ChartDataLabels);
Chart.defaults.animation = { duration: 500, easing: 'easeOutQuart' };
Chart.defaults.animations = {
numbers: { duration: 500, easing: 'easeOutQuart' },
colors: { duration: 500, easing: 'easeOutQuart' }
};
let chartInstances = {
pieTotal: null,
barWilayah: null,
pieRumah: null,
barRumahWilayah: null,
piePerWilayah: {},
satpel: {},
detailRumah: {}
};
function updateCharts(data) {
if (chartInstances.pieTotal) chartInstances.pieTotal.destroy();
if (chartInstances.barWilayah) chartInstances.barWilayah.destroy();
if (chartInstances.pieRumah) chartInstances.pieRumah.destroy();
if (chartInstances.barRumahWilayah) chartInstances.barRumahWilayah.destroy();
Object.values(chartInstances.piePerWilayah).forEach(chart => chart && chart.destroy());
chartInstances.piePerWilayah = {};
Object.values(chartInstances.satpel).forEach(chart => chart && chart.destroy());
chartInstances.satpel = {};
Object.values(chartInstances.detailRumah).forEach(chart => chart && chart.destroy());
chartInstances.detailRumah = {};
renderCharts(data);
}
function renderCharts(data) {
document.getElementById('titleTargetRumah').textContent = `Total Target ${data.rumah.total} Rumah Tangga Memilah`;
document.getElementById('descPeriodeRumah').textContent = `Capaian Rumah memilah dalam pelaksanaan instruksi Kepala Dinas Lingkungan Hidup Periode ${data.periode}`;
// Pie Kepatuhan PJLP
chartInstances.pieTotal = new Chart(document.getElementById('pieTotal'), {
type: 'pie',
data: {
labels: ['Ceklis', 'Belum Ceklis'],
datasets: [{ data: [data.pieTotal.ceklis, data.pieTotal.belumCeklis], backgroundColor: ['#9FCE62', '#EF4444'] }]
},
options: {
responsive: true,
maintainAspectRatio: false,
aspectRatio: 1,
plugins: {
legend: { position: 'bottom', labels: { usePointStyle: true, pointStyle: "circle" } },
datalabels: {
color: '#fff',
formatter: (val, ctx) => {
const total = ctx.dataset.data.reduce((a, b) => a + b, 0);
const percent = Math.round(val / total * 100);
return `${val} PJLP\n${percent}%`;
},
font: { weight: 'bold', size: 12 },
align: 'center',
anchor: 'center'
}
}
}
});
// bar Kepatuhan PJLP
chartInstances.barWilayah = new Chart(document.getElementById('barWilayah'), {
type: 'bar',
data: {
labels: data.barWilayah.labels,
datasets: [
{ label: 'Ceklis', data: data.barWilayah.ceklis, backgroundColor: '#9FCE62' },
{ label: 'Belum Ceklis', data: data.barWilayah.belumCeklis, backgroundColor: '#EF4444' },
{ label: 'Total', data: data.barWilayah.total, backgroundColor: '#C6C6C6' }
]
},
options: {
responsive: true,
maintainAspectRatio: false,
layout: {
padding: {
top: 20
}
},
scales: { y: { beginAtZero: true } },
plugins: {
legend: { position: 'bottom', labels: { usePointStyle: true, pointStyle: "circle" } },
datalabels: { anchor: 'end', align: 'top', color: '#444', font: { size: 10 } }
}
}
});
// Pie Detail Kepatuhan
const detailPjlpContainer = document.getElementById('detailPjlpContainer');
detailPjlpContainer.innerHTML = '';
data.piePerWilayah.forEach((item, i) => {
const wilayahId = item.wilayah.replace(/\s+/g, '_');
const card = document.createElement('div');
card.className = 'relative border border-gray-400 rounded-sm p-4 text-center space-y-2 h-[300px] flex flex-col items-center justify-between';
card.innerHTML = `
<h4 class="font-semibold text-sm leading-tight">${item.wilayah} : ${data.wilayahPjlp[i].jumlahPjlp} PJLP<br />Pendamping BPS RW</h4>
<div class="w-full h-[200px]">
<canvas id="pie_${wilayahId}"></canvas>
</div>
`;
detailPjlpContainer.appendChild(card);
// chart wilayah
const ctx = document.getElementById(`pie_${wilayahId}`);
if (ctx) {
chartInstances.piePerWilayah[wilayahId] = new Chart(ctx, {
type: 'pie',
data: {
labels: ['Ceklis', 'Belum Ceklis'],
datasets: [{
data: [item.ceklis, item.belumCeklis],
backgroundColor: ['#9FCE62', '#EF4444']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
aspectRatio: 1,
plugins: {
legend: { position: 'bottom', labels: { usePointStyle: true, pointStyle: "circle" } },
datalabels: {
color: '#fff',
formatter: (val, ctx) => {
const total = ctx.dataset.data.reduce((a, b) => a + b, 0);
return Math.round(val / total * 100) + '%';
},
font: { weight: 'bold', size: 12 }
}
}
}
});
}
});
// Status Validasi
const satpelContainer = document.getElementById('satpelContainer');
satpelContainer.innerHTML = '';
data.satpel.forEach((item, i) => {
const card = document.createElement('div');
card.className = 'bg-base-100 rounded-sm p-4 space-y-4';
card.innerHTML = `
<div>
<h3 class="text-lg font-semibold">Status Validasi ${item.validator} ${data.periode}</h3>
<span class="text-sm text-gray-500">DKI Jakarta</span>
<div class="bg-white rounded-sm border border-gray-400 p-6 mt-6">
<div class="w-full h-[250px] flex items-center justify-center">
<canvas id="satpel_${i}"></canvas>
</div>
<div class="text-sm leading-relaxed text-gray-700">
Terhadap status validasi yang dilakukan ${item.validator}:<br />
Periode bulan ini terdapat <strong>${item.sudah.toLocaleString()}</strong> aktifitas PJLP pendamping BPS RW melakukan checklist pada sistem
informasi Dashboard BPS RW yang sudah divalidasi,
sedangkan <strong>${item.belum.toLocaleString()}</strong> aktifitas tidak tervalidasi
(Terlewat/diragukan kebenaran checklist).
</div>
</div>
</div>
`;
satpelContainer.appendChild(card);
const ctx = document.getElementById(`satpel_${i}`);
if (ctx) {
chartInstances.satpel[i] = new Chart(ctx, {
type: 'pie',
data: {
labels: ['Sudah', 'Belum'],
datasets: [{
data: [item.sudah, item.belum],
backgroundColor: ['#93C5FD ', '#FACC15']
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
aspectRatio: 1,
plugins: {
legend: { position: 'bottom', labels: { usePointStyle: true, pointStyle: "circle" } },
datalabels: {
color: '#fff',
formatter: (val, ctx) => {
const total = ctx.dataset.data.reduce((a, b) => a + b, 0);
return val.toLocaleString() + '\n' + Math.round(val / total * 100) + '%';
}
}
}
}
});
}
});
// Pie Capaian Program
chartInstances.pieRumah = new Chart(document.getElementById('pieRumah'), {
type: 'pie',
data: { labels: ['Rumah Memilih Konsisten', 'Rumah Memilih Tidak Konsisten'], datasets: [{ data: [data.rumah.konsisten, data.rumah.tidakKonsisten], backgroundColor: ['#9FCE62', '#FDBA74'] }] },
options: {
responsive: true,
maintainAspectRatio: true,
aspectRatio: 1,
plugins: {
legend: { position: 'bottom', labels: { usePointStyle: true, pointStyle: "circle" } },
datalabels: {
color: '#fff',
formatter: (val, ctx) => {
const total = ctx.dataset.data.reduce((a, b) => a + b, 0);
return val + '\n' + Math.round(val / total * 100) + '%';
}
}
}
}
});
// Bar Capaian Program
chartInstances.barRumahWilayah = new Chart(document.getElementById('barRumahWilayah'), {
type: 'bar',
data: {
labels: data.barRumah.labels,
datasets: [
{ label: 'Rumah Memilah Konsisten', data: data.barRumah.konsisten, backgroundColor: '#9FCE62' },
{ label: 'Rumah Memilih Tidak Konsisten', data: data.barRumah.tidakKonsisten, backgroundColor: '#FDBA74' },
{ label: 'Target', data: data.barRumah.target, backgroundColor: '#C6C6C6' }
]
},
options: {
responsive: true,
maintainAspectRatio: false,
layout: {
padding: {
top: 20
}
},
scales: { y: { beginAtZero: true } },
plugins: {
legend: { position: 'bottom', labels: { usePointStyle: true, pointStyle: "circle" } },
datalabels: { anchor: 'end', align: 'top', color: '#444', font: { size: 10 } }
}
}
});
// Pie Detail Rumah Memilah
const detailRumahContainer = document.getElementById('detailRumahContainer');
detailRumahContainer.innerHTML = '';
data.detailRumahPerWilayah.forEach((item, i) => {
const wilayahId = item.wilayah.replace(/\s+/g, '_');
const card = document.createElement('div');
card.className = 'border border-gray-400 rounded-sm p-4 space-y-4';
card.innerHTML = `
<h4 class="font-semibold text-sm text-center">${item.wilayah}</h4>
<div class="flex justify-center">
<div class="w-[200px] h-[200px]">
<canvas id="rm_${wilayahId}"></canvas>
</div>
</div>
<div class="text-sm text-gray-700 border border-gray-400 leading-relaxed mt-2 bg-gray-50 p-2 rounded">
Target Rumah Pemilah : ${item.target.toLocaleString()} <br />
Rumah Memilah Konsisten : ${item.konsisten.toLocaleString()} <br />
Rumah Memilah Tidak Konsisten : ${item.tidakKonsisten.toLocaleString()}
</div>
`;
detailRumahContainer.appendChild(card);
// Buat chart untuk wilayah ini
const ctx = document.getElementById(`rm_${wilayahId}`);
if (ctx) {
chartInstances.detailRumah[wilayahId] = new Chart(ctx, {
type: 'pie',
data: {
labels: ['Rumah Memilih Konsisten', 'Rumah Memilih Tidak Konsisten'],
datasets: [{
data: [item.konsisten, item.tidakKonsisten],
backgroundColor: ['#9FCE62', '#FDBA74']
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
aspectRatio: 1,
plugins: {
legend: { position: 'bottom', labels: { usePointStyle: true, pointStyle: "circle" } },
datalabels: {
color: '#fff',
formatter: (val, ctx) => {
const total = ctx.dataset.data.reduce((a, b) => a + b, 0);
return Math.round(val / total * 100) + '%';
},
font: { weight: 'bold', size: 12 }
}
}
}
});
}
});
}
const filterBulan = document.getElementById('filterBulan');
function loadDataByDate(date) {
const url = `/LaporanCapaian/GetData?date=${encodeURIComponent(date)}`;
fetch(url, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => updateCharts(data))
.catch(err => console.error('Gagal memuat data:', err));
}
document.addEventListener('DOMContentLoaded', () => {
const defaultDate = filterBulan.value;
loadDataByDate(defaultDate);
});
filterBulan.addEventListener('change', () => {
const selectedDate = filterBulan.value;
loadDataByDate(selectedDate);
});
</script>