Merge branch 'main' of https://git.probindo.com/Probindo/database-pertani-web
This commit is contained in:
@@ -55,6 +55,48 @@ class SummaryController extends Controller
|
||||
] + $filterData + $this->productionDashboardData($request));
|
||||
}
|
||||
|
||||
public function mapInformation(Request $request)
|
||||
{
|
||||
if (!Gate::allows('super_admin') && !Gate::allows('admin') && !Gate::allows('fasilitator')) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'level' => ['nullable', 'in:provinsi,kabupaten,kecamatan,desa'],
|
||||
'id' => ['required_with:level', 'integer'],
|
||||
'provinsi_id' => ['nullable', 'integer'],
|
||||
'kabupatenKota_id' => ['nullable', 'integer'],
|
||||
'kecamatan_id' => ['nullable', 'integer'],
|
||||
'desaKelurahan_id' => ['nullable', 'integer'],
|
||||
'komoditas_id' => ['nullable', 'integer'],
|
||||
'segmen' => ['nullable', 'string'],
|
||||
]);
|
||||
|
||||
$filters = [
|
||||
'provinsi_id' => null,
|
||||
'kabupatenKota_id' => null,
|
||||
'kecamatan_id' => null,
|
||||
'desaKelurahan_id' => null,
|
||||
'komoditas_id' => $validated['komoditas_id'] ?? null,
|
||||
'segmen' => $validated['segmen'] ?? null,
|
||||
];
|
||||
$regionFilter = match ($validated['level'] ?? null) {
|
||||
'provinsi' => 'provinsi_id',
|
||||
'kabupaten' => 'kabupatenKota_id',
|
||||
'kecamatan' => 'kecamatan_id',
|
||||
'desa' => 'desaKelurahan_id',
|
||||
default => null,
|
||||
};
|
||||
|
||||
if ($regionFilter) {
|
||||
$filters[$regionFilter] = $validated['id'];
|
||||
}
|
||||
|
||||
$scopedRequest = Request::create('/dashboard/produktivitas/map-information', 'GET', $filters);
|
||||
|
||||
return response()->json($this->productionMapInformationData($scopedRequest));
|
||||
}
|
||||
|
||||
public function pasar(Request $request)
|
||||
{
|
||||
if (!Gate::allows('super_admin') && !Gate::allows('admin') && !Gate::allows('fasilitator')) {
|
||||
@@ -181,6 +223,7 @@ class SummaryController extends Controller
|
||||
$healthSourceName = $tanaman->isNotEmpty() ? 'tanaman' : 'lahan';
|
||||
|
||||
$landMetrics = $this->karakteristikLandMetrics($request);
|
||||
$totalPetani = $landMetrics['total_petani'];
|
||||
$totalLuasLahan = $landMetrics['total_luas_lahan'];
|
||||
$avgLuasPerPetani = $landMetrics['ratarata_luas_lahan'];
|
||||
$actualProductivity = $this->actualProductivityByCommodityChart($request);
|
||||
@@ -190,6 +233,14 @@ class SummaryController extends Controller
|
||||
$aksesPasar = $this->filteredAksesPasar($request);
|
||||
|
||||
$kpiCards = [
|
||||
[
|
||||
'value' => number_format($totalPetani, 0, ',', '.'),
|
||||
'unit' => 'orang',
|
||||
'label' => 'Jumlah Petani',
|
||||
'description' => 'Petani sesuai filter yang dipilih',
|
||||
'icon' => 'tabler-users',
|
||||
'variant' => 'primary',
|
||||
],
|
||||
[
|
||||
'value' => number_format($totalLuasLahan, 2, ',', '.'),
|
||||
'unit' => 'ha',
|
||||
@@ -262,6 +313,121 @@ class SummaryController extends Controller
|
||||
],
|
||||
'levels' => $levels,
|
||||
'cultivationLevels' => $this->productionCultivationMapLevelData($request, $levels),
|
||||
'information' => $this->productionMapInformationData($request),
|
||||
];
|
||||
}
|
||||
|
||||
private function productionMapInformationData(Request $request): array
|
||||
{
|
||||
$filteredMapLahan = (clone $this->filteredLahanQuery($request))
|
||||
->leftJoin('profiles', 'profiles.id', '=', 'map_lahan.profile_id')
|
||||
->leftJoin('petani', 'petani.id', '=', 'map_lahan.petani_id')
|
||||
->tap(fn ($query) => $this->whereActiveMapOwner($query));
|
||||
$mapLahanRows = (clone $filteredMapLahan)->get([
|
||||
'map_lahan.id',
|
||||
'map_lahan.petani_id',
|
||||
'map_lahan.luas_lahan',
|
||||
'map_lahan.serangan_hama',
|
||||
'map_lahan.penyakit_tanaman',
|
||||
'map_lahan.jenis_hama',
|
||||
'map_lahan.jenis_penyakit',
|
||||
]);
|
||||
$petaniIds = $mapLahanRows->pluck('petani_id')->filter()->unique()->values();
|
||||
$commodityRows = (clone $filteredMapLahan)
|
||||
->leftJoin('master_komoditas as map_komoditas', 'map_komoditas.id', '=', 'map_lahan.komoditas_id')
|
||||
->whereNotNull('map_lahan.petani_id')
|
||||
->selectRaw("COALESCE(map_komoditas.nama, 'Belum ditentukan') as nama")
|
||||
->selectRaw('COUNT(DISTINCT map_lahan.petani_id) as jumlah')
|
||||
->groupBy('map_komoditas.nama')
|
||||
->orderByDesc('jumlah')
|
||||
->get()
|
||||
->map(fn ($row) => ['nama' => $row->nama, 'jumlah' => (int) $row->jumlah])
|
||||
->all();
|
||||
|
||||
$tanamanByLahan = DB::table('map_tanaman')
|
||||
->whereNull('map_tanaman.deleted_at')
|
||||
->whereIn('map_tanaman.lahan_id', $mapLahanRows->pluck('id'))
|
||||
->get([
|
||||
'map_tanaman.lahan_id',
|
||||
'map_tanaman.serangan_hama',
|
||||
'map_tanaman.penyakit_tanaman',
|
||||
'map_tanaman.jenis_hama',
|
||||
'map_tanaman.jenis_penyakit',
|
||||
])
|
||||
->groupBy('lahan_id');
|
||||
$optCounts = [];
|
||||
|
||||
foreach ($mapLahanRows as $land) {
|
||||
foreach ($this->productionOptDetails($land, $tanamanByLahan->get($land->id, collect())) as $opt) {
|
||||
$optCounts[$opt] = ($optCounts[$opt] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
arsort($optCounts);
|
||||
|
||||
$mainCommodityIds = DB::table('master_komoditas')
|
||||
->where('is_utama', true)
|
||||
->whereNull('deleted_at')
|
||||
->select('id');
|
||||
$scorePetaniIds = DB::table('petani')
|
||||
->whereIn('petani.id', $petaniIds)
|
||||
->whereIn('petani.komoditas_id', $mainCommodityIds)
|
||||
->select('petani.id')
|
||||
->pluck('petani.id');
|
||||
$scoreQuery = DB::table('petani')
|
||||
->leftJoin('petani_skor', function ($join) {
|
||||
$join->on('petani_skor.petani_id', '=', 'petani.id')
|
||||
->whereNull('petani_skor.deleted_at');
|
||||
})
|
||||
->whereNull('petani.deleted_at')
|
||||
->whereIn('petani.id', $scorePetaniIds);
|
||||
$averageIndex = (clone $scoreQuery)->avg('petani_skor.skor_kesiapan_adopsi');
|
||||
$segmentCase = "CASE
|
||||
WHEN petani_skor.skor_kesiapan_adopsi >= 4 THEN 'Penggerak'
|
||||
WHEN petani_skor.skor_kesiapan_adopsi >= 3 THEN 'Perintis'
|
||||
WHEN petani_skor.skor_kesiapan_adopsi >= 2 THEN 'Pemerhati'
|
||||
WHEN petani_skor.skor_kesiapan_adopsi >= 1 THEN 'Pemula'
|
||||
ELSE 'Belum Lengkap'
|
||||
END";
|
||||
$segmentCounts = (clone $scoreQuery)
|
||||
->selectRaw("{$segmentCase} as segmen")
|
||||
->selectRaw('COUNT(DISTINCT petani.id) as jumlah')
|
||||
->groupByRaw($segmentCase)
|
||||
->get();
|
||||
$segmentPriority = [
|
||||
'Penggerak' => 0,
|
||||
'Perintis' => 1,
|
||||
'Pemerhati' => 2,
|
||||
'Pemula' => 3,
|
||||
'Belum Lengkap' => 4,
|
||||
];
|
||||
$dominantSegment = $segmentCounts
|
||||
->sort(function ($left, $right) use ($segmentPriority) {
|
||||
return ((int) $right->jumlah <=> (int) $left->jumlah)
|
||||
?: (($segmentPriority[$left->segmen] ?? 99) <=> ($segmentPriority[$right->segmen] ?? 99));
|
||||
})
|
||||
->first();
|
||||
$averageSegment = match (true) {
|
||||
$averageIndex === null => 'Belum Lengkap',
|
||||
$averageIndex >= 4 => 'Penggerak',
|
||||
$averageIndex >= 3 => 'Perintis',
|
||||
$averageIndex >= 2 => 'Pemerhati',
|
||||
$averageIndex >= 1 => 'Pemula',
|
||||
default => 'Belum Lengkap',
|
||||
};
|
||||
|
||||
return [
|
||||
'jumlah_petani' => $petaniIds->count(),
|
||||
'jumlah_lahan' => $mapLahanRows->count(),
|
||||
'total_luas_lahan' => (float) $mapLahanRows->sum('luas_lahan'),
|
||||
'petani_per_komoditas' => $commodityRows,
|
||||
'opt' => collect($optCounts)
|
||||
->map(fn ($jumlah, $nama) => ['nama' => $nama, 'jumlah' => $jumlah])
|
||||
->values()
|
||||
->all(),
|
||||
'indeks' => $averageIndex === null ? null : round((float) $averageIndex, 2),
|
||||
'segmen' => $averageSegment,
|
||||
'segmen_dominan' => $dominantSegment->segmen ?? 'Belum Lengkap',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -241,6 +241,151 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.produksi-map-information-overlay {
|
||||
background: rgba(255, 255, 255, .94);
|
||||
border: 1px solid rgba(0, 0, 0, .08);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 6px 24px rgba(0, 0, 0, .2);
|
||||
left: auto;
|
||||
max-height: calc(100% - 2rem);
|
||||
overscroll-behavior: contain;
|
||||
overflow-y: auto;
|
||||
padding: .875rem;
|
||||
pointer-events: auto;
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 1rem;
|
||||
width: min(350px, calc(100% - 5.5rem));
|
||||
z-index: 700;
|
||||
}
|
||||
|
||||
.produksi-map-information-overlay:focus-visible {
|
||||
outline: 2px solid var(--bs-primary, #7367f0);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.produksi-map-information {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .75rem;
|
||||
}
|
||||
|
||||
.produksi-map-information-summary {
|
||||
align-items: center;
|
||||
background: rgba(var(--bs-primary-rgb, 115, 103, 240), .07);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
gap: .75rem;
|
||||
padding: .875rem;
|
||||
}
|
||||
|
||||
.produksi-map-information-summary-icon {
|
||||
align-items: center;
|
||||
background: var(--bs-primary, #7367f0);
|
||||
border-radius: 10px;
|
||||
color: #fff;
|
||||
display: inline-flex;
|
||||
flex: 0 0 38px;
|
||||
height: 38px;
|
||||
justify-content: center;
|
||||
width: 38px;
|
||||
}
|
||||
|
||||
.produksi-map-information-summary-value {
|
||||
color: var(--bs-heading-color);
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.produksi-map-information-summary-label,
|
||||
.produksi-map-information-section-title {
|
||||
color: var(--bs-heading-color);
|
||||
font-size: .8125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.produksi-map-information-metrics {
|
||||
display: grid;
|
||||
gap: .4rem;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.produksi-map-information-metric {
|
||||
background: rgba(var(--bs-primary-rgb, 115, 103, 240), .07);
|
||||
border-radius: 8px;
|
||||
min-width: 0;
|
||||
padding: .5rem .625rem;
|
||||
}
|
||||
|
||||
.produksi-map-information-metric-label {
|
||||
color: var(--bs-secondary-color);
|
||||
display: block;
|
||||
font-size: .65rem;
|
||||
line-height: 1.2;
|
||||
margin-bottom: .2rem;
|
||||
}
|
||||
|
||||
.produksi-map-information-metric-value {
|
||||
color: var(--bs-heading-color);
|
||||
display: block;
|
||||
font-size: .75rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.produksi-map-information-section-title {
|
||||
display: block;
|
||||
margin-bottom: .625rem;
|
||||
}
|
||||
|
||||
.produksi-map-information-bars {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5rem;
|
||||
}
|
||||
|
||||
.produksi-map-information-bar-label {
|
||||
align-items: baseline;
|
||||
color: var(--bs-body-color);
|
||||
display: flex;
|
||||
font-size: .75rem;
|
||||
gap: .5rem;
|
||||
justify-content: space-between;
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
|
||||
.produksi-map-information-bar-label span:first-child {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.produksi-map-information-bar-label strong {
|
||||
color: var(--bs-heading-color);
|
||||
flex: 0 0 auto;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.produksi-map-information-track {
|
||||
background: var(--bs-secondary-bg, rgba(128, 128, 128, .14));
|
||||
border-radius: 999px;
|
||||
height: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.produksi-map-information-fill {
|
||||
background: var(--bs-primary, #7367f0);
|
||||
border-radius: inherit;
|
||||
height: 100%;
|
||||
min-width: 3px;
|
||||
}
|
||||
|
||||
.produksi-map-information-empty {
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: .75rem;
|
||||
}
|
||||
|
||||
.produksi-map-back {
|
||||
display: none;
|
||||
left: 50px;
|
||||
@@ -479,6 +624,13 @@
|
||||
left: 16px;
|
||||
top: 16px;
|
||||
}
|
||||
|
||||
.produksi-map-information-overlay {
|
||||
left: auto;
|
||||
right: .75rem;
|
||||
top: 1rem;
|
||||
width: min(320px, calc(100% - 1.5rem));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
@@ -571,7 +723,7 @@
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
@foreach ($kpiCards as $card)
|
||||
<div class="col-xl-4 col-md-4">
|
||||
<div class="col-12 col-sm-6 col-xl-4">
|
||||
<div class="card card-border-shadow-{{ $card['variant'] }} h-100 produksi-kpi-card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center gap-4">
|
||||
@@ -606,7 +758,7 @@
|
||||
<div class="card-body d-flex justify-content-between align-items-center gap-3">
|
||||
<div>
|
||||
<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>
|
||||
<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>
|
||||
@@ -618,9 +770,10 @@
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card h-100 produksi-chart-card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center gap-3 flex-wrap">
|
||||
<div class="card-title mb-0">
|
||||
<h5 class="m-0 me-2">Peta Sebaran</h5>
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center justify-content-between gap-3 flex-wrap mb-2">
|
||||
<div>
|
||||
<h6 class="mb-0">Peta Sebaran Lahan & Tanaman</h6>
|
||||
<small class="text-body-secondary">Lahan dan tanaman berdasarkan filter wilayah, komoditas, dan segmen</small>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm">
|
||||
@@ -629,26 +782,27 @@
|
||||
<button type="button" class="btn btn-outline-secondary produksi-map-layer-btn" data-map-layer="parcels">Parcels</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="produksi-sebaran-map" class="produksi-map">
|
||||
<button type="button" id="produksi-map-back" class="btn btn-sm btn-dark shadow produksi-map-back">
|
||||
<i class="icon-base ti tabler-arrow-left me-1"></i>
|
||||
Kembali ke Atas
|
||||
</button>
|
||||
<div id="produksi-map-empty" class="produksi-map-empty">Belum ada poligon lahan untuk filter ini.</div>
|
||||
<div class="produksi-map-information-overlay" tabindex="0" role="region" aria-label="Informasi peta sebaran">
|
||||
<div class="produksi-map-information" id="produksi-map-information"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card h-100 produksi-chart-card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center gap-3 flex-wrap">
|
||||
<div class="card-title mb-0">
|
||||
<h5 class="m-0 me-2">Peta Praktik Budidaya</h5>
|
||||
<small class="text-body-secondary">Intensitas pemangkasan, pemupukan, dan kebiasaan praktik baik berdasarkan filter wilayah</small>
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center justify-content-between gap-3 flex-wrap mb-2">
|
||||
<div>
|
||||
<h6 class="mb-0">Peta Praktik Budidaya</h6>
|
||||
<small class="text-body-secondary">Intensitas pemangkasan, pemupukan, dan kebiasaan praktik baik</small>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-outline-secondary active produksi-cultivation-map-layer-btn" data-map-layer="satellite">Satellite</button>
|
||||
@@ -656,13 +810,15 @@
|
||||
<button type="button" class="btn btn-outline-secondary produksi-cultivation-map-layer-btn" data-map-layer="parcels">Parcels</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="produksi-budidaya-map" class="produksi-map">
|
||||
<button type="button" id="produksi-budidaya-map-back" class="btn btn-sm btn-dark shadow produksi-map-back">
|
||||
<i class="icon-base ti tabler-arrow-left me-1"></i>
|
||||
Kembali ke Atas
|
||||
</button>
|
||||
<div id="produksi-budidaya-map-empty" class="produksi-map-empty">Belum ada poligon lahan untuk filter ini.</div>
|
||||
<div class="produksi-map-information-overlay" tabindex="0" role="region" aria-label="Informasi peta praktik budidaya">
|
||||
<div class="produksi-map-information" id="produksi-budidaya-map-information"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -921,6 +1077,13 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const chartData = @json($chartData);
|
||||
const mapData = @json($mapData);
|
||||
const dashboardFilters = @json($dashboardFilters);
|
||||
const mapInformationUrl = @json(route('dashboard.produktivitas.map-information'));
|
||||
const initialMapInformation = mapData.information || {};
|
||||
const mapInformation = {
|
||||
production: { ...initialMapInformation },
|
||||
cultivation: { ...initialMapInformation },
|
||||
};
|
||||
const wilayah = @json($wilayah);
|
||||
const headingColor = config.colors.headingColor;
|
||||
const legendColor = config.colors.bodyColor;
|
||||
@@ -933,6 +1096,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
let productionMapRendered = false;
|
||||
let cultivationMapRendered = false;
|
||||
let actualProductivityRendered = false;
|
||||
const mapInformationRequestIds = { production: 0, cultivation: 0 };
|
||||
|
||||
function hasData(data) {
|
||||
return data && Array.isArray(data.series) && data.series.some(function (value) {
|
||||
@@ -950,6 +1114,130 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
}
|
||||
|
||||
function renderProductionMapInformation(elementId, information, includeOpt = true) {
|
||||
const element = document.getElementById(elementId);
|
||||
|
||||
if (!element) return;
|
||||
|
||||
const commodityRows = Array.isArray(information.petani_per_komoditas)
|
||||
? information.petani_per_komoditas
|
||||
: [];
|
||||
const optRows = Array.isArray(information.opt) ? information.opt : [];
|
||||
const indexValue = information.indeks === null || information.indeks === undefined
|
||||
? '-'
|
||||
: `${formatNumber(information.indeks, 2)} / 5`;
|
||||
const renderBars = function (rows, emptyLabel, unitLabel) {
|
||||
if (!rows.length) {
|
||||
return `<div class="produksi-map-information-empty">${emptyLabel}</div>`;
|
||||
}
|
||||
|
||||
const maxValue = Math.max(...rows.map(row => Number(row.jumlah || 0)), 1);
|
||||
|
||||
return rows.map(function (row) {
|
||||
const value = Number(row.jumlah || 0);
|
||||
const width = Math.max(value > 0 ? 3 : 0, (value / maxValue) * 100);
|
||||
|
||||
return `
|
||||
<div>
|
||||
<div class="produksi-map-information-bar-label">
|
||||
<span>${escapeHtml(row.nama || 'Belum ditentukan')}</span>
|
||||
<strong>${formatNumber(value, 0)} ${unitLabel}</strong>
|
||||
</div>
|
||||
<div class="produksi-map-information-track" aria-hidden="true">
|
||||
<div class="produksi-map-information-fill" style="width: ${width}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
};
|
||||
|
||||
element.innerHTML = `
|
||||
<div>
|
||||
<div class="produksi-map-information-summary">
|
||||
<span class="produksi-map-information-summary-icon"><i class="icon-base ti tabler-users"></i></span>
|
||||
<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>
|
||||
</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">
|
||||
<span class="produksi-map-information-summary-icon"><i class="icon-base ti tabler-map-2"></i></span>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="produksi-map-information-metrics">
|
||||
<div class="produksi-map-information-metric">
|
||||
<span class="produksi-map-information-metric-label">Indeks</span>
|
||||
<strong class="produksi-map-information-metric-value">${indexValue}</strong>
|
||||
</div>
|
||||
<div class="produksi-map-information-metric">
|
||||
<span class="produksi-map-information-metric-label">Segmen</span>
|
||||
<strong class="produksi-map-information-metric-value">${escapeHtml(information.segmen || 'Belum Lengkap')}</strong>
|
||||
</div>
|
||||
<div class="produksi-map-information-metric">
|
||||
<span class="produksi-map-information-metric-label">Segmen Dominan</span>
|
||||
<strong class="produksi-map-information-metric-value">${escapeHtml(information.segmen_dominan || 'Belum Lengkap')}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<section>
|
||||
<span class="produksi-map-information-section-title">Jumlah Petani / Komoditas</span>
|
||||
<div class="produksi-map-information-bars">${renderBars(commodityRows, 'Belum ada data komoditas.', 'petani')}</div>
|
||||
</section>
|
||||
${includeOpt ? `
|
||||
<section>
|
||||
<span class="produksi-map-information-section-title">OPT (Organisme Pengganggu Tanaman)</span>
|
||||
<div class="produksi-map-information-bars">${renderBars(optRows, 'Belum ada data OPT.', 'lahan')}</div>
|
||||
</section>
|
||||
` : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
async function loadProductionMapInformation(mapType, level = null, id = null) {
|
||||
const requestId = ++mapInformationRequestIds[mapType];
|
||||
const params = new URLSearchParams();
|
||||
|
||||
Object.entries(dashboardFilters || {}).forEach(function ([key, value]) {
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
params.set(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
if (level && id !== null && id !== undefined) {
|
||||
params.set('level', level);
|
||||
params.set('id', id);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${mapInformationUrl}?${params.toString()}`, {
|
||||
headers: { Accept: 'application/json' },
|
||||
});
|
||||
|
||||
if (!response.ok) return;
|
||||
|
||||
const information = await response.json();
|
||||
if (requestId !== mapInformationRequestIds[mapType]) return;
|
||||
|
||||
Object.assign(mapInformation[mapType], information);
|
||||
if (mapType === 'production') {
|
||||
renderProductionMapInformation('produksi-map-information', mapInformation.production);
|
||||
} else {
|
||||
renderProductionMapInformation('produksi-budidaya-map-information', mapInformation.cultivation, false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Gagal memuat informasi wilayah pada peta.', error);
|
||||
}
|
||||
}
|
||||
|
||||
function toGroupedBarData(data, seriesName) {
|
||||
if (!data || !Array.isArray(data.labels) || !Array.isArray(data.series)) {
|
||||
return data;
|
||||
@@ -1172,10 +1460,23 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
empty: null,
|
||||
group: null,
|
||||
history: [],
|
||||
informationId: null,
|
||||
informationLevel: null,
|
||||
layers: {},
|
||||
map: null,
|
||||
};
|
||||
|
||||
function enableMapInformationInteraction(mapElement) {
|
||||
const informationOverlay = mapElement.querySelector('.produksi-map-information-overlay');
|
||||
if (!informationOverlay) return;
|
||||
|
||||
L.DomEvent.disableScrollPropagation(informationOverlay);
|
||||
L.DomEvent.disableClickPropagation(informationOverlay);
|
||||
informationOverlay.addEventListener('click', function () {
|
||||
informationOverlay.focus({ preventScroll: true });
|
||||
});
|
||||
}
|
||||
|
||||
function initProductionMap() {
|
||||
const element = document.getElementById('produksi-sebaran-map');
|
||||
|
||||
@@ -1201,6 +1502,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
productionMapState.group = L.featureGroup().addTo(productionMapState.map);
|
||||
productionMapState.backButton = document.getElementById('produksi-map-back');
|
||||
productionMapState.empty = document.getElementById('produksi-map-empty');
|
||||
enableMapInformationInteraction(element);
|
||||
|
||||
L.control.zoom({ position: 'topleft' }).addTo(productionMapState.map);
|
||||
|
||||
@@ -1209,6 +1511,9 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
if (previous) {
|
||||
renderProductionMapLevel(previous.level, previous.parentId);
|
||||
productionMapState.informationLevel = previous.informationLevel;
|
||||
productionMapState.informationId = previous.informationId;
|
||||
loadProductionMapInformation('production', previous.informationLevel, previous.informationId);
|
||||
|
||||
if (previous.bounds?.isValid()) {
|
||||
productionMapState.map.fitBounds(previous.bounds, { padding: [30, 30] });
|
||||
@@ -1296,7 +1601,12 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
level: nextLevel,
|
||||
parentId,
|
||||
bounds: productionMapState.group.getBounds(),
|
||||
informationLevel: productionMapState.informationLevel,
|
||||
informationId: productionMapState.informationId,
|
||||
});
|
||||
productionMapState.informationLevel = nextLevel;
|
||||
productionMapState.informationId = item.id;
|
||||
loadProductionMapInformation('production', nextLevel, item.id);
|
||||
renderProductionMapLevel(nextProductionMapLevel(nextLevel), item.id, event.target.getBounds());
|
||||
});
|
||||
}
|
||||
@@ -1389,6 +1699,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
empty: null,
|
||||
group: null,
|
||||
history: [],
|
||||
informationId: null,
|
||||
informationLevel: null,
|
||||
layers: {},
|
||||
map: null,
|
||||
};
|
||||
@@ -1418,6 +1730,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
cultivationMapState.group = L.featureGroup().addTo(cultivationMapState.map);
|
||||
cultivationMapState.backButton = document.getElementById('produksi-budidaya-map-back');
|
||||
cultivationMapState.empty = document.getElementById('produksi-budidaya-map-empty');
|
||||
enableMapInformationInteraction(element);
|
||||
|
||||
L.control.zoom({ position: 'topleft' }).addTo(cultivationMapState.map);
|
||||
|
||||
@@ -1426,6 +1739,9 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
if (previous) {
|
||||
renderCultivationMapLevel(previous.level, previous.parentId);
|
||||
cultivationMapState.informationLevel = previous.informationLevel;
|
||||
cultivationMapState.informationId = previous.informationId;
|
||||
loadProductionMapInformation('cultivation', previous.informationLevel, previous.informationId);
|
||||
|
||||
if (previous.bounds?.isValid()) {
|
||||
cultivationMapState.map.fitBounds(previous.bounds, { padding: [30, 30] });
|
||||
@@ -1513,7 +1829,12 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
level: nextLevel,
|
||||
parentId,
|
||||
bounds: cultivationMapState.group.getBounds(),
|
||||
informationLevel: cultivationMapState.informationLevel,
|
||||
informationId: cultivationMapState.informationId,
|
||||
});
|
||||
cultivationMapState.informationLevel = nextLevel;
|
||||
cultivationMapState.informationId = item.id;
|
||||
loadProductionMapInformation('cultivation', nextLevel, item.id);
|
||||
renderCultivationMapLevel(nextProductionMapLevel(nextLevel), item.id, event.target.getBounds());
|
||||
});
|
||||
}
|
||||
@@ -1955,6 +2276,9 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
cultivationMapRendered = true;
|
||||
}
|
||||
|
||||
renderProductionMapInformation('produksi-map-information', mapInformation.production);
|
||||
renderProductionMapInformation('produksi-budidaya-map-information', mapInformation.cultivation, false);
|
||||
|
||||
if (typeof L === 'undefined') {
|
||||
const mapEmpty = document.getElementById('produksi-map-empty');
|
||||
const cultivationMapEmpty = document.getElementById('produksi-budidaya-map-empty');
|
||||
@@ -2041,9 +2365,10 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const control = document.querySelector('.chart-group-header-toggle[aria-controls="' + panel.id + '"]');
|
||||
if (!control) return;
|
||||
const icon = control.querySelector('.chart-group-icon-toggle i');
|
||||
const groupLabel = panel.id === 'peta-sebaran-panel' ? 'peta' : 'chart';
|
||||
const syncState = function () {
|
||||
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-label', label);
|
||||
control.title = label;
|
||||
|
||||
@@ -41,6 +41,7 @@ Route::middleware('auth')
|
||||
|
||||
// Home
|
||||
|
||||
Route::get('/dashboard/produktivitas/map-information', [App\Http\Controllers\Web\Dashboard\SummaryController::class, 'mapInformation'])->name('dashboard.produktivitas.map-information');
|
||||
Route::get('/dashboard/produktivitas', [App\Http\Controllers\Web\Dashboard\SummaryController::class, 'produktivitas'])->name('dashboard.produktivitas');
|
||||
Route::get('/dashboard/pasar', [App\Http\Controllers\Web\Dashboard\SummaryController::class, 'pasar'])->name('dashboard.pasar');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user