solve dashboard indikator pertanian
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user