From 6830142a824c42c1ee5a69d19777371cdac87b84 Mon Sep 17 00:00:00 2001
From: SyaharaniSalsabilla
Date: Thu, 13 Aug 2026 16:07:55 +0700
Subject: [PATCH] solve dashboard indikator pertanian
---
.../IndikatorPertanianBackupController.php | 769 ++++++++++++++++++
.../IndikatorPertanianController.php | 115 ++-
.../dashboard/indikator-pertanian.blade.php | 368 +++++++--
routes/web.php | 1 +
4 files changed, 1120 insertions(+), 133 deletions(-)
create mode 100644 app/Http/Controllers/Web/Dashboard/IndikatorPertanianBackupController.php
diff --git a/app/Http/Controllers/Web/Dashboard/IndikatorPertanianBackupController.php b/app/Http/Controllers/Web/Dashboard/IndikatorPertanianBackupController.php
new file mode 100644
index 0000000..ff39f00
--- /dev/null
+++ b/app/Http/Controllers/Web/Dashboard/IndikatorPertanianBackupController.php
@@ -0,0 +1,769 @@
+kabupatenOptions();
+ $ngadaOption = collect($kabupatenOptions)->first(
+ fn ($option) => str_contains(strtolower($option['label']), 'ngada')
+ );
+ $initialFilters = $this->selectedFilters($request, $ngadaOption['value'] ?? 'all');
+
+ $totalPetani = $this->applyPetaniFilters(Petani::query(), $initialFilters)->count();
+ $totalLuasLahan = ((float) $this->applyLahanFilters(Lahan::query(), $initialFilters)->sum('luas_lahan')) / 10000;
+ $rataRataLuasLahan = $totalPetani > 0
+ ? $totalLuasLahan / $totalPetani
+ : 0;
+ $totalProduksiKg = $this->totalProduksiKg($initialFilters);
+ $rataRataProduksiKg = $totalPetani > 0
+ ? $totalProduksiKg / $totalPetani
+ : 0;
+ $rataRataProduktivitas = $totalLuasLahan > 0
+ ? $totalProduksiKg / $totalLuasLahan
+ : 0;
+
+ $filterOptions = [
+ [
+ 'key' => 'kabupaten',
+ 'label' => 'Kabupaten',
+ 'options' => $kabupatenOptions,
+ 'icon' => 'tabler-map-pin',
+ 'readonly' => true,
+ ],
+ [
+ 'key' => 'kecamatan',
+ 'label' => 'Kecamatan',
+ 'options' => $this->kecamatanOptions(),
+ ],
+ [
+ 'key' => 'desa',
+ 'label' => 'Desa',
+ 'options' => $this->desaOptions(),
+ ],
+ [
+ 'key' => 'komoditas',
+ 'label' => 'Komoditas',
+ 'options' => $this->komoditasOptions(),
+ ],
+ [
+ 'key' => 'segmentasi',
+ 'label' => 'Segmentasi',
+ 'options' => $this->segmentasiOptions(),
+ ],
+ ];
+
+ $summaryData = [
+ [
+ 'value' => number_format($totalPetani, 0, ',', '.'),
+ 'label' => 'Petani Terdata',
+ 'description' => 'Total petani yang masuk sistem',
+ 'icon' => 'tabler-users',
+ 'variant' => 'primary',
+ ],
+ [
+ 'value' => number_format($totalLuasLahan, 2, ',', '.').' ha',
+ 'label' => 'Total Luas Lahan',
+ 'description' => 'Rata-rata '.number_format($rataRataLuasLahan, 2, ',', '.').' ha/petani',
+ 'icon' => 'tabler-mountain',
+ 'variant' => 'success',
+ ],
+ [
+ 'value' => number_format($totalProduksiKg, 0, ',', '.').' kg',
+ 'label' => 'Total Produksi (Semua Komoditas)',
+ 'description' => 'Rata-rata '.number_format($rataRataProduksiKg, 0, ',', '.').' kg/petani',
+ 'icon' => 'tabler-wheat',
+ 'variant' => 'warning',
+ ],
+ [
+ 'value' => number_format($rataRataProduktivitas, 0, ',', '.').' kg/ha',
+ 'label' => 'Rata-rata Produktivitas',
+ 'description' => 'Semua komoditas berdasarkan total produksi dan luas lahan',
+ 'icon' => 'tabler-chart-bar',
+ 'variant' => 'info',
+ ],
+ ];
+
+ return view('dashboard.indikator-pertanian', [
+ 'title' => 'Indikator Pertanian',
+ 'initialFilters' => $initialFilters,
+ 'filterOptions' => $filterOptions,
+ 'summaryData' => $summaryData,
+ 'chartData' => [
+ 'petaniPerKomoditas' => $this->petaniPerKomoditasChart($initialFilters),
+ 'luasLahanPerKecamatan' => $this->luasLahanPerKecamatanChart($initialFilters),
+ 'kondisiTanaman' => $this->kondisiTanamanChart($initialFilters),
+ 'statusKepemilikanLahan' => $this->statusKepemilikanLahanChart($initialFilters),
+ 'seranganHama' => $this->seranganHamaChart($initialFilters),
+ 'penyakitTanaman' => $this->penyakitTanamanChart($initialFilters),
+ 'keparahanSerangan' => $this->keparahanSeranganChart($initialFilters),
+ 'statusHakKelolaLahan' => $this->statusHakKelolaLahanChart($initialFilters),
+ 'praktikPemupukan' => $this->praktikPemupukanChart($initialFilters),
+ 'produksiPerKomoditas' => $this->produksiPerKomoditasChart($initialFilters),
+ 'produktivitasPerKomoditas' => $this->produktivitasPerKomoditasChart($initialFilters),
+ 'luasPanenPerKomoditas' => $this->luasPanenPerKomoditasChart($initialFilters),
+ 'statusPanen' => $this->statusPanenChart($initialFilters),
+ 'surveyProduktivitasHa' => $this->surveyProduktivitasByUnitChart($initialFilters, 'ha_tahun'),
+ 'surveyProduktivitasPohon' => $this->surveyProduktivitasByUnitChart($initialFilters, 'pohon_tahun'),
+ ],
+ ]);
+ }
+
+ private function selectedFilters(Request $request, string $defaultKabupaten): array
+ {
+ $filters = [
+ 'kabupaten' => $defaultKabupaten,
+ 'kecamatan' => $this->validFilterValue($request->query('kecamatan'), Kecamatan::class),
+ 'desa' => $this->validFilterValue($request->query('desa'), DesaKelurahan::class),
+ 'komoditas' => $this->validFilterValue($request->query('komoditas'), Komoditas::class),
+ 'segmentasi' => $this->validSegmentasi((string) $request->query('segmentasi', 'all')),
+ ];
+
+ if ($filters['desa'] !== 'all') {
+ $desa = DesaKelurahan::query()->find($filters['desa']);
+
+ if (! $desa || ($filters['kecamatan'] !== 'all' && (string) $desa->kecamatan_id !== $filters['kecamatan'])) {
+ $filters['desa'] = 'all';
+ }
+ }
+
+ if ($filters['kecamatan'] === 'all') {
+ $filters['desa'] = 'all';
+ }
+
+ return $filters;
+ }
+
+ private function validFilterValue(mixed $value, string $modelClass): string
+ {
+ if (! $value || $value === 'all') {
+ return 'all';
+ }
+
+ return $modelClass::query()->whereKey($value)->exists()
+ ? (string) $value
+ : 'all';
+ }
+
+ private function validSegmentasi(string $value): string
+ {
+ return in_array($value, [
+ 'Penggerak',
+ 'Perintis',
+ 'Pemerhati',
+ 'Pemula',
+ 'Belum tersegmentasi',
+ 'all',
+ ], true) ? $value : 'all';
+ }
+
+ private function kabupatenOptions(): array
+ {
+ return $this->withAllOption(
+ 'Semua Kabupaten',
+ KabupatenKota::query()
+ ->orderBy('nama')
+ ->get(['id', 'kode', 'nama'])
+ ->map(fn (KabupatenKota $kabupaten) => [
+ 'value' => (string) $kabupaten->id,
+ 'label' => $this->kabupatenLabel($kabupaten),
+ ])
+ ->values()
+ ->all()
+ );
+ }
+
+ private function kecamatanOptions(): array
+ {
+ return $this->withAllOption(
+ 'Semua Kecamatan',
+ Kecamatan::query()
+ ->orderBy('nama')
+ ->get(['id', 'nama'])
+ ->map(fn (Kecamatan $kecamatan) => [
+ 'value' => (string) $kecamatan->id,
+ 'label' => $kecamatan->nama,
+ ])
+ ->values()
+ ->all()
+ );
+ }
+
+ private function desaOptions(): array
+ {
+ return $this->withAllOption(
+ 'Semua Desa',
+ DesaKelurahan::query()
+ ->orderBy('nama')
+ ->get(['id', 'nama', 'kecamatan_id'])
+ ->map(fn (DesaKelurahan $desa) => [
+ 'value' => (string) $desa->id,
+ 'label' => $desa->nama,
+ 'parent' => (string) $desa->kecamatan_id,
+ ])
+ ->values()
+ ->all()
+ );
+ }
+
+ private function komoditasOptions(): array
+ {
+ return $this->withAllOption(
+ 'Semua Komoditas',
+ Komoditas::query()
+ ->orderBy('nama')
+ ->get(['id', 'nama'])
+ ->map(fn (Komoditas $komoditas) => [
+ 'value' => (string) $komoditas->id,
+ 'label' => $komoditas->nama,
+ ])
+ ->values()
+ ->all()
+ );
+ }
+
+ private function segmentasiOptions(): array
+ {
+ // Aggregate bucket counts in SQL instead of pulling every skor_kesiapan_adopsi
+ // value into PHP just to map/filter/unique it.
+ $row = SkorPetani::query()->selectRaw("
+ COUNT(*) FILTER (WHERE skor_kesiapan_adopsi >= 4) as penggerak,
+ COUNT(*) FILTER (WHERE skor_kesiapan_adopsi >= 3 AND skor_kesiapan_adopsi < 4) as perintis,
+ COUNT(*) FILTER (WHERE skor_kesiapan_adopsi >= 2 AND skor_kesiapan_adopsi < 3) as pemerhati,
+ COUNT(*) FILTER (WHERE skor_kesiapan_adopsi >= 1 AND skor_kesiapan_adopsi < 2) as pemula,
+ COUNT(*) FILTER (WHERE skor_kesiapan_adopsi IS NOT NULL AND skor_kesiapan_adopsi < 1) as belum_tersegmentasi
+ ")->first();
+
+ $order = [
+ 'Penggerak' => $row->penggerak,
+ 'Perintis' => $row->perintis,
+ 'Pemerhati' => $row->pemerhati,
+ 'Pemula' => $row->pemula,
+ 'Belum tersegmentasi' => $row->belum_tersegmentasi,
+ ];
+
+ $segments = collect($order)
+ ->filter(fn ($count) => $count > 0)
+ ->keys()
+ ->map(fn ($segment) => [
+ 'value' => $segment,
+ 'label' => $segment,
+ ])
+ ->values()
+ ->all();
+
+ return $this->withAllOption('Semua Segmentasi', $segments);
+ }
+
+ private function totalProduksiKg(array $filters): float
+ {
+ $expr = $this->kgCaseExpr('jumlah_panen', 'satuan');
+
+ return (float) ($this->applyProduksiFilters(Produksi::query(), $filters)
+ ->whereNotNull('jumlah_panen')
+ ->selectRaw("SUM({$expr}) as total")
+ ->value('total') ?? 0);
+ }
+
+ private function petaniPerKomoditasChart(array $filters): array
+ {
+ $rows = $this->applyPetaniFilters(Petani::query(), $filters)
+ ->leftJoin('master_komoditas as k', 'k.id', '=', 'petani.komoditas_id')
+ ->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
+ ->selectRaw('COUNT(petani.id) as total')
+ ->groupBy('k.id', 'k.nama')
+ ->orderByDesc('total')
+ ->get();
+
+ return [
+ 'labels' => $rows->pluck('label')->values()->all(),
+ 'series' => $rows->pluck('total')->map(fn ($value) => (int) $value)->values()->all(),
+ ];
+ }
+
+ private function luasLahanPerKecamatanChart(array $filters): array
+ {
+ $rows = $this->applyLahanFilters(Lahan::query(), $filters)
+ ->leftJoin('master_desa_kelurahan as d', 'd.id', '=', 'map_lahan.desa_kelurahan_id')
+ ->leftJoin('master_kecamatan as k', 'k.id', '=', 'd.kecamatan_id')
+ ->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
+ ->selectRaw('SUM(map_lahan.luas_lahan) / 10000 as total')
+ ->groupBy('k.id', 'k.nama')
+ ->havingRaw('SUM(map_lahan.luas_lahan) > 0')
+ ->orderByDesc('total')
+ ->limit(8)
+ ->get()
+ ->filter(fn ($row) => round((float) $row->total, 2) > 0)
+ ->values();
+
+ return [
+ 'labels' => $rows->pluck('label')->values()->all(),
+ 'series' => $rows->pluck('total')->map(fn ($value) => round((float) $value, 2))->values()->all(),
+ ];
+ }
+
+ private function kondisiTanamanChart(array $filters): array
+ {
+ $rows = $this->applyTanamanTableFilters(DB::table('map_tanaman'), $filters)
+ ->whereNull('map_tanaman.deleted_at')
+ ->whereNotNull('map_tanaman.kondisi_tanaman')
+ ->selectRaw('map_tanaman.kondisi_tanaman as label')
+ ->selectRaw('COUNT(map_tanaman.id) as total')
+ ->groupBy('map_tanaman.kondisi_tanaman')
+ ->orderByDesc('total')
+ ->get();
+
+ if ($rows->isEmpty()) {
+ $rows = $this->applyLahanFilters(Lahan::query(), $filters)
+ ->whereNull('deleted_at')
+ ->whereNotNull('kondisi_tanaman')
+ ->selectRaw('kondisi_tanaman as label')
+ ->selectRaw('COUNT(map_lahan.id) as total')
+ ->groupBy('kondisi_tanaman')
+ ->orderByDesc('total')
+ ->get();
+ }
+
+ return [
+ 'labels' => $rows->pluck('label')->map(fn ($label) => $this->shortEnumLabel((string) $label))->values()->all(),
+ 'series' => $rows->pluck('total')->map(fn ($value) => (int) $value)->values()->all(),
+ ];
+ }
+
+ private function seranganHamaChart(array $filters): array
+ {
+ return $this->tanamanAttributeChart(
+ $filters,
+ 'serangan_hama',
+ fn ($label) => $this->enumLabel(SeranganHama::class, (string) $label)
+ );
+ }
+
+ private function penyakitTanamanChart(array $filters): array
+ {
+ return $this->tanamanAttributeChart(
+ $filters,
+ 'penyakit_tanaman',
+ fn ($label) => $this->enumLabel(PenyakitTanaman::class, (string) $label)
+ );
+ }
+
+ private function keparahanSeranganChart(array $filters): array
+ {
+ return $this->tanamanAttributeChart(
+ $filters,
+ 'keparahan_serangan',
+ fn ($label) => $this->keparahanSeranganLabel((string) $label)
+ );
+ }
+
+ private function statusKepemilikanLahanChart(array $filters): array
+ {
+ $rows = $this->applyLahanFilters(Lahan::query(), $filters)
+ ->whereNotNull('status_kepemilikan')
+ ->selectRaw('status_kepemilikan as label')
+ ->selectRaw('COUNT(map_lahan.id) as total')
+ ->groupBy('status_kepemilikan')
+ ->orderByDesc('total')
+ ->get();
+
+ return $this->chartFromRows($rows, fn ($label) => $this->shortEnumLabel((string) $label));
+ }
+
+ private function statusHakKelolaLahanChart(array $filters): array
+ {
+ $rows = $this->applySosialEkonomiFilters(SosialEkonomi::query(), $filters)
+ ->whereNotNull('status_kepemilikan_lahan')
+ ->selectRaw('status_kepemilikan_lahan as label')
+ ->selectRaw('COUNT(sosial_ekonomi.id) as total')
+ ->groupBy('status_kepemilikan_lahan')
+ ->orderByDesc('total')
+ ->get();
+
+ return $this->chartFromRows(
+ $rows,
+ fn ($label) => $this->enumLabel(StatusKepemilikanLahanSosialEkonomi::class, (string) $label)
+ );
+ }
+
+ private function praktikPemupukanChart(array $filters): array
+ {
+ $rows = $this->applyPraktikBudidayaFilters(PraktikBudidaya::query(), $filters)
+ ->whereNotNull('pemberian_pupuk')
+ ->selectRaw('pemberian_pupuk as label')
+ ->selectRaw('COUNT(praktik_budidaya.id) as total')
+ ->groupBy('pemberian_pupuk')
+ ->orderBy('pemberian_pupuk')
+ ->get();
+
+ return $this->chartFromRows(
+ $rows,
+ fn ($label) => $this->pemberianPupukLabel((int) $label)
+ );
+ }
+
+ private function produksiPerKomoditasChart(array $filters): array
+ {
+ $expr = $this->kgCaseExpr('produksi.jumlah_panen', 'produksi.satuan');
+
+ $rows = $this->applyProduksiFilters(Produksi::query(), $filters)
+ ->leftJoin('master_komoditas as k', 'k.id', '=', 'produksi.komoditas_id')
+ ->whereNotNull('jumlah_panen')
+ ->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
+ ->selectRaw("SUM({$expr}) as total")
+ ->groupBy('k.id', 'k.nama')
+ ->get()
+ ->filter(fn ($row) => round((float) $row->total, 2) > 0)
+ ->sortByDesc('total')
+ ->values();
+
+ return [
+ 'labels' => $rows->pluck('label')->values()->all(),
+ 'series' => $rows->pluck('total')->map(fn ($value) => round((float) $value, 2))->values()->all(),
+ ];
+ }
+
+ private function produktivitasPerKomoditasChart(array $filters): array
+ {
+ $expr = $this->kgCaseExpr('produksi.jumlah_panen', 'produksi.satuan');
+
+ $rows = $this->applyProduksiFilters(Produksi::query(), $filters)
+ ->leftJoin('master_komoditas as k', 'k.id', '=', 'produksi.komoditas_id')
+ ->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
+ ->selectRaw('AVG(produksi.produktivitas) FILTER (WHERE produksi.produktivitas > 0) as avg_prod')
+ ->selectRaw("SUM({$expr}) as total_kg")
+ ->selectRaw('SUM(produksi.luas_panen) as total_luas')
+ ->groupBy('k.id', 'k.nama')
+ ->get()
+ ->map(function ($row) {
+ $avgProd = (float) $row->avg_prod;
+ $totalLuas = (float) $row->total_luas;
+ $totalKg = (float) $row->total_kg;
+
+ $value = $avgProd > 0
+ ? $avgProd
+ : ($totalLuas > 0 ? $totalKg / $totalLuas : 0);
+
+ return (object) ['label' => $row->label, 'total' => round($value, 2)];
+ })
+ ->filter(fn ($row) => $row->total > 0)
+ ->sortByDesc('total')
+ ->values();
+
+ return [
+ 'labels' => $rows->pluck('label')->values()->all(),
+ 'series' => $rows->pluck('total')->values()->all(),
+ ];
+ }
+
+ private function luasPanenPerKomoditasChart(array $filters): array
+ {
+ $rows = $this->applyProduksiFilters(Produksi::query(), $filters)
+ ->leftJoin('master_komoditas as k', 'k.id', '=', 'produksi.komoditas_id')
+ ->whereNotNull('luas_panen')
+ ->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
+ ->selectRaw('SUM(produksi.luas_panen) as total')
+ ->groupBy('k.id', 'k.nama')
+ ->havingRaw('SUM(produksi.luas_panen) > 0')
+ ->orderByDesc('total')
+ ->get();
+
+ return [
+ 'labels' => $rows->pluck('label')->values()->all(),
+ 'series' => $rows->pluck('total')->map(fn ($value) => round((float) $value, 2))->values()->all(),
+ ];
+ }
+
+ private function statusPanenChart(array $filters): array
+ {
+ $rows = $this->applyProduksiFilters(Produksi::query(), $filters)
+ ->whereNotNull('status_panen')
+ ->selectRaw("COALESCE(status_panen, 'Belum ditentukan') as label")
+ ->selectRaw('COUNT(produksi.id) as total')
+ ->groupBy('status_panen')
+ ->orderByDesc('total')
+ ->get();
+
+ return $this->chartFromRows($rows, fn ($label) => $this->shortEnumLabel((string) $label));
+ }
+
+ private function surveyProduktivitasByUnitChart(array $filters, string $unit): array
+ {
+ $rows = $this->applySurveyProduktivitasFilters(SurveyProduktivitas::query(), $filters)
+ ->leftJoin('master_komoditas as k', 'k.id', '=', 'survey_produktivitas.komoditas_id')
+ ->where('satuan', $unit)
+ ->whereNotNull('kuantitas')
+ ->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
+ ->selectRaw('AVG(survey_produktivitas.kuantitas) as total')
+ ->groupBy('k.id', 'k.nama')
+ ->havingRaw('AVG(survey_produktivitas.kuantitas) > 0')
+ ->orderByDesc('total')
+ ->get();
+
+ return [
+ 'labels' => $rows->pluck('label')->values()->all(),
+ 'series' => $rows->pluck('total')->map(fn ($value) => round((float) $value, 2))->values()->all(),
+ ];
+ }
+
+ private function chartFromRows($rows, callable $labelResolver): array
+ {
+ return [
+ 'labels' => $rows
+ ->pluck('label')
+ ->map(fn ($label) => $labelResolver($label))
+ ->values()
+ ->all(),
+ 'series' => $rows
+ ->pluck('total')
+ ->map(fn ($value) => (int) $value)
+ ->values()
+ ->all(),
+ ];
+ }
+
+ private function tanamanAttributeChart(array $filters, string $field, callable $labelResolver): array
+ {
+ $qualifiedField = 'map_tanaman.'.$field;
+
+ $rows = $this->applyTanamanTableFilters(DB::table('map_tanaman'), $filters)
+ ->whereNull('map_tanaman.deleted_at')
+ ->whereNotNull($qualifiedField)
+ ->selectRaw($qualifiedField.' as label')
+ ->selectRaw('COUNT(map_tanaman.id) as total')
+ ->groupBy($qualifiedField)
+ ->orderByDesc('total')
+ ->get();
+
+ if ($rows->isEmpty()) {
+ $rows = $this->applyLahanFilters(Lahan::query(), $filters)
+ ->whereNotNull($field)
+ ->selectRaw($field.' as label')
+ ->selectRaw('COUNT(map_lahan.id) as total')
+ ->groupBy($field)
+ ->orderByDesc('total')
+ ->get();
+ }
+
+ return $this->chartFromRows($rows, $labelResolver);
+ }
+
+ private function applyPetaniFilters($query, array $filters)
+ {
+ return $query
+ ->when($filters['kecamatan'] !== 'all', function ($query) use ($filters) {
+ $query->whereHas('desaKelurahan', fn ($desa) => $desa->where('kecamatan_id', $filters['kecamatan']));
+ })
+ ->when($filters['desa'] !== 'all', fn ($query) => $query->where('desa_kelurahan_id', $filters['desa']))
+ ->when($filters['komoditas'] !== 'all', fn ($query) => $query->where('komoditas_id', $filters['komoditas']))
+ ->when($filters['segmentasi'] !== 'all', function ($query) use ($filters) {
+ $query->whereHas('skor', function ($skor) use ($filters) {
+ $this->applySegmentasiRange($skor, $filters['segmentasi']);
+ });
+ });
+ }
+
+ private function applyLahanFilters($query, array $filters)
+ {
+ return $query
+ ->when($filters['kecamatan'] !== 'all', function ($query) use ($filters) {
+ $query->whereHas('desaKelurahan', fn ($desa) => $desa->where('kecamatan_id', $filters['kecamatan']));
+ })
+ ->when($filters['desa'] !== 'all', fn ($query) => $query->where('desa_kelurahan_id', $filters['desa']))
+ ->when($filters['komoditas'] !== 'all', fn ($query) => $query->where('komoditas_id', $filters['komoditas']))
+ ->when($filters['segmentasi'] !== 'all', function ($query) use ($filters) {
+ $query->whereIn('petani_id', $this->segmentasiPetaniIdsQuery($filters['segmentasi']));
+ });
+ }
+
+ private function applyProduksiFilters($query, array $filters)
+ {
+ return $query
+ ->when($filters['kecamatan'] !== 'all' || $filters['desa'] !== 'all' || $filters['segmentasi'] !== 'all', function ($query) use ($filters) {
+ $query->whereHas('lahan', function ($lahan) use ($filters) {
+ $this->applyLahanFilters($lahan, $filters);
+ });
+ })
+ ->when($filters['komoditas'] !== 'all', fn ($query) => $query->where('komoditas_id', $filters['komoditas']));
+ }
+
+ private function applySosialEkonomiFilters($query, array $filters)
+ {
+ return $query->whereHas('petani', function ($petani) use ($filters) {
+ $this->applyPetaniFilters($petani, $filters);
+ });
+ }
+
+ private function applyPraktikBudidayaFilters($query, array $filters)
+ {
+ return $query->whereHas('petani', function ($petani) use ($filters) {
+ $this->applyPetaniFilters($petani, $filters);
+ });
+ }
+
+ private function applySurveyProduktivitasFilters($query, array $filters)
+ {
+ return $query
+ ->whereHas('petani', function ($petani) use ($filters) {
+ $this->applyPetaniFilters($petani, $filters);
+ })
+ ->when($filters['komoditas'] !== 'all', fn ($query) => $query->where('komoditas_id', $filters['komoditas']));
+ }
+
+ private function applyTanamanTableFilters($query, array $filters)
+ {
+ return $query
+ ->leftJoin('map_lahan as l', 'l.id', '=', 'map_tanaman.lahan_id')
+ ->when($filters['kecamatan'] !== 'all', function ($query) use ($filters) {
+ $query
+ ->leftJoin('master_desa_kelurahan as d', 'd.id', '=', 'l.desa_kelurahan_id')
+ ->where('d.kecamatan_id', $filters['kecamatan']);
+ })
+ ->when($filters['desa'] !== 'all', fn ($query) => $query->where('l.desa_kelurahan_id', $filters['desa']))
+ ->when($filters['komoditas'] !== 'all', fn ($query) => $query->where('map_tanaman.komoditas_id', $filters['komoditas']))
+ ->when($filters['segmentasi'] !== 'all', function ($query) use ($filters) {
+ $query->where(function ($query) use ($filters) {
+ $query
+ ->whereIn('map_tanaman.petani_id', $this->segmentasiPetaniIdsQuery($filters['segmentasi']))
+ ->orWhereIn('l.petani_id', $this->segmentasiPetaniIdsQuery($filters['segmentasi']));
+ });
+ });
+ }
+
+ private function segmentasiPetaniIdsQuery(string $segmentasi)
+ {
+ return Petani::query()
+ ->select('id')
+ ->whereHas('skor', function ($skor) use ($segmentasi) {
+ $this->applySegmentasiRange($skor, $segmentasi);
+ });
+ }
+
+ private function applySegmentasiRange($query, string $segmentasi)
+ {
+ return match ($segmentasi) {
+ 'Penggerak' => $query->where('skor_kesiapan_adopsi', '>=', 4),
+ 'Perintis' => $query->where('skor_kesiapan_adopsi', '>=', 3)->where('skor_kesiapan_adopsi', '<', 4),
+ 'Pemerhati' => $query->where('skor_kesiapan_adopsi', '>=', 2)->where('skor_kesiapan_adopsi', '<', 3),
+ 'Pemula' => $query->where('skor_kesiapan_adopsi', '>=', 1)->where('skor_kesiapan_adopsi', '<', 2),
+ default => $query->where(function ($query) {
+ $query->whereNull('skor_kesiapan_adopsi')->orWhere('skor_kesiapan_adopsi', '<', 1);
+ }),
+ };
+ }
+
+ /**
+ * SQL CASE expression converting a jumlah/satuan pair to kilograms,
+ * so aggregation happens in the database instead of pulling every
+ * row of `produksi` into PHP to sum with toKilograms().
+ */
+ private function kgCaseExpr(string $jumlahColumn, string $satuanColumn): string
+ {
+ return "CASE LOWER({$satuanColumn})
+ WHEN 'ton' THEN {$jumlahColumn} * 1000
+ WHEN 'kuintal' THEN {$jumlahColumn} * 100
+ WHEN 'kwintal' THEN {$jumlahColumn} * 100
+ ELSE {$jumlahColumn}
+ END";
+ }
+
+ private function shortEnumLabel(string $value): string
+ {
+ return str($value)
+ ->replace('_', ' ')
+ ->headline()
+ ->toString();
+ }
+
+ private function enumLabel(string $enumClass, int|string $value): string
+ {
+ $case = $enumClass::tryFrom($value);
+
+ if (! $case) {
+ return $this->shortEnumLabel((string) $value);
+ }
+
+ return method_exists($case, 'label')
+ ? $case->label()
+ : $this->shortEnumLabel((string) $case->value);
+ }
+
+ private function keparahanSeranganLabel(string $value): string
+ {
+ return match (KeparahanSerangan::tryFrom($value)) {
+ KeparahanSerangan::RINGAN => 'Ringan',
+ KeparahanSerangan::SEDANG => 'Sedang',
+ KeparahanSerangan::BERAT => 'Parah',
+ default => $this->shortEnumLabel($value),
+ };
+ }
+
+ private function pemberianPupukLabel(int $value): string
+ {
+ return match (PemberianPupuk::tryFrom($value)) {
+ PemberianPupuk::TIDAK_PERNAH => 'Tidak pernah',
+ PemberianPupuk::JENIS_DOSIS_WAKTU_TIDAK_MENENTU => 'Tidak menentu',
+ PemberianPupuk::JENIS_SESUAI_DOSIS_TIDAK_MENENTU => 'Jenis sesuai',
+ PemberianPupuk::JENIS_DOSIS_SESUAI_WAKTU_CUKUP_TEPAT => 'Cukup tepat',
+ PemberianPupuk::JENIS_DOSIS_SESUAI_WAKTU_TEPAT => 'Tepat & rutin',
+ default => $this->shortEnumLabel((string) $value),
+ };
+ }
+
+ private function toKilograms(float $value, string $unit): float
+ {
+ return match (strtolower($unit)) {
+ 'ton' => $value * 1000,
+ 'kuintal', 'kwintal' => $value * 100,
+ default => $value,
+ };
+ }
+
+ private function withAllOption(string $label, array $options): array
+ {
+ return array_merge([
+ [
+ 'value' => 'all',
+ 'label' => $label,
+ ],
+ ], $options);
+ }
+
+ private function kabupatenLabel(KabupatenKota $kabupaten): string
+ {
+ return preg_replace('/^(kabupaten|kota)\s+/i', '', $kabupaten->nama);
+ }
+
+ private function segmentLabel(float $score): string
+ {
+ return match (true) {
+ $score >= 4.0 => 'Penggerak',
+ $score >= 3.0 => 'Perintis',
+ $score >= 2.0 => 'Pemerhati',
+ $score >= 1.0 => 'Pemula',
+ default => 'Belum tersegmentasi',
+ };
+ }
+}
diff --git a/app/Http/Controllers/Web/Dashboard/IndikatorPertanianController.php b/app/Http/Controllers/Web/Dashboard/IndikatorPertanianController.php
index 9f4a55c..bb9bfca 100644
--- a/app/Http/Controllers/Web/Dashboard/IndikatorPertanianController.php
+++ b/app/Http/Controllers/Web/Dashboard/IndikatorPertanianController.php
@@ -26,30 +26,14 @@ class IndikatorPertanianController extends Controller
{
public function index(Request $request)
{
- $kabupatenOptions = $this->kabupatenOptions();
- $ngadaOption = collect($kabupatenOptions)->first(
- fn ($option) => str_contains(strtolower($option['label']), 'ngada')
- );
- $initialFilters = $this->selectedFilters($request, $ngadaOption['value'] ?? 'all');
-
- $totalPetani = $this->applyPetaniFilters(Petani::query(), $initialFilters)->count();
- $totalLuasLahan = ((float) $this->applyLahanFilters(Lahan::query(), $initialFilters)->sum('luas_lahan')) / 10000;
- $rataRataLuasLahan = $totalPetani > 0
- ? $totalLuasLahan / $totalPetani
- : 0;
- $totalProduksiKg = $this->totalProduksiKg($initialFilters);
- $rataRataProduksiKg = $totalPetani > 0
- ? $totalProduksiKg / $totalPetani
- : 0;
- $rataRataProduktivitas = $totalLuasLahan > 0
- ? $totalProduksiKg / $totalLuasLahan
- : 0;
+ $initialFilters = $this->selectedFilters($request, $this->defaultKabupatenId());
+ $payload = $this->dashboardPayload($initialFilters);
$filterOptions = [
[
'key' => 'kabupaten',
'label' => 'Kabupaten',
- 'options' => $kabupatenOptions,
+ 'options' => $this->kabupatenOptions(),
'icon' => 'tabler-map-pin',
'readonly' => true,
],
@@ -75,7 +59,55 @@ class IndikatorPertanianController extends Controller
],
];
- $summaryData = [
+ return view('dashboard.indikator-pertanian', [
+ 'title' => 'Indikator Pertanian',
+ 'initialFilters' => $initialFilters,
+ 'filterOptions' => $filterOptions,
+ ] + $payload);
+ }
+
+ public function data(Request $request)
+ {
+ $filters = $this->selectedFilters($request, $this->defaultKabupatenId());
+
+ return response()->json([
+ 'filters' => $filters,
+ ] + $this->dashboardPayload($filters));
+ }
+
+ private function defaultKabupatenId(): string
+ {
+ $ngadaOption = collect($this->kabupatenOptions())->first(
+ fn ($option) => str_contains(strtolower($option['label']), 'ngada')
+ );
+
+ return $ngadaOption['value'] ?? 'all';
+ }
+
+ private function dashboardPayload(array $filters): array
+ {
+ return [
+ 'summaryData' => $this->summaryData($filters),
+ 'chartData' => $this->chartData($filters),
+ ];
+ }
+
+ private function summaryData(array $filters): array
+ {
+ $totalPetani = $this->applyPetaniFilters(Petani::query(), $filters)->count();
+ $totalLuasLahan = ((float) $this->applyLahanFilters(Lahan::query(), $filters)->sum('luas_lahan')) / 10000;
+ $rataRataLuasLahan = $totalPetani > 0
+ ? $totalLuasLahan / $totalPetani
+ : 0;
+ $totalProduksiKg = $this->totalProduksiKg($filters);
+ $rataRataProduksiKg = $totalPetani > 0
+ ? $totalProduksiKg / $totalPetani
+ : 0;
+ $rataRataProduktivitas = $totalLuasLahan > 0
+ ? $totalProduksiKg / $totalLuasLahan
+ : 0;
+
+ return [
[
'value' => number_format($totalPetani, 0, ',', '.'),
'label' => 'Petani Terdata',
@@ -105,30 +137,27 @@ class IndikatorPertanianController extends Controller
'variant' => 'info',
],
];
+ }
- return view('dashboard.indikator-pertanian', [
- 'title' => 'Indikator Pertanian',
- 'initialFilters' => $initialFilters,
- 'filterOptions' => $filterOptions,
- 'summaryData' => $summaryData,
- 'chartData' => [
- 'petaniPerKomoditas' => $this->petaniPerKomoditasChart($initialFilters),
- 'luasLahanPerKecamatan' => $this->luasLahanPerKecamatanChart($initialFilters),
- 'kondisiTanaman' => $this->kondisiTanamanChart($initialFilters),
- 'statusKepemilikanLahan' => $this->statusKepemilikanLahanChart($initialFilters),
- 'seranganHama' => $this->seranganHamaChart($initialFilters),
- 'penyakitTanaman' => $this->penyakitTanamanChart($initialFilters),
- 'keparahanSerangan' => $this->keparahanSeranganChart($initialFilters),
- 'statusHakKelolaLahan' => $this->statusHakKelolaLahanChart($initialFilters),
- 'praktikPemupukan' => $this->praktikPemupukanChart($initialFilters),
- 'produksiPerKomoditas' => $this->produksiPerKomoditasChart($initialFilters),
- 'produktivitasPerKomoditas' => $this->produktivitasPerKomoditasChart($initialFilters),
- 'luasPanenPerKomoditas' => $this->luasPanenPerKomoditasChart($initialFilters),
- 'statusPanen' => $this->statusPanenChart($initialFilters),
- 'surveyProduktivitasHa' => $this->surveyProduktivitasByUnitChart($initialFilters, 'ha_tahun'),
- 'surveyProduktivitasPohon' => $this->surveyProduktivitasByUnitChart($initialFilters, 'pohon_tahun'),
- ],
- ]);
+ private function chartData(array $filters): array
+ {
+ return [
+ 'petaniPerKomoditas' => $this->petaniPerKomoditasChart($filters),
+ 'luasLahanPerKecamatan' => $this->luasLahanPerKecamatanChart($filters),
+ 'kondisiTanaman' => $this->kondisiTanamanChart($filters),
+ 'statusKepemilikanLahan' => $this->statusKepemilikanLahanChart($filters),
+ 'seranganHama' => $this->seranganHamaChart($filters),
+ 'penyakitTanaman' => $this->penyakitTanamanChart($filters),
+ 'keparahanSerangan' => $this->keparahanSeranganChart($filters),
+ 'statusHakKelolaLahan' => $this->statusHakKelolaLahanChart($filters),
+ 'praktikPemupukan' => $this->praktikPemupukanChart($filters),
+ 'produksiPerKomoditas' => $this->produksiPerKomoditasChart($filters),
+ 'produktivitasPerKomoditas' => $this->produktivitasPerKomoditasChart($filters),
+ 'luasPanenPerKomoditas' => $this->luasPanenPerKomoditasChart($filters),
+ 'statusPanen' => $this->statusPanenChart($filters),
+ 'surveyProduktivitasHa' => $this->surveyProduktivitasByUnitChart($filters, 'ha_tahun'),
+ 'surveyProduktivitasPohon' => $this->surveyProduktivitasByUnitChart($filters, 'pohon_tahun'),
+ ];
}
private function selectedFilters(Request $request, string $defaultKabupaten): array
diff --git a/resources/views/dashboard/indikator-pertanian.blade.php b/resources/views/dashboard/indikator-pertanian.blade.php
index 6046602..cc3048c 100644
--- a/resources/views/dashboard/indikator-pertanian.blade.php
+++ b/resources/views/dashboard/indikator-pertanian.blade.php
@@ -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 @@
-
+
@foreach ($filterOptions as $filter)
-
+
@foreach ($summaryData as $summary)
-
+
@@ -308,17 +318,17 @@
-
+
{{ $summary['value'] }}
-
+
{{ $summary['label'] }}
-
+
{{ $summary['description'] }}
@@ -647,11 +657,13 @@
@push('scripts')