solve dashboard indikator pertanian

This commit is contained in:
2026-08-13 16:07:55 +07:00
parent 4b26e82afb
commit 6830142a82
4 changed files with 1120 additions and 133 deletions
@@ -72,6 +72,16 @@
box-shadow: 0 0 0 .2rem rgba(40, 167, 69, .12);
}
.indicator-filter-bar.is-loading .form-select,
.indicator-filter-bar.is-loading .btn {
pointer-events: none;
}
.indicator-filter-bar.is-loading .indicator-filter-select,
.indicator-filter-bar.is-loading .btn {
opacity: .65;
}
.indicator-filter-static {
align-items: center;
background-color: var(--bs-white);
@@ -203,7 +213,7 @@
</p>
</div>
<div class="indicator-filter-bar" aria-label="Filter dashboard indikator pertanian">
<div class="indicator-filter-bar" id="indicator-filter-bar" aria-label="Filter dashboard indikator pertanian">
@foreach ($filterOptions as $filter)
<div class="indicator-filter-group">
<label for="filter-{{ $filter['key'] }}" class="indicator-filter-label">
@@ -296,9 +306,9 @@
</div>
</div>
<div class="row g-6">
<div class="row g-6" id="indicator-summary-row">
@foreach ($summaryData as $summary)
<div class="col-lg-3 col-sm-6">
<div class="col-lg-3 col-sm-6" data-summary-card="{{ $loop->index }}">
<div class="card card-border-shadow-{{ $summary['variant'] }} h-100">
<div class="card-body">
<div class="d-flex align-items-center mb-2">
@@ -308,17 +318,17 @@
</span>
</div>
<h4 class="mb-0 indicator-summary-value">
<h4 class="mb-0 indicator-summary-value" data-summary-value>
{{ $summary['value'] }}
</h4>
</div>
<p class="mb-1">
<p class="mb-1" data-summary-label>
{{ $summary['label'] }}
</p>
<p class="mb-0">
<small class="text-body-secondary">
<small class="text-body-secondary" data-summary-description>
{{ $summary['description'] }}
</small>
</p>
@@ -647,11 +657,13 @@
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function () {
const chartData = @json($chartData);
let chartData = @json($chartData);
const dashboardUrl = @json(route('dashboard.indikator-pertanian'));
const dashboardDataUrl = @json(route('dashboard.indikator-pertanian.data'));
const initialFilters = @json($initialFilters);
const filters = { ...initialFilters };
const filterSelects = document.querySelectorAll('.indicator-filter-select');
const filterBar = document.getElementById('indicator-filter-bar');
const kecamatanSelect = document.querySelector('[data-filter-key="kecamatan"]');
const desaSelect = document.querySelector('[data-filter-key="desa"]');
const desaOptions = desaSelect
@@ -665,8 +677,12 @@
: [];
const resetButton = document.getElementById('indicator-reset-filter');
const exportButton = document.getElementById('indicator-export-data');
const chartInstances = {};
let activeRequest = null;
let lahanChartsDirty = false;
let productionChartsRendered = false;
function applyFilters() {
function buildFilterParams() {
const params = new URLSearchParams();
['kecamatan', 'desa', 'komoditas', 'segmentasi'].forEach(function (key) {
@@ -675,9 +691,139 @@
}
});
window.location.href = params.toString()
return params;
}
function updateAddressBar() {
const params = buildFilterParams();
const nextUrl = params.toString()
? dashboardUrl + '?' + params.toString()
: dashboardUrl;
window.history.replaceState({}, '', nextUrl);
}
function setLoadingState(isLoading) {
filterBar?.classList.toggle('is-loading', isLoading);
filterSelects.forEach(function (select) {
if (select.dataset.filterKey === 'desa') {
select.disabled = isLoading || filters.kecamatan === 'all';
return;
}
select.disabled = isLoading;
});
if (resetButton) {
resetButton.disabled = isLoading;
}
if (exportButton) {
exportButton.disabled = isLoading;
}
}
function updateSummaryCards(summaryData) {
document.querySelectorAll('[data-summary-card]').forEach(function (card) {
const summary = summaryData[Number(card.dataset.summaryCard)];
if (!summary) {
return;
}
const value = card.querySelector('[data-summary-value]');
const label = card.querySelector('[data-summary-label]');
const description = card.querySelector('[data-summary-description]');
if (value) {
value.textContent = summary.value;
}
if (label) {
label.textContent = summary.label;
}
if (description) {
description.textContent = summary.description;
}
});
}
async function loadIndicatorData() {
const params = buildFilterParams();
if (activeRequest) {
activeRequest.abort();
}
const requestController = new AbortController();
activeRequest = requestController;
setLoadingState(true);
try {
const response = await fetch(`${dashboardDataUrl}?${params.toString()}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
signal: requestController.signal,
});
if (!response.ok) {
throw new Error(`HTTP Error ${response.status}`);
}
const result = await response.json();
Object.assign(filters, result.filters || {});
if (kecamatanSelect && kecamatanSelect.value !== filters.kecamatan) {
kecamatanSelect.value = filters.kecamatan;
}
renderDesaOptions(filters.desa);
filterSelects.forEach(function (select) {
const key = select.dataset.filterKey;
if (key !== 'desa' && filters[key] !== undefined) {
select.value = filters[key];
}
});
chartData = result.chartData || {};
updateSummaryCards(result.summaryData || []);
productionChartsRendered = false;
if (
typeof renderLahanCharts === 'function'
&& document.querySelector('#indicator-tab-lahan-tanaman')?.classList.contains('active')
) {
renderLahanCharts();
} else {
lahanChartsDirty = true;
}
if (
typeof renderProductionCharts === 'function'
&& document.querySelector('#indicator-tab-produksi')?.classList.contains('active')
) {
renderProductionCharts();
}
updateAddressBar();
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Gagal memuat indikator pertanian:', error);
}
} finally {
if (activeRequest === requestController) {
activeRequest = null;
setLoadingState(false);
}
}
}
function renderDesaOptions(selectedValue = initialFilters.desa) {
@@ -720,12 +866,24 @@
renderDesaOptions('all');
}
applyFilters();
loadIndicatorData();
});
});
resetButton?.addEventListener('click', function () {
window.location.href = dashboardUrl;
Object.assign(filters, {
kecamatan: 'all',
desa: 'all',
komoditas: 'all',
segmentasi: 'all',
});
filterSelects.forEach(function (select) {
select.value = 'all';
});
renderDesaOptions('all');
loadIndicatorData();
});
exportButton?.addEventListener('click', function () {});
@@ -830,6 +988,13 @@
return;
}
if (chartInstances[selector]) {
chartInstances[selector].destroy();
delete chartInstances[selector];
}
element.innerHTML = '';
if (!hasSeries(data)) {
element.innerHTML = '<div class="text-center text-body-secondary py-5">Belum ada data.</div>';
return;
@@ -841,7 +1006,7 @@
const unit = options.unit || '';
const valueDigits = options.valueDigits || 0;
new ApexCharts(element, {
chartInstances[selector] = new ApexCharts(element, {
chart: {
type: 'donut',
height: options.height || 330,
@@ -975,7 +1140,9 @@
},
},
},
}).render();
});
chartInstances[selector].render();
}
function renderBar(selector, data, options = {}) {
@@ -985,6 +1152,13 @@
return;
}
if (chartInstances[selector]) {
chartInstances[selector].destroy();
delete chartInstances[selector];
}
element.innerHTML = '';
if (!hasSeries(data)) {
element.innerHTML = '<div class="text-center text-body-secondary py-5">Belum ada data.</div>';
return;
@@ -1007,7 +1181,7 @@
? options.height || 330
: (options.height || 330) + Math.max(maxLabelLines - 3, 0) * 16;
new ApexCharts(element, {
chartInstances[selector] = new ApexCharts(element, {
chart: {
type: 'bar',
height: chartHeight,
@@ -1165,91 +1339,95 @@
},
},
},
}).render();
});
chartInstances[selector].render();
}
renderDonut(
'#chart-petani-komoditas',
chartData.petaniPerKomoditas,
colorsFor(0),
{
totalLabel: 'Petani',
unit: 'petani',
function renderLahanCharts() {
lahanChartsDirty = false;
renderDonut(
'#chart-petani-komoditas',
chartData.petaniPerKomoditas,
colorsFor(0),
{
totalLabel: 'Petani',
unit: 'petani',
valueDigits: 0,
}
);
renderBar('#chart-luas-kecamatan', chartData.luasLahanPerKecamatan, {
name: 'Luas lahan',
suffix: 'ha',
colors: colorsFor(1),
height: 330,
horizontal: true,
labelWidth: 150,
valueDigits: 2,
});
renderDonut(
'#chart-kondisi-tanaman',
chartData.kondisiTanaman,
colorsFor(2),
{
totalLabel: 'Data',
valueDigits: 0,
}
);
renderDonut(
'#chart-status-kepemilikan-lahan',
chartData.statusKepemilikanLahan,
colorsFor(3),
{
totalLabel: 'Lahan',
unit: 'lahan',
valueDigits: 0,
}
);
renderBar('#chart-serangan-hama', chartData.seranganHama, {
name: 'Data',
colors: colorsFor(4),
height: 330,
valueDigits: 0,
}
);
});
renderBar('#chart-luas-kecamatan', chartData.luasLahanPerKecamatan, {
name: 'Luas lahan',
suffix: 'ha',
colors: colorsFor(1),
height: 330,
horizontal: true,
labelWidth: 150,
valueDigits: 2,
});
renderDonut(
'#chart-kondisi-tanaman',
chartData.kondisiTanaman,
colorsFor(2),
{
totalLabel: 'Data',
renderBar('#chart-penyakit-tanaman', chartData.penyakitTanaman, {
name: 'Data',
colors: colorsFor(5),
height: 330,
valueDigits: 0,
}
);
});
renderDonut(
'#chart-status-kepemilikan-lahan',
chartData.statusKepemilikanLahan,
colorsFor(3),
{
totalLabel: 'Lahan',
unit: 'lahan',
renderBar('#chart-keparahan-serangan', chartData.keparahanSerangan, {
name: 'Data',
colors: colorsFor(6),
height: 330,
valueDigits: 0,
}
);
});
renderBar('#chart-serangan-hama', chartData.seranganHama, {
name: 'Data',
colors: colorsFor(4),
height: 330,
valueDigits: 0,
});
renderBar('#chart-status-hak-kelola-lahan', chartData.statusHakKelolaLahan, {
name: 'Petani',
colors: colorsFor(7),
height: 330,
horizontal: true,
labelWidth: 180,
valueDigits: 0,
});
renderBar('#chart-penyakit-tanaman', chartData.penyakitTanaman, {
name: 'Data',
colors: colorsFor(5),
height: 330,
valueDigits: 0,
});
renderBar('#chart-keparahan-serangan', chartData.keparahanSerangan, {
name: 'Data',
colors: colorsFor(6),
height: 330,
valueDigits: 0,
});
renderBar('#chart-status-hak-kelola-lahan', chartData.statusHakKelolaLahan, {
name: 'Petani',
colors: colorsFor(7),
height: 330,
horizontal: true,
labelWidth: 180,
valueDigits: 0,
});
renderBar('#chart-praktik-pemupukan', chartData.praktikPemupukan, {
name: 'Petani',
colors: colorsFor(8),
height: 330,
horizontal: true,
labelWidth: 180,
valueDigits: 0,
});
let productionChartsRendered = false;
renderBar('#chart-praktik-pemupukan', chartData.praktikPemupukan, {
name: 'Petani',
colors: colorsFor(8),
height: 330,
horizontal: true,
labelWidth: 180,
valueDigits: 0,
});
}
function renderProductionCharts() {
if (productionChartsRendered) {
@@ -1319,6 +1497,16 @@
});
}
renderLahanCharts();
document
.querySelector('[data-bs-target="#indicator-tab-lahan-tanaman"]')
?.addEventListener('shown.bs.tab', function () {
if (lahanChartsDirty) {
renderLahanCharts();
}
});
document
.querySelector('[data-bs-target="#indicator-tab-produksi"]')
?.addEventListener('shown.bs.tab', renderProductionCharts);