solve dashboard indikator pertanian
This commit is contained in:
@@ -0,0 +1,769 @@
|
|||||||
|
<?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\Pertanian\SurveyProduktivitas;
|
||||||
|
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),
|
||||||
|
'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',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,30 +26,14 @@ class IndikatorPertanianController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$kabupatenOptions = $this->kabupatenOptions();
|
$initialFilters = $this->selectedFilters($request, $this->defaultKabupatenId());
|
||||||
$ngadaOption = collect($kabupatenOptions)->first(
|
$payload = $this->dashboardPayload($initialFilters);
|
||||||
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 = [
|
$filterOptions = [
|
||||||
[
|
[
|
||||||
'key' => 'kabupaten',
|
'key' => 'kabupaten',
|
||||||
'label' => 'Kabupaten',
|
'label' => 'Kabupaten',
|
||||||
'options' => $kabupatenOptions,
|
'options' => $this->kabupatenOptions(),
|
||||||
'icon' => 'tabler-map-pin',
|
'icon' => 'tabler-map-pin',
|
||||||
'readonly' => true,
|
'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, ',', '.'),
|
'value' => number_format($totalPetani, 0, ',', '.'),
|
||||||
'label' => 'Petani Terdata',
|
'label' => 'Petani Terdata',
|
||||||
@@ -105,30 +137,27 @@ class IndikatorPertanianController extends Controller
|
|||||||
'variant' => 'info',
|
'variant' => 'info',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return view('dashboard.indikator-pertanian', [
|
private function chartData(array $filters): array
|
||||||
'title' => 'Indikator Pertanian',
|
{
|
||||||
'initialFilters' => $initialFilters,
|
return [
|
||||||
'filterOptions' => $filterOptions,
|
'petaniPerKomoditas' => $this->petaniPerKomoditasChart($filters),
|
||||||
'summaryData' => $summaryData,
|
'luasLahanPerKecamatan' => $this->luasLahanPerKecamatanChart($filters),
|
||||||
'chartData' => [
|
'kondisiTanaman' => $this->kondisiTanamanChart($filters),
|
||||||
'petaniPerKomoditas' => $this->petaniPerKomoditasChart($initialFilters),
|
'statusKepemilikanLahan' => $this->statusKepemilikanLahanChart($filters),
|
||||||
'luasLahanPerKecamatan' => $this->luasLahanPerKecamatanChart($initialFilters),
|
'seranganHama' => $this->seranganHamaChart($filters),
|
||||||
'kondisiTanaman' => $this->kondisiTanamanChart($initialFilters),
|
'penyakitTanaman' => $this->penyakitTanamanChart($filters),
|
||||||
'statusKepemilikanLahan' => $this->statusKepemilikanLahanChart($initialFilters),
|
'keparahanSerangan' => $this->keparahanSeranganChart($filters),
|
||||||
'seranganHama' => $this->seranganHamaChart($initialFilters),
|
'statusHakKelolaLahan' => $this->statusHakKelolaLahanChart($filters),
|
||||||
'penyakitTanaman' => $this->penyakitTanamanChart($initialFilters),
|
'praktikPemupukan' => $this->praktikPemupukanChart($filters),
|
||||||
'keparahanSerangan' => $this->keparahanSeranganChart($initialFilters),
|
'produksiPerKomoditas' => $this->produksiPerKomoditasChart($filters),
|
||||||
'statusHakKelolaLahan' => $this->statusHakKelolaLahanChart($initialFilters),
|
'produktivitasPerKomoditas' => $this->produktivitasPerKomoditasChart($filters),
|
||||||
'praktikPemupukan' => $this->praktikPemupukanChart($initialFilters),
|
'luasPanenPerKomoditas' => $this->luasPanenPerKomoditasChart($filters),
|
||||||
'produksiPerKomoditas' => $this->produksiPerKomoditasChart($initialFilters),
|
'statusPanen' => $this->statusPanenChart($filters),
|
||||||
'produktivitasPerKomoditas' => $this->produktivitasPerKomoditasChart($initialFilters),
|
'surveyProduktivitasHa' => $this->surveyProduktivitasByUnitChart($filters, 'ha_tahun'),
|
||||||
'luasPanenPerKomoditas' => $this->luasPanenPerKomoditasChart($initialFilters),
|
'surveyProduktivitasPohon' => $this->surveyProduktivitasByUnitChart($filters, 'pohon_tahun'),
|
||||||
'statusPanen' => $this->statusPanenChart($initialFilters),
|
];
|
||||||
'surveyProduktivitasHa' => $this->surveyProduktivitasByUnitChart($initialFilters, 'ha_tahun'),
|
|
||||||
'surveyProduktivitasPohon' => $this->surveyProduktivitasByUnitChart($initialFilters, 'pohon_tahun'),
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function selectedFilters(Request $request, string $defaultKabupaten): array
|
private function selectedFilters(Request $request, string $defaultKabupaten): array
|
||||||
|
|||||||
@@ -72,6 +72,16 @@
|
|||||||
box-shadow: 0 0 0 .2rem rgba(40, 167, 69, .12);
|
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 {
|
.indicator-filter-static {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: var(--bs-white);
|
background-color: var(--bs-white);
|
||||||
@@ -203,7 +213,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="indicator-filter-bar" aria-label="Filter dashboard indikator pertanian">
|
<div class="indicator-filter-bar" id="indicator-filter-bar" aria-label="Filter dashboard indikator pertanian">
|
||||||
@foreach ($filterOptions as $filter)
|
@foreach ($filterOptions as $filter)
|
||||||
<div class="indicator-filter-group">
|
<div class="indicator-filter-group">
|
||||||
<label for="filter-{{ $filter['key'] }}" class="indicator-filter-label">
|
<label for="filter-{{ $filter['key'] }}" class="indicator-filter-label">
|
||||||
@@ -296,9 +306,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-6">
|
<div class="row g-6" id="indicator-summary-row">
|
||||||
@foreach ($summaryData as $summary)
|
@foreach ($summaryData as $summary)
|
||||||
<div class="col-lg-3 col-sm-6">
|
<div class="col-lg-3 col-sm-6" data-summary-card="{{ $loop->index }}">
|
||||||
<div class="card card-border-shadow-{{ $summary['variant'] }} h-100">
|
<div class="card card-border-shadow-{{ $summary['variant'] }} h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="d-flex align-items-center mb-2">
|
||||||
@@ -308,17 +318,17 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4 class="mb-0 indicator-summary-value">
|
<h4 class="mb-0 indicator-summary-value" data-summary-value>
|
||||||
{{ $summary['value'] }}
|
{{ $summary['value'] }}
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="mb-1">
|
<p class="mb-1" data-summary-label>
|
||||||
{{ $summary['label'] }}
|
{{ $summary['label'] }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="mb-0">
|
<p class="mb-0">
|
||||||
<small class="text-body-secondary">
|
<small class="text-body-secondary" data-summary-description>
|
||||||
{{ $summary['description'] }}
|
{{ $summary['description'] }}
|
||||||
</small>
|
</small>
|
||||||
</p>
|
</p>
|
||||||
@@ -647,11 +657,13 @@
|
|||||||
@push('scripts')
|
@push('scripts')
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const chartData = @json($chartData);
|
let chartData = @json($chartData);
|
||||||
const dashboardUrl = @json(route('dashboard.indikator-pertanian'));
|
const dashboardUrl = @json(route('dashboard.indikator-pertanian'));
|
||||||
|
const dashboardDataUrl = @json(route('dashboard.indikator-pertanian.data'));
|
||||||
const initialFilters = @json($initialFilters);
|
const initialFilters = @json($initialFilters);
|
||||||
const filters = { ...initialFilters };
|
const filters = { ...initialFilters };
|
||||||
const filterSelects = document.querySelectorAll('.indicator-filter-select');
|
const filterSelects = document.querySelectorAll('.indicator-filter-select');
|
||||||
|
const filterBar = document.getElementById('indicator-filter-bar');
|
||||||
const kecamatanSelect = document.querySelector('[data-filter-key="kecamatan"]');
|
const kecamatanSelect = document.querySelector('[data-filter-key="kecamatan"]');
|
||||||
const desaSelect = document.querySelector('[data-filter-key="desa"]');
|
const desaSelect = document.querySelector('[data-filter-key="desa"]');
|
||||||
const desaOptions = desaSelect
|
const desaOptions = desaSelect
|
||||||
@@ -665,8 +677,12 @@
|
|||||||
: [];
|
: [];
|
||||||
const resetButton = document.getElementById('indicator-reset-filter');
|
const resetButton = document.getElementById('indicator-reset-filter');
|
||||||
const exportButton = document.getElementById('indicator-export-data');
|
const exportButton = document.getElementById('indicator-export-data');
|
||||||
|
const chartInstances = {};
|
||||||
|
let activeRequest = null;
|
||||||
|
let lahanChartsDirty = false;
|
||||||
|
let productionChartsRendered = false;
|
||||||
|
|
||||||
function applyFilters() {
|
function buildFilterParams() {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
['kecamatan', 'desa', 'komoditas', 'segmentasi'].forEach(function (key) {
|
['kecamatan', 'desa', 'komoditas', 'segmentasi'].forEach(function (key) {
|
||||||
@@ -675,9 +691,139 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.location.href = params.toString()
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAddressBar() {
|
||||||
|
const params = buildFilterParams();
|
||||||
|
const nextUrl = params.toString()
|
||||||
? dashboardUrl + '?' + params.toString()
|
? dashboardUrl + '?' + params.toString()
|
||||||
: dashboardUrl;
|
: dashboardUrl;
|
||||||
|
|
||||||
|
window.history.replaceState({}, '', nextUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLoadingState(isLoading) {
|
||||||
|
filterBar?.classList.toggle('is-loading', isLoading);
|
||||||
|
|
||||||
|
filterSelects.forEach(function (select) {
|
||||||
|
if (select.dataset.filterKey === 'desa') {
|
||||||
|
select.disabled = isLoading || filters.kecamatan === 'all';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
select.disabled = isLoading;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resetButton) {
|
||||||
|
resetButton.disabled = isLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportButton) {
|
||||||
|
exportButton.disabled = isLoading;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSummaryCards(summaryData) {
|
||||||
|
document.querySelectorAll('[data-summary-card]').forEach(function (card) {
|
||||||
|
const summary = summaryData[Number(card.dataset.summaryCard)];
|
||||||
|
|
||||||
|
if (!summary) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = card.querySelector('[data-summary-value]');
|
||||||
|
const label = card.querySelector('[data-summary-label]');
|
||||||
|
const description = card.querySelector('[data-summary-description]');
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
value.textContent = summary.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (label) {
|
||||||
|
label.textContent = summary.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (description) {
|
||||||
|
description.textContent = summary.description;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadIndicatorData() {
|
||||||
|
const params = buildFilterParams();
|
||||||
|
|
||||||
|
if (activeRequest) {
|
||||||
|
activeRequest.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestController = new AbortController();
|
||||||
|
activeRequest = requestController;
|
||||||
|
setLoadingState(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${dashboardDataUrl}?${params.toString()}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
},
|
||||||
|
signal: requestController.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP Error ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
Object.assign(filters, result.filters || {});
|
||||||
|
|
||||||
|
if (kecamatanSelect && kecamatanSelect.value !== filters.kecamatan) {
|
||||||
|
kecamatanSelect.value = filters.kecamatan;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDesaOptions(filters.desa);
|
||||||
|
|
||||||
|
filterSelects.forEach(function (select) {
|
||||||
|
const key = select.dataset.filterKey;
|
||||||
|
|
||||||
|
if (key !== 'desa' && filters[key] !== undefined) {
|
||||||
|
select.value = filters[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
chartData = result.chartData || {};
|
||||||
|
updateSummaryCards(result.summaryData || []);
|
||||||
|
productionChartsRendered = false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof renderLahanCharts === 'function'
|
||||||
|
&& document.querySelector('#indicator-tab-lahan-tanaman')?.classList.contains('active')
|
||||||
|
) {
|
||||||
|
renderLahanCharts();
|
||||||
|
} else {
|
||||||
|
lahanChartsDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof renderProductionCharts === 'function'
|
||||||
|
&& document.querySelector('#indicator-tab-produksi')?.classList.contains('active')
|
||||||
|
) {
|
||||||
|
renderProductionCharts();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAddressBar();
|
||||||
|
} catch (error) {
|
||||||
|
if (error.name !== 'AbortError') {
|
||||||
|
console.error('Gagal memuat indikator pertanian:', error);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (activeRequest === requestController) {
|
||||||
|
activeRequest = null;
|
||||||
|
setLoadingState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDesaOptions(selectedValue = initialFilters.desa) {
|
function renderDesaOptions(selectedValue = initialFilters.desa) {
|
||||||
@@ -720,12 +866,24 @@
|
|||||||
renderDesaOptions('all');
|
renderDesaOptions('all');
|
||||||
}
|
}
|
||||||
|
|
||||||
applyFilters();
|
loadIndicatorData();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
resetButton?.addEventListener('click', function () {
|
resetButton?.addEventListener('click', function () {
|
||||||
window.location.href = dashboardUrl;
|
Object.assign(filters, {
|
||||||
|
kecamatan: 'all',
|
||||||
|
desa: 'all',
|
||||||
|
komoditas: 'all',
|
||||||
|
segmentasi: 'all',
|
||||||
|
});
|
||||||
|
|
||||||
|
filterSelects.forEach(function (select) {
|
||||||
|
select.value = 'all';
|
||||||
|
});
|
||||||
|
|
||||||
|
renderDesaOptions('all');
|
||||||
|
loadIndicatorData();
|
||||||
});
|
});
|
||||||
|
|
||||||
exportButton?.addEventListener('click', function () {});
|
exportButton?.addEventListener('click', function () {});
|
||||||
@@ -830,6 +988,13 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (chartInstances[selector]) {
|
||||||
|
chartInstances[selector].destroy();
|
||||||
|
delete chartInstances[selector];
|
||||||
|
}
|
||||||
|
|
||||||
|
element.innerHTML = '';
|
||||||
|
|
||||||
if (!hasSeries(data)) {
|
if (!hasSeries(data)) {
|
||||||
element.innerHTML = '<div class="text-center text-body-secondary py-5">Belum ada data.</div>';
|
element.innerHTML = '<div class="text-center text-body-secondary py-5">Belum ada data.</div>';
|
||||||
return;
|
return;
|
||||||
@@ -841,7 +1006,7 @@
|
|||||||
const unit = options.unit || '';
|
const unit = options.unit || '';
|
||||||
const valueDigits = options.valueDigits || 0;
|
const valueDigits = options.valueDigits || 0;
|
||||||
|
|
||||||
new ApexCharts(element, {
|
chartInstances[selector] = new ApexCharts(element, {
|
||||||
chart: {
|
chart: {
|
||||||
type: 'donut',
|
type: 'donut',
|
||||||
height: options.height || 330,
|
height: options.height || 330,
|
||||||
@@ -975,7 +1140,9 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).render();
|
});
|
||||||
|
|
||||||
|
chartInstances[selector].render();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderBar(selector, data, options = {}) {
|
function renderBar(selector, data, options = {}) {
|
||||||
@@ -985,6 +1152,13 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (chartInstances[selector]) {
|
||||||
|
chartInstances[selector].destroy();
|
||||||
|
delete chartInstances[selector];
|
||||||
|
}
|
||||||
|
|
||||||
|
element.innerHTML = '';
|
||||||
|
|
||||||
if (!hasSeries(data)) {
|
if (!hasSeries(data)) {
|
||||||
element.innerHTML = '<div class="text-center text-body-secondary py-5">Belum ada data.</div>';
|
element.innerHTML = '<div class="text-center text-body-secondary py-5">Belum ada data.</div>';
|
||||||
return;
|
return;
|
||||||
@@ -1007,7 +1181,7 @@
|
|||||||
? options.height || 330
|
? options.height || 330
|
||||||
: (options.height || 330) + Math.max(maxLabelLines - 3, 0) * 16;
|
: (options.height || 330) + Math.max(maxLabelLines - 3, 0) * 16;
|
||||||
|
|
||||||
new ApexCharts(element, {
|
chartInstances[selector] = new ApexCharts(element, {
|
||||||
chart: {
|
chart: {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
height: chartHeight,
|
height: chartHeight,
|
||||||
@@ -1165,91 +1339,95 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).render();
|
});
|
||||||
|
|
||||||
|
chartInstances[selector].render();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderDonut(
|
function renderLahanCharts() {
|
||||||
'#chart-petani-komoditas',
|
lahanChartsDirty = false;
|
||||||
chartData.petaniPerKomoditas,
|
|
||||||
colorsFor(0),
|
renderDonut(
|
||||||
{
|
'#chart-petani-komoditas',
|
||||||
totalLabel: 'Petani',
|
chartData.petaniPerKomoditas,
|
||||||
unit: 'petani',
|
colorsFor(0),
|
||||||
|
{
|
||||||
|
totalLabel: 'Petani',
|
||||||
|
unit: 'petani',
|
||||||
|
valueDigits: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
renderBar('#chart-luas-kecamatan', chartData.luasLahanPerKecamatan, {
|
||||||
|
name: 'Luas lahan',
|
||||||
|
suffix: 'ha',
|
||||||
|
colors: colorsFor(1),
|
||||||
|
height: 330,
|
||||||
|
horizontal: true,
|
||||||
|
labelWidth: 150,
|
||||||
|
valueDigits: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
renderDonut(
|
||||||
|
'#chart-kondisi-tanaman',
|
||||||
|
chartData.kondisiTanaman,
|
||||||
|
colorsFor(2),
|
||||||
|
{
|
||||||
|
totalLabel: 'Data',
|
||||||
|
valueDigits: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
renderDonut(
|
||||||
|
'#chart-status-kepemilikan-lahan',
|
||||||
|
chartData.statusKepemilikanLahan,
|
||||||
|
colorsFor(3),
|
||||||
|
{
|
||||||
|
totalLabel: 'Lahan',
|
||||||
|
unit: 'lahan',
|
||||||
|
valueDigits: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
renderBar('#chart-serangan-hama', chartData.seranganHama, {
|
||||||
|
name: 'Data',
|
||||||
|
colors: colorsFor(4),
|
||||||
|
height: 330,
|
||||||
valueDigits: 0,
|
valueDigits: 0,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
renderBar('#chart-luas-kecamatan', chartData.luasLahanPerKecamatan, {
|
renderBar('#chart-penyakit-tanaman', chartData.penyakitTanaman, {
|
||||||
name: 'Luas lahan',
|
name: 'Data',
|
||||||
suffix: 'ha',
|
colors: colorsFor(5),
|
||||||
colors: colorsFor(1),
|
height: 330,
|
||||||
height: 330,
|
|
||||||
horizontal: true,
|
|
||||||
labelWidth: 150,
|
|
||||||
valueDigits: 2,
|
|
||||||
});
|
|
||||||
|
|
||||||
renderDonut(
|
|
||||||
'#chart-kondisi-tanaman',
|
|
||||||
chartData.kondisiTanaman,
|
|
||||||
colorsFor(2),
|
|
||||||
{
|
|
||||||
totalLabel: 'Data',
|
|
||||||
valueDigits: 0,
|
valueDigits: 0,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
renderDonut(
|
renderBar('#chart-keparahan-serangan', chartData.keparahanSerangan, {
|
||||||
'#chart-status-kepemilikan-lahan',
|
name: 'Data',
|
||||||
chartData.statusKepemilikanLahan,
|
colors: colorsFor(6),
|
||||||
colorsFor(3),
|
height: 330,
|
||||||
{
|
|
||||||
totalLabel: 'Lahan',
|
|
||||||
unit: 'lahan',
|
|
||||||
valueDigits: 0,
|
valueDigits: 0,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
renderBar('#chart-serangan-hama', chartData.seranganHama, {
|
renderBar('#chart-status-hak-kelola-lahan', chartData.statusHakKelolaLahan, {
|
||||||
name: 'Data',
|
name: 'Petani',
|
||||||
colors: colorsFor(4),
|
colors: colorsFor(7),
|
||||||
height: 330,
|
height: 330,
|
||||||
valueDigits: 0,
|
horizontal: true,
|
||||||
});
|
labelWidth: 180,
|
||||||
|
valueDigits: 0,
|
||||||
|
});
|
||||||
|
|
||||||
renderBar('#chart-penyakit-tanaman', chartData.penyakitTanaman, {
|
renderBar('#chart-praktik-pemupukan', chartData.praktikPemupukan, {
|
||||||
name: 'Data',
|
name: 'Petani',
|
||||||
colors: colorsFor(5),
|
colors: colorsFor(8),
|
||||||
height: 330,
|
height: 330,
|
||||||
valueDigits: 0,
|
horizontal: true,
|
||||||
});
|
labelWidth: 180,
|
||||||
|
valueDigits: 0,
|
||||||
renderBar('#chart-keparahan-serangan', chartData.keparahanSerangan, {
|
});
|
||||||
name: 'Data',
|
}
|
||||||
colors: colorsFor(6),
|
|
||||||
height: 330,
|
|
||||||
valueDigits: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
renderBar('#chart-status-hak-kelola-lahan', chartData.statusHakKelolaLahan, {
|
|
||||||
name: 'Petani',
|
|
||||||
colors: colorsFor(7),
|
|
||||||
height: 330,
|
|
||||||
horizontal: true,
|
|
||||||
labelWidth: 180,
|
|
||||||
valueDigits: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
renderBar('#chart-praktik-pemupukan', chartData.praktikPemupukan, {
|
|
||||||
name: 'Petani',
|
|
||||||
colors: colorsFor(8),
|
|
||||||
height: 330,
|
|
||||||
horizontal: true,
|
|
||||||
labelWidth: 180,
|
|
||||||
valueDigits: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
let productionChartsRendered = false;
|
|
||||||
|
|
||||||
function renderProductionCharts() {
|
function renderProductionCharts() {
|
||||||
if (productionChartsRendered) {
|
if (productionChartsRendered) {
|
||||||
@@ -1319,6 +1497,16 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderLahanCharts();
|
||||||
|
|
||||||
|
document
|
||||||
|
.querySelector('[data-bs-target="#indicator-tab-lahan-tanaman"]')
|
||||||
|
?.addEventListener('shown.bs.tab', function () {
|
||||||
|
if (lahanChartsDirty) {
|
||||||
|
renderLahanCharts();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
document
|
document
|
||||||
.querySelector('[data-bs-target="#indicator-tab-produksi"]')
|
.querySelector('[data-bs-target="#indicator-tab-produksi"]')
|
||||||
?.addEventListener('shown.bs.tab', renderProductionCharts);
|
?.addEventListener('shown.bs.tab', renderProductionCharts);
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ Route::middleware('auth')
|
|||||||
Route::get('/dashboard/bumdes', [App\Http\Controllers\Web\Bumdes\PengurusBumdesController::class, 'index'])->name('dashboard.bumdes');
|
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/demografi', [App\Http\Controllers\Web\Dashboard\DemografiPetaniController::class, 'index'])->name('dashboard.demografi-petani');
|
||||||
Route::get('/dashboard/petani/demografi/table',[App\Http\Controllers\Web\Dashboard\DemografiPetaniController::class, 'data'])->name('dashboard.demografi-petani.data');
|
Route::get('/dashboard/petani/demografi/table',[App\Http\Controllers\Web\Dashboard\DemografiPetaniController::class, 'data'])->name('dashboard.demografi-petani.data');
|
||||||
|
Route::get('/dashboard/petani/indikator-pertanian/data', [App\Http\Controllers\Web\Dashboard\IndikatorPertanianController::class, 'data'])->name('dashboard.indikator-pertanian.data');
|
||||||
Route::get('/dashboard/petani/indikator-pertanian', [App\Http\Controllers\Web\Dashboard\IndikatorPertanianController::class, 'index'])->name('dashboard.indikator-pertanian');
|
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');
|
Route::get('/dashboard/petani/indeks', [App\Http\Controllers\Web\Dashboard\IndeksPetaniController::class, 'index'])->name('dashboard.indeks-petani');
|
||||||
|
|||||||
Reference in New Issue
Block a user