1467 lines
57 KiB
PHP
1467 lines
57 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Web\Dashboard;
|
|
|
|
use App\Enums\KeparahanSerangan;
|
|
use App\Enums\PenyakitTanaman;
|
|
use App\Enums\PraktikBudidaya\KondisiPengelolaanAir;
|
|
use App\Enums\PraktikBudidaya\PemberianPupuk;
|
|
use App\Enums\SeranganHama;
|
|
use App\Enums\SosialEkonomi\StatusKepemilikanLahanSosialEkonomi;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Pertanian\Komoditas;
|
|
use App\Models\Pertanian\Lahan;
|
|
use App\Models\Pertanian\Petani;
|
|
use App\Models\Pertanian\PraktikBudidaya;
|
|
use App\Models\Pertanian\Produksi;
|
|
use App\Models\Pertanian\SurveyPanen;
|
|
use App\Models\Pertanian\SkorPetani;
|
|
use App\Models\Pertanian\SosialEkonomi;
|
|
use App\Models\Pertanian\SurveyProduktivitas;
|
|
use App\Models\Wilayah\DesaKelurahan;
|
|
use App\Models\Wilayah\KabupatenKota;
|
|
use App\Models\Wilayah\Kecamatan;
|
|
use App\Models\Wilayah\Provinsi;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class IndikatorPertanianController extends Controller
|
|
{
|
|
public function index(Request $request)
|
|
{
|
|
$defaultKabupatenId = $this->defaultKabupatenId();
|
|
$initialFilters = $this->selectedFilters($request, $defaultKabupatenId);
|
|
$payload = $this->dashboardPayload($initialFilters);
|
|
|
|
$filterOptions = [
|
|
[
|
|
'key' => 'kabupaten',
|
|
'label' => 'Kabupaten',
|
|
'options' => $this->kabupatenOptions(),
|
|
'icon' => 'tabler-map-pin',
|
|
'readonly' => true,
|
|
],
|
|
[
|
|
'key' => 'kecamatan',
|
|
'label' => 'Kecamatan',
|
|
'options' => $this->withAllOption('Semua Kecamatan', []),
|
|
],
|
|
[
|
|
'key' => 'desa',
|
|
'label' => 'Desa',
|
|
'options' => $this->withAllOption('Semua Desa', []),
|
|
],
|
|
[
|
|
'key' => 'komoditas',
|
|
'label' => 'Komoditas',
|
|
'options' => $this->withAllOption('Semua Komoditas', []),
|
|
],
|
|
[
|
|
'key' => 'segmentasi',
|
|
'label' => 'Segmentasi',
|
|
'options' => $this->withAllOption('Semua Segmentasi', []),
|
|
],
|
|
];
|
|
|
|
return view('dashboard.indikator-pertanian', [
|
|
'title' => 'Indikator Pertanian',
|
|
'initialFilters' => $initialFilters,
|
|
'filterOptions' => $filterOptions,
|
|
] + $payload);
|
|
}
|
|
|
|
public function data(Request $request)
|
|
{
|
|
if ($request->query('mode') === 'filter-options') {
|
|
return response()->json([
|
|
'komoditas' => $this->komoditasOptions(),
|
|
'segmentasi' => $this->segmentasiOptions(),
|
|
]);
|
|
}
|
|
|
|
$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 [
|
|
'overviewData' => $this->overviewData($filters),
|
|
'landProfileData' => $this->landProfileData($filters),
|
|
'summaryData' => $this->summaryData($filters),
|
|
'chartData' => $this->chartData($filters),
|
|
];
|
|
}
|
|
|
|
private function overviewData(array $filters): array
|
|
{
|
|
$totalPetani = $this->applyPetaniFilters(Petani::query(), $filters)->count();
|
|
$totalLahan = $this->applyLahanFilters(Lahan::query(), $filters)->count();
|
|
$totalLuasLahan = (float) $this->applyLahanFilters(Lahan::has('petani'), $filters)->sum('luas_lahan');
|
|
$jumlahDesa = $this->applyLahanFilters(Lahan::query(), $filters)
|
|
->whereNotNull('desa_kelurahan_id')
|
|
->distinct('desa_kelurahan_id')
|
|
->count('desa_kelurahan_id');
|
|
$jumlahKecamatan = $this->applyLahanFilters(Lahan::query(), $filters)
|
|
->leftJoin('master_desa_kelurahan as d', 'd.id', '=', 'map_lahan.desa_kelurahan_id')
|
|
->whereNotNull('d.kecamatan_id')
|
|
->distinct('d.kecamatan_id')
|
|
->count('d.kecamatan_id');
|
|
$totalProduksiKg = $this->totalProduksiKg($filters);
|
|
$rataRataProduktivitas = $totalLuasLahan > 0 ? $totalProduksiKg / $totalLuasLahan : 0;
|
|
$tanamanProduktif = $this->tanamanProduktifCount($filters);
|
|
$batchPanen = $this->batchPanenCount($filters);
|
|
|
|
return [
|
|
'kpiCards' => [
|
|
[
|
|
'value' => number_format($totalPetani, 0, ',', '.'),
|
|
'label' => 'Jumlah Petani',
|
|
'description' => 'Petani yang tercakup dalam filter aktif',
|
|
'icon' => 'tabler-users',
|
|
'variant' => 'primary',
|
|
],
|
|
[
|
|
'value' => number_format($totalLahan, 0, ',', '.'),
|
|
'label' => 'Jumlah Kebun/Lahan',
|
|
'description' => 'Kebun atau lahan terdata',
|
|
'icon' => 'tabler-trees',
|
|
'variant' => 'success',
|
|
],
|
|
[
|
|
'value' => number_format($totalLuasLahan, 2, ',', '.').' ha',
|
|
'label' => 'Luas Lahan',
|
|
'description' => 'Akumulasi luas lahan terdata',
|
|
'icon' => 'tabler-ruler-measure',
|
|
'variant' => 'warning',
|
|
],
|
|
[
|
|
'value' => number_format($jumlahDesa, 0, ',', '.').' desa',
|
|
'label' => 'Desa/Kecamatan',
|
|
'description' => number_format($jumlahKecamatan, 0, ',', '.').' kecamatan tercakup',
|
|
'icon' => 'tabler-map-pin',
|
|
'variant' => 'info',
|
|
],
|
|
[
|
|
'value' => number_format($totalProduksiKg, 0, ',', '.').' kg',
|
|
'label' => 'Total Produksi',
|
|
'description' => $this->productionSummaryDescription($filters),
|
|
'icon' => 'tabler-wheat',
|
|
'variant' => 'primary',
|
|
],
|
|
[
|
|
'value' => number_format($rataRataProduktivitas, 0, ',', '.').' kg/ha',
|
|
'label' => 'Produktivitas Rata-rata',
|
|
'description' => 'Berdasarkan produksi dan luas lahan terdata',
|
|
'icon' => 'tabler-chart-bar',
|
|
'variant' => 'success',
|
|
],
|
|
[
|
|
'value' => number_format($tanamanProduktif, 0, ',', '.'),
|
|
'label' => 'Tanaman Produktif',
|
|
'description' => 'Tanaman dengan kondisi baik',
|
|
'icon' => 'tabler-plant-2',
|
|
'variant' => 'warning',
|
|
],
|
|
[
|
|
'value' => number_format($batchPanen, 0, ',', '.'),
|
|
'label' => 'Batch Panen Tercatat',
|
|
'description' => 'Catatan produksi dan survey panen',
|
|
'icon' => 'tabler-basket',
|
|
'variant' => 'info',
|
|
],
|
|
],
|
|
'commodityFocus' => $this->commodityFocusData($filters),
|
|
'map' => [
|
|
'features' => $this->farmMapFeatures($filters),
|
|
'levels' => $this->mapLevelData($filters),
|
|
],
|
|
];
|
|
}
|
|
|
|
private function landProfileData(array $filters): array
|
|
{
|
|
$totalTanaman = (int) $this->applyLahanFilters(Lahan::query(), $filters)->sum('jumlah_tanaman');
|
|
$rataRataUmurTanaman = (float) $this->applyLahanFilters(Lahan::query(), $filters)
|
|
->whereNotNull('umur_tanaman')
|
|
->avg('umur_tanaman');
|
|
$komoditasCount = (int) $this->applyLahanFilters(Lahan::query(), $filters)
|
|
->whereNotNull('komoditas_id')
|
|
->distinct('komoditas_id')
|
|
->count('komoditas_id');
|
|
$statusLahanCount = (int) $this->applyLahanFilters(Lahan::query(), $filters)
|
|
->whereNotNull('status_kepemilikan')
|
|
->distinct('status_kepemilikan')
|
|
->count('status_kepemilikan');
|
|
$tanamanPenaung = $this->shadowTreeSummary($filters);
|
|
$tanamanPendamping = $this->companionPlantSummary($filters);
|
|
$diversityAverage = $this->plantDiversityAverage($filters);
|
|
|
|
return [
|
|
'conditionCards' => [
|
|
[
|
|
'value' => number_format($komoditasCount, 0, ',', '.'),
|
|
'label' => 'Luas Lahan per Komoditas',
|
|
'description' => 'Komoditas dengan luas lahan terdata',
|
|
'icon' => 'tabler-chart-treemap',
|
|
'variant' => 'primary',
|
|
],
|
|
[
|
|
'value' => number_format($totalTanaman, 0, ',', '.'),
|
|
'label' => 'Jumlah Tanaman',
|
|
'description' => 'Akumulasi jumlah tanaman pada lahan',
|
|
'icon' => 'tabler-trees',
|
|
'variant' => 'success',
|
|
],
|
|
[
|
|
'value' => number_format($rataRataUmurTanaman, 1, ',', '.').' tahun',
|
|
'label' => 'Umur Tanaman',
|
|
'description' => 'Rata-rata umur tanaman terdata',
|
|
'icon' => 'tabler-calendar-stats',
|
|
'variant' => 'warning',
|
|
],
|
|
[
|
|
'value' => number_format($statusLahanCount, 0, ',', '.'),
|
|
'label' => 'Status Lahan',
|
|
'description' => 'Jumlah kategori status kepemilikan lahan',
|
|
'icon' => 'tabler-certificate',
|
|
'variant' => 'info',
|
|
],
|
|
],
|
|
'agroecologyItems' => [
|
|
['label' => 'Sumber Air Lahan', 'value' => $this->sourceWaterSummary($filters), 'status' => 'available'],
|
|
['label' => 'Pengelolaan Air & Irigasi', 'value' => $this->waterManagementSummary($filters), 'status' => 'available'],
|
|
['label' => 'Kebutuhan Air per Musim', 'value' => $this->seasonalWaterNeedSummary($filters), 'status' => 'available'],
|
|
['label' => 'Praktik Konservasi Tanah', 'value' => $this->soilConservationSummary($filters), 'status' => 'available'],
|
|
],
|
|
'agroforestryCards' => [
|
|
[
|
|
'value' => number_format($tanamanPenaung['total'], 0, ',', '.'),
|
|
'label' => 'Jumlah Tanaman Penaung',
|
|
'description' => $tanamanPenaung['types'].' jenis tanaman penaung',
|
|
'icon' => 'tabler-leaf',
|
|
'variant' => 'success',
|
|
],
|
|
[
|
|
'value' => number_format($tanamanPendamping['types'], 0, ',', '.'),
|
|
'label' => 'Jenis Tanaman Pendamping',
|
|
'description' => number_format($tanamanPendamping['total'], 0, ',', '.').' entri tanaman pendamping',
|
|
'icon' => 'tabler-flower',
|
|
'variant' => 'warning',
|
|
],
|
|
[
|
|
'value' => number_format(round($diversityAverage), 0, ',', '.'),
|
|
'label' => 'Tingkat Campuran Tanaman',
|
|
'description' => 'Rata-rata kombinasi tanaman utama, pendamping, dan penaung',
|
|
'icon' => 'tabler-binary-tree',
|
|
'variant' => 'primary',
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
private function summaryData(array $filters): array
|
|
{
|
|
$totalPetani = $this->applyPetaniFilters(Petani::query(), $filters)->count();
|
|
// $totalLuasLahan = ((float) $this->applyLahanFilters(Lahan::query(), $filters)->sum('luas_lahan')) / 10000;
|
|
$totalLuasLahan = $this->applyLahanFilters(Lahan::has('petani'), $filters)->sum('luas_lahan');
|
|
$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',
|
|
'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',
|
|
],
|
|
];
|
|
}
|
|
|
|
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'),
|
|
'luasLahanPerKomoditas' => $this->luasLahanPerKomoditasChart($filters),
|
|
'jumlahTanamanPerKomoditas' => $this->jumlahTanamanPerKomoditasChart($filters),
|
|
'umurTanaman' => $this->umurTanamanChart($filters),
|
|
'sumberAir' => $this->sumberAirChart($filters),
|
|
];
|
|
}
|
|
|
|
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
|
|
{
|
|
$segments = SkorPetani::query()
|
|
->whereNotNull('skor_kesiapan_adopsi')
|
|
->pluck('skor_kesiapan_adopsi')
|
|
->map(fn ($score) => $this->segmentLabel((float) $score))
|
|
->filter()
|
|
->unique()
|
|
->sortBy(fn ($segment) => array_search($segment, [
|
|
'Penggerak',
|
|
'Perintis',
|
|
'Pemerhati',
|
|
'Pemula',
|
|
'Belum tersegmentasi',
|
|
], true))
|
|
->map(fn ($segment) => [
|
|
'value' => $segment,
|
|
'label' => $segment,
|
|
])
|
|
->values()
|
|
->all();
|
|
|
|
return $this->withAllOption('Semua Segmentasi', $segments);
|
|
}
|
|
|
|
private function totalProduksiKg(array $filters): float
|
|
{
|
|
return $this->applyProduksiFilters(Produksi::query(), $filters)
|
|
->whereNotNull('jumlah_panen')
|
|
->get(['jumlah_panen', 'satuan'])
|
|
->sum(fn (Produksi $produksi) => $this->toKilograms(
|
|
(float) $produksi->jumlah_panen,
|
|
(string) $produksi->satuan
|
|
));
|
|
}
|
|
|
|
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 luasLahanPerKomoditasChart(array $filters): array
|
|
{
|
|
$rows = $this->applyLahanFilters(Lahan::query(), $filters)
|
|
->leftJoin('master_komoditas as k', 'k.id', '=', 'map_lahan.komoditas_id')
|
|
->whereNotNull('map_lahan.luas_lahan')
|
|
->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
|
|
->selectRaw('SUM(map_lahan.luas_lahan) as total')
|
|
->groupBy('k.id', 'k.nama')
|
|
->havingRaw('SUM(map_lahan.luas_lahan) > 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 jumlahTanamanPerKomoditasChart(array $filters): array
|
|
{
|
|
$rows = $this->applyLahanFilters(Lahan::query(), $filters)
|
|
->leftJoin('master_komoditas as k', 'k.id', '=', 'map_lahan.komoditas_id')
|
|
->whereNotNull('map_lahan.jumlah_tanaman')
|
|
->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
|
|
->selectRaw('SUM(map_lahan.jumlah_tanaman) as total')
|
|
->groupBy('k.id', 'k.nama')
|
|
->havingRaw('SUM(map_lahan.jumlah_tanaman) > 0')
|
|
->orderByDesc('total')
|
|
->get();
|
|
|
|
return [
|
|
'labels' => $rows->pluck('label')->values()->all(),
|
|
'series' => $rows->pluck('total')->map(fn ($value) => (int) $value)->values()->all(),
|
|
];
|
|
}
|
|
|
|
private function umurTanamanChart(array $filters): array
|
|
{
|
|
$bucketOrder = ['< 3 tahun', '3-7 tahun', '8-15 tahun', '> 15 tahun'];
|
|
$rows = $this->applyLahanFilters(Lahan::query(), $filters)
|
|
->whereNotNull('umur_tanaman')
|
|
->pluck('umur_tanaman')
|
|
->map(function ($age) {
|
|
$age = (float) $age;
|
|
|
|
return match (true) {
|
|
$age < 3 => '< 3 tahun',
|
|
$age <= 7 => '3-7 tahun',
|
|
$age <= 15 => '8-15 tahun',
|
|
default => '> 15 tahun',
|
|
};
|
|
})
|
|
->countBy()
|
|
->sortBy(fn ($value, $label) => array_search($label, $bucketOrder, true));
|
|
|
|
return [
|
|
'labels' => $rows->keys()->values()->all(),
|
|
'series' => $rows->values()->map(fn ($value) => (int) $value)->values()->all(),
|
|
];
|
|
}
|
|
|
|
private function sumberAirChart(array $filters): array
|
|
{
|
|
$rows = $this->applyPraktikBudidayaFilters(PraktikBudidaya::query(), $filters)
|
|
->get(['sumber_air'])
|
|
->flatMap(fn (PraktikBudidaya $praktik) => $this->arrayValues($praktik->sumber_air))
|
|
->filter()
|
|
->countBy()
|
|
->sortDesc();
|
|
|
|
return [
|
|
'labels' => $rows->keys()->values()->all(),
|
|
'series' => $rows->values()->map(fn ($value) => (int) $value)->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
|
|
{
|
|
$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('produksi.jumlah_panen')
|
|
->selectRaw('produksi.satuan')
|
|
->get()
|
|
->groupBy('label')
|
|
->map(fn ($items) => round($items->sum(
|
|
fn (Produksi $produksi) => $this->toKilograms(
|
|
(float) $produksi->jumlah_panen,
|
|
(string) $produksi->satuan
|
|
)
|
|
), 2))
|
|
->filter(fn ($total) => $total > 0)
|
|
->sortDesc();
|
|
|
|
return [
|
|
'labels' => $rows->keys()->values()->all(),
|
|
'series' => $rows->values()->map(fn ($value) => round((float) $value, 2))->values()->all(),
|
|
];
|
|
}
|
|
|
|
private function produktivitasPerKomoditasChart(array $filters): array
|
|
{
|
|
$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('produksi.jumlah_panen')
|
|
->selectRaw('produksi.satuan')
|
|
->selectRaw('produksi.luas_panen')
|
|
->selectRaw('produksi.produktivitas')
|
|
->get()
|
|
->groupBy('label')
|
|
->map(function ($items) {
|
|
$storedAverage = $items
|
|
->pluck('produktivitas')
|
|
->filter(fn ($value) => (float) $value > 0)
|
|
->avg();
|
|
|
|
if ($storedAverage > 0) {
|
|
return round((float) $storedAverage, 2);
|
|
}
|
|
|
|
$totalProduksiKg = $items->sum(
|
|
fn (Produksi $produksi) => $this->toKilograms(
|
|
(float) $produksi->jumlah_panen,
|
|
(string) $produksi->satuan
|
|
)
|
|
);
|
|
$totalLuasPanen = $items->sum(fn (Produksi $produksi) => (float) $produksi->luas_panen);
|
|
|
|
return $totalLuasPanen > 0
|
|
? round($totalProduksiKg / $totalLuasPanen, 2)
|
|
: 0;
|
|
})
|
|
->filter(fn ($total) => $total > 0)
|
|
->sortDesc();
|
|
|
|
return [
|
|
'labels' => $rows->keys()->values()->all(),
|
|
'series' => $rows->values()->map(fn ($value) => round((float) $value, 2))->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 tanamanProduktifCount(array $filters): int
|
|
{
|
|
return (int) $this->applyTanamanTableFilters(DB::table('map_tanaman'), $filters)
|
|
->whereNull('map_tanaman.deleted_at')
|
|
->where('map_tanaman.kondisi_tanaman', 'baik')
|
|
->count('map_tanaman.id');
|
|
}
|
|
|
|
private function batchPanenCount(array $filters): int
|
|
{
|
|
$produksiCount = $this->applyProduksiFilters(Produksi::query(), $filters)->count();
|
|
$surveyPanenCount = $this->applySurveyPanenFilters(SurveyPanen::query(), $filters)->count();
|
|
|
|
return (int) $produksiCount + (int) $surveyPanenCount;
|
|
}
|
|
|
|
private function productionSummaryDescription(array $filters): string
|
|
{
|
|
$chart = $this->produksiPerKomoditasChart($filters);
|
|
|
|
if (empty($chart['series'])) {
|
|
return 'Belum ada produksi aktual per komoditas';
|
|
}
|
|
|
|
return collect($chart['labels'])
|
|
->map(fn ($label, $index) => $label.': '.number_format((float) ($chart['series'][$index] ?? 0), 0, ',', '.').' kg')
|
|
->take(3)
|
|
->implode(' | ');
|
|
}
|
|
|
|
private function commodityFocusData(array $filters): array
|
|
{
|
|
$focusCommodities = ['Kopi', 'Kakao', 'Vanili', 'Kemiri'];
|
|
$rows = $this->applyLahanFilters(Lahan::query(), $filters)
|
|
->leftJoin('master_komoditas as k', 'k.id', '=', 'map_lahan.komoditas_id')
|
|
->whereIn(DB::raw('LOWER(k.nama)'), collect($focusCommodities)->map(fn ($name) => strtolower($name))->all())
|
|
->selectRaw('k.nama as label')
|
|
->selectRaw('COUNT(map_lahan.id) as total')
|
|
->groupBy('k.nama')
|
|
->get()
|
|
->mapWithKeys(fn ($row) => [strtolower((string) $row->label) => (int) $row->total]);
|
|
|
|
return collect($focusCommodities)
|
|
->map(fn ($name) => [
|
|
'label' => $name,
|
|
'total' => $rows[strtolower($name)] ?? 0,
|
|
])
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
private function farmMapFeatures(array $filters): array
|
|
{
|
|
return $this->applyLahanFilters(
|
|
Lahan::query()->with(['desaKelurahan.kecamatan', 'komoditas']),
|
|
$filters
|
|
)
|
|
->where(function ($query) {
|
|
$query
|
|
->whereNotNull('lat')
|
|
->orWhereNotNull('long')
|
|
->orWhereNotNull('path');
|
|
})
|
|
->limit(250)
|
|
->get()
|
|
->map(fn (Lahan $lahan) => [
|
|
'id' => $lahan->id,
|
|
'name' => $lahan->nama,
|
|
'commodity' => $lahan->komoditas?->nama ?? 'Belum ditentukan',
|
|
'area' => (float) $lahan->luas_lahan,
|
|
'village' => $lahan->desaKelurahan?->nama,
|
|
'district' => $lahan->desaKelurahan?->kecamatan?->nama,
|
|
'lat' => is_numeric($lahan->lat) ? (float) $lahan->lat : null,
|
|
'lng' => is_numeric($lahan->long) ? (float) $lahan->long : null,
|
|
'path' => is_array($lahan->path) ? $lahan->path : null,
|
|
])
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
private function mapLevelData(array $filters): array
|
|
{
|
|
$lahanSummaryByDesa = $this->applyLahanFilters(Lahan::query(), $filters)
|
|
->whereNotNull('desa_kelurahan_id')
|
|
->select('desa_kelurahan_id')
|
|
->selectRaw('COUNT(*) as total_lahan')
|
|
->selectRaw('COALESCE(SUM(luas_lahan), 0) as total_luas')
|
|
->groupBy('desa_kelurahan_id')
|
|
->get();
|
|
|
|
$desaIds = $lahanSummaryByDesa->pluck('desa_kelurahan_id')->filter()->unique()->values();
|
|
|
|
if ($filters['desa'] !== 'all') {
|
|
$desaIds->push($filters['desa']);
|
|
}
|
|
|
|
$desaList = DesaKelurahan::query()
|
|
->select('id', 'nama', 'path', 'kecamatan_id')
|
|
->when($desaIds->isNotEmpty(), fn ($query) => $query->whereIn('id', $desaIds->unique()->values()))
|
|
->with([
|
|
'kecamatan:id,nama,path,kabupaten_kota_id',
|
|
'kecamatan.kabupatenKota:id,nama,path,provinsi_id',
|
|
'kecamatan.kabupatenKota.provinsi:id,nama,path',
|
|
])
|
|
->get()
|
|
->keyBy('id');
|
|
|
|
$geoProvinsi = [];
|
|
$geoKabupaten = [];
|
|
$geoKecamatan = [];
|
|
$geoDesa = [];
|
|
$geoLahan = [];
|
|
|
|
foreach ($lahanSummaryByDesa as $summary) {
|
|
$desa = $desaList->get($summary->desa_kelurahan_id);
|
|
$kec = $desa?->kecamatan;
|
|
$kab = $kec?->kabupatenKota;
|
|
$prov = $kab?->provinsi;
|
|
$totalLahan = (int) $summary->total_lahan;
|
|
$totalLuas = (float) $summary->total_luas;
|
|
|
|
if ($prov && $prov->path) {
|
|
$this->accumulateGeoLevel($geoProvinsi, $prov->id, null, $prov->nama, $prov->path, $totalLahan, $totalLuas);
|
|
}
|
|
|
|
if ($kab && $kab->path) {
|
|
$this->accumulateGeoLevel($geoKabupaten, $kab->id, $prov?->id, $kab->nama, $kab->path, $totalLahan, $totalLuas);
|
|
}
|
|
|
|
if ($kec && $kec->path) {
|
|
$this->accumulateGeoLevel($geoKecamatan, $kec->id, $kab?->id, $kec->nama, $kec->path, $totalLahan, $totalLuas);
|
|
}
|
|
|
|
if ($desa && $desa->path) {
|
|
$this->accumulateGeoLevel($geoDesa, $desa->id, $kec?->id, $desa->nama, $desa->path, $totalLahan, $totalLuas);
|
|
}
|
|
}
|
|
|
|
$this->ensureSelectedRegionOnMap($filters, $geoProvinsi, $geoKabupaten, $geoKecamatan, $geoDesa);
|
|
|
|
$mapLahan = $this->applyLahanFilters(
|
|
Lahan::query()->with([
|
|
'profile:id,nama',
|
|
'petani:id,nama',
|
|
'komoditas:id,nama',
|
|
'tanaman:id,lahan_id,komoditas_id',
|
|
'tanaman.komoditas:id,nama',
|
|
]),
|
|
$filters
|
|
)
|
|
->whereNotNull('path')
|
|
->orderByDesc('updated_at')
|
|
->limit(500)
|
|
->get();
|
|
|
|
foreach ($mapLahan as $lahan) {
|
|
if (! $this->hasMapPolygon($lahan->path)) {
|
|
continue;
|
|
}
|
|
|
|
$tanaman = $lahan->tanaman
|
|
->map(fn ($tanaman) => $tanaman->komoditas?->nama)
|
|
->filter()
|
|
->unique()
|
|
->values();
|
|
|
|
if ($tanaman->isEmpty() && $lahan->komoditas?->nama) {
|
|
$tanaman = collect([$lahan->komoditas->nama]);
|
|
}
|
|
|
|
$geoLahan[] = [
|
|
'id' => $lahan->id,
|
|
'parent_id' => $lahan->desa_kelurahan_id,
|
|
'polygon' => $lahan->path,
|
|
'nama_petani' => $lahan->petani?->nama ?? $lahan->profile?->nama ?? 'N/A',
|
|
'luas' => (float) $lahan->luas_lahan,
|
|
'tanaman' => $tanaman,
|
|
];
|
|
}
|
|
|
|
return [
|
|
'provinsi' => array_values($geoProvinsi),
|
|
'kabupaten' => array_values($geoKabupaten),
|
|
'kecamatan' => array_values($geoKecamatan),
|
|
'desa' => array_values($geoDesa),
|
|
'lahan' => $geoLahan,
|
|
];
|
|
}
|
|
|
|
private function accumulateGeoLevel(array &$bucket, int|string $id, int|string|null $parentId, string $name, mixed $polygon, int $totalLahan, float $totalLuas): void
|
|
{
|
|
if (! isset($bucket[$id])) {
|
|
$bucket[$id] = [
|
|
'id' => $id,
|
|
'parent_id' => $parentId,
|
|
'nama' => $name,
|
|
'polygon' => $polygon,
|
|
'total_lahan' => 0,
|
|
'total_luas' => 0,
|
|
];
|
|
}
|
|
|
|
$bucket[$id]['total_lahan'] += $totalLahan;
|
|
$bucket[$id]['total_luas'] += $totalLuas;
|
|
}
|
|
|
|
private function ensureSelectedRegionOnMap(array $filters, array &$geoProvinsi, array &$geoKabupaten, array &$geoKecamatan, array &$geoDesa): void
|
|
{
|
|
$kabupaten = $filters['kabupaten'] !== 'all'
|
|
? KabupatenKota::query()->with('provinsi:id,nama,path')->find($filters['kabupaten'])
|
|
: null;
|
|
|
|
if ($kabupaten && $kabupaten->path && ! isset($geoKabupaten[$kabupaten->id])) {
|
|
$this->accumulateGeoLevel($geoKabupaten, $kabupaten->id, $kabupaten->provinsi_id, $kabupaten->nama, $kabupaten->path, 0, 0);
|
|
}
|
|
|
|
if ($kabupaten?->provinsi && $kabupaten->provinsi->path && ! isset($geoProvinsi[$kabupaten->provinsi->id])) {
|
|
$this->accumulateGeoLevel($geoProvinsi, $kabupaten->provinsi->id, null, $kabupaten->provinsi->nama, $kabupaten->provinsi->path, 0, 0);
|
|
}
|
|
|
|
if ($filters['kecamatan'] !== 'all') {
|
|
$kecamatan = Kecamatan::query()->find($filters['kecamatan']);
|
|
|
|
if ($kecamatan && $kecamatan->path && ! isset($geoKecamatan[$kecamatan->id])) {
|
|
$this->accumulateGeoLevel($geoKecamatan, $kecamatan->id, $kecamatan->kabupaten_kota_id, $kecamatan->nama, $kecamatan->path, 0, 0);
|
|
}
|
|
}
|
|
|
|
if ($filters['desa'] !== 'all') {
|
|
$desa = DesaKelurahan::query()->find($filters['desa']);
|
|
|
|
if ($desa && $desa->path && ! isset($geoDesa[$desa->id])) {
|
|
$this->accumulateGeoLevel($geoDesa, $desa->id, $desa->kecamatan_id, $desa->nama, $desa->path, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function hasMapPolygon(mixed $path): bool
|
|
{
|
|
if (is_array($path)) {
|
|
return ! empty($path);
|
|
}
|
|
|
|
if (! is_string($path) || blank($path) || strtolower($path) === 'null') {
|
|
return false;
|
|
}
|
|
|
|
$decoded = json_decode($path, true);
|
|
|
|
return json_last_error() === JSON_ERROR_NONE && ! empty($decoded) && $decoded !== 'null';
|
|
}
|
|
|
|
private function shadowTreeSummary(array $filters): array
|
|
{
|
|
$items = $this->applyLahanFilters(Lahan::query(), $filters)
|
|
->get(['tanaman_penaung'])
|
|
->flatMap(fn (Lahan $lahan) => $this->arrayValues($lahan->tanaman_penaung))
|
|
->filter()
|
|
->values();
|
|
|
|
return [
|
|
'total' => $items->count(),
|
|
'types' => $items->unique(fn ($item) => strtolower((string) $item))->count(),
|
|
];
|
|
}
|
|
|
|
private function companionPlantSummary(array $filters): array
|
|
{
|
|
$items = $this->applyLahanFilters(Lahan::query(), $filters)
|
|
->get(['id', 'komoditas_lain'])
|
|
->flatMap(fn (Lahan $lahan) => $this->arrayValues($lahan->komoditas_lain))
|
|
->filter()
|
|
->values();
|
|
|
|
return [
|
|
'total' => $items->count(),
|
|
'types' => $items->unique(fn ($item) => strtolower((string) $item))->count(),
|
|
];
|
|
}
|
|
|
|
private function plantDiversityAverage(array $filters): float
|
|
{
|
|
$lahan = $this->applyLahanFilters(Lahan::query(), $filters)
|
|
->get(['komoditas_id', 'komoditas_lain', 'tanaman_penaung']);
|
|
|
|
if ($lahan->isEmpty()) {
|
|
return 0;
|
|
}
|
|
|
|
return (float) $lahan->avg(function (Lahan $item) {
|
|
$total = $item->komoditas_id ? 1 : 0;
|
|
$total += count($this->arrayValues($item->komoditas_lain));
|
|
$total += count($this->arrayValues($item->tanaman_penaung));
|
|
|
|
return $total;
|
|
});
|
|
}
|
|
|
|
private function sourceWaterSummary(array $filters): string
|
|
{
|
|
$chart = $this->sumberAirChart($filters);
|
|
|
|
if (empty($chart['labels'])) {
|
|
return 'Belum ada data';
|
|
}
|
|
|
|
return collect($chart['labels'])
|
|
->map(fn ($label, $index) => $label.' ('.number_format((int) ($chart['series'][$index] ?? 0), 0, ',', '.').')')
|
|
->take(2)
|
|
->implode(', ');
|
|
}
|
|
|
|
private function waterManagementSummary(array $filters): string
|
|
{
|
|
$rows = $this->applyPraktikBudidayaFilters(PraktikBudidaya::query(), $filters)
|
|
->whereNotNull('kondisi_pengelolaan_air')
|
|
->pluck('kondisi_pengelolaan_air')
|
|
->map(fn ($value) => $this->waterManagementShortLabel((int) $value))
|
|
->countBy()
|
|
->sortDesc();
|
|
|
|
return $this->topCountSummary($rows);
|
|
}
|
|
|
|
private function seasonalWaterNeedSummary(array $filters): string
|
|
{
|
|
$rows = $this->applyPraktikBudidayaFilters(PraktikBudidaya::query(), $filters)
|
|
->whereNotNull('kebutuhan_air_permusim')
|
|
->pluck('kebutuhan_air_permusim')
|
|
->map(fn ($value) => $this->shortEnumLabel((string) $value))
|
|
->countBy()
|
|
->sortDesc();
|
|
|
|
return $this->topCountSummary($rows);
|
|
}
|
|
|
|
private function soilConservationSummary(array $filters): string
|
|
{
|
|
$rows = $this->applyPraktikBudidayaFilters(PraktikBudidaya::query(), $filters)
|
|
->get(['kebiasaan_konservasi_tanah'])
|
|
->flatMap(fn (PraktikBudidaya $praktik) => $this->arrayValues($praktik->kebiasaan_konservasi_tanah))
|
|
->filter()
|
|
->countBy()
|
|
->sortDesc();
|
|
|
|
return $this->topCountSummary($rows);
|
|
}
|
|
|
|
private function soilConservationScoreSummary(array $filters): string
|
|
{
|
|
$average = $this->applyPraktikBudidayaFilters(PraktikBudidaya::query(), $filters)
|
|
->whereNotNull('nilai_konservasi_tanah')
|
|
->avg('nilai_konservasi_tanah');
|
|
|
|
if ($average === null) {
|
|
return 'Belum ada data';
|
|
}
|
|
|
|
return 'Rata-rata '.number_format((float) $average, 1, ',', '.');
|
|
}
|
|
|
|
private function topCountSummary($rows): string
|
|
{
|
|
if ($rows->isEmpty()) {
|
|
return 'Belum ada data';
|
|
}
|
|
|
|
return $rows
|
|
->map(fn ($count, $label) => $label.' ('.number_format((int) $count, 0, ',', '.').')')
|
|
->take(2)
|
|
->implode(', ');
|
|
}
|
|
|
|
private function waterManagementShortLabel(int $value): string
|
|
{
|
|
return match (KondisiPengelolaanAir::tryFrom($value)) {
|
|
KondisiPengelolaanAir::KURANG_TIDAK_ADA_UPAYA => 'Kurang, tanpa upaya',
|
|
KondisiPengelolaanAir::KURANG_TIDAK_SUDAH_BERUPAYA => 'Kurang, ada upaya',
|
|
KondisiPengelolaanAir::CUKUP_DARI_HUJAN => 'Cukup dari hujan',
|
|
KondisiPengelolaanAir::CUKUP_IRIGASI_PINJAM => 'Cukup, irigasi pinjam',
|
|
KondisiPengelolaanAir::CUKUP_IRIGASI_SENDIRI => 'Cukup, irigasi sendiri',
|
|
default => 'Belum ditentukan',
|
|
};
|
|
}
|
|
|
|
private function arrayValues(mixed $value): array
|
|
{
|
|
if (is_array($value)) {
|
|
return collect($value)
|
|
->flatten()
|
|
->filter(fn ($item) => filled($item))
|
|
->map(fn ($item) => is_scalar($item) ? (string) $item : json_encode($item))
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
if (is_string($value) && filled($value) && strtolower($value) !== 'null') {
|
|
$decoded = json_decode($value, true);
|
|
|
|
if (json_last_error() === JSON_ERROR_NONE) {
|
|
return $this->arrayValues($decoded);
|
|
}
|
|
|
|
return [$value];
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
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 applySurveyPanenFilters($query, array $filters)
|
|
{
|
|
return $query->whereHas('petani', function ($petani) use ($filters) {
|
|
$this->applyPetaniFilters($petani, $filters);
|
|
});
|
|
}
|
|
|
|
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);
|
|
}),
|
|
};
|
|
}
|
|
|
|
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',
|
|
};
|
|
}
|
|
}
|