This commit is contained in:
2026-10-15 14:05:01 +07:00
parent 9b5c0a7309
commit 2e8271d27a
2 changed files with 97 additions and 87 deletions
@@ -233,14 +233,14 @@ class SummaryController extends Controller
$aksesPasar = $this->filteredAksesPasar($request); $aksesPasar = $this->filteredAksesPasar($request);
$kpiCards = [ $kpiCards = [
[ // [
'value' => number_format($totalPetani, 0, ',', '.'), // 'value' => number_format($totalPetani, 0, ',', '.'),
'unit' => 'orang', // 'unit' => 'orang',
'label' => 'Jumlah Petani', // 'label' => 'Jumlah Petani',
'description' => 'Petani sesuai filter yang dipilih', // 'description' => 'Petani sesuai filter yang dipilih',
'icon' => 'tabler-users', // 'icon' => 'tabler-users',
'variant' => 'primary', // 'variant' => 'primary',
], // ],
[ [
'value' => number_format($totalLuasLahan, 2, ',', '.'), 'value' => number_format($totalLuasLahan, 2, ',', '.'),
'unit' => 'ha', 'unit' => 'ha',
@@ -418,6 +418,7 @@ class SummaryController extends Controller
return [ return [
'jumlah_petani' => $petaniIds->count(), 'jumlah_petani' => $petaniIds->count(),
'jumlah_lahan' => $mapLahanRows->count(),
'total_luas_lahan' => (float) $mapLahanRows->sum('luas_lahan'), 'total_luas_lahan' => (float) $mapLahanRows->sum('luas_lahan'),
'petani_per_komoditas' => $commodityRows, 'petani_per_komoditas' => $commodityRows,
'opt' => collect($optCounts) 'opt' => collect($optCounts)
@@ -752,7 +752,7 @@
<div class="card-body d-flex justify-content-between align-items-center gap-3"> <div class="card-body d-flex justify-content-between align-items-center gap-3">
<div> <div>
<h5>Peta Sebaran</h5> <h5>Peta Sebaran</h5>
<p class="text-body-secondary mb-0">Lahan, tanaman, OPT, dan praktik budidaya berdasarkan filter wilayah.</p> <p class="text-body-secondary mb-0">Lahan, tanaman, dan OPT berdasarkan filter wilayah.</p>
</div> </div>
<span class="btn btn-icon btn-label-secondary btn-sm chart-group-icon-toggle" aria-hidden="true"> <span class="btn btn-icon btn-label-secondary btn-sm chart-group-icon-toggle" aria-hidden="true">
<i class="icon-base ti tabler-chevron-down"></i> <i class="icon-base ti tabler-chevron-down"></i>
@@ -764,61 +764,55 @@
<div class="row g-4 mb-4"> <div class="row g-4 mb-4">
<div class="col-12"> <div class="col-12">
<div class="card h-100 produksi-chart-card"> <div class="card h-100 produksi-chart-card">
<!-- <div class="card-header">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Peta Sebaran & Praktik Budidaya</h5>
<small class="text-body-secondary">Petani, luas lahan, komoditas, OPT, dan praktik budidaya berdasarkan filter aktif</small>
</div>
</div> -->
<div class="card-body"> <div class="card-body">
<div class="d-flex flex-column gap-4"> <div class="d-flex align-items-center justify-content-between gap-3 flex-wrap mb-2">
<section> <div>
<div class="d-flex align-items-center justify-content-between gap-3 flex-wrap mb-2"> <h6 class="mb-0">Peta Sebaran Lahan & Tanaman</h6>
<div> <small class="text-body-secondary">Lahan dan tanaman berdasarkan filter wilayah, komoditas, dan segmen</small>
<h6 class="mb-0">Peta Sebaran Lahan & Tanaman</h6> </div>
<small class="text-body-secondary">Lahan dan tanaman berdasarkan filter wilayah, komoditas, dan segmen</small> <div class="btn-group btn-group-sm">
</div> <button type="button" class="btn btn-outline-secondary active produksi-map-layer-btn" data-map-layer="satellite">Satellite</button>
<div class="btn-group btn-group-sm"> <button type="button" class="btn btn-outline-secondary produksi-map-layer-btn" data-map-layer="topo">Topographic</button>
<button type="button" class="btn btn-outline-secondary active produksi-map-layer-btn" data-map-layer="satellite">Satellite</button> <button type="button" class="btn btn-outline-secondary produksi-map-layer-btn" data-map-layer="parcels">Parcels</button>
<button type="button" class="btn btn-outline-secondary produksi-map-layer-btn" data-map-layer="topo">Topographic</button> </div>
<button type="button" class="btn btn-outline-secondary produksi-map-layer-btn" data-map-layer="parcels">Parcels</button> </div>
</div> <div id="produksi-sebaran-map" class="produksi-map">
</div> <button type="button" id="produksi-map-back" class="btn btn-sm btn-dark shadow produksi-map-back">
<div id="produksi-sebaran-map" class="produksi-map"> <i class="icon-base ti tabler-arrow-left me-1"></i>
<button type="button" id="produksi-map-back" class="btn btn-sm btn-dark shadow produksi-map-back"> Kembali ke Atas
<i class="icon-base ti tabler-arrow-left me-1"></i> </button>
Kembali ke Atas <div id="produksi-map-empty" class="produksi-map-empty">Belum ada poligon lahan untuk filter ini.</div>
</button> <div class="produksi-map-information-overlay">
<div id="produksi-map-empty" class="produksi-map-empty">Belum ada poligon lahan untuk filter ini.</div> <div class="produksi-map-information" id="produksi-map-information"></div>
<div class="produksi-map-information-overlay"> </div>
<div class="produksi-map-information" id="produksi-map-information"></div> </div>
</div> </div>
</div> </div>
</section> </div>
<section> <div class="col-12">
<div class="d-flex align-items-center justify-content-between gap-3 flex-wrap mb-2"> <div class="card h-100 produksi-chart-card">
<div> <div class="card-body">
<h6 class="mb-0">Peta Praktik Budidaya</h6> <div class="d-flex align-items-center justify-content-between gap-3 flex-wrap mb-2">
<small class="text-body-secondary">Intensitas pemangkasan, pemupukan, dan kebiasaan praktik baik</small> <div>
</div> <h6 class="mb-0">Peta Praktik Budidaya</h6>
<div class="btn-group btn-group-sm"> <small class="text-body-secondary">Intensitas pemangkasan, pemupukan, dan kebiasaan praktik baik</small>
<button type="button" class="btn btn-outline-secondary active produksi-cultivation-map-layer-btn" data-map-layer="satellite">Satellite</button> </div>
<button type="button" class="btn btn-outline-secondary produksi-cultivation-map-layer-btn" data-map-layer="topo">Topographic</button> <div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-secondary produksi-cultivation-map-layer-btn" data-map-layer="parcels">Parcels</button> <button type="button" class="btn btn-outline-secondary active produksi-cultivation-map-layer-btn" data-map-layer="satellite">Satellite</button>
</div> <button type="button" class="btn btn-outline-secondary produksi-cultivation-map-layer-btn" data-map-layer="topo">Topographic</button>
</div> <button type="button" class="btn btn-outline-secondary produksi-cultivation-map-layer-btn" data-map-layer="parcels">Parcels</button>
<div id="produksi-budidaya-map" class="produksi-map"> </div>
<button type="button" id="produksi-budidaya-map-back" class="btn btn-sm btn-dark shadow produksi-map-back"> </div>
<i class="icon-base ti tabler-arrow-left me-1"></i> <div id="produksi-budidaya-map" class="produksi-map">
Kembali ke Atas <button type="button" id="produksi-budidaya-map-back" class="btn btn-sm btn-dark shadow produksi-map-back">
</button> <i class="icon-base ti tabler-arrow-left me-1"></i>
<div id="produksi-budidaya-map-empty" class="produksi-map-empty">Belum ada poligon lahan untuk filter ini.</div> Kembali ke Atas
<div class="produksi-map-information-overlay"> </button>
<div class="produksi-map-information" id="produksi-budidaya-map-information"></div> <div id="produksi-budidaya-map-empty" class="produksi-map-empty">Belum ada poligon lahan untuk filter ini.</div>
</div> <div class="produksi-map-information-overlay">
</div> <div class="produksi-map-information" id="produksi-budidaya-map-information"></div>
</section> </div>
</div> </div>
</div> </div>
</div> </div>
@@ -1079,7 +1073,11 @@ document.addEventListener('DOMContentLoaded', function () {
const mapData = @json($mapData); const mapData = @json($mapData);
const dashboardFilters = @json($dashboardFilters); const dashboardFilters = @json($dashboardFilters);
const mapInformationUrl = @json(route('dashboard.produktivitas.map-information')); const mapInformationUrl = @json(route('dashboard.produktivitas.map-information'));
const mapInformation = mapData.information || {}; const initialMapInformation = mapData.information || {};
const mapInformation = {
production: { ...initialMapInformation },
cultivation: { ...initialMapInformation },
};
const wilayah = @json($wilayah); const wilayah = @json($wilayah);
const headingColor = config.colors.headingColor; const headingColor = config.colors.headingColor;
const legendColor = config.colors.bodyColor; const legendColor = config.colors.bodyColor;
@@ -1092,7 +1090,7 @@ document.addEventListener('DOMContentLoaded', function () {
let productionMapRendered = false; let productionMapRendered = false;
let cultivationMapRendered = false; let cultivationMapRendered = false;
let actualProductivityRendered = false; let actualProductivityRendered = false;
let mapInformationRequestId = 0; const mapInformationRequestIds = { production: 0, cultivation: 0 };
function hasData(data) { function hasData(data) {
return data && Array.isArray(data.series) && data.series.some(function (value) { return data && Array.isArray(data.series) && data.series.some(function (value) {
@@ -1110,18 +1108,18 @@ document.addEventListener('DOMContentLoaded', function () {
}); });
} }
function renderProductionMapInformation(elementId, includeOpt = true) { function renderProductionMapInformation(elementId, information, includeOpt = true) {
const element = document.getElementById(elementId); const element = document.getElementById(elementId);
if (!element) return; if (!element) return;
const commodityRows = Array.isArray(mapInformation.petani_per_komoditas) const commodityRows = Array.isArray(information.petani_per_komoditas)
? mapInformation.petani_per_komoditas ? information.petani_per_komoditas
: []; : [];
const optRows = Array.isArray(mapInformation.opt) ? mapInformation.opt : []; const optRows = Array.isArray(information.opt) ? information.opt : [];
const indexValue = mapInformation.indeks === null || mapInformation.indeks === undefined const indexValue = information.indeks === null || information.indeks === undefined
? '-' ? '-'
: `${formatNumber(mapInformation.indeks, 2)} / 5`; : `${formatNumber(information.indeks, 2)} / 5`;
const renderBars = function (rows, emptyLabel, unitLabel) { const renderBars = function (rows, emptyLabel, unitLabel) {
if (!rows.length) { if (!rows.length) {
return `<div class="produksi-map-information-empty">${emptyLabel}</div>`; return `<div class="produksi-map-information-empty">${emptyLabel}</div>`;
@@ -1152,14 +1150,21 @@ document.addEventListener('DOMContentLoaded', function () {
<div class="produksi-map-information-summary"> <div class="produksi-map-information-summary">
<span class="produksi-map-information-summary-icon"><i class="icon-base ti tabler-users"></i></span> <span class="produksi-map-information-summary-icon"><i class="icon-base ti tabler-users"></i></span>
<div> <div>
<div class="produksi-map-information-summary-value">${formatNumber(mapInformation.jumlah_petani || 0, 0)}</div> <div class="produksi-map-information-summary-value">${formatNumber(information.jumlah_petani || 0, 0)}</div>
<div class="produksi-map-information-summary-label">Jumlah Petani</div> <div class="produksi-map-information-summary-label">Jumlah Petani</div>
</div> </div>
</div> </div>
<div class="produksi-map-information-summary mt-2">
<span class="produksi-map-information-summary-icon"><i class="icon-base ti tabler-map-pin"></i></span>
<div>
<div class="produksi-map-information-summary-value">${formatNumber(information.jumlah_lahan || 0, 0)}</div>
<div class="produksi-map-information-summary-label">Jumlah Lahan</div>
</div>
</div>
<div class="produksi-map-information-summary mt-2"> <div class="produksi-map-information-summary mt-2">
<span class="produksi-map-information-summary-icon"><i class="icon-base ti tabler-map-2"></i></span> <span class="produksi-map-information-summary-icon"><i class="icon-base ti tabler-map-2"></i></span>
<div> <div>
<div class="produksi-map-information-summary-value">${formatNumber(mapInformation.total_luas_lahan || 0, 2)} <small class="fs-6 fw-normal">ha</small></div> <div class="produksi-map-information-summary-value">${formatNumber(information.total_luas_lahan || 0, 2)} <small class="fs-6 fw-normal">ha</small></div>
<div class="produksi-map-information-summary-label">Total Luas Lahan</div> <div class="produksi-map-information-summary-label">Total Luas Lahan</div>
</div> </div>
</div> </div>
@@ -1171,11 +1176,11 @@ document.addEventListener('DOMContentLoaded', function () {
</div> </div>
<div class="produksi-map-information-metric"> <div class="produksi-map-information-metric">
<span class="produksi-map-information-metric-label">Segmen</span> <span class="produksi-map-information-metric-label">Segmen</span>
<strong class="produksi-map-information-metric-value">${escapeHtml(mapInformation.segmen || 'Belum Lengkap')}</strong> <strong class="produksi-map-information-metric-value">${escapeHtml(information.segmen || 'Belum Lengkap')}</strong>
</div> </div>
<div class="produksi-map-information-metric"> <div class="produksi-map-information-metric">
<span class="produksi-map-information-metric-label">Segmen Dominan</span> <span class="produksi-map-information-metric-label">Segmen Dominan</span>
<strong class="produksi-map-information-metric-value">${escapeHtml(mapInformation.segmen_dominan || 'Belum Lengkap')}</strong> <strong class="produksi-map-information-metric-value">${escapeHtml(information.segmen_dominan || 'Belum Lengkap')}</strong>
</div> </div>
</div> </div>
<section> <section>
@@ -1191,8 +1196,8 @@ document.addEventListener('DOMContentLoaded', function () {
`; `;
} }
async function loadProductionMapInformation(level = null, id = null) { async function loadProductionMapInformation(mapType, level = null, id = null) {
const requestId = ++mapInformationRequestId; const requestId = ++mapInformationRequestIds[mapType];
const params = new URLSearchParams(); const params = new URLSearchParams();
Object.entries(dashboardFilters || {}).forEach(function ([key, value]) { Object.entries(dashboardFilters || {}).forEach(function ([key, value]) {
@@ -1214,11 +1219,14 @@ document.addEventListener('DOMContentLoaded', function () {
if (!response.ok) return; if (!response.ok) return;
const information = await response.json(); const information = await response.json();
if (requestId !== mapInformationRequestId) return; if (requestId !== mapInformationRequestIds[mapType]) return;
Object.assign(mapInformation, information); Object.assign(mapInformation[mapType], information);
renderProductionMapInformation('produksi-map-information'); if (mapType === 'production') {
renderProductionMapInformation('produksi-budidaya-map-information', false); renderProductionMapInformation('produksi-map-information', mapInformation.production);
} else {
renderProductionMapInformation('produksi-budidaya-map-information', mapInformation.cultivation, false);
}
} catch (error) { } catch (error) {
console.error('Gagal memuat informasi wilayah pada peta.', error); console.error('Gagal memuat informasi wilayah pada peta.', error);
} }
@@ -1487,7 +1495,7 @@ document.addEventListener('DOMContentLoaded', function () {
renderProductionMapLevel(previous.level, previous.parentId); renderProductionMapLevel(previous.level, previous.parentId);
productionMapState.informationLevel = previous.informationLevel; productionMapState.informationLevel = previous.informationLevel;
productionMapState.informationId = previous.informationId; productionMapState.informationId = previous.informationId;
loadProductionMapInformation(previous.informationLevel, previous.informationId); loadProductionMapInformation('production', previous.informationLevel, previous.informationId);
if (previous.bounds?.isValid()) { if (previous.bounds?.isValid()) {
productionMapState.map.fitBounds(previous.bounds, { padding: [30, 30] }); productionMapState.map.fitBounds(previous.bounds, { padding: [30, 30] });
@@ -1580,7 +1588,7 @@ document.addEventListener('DOMContentLoaded', function () {
}); });
productionMapState.informationLevel = nextLevel; productionMapState.informationLevel = nextLevel;
productionMapState.informationId = item.id; productionMapState.informationId = item.id;
loadProductionMapInformation(nextLevel, item.id); loadProductionMapInformation('production', nextLevel, item.id);
renderProductionMapLevel(nextProductionMapLevel(nextLevel), item.id, event.target.getBounds()); renderProductionMapLevel(nextProductionMapLevel(nextLevel), item.id, event.target.getBounds());
}); });
} }
@@ -1714,7 +1722,7 @@ document.addEventListener('DOMContentLoaded', function () {
renderCultivationMapLevel(previous.level, previous.parentId); renderCultivationMapLevel(previous.level, previous.parentId);
cultivationMapState.informationLevel = previous.informationLevel; cultivationMapState.informationLevel = previous.informationLevel;
cultivationMapState.informationId = previous.informationId; cultivationMapState.informationId = previous.informationId;
loadProductionMapInformation(previous.informationLevel, previous.informationId); loadProductionMapInformation('cultivation', previous.informationLevel, previous.informationId);
if (previous.bounds?.isValid()) { if (previous.bounds?.isValid()) {
cultivationMapState.map.fitBounds(previous.bounds, { padding: [30, 30] }); cultivationMapState.map.fitBounds(previous.bounds, { padding: [30, 30] });
@@ -1807,7 +1815,7 @@ document.addEventListener('DOMContentLoaded', function () {
}); });
cultivationMapState.informationLevel = nextLevel; cultivationMapState.informationLevel = nextLevel;
cultivationMapState.informationId = item.id; cultivationMapState.informationId = item.id;
loadProductionMapInformation(nextLevel, item.id); loadProductionMapInformation('cultivation', nextLevel, item.id);
renderCultivationMapLevel(nextProductionMapLevel(nextLevel), item.id, event.target.getBounds()); renderCultivationMapLevel(nextProductionMapLevel(nextLevel), item.id, event.target.getBounds());
}); });
} }
@@ -2249,8 +2257,8 @@ document.addEventListener('DOMContentLoaded', function () {
cultivationMapRendered = true; cultivationMapRendered = true;
} }
renderProductionMapInformation('produksi-map-information'); renderProductionMapInformation('produksi-map-information', mapInformation.production);
renderProductionMapInformation('produksi-budidaya-map-information', false); renderProductionMapInformation('produksi-budidaya-map-information', mapInformation.cultivation, false);
if (typeof L === 'undefined') { if (typeof L === 'undefined') {
const mapEmpty = document.getElementById('produksi-map-empty'); const mapEmpty = document.getElementById('produksi-map-empty');
@@ -2338,9 +2346,10 @@ document.addEventListener('DOMContentLoaded', function () {
const control = document.querySelector('.chart-group-header-toggle[aria-controls="' + panel.id + '"]'); const control = document.querySelector('.chart-group-header-toggle[aria-controls="' + panel.id + '"]');
if (!control) return; if (!control) return;
const icon = control.querySelector('.chart-group-icon-toggle i'); const icon = control.querySelector('.chart-group-icon-toggle i');
const groupLabel = panel.id === 'peta-sebaran-panel' ? 'peta' : 'chart';
const syncState = function () { const syncState = function () {
const expanded = panel.classList.contains('show'); const expanded = panel.classList.contains('show');
const label = expanded ? 'Sembunyikan chart' : 'Tampilkan chart'; const label = expanded ? `Sembunyikan ${groupLabel}` : `Tampilkan ${groupLabel}`;
control.setAttribute('aria-expanded', String(expanded)); control.setAttribute('aria-expanded', String(expanded));
control.setAttribute('aria-label', label); control.setAttribute('aria-label', label);
control.title = label; control.title = label;