indikator pertanian

This commit is contained in:
2026-08-07 15:56:37 +07:00
parent 81775b0c13
commit 0d9f6c0b64
4 changed files with 1794 additions and 0 deletions
@@ -0,0 +1,627 @@
<?php
namespace App\Http\Controllers\Web\Dashboard;
use App\Enums\KeparahanSerangan;
use App\Enums\PenyakitTanaman;
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\SkorPetani;
use App\Models\Pertanian\SosialEkonomi;
use App\Models\Wilayah\DesaKelurahan;
use App\Models\Wilayah\KabupatenKota;
use App\Models\Wilayah\Kecamatan;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
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;
$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),
],
]);
}
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 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 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 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',
};
}
}
File diff suppressed because it is too large Load Diff
+7
View File
@@ -76,6 +76,13 @@
</a>
</li>
</ul>
<ul class="menu-sub">
<li class="menu-item {{ request()->routeIs('dashboard.indikator-pertanian') ? 'active' : '' }}">
<a href="{{ route('dashboard.indikator-pertanian') }}" class="menu-link">
<div data-i18n="Indikator Pertanian">Indikator Pertanian</div>
</a>
</li>
</ul>
@endif
@if(in_array($userRole, ['super_admin', 'admin', 'fasilitator']) && env('APP_NAME') == 'AgroMonev')
+1
View File
@@ -47,6 +47,7 @@ Route::middleware('auth')
Route::get('/dashboard/petani', [App\Http\Controllers\Web\Dashboard\ProfilPetaniController::class, 'index'])->name('dashboard.petani');
Route::get('/dashboard/bumdes', [App\Http\Controllers\Web\Bumdes\PengurusBumdesController::class, 'index'])->name('dashboard.bumdes');
Route::get('/dashboard/petani/demografi', [App\Http\Controllers\Web\Dashboard\DemografiPetaniController::class, 'index'])->name('dashboard.demografi-petani');
Route::get('/dashboard/petani/indikator-pertanian', [App\Http\Controllers\Web\Dashboard\IndikatorPertanianController::class, 'index'])->name('dashboard.indikator-pertanian');
Route::get('/dashboard/petani/indeks', [App\Http\Controllers\Web\Dashboard\IndeksPetaniController::class, 'index'])->name('dashboard.indeks-petani');