Update Alert Profil Petani

This commit is contained in:
2026-11-04 16:20:33 +07:00
parent 41da3a71ee
commit 89c5a1371a
10 changed files with 943 additions and 1 deletions
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace App\Enums;
// Status kelengkapan data profil petani.
// Draf → Lengkap (ditandai enumerator penginput) → Terverifikasi (oleh koordinator, menu menyusul).
enum StatusDataPetani: string
{
case DRAFT = 'draft';
case LENGKAP = 'lengkap';
case TERVERIFIKASI = 'terverifikasi';
public function label(): string
{
return match ($this) {
self::DRAFT => 'Draf',
self::LENGKAP => 'Lengkap',
self::TERVERIFIKASI => 'Terverifikasi',
};
}
// Warna badge Vuexy (bg-label-{warna})
public function warna(): string
{
return match ($this) {
self::DRAFT => 'secondary',
self::LENGKAP => 'info',
self::TERVERIFIKASI => 'success',
};
}
public static function values(): array
{
return array_column(self::cases(), 'value');
}
}
@@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\Web\Produktivitas;
use App\Enums\StatusDataPetani;
use App\Http\Controllers\Controller;
use App\Models\Pertanian\Petani;
use App\Services\Produktivitas\KelengkapanPetani;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
/**
* Enumerator penginput menandai data petani Lengkap, atau membatalkannya kembali ke Draf.
* Perubahan ke Terverifikasi dilakukan koordinator (menu menyusul), bukan di sini.
*/
class PetaniStatusController extends Controller
{
public function update(Request $request, Petani $petani)
{
abort_unless($petani->bisaDitandaiOleh($request->user()), 403);
$validated = $request->validate([
'status' => ['required', Rule::in([StatusDataPetani::LENGKAP->value, StatusDataPetani::DRAFT->value])],
]);
$tujuan = StatusDataPetani::from($validated['status']);
if ($petani->status === StatusDataPetani::TERVERIFIKASI) {
return back()->with('status_petani_error', 'Data petani sudah terverifikasi. Hubungi koordinator untuk mengubahnya.');
}
if ($tujuan === StatusDataPetani::LENGKAP) {
if ($petani->status !== StatusDataPetani::DRAFT) {
return back()->with('status_petani_error', 'Data petani sudah ditandai lengkap.');
}
$wajibKosong = KelengkapanPetani::untuk($petani)->jumlahWajibKosong();
if ($wajibKosong > 0) {
return back()->with('status_petani_error', "Masih ada {$wajibKosong} isian wajib yang kosong.");
}
}
if ($tujuan === StatusDataPetani::DRAFT && $petani->status !== StatusDataPetani::LENGKAP) {
return back()->with('status_petani_error', 'Data petani masih berstatus draf.');
}
$petani->status = $tujuan;
$petani->updated_by = $request->user()->id;
$petani->save();
return back()->with('status_petani_success', $tujuan === StatusDataPetani::LENGKAP
? 'Data petani berhasil ditandai lengkap.'
: 'Tanda lengkap berhasil dibatalkan. Status kembali ke draf.');
}
}
+16
View File
@@ -4,6 +4,8 @@ namespace App\Models\Pertanian;
use App\Enums\Pendidikan; use App\Enums\Pendidikan;
use App\Enums\Pernikahan; use App\Enums\Pernikahan;
use App\Enums\Role;
use App\Enums\StatusDataPetani;
use App\Models\User; use App\Models\User;
use App\Models\Wilayah\DesaKelurahan; use App\Models\Wilayah\DesaKelurahan;
use App\Models\Pertanian\Lahan; use App\Models\Pertanian\Lahan;
@@ -70,6 +72,12 @@ class Petani extends Model
'pendidikan_terakhir' => Pendidikan::class, 'pendidikan_terakhir' => Pendidikan::class,
'status_perkawinan' => Pernikahan::class, 'status_perkawinan' => Pernikahan::class,
'status' => StatusDataPetani::class,
];
// Sengaja tidak masuk $fillable: status hanya diubah lewat PetaniStatusController.
protected $attributes = [
'status' => 'draft',
]; ];
public function getUmurAttribute(): ?int public function getUmurAttribute(): ?int
@@ -113,6 +121,14 @@ class Petani extends Model
return $this->belongsTo(Komoditas::class, 'komoditas_id'); return $this->belongsTo(Komoditas::class, 'komoditas_id');
} }
// Hanya enumerator yang menginput petani ini yang boleh menandai Lengkap / membatalkannya.
public function bisaDitandaiOleh(?User $user): bool
{
return $user
&& $user->role === Role::ENUMERATOR
&& (int) $this->created_by === (int) $user->id;
}
// Pertanyaan penyerbukan manual hanya berlaku untuk petani vanili. // Pertanyaan penyerbukan manual hanya berlaku untuk petani vanili.
public function isKomoditasVanili(): bool public function isKomoditasVanili(): bool
{ {
@@ -0,0 +1,466 @@
<?php
namespace App\Services\Produktivitas;
use App\Enums\AksesPasar\PraktikFermentasi;
use App\Enums\PraktikBudidaya\MetodePenangananHamaPenyakit;
use App\Enums\PraktikBudidaya\PemberianPupuk;
use App\Enums\SosialEkonomi\TerdaftarKelompokTani;
use App\Models\Pertanian\Lahan;
use App\Models\Pertanian\Petani;
use Illuminate\Support\Collection;
/**
* Daftar isian Profil Petani yang WAJIB (dipakai skor Kesiapan Adopsi)
* dan PERLU (dipakai dashboard Karakteristik, Produksi, Pasar).
*
* Satu-satunya tempat daftar ini ditulis. Dipakai oleh ringkasan kelengkapan
* di atas setiap tab petani, dan nanti oleh fitur verifikasi.
*
* Isian yang tidak berlaku (mis. jenis hama kalau "tidak ada hama",
* data kelompok kalau bukan anggota) tidak dimasukkan.
*/
class KelengkapanPetani
{
public const WAJIB = 'wajib';
public const PERLU = 'perlu';
public const TAB_LABEL = [
'profil' => 'Profil',
'lahan' => 'Lahan & Tanaman',
'praktik-budidaya' => 'Praktik Budidaya',
'sosial-ekonomi' => 'Sosial Ekonomi',
'akses-pasar' => 'Akses Pasar',
];
private const KONSERVASI_TIDAK_ADA = 'Tidak ada yang seperti itu di kebun saya';
/*
* Seluruh isian Profil Petani (total 209 untuk 1 lahan), per tabel.
* Aturan hitung: satu pertanyaan = satu isian; kolom repeater dihitung sekali per kolom;
* teks "Lainnya" tidak dihitung; provinsi/kabupaten/kecamatan ikut terisi bila desa terisi.
*/
private const ISIAN_PROFIL = [ // 15 + 3 wilayah = 18
'nama', 'nomor_telepon', 'alamat', 'kk', 'ktp', 'path_kk', 'path_ktp', 'foto',
'jenis_kelamin', 'tempat_lahir', 'tanggal_lahir', 'pendidikan_terakhir', 'status_perkawinan',
'komoditas_id', 'desa_kelurahan_id',
];
private const ISIAN_LAHAN = [ // 18 + 3 wilayah = 21, per lahan
'desa_kelurahan_id', 'path', 'luas_lahan', 'komoditas_id', 'jenis_komoditas_id', 'varietas_id',
'jumlah_tanaman', 'kondisi_tanaman', 'serangan_hama', 'penyakit_tanaman', 'keparahan_serangan',
'jenis_hama', 'jenis_penyakit', 'umur_tanaman', 'umur_lahan', 'komoditas_lain', 'tanaman_penaung', 'foto',
];
private const ISIAN_PRAKTIK_BUDIDAYA = [ // 39
'tenaga_kerja', 'frekuensi_pemangkasan_utama', 'teknik_pemangkasan', 'frekuensi_pemangkasan_penaung',
'penyerbukan_manual', 'deskripsi_penyerbukan_manual', 'tingkat_keberhasilan_penyerbukan',
'pemberian_pupuk', 'jenis_pupuk', 'frekuensi_pemberian_pupuk', 'estimasi_dosis_pupuk_organik',
'estimasi_dosis_pupuk_anorganik', 'waktu_pemberian_pupuk', 'cara_pemberian_pupuk',
'metode_penanganan_opt', 'jenis_hama', 'jenis_penyakit', 'intensitas_serangan', 'waktu_kemunculan_terparah',
'metode_paling_sering_dilakukan', 'volume_obat_obatan_padat_permusim', 'volume_obat_obatan_cair_permusim',
'frekuensi_sanitasi_lahan', 'penanganan_sisa_buah_ranting', 'penanganan_gulma',
'kondisi_pengelolaan_air', 'sumber_air', 'sistem_irigasi', 'kebutuhan_air_permusim',
'kebiasaan_konservasi_tanah', 'pemahaman_manfaat_konservasi_tanah',
'upah_tenaga_kerja', 'biaya_bibit', 'biaya_pupuk', 'biaya_obat_obatan', 'biaya_sewa_alat_produksi',
'biaya_penyediaan_air', 'biaya_produksi_lainnya', 'total_estimasi_biaya_produksi',
];
private const ISIAN_PANEN = [ // 21
'dasar_penentuan_panen', 'frekuensi_panen_pertahun', 'bulan_panen_utama', 'hasil_komoditas_utama_permusim',
'hasil_komoditas_lainnya_permusim', 'disimpan_untuk_sendiri', 'hasil_panen_disimpan', 'tujuan_disimpan',
'praktik_sortasi', 'bentuk_sortasi', 'hasil_panen_reject', 'jumlah_panen_reject', 'tindakan_panen_reject',
'upah_tenaga_panen', 'biaya_pengangkutan_panen', 'biaya_wadah_panen', 'biaya_sewa_alat_panen',
'biaya_panen_lainnya', 'total_estimasi_biaya_panen', 'estimasi_kehilangan_akibat_opt',
'estimasi_kehilangan_akibat_hama_penyakit',
];
private const ISIAN_PRODUKTIVITAS = ['komoditas_id', 'kuantitas', 'satuan']; // 3 (repeater)
private const ISIAN_PELATIHAN = ['jenis_pelatihan', 'penyelenggara', 'tahun_pelaksanaan']; // 3 (repeater)
private const ISIAN_SERTIFIKASI = ['jenis_sertifikasi', 'lembaga_penerbit', 'tahun_pelaksanaan', 'is_aktif']; // 4 (repeater)
private const ISIAN_SOSIAL_EKONOMI = [ // 70 + 4 bantuan NGO + 4 pendapatan komoditas + 2 pendapatan luar = 80
'jumlah_tanggungan', 'anak_masih_sekolah', 'yang_ikut_bekerja', 'skor_periode_paceklik', 'bulan_paceklik',
'sumber_penghasilan_paceklik', 'total_estimasi_biaya_kebun_per_musim', 'skor_kemampuan_menabung',
'estimasi_tabungan_per_bulan', 'skor_kecukupan_penghasilan', 'alasan_kecukupan_penghasilan',
'status_kepemilikan_lahan', 'skor_kejelasan_status_lahan', 'ada_sertifikat_dokumen', 'ada_sengketa',
'keterangan_sengketa', 'pihak_membantu_konflik', 'pihak_membantu_konflik_nama', 'tahun_bantuan_konflik',
'status_konflik', 'kemauan_mencoba_praktik_baru', 'alasan_mencoba_praktik_baru',
'hambatan_mengubah_cara_bertani', 'pemahaman_mutu_harga', 'contoh_pemahaman_mutu_harga',
'pengetahuan_harga_hasil_olah', 'pernah_jual_harga_lebih_tinggi', 'penyebab_harga_lebih_tinggi',
'usaha_mutu_tambahan_harga', 'alasan_usaha_mutu_tambahan_harga', 'panutan_petani_sukses',
'nama_lokasi_panutan', 'kemauan_ikut_program', 'harapan_program', 'orientasi_rencana_jangka_panjang',
'rencana_jangka_panjang', 'terdaftar_kelompok_tani', 'nama_kelompok_tani', 'lama_bergabung_kelompok_tahun',
'kedekatan_sesama_anggota', 'keaktifan_pertemuan_kelompok', 'topik_biasa_dibahas', 'peran_dalam_kelompok',
'manfaat_kelompok_tani', 'status_kartu_tani', 'bisa_beli_pupuk_bersubsidi', 'manfaat_lain_kartu_tani',
'status_rekening_tabungan', 'nama_bank', 'pengalaman_kredit_kebun', 'alasan_kredit_ditolak',
'kondisi_aset_produktif', 'jenis_aset_produktif', 'keterangan_kondisi_aset',
'keterlibatan_program_keuangan_formal', 'status_program_keuangan_formal', 'pernah_bantuan_ngo_perusahaan',
'keterikatan_hutang_panen', 'pinjaman_tokoh_adat_panen', 'keterangan_pinjaman_tokoh_adat',
'total_hutang_rupiah', 'total_hutang_setara_kg_panen', 'total_hutang_tidak_bersedia', 'praktik_ijon',
'tekanan_jual_segera', 'penyebab_tekanan_jual_segera', 'kebebasan_memilih_pembeli',
'alasan_tidak_bebas_memilih_pembeli', 'minat_pembeli_harga_stabil', 'alasan_minat_pembeli_harga_stabil',
];
private const ISIAN_BANTUAN_NGO = ['nama_lembaga', 'bentuk_bantuan', 'frekuensi', 'tahun_terakhir'];
private const ISIAN_PENDAPATAN_KOMODITAS = ['kategori', 'komoditas', 'pendapatan', 'periode'];
private const ISIAN_PENDAPATAN_LUAR = ['jenis_pekerjaan', 'estimasi_per_bulan'];
private const ISIAN_AKSES_PASAR = [ // 16 + 4 harga jual = 20
'tujuan_penjualan', 'sifat_hubungan_bisnis', 'bentuk_penjualan', 'frekuensi_penjualan', 'skor_pengalaman',
'saluran_terdahulu', 'alasan_berhenti', 'tahu_harga_sebelumnya', 'sumber_info_harga', 'praktik_fermentasi',
'proses_fermentasi', 'model_fermentasi_memungkinkan', 'skor_kesadaran_premium', 'sumber_info_premium',
'tingkat_kepercayaan_pembeli_baru', 'alasan_kepercayaan_pembeli_baru',
];
private const ISIAN_HARGA_JUAL = ['bentuk_produk', 'harga_panen_terakhir', 'harga_tahun_lalu', 'harga_dua_tahun_lalu'];
/**
* Satu instance per objek Petani, supaya header dan ringkasan tidak memuat/menghitung ulang.
* WeakMap: otomatis dibuang saat objek Petani tidak dipakai lagi (tidak menumpuk antar-request).
*/
private static ?\WeakMap $cache = null;
private ?Collection $semuaKosong = null;
private ?array $hitungIsian = null;
public function __construct(private Petani $petani)
{
$petani->loadMissing([
'komoditas',
'lahans',
'praktikBudidaya',
'surveyPanen',
'surveyProduktivitas',
'surveyPelatihan',
'surveySertifikasi',
'sosialEkonomi.pendapatanKomoditas',
'sosialEkonomi.pendapatanLuarPertanian',
'aksesPasar',
'hargaJualPanen',
]);
}
public static function untuk(Petani $petani): self
{
self::$cache ??= new \WeakMap();
return self::$cache[$petani] ??= new self($petani);
}
/** Jumlah isian WAJIB (skor) yang masih kosong di semua tab. */
public function jumlahWajibKosong(): int
{
return $this->semuaKosong()->flatten(1)->where('prioritas', self::WAJIB)->count();
}
/**
* Hitungan seluruh isian: terisi, total (209 untuk 1 lahan; +21 per lahan tambahan),
* tidak_berlaku (pertanyaan lanjutan yang tidak perlu dijawab), berlaku, persen.
*/
public function hitungIsian(): array
{
return $this->hitungIsian ??= $this->hitungSemuaIsian();
}
private function hitungSemuaIsian(): array
{
$hasil = ['terisi' => 0, 'total' => 0, 'tidak_berlaku' => 0];
$p = $this->petani;
// Profil (+ provinsi/kabupaten/kecamatan yang mengikuti desa)
$this->hitungKolom($hasil, $p, self::ISIAN_PROFIL);
$this->hitungNilai($hasil, 3, self::terisi($p->desa_kelurahan_id));
// Lahan & Tanaman, per lahan. Belum ada lahan = dihitung 1 lahan kosong.
$daftarLahan = $p->lahans->isEmpty() ? collect([null]) : $p->lahans;
foreach ($daftarLahan as $lahan) {
$this->hitungKolom($hasil, $lahan, self::ISIAN_LAHAN);
$this->hitungNilai($hasil, 3, self::terisi($lahan?->desa_kelurahan_id));
}
// Praktik Budidaya, Panen, Produktivitas
$pb = $p->praktikBudidaya;
$tidakBerlakuPb = [];
if (! $p->isKomoditasVanili()) {
array_push($tidakBerlakuPb, 'penyerbukan_manual', 'deskripsi_penyerbukan_manual', 'tingkat_keberhasilan_penyerbukan');
}
if ((int) $pb?->pemberian_pupuk === PemberianPupuk::TIDAK_PERNAH->value) {
array_push($tidakBerlakuPb, 'jenis_pupuk', 'frekuensi_pemberian_pupuk', 'estimasi_dosis_pupuk_organik',
'estimasi_dosis_pupuk_anorganik', 'waktu_pemberian_pupuk', 'cara_pemberian_pupuk');
}
if ((int) $pb?->metode_penanganan_opt === MetodePenangananHamaPenyakit::TIDAK_ADA->value) {
array_push($tidakBerlakuPb, 'jenis_hama', 'jenis_penyakit');
}
$this->hitungKolom($hasil, $pb, self::ISIAN_PRAKTIK_BUDIDAYA, $tidakBerlakuPb);
$this->hitungKolom($hasil, $p->surveyPanen, self::ISIAN_PANEN);
$this->hitungRepeater($hasil, $p->surveyProduktivitas, self::ISIAN_PRODUKTIVITAS);
// Pendidikan & Pelatihan
$this->hitungRepeater($hasil, $p->surveyPelatihan, self::ISIAN_PELATIHAN);
$this->hitungRepeater($hasil, $p->surveySertifikasi, self::ISIAN_SERTIFIKASI);
// Sosial Ekonomi (data kelompok tidak berlaku kalau bukan anggota)
$se = $p->sosialEkonomi;
$tidakBerlakuSe = self::nilaiEnum($se?->terdaftar_kelompok_tani) === TerdaftarKelompokTani::TIDAK->value
? ['nama_kelompok_tani', 'lama_bergabung_kelompok_tahun', 'kedekatan_sesama_anggota',
'keaktifan_pertemuan_kelompok', 'topik_biasa_dibahas', 'peran_dalam_kelompok', 'manfaat_kelompok_tani']
: [];
$this->hitungKolom($hasil, $se, self::ISIAN_SOSIAL_EKONOMI, $tidakBerlakuSe);
$this->hitungRepeater($hasil, collect($se?->detail_bantuan_ngo_perusahaan ?? []), self::ISIAN_BANTUAN_NGO);
$this->hitungRepeater($hasil, $se?->pendapatanKomoditas ?? collect(), self::ISIAN_PENDAPATAN_KOMODITAS);
$this->hitungRepeater($hasil, $se?->pendapatanLuarPertanian ?? collect(), self::ISIAN_PENDAPATAN_LUAR);
// Akses Pasar (detail fermentasi tidak berlaku kalau belum pernah)
$ap = $p->aksesPasar;
$tidakBerlakuAp = self::nilaiEnum($ap?->praktik_fermentasi) === PraktikFermentasi::BELUM_PERNAH->value
? ['proses_fermentasi', 'model_fermentasi_memungkinkan']
: [];
$this->hitungKolom($hasil, $ap, self::ISIAN_AKSES_PASAR, $tidakBerlakuAp);
$this->hitungRepeater($hasil, $p->hargaJualPanen, self::ISIAN_HARGA_JUAL);
$hasil['berlaku'] = $hasil['total'] - $hasil['tidak_berlaku'];
$hasil['persen'] = $hasil['berlaku'] > 0 ? (int) floor($hasil['terisi'] / $hasil['berlaku'] * 100) : 0;
return $hasil;
}
private static function nilaiEnum(mixed $nilai): mixed
{
return $nilai instanceof \BackedEnum ? $nilai->value : $nilai;
}
private function hitungKolom(array &$hasil, ?object $model, array $kolom, array $tidakBerlaku = []): void
{
foreach ($kolom as $k) {
$hasil['total']++;
if (in_array($k, $tidakBerlaku, true)) {
$hasil['tidak_berlaku']++;
} elseif ($model && self::terisi($model->{$k})) {
$hasil['terisi']++;
}
}
}
private function hitungNilai(array &$hasil, int $jumlah, bool $terisi): void
{
$hasil['total'] += $jumlah;
$hasil['terisi'] += $terisi ? $jumlah : 0;
}
/** Kolom repeater dianggap terisi kalau minimal satu baris mengisinya. */
private function hitungRepeater(array &$hasil, Collection $baris, array $kolom): void
{
foreach ($kolom as $k) {
$hasil['total']++;
$hasil['terisi'] += $baris->contains(fn ($row) => self::terisi(data_get($row, $k))) ? 1 : 0;
}
}
/**
* Isian yang masih kosong di satu tab.
* Setiap item: label, prioritas (wajib/perlu), input (atribut name di form),
* grup (judul pengelompokan, mis. nama lahan), lahan_id, url (opsional, halaman lain).
*/
public function kosong(string $tab): Collection
{
return $this->semuaKosong()->get($tab, collect());
}
private function hitungKosong(string $tab): Collection
{
$isian = match ($tab) {
'profil' => $this->profil(),
'lahan' => $this->lahan(),
'praktik-budidaya' => $this->praktikBudidaya(),
'sosial-ekonomi' => $this->sosialEkonomi(),
'akses-pasar' => $this->aksesPasar(),
default => [],
};
return collect($isian)
->reject(fn (array $item) => $item['terisi'])
->map(fn (array $item) => collect($item)->except('terisi')->all())
->values();
}
/** Isian kosong semua tab, dikelompokkan per tab (untuk tab Kesiapan Adopsi). */
public function semuaKosong(): Collection
{
return $this->semuaKosong ??= collect(array_keys(self::TAB_LABEL))
->mapWithKeys(fn (string $tab) => [$tab => $this->hitungKosong($tab)]);
}
private function item(string $label, string $prioritas, string $input, mixed $nilai, array $tambahan = []): array
{
return $this->itemCek($label, $prioritas, $input, self::terisi($nilai), $tambahan);
}
/** Untuk isian yang status terisinya dihitung sendiri (mis. repeater). */
private function itemCek(string $label, string $prioritas, string $input, bool $terisi, array $tambahan = []): array
{
return [
'label' => $label,
'prioritas' => $prioritas,
'input' => $input,
'terisi' => $terisi,
'grup' => null,
'lahan_id' => null,
'url' => null,
...$tambahan,
];
}
private static function terisi(mixed $nilai): bool
{
if ($nilai instanceof Collection) {
return $nilai->isNotEmpty();
}
return ! (is_null($nilai) || $nilai === '' || $nilai === []);
}
private function profil(): array
{
$p = $this->petani;
return [
$this->item('Jenis kelamin', self::PERLU, 'jenis_kelamin', $p->jenis_kelamin),
$this->item('Tanggal lahir', self::PERLU, 'tanggal_lahir', $p->tanggal_lahir),
$this->item('Pendidikan terakhir', self::PERLU, 'pendidikan_terakhir', $p->pendidikan_terakhir),
$this->item('Komoditas utama', self::PERLU, 'komoditas_id', $p->komoditas_id),
$this->item('Desa/kelurahan', self::PERLU, 'desa_kelurahan_id', $p->desa_kelurahan_id),
];
}
/** Diisi per lahan; setiap item membawa link ke halaman edit lahannya. */
private function lahan(): array
{
$daftarLahan = $this->petani->lahans->sortBy('id');
if ($daftarLahan->isEmpty()) {
return [$this->itemCek('Belum ada lahan', self::PERLU, '', false, [
'url' => route('petani.lahan-tanaman.create', $this->petani->id),
])];
}
return $daftarLahan->values()->flatMap(function (Lahan $l, int $i) {
$tambahan = [
'grup' => $l->nama ?: 'Lahan ' . ($i + 1),
'lahan_id' => $l->id,
'url' => route('petani.lahan-tanaman.edit', [$this->petani->id, $l->id]),
];
$adaSerangan = self::terisi($l->serangan_hama) || self::terisi($l->penyakit_tanaman);
return array_filter([
$this->item('Luas lahan', self::PERLU, 'luas_lahan', $l->luas_lahan, $tambahan),
$this->item('Peta/polygon lahan', self::PERLU, 'path', $l->path, $tambahan),
$this->item('Desa lahan', self::PERLU, 'desa_kelurahan_id', $l->desa_kelurahan_id, $tambahan),
$this->item('Umur tanaman', self::PERLU, 'umur_tanaman', $l->umur_tanaman, $tambahan),
$this->item('Jumlah tanaman', self::PERLU, 'jumlah_tanaman', $l->jumlah_tanaman, $tambahan),
$this->item('Kondisi tanaman', self::PERLU, 'kondisi_tanaman', $l->kondisi_tanaman, $tambahan),
$this->item('Serangan hama', self::PERLU, 'serangan_hama', $l->serangan_hama, $tambahan),
$this->item('Penyakit tanaman', self::PERLU, 'penyakit_tanaman', $l->penyakit_tanaman, $tambahan),
$adaSerangan
? $this->item('Keparahan serangan', self::PERLU, 'keparahan_serangan', $l->keparahan_serangan, $tambahan)
: null,
]);
})->all();
}
private function praktikBudidaya(): array
{
$pb = $this->petani->praktikBudidaya;
$panen = $this->petani->surveyPanen;
$adaHama = $pb && self::terisi($pb->metode_penanganan_opt)
&& (int) $pb->metode_penanganan_opt !== MetodePenangananHamaPenyakit::TIDAK_ADA->value;
$konservasi = (array) ($pb?->kebiasaan_konservasi_tanah ?? []);
$adaKonservasi = collect($konservasi)->contains(fn ($v) => $v !== self::KONSERVASI_TIDAK_ADA);
return array_filter([
$this->item('Frekuensi pemangkasan', self::WAJIB, 'frekuensi_pemangkasan_komoditas_utama', $pb?->frekuensi_pemangkasan_utama),
$this->item('Pemberian pupuk', self::WAJIB, 'pemberian_pupuk', $pb?->pemberian_pupuk),
$this->item('Metode penanganan hama & penyakit', self::WAJIB, 'metode_penanganan_hama_penyakit', $pb?->metode_penanganan_opt),
$adaHama ? $this->item('Jenis hama', self::PERLU, 'jenis_hama', $pb->jenis_hama) : null,
$adaHama ? $this->item('Jenis penyakit', self::PERLU, 'jenis_penyakit', $pb->jenis_penyakit) : null,
$this->item('Frekuensi sanitasi lahan', self::WAJIB, 'frekuensi_sanitasi_lahan', $pb?->frekuensi_sanitasi_lahan),
$this->item('Penanganan gulma', self::WAJIB, 'penanganan_gulma', $pb?->penanganan_gulma),
$this->item('Kondisi pengelolaan air & irigasi', self::WAJIB, 'kondisi_pengelolaan_air_irigasi', $pb?->kondisi_pengelolaan_air),
$this->item('Sumber air', self::PERLU, 'sumber_air', $pb?->sumber_air),
$this->item('Kebiasaan konservasi tanah', self::WAJIB, 'kebiasaan_konservasi_tanah', $konservasi),
$adaKonservasi
? $this->item('Pemahaman manfaat konservasi', self::WAJIB, 'pemahaman_manfaat_konservasi', $pb->pemahaman_manfaat_konservasi_tanah)
: null,
$this->item('Total estimasi biaya produksi', self::WAJIB, 'total_estimasi_biaya_produksi', $pb?->total_estimasi_biaya_produksi),
$this->item('Dasar penentuan panen', self::WAJIB, 'dasar_penentuan_panen', $panen?->dasar_penentuan_panen),
$this->item('Hasil komoditas utama per musim', self::PERLU, 'hasil_komoditas_utama_permusim', $panen?->hasil_komoditas_utama_permusim),
$this->item('Praktik sortasi', self::WAJIB, 'praktik_sortasi', $panen?->praktik_sortasi),
]);
}
private function sosialEkonomi(): array
{
$se = $this->petani->sosialEkonomi;
$anggotaKelompok = $se?->terdaftar_kelompok_tani === TerdaftarKelompokTani::YA
|| $se?->terdaftar_kelompok_tani === TerdaftarKelompokTani::YA->value;
$adaPendapatan = (bool) $se?->pendapatanKomoditas->contains(fn ($row) => (int) $row->pendapatan > 0);
return array_filter([
// Utama
$this->item('Jumlah tanggungan', self::PERLU, 'jumlah_tanggungan', $se?->jumlah_tanggungan),
$this->itemCek('Pendapatan per komoditas', self::WAJIB, 'pendapatan_komoditas', $adaPendapatan),
$this->item('Periode paceklik', self::WAJIB, 'skor_periode_paceklik', $se?->skor_periode_paceklik),
$this->item('Kemampuan menabung', self::WAJIB, 'skor_kemampuan_menabung', $se?->skor_kemampuan_menabung),
$this->item('Kecukupan penghasilan', self::WAJIB, 'skor_kecukupan_penghasilan', $se?->skor_kecukupan_penghasilan),
$this->item('Kejelasan status lahan', self::WAJIB, 'skor_kejelasan_status_lahan', $se?->skor_kejelasan_status_lahan),
// Sosial budaya
$this->item('Kemauan mencoba praktik baru', self::WAJIB, 'kemauan_mencoba_praktik_baru', $se?->kemauan_mencoba_praktik_baru),
$this->item('Pemahaman mutu & harga', self::WAJIB, 'pemahaman_mutu_harga', $se?->pemahaman_mutu_harga),
$this->item('Panutan petani sukses', self::WAJIB, 'panutan_petani_sukses', $se?->panutan_petani_sukses),
$this->item('Kemauan ikut program', self::WAJIB, 'kemauan_ikut_program', $se?->kemauan_ikut_program),
$this->item('Orientasi rencana jangka panjang', self::WAJIB, 'orientasi_rencana_jangka_panjang', $se?->orientasi_rencana_jangka_panjang),
// Keanggotaan kelompok: pertanyaan lanjutan hanya untuk anggota
$this->item('Terdaftar di kelompok tani', self::WAJIB, 'terdaftar_kelompok_tani', $se?->terdaftar_kelompok_tani),
$anggotaKelompok ? $this->item('Keaktifan pertemuan kelompok', self::WAJIB, 'keaktifan_pertemuan_kelompok', $se->keaktifan_pertemuan_kelompok) : null,
$anggotaKelompok ? $this->item('Peran dalam kelompok', self::WAJIB, 'peran_dalam_kelompok', $se->peran_dalam_kelompok) : null,
$anggotaKelompok ? $this->item('Manfaat kelompok tani', self::WAJIB, 'manfaat_kelompok_tani', $se->manfaat_kelompok_tani) : null,
// Akses keuangan
$this->item('Status rekening tabungan', self::WAJIB, 'status_rekening_tabungan', $se?->status_rekening_tabungan),
$this->item('Pengalaman kredit kebun', self::WAJIB, 'pengalaman_kredit_kebun', $se?->pengalaman_kredit_kebun),
$this->item('Kondisi aset produktif', self::WAJIB, 'kondisi_aset_produktif', $se?->kondisi_aset_produktif),
$this->item('Keterlibatan program keuangan formal', self::WAJIB, 'keterlibatan_program_keuangan_formal', $se?->keterlibatan_program_keuangan_formal),
// Keterikatan hutang
$this->item('Keterikatan hutang panen', self::WAJIB, 'keterikatan_hutang_panen', $se?->keterikatan_hutang_panen),
$this->item('Praktik ijon', self::WAJIB, 'praktik_ijon', $se?->praktik_ijon),
$this->item('Kebebasan memilih pembeli', self::WAJIB, 'kebebasan_memilih_pembeli', $se?->kebebasan_memilih_pembeli),
]);
}
private function aksesPasar(): array
{
$ap = $this->petani->aksesPasar;
$harga = $this->petani->hargaJualPanen;
return [
$this->item('Tujuan penjualan', self::PERLU, 'tujuan_penjualan', $ap?->tujuan_penjualan),
$this->item('Sifat hubungan dengan pembeli', self::PERLU, 'sifat_hubungan_bisnis', $ap?->sifat_hubungan_bisnis),
$this->item('Frekuensi penjualan', self::PERLU, 'frekuensi_penjualan', $ap?->frekuensi_penjualan),
$this->item('Pengalaman menjual ke saluran lain', self::WAJIB, 'skor_pengalaman', $ap?->skor_pengalaman),
$this->item('Praktik fermentasi', self::PERLU, 'praktik_fermentasi', $ap?->praktik_fermentasi),
$this->item('Kesadaran pasar premium', self::WAJIB, 'skor_kesadaran_premium', $ap?->skor_kesadaran_premium),
$this->item('Kepercayaan pada pembeli baru', self::WAJIB, 'tingkat_kepercayaan_pembeli_baru', $ap?->tingkat_kepercayaan_pembeli_baru),
$this->itemCek('Harga jual panen terakhir', self::PERLU, 'harga_jual',
$harga->contains(fn ($h) => self::terisi($h->bentuk_produk) && (float) $h->harga_panen_terakhir > 0)),
$this->itemCek('Harga jual tahun lalu', self::PERLU, 'harga_jual',
$harga->contains(fn ($h) => (float) $h->harga_tahun_lalu > 0)),
$this->itemCek('Harga jual dua tahun lalu', self::PERLU, 'harga_jual',
$harga->contains(fn ($h) => (float) $h->harga_dua_tahun_lalu > 0)),
];
}
}
@@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Status kelengkapan data petani (App\Enums\StatusDataPetani):
* draft → lengkap → terverifikasi. Data lama otomatis "draft".
*/
public function up(): void
{
Schema::table('petani', function (Blueprint $table) {
$table->string('status', 20)->default('draft')->index();
});
}
public function down(): void
{
Schema::table('petani', function (Blueprint $table) {
$table->dropIndex(['status']);
$table->dropColumn('status');
});
}
};
@@ -0,0 +1,197 @@
{{--
Ringkasan isian WAJIB (skor) dan PERLU (dashboard) yang belum terisi di tab petani yang sedang dibuka.
Daftar isiannya ada di App\Services\Produktivitas\KelengkapanPetani.
Klik isian → halaman menggulir ke kolomnya (dicari lewat atribut name).
--}}
@props(['petani', 'lahan' => null])
@use('App\Services\Produktivitas\KelengkapanPetani')
@php
$tab = match (true) {
request()->routeIs('petani.edit') => 'profil',
request()->routeIs('petani.lahan-tanaman.*') => 'lahan',
request()->routeIs('petani.praktik-budidaya.*') => 'praktik-budidaya',
request()->routeIs('petani.sosial-ekonomi.*') => 'sosial-ekonomi',
request()->routeIs('petani.akses-pasar.*') => 'akses-pasar',
request()->routeIs('petani.skor.*') => 'semua',
default => null,
};
$urlTab = fn (string $t) => $t === 'profil'
? route('petani.edit', $petani->id)
: route('petani.' . ($t === 'lahan' ? 'lahan-tanaman' : $t) . '.index', $petani->id);
$kelengkapan = $tab ? KelengkapanPetani::untuk($petani) : null;
if ($tab === 'semua') {
// Tab Kesiapan Adopsi: rangkuman semua tab, klik → pindah ke tab terkait.
$kelompok = $kelengkapan->semuaKosong()
->map(fn ($items, $t) => $items->map(fn ($item) => [
...$item,
'url' => ($item['url'] ?? $urlTab($t)) . ($item['input'] ? '#isian=' . $item['input'] : ''),
]))
->filter(fn ($items) => $items->isNotEmpty())
->mapWithKeys(fn ($items, $t) => [KelengkapanPetani::TAB_LABEL[$t] => $items]);
} elseif ($tab) {
// Di halaman edit lahan, isian lahan yang sedang dibuka langsung digulir (tanpa pindah halaman).
$lahanDibukaId = $tab === 'lahan' && request()->routeIs('petani.lahan-tanaman.edit') ? $lahan?->id : null;
$kelompok = $kelengkapan->kosong($tab)
->map(function ($item) use ($lahanDibukaId) {
$dibuka = $lahanDibukaId && $item['lahan_id'] === $lahanDibukaId;
return [
...$item,
'grup' => $dibuka ? $item['grup'] . ' (sedang dibuka)' : $item['grup'],
'url' => ($dibuka || ! $item['url']) ? null : $item['url'] . ($item['input'] ? '#isian=' . $item['input'] : ''),
];
})
->groupBy(fn ($item) => $item['grup'] ?? '');
}
$semuaItem = $tab ? $kelompok->flatten(1) : collect();
$jumlahWajib = $semuaItem->where('prioritas', KelengkapanPetani::WAJIB)->count();
$jumlahPerlu = $semuaItem->where('prioritas', KelengkapanPetani::PERLU)->count();
@endphp
@if ($tab)
@if ($semuaItem->isEmpty())
<div class="alert alert-success d-flex align-items-center py-2 mb-6" role="alert">
<i class="icon-base ti tabler-circle-check me-2"></i>
{{ $tab === 'semua' ? 'Semua isian wajib dan perlu sudah terisi.' : 'Semua isian wajib dan perlu di tab ini sudah terisi.' }}
</div>
@else
{{-- Klik di mana saja pada kartu untuk membuka/menutup detail (kecuali tombol isian). --}}
<div class="card mb-6 border border-warning kartu-kelengkapan" id="kelengkapan-petani"
role="button" aria-expanded="false" aria-controls="kelengkapan-petani-detail" title="Klik untuk menampilkan/menyembunyikan detail">
<div class="card-body py-3">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
<div class="d-flex align-items-center">
<i class="icon-base ti tabler-alert-triangle text-warning me-2"></i>
<span class="fw-medium">
Belum terisi{{ $tab === 'semua' ? '' : ' di tab ini' }}:
@if ($jumlahWajib)
<span class="badge bg-label-danger ms-1">{{ $jumlahWajib }} wajib</span>
@endif
@if ($jumlahPerlu)
<span class="badge bg-label-warning ms-1">{{ $jumlahPerlu }} perlu</span>
@endif
</span>
</div>
<span class="btn btn-sm btn-icon btn-text-secondary rounded-pill ikon-toggle-kelengkapan" aria-hidden="true">
<i class="icon-base ti tabler-chevron-down"></i>
</span>
</div>
<div class="collapse" id="kelengkapan-petani-detail">
<small class="text-muted d-block mt-2">
<span class="badge badge-dot bg-danger me-1"></span>Wajib: dipakai skor Kesiapan Adopsi
<span class="badge badge-dot bg-warning ms-3 me-1"></span>Perlu: dipakai dashboard.
Klik isian untuk menuju kolomnya.
</small>
@foreach ($kelompok as $judul => $items)
<div class="mt-3">
@if ($judul !== '')
<div class="small fw-medium mb-1">{{ $judul }}</div>
@endif
<div class="d-flex flex-wrap gap-2">
@foreach ($items->sortBy(fn ($i) => $i['prioritas'] === KelengkapanPetani::WAJIB ? 0 : 1) as $item)
<button type="button"
class="btn btn-xs btn-isian-kosong {{ $item['prioritas'] === KelengkapanPetani::WAJIB ? 'btn-label-danger' : 'btn-label-warning' }}"
data-input="{{ $item['input'] }}"
data-url="{{ $item['url'] }}">
{{ $item['label'] }}
</button>
@endforeach
</div>
</div>
@endforeach
</div>
</div>
</div>
@endif
@once
@push('style')
<style>
.kartu-kelengkapan { cursor: pointer; }
.kartu-kelengkapan .btn-isian-kosong { cursor: pointer; }
.ikon-toggle-kelengkapan i { transition: transform .2s; }
.kartu-kelengkapan.terbuka .ikon-toggle-kelengkapan i { transform: rotate(180deg); }
.isian-disorot {
outline: 2px solid var(--bs-warning);
outline-offset: 4px;
border-radius: .375rem;
}
</style>
@endpush
@push('scripts')
<script>
(function () {
function cariKolom(name) {
if (!name) return null;
const el = document.querySelector(`[name="${name}"], [name="${name}[]"], [name^="${name}["]`);
if (!el) return null;
// Kolom tersembunyi (mis. radio custom/select2): sorot pembungkus terdekat yang terlihat.
let target = el.closest('.col-md-12, .col-md-6, .col-md-4, .col-12, .col, .mb-4, .mb-6') || el;
while (target && target.offsetParent === null && target.parentElement) {
target = target.parentElement;
}
return { el, target };
}
function sorot(name) {
const hasil = cariKolom(name);
if (!hasil) return false;
hasil.target.scrollIntoView({ behavior: 'smooth', block: 'center' });
hasil.target.classList.add('isian-disorot');
setTimeout(() => hasil.target.classList.remove('isian-disorot'), 2500);
if (hasil.el.offsetParent !== null && typeof hasil.el.focus === 'function') {
hasil.el.focus({ preventScroll: true });
}
return true;
}
// Buka/tutup detail dengan klik di mana saja pada kartu.
const kartu = document.getElementById('kelengkapan-petani');
const detail = document.getElementById('kelengkapan-petani-detail');
if (kartu && detail) {
kartu.addEventListener('click', function (e) {
if (e.target.closest('.btn-isian-kosong')) return;
if (window.getSelection().toString()) return; // sedang menyeleksi teks
bootstrap.Collapse.getOrCreateInstance(detail, { toggle: false }).toggle();
});
detail.addEventListener('show.bs.collapse', () => { kartu.classList.add('terbuka'); kartu.setAttribute('aria-expanded', 'true'); });
detail.addEventListener('hide.bs.collapse', () => { kartu.classList.remove('terbuka'); kartu.setAttribute('aria-expanded', 'false'); });
}
document.addEventListener('click', function (e) {
const btn = e.target.closest('.btn-isian-kosong');
if (!btn) return;
if (btn.dataset.url) {
window.location.href = btn.dataset.url;
return;
}
if (!sorot(btn.dataset.input)) {
const pesan = 'Kolom ini belum tampil di halaman ini. Simpan bagian yang sudah ada terlebih dahulu, atau periksa jawaban pertanyaan sebelumnya.';
window.Swal ? Swal.fire({ icon: 'info', text: pesan }) : alert(pesan);
}
});
// Datang dari tab lain dengan #isian=nama_kolom
window.addEventListener('load', function () {
const cocok = window.location.hash.match(/^#isian=(.+)$/);
if (cocok) setTimeout(() => sorot(decodeURIComponent(cocok[1])), 300);
});
})();
</script>
@endpush
@endonce
@endif
@@ -0,0 +1,102 @@
{{--
Kelengkapan data (terisi X dari 209) + status Draf/Lengkap/Terverifikasi di kartu Profil Petani.
Tombol "Tandai Lengkap" hanya untuk enumerator penginput, dan nonaktif selama isian wajib masih kosong.
--}}
@props(['petani'])
@use('App\Enums\StatusDataPetani')
@use('App\Services\Produktivitas\KelengkapanPetani')
@php
$kelengkapan = KelengkapanPetani::untuk($petani);
$hitung = $kelengkapan->hitungIsian();
$wajibKosong = $kelengkapan->jumlahWajibKosong();
$status = $petani->status ?? StatusDataPetani::DRAFT;
$bisaTandai = $petani->bisaDitandaiOleh(auth()->user());
@endphp
<hr class="my-2">
<div class="d-flex justify-content-between align-items-center mb-1">
<h6 class="mb-0" style="font-size: 0.85rem;">Kelengkapan Data</h6>
<span class="badge bg-label-{{ $status->warna() }}">{{ $status->label() }}</span>
</div>
<div class="d-flex justify-content-between" style="font-size: 0.75rem;">
<span>{{ $hitung['terisi'] }} dari {{ $hitung['total'] }} isian terisi</span>
<span class="fw-medium">{{ $hitung['persen'] }}%</span>
</div>
<div class="progress my-1" style="height: 6px;">
<div class="progress-bar {{ $hitung['persen'] >= 100 ? 'bg-success' : '' }}" role="progressbar"
style="width: {{ $hitung['persen'] }}%;" aria-valuenow="{{ $hitung['persen'] }}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<p class="mb-0 text-muted" style="font-size: 0.7rem;">
@if ($hitung['tidak_berlaku'])
{{ $hitung['tidak_berlaku'] }} isian tidak berlaku untuk petani ini.
@endif
@if ($wajibKosong)
<span class="text-danger">{{ $wajibKosong }} isian wajib masih kosong.</span>
@else
<span class="text-success">Semua isian wajib sudah terisi.</span>
@endif
</p>
@if ($bisaTandai && $status === StatusDataPetani::DRAFT)
<form method="POST" action="{{ route('petani.status.update', $petani->id) }}" class="form-status-petani mt-2"
data-konfirmasi="Tandai data petani ini sudah lengkap? Koordinator akan memverifikasinya.">
@csrf
@method('PUT')
<input type="hidden" name="status" value="{{ StatusDataPetani::LENGKAP->value }}">
<button type="submit" class="btn btn-sm btn-success w-100" @disabled($wajibKosong > 0)
@if ($wajibKosong) title="Lengkapi {{ $wajibKosong }} isian wajib terlebih dahulu" @endif>
<i class="icon-base ti tabler-checks icon-sm me-1"></i>Tandai Sudah Lengkap
</button>
</form>
@elseif ($bisaTandai && $status === StatusDataPetani::LENGKAP)
<form method="POST" action="{{ route('petani.status.update', $petani->id) }}" class="form-status-petani mt-2"
data-konfirmasi="Batalkan tanda lengkap? Status kembali ke draf.">
@csrf
@method('PUT')
<input type="hidden" name="status" value="{{ StatusDataPetani::DRAFT->value }}">
<button type="submit" class="btn btn-sm btn-outline-secondary w-100">
<i class="icon-base ti tabler-arrow-back-up icon-sm me-1"></i>Batalkan Tanda Lengkap
</button>
</form>
@elseif ($status === StatusDataPetani::DRAFT)
<p class="mb-0 mt-1 text-muted fst-italic" style="font-size: 0.7rem;">Hanya enumerator penginput yang dapat menandai lengkap.</p>
@elseif ($status === StatusDataPetani::LENGKAP)
<p class="mb-0 mt-1 text-muted fst-italic" style="font-size: 0.7rem;">Menunggu verifikasi koordinator.</p>
@endif
@once
@push('scripts')
<script>
document.addEventListener('submit', function (e) {
const form = e.target.closest('.form-status-petani');
if (!form || form.dataset.terkonfirmasi) return;
e.preventDefault();
const kirim = () => { form.dataset.terkonfirmasi = '1'; form.submit(); };
if (!window.Swal) {
if (confirm(form.dataset.konfirmasi)) kirim();
return;
}
Swal.fire({
icon: 'question',
text: form.dataset.konfirmasi,
showCancelButton: true,
confirmButtonText: 'Ya',
cancelButtonText: 'Batal',
}).then((r) => { if (r.isConfirmed) kirim(); });
});
@if (session('status_petani_success') || session('status_petani_error'))
window.addEventListener('load', function () {
const pesan = @json(session('status_petani_success') ?? session('status_petani_error'));
const ikon = @json(session('status_petani_success') ? 'success' : 'error');
window.Swal ? Swal.fire({ icon: ikon, text: pesan }) : alert(pesan);
});
@endif
</script>
@endpush
@endonce
+35
View File
@@ -113,6 +113,41 @@
} }
} }
</style> --> </style> -->
{{-- Tombol kembali ke atas: muncul setelah halaman digulir ke bawah. --}}
<button type="button" id="btn-kembali-ke-atas" class="btn btn-primary btn-icon rounded-pill shadow"
title="Kembali ke atas" aria-label="Kembali ke atas">
<i class="icon-base ti tabler-arrow-up"></i>
</button>
<style>
#btn-kembali-ke-atas {
position: fixed;
right: 1.5rem;
bottom: 1.5rem;
z-index: 1040;
width: 2.75rem;
height: 2.75rem;
opacity: 0;
visibility: hidden;
transform: translateY(1rem);
transition: opacity .25s, transform .25s, visibility .25s;
}
#btn-kembali-ke-atas.tampil {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
</style>
<script>
(function () {
const btn = document.getElementById('btn-kembali-ke-atas');
const cek = () => btn.classList.toggle('tampil', window.scrollY > 300);
window.addEventListener('scroll', cek, { passive: true });
btn.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' }));
cek();
})();
</script>
@stack('scripts') @stack('scripts')
<script> <script>
@@ -59,6 +59,10 @@
<hr class="my-2"> <h6 class="mb-1" style="font-size: 0.85rem;">Domisili</h6> <hr class="my-2"> <h6 class="mb-1" style="font-size: 0.85rem;">Domisili</h6>
<p class="mb-0 text-muted" style="font-size: 0.75rem;">Alamat: {{ isset($petani) ? $petani->alamat : 'Alamat' }}</p> <p class="mb-0 text-muted" style="font-size: 0.75rem;">Alamat: {{ isset($petani) ? $petani->alamat : 'Alamat' }}</p>
<p class="mb-0 text-muted" style="font-size: 0.75rem; line-height: 1.3;">{{ isset($petani) ? $petani->desaKelurahan?->nama ?? 'Belum diisi' : 'Desa/Kelurahan' }}, {{ $petani ? $petani->desaKelurahan?->kecamatan?->nama ?? 'Belum diisi' : 'Kecamatan' }}, {{ $petani ? $petani->desaKelurahan?->kecamatan?->kabupatenKota?->nama ?? 'Belum diisi' : 'Kabupaten/Kota' }}, {{ $petani ? $petani->desaKelurahan?->kecamatan?->kabupatenKota?->provinsi?->nama ?? 'Belum diisi' : 'Provinsi' }}</p> <p class="mb-0 text-muted" style="font-size: 0.75rem; line-height: 1.3;">{{ isset($petani) ? $petani->desaKelurahan?->nama ?? 'Belum diisi' : 'Desa/Kelurahan' }}, {{ $petani ? $petani->desaKelurahan?->kecamatan?->nama ?? 'Belum diisi' : 'Kecamatan' }}, {{ $petani ? $petani->desaKelurahan?->kecamatan?->kabupatenKota?->nama ?? 'Belum diisi' : 'Kabupaten/Kota' }}, {{ $petani ? $petani->desaKelurahan?->kecamatan?->kabupatenKota?->provinsi?->nama ?? 'Belum diisi' : 'Provinsi' }}</p>
@if ($petani)
<x-status-petani :petani="$petani" />
@endif
</div> </div>
</div> </div>
</div> </div>
@@ -68,7 +72,7 @@
$skorLahanTanaman = isset($petani) ? $petani->skorLahanTanaman() : null; $skorLahanTanaman = isset($petani) ? $petani->skorLahanTanaman() : null;
$lahanPetani = isset($petani) && \Illuminate\Support\Facades\Schema::hasTable('map_lahan') $lahanPetani = isset($petani) && \Illuminate\Support\Facades\Schema::hasTable('map_lahan')
? $petani->lahans()->get() ? $petani->lahans
: collect(); : collect();
$totalLuasLahanHa = $petani ? $lahanPetani->where('komoditas_id', $petani->komoditas_id)->sum('luas_lahan') : 0; $totalLuasLahanHa = $petani ? $lahanPetani->where('komoditas_id', $petani->komoditas_id)->sum('luas_lahan') : 0;
$lahanKomoditasUtamaIds = $petani $lahanKomoditasUtamaIds = $petani
@@ -393,3 +397,7 @@
</ul> </ul>
@endif @endif
</div> </div>
@if ($petani)
<x-kelengkapan-petani :petani="$petani" :lahan="$lahanSelected ?? null" />
@endif
+1
View File
@@ -230,6 +230,7 @@ Route::middleware('auth')
Route::get('petani/show-image/{id}/{field}', [App\Http\Controllers\Web\Produktivitas\PetaniController::class, 'showImage'])->name('petani.show-image'); Route::get('petani/show-image/{id}/{field}', [App\Http\Controllers\Web\Produktivitas\PetaniController::class, 'showImage'])->name('petani.show-image');
Route::resource('petani', App\Http\Controllers\Web\Produktivitas\PetaniController::class)->whereNumber('petani'); Route::resource('petani', App\Http\Controllers\Web\Produktivitas\PetaniController::class)->whereNumber('petani');
Route::singleton('petani.status', App\Http\Controllers\Web\Produktivitas\PetaniStatusController::class)->only(['update']);
Route::get('petani/{petani}/akses-pasar', [App\Http\Controllers\Web\Produktivitas\AksesPasarController::class, 'editByPetani'])->name('petani.akses-pasar.edit'); Route::get('petani/{petani}/akses-pasar', [App\Http\Controllers\Web\Produktivitas\AksesPasarController::class, 'editByPetani'])->name('petani.akses-pasar.edit');
Route::get('petani/{petani}/lahan-tanaman/summary', [App\Http\Controllers\Web\Produktivitas\PetaniLahanController::class, 'summary'])->name('petani.lahan-tanaman.summary'); Route::get('petani/{petani}/lahan-tanaman/summary', [App\Http\Controllers\Web\Produktivitas\PetaniLahanController::class, 'summary'])->name('petani.lahan-tanaman.summary');
Route::resource('petani.lahan-tanaman', App\Http\Controllers\Web\Produktivitas\PetaniLahanController::class)->whereNumber('petani'); Route::resource('petani.lahan-tanaman', App\Http\Controllers\Web\Produktivitas\PetaniLahanController::class)->whereNumber('petani');