This commit is contained in:
2026-10-16 15:11:49 +07:00
25 changed files with 1161 additions and 828 deletions
@@ -223,7 +223,6 @@ class SummaryController extends Controller
$healthSourceName = $tanaman->isNotEmpty() ? 'tanaman' : 'lahan';
$landMetrics = $this->karakteristikLandMetrics($request);
$totalPetani = $landMetrics['total_petani'];
$totalLuasLahan = $landMetrics['total_luas_lahan'];
$avgLuasPerPetani = $landMetrics['ratarata_luas_lahan'];
$actualProductivity = $this->actualProductivityByCommodityChart($request);
@@ -233,14 +232,6 @@ class SummaryController extends Controller
$aksesPasar = $this->filteredAksesPasar($request);
$kpiCards = [
[
'value' => number_format($totalPetani, 0, ',', '.'),
'unit' => 'orang',
'label' => 'Jumlah Petani',
'description' => 'Petani sesuai filter yang dipilih',
'icon' => 'tabler-users',
'variant' => 'primary',
],
[
'value' => number_format($totalLuasLahan, 2, ',', '.'),
'unit' => 'ha',
@@ -294,7 +285,7 @@ class SummaryController extends Controller
: $value,
),
'praktikSortasiPanen' => $this->enumOptionChart($surveyPanen, 'praktik_sortasi', PraktikSortasi::class),
'praktikFermentasiPascapanen' => $this->fermentationPracticeChart($aksesPasar),
'praktikFermentasiPascapanen' => $this->fermentationPracticeByCommodityChart($aksesPasar),
'produktivitasAktualPerKomoditas' => $actualProductivity,
],
];
@@ -312,7 +303,6 @@ class SummaryController extends Controller
'desa' => (string) $request->input('desaKelurahan_id', ''),
],
'levels' => $levels,
'cultivationLevels' => $this->productionCultivationMapLevelData($request, $levels),
'information' => $this->productionMapInformationData($request),
];
}
@@ -511,6 +501,7 @@ class SummaryController extends Controller
'map_lahan.path',
'map_lahan.luas_lahan',
'map_lahan.jumlah_tanaman',
'map_lahan.petani_id',
'map_lahan.umur_tanaman',
'map_lahan.serangan_hama',
'map_lahan.penyakit_tanaman',
@@ -537,58 +528,10 @@ class SummaryController extends Controller
])
->groupBy('lahan_id');
$geoLahan = $mapLahan
->filter(fn ($lahan) => $this->hasMapPolygon($lahan->path))
->map(function ($lahan) use ($tanamanByLahan) {
$tanamanRows = $tanamanByLahan->get($lahan->id, collect());
$komoditas = $this->productionCommodityDetails($lahan, $tanamanRows);
$opt = $this->productionOptDetails($lahan, $tanamanRows);
return [
'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,
'komoditas' => $komoditas,
'opt' => $opt,
];
})
->values()
->all();
return [
'provinsi' => array_values($geoProvinsi),
'kabupaten' => array_values($geoKabupaten),
'kecamatan' => array_values($geoKecamatan),
'desa' => array_values($geoDesa),
'lahan' => $geoLahan,
];
}
private function productionCultivationMapLevelData(Request $request, array $baseLevels): array
{
$mapLahan = (clone $this->filteredLahanQuery($request))
->whereNotNull('map_lahan.path')
->leftJoin('profiles', 'profiles.id', '=', 'map_lahan.profile_id')
->leftJoin('petani', 'petani.id', '=', 'map_lahan.petani_id')
->tap(fn ($query) => $this->whereActiveMapOwner($query))
->orderByDesc('map_lahan.updated_at')
->limit(500)
->get([
'map_lahan.id',
'map_lahan.desa_kelurahan_id',
'map_lahan.path',
'map_lahan.luas_lahan',
'map_lahan.petani_id',
'profiles.nama as profile_nama',
'petani.nama as petani_nama',
]);
$petaniIds = $mapLahan->pluck('petani_id')->filter()->unique()->values();
$practiceByPetani = DB::table('praktik_budidaya')
->whereNull('praktik_budidaya.deleted_at')
->when($petaniIds->isNotEmpty(), fn ($query) => $query->whereIn('praktik_budidaya.petani_id', $petaniIds))
->when($petaniIds->isEmpty(), fn ($query) => $query->whereRaw('1 = 0'))
->whereIn('praktik_budidaya.petani_id', $petaniIds)
->orderByDesc('praktik_budidaya.updated_at')
->get([
'praktik_budidaya.petani_id',
@@ -601,7 +544,10 @@ class SummaryController extends Controller
$geoLahan = $mapLahan
->filter(fn ($lahan) => $this->hasMapPolygon($lahan->path))
->map(function ($lahan) use ($practiceByPetani) {
->map(function ($lahan) use ($tanamanByLahan, $practiceByPetani) {
$tanamanRows = $tanamanByLahan->get($lahan->id, collect());
$komoditas = $this->productionCommodityDetails($lahan, $tanamanRows);
$opt = $this->productionOptDetails($lahan, $tanamanRows);
$practice = $practiceByPetani->get($lahan->petani_id);
return [
@@ -610,6 +556,8 @@ class SummaryController extends Controller
'polygon' => $lahan->path,
'nama_petani' => $lahan->petani_nama ?? $lahan->profile_nama ?? 'N/A',
'luas' => (float) $lahan->luas_lahan,
'komoditas' => $komoditas,
'opt' => $opt,
'pemangkasan' => $this->enumLabel(FrekuensiPemangkasanUtama::class, $practice->frekuensi_pemangkasan_utama ?? null, 'Belum ada data'),
'pemupukan' => $this->enumLabel(PemberianPupuk::class, $practice->pemberian_pupuk ?? null, 'Belum ada data'),
'praktik_baik' => $this->arrayValues($practice->kebiasaan_konservasi_tanah ?? null),
@@ -619,10 +567,10 @@ class SummaryController extends Controller
->all();
return [
'provinsi' => $baseLevels['provinsi'] ?? [],
'kabupaten' => $baseLevels['kabupaten'] ?? [],
'kecamatan' => $baseLevels['kecamatan'] ?? [],
'desa' => $baseLevels['desa'] ?? [],
'provinsi' => array_values($geoProvinsi),
'kabupaten' => array_values($geoKabupaten),
'kecamatan' => array_values($geoKecamatan),
'desa' => array_values($geoDesa),
'lahan' => $geoLahan,
];
}
@@ -826,7 +774,12 @@ class SummaryController extends Controller
return DB::table('akses_pasar')
->whereNull('akses_pasar.deleted_at')
->whereIn('akses_pasar.petani_id', $petaniIds)
->get(['praktik_fermentasi']);
->leftJoin('petani as akses_petani', 'akses_petani.id', '=', 'akses_pasar.petani_id')
->leftJoin('master_komoditas as akses_komoditas', 'akses_komoditas.id', '=', 'akses_petani.komoditas_id')
->get([
'akses_pasar.praktik_fermentasi',
'akses_komoditas.nama as komoditas_nama',
]);
}
private function filteredSurveyProduktivitas(Request $request)
@@ -1254,10 +1207,10 @@ class SummaryController extends Controller
];
}
private function fermentationPracticeChart($rows): array
private function fermentationPracticeByCommodityChart($rows): array
{
$labels = ['Ya, rutin', 'Pernah mencoba', 'Belum pernah'];
$counts = collect($labels)->mapWithKeys(fn ($label) => [$label => 0]);
$matrix = [];
foreach ($rows as $row) {
$label = $this->fermentationPracticeLabel($row->praktik_fermentasi ?? null);
@@ -1266,12 +1219,21 @@ class SummaryController extends Controller
continue;
}
$counts[$label] = (int) $counts[$label] + 1;
$commodity = $row->komoditas_nama ?: 'Belum ditentukan';
$matrix[$commodity] ??= array_fill_keys($labels, 0);
$matrix[$commodity][$label]++;
}
ksort($matrix, SORT_NATURAL | SORT_FLAG_CASE);
return [
'labels' => $labels,
'series' => collect($labels)->map(fn ($label) => (int) ($counts[$label] ?? 0))->values()->all(),
'labels' => array_keys($matrix),
'series' => collect($labels)
->map(fn ($label) => [
'name' => $label,
'data' => collect($matrix)->map(fn ($counts) => (int) $counts[$label])->values()->all(),
])
->all(),
];
}
@@ -49,20 +49,43 @@ class ProfilPetaniController extends Controller
$skorPetani = SkorPetani::where('petani_id', $id)->first();
$enumerator = Profile::where('user_id', $petani->created_by)->first();
$komoditasUtama = Komoditas::where('id', $petani->komoditas_id)->first();
$lahan = Lahan::where('petani_id', $id )->get();
$lahan = Lahan::query()
->select(['id', 'petani_id', 'nama', 'path', 'luas_lahan', 'komoditas_id', 'desa_kelurahan_id'])
->with([
'komoditas:id,nama',
'desaKelurahan:id,nama,path,kecamatan_id',
'desaKelurahan.kecamatan:id,nama,path',
])
->where('petani_id', $id)
->get();
$desaKelurahan = DesaKelurahan::with('kecamatan')
->where('id', $petani->desa_kelurahan_id)
->first();
$ktpMasked = $petani->ktp ? Str::mask($petani->ktp, '*', 4, -4) : '-';
$profiling = $skorPetani ? $this->buildProfiling($skorPetani) : null;
$assessmentFields = [
'skor_kesiapan_adopsi',
'skor_d2_sistem_praktik_budidaya',
'skor_d4_sosial_ekonomi',
'skor_d6_rantai_nilai_akses_pasar',
'skor_komp_1_d4_k3_motivasi_kapasitas_adopsi',
'skor_komp_2_d4_k2_ekonomi_rumah_tangga',
'skor_komp_3_d2_praktik_budidaya_gap',
'skor_komp_4_d4_k4_keterlibatan_kelembagaan',
'skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan',
'skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar',
];
$hasAssessment = $skorPetani !== null && collect($assessmentFields)->every(
fn (string $field) => $skorPetani->{$field} !== null
);
$profiling = $hasAssessment ? $this->buildProfiling($skorPetani) : null;
$tanaman = Tanaman::where('petani_id', $petani->id)
->where('komoditas_id', $petani->komoditas_id)
->first();
if ($tanaman) {
if ($tanaman?->updated_at && $tanaman->umur_tanaman !== null) {
$tahunInputTanaman = $tanaman->updated_at->year;
$umurTanaman = (int) $tanaman->umur_tanaman;
$tahunSekarang = now()->year;
@@ -162,8 +185,8 @@ class ProfilPetaniController extends Controller
$d4SosialEkonomi = null;
$terdaftarKelompok = $sosialEkonomi->terdaftar_kelompok_tani;
$peranKelompok = $sosialEkonomi->peran_dalam_kelompok;
$terdaftarKelompok = $sosialEkonomi?->terdaftar_kelompok_tani;
$peranKelompok = $sosialEkonomi?->peran_dalam_kelompok;
if ($sosialEkonomi) {
@@ -207,8 +230,8 @@ class ProfilPetaniController extends Controller
'icon' => 'tabler-minus',
],
default => [
'label' => Str::headline($status),
'short' => Str::headline($status),
'label' => filled($status) ? Str::headline($status) : 'Belum diisi',
'short' => filled($status) ? Str::headline($status) : 'Belum diisi',
'color' => 'secondary',
'icon' => 'tabler-help-circle',
],
@@ -434,12 +457,61 @@ class ProfilPetaniController extends Controller
],
];
// Report missing profile data without treating an unanswered field as zero.
$missingSections = [];
$identityFields = [
'nama' => 'Nama', 'ktp' => 'NIK', 'tanggal_lahir' => 'Tanggal lahir',
'jenis_kelamin' => 'Jenis kelamin', 'pendidikan_terakhir' => 'Pendidikan terakhir',
'status_perkawinan' => 'Status perkawinan',
];
foreach ($identityFields as $field => $label) {
if (blank($petani->{$field})) {
$missingSections[] = $label;
}
}
if (!$desaKelurahan?->nama || !$desaKelurahan?->kecamatan?->nama) {
$missingSections[] = 'Wilayah desa/kecamatan';
}
if (!$komoditasUtama?->nama) {
$missingSections[] = 'Komoditas utama';
}
if ($lahan->isEmpty()) {
$missingSections[] = 'Data lahan';
}
if ($tahunTanam === null) {
$missingSections[] = 'Umur tanaman';
}
if (!$praktikBudidaya || $indikatorOnFarm->contains(fn ($item) => $item['skor'] === null)) {
$missingSections[] = 'Praktik budidaya';
}
if ($surveyPanen?->hasil_komoditas_utama_permusim === null) {
$missingSections[] = 'Survei panen';
}
$socialFields = [
'jumlah_tanggungan', 'skor_kecukupan_penghasilan', 'skor_kemampuan_menabung',
'status_kepemilikan_lahan', 'terdaftar_kelompok_tani', 'status_rekening_tabungan',
'pengalaman_kredit_kebun', 'keterikatan_hutang_panen', 'praktik_ijon',
'kebebasan_memilih_pembeli',
];
if (!$sosialEkonomi || collect($socialFields)->contains(fn ($field) => blank($sosialEkonomi->{$field}))) {
$missingSections[] = 'Sosial ekonomi';
}
if (!$aksesPasar || blank($aksesPasar->saluran_terdahulu)) {
$missingSections[] = 'Akses pasar';
}
if (!$hasAssessment) {
$missingSections[] = 'Penilaian kesiapan adopsi belum lengkap';
}
$data = [
'missingSections' => $missingSections,
'hasAssessment' => $hasAssessment,
'petani' => $petani,
'skorPetani' => $skorPetani,
'enumerator' => $enumerator,
'komoditasUtama' => $komoditasUtama,
'lahan' => $lahan,
'farmerLandMap' => $this->farmerLandMapData($lahan),
'desaKelurahan' => $desaKelurahan,
'ktpMasked' => $ktpMasked,
'profiling' => $profiling,
@@ -447,7 +519,6 @@ class ProfilPetaniController extends Controller
'umurTanaman' => $umurTanaman,
'surveyPanen' => $surveyPanen,
'praktikBudidaya' => $praktikBudidaya,
'praktikBudidaya' => $praktikBudidaya,
'indikatorOnFarm' => $indikatorOnFarm,
'detailOnFarm' => $detailOnFarm,
'sosialEkonomi' => $sosialEkonomi,
@@ -464,6 +535,35 @@ class ProfilPetaniController extends Controller
return view('produktivitas.profil-petani.index', $data);
}
private function farmerLandMapData(\Illuminate\Support\Collection $lands): array
{
$regions = ['desa' => [], 'kecamatan' => []];
$plots = [];
foreach ($lands as $land) {
$plot = [
'id' => $land->id,
'nama' => $land->nama,
'path' => $land->path,
'luas' => $land->luas_lahan,
'komoditas' => $land->komoditas?->nama,
];
foreach (['desa' => $land->desaKelurahan, 'kecamatan' => $land->desaKelurahan?->kecamatan] as $level => $region) {
$plot[$level . '_id'] = $region?->id;
if ($region && !isset($regions[$level][$region->id])) {
$regions[$level][$region->id] = [
'id' => $region->id,
'nama' => $region->nama,
'path' => $region->path,
];
}
}
$plots[] = $plot;
}
return ['lands' => $plots, 'regions' => $regions];
}
private function getScoreColor(?int $score): string
{
if ($score === null) {