feat: slicing laporan capaian rw jakarta

main-dlh
shola 2025-11-28 14:06:14 +07:00
parent 5ff198a883
commit 862788c17c
No known key found for this signature in database
GPG Key ID: FA9358FFDCCD05D9
3 changed files with 503 additions and 1 deletions

View File

@ -0,0 +1,81 @@
using Microsoft.AspNetCore.Mvc;
using System.Globalization;
namespace BpsRwApp.Controllers
{
[Route("[controller]/[action]")]
public class LaporanCapaianController : AppControllerBase
{
public IActionResult Index()
{
return View();
}
[HttpGet]
public IActionResult GetData(string? date)
{
// Parse filter date
var targetDate = string.IsNullOrEmpty(date)
? DateTime.Now
: DateTime.Parse(date + "-01");
var indonesianCulture = new CultureInfo("id-ID");
var periode = targetDate.ToString("MMMM yyyy", indonesianCulture);
var data = new
{
periode,
pieTotal = new { ceklis = 700, belumCeklis = 282 },
barWilayah = new
{
labels = new[] { "Jakarta Pusat", "Jakarta Utara", "Jakarta Barat", "Jakarta Timur", "Jakarta Selatan", "Kepulauan Seribu" },
ceklis = new[] { 120, 115, 130, 125, 140, 70 },
belumCeklis = new[] { 45, 50, 42, 48, 35, 62 },
total = new[] { 165, 165, 172, 173, 175, 132 }
},
piePerWilayah = new[]
{
new { wilayah = "Jakarta Pusat", ceklis = 120, belumCeklis = 45 },
new { wilayah = "Jakarta Utara", ceklis = 115, belumCeklis = 50 },
new { wilayah = "Jakarta Barat", ceklis = 130, belumCeklis = 42 },
new { wilayah = "Jakarta Timur", ceklis = 125, belumCeklis = 48 },
new { wilayah = "Jakarta Selatan", ceklis = 140, belumCeklis = 35 },
new { wilayah = "Kepulauan Seribu", ceklis = 70, belumCeklis = 62 }
},
wilayahPjlp = new[]
{
new { wilayah = "Jakarta Pusat", jumlahPjlp = 165 },
new { wilayah = "Jakarta Utara", jumlahPjlp = 165 },
new { wilayah = "Jakarta Barat", jumlahPjlp = 172 },
new { wilayah = "Jakarta Timur", jumlahPjlp = 173 },
new { wilayah = "Jakarta Selatan", jumlahPjlp = 175 },
new { wilayah = "Kepulauan Seribu", jumlahPjlp = 132 }
},
satpel = new[]
{
new { validator = "Sudin LH", sudah = 812, belum = 124 },
new { validator = "Satpel LH", sudah = 790, belum = 400 }
},
rumah = new { konsisten = 723, tidakKonsisten = 300, total = 1023 },
barRumah = new
{
labels = new[] { "Jakarta Pusat", "Jakarta Utara", "Jakarta Barat", "Jakarta Timur", "Jakarta Selatan", "Kepulauan Seribu" },
konsisten = new[] { 833, 950, 1200, 1400, 1100, 500 },
tidakKonsisten = new[] { 124, 200, 300, 350, 250, 100 },
target = new[] { 1210, 1500, 1800, 2000, 1600, 712 }
},
detailRumahPerWilayah = new[]
{
new { wilayah = "Jakarta Pusat", target = 1210, konsisten = 833, tidakKonsisten = 124 },
new { wilayah = "Jakarta Utara", target = 1500, konsisten = 950, tidakKonsisten = 200 },
new { wilayah = "Jakarta Barat", target = 1800, konsisten = 1200, tidakKonsisten = 300 },
new { wilayah = "Jakarta Selatan", target = 1600, konsisten = 1100, tidakKonsisten = 250 },
new { wilayah = "Jakarta Timur", target = 2000, konsisten = 1400, tidakKonsisten = 350 },
new { wilayah = "Kep. Seribu", target = 712, konsisten = 500, tidakKonsisten = 100 }
}
};
return Json(data);
}
}
}

View File

@ -0,0 +1,414 @@
@{
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="flex justify-between items-center mb-6">
<h2 class="text-xl font-semibold">Capaian BPS RW DKI Jakarta</h2>
<!-- Filter Bulan -->
<div class="flex items-center gap-3">
<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="2000-01" max="@DateTime.Now.ToString("yyyy-MM")" value="@DateTime.Now.ToString("yyyy-MM")" />
</select>
</div>
</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 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-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-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 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="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.5.0/chart.umd.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.2.0/chartjs-plugin-datalabels.min.js"></script>
<script>
Chart.register(ChartDataLabels);
Chart.defaults.animation = {
duration: 1500,
easing: 'easeOutQuart'
};
Chart.defaults.animations = {
numbers: { duration: 1500, easing: 'easeOutQuart' },
colors: { duration: 1500, 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,
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);
// Buat chart untuk wilayah ini
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,
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>

View File

@ -154,7 +154,7 @@
</details> </details>
</li> </li>
<li> <li>
<details @(new[] { "RincianTargetRumahMemilah", "VolumeTimbulanSampah" }.Contains(controller) ? "open" : "")> <details @(new[] { "RincianTargetRumahMemilah", "VolumeTimbulanSampah", "LaporanCapaian" }.Contains(controller) ? "open" : "")>
<summary>LAPORAN</summary> <summary>LAPORAN</summary>
<ul> <ul>
<li> <li>
@ -169,6 +169,13 @@
Volume Timbulan Sampah Volume Timbulan Sampah
</a> </a>
</li> </li>
<li>
<a asp-controller="LaporanCapaian" asp-action="Index"
class="@(controller == "LaporanCapaian" ? "menu-active" : "")">
<span class="icon icon-fill">flag</span>
Laporan Capaian BPS RW DKI Jakarta
</a>
</li>
</ul> </ul>
</details> </details>
</li> </li>