710 lines
28 KiB
Plaintext
710 lines
28 KiB
Plaintext
@{
|
||
ViewData["Title"] = "Capaian BPS RW DKI Terhadap Bulan Sebelumnya";
|
||
}
|
||
|
||
<div class="breadcrumbs text-sm">
|
||
<ul>
|
||
<li class="text-gray-500"><a>Laporan</a></li>
|
||
<li>Capaian BPS RW DKI Terhadap Bulan Sebelumnya</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 Terhadap Bulan Sebelumnya</h3>
|
||
</div>
|
||
<div class="justify-self-end lg:self-center">
|
||
<button class="btn rounded-full bg-white" type="button" onclick="modal_filter.showModal()">
|
||
<span class="icon icon-fill me-2">filter_list</span>
|
||
Filter
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h-6"></div>
|
||
|
||
<!-- Grid 2 Kolom untuk Tingkat Kepatuhan dan Capaian Rumah -->
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<!-- Tingkat Kepatuhan PJLP -->
|
||
<div class="bg-white rounded-sm shadow p-6">
|
||
<h3 class="text-lg font-bold mb-1" id="titleKepatuhanPjlp">Tingkat Kepatuhan PJLP</h3>
|
||
<p class="text-md font-medium mb-1" id="subtitleKepatuhanPjlp">Tingkat Kepatuhan PJLP Pendamping BPS RW dalam Menjalankan Instruksi Kepala Dinas</p>
|
||
<p class="text-sm text-gray-800 mb-4" id="locationKepatuhanPjlp">DKI Jakarta</p>
|
||
<div class="border border-gray-400 rounded-sm p-4">
|
||
<div class="w-full h-[400px]">
|
||
<canvas id="chartKepatuhanPjlp"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Capaian Rumah Memilah -->
|
||
<div class="bg-white rounded-sm shadow p-6">
|
||
<h3 class="text-lg font-bold mb-1" id="titleCapaianRumah">Capaian Rumah Memilah</h3>
|
||
<p class="text-md font-medium mb-1" id="subtitleCapaianRumah">Capaian rumah memilah periode Mei-Oktober 2025</p>
|
||
<p class="text-sm text-gray-800 mb-4" id="locationCapaianRumah">DKI Jakarta</p>
|
||
<div class="border border-gray-400 rounded-sm p-4">
|
||
<div class="w-full h-[400px]">
|
||
<canvas id="chartCapaianRumah"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Status Validasi SATPEL -->
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6" id="satpelContainer"></div>
|
||
|
||
<!-- Capaian Volume Pemilahan Sampah -->
|
||
<div class="bg-white rounded-sm shadow p-6 mt-6">
|
||
<h3 class="text-lg font-bold mb-1">Capaian Volume Pemilahan Sampah</h3>
|
||
<p class="text-md font-medium mb-4">Capaian Volume Pemilahan Sampah dari Sumber / Rumah Tangga Bulan Mei – Oktober 2025</p>
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6" id="volumeSampahContainer"></div>
|
||
</div>
|
||
|
||
<!-- Filter Modal -->
|
||
<dialog id="modal_filter" class="modal modal-bottom sm:modal-middle">
|
||
<div class="modal-box w-full sm:max-w-sm">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="text-lg font-bold">Filter</h3>
|
||
<button type="button" class="btn btn-sm btn-circle btn-ghost" onclick="modal_filter.close()">✕</button>
|
||
</div>
|
||
<form id="filterForm" action="#" method="get">
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<fieldset class="fieldset">
|
||
<legend class="fieldset-legend">Bulan Awal</legend>
|
||
<input type="month" id="bulanAwal" class="input w-full" name="bulanAwal" placeholder="Pilih bulan" min="2020-01" required>
|
||
</fieldset>
|
||
<fieldset class="fieldset">
|
||
<legend class="fieldset-legend">Bulan Akhir</legend>
|
||
<input type="month" id="bulanAkhir" class="input w-full" name="bulanAkhir" placeholder="Pilih bulan" required>
|
||
</fieldset>
|
||
</div>
|
||
<div id="errorMessage" class="text-red-500 text-sm mt-2 hidden"></div>
|
||
<div class="modal-action">
|
||
<button type="button" class="btn" onclick="clearFilter()">Bersihkan</button>
|
||
<button type="submit" class="btn btn-neutral">Terapkan Filter</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</dialog>
|
||
|
||
<script src="/lib/chart.js/chart.umd.js"></script>
|
||
<script src="/lib/chartjs-plugin-annotation/chartjs-plugin-annotation.min.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' }
|
||
};
|
||
|
||
const chartInstances = {
|
||
kepatuhanPjlp: null,
|
||
capaianRumah: null,
|
||
satpel: {},
|
||
volumeSampah: {}
|
||
};
|
||
|
||
function updateCharts(data) {
|
||
// Destroy existing charts
|
||
[chartInstances.kepatuhanPjlp, chartInstances.capaianRumah].forEach(c => c?.destroy());
|
||
Object.values(chartInstances.satpel).forEach(c => c?.destroy());
|
||
Object.values(chartInstances.volumeSampah).forEach(c => c?.destroy());
|
||
|
||
chartInstances.satpel = {};
|
||
chartInstances.volumeSampah = {};
|
||
|
||
if (data.bulanAwal) document.getElementById('bulanAwal').value = data.bulanAwal;
|
||
if (data.bulanAkhir) document.getElementById('bulanAkhir').value = data.bulanAkhir;
|
||
|
||
renderCharts(data);
|
||
}
|
||
|
||
function renderCharts(data) {
|
||
const mapFields = (target, source) => {
|
||
target.title.textContent = source.title;
|
||
target.subtitle.textContent = source.subtitle;
|
||
target.location.textContent = source.location;
|
||
};
|
||
|
||
mapFields({
|
||
title: document.getElementById('titleKepatuhanPjlp'),
|
||
subtitle: document.getElementById('subtitleKepatuhanPjlp'),
|
||
location: document.getElementById('locationKepatuhanPjlp')
|
||
}, data.kepatuhanPjlp);
|
||
|
||
mapFields({
|
||
title: document.getElementById('titleCapaianRumah'),
|
||
subtitle: document.getElementById('subtitleCapaianRumah'),
|
||
location: document.getElementById('locationCapaianRumah')
|
||
}, data.capaianRumah);
|
||
|
||
// Kepatuhan PJLP Chart
|
||
const kepatuhanDatasets = [
|
||
{
|
||
label: 'Ceklis',
|
||
data: data.kepatuhanPjlp.ceklis,
|
||
backgroundColor: '#12B76A',
|
||
barPercentage: 0.9,
|
||
categoryPercentage: 0.8
|
||
},
|
||
{
|
||
label: 'Belum Ceklis',
|
||
data: data.kepatuhanPjlp.belumCeklis,
|
||
backgroundColor: '#F04438',
|
||
barPercentage: 0.9,
|
||
categoryPercentage: 0.8
|
||
}
|
||
];
|
||
|
||
// lines for Kepatuhan PJLP
|
||
const kepatuhanAnnotations = {};
|
||
|
||
if (data.kepatuhanPjlp.labels.length > 1) {
|
||
const numBars = 2;
|
||
const categoryPercentage = 0.8;
|
||
const barWidth = categoryPercentage / numBars;
|
||
const offsetCeklis = -barWidth / 2;
|
||
const offsetBelumCeklis = barWidth / 2;
|
||
|
||
data.kepatuhanPjlp.labels.forEach((label, idx) => {
|
||
if (idx < data.kepatuhanPjlp.labels.length - 1) {
|
||
kepatuhanAnnotations[`lineCeklis${idx}`] = {
|
||
type: 'line',
|
||
xMin: idx + offsetCeklis,
|
||
xMax: idx + 1 + offsetCeklis,
|
||
yMin: data.kepatuhanPjlp.ceklis[idx],
|
||
yMax: data.kepatuhanPjlp.ceklis[idx + 1],
|
||
borderColor: '#84CC16',
|
||
borderWidth: 2,
|
||
borderDash: [5, 5]
|
||
};
|
||
|
||
kepatuhanAnnotations[`lineBelumCeklis${idx}`] = {
|
||
type: 'line',
|
||
xMin: idx + offsetBelumCeklis,
|
||
xMax: idx + 1 + offsetBelumCeklis,
|
||
yMin: data.kepatuhanPjlp.belumCeklis[idx],
|
||
yMax: data.kepatuhanPjlp.belumCeklis[idx + 1],
|
||
borderColor: '#EF4444',
|
||
borderWidth: 2,
|
||
borderDash: [5, 5]
|
||
};
|
||
}
|
||
});
|
||
}
|
||
|
||
chartInstances.kepatuhanPjlp = new Chart(document.getElementById('chartKepatuhanPjlp'), {
|
||
type: 'bar',
|
||
data: {
|
||
labels: data.kepatuhanPjlp.labels,
|
||
datasets: kepatuhanDatasets
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
interaction: { mode: 'index', intersect: false },
|
||
layout: {
|
||
padding: {
|
||
top: 20
|
||
}
|
||
},
|
||
scales: {
|
||
x: { grid: { offset: false } },
|
||
y: {
|
||
beginAtZero: true,
|
||
grace: '5%'
|
||
}
|
||
},
|
||
plugins: {
|
||
legend: {
|
||
position: 'bottom',
|
||
labels: {
|
||
usePointStyle: true,
|
||
generateLabels: (chart) => {
|
||
const original = Chart.defaults.plugins.legend.labels.generateLabels(chart);
|
||
if (data.kepatuhanPjlp.labels.length > 1) {
|
||
original.push(
|
||
{
|
||
text: 'Linear Ceklis',
|
||
fillStyle: 'transparent',
|
||
strokeStyle: '#84CC16',
|
||
lineWidth: 2,
|
||
lineDash: [5, 5],
|
||
pointStyle: 'line'
|
||
},
|
||
{
|
||
text: 'Linear Belum Ceklis',
|
||
fillStyle: 'transparent',
|
||
strokeStyle: '#EF4444',
|
||
lineWidth: 2,
|
||
lineDash: [5, 5],
|
||
pointStyle: 'line'
|
||
}
|
||
);
|
||
}
|
||
return original;
|
||
}
|
||
}
|
||
},
|
||
datalabels: {
|
||
anchor: 'end',
|
||
align: 'top',
|
||
color: '#444',
|
||
font: { size: 10 }
|
||
},
|
||
annotation: {
|
||
annotations: kepatuhanAnnotations
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// Capaian Rumah Chart
|
||
const capaianRumahDatasets = [
|
||
{
|
||
label: 'Rumah Memilah Konsisten',
|
||
data: data.capaianRumah.rumahMemilahKonsisten,
|
||
backgroundColor: '#84CC16',
|
||
barPercentage: 0.9,
|
||
categoryPercentage: 0.8
|
||
},
|
||
{
|
||
label: 'Rumah Memilah Tidak Konsisten',
|
||
data: data.capaianRumah.rumahMemilahTidakKonsisten,
|
||
backgroundColor: '#FB923C',
|
||
barPercentage: 0.9,
|
||
categoryPercentage: 0.8
|
||
},
|
||
{
|
||
label: 'Target',
|
||
data: data.capaianRumah.target,
|
||
backgroundColor: '#EF4444',
|
||
barPercentage: 0.9,
|
||
categoryPercentage: 0.8
|
||
}
|
||
];
|
||
|
||
// lines for trend - using pixel-based positioning
|
||
const capaianAnnotations = {};
|
||
|
||
if (data.capaianRumah.labels.length > 1) {
|
||
const numBars = 3;
|
||
const barPercentage = 0.9;
|
||
const categoryPercentage = 0.8;
|
||
|
||
const barWidth = categoryPercentage / numBars;
|
||
const offsetKonsisten = -barWidth;
|
||
const offsetTidakKonsisten = 0;
|
||
|
||
data.capaianRumah.labels.forEach((label, idx) => {
|
||
if (idx < data.capaianRumah.labels.length - 1) {
|
||
capaianAnnotations[`lineKonsisten${idx}`] = {
|
||
type: 'line',
|
||
xMin: idx + offsetKonsisten,
|
||
xMax: idx + 1 + offsetKonsisten,
|
||
yMin: data.capaianRumah.rumahMemilahKonsisten[idx],
|
||
yMax: data.capaianRumah.rumahMemilahKonsisten[idx + 1],
|
||
borderColor: '#84CC16',
|
||
borderWidth: 2,
|
||
borderDash: [5, 5]
|
||
};
|
||
|
||
capaianAnnotations[`lineTidakKonsisten${idx}`] = {
|
||
type: 'line',
|
||
xMin: idx + offsetTidakKonsisten,
|
||
xMax: idx + 1 + offsetTidakKonsisten,
|
||
yMin: data.capaianRumah.rumahMemilahTidakKonsisten[idx],
|
||
yMax: data.capaianRumah.rumahMemilahTidakKonsisten[idx + 1],
|
||
borderColor: '#FB923C',
|
||
borderWidth: 2,
|
||
borderDash: [5, 5]
|
||
};
|
||
}
|
||
});
|
||
}
|
||
|
||
chartInstances.capaianRumah = new Chart(document.getElementById('chartCapaianRumah'), {
|
||
type: 'bar',
|
||
data: {
|
||
labels: data.capaianRumah.labels,
|
||
datasets: capaianRumahDatasets
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
interaction: { mode: 'index', intersect: false },
|
||
layout: {
|
||
padding: {
|
||
top: 20
|
||
}
|
||
},
|
||
scales: {
|
||
x: {
|
||
grid: { offset: false }
|
||
},
|
||
y: {
|
||
beginAtZero: true,
|
||
grace: '5%'
|
||
}
|
||
},
|
||
plugins: {
|
||
legend: {
|
||
position: 'bottom',
|
||
labels: {
|
||
usePointStyle: true,
|
||
generateLabels: (chart) => {
|
||
const original = Chart.defaults.plugins.legend.labels.generateLabels(chart);
|
||
// custom legend items for trend lines
|
||
if (data.capaianRumah.labels.length > 1) {
|
||
original.push(
|
||
{
|
||
text: 'Linear Rumah Memilah Konsisten',
|
||
fillStyle: 'transparent',
|
||
strokeStyle: '#84CC16',
|
||
lineWidth: 2,
|
||
lineDash: [5, 5],
|
||
pointStyle: 'line'
|
||
},
|
||
{
|
||
text: 'Linear Rumah Memilah Tidak Konsisten',
|
||
fillStyle: 'transparent',
|
||
strokeStyle: '#FB923C',
|
||
lineWidth: 2,
|
||
lineDash: [5, 5],
|
||
pointStyle: 'line'
|
||
}
|
||
);
|
||
}
|
||
return original;
|
||
}
|
||
}
|
||
},
|
||
datalabels: {
|
||
anchor: 'end',
|
||
align: 'top',
|
||
color: '#444',
|
||
font: { size: 10 }
|
||
},
|
||
annotation: {
|
||
annotations: capaianAnnotations
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// SATPEL Charts
|
||
const satpelContainer = document.getElementById('satpelContainer');
|
||
satpelContainer.innerHTML = '';
|
||
|
||
data.validasiSatpel.forEach((item, i) => {
|
||
const card = document.createElement('div');
|
||
card.className = 'bg-white rounded-sm shadow p-6';
|
||
card.innerHTML = `
|
||
<h3 class="text-lg font-bold mb-1">${item.title}</h3>
|
||
<p class="text-md font-medium mb-4">${item.subtitle}</p>
|
||
<div class="border border-gray-400 rounded-sm p-4">
|
||
<div class="w-full h-[400px]"><canvas id="satpel_${i}"></canvas></div>
|
||
</div>
|
||
<div class="mt-4 p-4 bg-gray-100 rounded-lg">
|
||
${item.description.map((desc, idx) =>
|
||
`<div class="text-sm">${idx + 1}. ${desc}</div>`
|
||
).join('')}
|
||
</div>
|
||
`;
|
||
satpelContainer.appendChild(card);
|
||
|
||
const satpelDatasets = [
|
||
{
|
||
label: 'Sudah',
|
||
data: item.sudah,
|
||
backgroundColor: '#84CC16',
|
||
barPercentage: 0.9,
|
||
categoryPercentage: 0.8
|
||
},
|
||
{
|
||
label: 'Belum',
|
||
data: item.belum,
|
||
backgroundColor: '#FB923C',
|
||
barPercentage: 0.9,
|
||
categoryPercentage: 0.8
|
||
}
|
||
];
|
||
|
||
// lines for SATPEL
|
||
const satpelAnnotations = {};
|
||
|
||
if (item.labels.length > 1) {
|
||
const numBars = 2;
|
||
const categoryPercentage = 0.8;
|
||
const barWidth = categoryPercentage / numBars;
|
||
const offsetSudah = -barWidth / 2;
|
||
const offsetBelum = barWidth / 2;
|
||
|
||
item.labels.forEach((label, idx) => {
|
||
if (idx < item.labels.length - 1) {
|
||
satpelAnnotations[`lineSudah${idx}`] = {
|
||
type: 'line',
|
||
xMin: idx + offsetSudah,
|
||
xMax: idx + 1 + offsetSudah,
|
||
yMin: item.sudah[idx],
|
||
yMax: item.sudah[idx + 1],
|
||
borderColor: '#84CC16',
|
||
borderWidth: 2,
|
||
borderDash: [5, 5]
|
||
};
|
||
}
|
||
});
|
||
}
|
||
|
||
chartInstances.satpel[i] = new Chart(document.getElementById(`satpel_${i}`), {
|
||
type: 'bar',
|
||
data: {
|
||
labels: item.labels,
|
||
datasets: satpelDatasets
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
interaction: { mode: 'index', intersect: false },
|
||
layout: {
|
||
padding: {
|
||
top: 20
|
||
}
|
||
},
|
||
scales: {
|
||
x: { grid: { offset: false } },
|
||
y: {
|
||
beginAtZero: true,
|
||
grace: '5%'
|
||
}
|
||
},
|
||
plugins: {
|
||
legend: {
|
||
position: 'bottom',
|
||
labels: {
|
||
usePointStyle: true,
|
||
generateLabels: (chart) => {
|
||
const original = Chart.defaults.plugins.legend.labels.generateLabels(chart);
|
||
if (item.labels.length > 1) {
|
||
original.push({
|
||
text: 'Linear Sudah',
|
||
fillStyle: 'transparent',
|
||
strokeStyle: '#84CC16',
|
||
lineWidth: 2,
|
||
lineDash: [5, 5],
|
||
pointStyle: 'line'
|
||
});
|
||
}
|
||
return original;
|
||
}
|
||
}
|
||
},
|
||
datalabels: {
|
||
anchor: 'end',
|
||
align: 'top',
|
||
color: '#444',
|
||
font: { size: 10 }
|
||
},
|
||
annotation: {
|
||
annotations: satpelAnnotations
|
||
}
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
// Volume Sampah Charts
|
||
const volumeSampahContainer = document.getElementById('volumeSampahContainer');
|
||
volumeSampahContainer.innerHTML = '';
|
||
|
||
const categories = [
|
||
{ key: 'mudahTerurai', title: 'Mudah Terurai', color: '#16A34A' },
|
||
{ key: 'materialDaur', title: 'Material Daur', color: '#EAB308' },
|
||
{ key: 'b3', title: 'B3', color: '#DC2626' },
|
||
{ key: 'residu', title: 'Residu', color: '#475467' }
|
||
];
|
||
|
||
categories.forEach((cat, i) => {
|
||
const card = document.createElement('div');
|
||
card.className = 'border border-gray-400 rounded-sm p-4';
|
||
card.innerHTML = `
|
||
<div class="mb-3">
|
||
<span class="inline-block px-3 py-1 text-sm font-semibold rounded" style="background-color: ${cat.color}; color: white;">${cat.title}</span>
|
||
</div>
|
||
<div class="w-full h-[400px]"><canvas id="volumeSampah_${i}"></canvas></div>
|
||
`;
|
||
volumeSampahContainer.appendChild(card);
|
||
|
||
const values = data.volumeSampah[cat.key];
|
||
const min = Math.min(...values), max = Math.max(...values);
|
||
const bgColors = values.map(v => {
|
||
const opacity = min === max ? 1.0 : 0.4 + (0.6 * (v - min) / (max - min));
|
||
const [r, g, b] = [cat.color.slice(1,3), cat.color.slice(3,5), cat.color.slice(5,7)].map(x => parseInt(x, 16));
|
||
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
||
});
|
||
|
||
chartInstances.volumeSampah[i] = new Chart(document.getElementById(`volumeSampah_${i}`), {
|
||
type: 'bar',
|
||
data: {
|
||
labels: data.volumeSampah.labels,
|
||
datasets: [{ label: cat.title, data: values, backgroundColor: bgColors }]
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
layout: {
|
||
padding: {
|
||
top: 20
|
||
}
|
||
},
|
||
scales: {
|
||
x: { grid: { display: false } },
|
||
y: {
|
||
beginAtZero: true,
|
||
grace: '5%'
|
||
}
|
||
},
|
||
plugins: {
|
||
legend: { display: false },
|
||
datalabels: {
|
||
color: '#333',
|
||
anchor: 'end',
|
||
align: 'top',
|
||
font: { size: 10 }
|
||
}
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
const getCurrentMonth = () => new Date().toISOString().slice(0, 7);
|
||
|
||
const getDefaultDateRange = () => {
|
||
const today = new Date();
|
||
const lastMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1);
|
||
return {
|
||
start: lastMonth.toISOString().slice(0, 7),
|
||
end: getCurrentMonth()
|
||
};
|
||
};
|
||
|
||
const getErrorMsg = () => document.getElementById('errorMessage');
|
||
const getBulanAwalInput = () => document.getElementById('bulanAwal');
|
||
const getBulanAkhirInput = () => document.getElementById('bulanAkhir');
|
||
|
||
function loadDataByDateRange(startDate, endDate) {
|
||
const params = new URLSearchParams();
|
||
if (startDate) params.append('bulanAwal', startDate);
|
||
if (endDate) params.append('bulanAkhir', endDate);
|
||
|
||
fetch(`/LaporanCapaianBulanan/GetData?${params.toString()}`, {
|
||
headers: { 'Accept': 'application/json' }
|
||
})
|
||
.then(res => res.json())
|
||
.then(updateCharts)
|
||
.catch(err => console.error('Gagal memuat data:', err));
|
||
}
|
||
|
||
function setErrorMessage(message) {
|
||
const errorMsg = getErrorMsg();
|
||
if (message) {
|
||
errorMsg.textContent = message;
|
||
errorMsg.classList.remove('hidden');
|
||
} else {
|
||
errorMsg.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
function validateDateRange(startDate, endDate) {
|
||
const currentMonth = getCurrentMonth();
|
||
|
||
if (!startDate || !endDate) {
|
||
setErrorMessage('Bulan awal dan akhir harus diisi');
|
||
return false;
|
||
}
|
||
|
||
if (startDate < '2020-01') {
|
||
setErrorMessage('Bulan awal minimal tahun 2020');
|
||
return false;
|
||
}
|
||
|
||
if (startDate > currentMonth) {
|
||
setErrorMessage('Bulan awal tidak boleh lebih besar dari bulan berjalan');
|
||
return false;
|
||
}
|
||
|
||
if (endDate < startDate) {
|
||
setErrorMessage('Bulan akhir tidak boleh lebih kecil dari bulan awal');
|
||
return false;
|
||
}
|
||
|
||
if (endDate > currentMonth) {
|
||
setErrorMessage('Bulan akhir tidak boleh lebih besar dari bulan berjalan');
|
||
return false;
|
||
}
|
||
|
||
setErrorMessage('');
|
||
return true;
|
||
}
|
||
|
||
function updateDateInputConstraints() {
|
||
const currentMonth = getCurrentMonth();
|
||
const bulanAwalInput = getBulanAwalInput();
|
||
const bulanAkhirInput = getBulanAkhirInput();
|
||
|
||
bulanAwalInput.max = currentMonth;
|
||
if (bulanAwalInput.value) {
|
||
bulanAkhirInput.min = bulanAwalInput.value;
|
||
}
|
||
bulanAkhirInput.max = currentMonth;
|
||
}
|
||
|
||
function setDefaultValues() {
|
||
const defaultRange = getDefaultDateRange();
|
||
getBulanAwalInput().value = defaultRange.start;
|
||
getBulanAkhirInput().value = defaultRange.end;
|
||
updateDateInputConstraints();
|
||
}
|
||
|
||
function clearFilter() {
|
||
setDefaultValues();
|
||
setErrorMessage('');
|
||
modal_filter.close();
|
||
const defaultRange = getDefaultDateRange();
|
||
loadDataByDateRange(defaultRange.start, defaultRange.end);
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
setDefaultValues();
|
||
const defaultRange = getDefaultDateRange();
|
||
loadDataByDateRange(defaultRange.start, defaultRange.end);
|
||
|
||
document.getElementById('filterForm').addEventListener('submit', (e) => {
|
||
e.preventDefault();
|
||
const startDate = getBulanAwalInput().value;
|
||
const endDate = getBulanAkhirInput().value;
|
||
|
||
if (validateDateRange(startDate, endDate)) {
|
||
loadDataByDateRange(startDate, endDate);
|
||
modal_filter.close();
|
||
}
|
||
});
|
||
|
||
// Validasi filter
|
||
getBulanAwalInput().addEventListener('change', () => {
|
||
updateDateInputConstraints();
|
||
const startDate = getBulanAwalInput().value;
|
||
const endDate = getBulanAkhirInput().value;
|
||
if (startDate && endDate) validateDateRange(startDate, endDate);
|
||
});
|
||
|
||
getBulanAkhirInput().addEventListener('change', () => {
|
||
const startDate = getBulanAwalInput().value;
|
||
const endDate = getBulanAkhirInput().value;
|
||
if (startDate && endDate) validateDateRange(startDate, endDate);
|
||
});
|
||
});
|
||
</script> |