53 Commits
Author SHA1 Message Date
Althaf dacaec7a86 Merge branch 'main' of https://git.probindo.com/Probindo/database-pertani-web 2026-08-27 11:48:55 +07:00
Althaf 6967eafe98 add instrumen koperasi 2026-08-27 11:48:42 +07:00
Syaharani 118cda625a filter dashboard indikator pertanian 2026-08-20 17:24:31 +07:00
irwan.l.cahyono a3f7cb4669 filter segmen 2026-08-20 01:52:05 +07:00
irwan.l.cahyono ec009a2c55 indeks petani dan ajax 2026-08-20 01:10:56 +07:00
afriadirahman b63dafdaee Update Profile Petani 2026-08-20 01:01:27 +07:00
Althaf b97f682cd3 Merge branch 'main' of https://git.probindo.com/Probindo/database-pertani-web 2026-08-19 16:42:31 +07:00
Althaf 404019bab6 Added Score 2026-08-19 16:42:03 +07:00
Syaharani 609919ba30 solve dashboard survey petani 2026-08-19 16:21:42 +07:00
irwan.l.cahyono 047cb4a779 link ke profil 2026-08-19 10:37:17 +07:00
irwan.l.cahyono 530c297b53 revisi jumlahpetani 2026-08-19 01:57:52 +07:00
Syaharani 8fbf68e6ef solve keanggotaan kelompok tani 2026-08-18 14:05:46 +07:00
Syaharani ac8b370248 update dashboard indikator pertanian 2026-08-18 14:04:44 +07:00
Syaharani 88762f7afb update dashboard indikator pertanian 2026-08-13 17:00:00 +07:00
Syaharani b96de514e3 solve dashboard indikator pertanian 2026-08-13 16:31:11 +07:00
Syaharani 6830142a82 solve dashboard indikator pertanian 2026-08-13 16:07:55 +07:00
afriadirahman 4b26e82afb Optimize Demografi & Update Profil Petani 2026-08-13 15:31:04 +07:00
Syaharani b855a857b2 solve dashboard indikator pertanian 2026-08-13 13:07:03 +07:00
afriadirahman ba7520a23c Update Demografi Petani 2026-08-12 17:49:24 +07:00
afriadirahman 0b333ab4e9 Update Profil Detail Petani 2026-08-12 17:33:53 +07:00
Althaf 3b1f15d5b3 fixed aksespasar 2026-08-11 16:19:10 +07:00
Althaf 605506bba8 Merge branch 'main' of https://git.probindo.com/Probindo/database-pertani-web 2026-08-11 15:40:27 +07:00
afriadirahman c960ba14b1 Update Demografi Petani 2026-08-11 15:13:28 +07:00
Syaharani a4f38998d5 dashboard indikator pertanian 2026-08-07 16:09:24 +07:00
Syaharani 0d9f6c0b64 indikator pertanian 2026-08-07 15:56:37 +07:00
irwan.l.cahyono 81775b0c13 Merge branch 'main' of https://git.probindo.com/Probindo/database-pertani-web 2026-08-07 15:00:32 +07:00
irwan.l.cahyono ba1a318d09 soft deleted petani 2026-08-07 15:00:30 +07:00
afriadirahman 0761d64254 Merge branch 'main' of https://git.probindo.com/Probindo/database-pertani-web 2026-08-07 14:32:48 +07:00
afriadirahman 67c3c6c809 Update demografi dashboard 2026-08-07 14:32:40 +07:00
irwan.l.cahyono 04387268cd indeks petani 2026-08-07 14:31:16 +07:00
irwan.l.cahyono 946fabb9a5 lahan skor has petani 2026-08-07 14:17:06 +07:00
irwan.l.cahyono 0a0c86d648 Merge branch 'main' of https://git.probindo.com/Probindo/database-pertani-web 2026-08-07 12:59:26 +07:00
irwan.l.cahyono 8b21fab8b8 dashboard indeks petani 2026-08-07 12:59:25 +07:00
afriadirahman 16a0a3c56c Update Icon Demografi Dashboard 2026-08-07 11:33:08 +07:00
afriadirahman d4c6b64ba6 Update Sidebar Demografi Petani 2026-08-07 11:20:05 +07:00
afriadirahman b4089d72b3 Update Dashboard Demografi Petani 2026-08-07 11:17:53 +07:00
afriadirahman 01990ccec1 Merge branch 'main' of https://git.probindo.com/Probindo/database-pertani-web 2026-08-06 13:29:42 +07:00
irwan.l.cahyono 8aed88a31f dashboard indeks petani 2026-08-05 01:20:19 +07:00
irwan.l.cahyono 4aa0780802 dashboard profil petani komp 4 2026-08-03 13:08:01 +07:00
afriadirahman 4e41246382 Update Profile Petani Dashboard 2026-08-03 12:53:20 +07:00
Althaf 43fe9e5f7f Merge branch 'main' of https://git.probindo.com/Probindo/database-pertani-web 2026-08-03 09:22:47 +07:00
irwan.l.cahyono c8d1994d3b dashboard profil petani 2026-07-30 15:23:34 +07:00
Althaf cf0f4b47b4 update akses pasar 2026-07-30 11:14:31 +07:00
irwan.l.cahyono 24a8016710 petani booted skor 2026-07-29 16:52:51 +07:00
irwan.l.cahyono 20e1eee6ff skor petani domain komponen hitung 2026-07-29 16:27:33 +07:00
afriadirahman c0432cd5f8 Update Sidebar Filter Fasilitator 2026-07-29 16:12:31 +07:00
irwan.l.cahyono 0ce0936a80 skor petani update 2026-07-29 16:05:43 +07:00
Syaharani 502c932f21 Merge branch 'main' of https://git.probindo.com/Probindo/database-pertani-web 2026-07-28 16:54:37 +07:00
Syaharani e60a66eeb7 bumdes: profil & add pengurus bumdes 2026-07-28 16:54:27 +07:00
afriadirahman 4475cd0098 Update Logo Sidebar 2026-07-28 16:18:41 +07:00
afriadirahman 67c111a2b3 Update Logo Agromonev & Agropro 2026-07-28 16:10:47 +07:00
Syaharani 3bcaa3ea50 solve survey produktivitas 2026-07-27 10:14:36 +07:00
Syaharani bed9a6f620 solve 2026-07-23 18:26:24 +07:00
44 changed files with 11147 additions and 383 deletions
@@ -0,0 +1,26 @@
<?php
namespace App\Enums\AksesPasar;
enum TingkatKepercayaanPembeli: int
{
case SANGAT_PERCAYA = 1;
case CUKUP_PERCAYA = 2;
case RAGU_RAGU = 3;
case TIDAK_PERCAYA = 4;
public static function options(): array
{
return [
['value' => self::SANGAT_PERCAYA->value, 'label' => 'Sangat percaya, berani langsung coba'],
['value' => self::CUKUP_PERCAYA->value, 'label' => 'Cukup percaya, tapi hati hati'],
['value' => self::RAGU_RAGU->value, 'label' => 'Ragu ragu'],
['value' => self::TIDAK_PERCAYA->value, 'label' => 'Tidak percaya, enggan mencoba'],
];
}
public static function values(): array
{
return array_column(self::cases(), 'value');
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace App\Enums\Bumdes;
enum JabatanBumdes: string
{
case DIREKTUR = 'direktur';
case DIREKTUR_KETUA = 'direktur_ketua';
case SEKRETARIS = 'sekretaris';
case BENDAHARA = 'bendahara';
case KEPALA_UNIT_USAHA = 'kepala_unit_usaha';
case PENGAWAS = 'pengawas';
case PENASIHAT = 'penasihat';
case ANGGOTA = 'anggota';
case STAF_OPERASIONAL = 'staf_operasional';
case LAINNYA = 'lainnya';
public function label(): string
{
return match ($this) {
self::DIREKTUR => 'Direktur/Ketua',
self::DIREKTUR_KETUA => 'Direktur/Ketua',
self::SEKRETARIS => 'Sekretaris',
self::BENDAHARA => 'Bendahara',
self::KEPALA_UNIT_USAHA => 'Kepala Unit Usaha',
self::PENGAWAS => 'Pengawas',
self::PENASIHAT => 'Penasihat',
self::ANGGOTA => 'Anggota',
self::STAF_OPERASIONAL => 'Staf Operasional',
self::LAINNYA => 'Lainnya',
};
}
public static function options(): array
{
return collect([
self::DIREKTUR_KETUA,
self::SEKRETARIS,
self::BENDAHARA,
self::LAINNYA,
])->map(fn ($case) => [
'value' => $case->value,
'label' => $case->label(),
])->toArray();
}
}
@@ -0,0 +1,218 @@
<?php
namespace App\Http\Controllers\Web\Bumdes;
use App\Enums\Bumdes\JabatanBumdes;
use App\Enums\Pendidikan;
use App\Http\Controllers\Controller;
use App\Models\Bumdes\PengurusBumdes;
use App\Models\Wilayah\Provinsi;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class PengurusBumdesController extends Controller
{
public function index(Request $request)
{
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$query = PengurusBumdes::with([
'desaKelurahan:id,nama,kecamatan_id',
'desaKelurahan.kecamatan:id,nama,kabupaten_kota_id',
'desaKelurahan.kecamatan.kabupatenKota:id,nama,provinsi_id',
'desaKelurahan.kecamatan.kabupatenKota.provinsi:id,nama',
'creator:id,name',
])
->orderBy('created_at', 'desc');
if ($request->search) {
$query->where(function ($query) use ($request) {
$query->where('nama', 'like', '%' . $request->search . '%')
->orWhere('jabatan', 'like', '%' . $request->search . '%')
->orWhere('jabatan_lainnya', 'like', '%' . $request->search . '%');
});
}
if (auth()->user()->role->value === 'fasilitator') {
$query->where('created_by', auth()->id());
}
$summaryQuery = clone $query;
$totalPengurus = (clone $summaryQuery)->count();
$totalLakiLaki = (clone $summaryQuery)->where('jenis_kelamin', true)->count();
$totalPerempuan = (clone $summaryQuery)->where('jenis_kelamin', false)->count();
$totalDesaKelurahan = (clone $summaryQuery)->distinct('desa_kelurahan_id')->count('desa_kelurahan_id');
$totalJabatan = (clone $summaryQuery)->distinct('jabatan')->count('jabatan');
$pengurus = $query->paginate($request->per_page ?? 10);
$userRole = auth()->user()->role->value;
$showEnumerator = in_array($userRole, ['super_admin', 'admin']);
return view('bumdes.profil.index', compact(
'pengurus',
'totalPengurus',
'totalLakiLaki',
'totalPerempuan',
'totalDesaKelurahan',
'totalJabatan',
'showEnumerator'
));
}
public function create()
{
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$provinsi = Provinsi::select('id', 'kode', 'nama')->orderBy('kode', 'asc')->get();
$jabatan = JabatanBumdes::options();
$pendidikan = Pendidikan::options();
return view('bumdes.profil.add', compact('provinsi', 'jabatan', 'pendidikan'));
}
public function store(Request $request)
{
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$validatedData = $this->validatedData($request);
$pengurus = PengurusBumdes::create([
...$this->formatData($validatedData),
'uid' => 'BMD-' . strtoupper(Str::random(10)),
'created_by' => auth()->id(),
'updated_by' => auth()->id(),
]);
return redirect()->route('bumdes.edit', $pengurus->id)->with('success', 'Data pengurus BUMDes berhasil ditambahkan.');
}
public function show(string $id)
{
return redirect()->route('bumdes.edit', $id);
}
public function edit(string $id)
{
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$pengurus = $this->pengurusQuery()->with([
'desaKelurahan.kecamatan.kabupatenKota.provinsi',
])->findOrFail($id);
$provinsi = Provinsi::select('id', 'kode', 'nama')->orderBy('kode', 'asc')->get();
$jabatan = JabatanBumdes::options();
$pendidikan = Pendidikan::options();
return view('bumdes.profil.edit', compact('pengurus', 'provinsi', 'jabatan', 'pendidikan'));
}
public function update(Request $request, string $id)
{
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$pengurus = $this->pengurusQuery()->findOrFail($id);
$validatedData = $this->validatedData($request);
$pengurus->update([
...$this->formatData($validatedData),
'updated_by' => auth()->id(),
]);
return redirect()->route('bumdes.edit', $pengurus->id)->with('success', 'Data pengurus BUMDes berhasil diupdate.');
}
public function destroy(string $id)
{
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$pengurus = $this->pengurusQuery()->findOrFail($id);
$pengurus->update(['deleted_by' => auth()->id()]);
$pengurus->delete();
return response()->json(['message' => 'Data pengurus BUMDes berhasil dihapus.']);
}
public function kelembagaanAktor(string $id)
{
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$pengurus = $this->pengurusQuery()->with([
'desaKelurahan.kecamatan.kabupatenKota.provinsi',
])->findOrFail($id);
return view('bumdes.kelembagaan-aktor.index', compact('pengurus'));
}
private function pengurusQuery()
{
$query = PengurusBumdes::query();
if (auth()->user()->role->value === 'fasilitator') {
$query->where('created_by', auth()->id());
}
return $query;
}
private function validatedData(Request $request): array
{
return $request->validate([
'nama' => ['required', 'string', 'max:255'],
'jenis_kelamin' => ['required', 'boolean'],
'tempat_lahir' => ['required', 'string', 'max:255'],
'tanggal_lahir' => ['required', 'date_format:d-m-Y'],
'pendidikan_terakhir' => ['required', Rule::enum(Pendidikan::class)],
'alamat' => ['required', 'string', 'max:500'],
'desa_kelurahan_id' => ['required', 'exists:master_desa_kelurahan,id'],
'nomor_telepon' => ['nullable', 'digits_between:11,13'],
'jabatan' => ['required', Rule::enum(JabatanBumdes::class)],
'jabatan_lainnya' => ['nullable', 'required_if:jabatan,' . JabatanBumdes::LAINNYA->value, 'string', 'max:255'],
'lama_menjabat_tahun' => ['required', 'integer', 'min:0', 'max:80'],
], [
'nama.required' => 'Nama harus diisi.',
'jenis_kelamin.required' => 'Jenis kelamin harus dipilih.',
'tempat_lahir.required' => 'Tempat lahir harus diisi.',
'tanggal_lahir.required' => 'Tanggal lahir harus diisi.',
'tanggal_lahir.date_format' => 'Tanggal lahir harus menggunakan format tanggal yang valid.',
'pendidikan_terakhir.required' => 'Pendidikan terakhir harus dipilih.',
'alamat.required' => 'Alamat harus diisi.',
'desa_kelurahan_id.required' => 'Desa kelurahan harus dipilih.',
'desa_kelurahan_id.exists' => 'Desa kelurahan tidak ditemukan.',
'nomor_telepon.digits_between' => 'Nomor telepon harus berupa angka 11 sampai 13 digit.',
'jabatan.required' => 'Jabatan/peran harus dipilih.',
'jabatan_lainnya.required_if' => 'Jabatan lainnya harus diisi.',
'lama_menjabat_tahun.required' => 'Lama menjabat harus diisi.',
'lama_menjabat_tahun.integer' => 'Lama menjabat harus berupa angka tahun.',
]);
}
private function formatData(array $data): array
{
$data['tanggal_lahir'] = Carbon::createFromFormat('d-m-Y', $data['tanggal_lahir'])->format('Y-m-d');
$data['tanggal_mulai_menjabat'] = Carbon::now()
->subYears((int) $data['lama_menjabat_tahun'])
->format('Y-m-d');
if (($data['jabatan'] ?? null) !== JabatanBumdes::LAINNYA->value) {
$data['jabatan_lainnya'] = null;
}
return $data;
}
}
@@ -0,0 +1,278 @@
<?php
namespace App\Http\Controllers\Web\Dashboard;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use App\Models\Pertanian\Petani;
use App\Models\Pertanian\Komoditas;
use App\Models\Pertanian\Lahan;
use App\Models\Pertanian\PendapatanKomoditas;
class DemografiPetaniController extends Controller
{
public function index(Request $request)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$petaniCounts = Petani::selectRaw("
COUNT(*) as total,
COUNT(*) FILTER (WHERE jenis_kelamin = true) as laki,
COUNT(*) FILTER (WHERE jenis_kelamin = false) as perempuan
")->first();
$totalPetani = $petaniCounts->total;
$petaniLakilaki = $petaniCounts->laki;
$petaniPerempuan = $petaniCounts->perempuan;
$totalKomoditas = Petani::whereNotNull('komoditas_id')
->distinct()
->count('komoditas_id');
$totalLuasLahan = Lahan::has('petani')->sum('luas_lahan');
$ratarataLuasLahan = $totalLuasLahan && $totalPetani ? $totalLuasLahan / $totalPetani : 0;
$umurCounts = Petani::whereNotNull('tanggal_lahir')->selectRaw("
COUNT(*) FILTER (WHERE EXTRACT(YEAR FROM AGE(CURRENT_DATE, tanggal_lahir)) < 30) as kurang_30,
COUNT(*) FILTER (WHERE EXTRACT(YEAR FROM AGE(CURRENT_DATE, tanggal_lahir)) BETWEEN 30 AND 39) as umur_30_39,
COUNT(*) FILTER (WHERE EXTRACT(YEAR FROM AGE(CURRENT_DATE, tanggal_lahir)) BETWEEN 40 AND 49) as umur_40_49,
COUNT(*) FILTER (WHERE EXTRACT(YEAR FROM AGE(CURRENT_DATE, tanggal_lahir)) BETWEEN 50 AND 59) as umur_50_59,
COUNT(*) FILTER (WHERE EXTRACT(YEAR FROM AGE(CURRENT_DATE, tanggal_lahir)) >= 60) as umur_60_keatas
")->first();
$pendidikanCounts = Petani::whereNotNull('pendidikan_terakhir')
->select('pendidikan_terakhir', DB::raw('COUNT(*) as total'))
->groupBy('pendidikan_terakhir')
->pluck('total', 'pendidikan_terakhir');
$pendidikan = [
'tidak_sekolah' => (int) ($pendidikanCounts['01_tidak_sekolah'] ?? 0),
'sd' => (int) ($pendidikanCounts['02_sd'] ?? 0),
'smp' => (int) ($pendidikanCounts['03_smp'] ?? 0),
'sma' => (int) ($pendidikanCounts['04_sma'] ?? 0),
'diploma' => (int) ($pendidikanCounts['05_diploma'] ?? 0),
's1_keatas' => (int) ($pendidikanCounts['06_sarjana'] ?? 0) + (int) ($pendidikanCounts['07_magister'] ?? 0),
];
$komoditasUtama = Komoditas::query()
->whereHas('petani')
->withCount('petani')
->orderByDesc('petani_count')
->get(['id', 'nama']);
$luasLahanBuckets = DB::query()->fromSub(
Lahan::query()->select('petani_id')->selectRaw('SUM(luas_lahan) as total_luas')->groupBy('petani_id'),
'agg'
)->selectRaw("
COUNT(*) FILTER (WHERE total_luas < 5000) as kurang_05,
COUNT(*) FILTER (WHERE total_luas >= 5000 AND total_luas <= 10000) as luas_05_1,
COUNT(*) FILTER (WHERE total_luas > 10000 AND total_luas <= 20000) as luas_1_2,
COUNT(*) FILTER (WHERE total_luas > 20000) as lebih_2
")->first();
$luasLahan = [
'kurang_05' => $luasLahanBuckets->kurang_05,
'05_1' => $luasLahanBuckets->luas_05_1,
'1_2' => $luasLahanBuckets->luas_1_2,
'lebih_2' => $luasLahanBuckets->lebih_2,
];
$penghasilanBuckets = DB::query()->fromSub(
PendapatanKomoditas::query()
->join('sosial_ekonomi', 'sosial_ekonomi.id', '=', 'pendapatan_komoditas.sosial_ekonomi_id')
->whereNotNull('sosial_ekonomi.petani_id')
->groupBy('sosial_ekonomi.petani_id')
->selectRaw('SUM(pendapatan_komoditas.pendapatan) as total_pendapatan'),
'agg'
)->selectRaw("
COUNT(*) FILTER (WHERE total_pendapatan < 1000000) as kurang_1,
COUNT(*) FILTER (WHERE total_pendapatan >= 1000000 AND total_pendapatan <= 3000000) as p1_3,
COUNT(*) FILTER (WHERE total_pendapatan > 3000000 AND total_pendapatan <= 5000000) as p3_5,
COUNT(*) FILTER (WHERE total_pendapatan > 5000000 AND total_pendapatan <= 10000000) as p5_10,
COUNT(*) FILTER (WHERE total_pendapatan > 10000000) as lebih_10
")->first();
$penghasilan = [
'kurang_1' => $penghasilanBuckets->kurang_1,
'1_3' => $penghasilanBuckets->p1_3,
'3_5' => $penghasilanBuckets->p3_5,
'5_10' => $penghasilanBuckets->p5_10,
'lebih_10' => $penghasilanBuckets->lebih_10,
];
$data = [
'total_petani' => $totalPetani,
'petani_lakilaki' => $petaniLakilaki,
'petani_perempuan' => $petaniPerempuan,
'total_komoditas' => $totalKomoditas,
'total_luas_lahan' => $totalLuasLahan,
'ratarata_luas_lahan' => $ratarataLuasLahan,
'kelompok_umur' => [
'kurang_30' => $umurCounts->kurang_30,
'30_39' => $umurCounts->umur_30_39,
'40_49' => $umurCounts->umur_40_49,
'50_59' => $umurCounts->umur_50_59,
'60_keatas' => $umurCounts->umur_60_keatas,
],
'pendidikan' => $pendidikan,
'komoditas_utama' => [
'ids' => $komoditasUtama->pluck('id')->values(),
'labels' => $komoditasUtama->pluck('nama')->values(),
'series' => $komoditasUtama->pluck('petani_count')->map(fn ($v) => (int) $v)->values(),
],
'luas_lahan' => $luasLahan,
'penghasilan' => $penghasilan,
];
$daftarPetani = Petani::with(['komoditas'])
->withSum('lahans as total_luas_lahan', 'luas_lahan')
->orderBy('id')
->paginate(10);
return view('dashboard.demografi-petani', compact('data', 'daftarPetani'));
}
public function data(Request $request)
{
$pendapatanSub = PendapatanKomoditas::query()
->join('sosial_ekonomi', 'sosial_ekonomi.id', '=', 'pendapatan_komoditas.sosial_ekonomi_id')
->whereColumn('sosial_ekonomi.petani_id', 'petani.id')
->selectRaw('SUM(pendapatan_komoditas.pendapatan)');
$query = Petani::with('komoditas')
->withSum('lahans as total_luas_lahan', 'luas_lahan')
->addSelect(['total_pendapatan' => $pendapatanSub]);
if ($request->filled('search')) {
$query->where('nama', 'ILIKE', "%{$request->search}%");
}
if ($request->has('gender') && $request->gender !== '') {
$query->where('jenis_kelamin', $request->gender);
}
if ($request->filled('umur')) {
match ($request->umur) {
'kurang_30' => $query->whereNotNull('tanggal_lahir')
->whereRaw("EXTRACT(YEAR FROM AGE(CURRENT_DATE, tanggal_lahir)) < 30"),
'30_39' => $query->whereNotNull('tanggal_lahir')
->whereRaw("EXTRACT(YEAR FROM AGE(CURRENT_DATE, tanggal_lahir)) BETWEEN 30 AND 39"),
'40_49' => $query->whereNotNull('tanggal_lahir')
->whereRaw("EXTRACT(YEAR FROM AGE(CURRENT_DATE, tanggal_lahir)) BETWEEN 40 AND 49"),
'50_59' => $query->whereNotNull('tanggal_lahir')
->whereRaw("EXTRACT(YEAR FROM AGE(CURRENT_DATE, tanggal_lahir)) BETWEEN 50 AND 59"),
'60_keatas' => $query->whereNotNull('tanggal_lahir')
->whereRaw("EXTRACT(YEAR FROM AGE(CURRENT_DATE, tanggal_lahir)) >= 60"),
default => null,
};
}
if ($request->filled('pendidikan')) {
$pendidikanMap = [
'tidak_sekolah' => ['01_tidak_sekolah'],
'sd' => ['02_sd'],
'smp' => ['03_smp'],
'sma' => ['04_sma'],
'diploma' => ['05_diploma'],
's1_keatas' => ['06_sarjana', '07_magister'],
];
if (isset($pendidikanMap[$request->pendidikan])) {
$query->whereIn('pendidikan_terakhir', $pendidikanMap[$request->pendidikan]);
}
}
if ($request->filled('komoditas')) {
$query->where('komoditas_id', $request->komoditas);
}
if ($request->filled('penghasilan')) {
[$min, $max] = match ($request->penghasilan) {
'kurang_1' => [null, 999999],
'1_3' => [1000000, 3000000],
'3_5' => [3000001, 5000000],
'5_10' => [5000001, 10000000],
'lebih_10' => [10000001, null],
default => [null, null],
};
$query->whereIn('id', function ($sub) use ($min, $max) {
$sub->from('pendapatan_komoditas')
->join('sosial_ekonomi', 'sosial_ekonomi.id', '=', 'pendapatan_komoditas.sosial_ekonomi_id')
->whereNotNull('sosial_ekonomi.petani_id')
->groupBy('sosial_ekonomi.petani_id')
->select('sosial_ekonomi.petani_id')
->when($min !== null, fn ($q) => $q->havingRaw('SUM(pendapatan_komoditas.pendapatan) >= ?', [$min]))
->when($max !== null, fn ($q) => $q->havingRaw('SUM(pendapatan_komoditas.pendapatan) <= ?', [$max]));
});
}
if ($request->filled('luas_lahan')) {
$lahanQuery = Lahan::query()->select('petani_id')->groupBy('petani_id');
match ($request->luas_lahan) {
'kurang_05' => $lahanQuery->havingRaw('SUM(luas_lahan) < 5000'),
'05_1' => $lahanQuery->havingRaw('SUM(luas_lahan) >= 5000 AND SUM(luas_lahan) <= 10000'),
'1_2' => $lahanQuery->havingRaw('SUM(luas_lahan) > 10000 AND SUM(luas_lahan) <= 20000'),
'lebih_2' => $lahanQuery->havingRaw('SUM(luas_lahan) > 20000'),
default => null,
};
$query->whereIn('id', $lahanQuery->pluck('petani_id'));
}
$daftarPetani = $query->orderBy('id')->paginate(10);
$pendidikanLabels = [
'01_tidak_sekolah' => 'Tidak Sekolah',
'02_sd' => 'SD',
'03_smp' => 'SMP',
'04_sma' => 'SMA',
'05_diploma' => 'Diploma',
'06_sarjana' => 'Sarjana',
'07_magister' => 'Magister',
];
$rows = collect($daftarPetani->items())->map(function ($petani) use ($pendidikanLabels) {
$umur = $petani->tanggal_lahir ? \Carbon\Carbon::parse($petani->tanggal_lahir)->age : null;
$pendidikanRaw = $petani->getRawOriginal('pendidikan_terakhir');
$pendapatan = $petani->total_pendapatan;
$rentangPenghasilan = match (true) {
$pendapatan === null => '-',
$pendapatan < 1000000 => '< Rp1 juta',
$pendapatan <= 3000000 => 'Rp1 - 3 juta',
$pendapatan <= 5000000 => 'Rp3 - 5 juta',
$pendapatan <= 10000000 => 'Rp5 - 10 juta',
default => '> Rp10 juta',
};
return [
'id' => $petani->id,
'nama' => $petani->nama ?? '-',
'jenis_kelamin' => $petani->jenis_kelamin,
'gender' => $petani->jenis_kelamin == 1 ? 'Laki-laki' : 'Perempuan',
'umur' => $umur,
'komoditas' => $petani->komoditas?->nama ?? '-',
'luas_lahan' => (float) ($petani->total_luas_lahan ?? 0),
'pendidikan' => $pendidikanLabels[$pendidikanRaw] ?? '-',
'rentang_penghasilan' => $rentangPenghasilan,
];
});
return response()->json([
'data' => $rows,
'pagination' => [
'current_page' => $daftarPetani->currentPage(),
'last_page' => $daftarPetani->lastPage(),
'per_page' => $daftarPetani->perPage(),
'total' => $daftarPetani->total(),
'from' => $daftarPetani->firstItem(),
'to' => $daftarPetani->lastItem(),
],
]);
}
}
@@ -0,0 +1,658 @@
<?php
namespace App\Http\Controllers\Web\Dashboard;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\JsonResponse;
use App\Models\Pertanian\Petani;
use App\Models\Pertanian\Komoditas;
use App\Models\Pertanian\Lahan;
use App\Models\Pertanian\Tanaman;
use App\Models\Pertanian\SkorPetani;
use App\Models\Wilayah\Provinsi;
use App\Models\Wilayah\KabupatenKota;
use App\Models\Wilayah\Kecamatan;
use App\Models\Wilayah\DesaKelurahan;
class IndeksPetaniController extends Controller
{
private function filters(Request $request): array
{
return [
'desaKelurahan_id' => $request->desaKelurahan_id,
'kecamatan_id' => $request->kecamatan_id,
'kabupatenKota_id' => $request->kabupatenKota_id,
'provinsi_id' => $request->provinsi_id,
'komoditas_id' => $request->komoditas_id,
'segmen' => $request->segmen,
];
}
private function petaniQuery(array $filters): \Illuminate\Database\Eloquent\Builder
{
$petaniQuery = Petani::query()
->join('master_desa_kelurahan', 'petani.desa_kelurahan_id', '=', 'master_desa_kelurahan.id')
->join('master_kecamatan', 'master_desa_kelurahan.kecamatan_id', '=', 'master_kecamatan.id')
->join('master_kabupaten_kota', 'master_kecamatan.kabupaten_kota_id', '=', 'master_kabupaten_kota.id')
->join('master_provinsi', 'master_kabupaten_kota.provinsi_id', '=', 'master_provinsi.id')
->whereNull('petani.deleted_at')
->when($filters['provinsi_id'], fn($q, $id) => $q->where('master_kabupaten_kota.provinsi_id', $id))
->when($filters['kabupatenKota_id'], fn($q, $id) => $q->where('master_kecamatan.kabupaten_kota_id', $id))
->when($filters['kecamatan_id'], fn($q, $id) => $q->where('master_desa_kelurahan.kecamatan_id', $id))
->when($filters['desaKelurahan_id'], fn($q, $id) => $q->where('petani.desa_kelurahan_id', $id))
->when($filters['komoditas_id'], fn($q, $id) => $q->where('petani.komoditas_id', $id));
return $petaniQuery;
}
private function skorPetaniQuery(array $filters, array $komoditas): \Illuminate\Database\Eloquent\Builder
{
$skor = SkorPetani::query()
->join('petani', 'petani_skor.petani_id', '=', 'petani.id')
->join('master_desa_kelurahan', 'petani.desa_kelurahan_id', '=', 'master_desa_kelurahan.id')
->join('master_kecamatan', 'master_desa_kelurahan.kecamatan_id', '=', 'master_kecamatan.id')
->join('master_kabupaten_kota', 'master_kecamatan.kabupaten_kota_id', '=', 'master_kabupaten_kota.id')
->join('master_provinsi', 'master_kabupaten_kota.provinsi_id', '=', 'master_provinsi.id')
->whereNull('petani.deleted_at')
->when($filters['provinsi_id'], fn($q, $id) => $q->where('master_kabupaten_kota.provinsi_id', $id))
->when($filters['kabupatenKota_id'], fn($q, $id) => $q->where('master_kecamatan.kabupaten_kota_id', $id))
->when($filters['kecamatan_id'], fn($q, $id) => $q->where('master_desa_kelurahan.kecamatan_id', $id))
->when($filters['desaKelurahan_id'], fn($q, $id) => $q->where('petani.desa_kelurahan_id', $id))
->whereIn('petani.komoditas_id', $komoditas);
if ($filters['segmen']) {
$skor->where(function ($q) use ($filters) {
match ($filters['segmen']) {
'Penggerak' => $q->where('skor_kesiapan_adopsi', '>=', 4),
'Perintis' => $q->where(function($q) {
$q->where('skor_kesiapan_adopsi', '>=', 3)
->where('skor_kesiapan_adopsi', '<', 4);
}),
'Pemerhati' => $q->where(function($q) {
$q->where('skor_kesiapan_adopsi', '>=', 2)
->where('skor_kesiapan_adopsi', '<', 3);
}),
'Pemula' => $q->where(function($q) {
$q->where('skor_kesiapan_adopsi', '>=', 1)
->where('skor_kesiapan_adopsi', '<', 2);
}),
'Belum Lengkap' => $q->where(function ($q) {
$q->whereNull('skor_kesiapan_adopsi')
->orWhere('skor_kesiapan_adopsi', '<', 1);
}),
default => null,
};
});
}
return $skor;
}
public function index(Request $request)
{
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
abort(403);
}
$filters = $this->filters($request);
$title = 'Dashboard Profil Petani';
$komoditas = Komoditas::where('is_utama', true)->get();
$petaniQuery = $this->petaniQuery($filters);
$desaKelurahan = $petaniQuery->clone()
->selectRaw("
master_desa_kelurahan.id as desa_kelurahan_id,
master_desa_kelurahan.nama as desa_kelurahan,
master_desa_kelurahan.path as path,
master_kecamatan.id as kecamatan_id,
master_kecamatan.nama as kecamatan,
master_kabupaten_kota.id as kabupaten_id,
master_kabupaten_kota.nama as kabupaten,
master_provinsi.id as provinsi_id,
master_provinsi.nama as provinsi
")
->distinct()
->get();
$wilayah = $desaKelurahan
->groupBy('provinsi_id')
->map(function ($provinsi) {
return [
'id' => $provinsi->first()->provinsi_id,
'nama' => $provinsi->first()->provinsi,
'kabupaten' => $provinsi
->groupBy('kabupaten_id')
->map(function ($kabupaten) {
return [
'id' => $kabupaten->first()->kabupaten_id,
'nama' => $kabupaten->first()->kabupaten,
'kecamatan' => $kabupaten
->groupBy('kecamatan_id')
->map(function ($kecamatan) {
return [
'id' => $kecamatan->first()->kecamatan_id,
'nama' => $kecamatan->first()->kecamatan,
'desa' => $kecamatan->map(function ($desa) {
return [
'id' => $desa->desa_kelurahan_id,
'nama' => $desa->desa_kelurahan,
'path' => $desa->path,
];
})->values(),
];
})->values(),
];
})->values(),
];
})->values();
return view('dashboard.indeks-petani', array_merge($filters,
compact(
'komoditas',
'desaKelurahan',
'wilayah',
)));
}
public function summary(Request $request): JsonResponse
{
$filters = $this->filters($request);
$petaniQuery = $this->petaniQuery($filters);
$komoditas = Komoditas::where('is_utama', true)
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get();
$countKomoditas = $petaniQuery->clone()
->whereHas('komoditas', fn ($query) => $query->where('is_utama', true))
->distinct()->count('komoditas_id');
$filteredPetaniIds = $petaniQuery
->clone()
->select('petani.id')
->distinct()
->pluck('petani.id');
$sumLahan = Lahan::has('petani')->whereIn('petani_id', $filteredPetaniIds)->whereIn('komoditas_id', $komoditas->pluck('id'))->sum('luas_lahan');
$avgLahan = Lahan::has('petani')->whereIn('petani_id', $filteredPetaniIds)->whereIn('komoditas_id', $komoditas->pluck('id'))->avg('luas_lahan');
$countDesa = $petaniQuery->clone()->whereIn('komoditas_id', $komoditas->pluck('id'))->distinct()->count('desa_kelurahan_id');
$countKecamatan = $petaniQuery->clone()->whereIn('komoditas_id', $komoditas->pluck('id'))->distinct()->count('master_kecamatan.id');
$skorQuery = $this->skorPetaniQuery($filters, $komoditas->pluck('id')->toArray());
$avgSkor = $skorQuery->clone()->avg('skor_kesiapan_adopsi');
if ($avgSkor >= 4.0) {
$avgSegmen = 'Penggerak';
} elseif ($avgSkor >= 3.0) {
$avgSegmen = 'Perintis';
} elseif ($avgSkor >= 2.0) {
$avgSegmen = 'Pemerhati';
} elseif ($avgSkor >= 1.0) {
$avgSegmen = 'Pemula';
} else {
$avgSegmen = 'Belum Lengkap';
}
return response()->json([
'petani' => $petaniQuery->clone()->count(),
'komoditas' => $countKomoditas,
'sum_lahan' => $sumLahan,
'avg_lahan' => $avgLahan,
'desa' => $countDesa,
'kecamatan' => $countKecamatan,
'skor' => $avgSkor,
'segmen' => $avgSegmen
]);
}
private function segments(): array
{
$segments = [
[
'name' => 'Penggerak',
'range' => '4.0 - 5.0',
'icon' => 'tabler-crown',
'color' => 'success',
],
[
'name' => 'Perintis',
'range' => '3.0 - 3.9',
'icon' => 'tabler-flag',
'color' => 'info',
],
[
'name' => 'Pemerhati',
'range' => '2.0 - 2.9',
'icon' => 'tabler-eye',
'color' => 'warning',
],
[
'name' => 'Pemula',
'range' => '1.0 - 1.9',
'icon' => 'tabler-seedling',
'color' => 'danger',
],
];
return $segments;
}
private function cases(): string
{
$case = "
CASE
WHEN skor_kesiapan_adopsi >= 4.0 THEN 'Penggerak'
WHEN skor_kesiapan_adopsi >= 3.0 THEN 'Perintis'
WHEN skor_kesiapan_adopsi >= 2.0 THEN 'Pemerhati'
WHEN skor_kesiapan_adopsi >= 1.0 THEN 'Pemula'
ELSE 'Belum Lengkap'
END
";
return $case;
}
public function segmen(Request $request): JsonResponse
{
$filters = $this->filters($request);
$segments = $this->segments();
$case = $this->cases();
$komoditas = Komoditas::where('is_utama', true)
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get();
$skorQuery = $this->skorPetaniQuery($filters, $komoditas->pluck('id')->toArray());
$countSegmentasi = $skorQuery->clone()
->selectRaw("$case AS segmen, COUNT(*) AS total")
->groupByRaw($case)
->get();
$series = collect($segments)
->map(function ($segment) use ($countSegmentasi) {
return $countSegmentasi
->where('segmen', $segment['name'])
->sum('total');
})
->values();
$countPetani = $series->sum();
$persentase = collect($segments)
->map(function ($segment) use ($countSegmentasi, $countPetani) {
$total = $countSegmentasi
->where('segmen', $segment['name'])
->sum('total');
return $countPetani > 0
? ($total / $countPetani) * 100
: 0;
})
->values();
return response()->json([
'series' => $series,
'persentase' => $persentase,
]);
}
public function segmenByKomoditas(Request $request): JsonResponse
{
$filters = $this->filters($request);
$segments = $this->segments();
$case = $this->cases();
$komoditas = Komoditas::where('is_utama', true)
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get();
$skorQuery = $this->skorPetaniQuery($filters, $komoditas->pluck('id')->toArray());
$countSegmentasiByKomoditas = $skorQuery->clone()
->selectRaw("
petani.komoditas_id,
$case AS segmen,
COUNT(petani.id) AS total
")
->groupBy('petani.komoditas_id')
->groupByRaw($case)
->get();
$seriesSegmentasiKomoditas = collect($segments)->map(function ($segment) use ($countSegmentasiByKomoditas, $komoditas) {
return [
'name' => $segment['name'],
'data' => $komoditas->map(function ($item) use ($countSegmentasiByKomoditas, $segment) {
return optional(
$countSegmentasiByKomoditas->first(function ($row) use ($item, $segment) {
return $row->komoditas_id == $item->id
&& $row->segmen == $segment['name'];
})
)->total ?? 0;
})->values(),
];
})->values();
return response()->json([
'categories' => $komoditas->pluck('nama')->values(),
'series' => $seriesSegmentasiKomoditas,
]);
}
public function dimensi(Request $request): JsonResponse
{
$filters = $this->filters($request);
$komoditas = Komoditas::where('is_utama', true)
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get();
$skorQuery = $this->skorPetaniQuery($filters, $komoditas->pluck('id')->toArray());
$avgDimensi = $skorQuery->clone()
->selectRaw("
avg(skor_d2_sistem_praktik_budidaya) as skor_d2_sistem_praktik_budidaya,
avg(skor_d4_sosial_ekonomi) as skor_d4_sosial_ekonomi,
avg(skor_d6_rantai_nilai_akses_pasar) as skor_d6_rantai_nilai_akses_pasar")
->first();
return response()->json([
'radarDimensi' => [
round($avgDimensi->skor_d2_sistem_praktik_budidaya, 2),
round($avgDimensi->skor_d4_sosial_ekonomi, 2),
round($avgDimensi->skor_d6_rantai_nilai_akses_pasar, 2),
]
]);
}
public function komponen(Request $request): JsonResponse
{
$filters = $this->filters($request);
$komoditas = Komoditas::where('is_utama', true)
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get();
$skorQuery = $this->skorPetaniQuery($filters, $komoditas->pluck('id')->toArray());
$avgKomponen = $skorQuery->clone()
->selectRaw("avg(skor_komp_1_d4_k3_motivasi_kapasitas_adopsi) as skor_komp_1_d4_k3_motivasi_kapasitas_adopsi,
avg(skor_komp_2_d4_k2_ekonomi_rumah_tangga) as skor_komp_2_d4_k2_ekonomi_rumah_tangga,
avg(skor_komp_3_d2_praktik_budidaya_gap) as skor_komp_3_d2_praktik_budidaya_gap,
avg(skor_komp_4_d4_k4_keterlibatan_kelembagaan) as skor_komp_4_d4_k4_keterlibatan_kelembagaan,
avg(skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan) as skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan,
avg(skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar) as skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar")
->first();
return response()->json([
'radarKomponen' => [
round($avgKomponen->skor_komp_1_d4_k3_motivasi_kapasitas_adopsi, 2),
round($avgKomponen->skor_komp_2_d4_k2_ekonomi_rumah_tangga, 2),
round($avgKomponen->skor_komp_3_d2_praktik_budidaya_gap, 2),
round($avgKomponen->skor_komp_4_d4_k4_keterlibatan_kelembagaan, 2),
round($avgKomponen->skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan, 2),
round($avgKomponen->skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar, 2)
]
]);
}
public function map(Request $request): JsonResponse
{
$filters = $this->filters($request);
$segments = $this->segments();
$colors = collect($segments)
->pluck('color')
->map(fn($c) => "var(--bs-{$c})")
->values();
$segmentStyles = collect($segments)->keyBy('name');
$case = $this->cases();
$petaniQuery = $this->petaniQuery($filters);
$desaKelurahan = $petaniQuery->clone()
->selectRaw("
master_desa_kelurahan.id as desa_kelurahan_id,
master_desa_kelurahan.nama as desa_kelurahan,
master_desa_kelurahan.path as path,
master_kecamatan.id as kecamatan_id,
master_kecamatan.nama as kecamatan,
master_kabupaten_kota.id as kabupaten_id,
master_kabupaten_kota.nama as kabupaten,
master_provinsi.id as provinsi_id,
master_provinsi.nama as provinsi
")
->distinct()
->get();
$komoditas = Komoditas::where('is_utama', true)
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get();
$skorQuery = $this->skorPetaniQuery($filters, $komoditas->pluck('id')->toArray());
$mapSegmentasi = $skorQuery->clone()
->selectRaw("petani.desa_kelurahan_id,
$case AS segmen,
COUNT(petani.id) AS total
")
->groupBy('petani.desa_kelurahan_id')
->groupByRaw($case)
->get();
$mapAvgSkorSegmentasi = $skorQuery->clone()
->selectRaw("petani.desa_kelurahan_id,
COUNT(petani.id) AS jumlah_petani,
avg(skor_kesiapan_adopsi) AS skor,
CASE
WHEN AVG(skor_kesiapan_adopsi) >= 4 THEN 'Penggerak'
WHEN AVG(skor_kesiapan_adopsi) >= 3 THEN 'Perintis'
WHEN AVG(skor_kesiapan_adopsi) >= 2 THEN 'Pemerhati'
WHEN AVG(skor_kesiapan_adopsi) >= 1 THEN 'Pemula'
ELSE 'Belum Lengkap'
END AS segmen
")
->groupBy('petani.desa_kelurahan_id')
->get();
$segmenDominan = $mapSegmentasi
->groupBy('desa_kelurahan_id')
->map(function ($items) {
return $items->sortByDesc('total')->first();
});
$mapAvgSkorSegmentasi->each(function ($item) use ($segmenDominan) {
$dominan = $segmenDominan->get($item->desa_kelurahan_id);
$item->segmen_dominan = $dominan?->segmen;
$item->jumlah_dominan = $dominan?->total;
});
$segmentasiByDesa = $mapAvgSkorSegmentasi->keyBy('desa_kelurahan_id');
$desaLookup = $desaKelurahan->keyBy('desa_kelurahan_id');
$mapDesaSegmentasi = $segmentasiByDesa->map(function ($segmentasi, $desaId) use ($desaLookup) {
$desa = $desaLookup->get($desaId);
if (!$desa) {
return null;
}
$desa->segmentasi = $segmentasi;
return $desa;
})->filter()->values();
return response()->json($mapDesaSegmentasi);
}
public function desa(Request $request): JsonResponse
{
$filters = $this->filters($request);
$segments = $this->segments();
$colors = collect($segments)
->pluck('color')
->map(fn($c) => "var(--bs-{$c})")
->values();
$segmentStyles = collect($segments)->keyBy('name');
$case = $this->cases();
$petaniQuery = $this->petaniQuery($filters);
$desaKelurahan = $petaniQuery->clone()
->selectRaw("
master_desa_kelurahan.id as desa_kelurahan_id,
master_desa_kelurahan.nama as desa_kelurahan,
master_desa_kelurahan.path as path,
master_kecamatan.id as kecamatan_id,
master_kecamatan.nama as kecamatan,
master_kabupaten_kota.id as kabupaten_id,
master_kabupaten_kota.nama as kabupaten,
master_provinsi.id as provinsi_id,
master_provinsi.nama as provinsi
")
->distinct()
->get();
$komoditas = Komoditas::where('is_utama', true)
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get();
$skorQuery = $this->skorPetaniQuery($filters, $komoditas->pluck('id')->toArray());
$mapSegmentasi = $skorQuery->clone()
->selectRaw("petani.desa_kelurahan_id,
$case AS segmen,
COUNT(petani.id) AS total
")
->groupBy('petani.desa_kelurahan_id')
->groupByRaw($case)
->get();
$mapAvgSkorSegmentasi = $skorQuery->clone()
->selectRaw("petani.desa_kelurahan_id,
COUNT(petani.id) AS jumlah_petani,
avg(skor_kesiapan_adopsi) AS skor,
CASE
WHEN AVG(skor_kesiapan_adopsi) >= 4 THEN 'Penggerak'
WHEN AVG(skor_kesiapan_adopsi) >= 3 THEN 'Perintis'
WHEN AVG(skor_kesiapan_adopsi) >= 2 THEN 'Pemerhati'
WHEN AVG(skor_kesiapan_adopsi) >= 1 THEN 'Pemula'
ELSE 'Belum Lengkap'
END AS segmen
")
->groupBy('petani.desa_kelurahan_id')
->get();
$segmenDominan = $mapSegmentasi
->groupBy('desa_kelurahan_id')
->map(function ($items) {
return $items->sortByDesc('total')->first();
});
$mapAvgSkorSegmentasi->each(function ($item) use ($segmenDominan) {
$dominan = $segmenDominan->get($item->desa_kelurahan_id);
$item->segmen_dominan = $dominan?->segmen;
$item->jumlah_dominan = $dominan?->total;
});
$segmentasiByDesa = $mapAvgSkorSegmentasi->keyBy('desa_kelurahan_id');
$desaLookup = $desaKelurahan->keyBy('desa_kelurahan_id');
$mapDesaSegmentasi = $segmentasiByDesa->map(function ($segmentasi, $desaId) use ($desaLookup) {
$desa = $desaLookup->get($desaId);
if (!$desa) {
return null;
}
$desa->segmentasi = $segmentasi;
return $desa;
})->filter()->values();
$rows = $mapDesaSegmentasi->map(function ($desa) use ($segmentStyles) {
$style = $segmentStyles[$desa->segmentasi->segmen] ?? ['color' => 'secondary'];
$styleDominan = $segmentStyles[$desa->segmentasi->segmen_dominan] ?? ['color' => 'secondary'];
return [
'desa' => $desa->desa_kelurahan,
'jumlah_petani' => $desa->segmentasi->jumlah_petani,
'segmen' => $desa->segmentasi->segmen,
'skor' => $desa->segmentasi->skor,
'segmen_dominan' => $desa->segmentasi->segmen_dominan,
'jumlah_dominan' => $desa->segmentasi->jumlah_dominan,
'color' => $style['color'],
'color_dominan' => $styleDominan['color'],
];
});
return response()->json($rows);
}
public function data(Request $request): JsonResponse
{
$filters = $this->filters($request);
$segments = $this->segments();
$colors = collect($segments)
->pluck('color')
->map(fn($c) => "var(--bs-{$c})")
->values();
$segmentStyles = collect($segments)->keyBy('name');
$petaniQuery = $this->petaniQuery($filters);
$komoditas = Komoditas::where('is_utama', true)
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get();
$daftarPetani = $petaniQuery->clone()
->select('petani.*')
->whereIn('komoditas_id', $komoditas->pluck('id'))
->get();
$data = $daftarPetani
->filter(function ($item) use ($filters) {
if (!$filters['segmen']) {
return true;
}
$segmen = $item->skor ? $item->segmen() : 'Belum Lengkap';
return $segmen === $filters['segmen'];
})
->map(function ($item) use ($segmentStyles) {
$segmen = $item->skor
? $item->segmen()
: null;
$style = $segmentStyles[$segmen]
?? ['color' => 'secondary'];
return [
'id' => $item->id,
'nama' => $item->nama,
'desa' => $item->desaKelurahan?->nama,
'desa_kelurahan_id' => $item->desa_kelurahan_id,
'komoditas' => $item->komoditas?->nama,
'luas_lahan' => $item->lahans?->sum('luas_lahan') ?? 0,
'skor' => $item->skor
? $item->skor->skor_kesiapan_adopsi
: null,
'segmen' => $segmen,
'color' => $style['color'],
'url' => route('dashboard.profil-petani', $item->id),
];
})
->values();
return response()->json($data);
}
}
@@ -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',
};
}
}
@@ -0,0 +1,798 @@
<?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)
{
$defaultKabupatenId = $this->defaultKabupatenId();
$initialFilters = $this->selectedFilters($request, $defaultKabupatenId);
$payload = $this->dashboardPayload($initialFilters);
$filterOptions = [
[
'key' => 'kabupaten',
'label' => 'Kabupaten',
'options' => $this->kabupatenOptions(),
'icon' => 'tabler-map-pin',
'readonly' => true,
],
[
'key' => 'kecamatan',
'label' => 'Kecamatan',
'options' => $this->withAllOption('Semua Kecamatan', []),
],
[
'key' => 'desa',
'label' => 'Desa',
'options' => $this->withAllOption('Semua Desa', []),
],
[
'key' => 'komoditas',
'label' => 'Komoditas',
'options' => $this->withAllOption('Semua Komoditas', []),
],
[
'key' => 'segmentasi',
'label' => 'Segmentasi',
'options' => $this->withAllOption('Semua Segmentasi', []),
],
];
return view('dashboard.indikator-pertanian', [
'title' => 'Indikator Pertanian',
'initialFilters' => $initialFilters,
'filterOptions' => $filterOptions,
] + $payload);
}
public function data(Request $request)
{
if ($request->query('mode') === 'filter-options') {
return response()->json([
'komoditas' => $this->komoditasOptions(),
'segmentasi' => $this->segmentasiOptions(),
]);
}
$filters = $this->selectedFilters($request, $this->defaultKabupatenId());
return response()->json([
'filters' => $filters,
] + $this->dashboardPayload($filters));
}
private function defaultKabupatenId(): string
{
$ngadaOption = collect($this->kabupatenOptions())->first(
fn ($option) => str_contains(strtolower($option['label']), 'ngada')
);
return $ngadaOption['value'] ?? 'all';
}
private function dashboardPayload(array $filters): array
{
return [
'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;
$totalLuasLahan = $this->applyLahanFilters(Lahan::has('petani'), $filters)->sum('luas_lahan');
$rataRataLuasLahan = $totalPetani > 0
? $totalLuasLahan / $totalPetani
: 0;
$totalProduksiKg = $this->totalProduksiKg($filters);
$rataRataProduksiKg = $totalPetani > 0
? $totalProduksiKg / $totalPetani
: 0;
$rataRataProduktivitas = $totalLuasLahan > 0
? $totalProduksiKg / $totalLuasLahan
: 0;
return [
[
'value' => number_format($totalPetani, 0, ',', '.'),
'label' => 'Petani Terdata',
'description' => 'Total petani yang masuk sistem',
'icon' => 'tabler-users',
'variant' => 'primary',
],
[
'value' => number_format($totalLuasLahan, 2, ',', '.').' ha',
'label' => 'Total Luas Lahan',
'description' => 'Rata-rata '.number_format($rataRataLuasLahan, 2, ',', '.').' ha/petani',
'icon' => 'tabler-mountain',
'variant' => 'success',
],
[
'value' => number_format($totalProduksiKg, 0, ',', '.').' kg',
'label' => 'Total Produksi (Semua Komoditas)',
'description' => 'Rata-rata '.number_format($rataRataProduksiKg, 0, ',', '.').' kg/petani',
'icon' => 'tabler-wheat',
'variant' => 'warning',
],
[
'value' => number_format($rataRataProduktivitas, 0, ',', '.').' kg/ha',
'label' => 'Rata-rata Produktivitas',
'description' => 'Semua komoditas berdasarkan total produksi dan luas lahan',
'icon' => 'tabler-chart-bar',
'variant' => 'info',
],
];
}
private function chartData(array $filters): array
{
return [
'petaniPerKomoditas' => $this->petaniPerKomoditasChart($filters),
'luasLahanPerKecamatan' => $this->luasLahanPerKecamatanChart($filters),
'kondisiTanaman' => $this->kondisiTanamanChart($filters),
'statusKepemilikanLahan' => $this->statusKepemilikanLahanChart($filters),
'seranganHama' => $this->seranganHamaChart($filters),
'penyakitTanaman' => $this->penyakitTanamanChart($filters),
'keparahanSerangan' => $this->keparahanSeranganChart($filters),
'statusHakKelolaLahan' => $this->statusHakKelolaLahanChart($filters),
'praktikPemupukan' => $this->praktikPemupukanChart($filters),
'produksiPerKomoditas' => $this->produksiPerKomoditasChart($filters),
'produktivitasPerKomoditas' => $this->produktivitasPerKomoditasChart($filters),
'luasPanenPerKomoditas' => $this->luasPanenPerKomoditasChart($filters),
'statusPanen' => $this->statusPanenChart($filters),
'surveyProduktivitasHa' => $this->surveyProduktivitasByUnitChart($filters, 'ha_tahun'),
'surveyProduktivitasPohon' => $this->surveyProduktivitasByUnitChart($filters, 'pohon_tahun'),
];
}
private function selectedFilters(Request $request, string $defaultKabupaten): array
{
$filters = [
'kabupaten' => $defaultKabupaten,
'kecamatan' => $this->validFilterValue($request->query('kecamatan'), Kecamatan::class),
'desa' => $this->validFilterValue($request->query('desa'), DesaKelurahan::class),
'komoditas' => $this->validFilterValue($request->query('komoditas'), Komoditas::class),
'segmentasi' => $this->validSegmentasi((string) $request->query('segmentasi', 'all')),
];
if ($filters['desa'] !== 'all') {
$desa = DesaKelurahan::query()->find($filters['desa']);
if (! $desa || ($filters['kecamatan'] !== 'all' && (string) $desa->kecamatan_id !== $filters['kecamatan'])) {
$filters['desa'] = 'all';
}
}
if ($filters['kecamatan'] === 'all') {
$filters['desa'] = 'all';
}
return $filters;
}
private function validFilterValue(mixed $value, string $modelClass): string
{
if (! $value || $value === 'all') {
return 'all';
}
return $modelClass::query()->whereKey($value)->exists()
? (string) $value
: 'all';
}
private function validSegmentasi(string $value): string
{
return in_array($value, [
'Penggerak',
'Perintis',
'Pemerhati',
'Pemula',
'Belum tersegmentasi',
'all',
], true) ? $value : 'all';
}
private function kabupatenOptions(): array
{
return $this->withAllOption(
'Semua Kabupaten',
KabupatenKota::query()
->orderBy('nama')
->get(['id', 'kode', 'nama'])
->map(fn (KabupatenKota $kabupaten) => [
'value' => (string) $kabupaten->id,
'label' => $this->kabupatenLabel($kabupaten),
])
->values()
->all()
);
}
private function kecamatanOptions(): array
{
return $this->withAllOption(
'Semua Kecamatan',
Kecamatan::query()
->orderBy('nama')
->get(['id', 'nama'])
->map(fn (Kecamatan $kecamatan) => [
'value' => (string) $kecamatan->id,
'label' => $kecamatan->nama,
])
->values()
->all()
);
}
private function desaOptions(): array
{
return $this->withAllOption(
'Semua Desa',
DesaKelurahan::query()
->orderBy('nama')
->get(['id', 'nama', 'kecamatan_id'])
->map(fn (DesaKelurahan $desa) => [
'value' => (string) $desa->id,
'label' => $desa->nama,
'parent' => (string) $desa->kecamatan_id,
])
->values()
->all()
);
}
private function komoditasOptions(): array
{
return $this->withAllOption(
'Semua Komoditas',
Komoditas::query()
->orderBy('nama')
->get(['id', 'nama'])
->map(fn (Komoditas $komoditas) => [
'value' => (string) $komoditas->id,
'label' => $komoditas->nama,
])
->values()
->all()
);
}
private function segmentasiOptions(): array
{
$segments = SkorPetani::query()
->whereNotNull('skor_kesiapan_adopsi')
->pluck('skor_kesiapan_adopsi')
->map(fn ($score) => $this->segmentLabel((float) $score))
->filter()
->unique()
->sortBy(fn ($segment) => array_search($segment, [
'Penggerak',
'Perintis',
'Pemerhati',
'Pemula',
'Belum tersegmentasi',
], true))
->map(fn ($segment) => [
'value' => $segment,
'label' => $segment,
])
->values()
->all();
return $this->withAllOption('Semua Segmentasi', $segments);
}
private function totalProduksiKg(array $filters): float
{
return $this->applyProduksiFilters(Produksi::query(), $filters)
->whereNotNull('jumlah_panen')
->get(['jumlah_panen', 'satuan'])
->sum(fn (Produksi $produksi) => $this->toKilograms(
(float) $produksi->jumlah_panen,
(string) $produksi->satuan
));
}
private function petaniPerKomoditasChart(array $filters): array
{
$rows = $this->applyPetaniFilters(Petani::query(), $filters)
->leftJoin('master_komoditas as k', 'k.id', '=', 'petani.komoditas_id')
->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
->selectRaw('COUNT(petani.id) as total')
->groupBy('k.id', 'k.nama')
->orderByDesc('total')
->get();
return [
'labels' => $rows->pluck('label')->values()->all(),
'series' => $rows->pluck('total')->map(fn ($value) => (int) $value)->values()->all(),
];
}
private function luasLahanPerKecamatanChart(array $filters): array
{
$rows = $this->applyLahanFilters(Lahan::query(), $filters)
->leftJoin('master_desa_kelurahan as d', 'd.id', '=', 'map_lahan.desa_kelurahan_id')
->leftJoin('master_kecamatan as k', 'k.id', '=', 'd.kecamatan_id')
->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
->selectRaw('SUM(map_lahan.luas_lahan) / 10000 as total')
->groupBy('k.id', 'k.nama')
->havingRaw('SUM(map_lahan.luas_lahan) > 0')
->orderByDesc('total')
->limit(8)
->get()
->filter(fn ($row) => round((float) $row->total, 2) > 0)
->values();
return [
'labels' => $rows->pluck('label')->values()->all(),
'series' => $rows->pluck('total')->map(fn ($value) => round((float) $value, 2))->values()->all(),
];
}
private function kondisiTanamanChart(array $filters): array
{
$rows = $this->applyTanamanTableFilters(DB::table('map_tanaman'), $filters)
->whereNull('map_tanaman.deleted_at')
->whereNotNull('map_tanaman.kondisi_tanaman')
->selectRaw('map_tanaman.kondisi_tanaman as label')
->selectRaw('COUNT(map_tanaman.id) as total')
->groupBy('map_tanaman.kondisi_tanaman')
->orderByDesc('total')
->get();
if ($rows->isEmpty()) {
$rows = $this->applyLahanFilters(Lahan::query(), $filters)
->whereNull('deleted_at')
->whereNotNull('kondisi_tanaman')
->selectRaw('kondisi_tanaman as label')
->selectRaw('COUNT(map_lahan.id) as total')
->groupBy('kondisi_tanaman')
->orderByDesc('total')
->get();
}
return [
'labels' => $rows->pluck('label')->map(fn ($label) => $this->shortEnumLabel((string) $label))->values()->all(),
'series' => $rows->pluck('total')->map(fn ($value) => (int) $value)->values()->all(),
];
}
private function seranganHamaChart(array $filters): array
{
return $this->tanamanAttributeChart(
$filters,
'serangan_hama',
fn ($label) => $this->enumLabel(SeranganHama::class, (string) $label)
);
}
private function penyakitTanamanChart(array $filters): array
{
return $this->tanamanAttributeChart(
$filters,
'penyakit_tanaman',
fn ($label) => $this->enumLabel(PenyakitTanaman::class, (string) $label)
);
}
private function keparahanSeranganChart(array $filters): array
{
return $this->tanamanAttributeChart(
$filters,
'keparahan_serangan',
fn ($label) => $this->keparahanSeranganLabel((string) $label)
);
}
private function statusKepemilikanLahanChart(array $filters): array
{
$rows = $this->applyLahanFilters(Lahan::query(), $filters)
->whereNotNull('status_kepemilikan')
->selectRaw('status_kepemilikan as label')
->selectRaw('COUNT(map_lahan.id) as total')
->groupBy('status_kepemilikan')
->orderByDesc('total')
->get();
return $this->chartFromRows($rows, fn ($label) => $this->shortEnumLabel((string) $label));
}
private function statusHakKelolaLahanChart(array $filters): array
{
$rows = $this->applySosialEkonomiFilters(SosialEkonomi::query(), $filters)
->whereNotNull('status_kepemilikan_lahan')
->selectRaw('status_kepemilikan_lahan as label')
->selectRaw('COUNT(sosial_ekonomi.id) as total')
->groupBy('status_kepemilikan_lahan')
->orderByDesc('total')
->get();
return $this->chartFromRows(
$rows,
fn ($label) => $this->enumLabel(StatusKepemilikanLahanSosialEkonomi::class, (string) $label)
);
}
private function praktikPemupukanChart(array $filters): array
{
$rows = $this->applyPraktikBudidayaFilters(PraktikBudidaya::query(), $filters)
->whereNotNull('pemberian_pupuk')
->selectRaw('pemberian_pupuk as label')
->selectRaw('COUNT(praktik_budidaya.id) as total')
->groupBy('pemberian_pupuk')
->orderBy('pemberian_pupuk')
->get();
return $this->chartFromRows(
$rows,
fn ($label) => $this->pemberianPupukLabel((int) $label)
);
}
private function produksiPerKomoditasChart(array $filters): array
{
$rows = $this->applyProduksiFilters(Produksi::query(), $filters)
->leftJoin('master_komoditas as k', 'k.id', '=', 'produksi.komoditas_id')
->whereNotNull('jumlah_panen')
->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
->selectRaw('produksi.jumlah_panen')
->selectRaw('produksi.satuan')
->get()
->groupBy('label')
->map(fn ($items) => round($items->sum(
fn (Produksi $produksi) => $this->toKilograms(
(float) $produksi->jumlah_panen,
(string) $produksi->satuan
)
), 2))
->filter(fn ($total) => $total > 0)
->sortDesc();
return [
'labels' => $rows->keys()->values()->all(),
'series' => $rows->values()->map(fn ($value) => round((float) $value, 2))->values()->all(),
];
}
private function produktivitasPerKomoditasChart(array $filters): array
{
$rows = $this->applyProduksiFilters(Produksi::query(), $filters)
->leftJoin('master_komoditas as k', 'k.id', '=', 'produksi.komoditas_id')
->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
->selectRaw('produksi.jumlah_panen')
->selectRaw('produksi.satuan')
->selectRaw('produksi.luas_panen')
->selectRaw('produksi.produktivitas')
->get()
->groupBy('label')
->map(function ($items) {
$storedAverage = $items
->pluck('produktivitas')
->filter(fn ($value) => (float) $value > 0)
->avg();
if ($storedAverage > 0) {
return round((float) $storedAverage, 2);
}
$totalProduksiKg = $items->sum(
fn (Produksi $produksi) => $this->toKilograms(
(float) $produksi->jumlah_panen,
(string) $produksi->satuan
)
);
$totalLuasPanen = $items->sum(fn (Produksi $produksi) => (float) $produksi->luas_panen);
return $totalLuasPanen > 0
? round($totalProduksiKg / $totalLuasPanen, 2)
: 0;
})
->filter(fn ($total) => $total > 0)
->sortDesc();
return [
'labels' => $rows->keys()->values()->all(),
'series' => $rows->values()->map(fn ($value) => round((float) $value, 2))->values()->all(),
];
}
private function luasPanenPerKomoditasChart(array $filters): array
{
$rows = $this->applyProduksiFilters(Produksi::query(), $filters)
->leftJoin('master_komoditas as k', 'k.id', '=', 'produksi.komoditas_id')
->whereNotNull('luas_panen')
->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
->selectRaw('SUM(produksi.luas_panen) as total')
->groupBy('k.id', 'k.nama')
->havingRaw('SUM(produksi.luas_panen) > 0')
->orderByDesc('total')
->get();
return [
'labels' => $rows->pluck('label')->values()->all(),
'series' => $rows->pluck('total')->map(fn ($value) => round((float) $value, 2))->values()->all(),
];
}
private function statusPanenChart(array $filters): array
{
$rows = $this->applyProduksiFilters(Produksi::query(), $filters)
->whereNotNull('status_panen')
->selectRaw("COALESCE(status_panen, 'Belum ditentukan') as label")
->selectRaw('COUNT(produksi.id) as total')
->groupBy('status_panen')
->orderByDesc('total')
->get();
return $this->chartFromRows($rows, fn ($label) => $this->shortEnumLabel((string) $label));
}
private function surveyProduktivitasByUnitChart(array $filters, string $unit): array
{
$rows = $this->applySurveyProduktivitasFilters(SurveyProduktivitas::query(), $filters)
->leftJoin('master_komoditas as k', 'k.id', '=', 'survey_produktivitas.komoditas_id')
->where('satuan', $unit)
->whereNotNull('kuantitas')
->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
->selectRaw('AVG(survey_produktivitas.kuantitas) as total')
->groupBy('k.id', 'k.nama')
->havingRaw('AVG(survey_produktivitas.kuantitas) > 0')
->orderByDesc('total')
->get();
return [
'labels' => $rows->pluck('label')->values()->all(),
'series' => $rows->pluck('total')->map(fn ($value) => round((float) $value, 2))->values()->all(),
];
}
private function 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);
}),
};
}
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',
};
}
}
@@ -22,10 +22,13 @@ class ProfilPetaniController extends Controller
}
$title = 'Dashboard Profil Petani';
$komoditas = Komoditas::where('is_utama', true)->get();
$komoditas = Komoditas::select('id', 'nama')->where('is_utama', true)->get();
$komoditasIds = $komoditas->pluck('id');
$petani = Petani::pluck('id');
$lahan = Lahan::whereIn('petani_id', $petani)->get();
$petaniIds = fn () => Petani::query()->select('id');
$lahanIds = fn () => Lahan::query()
->whereIn('petani_id', $petaniIds())
->select('id');
$petaniSummary = Petani::select(
'komoditas_id',
@@ -38,14 +41,14 @@ class ProfilPetaniController extends Controller
DB::raw('COUNT(*) as total'),
DB::raw('SUM(luas_lahan) as luas_lahan')
)
->whereIn('petani_id', $petani)
->whereIn('petani_id', $petaniIds())
->groupBy('komoditas_id')
->get();
$tanamanSummary = Tanaman::select(
'komoditas_id',
DB::raw('COUNT(*) as total')
)
->whereIn('lahan_id', $lahan->pluck('id'))
->whereIn('lahan_id', $lahanIds())
->groupBy('komoditas_id')
->get();
@@ -59,7 +62,7 @@ class ProfilPetaniController extends Controller
END
";
$skorSummary = SkorPetani::selectRaw("$case AS segmen, COUNT(*) AS total")
->whereIn('petani_id', $petani)
->whereIn('petani_id', $petaniIds())
->groupByRaw($case)
->get();
@@ -70,16 +73,15 @@ class ProfilPetaniController extends Controller
$case AS segmen,
COUNT(petani.id) AS total
")
->whereIn('petani_skor.petani_id', $petani)
->whereIn('petani_skor.petani_id', $petaniIds())
->groupBy('petani.komoditas_id')
->groupByRaw($case)
->get();
$skorAverage = SkorPetani::query()
->join('petani', 'petani_skor.petani_id', '=', 'petani.id')
->whereIn('petani_id', $petani)
->whereIn('petani.komoditas_id', $komoditas->pluck('id'))
->whereIn('petani_id', $petaniIds())
->whereIn('petani.komoditas_id', $komoditasIds)
->avg('skor_kesiapan_adopsi');
if ($skorAverage >= 4.0) {
$segmenAverage = 'Penggerak';
@@ -95,8 +97,8 @@ class ProfilPetaniController extends Controller
$skorDimensi = SkorPetani::query()
->join('petani', 'petani_skor.petani_id', '=', 'petani.id')
->whereIn('petani_id', $petani)
->whereIn('petani.komoditas_id', $komoditas->pluck('id'))
->whereIn('petani_id', $petaniIds())
->whereIn('petani.komoditas_id', $komoditasIds)
->selectRaw("
avg(skor_d2_sistem_praktik_budidaya) as skor_d2_sistem_praktik_budidaya,
avg(skor_d4_sosial_ekonomi) as skor_d4_sosial_ekonomi,
@@ -105,20 +107,20 @@ class ProfilPetaniController extends Controller
$skorKomponen = SkorPetani::query()
->join('petani', 'petani_skor.petani_id', '=', 'petani.id')
->whereIn('petani_id', $petani)
->whereIn('petani.komoditas_id', $komoditas->pluck('id'))
->whereIn('petani_id', $petaniIds())
->whereIn('petani.komoditas_id', $komoditasIds)
->selectRaw("avg(skor_komp_1_d4_k3_motivasi_kapasitas_adopsi) as skor_komp_1_d4_k3_motivasi_kapasitas_adopsi,
avg(skor_komp_2_d4_k2_ekonomi_rumah_tangga) as skor_komp_2_d4_k2_ekonomi_rumah_tangga,
avg(skor_komp_3_d2_praktik_budidaya_gap) as skor_komp_3_d2_praktik_budidaya_gap,
avg(skor_komp_4_d4_k4_keterlibatan_kelembagaan) as skor_komp_4_d4_k4_keterlibatan_kelembagaan,
avg(skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan) as skor_komp_5_d4_k5_k6_akses_kebebasan_keuanga,
avg(skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan) as skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan,
avg(skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar) as skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar")
->first();
$skorMultiDimensi = SkorPetani::query()
->join('petani', 'petani_skor.petani_id', '=', 'petani.id')
->whereIn('petani_id', $petani)
->whereIn('petani.komoditas_id', $komoditas->pluck('id'))
->whereIn('petani_id', $petaniIds())
->whereIn('petani.komoditas_id', $komoditasIds)
->selectRaw("$case AS segmen,
avg(skor_kesiapan_adopsi) as skor_kesiapan_adopsi,
avg(skor_d2_sistem_praktik_budidaya) as skor_d2_sistem_praktik_budidaya,
@@ -127,9 +129,29 @@ class ProfilPetaniController extends Controller
->groupByRaw($case)
->get();
$petani = Petani::with(['skor'])
->whereIn('id', $petani)
->whereIn('komoditas_id', $komoditas->pluck('id'))
$skorMultiKomponen = SkorPetani::query()
->join('petani', 'petani_skor.petani_id', '=', 'petani.id')
->whereIn('petani_id', $petaniIds())
->whereIn('petani.komoditas_id', $komoditasIds)
->selectRaw("$case AS segmen,
avg(skor_kesiapan_adopsi) as skor_kesiapan_adopsi,
avg(skor_komp_1_d4_k3_motivasi_kapasitas_adopsi) as skor_komp_1_d4_k3_motivasi_kapasitas_adopsi,
avg(skor_komp_2_d4_k2_ekonomi_rumah_tangga) as skor_komp_2_d4_k2_ekonomi_rumah_tangga,
avg(skor_komp_3_d2_praktik_budidaya_gap) as skor_komp_3_d2_praktik_budidaya_gap,
avg(skor_komp_4_d4_k4_keterlibatan_kelembagaan) as skor_komp_4_d4_k4_keterlibatan_kelembagaan,
avg(skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan) as skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan,
avg(skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar) as skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar")
->groupByRaw($case)
->get();
$petani = Petani::query()
->select('id', 'nama', 'jenis_kelamin', 'tanggal_lahir', 'komoditas_id')
->with([
'komoditas:id,nama',
'skor:id,petani_id,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',
])
->whereIn('komoditas_id', $komoditasIds)
->orderBy('nama')
->get();
return view('dashboard.petani', compact('title',
@@ -144,6 +166,7 @@ class ProfilPetaniController extends Controller
'skorDimensi',
'skorKomponen',
'skorMultiDimensi',
'skorMultiKomponen',
'petani',
));
}
@@ -34,12 +34,42 @@ class SummaryController extends Controller
$totalpetani = Profile::where('role', 'petani')->count();
$totallahan = Lahan::whereNotNull('path')->count();
$petaniList = Profile::where('role', 'petani')
->with(['lahan.tanaman.komoditas', 'desaKelurahan'])
$petaniList = Profile::select('id', 'nama', 'role', 'desa_kelurahan_id')
->where('role', 'petani')
->with([
'lahan:id,profile_id,luas_lahan',
'desaKelurahan:id,nama,kecamatan_id',
])
->paginate(20);
$allLahan = Lahan::whereNotNull('path')
->with(['profile', 'tanaman.komoditas', 'desaKelurahan.kecamatan.kabupatenKota.provinsi'])
$lahanSummaryByDesa = Lahan::whereNotNull('path')
->whereNotNull('desa_kelurahan_id')
->select('desa_kelurahan_id')
->selectRaw('COUNT(*) as total_lahan')
->selectRaw('COALESCE(SUM(luas_lahan), 0) as total_luas')
->groupBy('desa_kelurahan_id')
->get();
$desaList = DesaKelurahan::select('id', 'nama', 'path', 'kecamatan_id')
->whereIn('id', $lahanSummaryByDesa->pluck('desa_kelurahan_id')->filter()->unique()->values())
->with([
'kecamatan:id,nama,path,kabupaten_kota_id',
'kecamatan.kabupatenKota:id,nama,path,provinsi_id',
'kecamatan.kabupatenKota.provinsi:id,nama,path',
])
->get()
->keyBy('id');
$mapLahanLimit = min(max((int) $request->integer('map_lahan_limit', 500), 0), 2000);
$mapLahan = Lahan::whereNotNull('path')
->select('id', 'profile_id', 'desa_kelurahan_id', 'path', 'luas_lahan')
->with([
'profile:id,nama',
'tanaman:id,lahan_id,komoditas_id',
'tanaman.komoditas:id,nama',
])
->orderByDesc('updated_at')
->limit($mapLahanLimit)
->get();
$geoProvinsi = [];
@@ -48,47 +78,51 @@ class SummaryController extends Controller
$geoDesa = [];
$geoLahan = [];
foreach ($allLahan as $lahan) {
$desa = $lahan->desaKelurahan;
foreach ($lahanSummaryByDesa as $summary) {
$desa = $desaList->get($summary->desa_kelurahan_id);
$kec = $desa ? $desa->kecamatan : null;
$kab = $kec ? $kec->kabupatenKota : null;
$prov = $kab ? $kab->provinsi : null;
$totalLahan = (int) $summary->total_lahan;
$totalLuas = (float) $summary->total_luas;
if ($prov && $prov->path) {
if (!isset($geoProvinsi[$prov->id])) {
$geoProvinsi[$prov->id] = ['id' => $prov->id, 'nama' => $prov->nama, 'polygon' => $prov->path, 'total_lahan' => 0, 'total_luas' => 0];
}
$geoProvinsi[$prov->id]['total_lahan']++;
$geoProvinsi[$prov->id]['total_luas'] += $lahan->luas_lahan;
$geoProvinsi[$prov->id]['total_lahan'] += $totalLahan;
$geoProvinsi[$prov->id]['total_luas'] += $totalLuas;
}
if ($kab && $kab->path) {
if (!isset($geoKabupaten[$kab->id])) {
$geoKabupaten[$kab->id] = ['id' => $kab->id, 'parent_id' => $prov->id ?? null, 'nama' => $kab->nama, 'polygon' => $kab->path, 'total_lahan' => 0, 'total_luas' => 0];
}
$geoKabupaten[$kab->id]['total_lahan']++;
$geoKabupaten[$kab->id]['total_luas'] += $lahan->luas_lahan;
$geoKabupaten[$kab->id]['total_lahan'] += $totalLahan;
$geoKabupaten[$kab->id]['total_luas'] += $totalLuas;
}
if ($kec && $kec->path) {
if (!isset($geoKecamatan[$kec->id])) {
$geoKecamatan[$kec->id] = ['id' => $kec->id, 'parent_id' => $kab->id ?? null, 'nama' => $kec->nama, 'polygon' => $kec->path, 'total_lahan' => 0, 'total_luas' => 0];
}
$geoKecamatan[$kec->id]['total_lahan']++;
$geoKecamatan[$kec->id]['total_luas'] += $lahan->luas_lahan;
$geoKecamatan[$kec->id]['total_lahan'] += $totalLahan;
$geoKecamatan[$kec->id]['total_luas'] += $totalLuas;
}
if ($desa && $desa->path) {
if (!isset($geoDesa[$desa->id])) {
$geoDesa[$desa->id] = ['id' => $desa->id, 'parent_id' => $kec->id ?? null, 'nama' => $desa->nama, 'polygon' => $desa->path, 'total_lahan' => 0, 'total_luas' => 0];
}
$geoDesa[$desa->id]['total_lahan']++;
$geoDesa[$desa->id]['total_luas'] += $lahan->luas_lahan;
$geoDesa[$desa->id]['total_lahan'] += $totalLahan;
$geoDesa[$desa->id]['total_luas'] += $totalLuas;
}
}
foreach ($mapLahan as $lahan) {
$geoLahan[] = [
'id' => $lahan->id,
'parent_id' => $desa->id ?? null,
'parent_id' => $lahan->desa_kelurahan_id,
'polygon' => $lahan->path,
'nama_petani' => $lahan->profile->nama ?? 'N/A',
'luas' => $lahan->luas_lahan,
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Web\Dashboard;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@@ -14,6 +15,9 @@ use App\Models\Pertanian\Lahan;
use App\Models\Pertanian\Tanaman;
use App\Models\Pertanian\SurveyPelatihan;
use App\Models\Pertanian\SurveySertifikasi;
use App\Enums\AksesPasar\PraktikFermentasi;
use App\Enums\AksesPasar\SaluranTerdahulu;
use App\Enums\AksesPasar\SkorPengalaman;
use BackedEnum;
@@ -63,7 +67,8 @@ class SurveyPetaniController extends Controller
->orderByDesc('petani_count')
->get();
$totalPetani = (int) $komoditasCollection->sum('petani_count');
// $totalPetani = (int) $komoditasCollection->sum('petani_count');
$totalPetani = Petani::count();
$circumference = 2 * pi() * 46;
$komoditas = $komoditasCollection
@@ -91,36 +96,34 @@ class SurveyPetaniController extends Controller
// Progres pengisian data per petani
$progressPetani = Petani::query()
->select([
'id',
'nama',
])
->withExists([
'lahans as has_lahan',
'praktikBudidaya as has_praktik_budidaya',
'sosialEkonomi as has_sosial_ekonomi',
'aksesPasar as has_akses_pasar',
->select(['id', 'nama', 'komoditas_id'])
->with([
'komoditas:id,nama',
'lahans',
'praktikBudidaya',
'sosialEkonomi',
'aksesPasar',
])
->orderBy('nama')
->get()
->map(function ($petani) {
$status = [
'lahan' => [
'label' => 'Lahan',
'done' => (bool) $petani->has_lahan,
],
'praktik_budidaya' => [
'label' => 'Praktik Budidaya',
'done' => (bool) $petani->has_praktik_budidaya,
],
'sosial_ekonomi' => [
'label' => 'Sosial Ekonomi',
'done' => (bool) $petani->has_sosial_ekonomi,
],
'akses_pasar' => [
'label' => 'Akses Pasar',
'done' => (bool) $petani->has_akses_pasar,
],
'lahan' => $this->moduleStatus(
'Lahan',
$this->hasCompleteLahan($petani)
),
'praktik_budidaya' => $this->moduleStatus(
'Praktik Budidaya',
$this->hasCompletePraktikBudidaya($petani->praktikBudidaya)
),
'sosial_ekonomi' => $this->moduleStatus(
'Sosial Ekonomi',
$this->hasCompleteSosialEkonomi($petani->sosialEkonomi)
),
'akses_pasar' => $this->moduleStatus(
'Akses Pasar',
$this->hasCompleteAksesPasar($petani->aksesPasar)
),
];
$totalModul = count($status);
@@ -130,12 +133,13 @@ class SurveyPetaniController extends Controller
->count();
return [
'id' => $petani->id,
'name' => $petani->nama,
'status' => $status,
'done' => $modulSelesai,
'total' => $totalModul,
'percent' => $totalModul > 0
'id' => $petani->id,
'name' => $petani->nama,
'komoditas' => $petani->komoditas?->nama ?? 'Belum ditentukan',
'status' => $status,
'done' => $modulSelesai,
'total' => $totalModul,
'percent' => $totalModul > 0
? (int) round(($modulSelesai / $totalModul) * 100)
: 0,
];
@@ -144,8 +148,9 @@ class SurveyPetaniController extends Controller
// Ringkasan lahan
$totalLuasLahan = ((float) Lahan::query()
->sum('luas_lahan')) / 10000;
// $totalLuasLahan = ((float) Lahan::query()
// ->sum('luas_lahan')) / 10000;
$totalLuasLahan = Lahan::has('petani')->sum('luas_lahan');
// Jumlah tanaman berdasarkan komoditas petani
@@ -276,6 +281,147 @@ class SurveyPetaniController extends Controller
]);
}
private function moduleStatus(string $label, bool $done): array
{
return [
'label' => $label,
'done' => $done,
];
}
private function hasCompleteLahan(Petani $petani): bool
{
return $petani->lahans->contains(function ($lahan) {
return $this->allFieldsFilled($lahan, [
'luas_lahan',
'komoditas_id',
'jenis_komoditas_id',
'umur_tanaman',
'kondisi_tanaman',
]);
});
}
private function hasCompletePraktikBudidaya(?Model $praktikBudidaya): bool
{
return $this->allFieldsFilled($praktikBudidaya, [
'frekuensi_pemangkasan_utama',
'pemberian_pupuk',
'metode_penanganan_opt',
'frekuensi_sanitasi_lahan',
'penanganan_gulma',
'kondisi_pengelolaan_air',
]);
}
private function hasCompleteSosialEkonomi(?Model $sosialEkonomi): bool
{
return $this->allFieldsFilled($sosialEkonomi, [
'skor_periode_paceklik',
'skor_kemampuan_menabung',
'skor_kecukupan_penghasilan',
'skor_kejelasan_status_lahan',
'kemauan_mencoba_praktik_baru',
'pemahaman_mutu_harga',
'panutan_petani_sukses',
'kemauan_ikut_program',
'orientasi_rencana_jangka_panjang',
'keaktifan_pertemuan_kelompok',
'peran_dalam_kelompok',
'manfaat_kelompok_tani',
'status_rekening_tabungan',
'pengalaman_kredit_kebun',
'kondisi_aset_produktif',
'keterlibatan_program_keuangan_formal',
'keterikatan_hutang_panen',
'praktik_ijon',
'kebebasan_memilih_pembeli',
]);
}
private function hasCompleteAksesPasar(?Model $aksesPasar): bool
{
if (! $this->allFieldsFilled($aksesPasar, [
'skor_pengalaman',
'praktik_fermentasi',
'skor_kesadaran_premium',
'tingkat_kepercayaan_pembeli_baru',
])) {
return false;
}
$skorPengalaman = (int) $aksesPasar->getAttribute('skor_pengalaman');
if (
$skorPengalaman !== SkorPengalaman::BELUM_PERNAH->value
&& ! $this->valueIsFilled($aksesPasar->getAttribute('saluran_terdahulu'))
) {
return false;
}
if (
in_array($skorPengalaman, [
SkorPengalaman::PERNAH_BERHENTI_TIDAK_ADA_AKSES->value,
SkorPengalaman::PERNAH_BERHENTI_MUNGKIN_KEMBALI->value,
], true)
&& ! $this->valueIsFilled($aksesPasar->getAttribute('alasan_berhenti'))
) {
return false;
}
if (
in_array(
SaluranTerdahulu::LAINNYA->value,
$aksesPasar->getAttribute('saluran_terdahulu') ?? [],
true
)
&& ! $this->valueIsFilled($aksesPasar->getAttribute('saluran_terdahulu_lainnya'))
) {
return false;
}
if (
$aksesPasar->getAttribute('praktik_fermentasi') !== PraktikFermentasi::BELUM_PERNAH->value
&& ! $this->allFieldsFilled($aksesPasar, [
'proses_fermentasi',
'model_fermentasi_memungkinkan',
])
) {
return false;
}
return true;
}
private function allFieldsFilled(?Model $model, array $fields): bool
{
if (! $model) {
return false;
}
foreach ($fields as $field) {
if (! $this->valueIsFilled($model->getAttribute($field))) {
return false;
}
}
return true;
}
private function valueIsFilled(mixed $value): bool
{
if ($value instanceof BackedEnum) {
$value = $value->value;
}
if (is_array($value)) {
return collect($value)
->contains(fn ($item) => $this->valueIsFilled($item));
}
return ! is_null($value) && $value !== '';
}
private function pelatihanGapOptions(): array
{
$enumClass = self::GAP_ENUM_CLASS;
@@ -55,19 +55,7 @@ class FormBasisDataController extends Controller
], 404);
}
$validated = $request->validate([
'parent_id' => ['nullable', 'exists:ekosistem_basis_data_form,id'],
'tipe' => ['required', Rule::enum(LayoutForm::class)],
'column_width' => [
Rule::requiredIf($request->tipe === LayoutForm::COLUMN->value),
'nullable', 'integer', 'min:1', 'max:12'],
'kolom_id' => [
Rule::requiredIf($request->tipe === LayoutForm::VALUE->value),
'nullable', 'exists:ekosistem_basis_data_kolom,id'],
'section_label' => [
Rule::requiredIf($request->tipe === LayoutForm::SECTION->value),
'nullable', 'string'],
]);
$validated = $this->validateFormPayload($request, $tabelId);
FormBasisData::create([...$validated,
'tabel_id' => $tabelId,
@@ -115,19 +103,7 @@ class FormBasisDataController extends Controller
], 404);
}
$validated = $request->validate([
'parent_id' => ['nullable', 'exists:ekosistem_basis_data_form,id'],
'tipe' => ['required', Rule::enum(LayoutForm::class)],
'column_width' => [
Rule::requiredIf($request->tipe === LayoutForm::COLUMN->value),
'nullable', 'integer', 'min:1', 'max:12'],
'kolom_id' => [
Rule::requiredIf($request->tipe === LayoutForm::VALUE->value),
'nullable', 'exists:ekosistem_basis_data_kolom,id'],
'section_label' => [
Rule::requiredIf($request->tipe === LayoutForm::SECTION->value),
'nullable', 'string'],
]);
$validated = $this->validateFormPayload($request, $tabelId, $form);
$form->update([...$validated,
'updated_by' => auth()->user()->id
@@ -293,4 +269,88 @@ class FormBasisDataController extends Controller
'message' => 'Formulir basis data berhasil diperbarui.'
], 200);
}
private function validateFormPayload(Request $request, string $tabelId, ?FormBasisData $form = null): array
{
$tipe = $request->input('tipe');
$needsParent = in_array($tipe, [LayoutForm::COLUMN->value, LayoutForm::VALUE->value], true);
$mustBeRoot = in_array($tipe, [LayoutForm::ROW->value, LayoutForm::SECTION->value, LayoutForm::HR_LINE->value], true);
$validator = Validator::make($request->all(), [
'parent_id' => [
Rule::requiredIf($needsParent),
Rule::prohibitedIf($mustBeRoot),
'nullable',
Rule::exists('ekosistem_basis_data_form', 'id')
->where(fn ($query) => $query
->where('tabel_id', $tabelId)
->whereNull('deleted_at')),
],
'tipe' => ['required', Rule::enum(LayoutForm::class)],
'column_width' => [
Rule::requiredIf($tipe === LayoutForm::COLUMN->value),
'nullable', 'integer', 'min:1', 'max:12'
],
'kolom_id' => [
Rule::requiredIf($tipe === LayoutForm::VALUE->value),
'nullable',
Rule::exists('ekosistem_basis_data_kolom', 'id')
->where(fn ($query) => $query
->where('tabel_id', $tabelId)
->whereNull('deleted_at')),
],
'section_label' => [
Rule::requiredIf($tipe === LayoutForm::SECTION->value),
'nullable', 'string'
],
]);
$validator->after(function ($validator) use ($request, $tabelId, $form) {
if ($validator->errors()->isNotEmpty() || !$request->filled('parent_id')) {
return;
}
if ($form && (int) $request->input('parent_id') === (int) $form->id) {
$validator->errors()->add('parent_id', 'Induk formulir tidak boleh menunjuk ke dirinya sendiri.');
return;
}
$layout = LayoutForm::from($request->input('tipe'));
$expectedParent = match ($layout) {
LayoutForm::COLUMN => LayoutForm::ROW,
LayoutForm::VALUE => LayoutForm::COLUMN,
default => null,
};
if (!$expectedParent) {
return;
}
$parent = FormBasisData::where('tabel_id', $tabelId)->find($request->input('parent_id'));
if (!$parent || $parent->tipe !== $expectedParent) {
$validator->errors()->add('parent_id', 'Induk formulir tidak sesuai dengan tipe formulir.');
}
});
$validated = $validator->validate();
$layout = LayoutForm::from($validated['tipe']);
if (in_array($layout, [LayoutForm::ROW, LayoutForm::SECTION, LayoutForm::HR_LINE], true)) {
$validated['parent_id'] = null;
}
if ($layout !== LayoutForm::COLUMN) {
$validated['column_width'] = null;
}
if ($layout !== LayoutForm::VALUE) {
$validated['kolom_id'] = null;
}
if ($layout !== LayoutForm::SECTION) {
$validated['section_label'] = null;
}
return $validated;
}
}
@@ -16,6 +16,7 @@ use App\Enums\AksesPasar\SkorPengalaman;
use App\Enums\AksesPasar\SaluranTerdahulu;
use App\Enums\AksesPasar\PraktikFermentasi;
use App\Enums\AksesPasar\SkorKesadaranPremium;
use App\Enums\AksesPasar\TingkatKepercayaanPembeli;
class AksesPasarController extends Controller
{
@@ -113,6 +114,7 @@ class AksesPasarController extends Controller
'praktikFermentasiOptions' => PraktikFermentasi::options(),
'praktikBelumPernahValue' => PraktikFermentasi::BELUM_PERNAH->value,
'skorKesadaranPremiumOptions' => SkorKesadaranPremium::options(),
'tingkatKepercayaanOptions' => TingkatKepercayaanPembeli::options(),
'skorPengalamanBerhentiValues' => [
SkorPengalaman::PERNAH_BERHENTI_TIDAK_ADA_AKSES->value,
SkorPengalaman::PERNAH_BERHENTI_MUNGKIN_KEMBALI->value
@@ -155,6 +157,9 @@ class AksesPasarController extends Controller
'skor_kesadaran_premium' => ['nullable', 'integer', Rule::in(SkorKesadaranPremium::values())],
'sumber_info_premium' => ['nullable', 'string', 'max:255'],
'tingkat_kepercayaan_pembeli_baru' => ['nullable', 'integer', Rule::in(TingkatKepercayaanPembeli::values())],
'alasan_kepercayaan_pembeli_baru' => ['nullable', 'string'],
'harga_jual' => ['nullable', 'array'],
'harga_jual.*.bentuk_produk' => ['nullable', 'string'],
'harga_jual.*.harga_panen_terakhir' => ['nullable', 'numeric', 'min:0'],
@@ -34,9 +34,16 @@ class PetaniController extends Controller
abort(403);
}
$provinsi = Provinsi::orderBy('kode', 'asc')->get();
$provinsi = Provinsi::select('id', 'kode', 'nama')->orderBy('kode', 'asc')->get();
$query = Petani::with('desakelurahan')
$query = Petani::with([
'desaKelurahan:id,nama,kecamatan_id',
'desaKelurahan.kecamatan:id,nama,kabupaten_kota_id',
'desaKelurahan.kecamatan.kabupatenKota:id,nama,provinsi_id',
'desaKelurahan.kecamatan.kabupatenKota.provinsi:id,nama',
'komoditas:id,nama',
'skor:id,petani_id,skor_kesiapan_adopsi',
])
->orderBy('created_at', 'desc');
if ($request->search) {
@@ -53,12 +60,12 @@ class PetaniController extends Controller
$summaryPetaniIds = (clone $query)->pluck('id');
$petani = $query->paginate($request->per_page ?? 10);
$komoditas = Komoditas::where('is_utama', true)->get();
$lahan = Lahan::whereIn('petani_id', $summaryPetaniIds)
->whereIn('komoditas_id', $komoditas->pluck('id'))
->get();
$tanaman = Tanaman::whereIn('lahan_id', $lahan->pluck('id'))
->get();
$komoditas = Komoditas::select('id', 'nama')->where('is_utama', true)->get();
$lahanQuery = Lahan::whereIn('petani_id', $summaryPetaniIds)
->whereIn('komoditas_id', $komoditas->pluck('id'));
$lahanIds = (clone $lahanQuery)->pluck('id');
$totalLuasLahan = (clone $lahanQuery)->sum('luas_lahan');
$totalTanaman = Tanaman::whereIn('lahan_id', $lahanIds)->count();
$case = "
CASE
@@ -74,7 +81,7 @@ class PetaniController extends Controller
->groupByRaw($case)
->get();
return view('produktivitas.petani.index', compact('petani', 'provinsi', 'lahan', 'komoditas', 'tanaman', 'skor'));
return view('produktivitas.petani.index', compact('petani', 'provinsi', 'komoditas', 'skor', 'totalLuasLahan', 'totalTanaman'));
}
public function create()
@@ -0,0 +1,878 @@
<?php
namespace App\Http\Controllers\Web\Produktivitas;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use App\Models\Pertanian\Petani;
use App\Models\Pertanian\SkorPetani;
use App\Models\Pertanian\Komoditas;
use App\Models\Pertanian\Lahan;
use App\Models\Pertanian\Tanaman;
use App\Models\Pertanian\PraktikBudidaya;
use App\Models\Pertanian\SurveyPanen;
use App\Models\Pertanian\SosialEkonomi;
use App\Models\Pertanian\SurveyPelatihan;
use App\Models\Pertanian\SurveySertifikasi;
use App\Models\Pertanian\AksesPasar;
use App\Models\Pertanian\HargaJualPanen;
use App\Models\Pertanian\PendapatanKomoditas;
use App\Models\Wilayah\DesaKelurahan;
use App\Models\Profile;
use App\Enums\PraktikBudidaya\FrekuensiPemangkasanUtama;
use App\Enums\PraktikBudidaya\PemberianPupuk;
use App\Enums\PraktikBudidaya\MetodePenangananHamaPenyakit;
use App\Enums\PraktikBudidaya\FrekuensiSanitasiLahan;
use App\Enums\PraktikBudidaya\PenangananGulma;
use App\Enums\PraktikBudidaya\KondisiPengelolaanAir;
use App\Enums\SosialEkonomi\SkorKecukupanPenghasilan;
use App\Enums\SosialEkonomi\SkorKemampuanMenabung;
use App\Enums\SosialEkonomi\TerdaftarKelompokTani;
use App\Enums\SosialEkonomi\PeranDalamKelompok;
use App\Enums\SosialEkonomi\StatusRekeningTabungan;
use App\Enums\SosialEkonomi\PengalamanKreditKebun;
use App\Enums\SosialEkonomi\KeterikatanHutangPanen;
use App\Enums\SosialEkonomi\PraktikIjon;
use App\Enums\SosialEkonomi\KebebasanMemilihPembeli;
class ProfilPetaniController extends Controller
{
private const DIMENSION_LOW_MAX = 2.49;
private const DIMENSION_MEDIUM_MAX = 3.49;
public function index(int $id)
{
$petani = Petani::findOrFail($id);
$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();
$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;
$tanaman = Tanaman::where('petani_id', $petani->id)
->where('komoditas_id', $petani->komoditas_id)
->first();
if ($tanaman) {
$tahunInputTanaman = $tanaman->updated_at->year;
$umurTanaman = (int) $tanaman->umur_tanaman;
$tahunSekarang = now()->year;
$tahunTanam = $tahunInputTanaman - $umurTanaman;
$umurTanaman = $tahunSekarang - $tahunTanam;
} else {
$tahunTanam = null;
$umurTanaman = null;
}
$praktikBudidaya = PraktikBudidaya::where('petani_id', $petani->id)->first();
$surveyPanen = SurveyPanen::where('petani_id', $petani->id)->first();
$sosialEkonomi = SosialEkonomi::where('petani_id', $id)->first();
$indikatorOnFarm = collect();
if ($praktikBudidaya) {
$indikatorOnFarm = collect([
[
'key' => 'pemangkasan',
'nama' => 'Pemangkasan',
'skor' => $praktikBudidaya->frekuensi_pemangkasan_utama,
'deskripsi' => FrekuensiPemangkasanUtama::tryFrom(
(int) $praktikBudidaya->frekuensi_pemangkasan_utama
)?->label(),
],
[
'key' => 'pemupukan',
'nama' => 'Pemupukan',
'skor' => $praktikBudidaya->pemberian_pupuk,
'deskripsi' => PemberianPupuk::tryFrom(
(int) $praktikBudidaya->pemberian_pupuk
)?->label(),
],
[
'key' => 'opt',
'nama' => 'Hama / Penyakit',
'skor' => $praktikBudidaya->metode_penanganan_opt,
'deskripsi' => MetodePenangananHamaPenyakit::tryFrom(
(int) $praktikBudidaya->metode_penanganan_opt
)?->label(),
],
[
'key' => 'sanitasi',
'nama' => 'Sanitasi Kebun',
'skor' => $praktikBudidaya->frekuensi_sanitasi_lahan,
'deskripsi' => FrekuensiSanitasiLahan::tryFrom(
(int) $praktikBudidaya->frekuensi_sanitasi_lahan
)?->label(),
],
[
'key' => 'gulma',
'nama' => 'Penanganan Gulma',
'skor' => $praktikBudidaya->penanganan_gulma,
'deskripsi' => PenangananGulma::tryFrom(
(int) $praktikBudidaya->penanganan_gulma
)?->label(),
],
[
'key' => 'air',
'nama' => 'Pengelolaan Air',
'skor' => $praktikBudidaya->kondisi_pengelolaan_air,
'deskripsi' => KondisiPengelolaanAir::tryFrom(
(int) $praktikBudidaya->kondisi_pengelolaan_air
)?->label(),
],
])
->map(function ($item) {
$skor = $item['skor'] !== null
? (int) $item['skor']
: null;
return array_merge($item, [
'skor' => $skor,
'persentase' => $skor !== null
? ($skor / 5) * 100
: 0,
'color' => $this->getScoreColor($skor),
]);
});
}
$detailOnFarm = [
'jenis_hama' => $praktikBudidaya?->jenis_hama ?? [],
'jenis_penyakit' => $praktikBudidaya?->jenis_penyakit ?? [],
'konservasi_tanah' => $praktikBudidaya?->kebiasaan_konservasi_tanah ?? [],
];
$d4SosialEkonomi = null;
$terdaftarKelompok = $sosialEkonomi->terdaftar_kelompok_tani;
$peranKelompok = $sosialEkonomi->peran_dalam_kelompok;
if ($sosialEkonomi) {
$kecukupanPenghasilan = $sosialEkonomi->skor_kecukupan_penghasilan;
$kemampuanMenabung = $sosialEkonomi->skor_kemampuan_menabung;
$rekening = $sosialEkonomi->status_rekening_tabungan;
$kredit = $sosialEkonomi->pengalaman_kredit_kebun;
$hutangPanen = $sosialEkonomi->keterikatan_hutang_panen;
$ijon = $sosialEkonomi->praktik_ijon;
$programKeuanganRaw = $sosialEkonomi->status_program_keuangan_formal ?? [];
$namaProgram = [
'pkh' => 'PKH',
'blt' => 'BLT',
'kur' => 'KUR',
'kredit_koperasi_resmi' => 'Kredit Koperasi',
'credit_union_cu' => 'Credit Union (CU)',
'pinjaman_bumdes' => 'Pinjaman BUMDes',
'modal_hibah_dinas_pertanian' => 'Hibah Dinas Pertanian',
];
$programBantuan = collect($programKeuanganRaw)->map(function ($status, $key) use ($namaProgram) {
$statusMeta = match ($status) {
'sedang_menerima' => [
'label' => 'Sedang Menerima',
'short' => 'Aktif',
'color' => 'success',
'icon' => 'tabler-circle-check',
],
'pernah_tapi_tidak_lagi' => [
'label' => 'Pernah, Tidak Lagi',
'short' => 'Pernah',
'color' => 'info',
'icon' => 'tabler-history',
],
'belum_pernah' => [
'label' => 'Belum Pernah',
'short' => 'Belum Pernah',
'color' => 'secondary',
'icon' => 'tabler-minus',
],
default => [
'label' => Str::headline($status),
'short' => Str::headline($status),
'color' => 'secondary',
'icon' => 'tabler-help-circle',
],
};
return [
'key' => $key,
'nama' => $namaProgram[$key] ?? Str::headline($key),
'status' => $status,
'label' => $statusMeta['label'],
'short' => $statusMeta['short'],
'color' => $statusMeta['color'],
'icon' => $statusMeta['icon'],
];
})
->values();
$d4SosialEkonomi = [
'jumlah_tanggungan' => $sosialEkonomi->jumlah_tanggungan,
'kecukupan_penghasilan' => [
'skor' => $kecukupanPenghasilan?->value,
'label' => $kecukupanPenghasilan?->label(),
'color' => $this->getScoreColor(
$kecukupanPenghasilan?->value
),
],
'kemampuan_menabung' => [
'skor' => $kemampuanMenabung?->value,
'label' => $kemampuanMenabung?->label(),
'color' => $this->getScoreColor(
$kemampuanMenabung?->value
),
],
'status_kepemilikan_lahan' => $sosialEkonomi->status_kepemilikan_lahan,
'kelembagaan' => [
'terdaftar' =>
$terdaftarKelompok === TerdaftarKelompokTani::YA,
'status' =>
$terdaftarKelompok?->label(),
'peran' =>
$peranKelompok?->label(),
],
'kondisi_keuangan' => [
'rekening' => [
'label' => $rekening?->label(),
'value' => $rekening?->value,
'color' => $this->getFinancialColor(
$rekening?->value
),
'icon' => 'tabler-building-bank',
],
'kredit' => [
'label' => $kredit?->label(),
'value' => $kredit?->value,
'color' => $this->getFinancialColor(
$kredit?->value
),
'icon' => 'tabler-credit-card',
],
'hutang_panen' => [
'label' => $hutangPanen?->label(),
'value' => $hutangPanen?->value,
'color' => $this->getFinancialColor(
$hutangPanen?->value
),
'icon' => 'tabler-coins',
],
'ijon' => [
'label' => $ijon?->label(),
'value' => $ijon?->value,
'color' => $this->getFinancialColor(
$ijon?->value
),
'icon' => 'tabler-link',
],
],
'program_bantuan' => $programBantuan,
];
}
$surveyPelatihan = SurveyPelatihan::where('petani_id', $id)
->orderByDesc('tahun_pelaksanaan')
->get();
$surveySertifikasi = SurveySertifikasi::where('petani_id', $id)
->orderByDesc('tahun_pelaksanaan')
->get();
$aksesPasar = AksesPasar::where('petani_id', $id)->first();
$saluranTerdahulu = collect($aksesPasar?->saluran_terdahulu ?? [] );
$d6SaluranPasar = [
[
'key' => 'tengkulak',
'label' => 'Tengkulak',
'icon' => 'tabler-user-dollar',
'aktif' => $saluranTerdahulu->contains(
fn ($item) => Str::contains(
Str::lower($item),
['tengkulak', 'pedagang lokal']
)
),
],
[
'key' => 'koperasi',
'label' => 'Koperasi',
'icon' => 'tabler-users-group',
'aktif' => $saluranTerdahulu->contains(
fn ($item) => Str::contains(
Str::lower($item),
'koperasi'
)
),
],
[
'key' => 'bumdes',
'label' => 'BUMDes',
'icon' => 'tabler-building-community',
'aktif' => $saluranTerdahulu->contains(
fn ($item) => Str::contains(
Str::lower($item),
'bumdes'
)
),
],
[
'key' => 'eksportir',
'label' => 'Eksportir',
'icon' => 'tabler-plane-departure',
'aktif' => $saluranTerdahulu->contains(
fn ($item) => Str::contains(
Str::lower($item),
'eksportir'
)
),
],
];
$hargaJualPanen = HargaJualPanen::where('petani_id', $id)->get();
$d6HargaJual = $hargaJualPanen->map(function ($item) {
$hargaSekarang = $item->harga_panen_terakhir !== null
? (float) $item->harga_panen_terakhir
: null;
$hargaTahunLalu = $item->harga_tahun_lalu !== null
? (float) $item->harga_tahun_lalu
: null;
$perubahan = null;
if (
$hargaSekarang !== null &&
$hargaTahunLalu !== null &&
$hargaTahunLalu > 0
) {
$perubahan = (
($hargaSekarang - $hargaTahunLalu)
/ $hargaTahunLalu
) * 100;
}
return [
'bentuk_produk' => $item->bentuk_produk,
'harga_sekarang' => $hargaSekarang,
'harga_tahun_lalu' => $hargaTahunLalu,
'harga_dua_tahun_lalu' =>
$item->harga_dua_tahun_lalu !== null
? (float) $item->harga_dua_tahun_lalu
: null,
'perubahan' => $perubahan,
'trend' => match (true) {
$perubahan === null => 'neutral',
$perubahan > 0 => 'up',
$perubahan < 0 => 'down',
default => 'stable',
},
];
});
$kebebasanPembeli = $sosialEkonomi?->kebebasan_memilih_pembeli;
$d6RantaiNilai = [
'saluran_pasar' => $d6SaluranPasar,
'harga_jual' => $d6HargaJual,
'kebebasan_memilih_pembeli' => [
'skor' => $kebebasanPembeli?->value,
'label' => $kebebasanPembeli?->label(),
'persentase' => $kebebasanPembeli
? ($kebebasanPembeli->value / 5) * 100
: 0,
'color' => $this->getScoreColor(
$kebebasanPembeli?->value
),
],
];
$data = [
'petani' => $petani,
'skorPetani' => $skorPetani,
'enumerator' => $enumerator,
'komoditasUtama' => $komoditasUtama,
'lahan' => $lahan,
'desaKelurahan' => $desaKelurahan,
'ktpMasked' => $ktpMasked,
'profiling' => $profiling,
'tahunTanam' => $tahunTanam,
'umurTanaman' => $umurTanaman,
'surveyPanen' => $surveyPanen,
'praktikBudidaya' => $praktikBudidaya,
'praktikBudidaya' => $praktikBudidaya,
'indikatorOnFarm' => $indikatorOnFarm,
'detailOnFarm' => $detailOnFarm,
'sosialEkonomi' => $sosialEkonomi,
'd4SosialEkonomi' => $d4SosialEkonomi,
'surveyPelatihan' => $surveyPelatihan,
'surveySertifikasi' => $surveySertifikasi,
'aksesPasar' => $aksesPasar,
'd6SaluranPasar' => $d6SaluranPasar,
'hargaJualPanen' => $hargaJualPanen,
'd6HargaJual' => $d6HargaJual,
'kebebasanPembeli' => $kebebasanPembeli,
'd6RantaiNilai' => $d6RantaiNilai
];
return view('produktivitas.profil-petani.index', $data);
}
private function getScoreColor(?int $score): string
{
if ($score === null) {
return 'secondary';
}
return match (true) {
$score >= 5 => 'success',
$score >= 4 => 'info',
$score >= 3 => 'primary',
$score >= 2 => 'warning',
default => 'danger',
};
}
private function getFinancialColor(?int $score): string
{
if ($score === null || $score === 0) {
return 'secondary';
}
return match (true) {
$score >= 5 => 'success',
$score >= 4 => 'info',
$score >= 2 => 'warning',
default => 'danger',
};
}
private function buildProfiling(SkorPetani $skorPetani): array
{
$skorAkhir = $this->normalizeScore($skorPetani->skor_kesiapan_adopsi);
$segmen = $this->getSegmen($skorAkhir);
$dimensions = [
'd2' => [
'kode' => 'D2',
'nama' => 'Sistem & Praktik Budidaya',
'skor' => $this->normalizeScore(
$skorPetani->skor_d2_sistem_praktik_budidaya
),
],
'd4' => [
'kode' => 'D4',
'nama' => 'Sosial-Ekonomi Rumah Tangga',
'skor' => $this->normalizeScore(
$skorPetani->skor_d4_sosial_ekonomi
),
],
'd6' => [
'kode' => 'D6',
'nama' => 'Rantai Nilai & Akses Pasar',
'skor' => $this->normalizeScore(
$skorPetani->skor_d6_rantai_nilai_akses_pasar
),
],
];
foreach ($dimensions as $key => $dimension) {
$dimensions[$key]['level'] = $this->getDimensionLevel($dimension['skor']);
}
$components = collect([
[
'key' => 'motivasi',
'nama' => 'Motivasi & Kapasitas Adopsi',
'short' => 'Motivasi',
'dimensi' => 'D4',
'bobot' => 25,
'icon' => 'tabler-bulb',
'skor' => $this->normalizeScore($skorPetani->skor_komp_1_d4_k3_motivasi_kapasitas_adopsi),
],
[
'key' => 'ekonomi',
'nama' => 'Kondisi Ekonomi Rumah Tangga',
'short' => 'Ekonomi',
'dimensi' => 'D4',
'bobot' => 20,
'icon' => 'tabler-home-dollar',
'skor' => $this->normalizeScore($skorPetani->skor_komp_2_d4_k2_ekonomi_rumah_tangga),
],
[
'key' => 'gap',
'nama' => 'Praktik Budidaya & GAP',
'short' => 'Budidaya / GAP',
'dimensi' => 'D2',
'bobot' => 20,
'icon' => 'tabler-plant-2',
'skor' => $this->normalizeScore($skorPetani->skor_komp_3_d2_praktik_budidaya_gap),
],
[
'key' => 'kelembagaan',
'nama' => 'Keterlibatan Kelembagaan',
'short' => 'Kelembagaan',
'dimensi' => 'D4',
'bobot' => 15,
'icon' => 'tabler-users-group',
'skor' => $this->normalizeScore($skorPetani->skor_komp_4_d4_k4_keterlibatan_kelembagaan),
],
[
'key' => 'keuangan',
'nama' => 'Akses & Kebebasan Keuangan',
'short' => 'Keuangan',
'dimensi' => 'D4',
'bobot' => 10,
'icon' => 'tabler-building-bank',
'skor' => $this->normalizeScore($skorPetani->skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan),
],
[
'key' => 'pasar',
'nama' => 'Rantai Nilai & Akses Pasar',
'short' => 'Akses Pasar',
'dimensi' => 'D6',
'bobot' => 10,
'icon' => 'tabler-building-store',
'skor' => $this->normalizeScore($skorPetani->skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar),
],
]);
$availableComponents = $components
->filter(fn ($item) => $item['skor'] !== null)
->values();
$strengths = $availableComponents
->sortByDesc('skor')
->take(2)
->values();
$barriers = $availableComponents
->filter(fn ($item) => $item['skor'] < 3)
->sortBy('skor')
->values();
$specialPattern = $this->detectSpecialPattern(
$segmen,
$dimensions,
$skorPetani
);
$title = $this->buildProfileTitle(
$segmen,
$specialPattern,
$barriers
);
$narrative = $this->buildNarrative(
$segmen,
$specialPattern,
$strengths,
$barriers
);
$recommendations = $this->buildRecommendations(
$specialPattern,
$barriers
);
return [
'skor_akhir' => $skorAkhir,
'segmen' => $segmen,
'title' => $title,
'dimensions' => $dimensions,
'components' => $components,
'strengths' => $strengths,
'barriers' => $barriers,
'special_pattern' => $specialPattern,
'narrative' => $narrative,
'recommendations' => $recommendations,
];
}
private function normalizeScore($score): ?float
{
if ($score === null || $score === '') {
return null;
}
$score = (float) $score;
if ($score <= 0) {
return null;
}
return $score;
}
private function getSegmen(?float $score): string
{
if ($score === null) {
return 'Belum Dinilai';
}
return match (true) {
$score >= 4.0 => 'Penggerak',
$score >= 3.0 => 'Perintis',
$score >= 2.0 => 'Pemerhati',
default => 'Pemula',
};
}
private function getDimensionLevel(?float $score): string
{
if ($score === null) {
return 'belum_dinilai';
}
if ($score <= self::DIMENSION_LOW_MAX) {
return 'rendah';
}
if ($score <= self::DIMENSION_MEDIUM_MAX) {
return 'sedang';
}
return 'tinggi';
}
private function detectSpecialPattern(
string $segmen,
array $dimensions,
SkorPetani $skorPetani
): ?array {
$d2 = $dimensions['d2']['level'];
$d4 = $dimensions['d4']['level'];
$d6 = $dimensions['d6']['level'];
if (
$segmen === 'Perintis' &&
$d4 === 'tinggi' &&
$d2 === 'rendah' &&
$d6 === 'sedang'
) {
return [
'kode' => 'perintis_teknis',
'nama' => 'Perintis dengan Hambatan Teknis',
'prioritas' => 'gap',
];
}
if (
$segmen === 'Perintis' &&
$d4 === 'tinggi' &&
in_array($d2, ['sedang', 'tinggi']) && $d6 === 'rendah'
) {
return [
'kode' => 'perintis_pasar',
'nama' => 'Perintis dengan Hambatan Pasar',
'prioritas' => 'pasar',
];
}
if (
$segmen === 'Pemerhati' &&
$d4 === 'sedang' &&
$d2 === 'rendah' &&
$d6 === 'sedang'
) {
return [
'kode' => 'pemerhati_teknis',
'nama' => 'Pemerhati Teknis',
'prioritas' => 'gap',
];
}
if (
$segmen === 'Pemula' &&
$d4 === 'rendah' &&
$d2 === 'rendah' &&
$d6 === 'rendah'
) {
return [
'kode' => 'pemula_struktural',
'nama' => 'Pemula Struktural',
'prioritas' => 'struktural',
];
}
return null;
}
private function buildProfileTitle(
string $segmen,
?array $specialPattern,
$barriers
): string {
if ($specialPattern) {
return $specialPattern['nama'];
}
if ($barriers->isNotEmpty()) {
$mainBarrier = $barriers->first();
return $segmen . ' dengan Prioritas ' . $mainBarrier['short'];
}
return $segmen . ' dengan Profil Relatif Seimbang';
}
private function buildNarrative(
string $segmen,
?array $specialPattern,
$strengths,
$barriers
): string {
if ($specialPattern) {
return match ($specialPattern['kode']) {
'perintis_teknis' =>
'Petani memiliki fondasi sosial-ekonomi yang relatif siap '
. 'untuk berkembang, tetapi penerapannya belum sepenuhnya '
. 'tercermin dalam praktik budidaya. Hambatan utama berada '
. 'pada aspek teknis di lapangan.',
'perintis_pasar' =>
'Petani memiliki fondasi sosial-ekonomi dan kesiapan '
. 'budidaya yang relatif baik, tetapi manfaat ekonominya '
. 'masih dibatasi oleh rantai nilai dan akses pasar.',
'pemerhati_teknis' =>
'Petani telah memiliki sebagian fondasi untuk berkembang, '
. 'namun praktik budidaya masih menjadi hambatan utama. '
. 'Pendekatan berbasis demonstrasi hasil nyata akan lebih '
. 'relevan sebelum intervensi teknis yang lebih lanjut.',
'pemula_struktural' =>
'Petani menghadapi hambatan pada beberapa dimensi sekaligus. '
. 'Kondisi sosial-ekonomi, praktik budidaya, serta akses '
. 'pasar perlu dibaca bersama untuk menentukan hambatan '
. 'mendasar yang paling mendesak.',
default =>
$this->buildGenericNarrative($segmen, $strengths, $barriers),
};
}
return $this->buildGenericNarrative($segmen, $strengths, $barriers);
}
private function buildGenericNarrative(
string $segmen,
$strengths,
$barriers
): string {
$text = 'Petani berada pada segmen ' . $segmen . '. ';
if ($strengths->isNotEmpty()) {
$strengthNames = $strengths->pluck('nama')->implode(' dan ');
$text .= 'Kekuatan relatif terlihat pada ' . $strengthNames . '. ';
}
if ($barriers->isNotEmpty()) {
$barrierNames = $barriers->take(3)->pluck('nama')->implode(', ');
$text .=
'Area yang masih memerlukan perhatian terutama pada ' . $barrierNames . '. Prioritas intervensi sebaiknya mengikuti ' . 'hambatan dengan skor paling rendah.';
} else {
$text .= 'Tidak terdapat komponen dengan skor rendah yang ' . 'menonjol. Pendampingan dapat difokuskan pada ' . 'pemeliharaan dan penguatan capaian yang sudah ada.';
}
return $text;
}
private function buildRecommendations(
?array $specialPattern,
$barriers
): array {
if ($specialPattern) {
$specialRecommendation = match ($specialPattern['kode']) {
'perintis_teknis' => [
'Prioritaskan pendampingan GAP langsung di lapangan.',
'Perkuat koneksi petani terhadap sumber input dan pelatihan teknis.',
],
'perintis_pasar' => [
'Perkuat koneksi ke saluran pasar yang lebih baik.',
'Fasilitasi akses pembeli, negosiasi, atau penguatan koperasi.',
],
'pemerhati_teknis' => [
'Gunakan demonstrasi lapangan untuk menunjukkan hasil nyata.',
'Lakukan pendampingan teknis secara bertahap sebelum pelatihan formal.',
],
'pemula_struktural' => [
'Identifikasi hambatan dasar seperti lahan, hutang, atau logistik.',
'Tangani hambatan struktural sebelum mendorong adopsi praktik baru.',
],
default => [],
};
if (!empty($specialRecommendation)) {
return $specialRecommendation;
}
}
$recommendationMap = [
'motivasi' => 'Gunakan demonstrasi hasil nyata, role model, dan pendekatan perubahan secara bertahap.',
'ekonomi' => 'Perkuat ruang gerak ekonomi rumah tangga sebelum mendorong investasi atau perubahan praktik yang berisiko.',
'gap' => 'Prioritaskan pendampingan praktik budidaya dan GAP langsung di kebun.',
'kelembagaan' => 'Perkuat keterlibatan petani dalam kelompok tani sebagai ruang belajar dan penguatan posisi tawar.',
'keuangan' => 'Tinjau akses keuangan formal serta kemungkinan ikatan hutang panen atau ijon yang membatasi ruang gerak petani.',
'pasar' => 'Perkuat koneksi dengan pembeli dan saluran pasar alternatif serta kemampuan negosiasi petani.',
];
return $barriers->take(3)->map(
fn ($item) =>
$recommendationMap[$item['key']] ?? null
)->filter()->values()->all();
}
}
@@ -398,15 +398,17 @@ class SosialEkonomiController extends Controller
}
if ($section === 'keanggotaan_kelompok') {
$terdaftarKelompokTani = $request->input('terdaftar_kelompok_tani') === TerdaftarKelompokTani::YA->value;
$rules = array_merge($rules, [
'terdaftar_kelompok_tani' => ['nullable', 'string', Rule::in(TerdaftarKelompokTani::values())],
'nama_kelompok_tani' => ['required_if:terdaftar_kelompok_tani,' . TerdaftarKelompokTani::YA->value, 'nullable', 'string', 'max:255'],
'lama_bergabung_kelompok_tahun' => ['required_if:terdaftar_kelompok_tani,' . TerdaftarKelompokTani::YA->value, 'nullable', 'integer', 'min:0'],
'kedekatan_sesama_anggota' => ['required_if:terdaftar_kelompok_tani,' . TerdaftarKelompokTani::YA->value, 'nullable', 'string', Rule::in(KedekatanSesamaAnggota::values())],
'keaktifan_pertemuan_kelompok' => ['required', 'integer', Rule::in(KeaktifanPertemuanKelompok::values())],
'topik_biasa_dibahas' => ['required', 'string'],
'peran_dalam_kelompok' => ['required', 'integer', Rule::in(PeranDalamKelompok::values())],
'manfaat_kelompok_tani' => ['required', 'integer', Rule::in(ManfaatKelompokTani::values())],
'keaktifan_pertemuan_kelompok' => [Rule::requiredIf($terdaftarKelompokTani), 'nullable', 'integer', Rule::in(KeaktifanPertemuanKelompok::values())],
'topik_biasa_dibahas' => [Rule::requiredIf($terdaftarKelompokTani), 'nullable', 'string'],
'peran_dalam_kelompok' => [Rule::requiredIf($terdaftarKelompokTani), 'nullable', 'integer', Rule::in(PeranDalamKelompok::values())],
'manfaat_kelompok_tani' => [Rule::requiredIf($terdaftarKelompokTani), 'nullable', 'integer', Rule::in(ManfaatKelompokTani::values())],
'status_kartu_tani' => ['nullable', 'string', Rule::in(StatusKartuTani::values())],
'bisa_beli_pupuk_bersubsidi' => ['nullable', 'string', Rule::in(BisaBeliPupukBersubsidi::values())],
'manfaat_lain_kartu_tani' => ['nullable', 'string'],
@@ -633,6 +635,10 @@ class SosialEkonomiController extends Controller
$payload['nama_kelompok_tani'] = null;
$payload['lama_bergabung_kelompok_tahun'] = null;
$payload['kedekatan_sesama_anggota'] = null;
$payload['keaktifan_pertemuan_kelompok'] = KeaktifanPertemuanKelompok::TIDAK_PERNAH_HADIR->value;
$payload['topik_biasa_dibahas'] = null;
$payload['peran_dalam_kelompok'] = PeranDalamKelompok::ANGGOTA_PASIF->value;
$payload['manfaat_kelompok_tani'] = ManfaatKelompokTani::TIDAK_ADA_MANFAAT->value;
}
if (
@@ -976,8 +982,11 @@ class SosialEkonomiController extends Controller
'terdaftarKelompokTaniTidakValue' => TerdaftarKelompokTani::TIDAK->value,
'kedekatanSesamaAnggotaOptions' => KedekatanSesamaAnggota::options(),
'keaktifanPertemuanKelompokOptions' => KeaktifanPertemuanKelompok::options(),
'keaktifanPertemuanKelompokLowestValue' => KeaktifanPertemuanKelompok::TIDAK_PERNAH_HADIR->value,
'peranDalamKelompokOptions' => PeranDalamKelompok::options(),
'peranDalamKelompokLowestValue' => PeranDalamKelompok::ANGGOTA_PASIF->value,
'manfaatKelompokTaniOptions' => ManfaatKelompokTani::options(),
'manfaatKelompokTaniLowestValue' => ManfaatKelompokTani::TIDAK_ADA_MANFAAT->value,
'statusKartuTaniOptions' => StatusKartuTani::options(),
'bisaBeliPupukBersubsidiOptions' => BisaBeliPupukBersubsidi::options(),
'statusRekeningTabunganOptions' => StatusRekeningTabungan::options(),
@@ -3,26 +3,43 @@
namespace App\Http\Controllers\Web\Produktivitas;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use App\Models\Pertanian\Petani;
use App\Models\Pertanian\SurveyProduktivitas;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
class SurveyProduktivitasController extends Controller
{
public function store($petaniId, Request $request)
{
$petani = Petani::findOrFail($petaniId);
$validated = $request->validate([
'produktivitas_komoditas' => ['required', 'array'],
'produktivitas_komoditas.*.komoditas_id' => ['nullable', 'exists:master_komoditas,id'],
'produktivitas_komoditas.*.hasil_panen_3_musim' => ['nullable', 'numeric'],
'produktivitas_komoditas.*.satuan' => ['nullable', 'string'],
$validator = Validator::make($request->all(), [
'produktivitas_komoditas' => ['required', 'array', 'min:1'],
'produktivitas_komoditas.*.komoditas_id' => ['required', 'exists:master_komoditas,id'],
'produktivitas_komoditas.*.hasil_panen_3_musim' => ['required', 'numeric', 'min:0'],
'produktivitas_komoditas.*.satuan' => ['required', 'in:ha_tahun,pohon_tahun'],
], [
'produktivitas_komoditas.required' => 'Minimal satu data produktivitas harus diisi.',
'produktivitas_komoditas.*.komoditas_id.required' => 'Komoditas wajib dipilih.',
'produktivitas_komoditas.*.komoditas_id.exists' => 'Komoditas yang dipilih tidak valid.',
'produktivitas_komoditas.*.hasil_panen_3_musim.required' => 'Hasil panen wajib diisi.',
'produktivitas_komoditas.*.hasil_panen_3_musim.numeric' => 'Hasil panen harus berupa angka.',
'produktivitas_komoditas.*.hasil_panen_3_musim.min' => 'Hasil panen tidak boleh kurang dari 0.',
'produktivitas_komoditas.*.satuan.required' => 'Satuan wajib dipilih.',
'produktivitas_komoditas.*.satuan.in' => 'Satuan yang dipilih tidak valid.',
]);
if ($validator->fails()) {
return $this->redirectBackToFragment($request)->withErrors($validator)->withInput();
}
$validated = $validator->validated();
foreach ($validated['produktivitas_komoditas'] as $row) {
SurveyProduktivitas::create([
'uid' => 'SPV-' . strtoupper(Str::random(10)),
'uid' => 'SPV-'.strtoupper(Str::random(10)),
'petani_id' => $petani->id,
'komoditas_id' => $row['komoditas_id'],
'kuantitas' => $row['hasil_panen_3_musim'],
@@ -51,10 +68,10 @@ class SurveyProduktivitasController extends Controller
{
$fragment = $request->input('redirect_fragment');
if (!is_string($fragment) || !preg_match('/^[A-Za-z0-9_-]+$/', $fragment)) {
if (! is_string($fragment) || ! preg_match('/^[A-Za-z0-9_-]+$/', $fragment)) {
return back();
}
return redirect()->to(Str::before(url()->previous(), '#') . '#' . $fragment);
return redirect()->to(Str::before(url()->previous(), '#').'#'.$fragment);
}
}
+108
View File
@@ -0,0 +1,108 @@
<?php
namespace App\Models\Bumdes;
use App\Enums\Bumdes\JabatanBumdes;
use App\Enums\Pendidikan;
use App\Models\User;
use App\Models\Wilayah\DesaKelurahan;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class PengurusBumdes extends Model
{
use SoftDeletes;
protected $table = 'bumdes_pengurus';
protected $fillable = [
'uid',
'nama',
'jenis_kelamin',
'tempat_lahir',
'tanggal_lahir',
'pendidikan_terakhir',
'alamat',
'desa_kelurahan_id',
'nomor_telepon',
'jabatan',
'jabatan_lainnya',
'lama_menjabat_tahun',
'tanggal_mulai_menjabat',
'created_by',
'updated_by',
'deleted_by',
];
protected $casts = [
'jenis_kelamin' => 'boolean',
'tanggal_lahir' => 'date',
'pendidikan_terakhir' => Pendidikan::class,
'tanggal_mulai_menjabat' => 'date',
'jabatan' => JabatanBumdes::class,
];
public function getUsiaAttribute(): ?int
{
if (! $this->tanggal_lahir) {
return null;
}
return Carbon::parse($this->tanggal_lahir)->age;
}
public function getLamaMenjabatAttribute(): string
{
if (! is_null($this->lama_menjabat_tahun)) {
return $this->lama_menjabat_tahun . ' tahun';
}
if (! $this->tanggal_mulai_menjabat) {
return '-';
}
$tanggalMulai = Carbon::parse($this->tanggal_mulai_menjabat)->startOfDay();
if ($tanggalMulai->isFuture()) {
return 'Belum mulai';
}
$selisih = $tanggalMulai->diff(Carbon::now());
$bagian = [];
if ($selisih->y > 0) {
$bagian[] = $selisih->y . ' tahun';
}
if ($selisih->m > 0) {
$bagian[] = $selisih->m . ' bulan';
}
if (empty($bagian)) {
return $selisih->d . ' hari';
}
return implode(' ', $bagian);
}
public function getNamaJabatanAttribute(): string
{
if ($this->jabatan === JabatanBumdes::LAINNYA && $this->jabatan_lainnya) {
return $this->jabatan_lainnya;
}
return $this->jabatan?->label() ?? '-';
}
public function desaKelurahan(): BelongsTo
{
return $this->belongsTo(DesaKelurahan::class, 'desa_kelurahan_id');
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
}
-1
View File
@@ -56,7 +56,6 @@ class FormBasisData extends Model
public function form(): HasMany
{
return $this->hasMany(FormBasisData::class, 'parent_id')
->with('form')
->orderBy('urutan');
}
+2
View File
@@ -31,6 +31,8 @@ class AksesPasar extends Model
'model_fermentasi_memungkinkan',
'skor_kesadaran_premium',
'sumber_info_premium',
'tingkat_kepercayaan_pembeli_baru',
'alasan_kepercayaan_pembeli_baru',
'waktu_mulai_wawancara',
'waktu_selesai_wawancara',
'skor_kepercayaan_responden',
+2 -1
View File
@@ -18,6 +18,7 @@ use App\Enums\KondisiTanaman;
use App\Enums\SeranganHama;
use App\Enums\PenyakitTanaman;
use App\Enums\KeparahanSerangan;
use App\Models\Pertanian\Petani;
class Lahan extends Model
{
@@ -98,7 +99,7 @@ class Lahan extends Model
public function petani(): BelongsTo
{
return $this->belongsTo(Profile::class, 'petani_id');
return $this->belongsTo(Petani::class, 'petani_id');
}
public function komoditas(): BelongsTo
+8 -8
View File
@@ -94,15 +94,15 @@ class Petani extends Model
protected static function booted(): void
{
static::saved(function (Petani $petani) {
$skor = SkorPetani::where('petani_id')->orderBy('id')->first();
if (! $skor) {
$skor = SkorPetani::where('petani_id', $petani->id)->orderBy('id')->first();
if (!$skor) {
$petaniUid = $petani->uid;
$part = explode('-', $petaniUid);
$skor = SkorPetani::create([
'uid' => 'SKR-'.$part[1].'-'.strtoupper(Str::random(10)),
'petani_id' => $petani->id,
'uid' => 'SKR-' . $part[1] . '-' . strtoupper(Str::random(10)),
'created_by' => $petani->created_by,
'updated_by' => $petani->updated_by,
'updated_by' => $petani->updated_by
]);
}
});
@@ -339,7 +339,7 @@ class Petani extends Model
$skor >= 3.0 => 'Perintis',
$skor >= 2.0 => 'Pemerhati',
$skor >= 1.0 => 'Pemula',
default => 'Belum tersegmentasi',
default => 'Belum lengkap',
};
}
@@ -365,10 +365,10 @@ class Petani extends Model
} elseif ($skor->skor_kesiapan_adopsi >= 1.0) {
return 'Pemula';
} else {
return 'Belum tersegmentasi';
return 'Belum lengkap';
}
return 'Belum tersegmentasi';
return 'Belum lengkap';
}
public function keteranganSegmen(): ?string
+23 -13
View File
@@ -4,6 +4,9 @@ namespace App\Models\Pertanian;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use App\Models\Pertanian\Petani;
class SkorPetani extends Model
{
@@ -57,6 +60,7 @@ class SkorPetani extends Model
'skor_d6_k1_6_pengalaman_menjual_ke_saluran_berbeda',
'skor_d6_k4_1_kesadaran_akses_pasar_premium',
'skor_d6_k5_1_hambatan_mutu',
'skor_d6_k5_2_tingkat_kepercayaan_pembeli',
'created_by',
'updated_by',
@@ -102,7 +106,7 @@ class SkorPetani extends Model
$skorPetani->skor_d4_k5_1_kepemilikan_rekening +
$skorPetani->skor_d4_k5_2_pengalaman_kredit_formal +
$skorPetani->skor_d4_k5_3_kepemilikan_aset_produktif +
$skorPetani->skor_d4_k5_4_5_akses_program_bantuan
$skorPetani->skor_d4_k5_4_akses_program_bantuan
) / 4 +
(
$skorPetani->skor_d4_k6_3_keberadaan_hutang_terkait_panen +
@@ -113,30 +117,36 @@ class SkorPetani extends Model
$skorPetani->skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar = (
$skorPetani->skor_d6_k1_6_pengalaman_menjual_ke_saluran_berbeda +
$skorPetani->skor_d6_k4_1_kesadaran_akses_pasar_premium +
$skorPetani->skor_d6_k5_1_hambatan_mutu
) / 3;
$skorPetani->skor_d6_k5_1_hambatan_mutu +
$skorPetani->skor_d6_k5_2_tingkat_kepercayaan_pembeli
) / 4;
$skorPetani->skor_d2_sistem_praktik_budidaya = (
$skorPetani->skor_komp_3_d2_praktik_budidaya_gap
);
$skorPetani->skor_d4_sosial_ekonomi = (
$skorPetani->skor_komp_1_d4_k3_motivasi_kapasitas_adopsi
);
$skorPetani->skor_d6_rantai_nilai_akses_pasar = (
$skorPetani->skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar * 0.25 +
$skorPetani->skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar
);
$skorPetani->skor_d4_sosial_ekonomi = (
$skorPetani->skor_komp_1_d4_k3_motivasi_kapasitas_adopsi * 0.25 +
$skorPetani->skor_komp_2_d4_k2_ekonomi_rumah_tangga * 0.20 +
$skorPetani->skor_komp_4_d4_k4_keterlibatan_kelembagaan * 0.15 +
$skorPetani->skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan * 0.10
) / 0.70;
$skorPetani->skor_kesiapan_adopsi = (
$skorPetani->skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar * 0.25 +
$skorPetani->skor_komp_2_d4_k2_ekonomi_rumah_tangga * 0.20 +
$skorPetani->skor_komp_3_d2_praktik_budidaya_gap * 0.20 +
$skorPetani->skor_komp_4_d4_k4_keterlibatan_kelembagaan * 0.15 +
$skorPetani->skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan * 0.10 +
$skorPetani->skor_komp_1_d4_k3_motivasi_kapasitas_adopsi * 0.10
$skorPetani->skor_komp_1_d4_k3_motivasi_kapasitas_adopsi * 0.25 + // D4
$skorPetani->skor_komp_2_d4_k2_ekonomi_rumah_tangga * 0.20 + // D4
$skorPetani->skor_komp_3_d2_praktik_budidaya_gap * 0.20 + // D2
$skorPetani->skor_komp_4_d4_k4_keterlibatan_kelembagaan * 0.15 + // D4
$skorPetani->skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan * 0.10 + // D4
$skorPetani->skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar * 0.10 // D6
);
});
}
public function petani(): BelongsTo
{
return $this->belongsTo(Petani::class, 'petani_id');
}
}
@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('bumdes_pengurus', function (Blueprint $table) {
$table->id();
$table->string('uid')->unique();
$table->string('nama');
$table->boolean('jenis_kelamin');
$table->string('tempat_lahir')->nullable();
$table->date('tanggal_lahir');
$table->string('pendidikan_terakhir')->nullable();
$table->text('alamat')->nullable();
$table->foreignId('desa_kelurahan_id')->nullable()->constrained('master_desa_kelurahan')->onDelete('set null');
$table->string('nomor_telepon')->nullable();
$table->string('jabatan');
$table->string('jabatan_lainnya')->nullable();
$table->unsignedSmallInteger('lama_menjabat_tahun')->nullable();
$table->date('tanggal_mulai_menjabat');
$table->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->constrained('users')->onDelete('set null');
$table->foreignId('deleted_by')->nullable()->constrained('users')->onDelete('set null');
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('bumdes_pengurus');
}
};
@@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('bumdes_pengurus', function (Blueprint $table) {
if (! Schema::hasColumn('bumdes_pengurus', 'pendidikan_terakhir')) {
$table->string('pendidikan_terakhir')->nullable()->after('tanggal_lahir');
}
if (! Schema::hasColumn('bumdes_pengurus', 'jabatan_lainnya')) {
$table->string('jabatan_lainnya')->nullable()->after('jabatan');
}
if (! Schema::hasColumn('bumdes_pengurus', 'lama_menjabat_tahun')) {
$table->unsignedSmallInteger('lama_menjabat_tahun')->nullable()->after('jabatan_lainnya');
}
});
}
public function down(): void
{
Schema::table('bumdes_pengurus', function (Blueprint $table) {
if (Schema::hasColumn('bumdes_pengurus', 'lama_menjabat_tahun')) {
$table->dropColumn('lama_menjabat_tahun');
}
if (Schema::hasColumn('bumdes_pengurus', 'jabatan_lainnya')) {
$table->dropColumn('jabatan_lainnya');
}
if (Schema::hasColumn('bumdes_pengurus', 'pendidikan_terakhir')) {
$table->dropColumn('pendidikan_terakhir');
}
});
}
};
@@ -0,0 +1,26 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('akses_pasar', function (Blueprint $table) {
$table->tinyInteger('tingkat_kepercayaan_pembeli_baru')->nullable()->after('sumber_info_premium');
$table->text('alasan_kepercayaan_pembeli_baru')->nullable()->after('tingkat_kepercayaan_pembeli_baru');
});
}
public function down(): void
{
Schema::table('akses_pasar', function (Blueprint $table) {
$table->dropColumn(['tingkat_kepercayaan_pembeli_baru', 'alasan_kepercayaan_pembeli_baru']);
});
}
};
@@ -0,0 +1,7 @@
@extends('layout.main')
@section('content')
<div class="container-xxl flex-grow-1 container-p-y">
@include('bumdes.profil.header', ['pengurus' => $pengurus])
</div>
@endsection
@@ -0,0 +1,280 @@
@php
$prefix = $mode . '-bumdes';
$currentProvinsiId = old('provinsi_id', $pengurus?->desaKelurahan?->kecamatan?->kabupatenKota?->provinsi_id);
$currentKabupaten = $pengurus?->desaKelurahan?->kecamatan?->kabupatenKota;
$currentKecamatan = $pengurus?->desaKelurahan?->kecamatan;
$currentDesa = $pengurus?->desaKelurahan;
$currentJabatan = $pengurus?->jabatan instanceof \App\Enums\Bumdes\JabatanBumdes ? $pengurus->jabatan->value : $pengurus?->jabatan;
$currentPendidikan = $pengurus?->pendidikan_terakhir instanceof \App\Enums\Pendidikan ? $pengurus->pendidikan_terakhir->value : $pengurus?->pendidikan_terakhir;
@endphp
<h5 class="pb-4 border-bottom mb-4">Data Pribadi</h5>
<div class="row">
<div class="col-md-3 mb-4 form-control-validation">
<label class="form-label" for="{{ $prefix }}-nama">Nama Pengurus BUMDes<span class="text-danger">*</span></label>
<input type="text" class="form-control @error('nama') is-invalid @enderror" id="{{ $prefix }}-nama" name="nama" placeholder="Contoh: John Doe" value="{{ old('nama', $pengurus?->nama) }}" />
@error('nama')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
<div class="col-md-3 mb-4 form-control-validation">
<label class="form-label" for="{{ $prefix }}-jenis-kelamin">Jenis Kelamin<span class="text-danger">*</span></label>
<select class="form-control @error('jenis_kelamin') is-invalid @enderror" id="{{ $prefix }}-jenis-kelamin" name="jenis_kelamin">
<option value="">Pilih Jenis Kelamin</option>
<option value="1" {{ old('jenis_kelamin', $pengurus?->jenis_kelamin) == '1' ? 'selected' : '' }}>Laki-laki</option>
<option value="0" {{ old('jenis_kelamin', $pengurus?->jenis_kelamin) == '0' ? 'selected' : '' }}>Perempuan</option>
</select>
@error('jenis_kelamin')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
<div class="col-md-3 mb-4 form-control-validation">
<label class="form-label" for="{{ $prefix }}-tempat-lahir">Tempat Lahir<span class="text-danger">*</span></label>
<input type="text" class="form-control @error('tempat_lahir') is-invalid @enderror" id="{{ $prefix }}-tempat-lahir" name="tempat_lahir" placeholder="Contoh: Ngada" value="{{ old('tempat_lahir', $pengurus?->tempat_lahir) }}" />
@error('tempat_lahir')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
<div class="col-md-3 mb-4 form-control-validation">
<label class="form-label" for="{{ $prefix }}-tanggal-lahir">Tanggal Lahir<span class="text-danger">*</span></label>
<input type="text" class="form-control @error('tanggal_lahir') is-invalid @enderror" id="{{ $prefix }}-tanggal-lahir" name="tanggal_lahir" value="{{ old('tanggal_lahir', $pengurus?->tanggal_lahir?->format('d-m-Y')) }}" />
@error('tanggal_lahir')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
</div>
<div class="row">
<div class="col-md-3 mb-4 form-control-validation">
<label class="form-label" for="{{ $prefix }}-pendidikan-terakhir">Pendidikan Terakhir<span class="text-danger">*</span></label>
<select class="form-control @error('pendidikan_terakhir') is-invalid @enderror" id="{{ $prefix }}-pendidikan-terakhir" name="pendidikan_terakhir">
<option value="">Pilih Pendidikan Terakhir</option>
@foreach($pendidikan as $value)
<option value="{{ $value['value'] }}" {{ old('pendidikan_terakhir', $currentPendidikan) == $value['value'] ? 'selected' : '' }}>{{ $value['label'] }}</option>
@endforeach
</select>
@error('pendidikan_terakhir')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
</div>
<h5 class="pb-4 border-bottom mb-4 mt-4">Peran Kepengurusan BUMDes</h5>
<div class="row">
<div class="col-md-3 mb-4 form-control-validation">
<label class="form-label" for="{{ $prefix }}-jabatan">Jabatan/Peran<span class="text-danger">*</span></label>
<select class="form-control @error('jabatan') is-invalid @enderror" id="{{ $prefix }}-jabatan" name="jabatan">
<option value="">Pilih Jabatan/Peran</option>
@foreach($jabatan as $value)
<option value="{{ $value['value'] }}" {{ old('jabatan', $currentJabatan) == $value['value'] ? 'selected' : '' }}>{{ $value['label'] }}</option>
@endforeach
</select>
@error('jabatan')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
<div class="col-md-3 mb-4 form-control-validation {{ old('jabatan', $currentJabatan) === \App\Enums\Bumdes\JabatanBumdes::LAINNYA->value ? '' : 'd-none' }}" id="{{ $prefix }}-jabatan-lainnya-wrapper">
<label class="form-label" for="{{ $prefix }}-jabatan-lainnya">Jabatan Lainnya<span class="text-danger">*</span></label>
<input type="text" class="form-control @error('jabatan_lainnya') is-invalid @enderror" id="{{ $prefix }}-jabatan-lainnya" name="jabatan_lainnya" placeholder="Contoh: Kepala Unit Usaha" value="{{ old('jabatan_lainnya', $pengurus?->jabatan_lainnya) }}" />
@error('jabatan_lainnya')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
<div class="col-md-3 mb-4 form-control-validation">
<label class="form-label" for="{{ $prefix }}-lama-menjabat">Lama Menjabat (Tahun)<span class="text-danger">*</span></label>
<input type="number" min="0" max="80" class="form-control @error('lama_menjabat_tahun') is-invalid @enderror" id="{{ $prefix }}-lama-menjabat" name="lama_menjabat_tahun" placeholder="Contoh: 2" value="{{ old('lama_menjabat_tahun', $pengurus?->lama_menjabat_tahun) }}" />
@error('lama_menjabat_tahun')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
</div>
<h5 class="pb-4 border-bottom mb-4 mt-4">Domisili dan Kontak</h5>
<div class="row">
<div class="col-md-12 mb-4 form-control-validation">
<label class="form-label" for="{{ $prefix }}-alamat">Alamat<span class="text-danger">*</span></label>
<textarea class="form-control @error('alamat') is-invalid @enderror" id="{{ $prefix }}-alamat" name="alamat" rows="2" placeholder="Contoh: Jl. Nama Jalan, Nama Dusun">{{ old('alamat', $pengurus?->alamat) }}</textarea>
@error('alamat')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
</div>
<div class="row">
<div class="col-md-3 mb-4 form-control-validation">
<label class="form-label" for="{{ $prefix }}-provinsi">Provinsi<span class="text-danger">*</span></label>
<select class="form-control @error('provinsi_id') is-invalid @enderror" id="{{ $prefix }}-provinsi" name="provinsi_id">
<option value="">Pilih Provinsi</option>
@foreach($provinsi as $p)
<option value="{{ $p->id }}" {{ $currentProvinsiId == $p->id ? 'selected' : '' }}>{{ $p->nama }}</option>
@endforeach
</select>
@error('provinsi_id')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
<div class="col-md-3 mb-4 form-control-validation">
<label class="form-label" for="{{ $prefix }}-kabupaten">Kabupaten/Kota<span class="text-danger">*</span></label>
<select class="form-control @error('kabupaten_id') is-invalid @enderror" id="{{ $prefix }}-kabupaten" name="kabupaten_id">
<option value="">Pilih Kabupaten/Kota</option>
@if($currentKabupaten)
<option value="{{ $currentKabupaten->id }}" selected>{{ $currentKabupaten->nama }}</option>
@endif
</select>
@error('kabupaten_id')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
<div class="col-md-3 mb-4 form-control-validation">
<label class="form-label" for="{{ $prefix }}-kecamatan">Kecamatan<span class="text-danger">*</span></label>
<select class="form-control @error('kecamatan_id') is-invalid @enderror" id="{{ $prefix }}-kecamatan" name="kecamatan_id">
<option value="">Pilih Kecamatan</option>
@if($currentKecamatan)
<option value="{{ $currentKecamatan->id }}" selected>{{ $currentKecamatan->nama }}</option>
@endif
</select>
@error('kecamatan_id')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
<div class="col-md-3 mb-4 form-control-validation">
<label class="form-label" for="{{ $prefix }}-desa">Desa/Kelurahan<span class="text-danger">*</span></label>
<select class="form-control @error('desa_kelurahan_id') is-invalid @enderror" id="{{ $prefix }}-desa" name="desa_kelurahan_id">
<option value="">Pilih Desa/Kelurahan</option>
@if($currentDesa)
<option value="{{ $currentDesa->id }}" selected>{{ $currentDesa->nama }}</option>
@endif
</select>
@error('desa_kelurahan_id')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
</div>
<div class="row">
<div class="col-md-3 mb-4 form-control-validation">
<label class="form-label" for="{{ $prefix }}-nomor-telepon">No HP</label>
<input type="text" class="form-control @error('nomor_telepon') is-invalid @enderror" id="{{ $prefix }}-nomor-telepon" name="nomor_telepon" placeholder="Contoh: 081234567890" value="{{ old('nomor_telepon', $pengurus?->nomor_telepon) }}" minlength="11" maxlength="13" />
@error('nomor_telepon')<div class="error invalid-feedback">{{ $message }}</div>@enderror
</div>
</div>
@push('scripts')
<script>
'use strict';
$(document).ready(function () {
const prefix = '#{{ $prefix }}';
const lainnyaValue = '{{ \App\Enums\Bumdes\JabatanBumdes::LAINNYA->value }}';
$(`${prefix}-jenis-kelamin, ${prefix}-pendidikan-terakhir, ${prefix}-jabatan`).select2();
$(`${prefix}-tanggal-lahir`).flatpickr({
dateFormat: 'd-m-Y'
});
function toggleJabatanLainnya() {
const isLainnya = $(`${prefix}-jabatan`).val() === lainnyaValue;
$(`${prefix}-jabatan-lainnya-wrapper`).toggleClass('d-none', !isLainnya);
if (!isLainnya) {
$(`${prefix}-jabatan-lainnya`).val('');
}
}
$(`${prefix}-jabatan`).on('change', toggleJabatanLainnya);
toggleJabatanLainnya();
$(`${prefix}-provinsi`).select2().change(function(e) {
e.preventDefault();
const provinsiId = $(this).val();
$(`${prefix}-kabupaten`).prop('disabled', !provinsiId).val('').trigger('change');
$(`${prefix}-kecamatan`).prop('disabled', true).val('').trigger('change');
$(`${prefix}-desa`).prop('disabled', true).val('').trigger('change');
if (provinsiId) {
$.ajax({
url: '{{ route("kabupatenkota.byprovinsi", ":id") }}'.replace(':id', provinsiId),
method: 'GET',
success: function(data) {
$(`${prefix}-kabupaten`).empty().append('<option value="">Pilih Kabupaten/Kota</option>');
data.forEach(function(kabupaten) {
$(`${prefix}-kabupaten`).append('<option value="' + kabupaten.id + '">' + kabupaten.nama + '</option>');
});
}
});
}
});
$(`${prefix}-kabupaten`).select2().change(function(e) {
e.preventDefault();
const kabupatenId = $(this).val();
$(`${prefix}-kecamatan`).prop('disabled', !kabupatenId).val('').trigger('change');
$(`${prefix}-desa`).prop('disabled', true).val('').trigger('change');
if (kabupatenId) {
$.ajax({
url: '{{ route("kecamatan.bykabupatenkota", ":id") }}'.replace(':id', kabupatenId),
method: 'GET',
success: function(data) {
$(`${prefix}-kecamatan`).empty().append('<option value="">Pilih Kecamatan</option>');
data.forEach(function(kecamatan) {
$(`${prefix}-kecamatan`).append('<option value="' + kecamatan.id + '">' + kecamatan.nama + '</option>');
});
}
});
}
});
$(`${prefix}-kecamatan`).select2().change(function(e) {
e.preventDefault();
const kecamatanId = $(this).val();
$(`${prefix}-desa`).prop('disabled', !kecamatanId).val('').trigger('change');
if (kecamatanId) {
$.ajax({
url: '{{ route("desakelurahan.bykecamatan", ":id") }}'.replace(':id', kecamatanId),
method: 'GET',
success: function(data) {
$(`${prefix}-desa`).empty().append('<option value="">Pilih Desa/Kelurahan</option>');
data.forEach(function(desa) {
$(`${prefix}-desa`).append('<option value="' + desa.id + '">' + desa.nama + '</option>');
});
}
});
}
});
$(`${prefix}-desa`).select2();
$('#{{ $mode }}BumdesForm').validate({
rules: {
nama: { required: true },
jenis_kelamin: { required: true },
tempat_lahir: { required: true },
tanggal_lahir: { required: true },
pendidikan_terakhir: { required: true },
jabatan: { required: true },
jabatan_lainnya: {
required: function() {
return $(`${prefix}-jabatan`).val() === lainnyaValue;
}
},
lama_menjabat_tahun: { required: true, digits: true, min: 0, max: 80 },
alamat: { required: true },
provinsi_id: { required: true },
kabupaten_id: { required: true },
kecamatan_id: { required: true },
desa_kelurahan_id: { required: true },
nomor_telepon: { required: false, digits: true, minlength: 11, maxlength: 13 }
},
messages: {
nama: { required: 'Nama pengurus BUMDes wajib diisi' },
jenis_kelamin: { required: 'Jenis kelamin wajib dipilih' },
tempat_lahir: { required: 'Tempat lahir wajib diisi' },
tanggal_lahir: { required: 'Tanggal lahir wajib diisi' },
pendidikan_terakhir: { required: 'Pendidikan terakhir wajib dipilih' },
jabatan: { required: 'Jabatan/peran wajib dipilih' },
jabatan_lainnya: { required: 'Jabatan lainnya wajib diisi' },
lama_menjabat_tahun: {
required: 'Lama menjabat wajib diisi',
digits: 'Lama menjabat harus berupa angka tahun',
min: 'Lama menjabat tidak boleh kurang dari 0 tahun',
max: 'Lama menjabat tidak boleh lebih dari 80 tahun'
},
alamat: { required: 'Alamat wajib diisi' },
provinsi_id: { required: 'Provinsi wajib dipilih' },
kabupaten_id: { required: 'Kabupaten/Kota wajib dipilih' },
kecamatan_id: { required: 'Kecamatan wajib dipilih' },
desa_kelurahan_id: { required: 'Desa/Kelurahan wajib dipilih' },
nomor_telepon: {
digits: 'Nomor telepon harus berupa angka',
minlength: 'Nomor telepon minimal 11 digit',
maxlength: 'Nomor telepon maksimal 13 digit'
}
},
errorElement: 'div',
errorPlacement: function (error, element) {
error.addClass('invalid-feedback');
element.closest('.form-control-validation').append(error);
},
highlight: function (element) {
$(element).addClass('is-invalid');
},
unhighlight: function (element) {
$(element).removeClass('is-invalid');
}
});
});
</script>
@endpush
@@ -0,0 +1,34 @@
@extends('layout.main')
@section('content')
<div class="container-xxl flex-grow-1 container-p-y">
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@include('bumdes.profil.header', ['pengurus' => null])
<div class="card mb-6">
<div class="card-header border-bottom">
<h5 class="card-title mb-0">Tambah Profil Pengurus BUMDes</h5>
</div>
<div class="card-body pt-6">
<form id="addBumdesForm" action="{{ route('bumdes.store') }}" method="POST">
@csrf
@include('bumdes.profil._form', ['pengurus' => null, 'mode' => 'add'])
<div class="mt-4 text-end">
<a href="{{ route('bumdes.index') }}" class="btn btn-secondary me-2 wave-effect wave-light">Kembali</a>
<button type="submit" class="btn btn-success data-submit wave-effect wave-light"><i class="ti tabler-device-floppy me-2"></i> Simpan Profil</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@@ -0,0 +1,39 @@
@extends('layout.main')
@section('content')
<div class="container-xxl flex-grow-1 container-p-y">
@if (session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@include('bumdes.profil.header', ['pengurus' => $pengurus])
<div class="card mb-6">
<div class="card-header border-bottom">
<h5 class="card-title mb-0">Edit Profil Pengurus BUMDes</h5>
</div>
<div class="card-body pt-6">
<form id="editBumdesForm" action="{{ route('bumdes.update', $pengurus->id) }}" method="POST">
@csrf
@method('PUT')
@include('bumdes.profil._form', ['pengurus' => $pengurus, 'mode' => 'edit'])
<div class="mt-4 text-end">
<a href="{{ route('bumdes.index') }}" class="btn btn-secondary me-2 wave-effect wave-light">Kembali</a>
<button type="submit" class="btn btn-success data-submit wave-effect wave-light"><i class="ti tabler-device-floppy me-2"></i> Simpan Profil</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@@ -0,0 +1,162 @@
<style>
@media (min-width: 768px) {
.nav-pills-custom-spacing {
display: flex;
width: 100%;
gap: 0.5rem;
flex-wrap: nowrap !important;
overflow-x: auto;
}
.nav-pills-custom-spacing::-webkit-scrollbar {
display: none;
}
.nav-pills-custom-spacing .nav-item {
margin-right: 0 !important;
flex: 1;
}
.nav-pills-custom-spacing .nav-link {
display: flex;
justify-content: center;
align-items: center;
padding-left: 0.5rem !important;
padding-right: 0.5rem !important;
width: 100%;
white-space: nowrap;
font-size: 0.9375rem !important;
}
.nav-pills-custom-spacing .nav-link i {
margin-right: 0.4rem !important;
}
}
</style>
@php
$jenisKelamin = isset($pengurus)
? ($pengurus->jenis_kelamin ? 'Laki-laki' : 'Perempuan')
: 'Jenis Kelamin';
$jabatanLabel = isset($pengurus) ? $pengurus->nama_jabatan : 'Jabatan/Peran';
$domisili = isset($pengurus) && $pengurus->desaKelurahan
? $pengurus->desaKelurahan->nama . ', ' . $pengurus->desaKelurahan->kecamatan?->nama . ', ' . $pengurus->desaKelurahan->kecamatan?->kabupatenKota?->nama . ', ' . $pengurus->desaKelurahan->kecamatan?->kabupatenKota?->provinsi?->nama
: 'Desa/Kelurahan, Kecamatan, Kabupaten/Kota, Provinsi';
@endphp
<div class="row mb-4">
<div class="col-md-4 mb-2 mb-md-0">
<div class="card h-100">
<div class="card-body p-3">
<h6 class="card-title mb-2">Profil Pengurus BUMDes</h6>
<div class="d-flex justify-content-start align-items-center mb-3">
<div class="avatar me-2">
<span class="avatar-initial rounded bg-label-primary">
<i class="icon-base ti tabler-user-check icon-22px"></i>
</span>
</div>
<div class="d-flex flex-column">
<h6 class="mb-0" style="font-size: 0.9rem;">{{ isset($pengurus) ? $pengurus->nama : 'Tambah Pengurus BUMDes Baru' }}</h6>
<span class="text-muted" style="font-size: 0.75rem;">{{ $jenisKelamin }}</span>
<span class="text-muted" style="font-size: 0.75rem;">Jabatan: {{ $jabatanLabel }}</span>
</div>
</div>
<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($pengurus) ? $pengurus->alamat : 'Alamat' }}</p>
<p class="mb-0 text-muted" style="font-size: 0.75rem; line-height: 1.3;">{{ $domisili }}</p>
</div>
</div>
</div>
<div class="col-md-8 d-flex flex-column">
<div class="card mb-2 flex-grow-1">
<div class="card-widget-separator-wrapper h-100">
<div class="card-body card-widget-separator py-2 h-100 d-flex flex-column justify-content-center">
<div class="row gy-2">
<div class="col-sm-6 col-lg-4">
<div class="d-flex justify-content-between align-items-center card-widget-1 border-end h-100 pe-3">
<div>
<p class="mb-0 text-muted" style="font-size: 0.75rem;">Jenis Kelamin</p>
<h5 class="mb-0">{{ $jenisKelamin }}</h5>
<p class="mb-0" style="font-size: 0.75rem;">
<span class="me-1">{{ isset($pengurus) ? ($pengurus->usia . ' tahun') : 'Belum terisi' }}</span>
</p>
</div>
<span class="avatar">
<span class="avatar-initial rounded">
<i class="icon-base ti tabler-users icon-22px text-heading"></i>
</span>
</span>
</div>
</div>
<div class="col-sm-6 col-lg-4">
<div class="d-flex justify-content-between align-items-center card-widget-1 border-end h-100 px-3">
<div>
<p class="mb-0 text-muted" style="font-size: 0.75rem;">Jabatan</p>
<h5 class="mb-0">{{ $jabatanLabel }}</h5>
<p class="mb-0" style="font-size: 0.75rem;">
<span class="me-1">{{ isset($pengurus) ? $pengurus->lama_menjabat : 'Belum terisi' }}</span>
</p>
</div>
<span class="avatar">
<span class="avatar-initial rounded">
<i class="icon-base ti tabler-briefcase icon-22px text-heading"></i>
</span>
</span>
</div>
</div>
<div class="col-sm-6 col-lg-4">
<div class="d-flex justify-content-between align-items-center card-widget-1 h-100 ps-3">
<div>
<p class="mb-0 text-muted" style="font-size: 0.75rem;">Domisili</p>
<h5 class="mb-0">{{ isset($pengurus) ? ($pengurus->desaKelurahan?->nama ?? '-') : '-' }}</h5>
<p class="mb-0" style="font-size: 0.75rem;">
<span class="me-1">{{ isset($pengurus) ? ($pengurus->desaKelurahan?->kecamatan?->nama ?? '-') : 'Belum terisi' }}</span>
</p>
</div>
<span class="avatar">
<span class="avatar-initial rounded">
<i class="icon-base ti tabler-map-pin icon-22px text-heading"></i>
</span>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card flex-grow-1">
<div class="card-body py-3 d-flex align-items-center">
<div class="w-100">
<p class="mb-1 text-muted" style="font-size: 0.75rem;">Alamat Domisili</p>
<h6 class="mb-0">{{ isset($pengurus) ? ($pengurus->alamat ?: '-') : '-' }}</h6>
<p class="mb-0 text-muted" style="font-size: 0.75rem;">{{ $domisili }}</p>
</div>
</div>
</div>
</div>
</div>
<div class="nav-align-top">
<ul class="nav nav-pills flex-column flex-md-row mb-6 row-gap-2 nav-pills-custom-spacing">
<li class="nav-item">
<a class="nav-link {{ request()->routeIs('bumdes.create') || request()->routeIs('bumdes.edit') ? 'active' : '' }}" href="{{ isset($pengurus) ? route('bumdes.edit', $pengurus->id) : route('bumdes.create') }}">
<i class="icon-base ti tabler-user-check icon-sm"></i>Profil
</a>
</li>
<li class="nav-item">
@if(isset($pengurus))
<a class="nav-link {{ request()->routeIs('bumdes.kelembagaan-aktor.*') ? 'active' : '' }}" href="{{ route('bumdes.kelembagaan-aktor.index', $pengurus->id) }}">
<i class="icon-base ti tabler-building-community icon-sm"></i>Kelembagaan dan Aktor
</a>
@else
<a class="nav-link" href="#" disabled data-bs-original-title="Kelembagaan dan Aktor, isi profil terlebih dahulu" data-bs-toggle="tooltip" data-bs-placement="top">
<i class="icon-base ti tabler-building-community icon-sm"></i>Kelembagaan dan Aktor
</a>
@endif
</li>
</ul>
</div>
@@ -0,0 +1,158 @@
@extends('layout.main')
@section('content')
<div class="container-xxl flex-grow-1 container-p-y">
<div class="d-flex flex-row gap-3 mb-4">
<div class="w-25">
<x-summary-card
title="Pengurus BUMDes"
count="{{ $totalPengurus }}"
subtitle="{{ $totalLakiLaki }} Laki-laki, {{ $totalPerempuan }} Perempuan"
icon="user-check"
color="primary"
/>
</div>
<div class="w-25">
<x-summary-card
title="Desa/Kelurahan"
count="{{ $totalDesaKelurahan }}"
subtitle="desa/kelurahan memiliki data pengurus"
icon="map-pin"
color="success"
/>
</div>
<div class="w-25">
<x-summary-card
title="Jabatan/Peran"
count="{{ $totalJabatan }}"
subtitle="jenis jabatan/peran terdata"
icon="briefcase"
color="info"
/>
</div>
</div>
<div class="card">
<div class="card-header border-bottom">
<h5 class="card-title mb-0">Data Pengurus BUMDes</h5>
</div>
<div class="card-body border-bottom pt-4">
<form method="GET" action="{{ route('bumdes.index') }}" class="d-flex justify-content-between align-items-center row gap-3 gap-md-0">
<div class="col-md-auto">
<div class="d-flex align-items-center">
<label class="me-2 text-nowrap">Show</label>
<select name="per_page" class="form-select form-select-sm w-auto" onchange="this.form.submit()">
<option value="10" {{ request('per_page') == 10 ? 'selected' : '' }}>10</option>
<option value="25" {{ request('per_page') == 25 ? 'selected' : '' }}>25</option>
</select>
<label class="ms-2 text-nowrap">entries</label>
</div>
</div>
<div class="col-md-auto d-flex gap-2">
<div class="input-group input-group-sm w-auto">
<input type="text" name="search" class="form-control" placeholder="Cari Nama..." value="{{ request('search') }}">
<button type="submit" class="btn btn-outline-primary"><i class="ti tabler-search"></i></button>
</div>
<a href="{{ route('bumdes.create') }}" class="btn btn-sm btn-primary">
<i class="ti tabler-plus me-1"></i> Tambah
</a>
</div>
</form>
</div>
<div class="table-responsive text-nowrap">
<table class="table table-hover">
<thead class="border-top">
<tr>
<th>No</th>
<th>Nama</th>
<th>Jenis Kelamin</th>
<th>Usia</th>
<th>Desa/Kelurahan</th>
<th>Jabatan/Peran</th>
<th>Lama Menjabat</th>
@if($showEnumerator)
<th>Nama Enumerator</th>
@endif
<th>Aksi</th>
</tr>
</thead>
<tbody>
@forelse($pengurus as $item)
<tr>
<td>{{ ($pengurus->firstItem() ?? 0) + $loop->index }}</td>
<td><span class="text-heading">{{ $item->nama }}</span></td>
<td><span class="text-muted">{{ $item->jenis_kelamin ? 'Laki-laki' : 'Perempuan' }}</span></td>
<td><span class="text-muted">{{ $item->usia }} tahun</span></td>
<td>
<span class="text-muted">
{{ $item->desaKelurahan?->nama }},
{{ $item->desaKelurahan?->kecamatan?->nama }},
{{ $item->desaKelurahan?->kecamatan?->kabupatenKota?->nama }},
{{ $item->desaKelurahan?->kecamatan?->kabupatenKota?->provinsi?->nama }}
</span>
</td>
<td>{{ $item->nama_jabatan }}</td>
<td><span class="text-muted">{{ $item->lama_menjabat }}</span></td>
@if($showEnumerator)
<td><span class="text-muted">{{ $item->creator?->name ?? '-' }}</span></td>
@endif
<td>
<div class="d-flex align-items-center">
<a href="{{ route('bumdes.edit', $item->id) }}" class="btn btn-icon btn-text-secondary rounded-pill edit-record"><i class="ti tabler-edit ti-md"></i></a>
<a href="javascript:;" class="btn btn-icon btn-text-secondary rounded-pill delete-record" data-id="{{ $item->id }}" data-nama="{{ $item->nama }}"><i class="ti tabler-trash ti-md"></i></a>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="{{ $showEnumerator ? 9 : 8 }}" class="text-center py-4">Tidak ada data ditemukan.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="card-footer d-flex justify-content-center justify-content-md-end border-top">
{{ $pengurus->appends(request()->query())->links('pagination::bootstrap-5') }}
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
'use strict';
const csrfToken = "{{ csrf_token() }}";
$(document).ready(function () {
$(document).on('click', '.delete-record', function () {
const id = $(this).data('id');
const name = $(this).data('nama');
Swal.fire({
title: 'Apakah Anda yakin?',
text: `Profile "${name}" akan dihapus secara permanen!`,
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Ya, Hapus!',
customClass: { confirmButton: 'btn btn-primary me-3', cancelButton: 'btn btn-label-secondary' },
buttonsStyling: false
}).then(function (result) {
if (result.value) {
$.ajax({
url: '{{ route('bumdes.destroy', ':id') }}'.replace(':id', id),
type: 'DELETE',
headers: { 'X-CSRF-TOKEN': csrfToken },
success: function (res) {
Swal.fire({ icon: 'success', title: 'Terhapus!', text: res.message, showConfirmButton: false, timer: 1500 })
.then(() => window.location.reload());
},
error: () => Swal.fire({ icon: 'error', title: 'Gagal!', text: 'Terjadi kesalahan sistem.' })
});
}
});
});
});
</script>
@endpush
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+207 -222
View File
@@ -1,6 +1,35 @@
@extends('layout.main')
@section('content')
@php
$totalPetani = $petaniSummary->sum('total');
$segments = [
[
'name' => 'Penggerak',
'range' => '4.0 - 5.0',
'icon' => 'tabler-crown',
'color' => 'success',
],
[
'name' => 'Perintis',
'range' => '3.0 - 3.9',
'icon' => 'tabler-flag',
'color' => 'info',
],
[
'name' => 'Pemerhati',
'range' => '2.0 - 2.9',
'icon' => 'tabler-eye',
'color' => 'warning',
],
[
'name' => 'Pemula',
'range' => '1.0 - 1.9',
'icon' => 'tabler-seedling',
'color' => 'danger',
],
];
@endphp
<div class="container-xxl flex-grow-1 container-p-y">
<div class="row g-6">
<div class="col-lg-3 col-sm-6">
@@ -12,9 +41,9 @@
<i class="icon-base ti tabler-user-check icon-28px"></i>
</span>
</div>
<h4 class="mb-0">{{ $petaniSummary->sum('total') }}</h4>
<h4 class="mb-0">{{ $totalPetani }}</h4>
</div>
<p class="mb-1">Petani</p>
<p class="mb-1">Total Petani</p>
<p class="mb-0">
<span class="text-heading fw-medium me-2">{{ $petaniSummary->count('total') }}</span>
<small class="text-body-secondary">komoditas utama</small>
@@ -41,29 +70,6 @@
</div>
</div>
</div>
<!-- <div class="col-lg-6 col-sm-12">
<div class="card card-border-shadow-danger h-100">
<div class="card-body">
<div class="d-flex align-items-center mb-2">
<div class="avatar me-4">
<span class="avatar-initial rounded bg-label-danger">
<i class="icon-base ti tabler-seedling icon-28px"></i>
</span>
</div>
<h4 class="mb-0">{{ $tanamanSummary->sum('total') }}</h4>
</div>
<p class="mb-1">Tanaman Komoditas Utama</p>
<div class="d-flex justify-content-between mb-2">
@foreach($komoditas as $k)
<p class="mb-0">
<span class="text-heading fw-medium me-2">{{ $tanamanSummary->where('komoditas_id', $k->id)->sum('total') }}</span>
<small class="text-body-danger">tanaman {{ $k->nama }}</small>
</p>
@endforeach
</div>
</div>
</div>
</div> -->
<div class="col-lg-3 col-sm-6">
<div class="card card-border-shadow-info h-100">
<div class="card-body">
@@ -78,7 +84,6 @@
<p class="mb-1">Rata-rata Indeks Kesiapan Adopsi</p>
<p class="mb-0">
<span class="text-heading fw-medium me-2">{{ $segmenAverage }}</span>
<small class="text-body-info">komoditas utama</small>
</p>
</div>
</div>
@@ -95,146 +100,92 @@
</div>
</div>
<div class="card-body">
<table class="table card-table table-border-top-0 table-border-bottom-0">
<table class="table card-table table-border-top-0 table-border-bottom-0 data-table">
<thead>
<tr>
<th></th>
<th class="text-end pe-0">Petani</th>
<th class="text-end pe-0">%</th>
</tr>
</thead>
<tbody>
@foreach ($segments as $segment)
@php
$total = $skorSummary->where('segmen', $segment['name'])->sum('total');
$persen = $totalPetani ? ($total / $totalPetani) * 100 : 0;
@endphp
<tr>
<td class="w-60 ps-0"></td>
<td class="text-end pe-0">Petani</td>
<td class="text-end pe-0">%</td>
</tr>
<tr>
<td class="w-60 ps-0">
<td>
<div class="d-flex justify-content-start align-items-center">
<div class="me-2">
<i class="icon-base ti tabler-crown icon-lg text-heading"></i>
</div>
<div>
<h6 class="mb-0 fw-normal text-success">Penggerak (4.0 - 5.0)</h6>
<!-- <small class="text-body-secondary text-wrap">Siap menjadi peserta agrolab utama dan panutan bagi petani lain</small> -->
<i class="icon-base ti {{ $segment['icon'] }} icon-lg text-{{ $segment['color'] }}"></i>
</div>
<span class="text-{{ $segment['color'] }}">
{{ $segment['name'] }}
<small class="text-body-secondary">({{ $segment['range'] }})</small>
</span>
</div>
</td>
<td class="text-end pe-0">
<span>{{ $skorSummary->where('segmen', 'Penggerak')->sum('total') }} petani</span>
</td>
<td class="text-end pe-0">
<span>{{ number_format(($skorSummary->where('segmen', 'Penggerak')->sum('total') / $petaniSummary->sum('total')) * 100, 2) }}%</span>
</td>
</tr>
<tr>
<td class="w-60 ps-0">
<div class="d-flex justify-content-start align-items-center">
<div class="me-2">
<i class="icon-base ti tabler-flag icon-lg text-heading"></i>
</div>
<div>
<h6 class="mb-0 fw-normal text-info">Perintis (3.0 - 3.9)</h6>
<!-- <small class="text-body-secondary text-wrap">Berpotensi berkembang menjadi Penggerak dengan dukungan yang tepat</small> -->
</div>
</div>
</td>
<td class="text-end pe-0">
<span>{{ $skorSummary->where('segmen', 'Perintis')->sum('total') }} petani</span>
</td>
<td class="text-end pe-0">
<span>{{ number_format(($skorSummary->where('segmen', 'Perintis')->sum('total') / $petaniSummary->sum('total')) * 100, 2) }}%</span>
</td>
</tr>
<tr>
<td class="w-60 ps-0">
<div class="d-flex justify-content-start align-items-center">
<div class="me-2">
<i class="icon-base ti tabler-eye icon-lg text-heading text-success"></i>
</div>
<div>
<h6 class="mb-0 fw-normal text-warning">Pemerhati (2.0 - 2.9)</h6>
<!-- <small class="text-body-secondary text-wrap">Perlu melihat bukti hasil nyata dari Penggerak sebelum mau bergerak</small> -->
</div>
</div>
</td>
<td class="text-end pe-0">
<span>{{ $skorSummary->where('segmen', 'Pemerhati')->sum('total') }} petani</span>
</td>
<td class="text-end pe-0">
<span>{{ number_format(($skorSummary->where('segmen', 'Pemerhati')->sum('total') / $petaniSummary->sum('total')) * 100, 2) }}%</span>
</td>
</tr>
<tr>
<td class="w-60 ps-0">
<div class="d-flex justify-content-start align-items-center">
<div class="me-2">
<i class="icon-base ti tabler-seedling icon-lg text-heading"></i>
</div>
<div>
<h6 class="mb-0 fw-normal text-danger">Pemula (1.0 - 1.9)</h6>
<!-- <small class="text-body-secondary text-wrap">Butuh penanganan hambatan dasar terlebih dahulu sebelum intervensi langsung</small> -->
</div>
</div>
</td>
<td class="text-end pe-0">
<span>{{ $skorSummary->where('segmen', 'Pemula')->sum('total') }} petani</span>
</td>
<td class="text-end pe-0">
<span>{{ number_format(($skorSummary->where('segmen', 'Pemula')->sum('total') / $petaniSummary->sum('total')) * 100, 2) }}%</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- <div class="col-lg-6 col-sm-12">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Segmentasi Petani Komoditas</h5>
<p class="card-subtitle">berdasarkan mayoritas segmentasi</p>
</div>
</div>
<div class="card-body">
<table class="table card-table table-border-top-0 table-border-bottom-0">
<tbody>
@foreach ($komoditas as $k)
<?php
$max = $skorKomoditasSummary->where('komoditas_id', $k->id)
->whereIn('segmen', ['Penggerak', 'Perintis', 'Pemerhati', 'Pemula'])
->sortByDesc('total')
->first();
?>
<tr>
<td class="w-40 ps-0">
<h6 class="mb-0 fw-normal text-heading">{{ $k->nama }}</h6>
</td>
<td class="pe-0">
@if(isset($max))
@if($max->segmen == 'Penggerak')
<span class="text-success">Petani Penggerak</span>
@elseif($max->segmen == 'Perintis')
<span class="text-info">Petani Perintis</span>
@elseif($max->segmen == 'Pemerhati')
<span class="text-warning">Petani Pemerhati</span>
@elseif($max->segmen == 'Pemula')
<span class="text-danger">Petani Pemula</span>
@else
<span>Belum Lengkap</span>
@endif
@else
<span>Belum Tersegmentasi</span>
@endif
</td>
<td class="text-end pe-0">
<span>{{ isset($max) ? $max->total : 0 }} Petani</span>
</td>
<td class="text-end pe-0">
<span>{{ isset($max) ? number_format(($max->total / $petaniSummary->sum('total')) * 100, 2) : 0 }}%</span>
</td>
<td class="text-end pe-0">{{ number_format($total) }}</td>
<td class="text-end pe-0">{{ number_format($persen, 2) }}%</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div> -->
</div>
<div class="col-lg-6 col-sm-12">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Segmentasi Petani</h5>
<p class="card-subtitle">per komoditas</p>
</div>
</div>
<div class="card-body">
<table class="table card-table table-border-top-0 table-border-bottom-0 data-table">
<thead>
<tr>
<th></th>
@foreach ($komoditas as $k)
<th class="text-end pe-0">{{ $k->nama }}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach ($segments as $segment)
<tr>
<td>
<div class="d-flex justify-content-start align-items-center">
<div class="me-2">
<i class="icon-base ti {{ $segment['icon'] }} icon-lg text-{{ $segment['color'] }}"></i>
</div>
<span class="text-{{ $segment['color'] }}">
{{ $segment['name'] }}
<small class="text-body-secondary">({{ $segment['range'] }})</small>
</span>
</div>
</td>
@foreach ($komoditas as $k)
@php
$jumlah = $skorKomoditasSummary->where('segmen', $segment['name'])->where('komoditas_id', $k->id)->sum('total');
@endphp
<td class="text-end pe-0">{{ number_format($jumlah) }}</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="row g-6 py-4">
<div class="col-lg-6 col-sm-12">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
@@ -244,115 +195,124 @@
</div>
</div>
<div class="card-body">
<table class="table card-table table-border-top-0 table-border-bottom-0">
<table class="table card-table table-border-top-0 table-border-bottom-0 data-table">
<thead>
<tr>
<th></th>
<th class="text-end pe-0">Indeks</th>
<th class="text-end pe-0">D1</th>
<th class="text-end pe-0">D2</th>
<th class="text-end pe-0">D3</th>
</tr>
</thead>
<tbody>
@foreach ($segments as $segment)
@php
$skor = $skorMultiDimensi->firstWhere('segmen', $segment['name']);
@endphp
<tr>
<td class="w-60 ps-0"></td>
<td class="text-end pe-0">Indeks</td>
<td class="text-end pe-0">D1</td>
<td class="text-end pe-0">D2</td>
<td class="text-end pe-0">D3</td>
</tr>
<tr>
<td class="w-60 ps-0">
<td>
<div class="d-flex justify-content-start align-items-center">
<div class="me-2">
<i class="icon-base ti tabler-crown icon-lg text-heading"></i>
<i class="icon-base ti {{ $segment['icon'] }} icon-lg text-{{ $segment['color'] }}"></i>
</div>
<div>
<h6 class="mb-0 fw-normal text-success">Penggerak (4.0 - 5.0)</h6>
<!-- <small class="text-body-secondary text-wrap">Siap menjadi peserta agrolab utama dan panutan bagi petani lain</small> -->
<h6 class="mb-0 fw-normal text-{{ $segment['color'] }}">
{{ $segment['name'] }}
<small class="text-body-secondary">({{ $segment['range'] }})</small>
</h6>
</div>
</div>
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Penggerak')->value('skor_kesiapan_adopsi'), 2, '.', ',') }}</span>
{{ number_format($skor?->skor_kesiapan_adopsi ?? 0, 2, '.', ',') }}
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Penggerak')->value('skor_d2_sistem_praktik_budidaya'), 2, '.', ',') }}</span>
{{ number_format($skor?->skor_d2_sistem_praktik_budidaya ?? 0, 2, '.', ',') }}
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Penggerak')->value('skor_d4_sosial_ekonomi'), 2, '.', ',') }}</span>
{{ number_format($skor?->skor_d4_sosial_ekonomi ?? 0, 2, '.', ',') }}
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Penggerak')->value('skor_d6_rantai_nilai_akses_pasar'), 2, '.', ',') }}</span>
{{ number_format($skor?->skor_d6_rantai_nilai_akses_pasar ?? 0, 2, '.', ',') }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
<div class="col-lg-6 col-sm-12">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Segmentasi Multi Komponen Petani</h5>
<p class="card-subtitle">untuk semua komoditas</p>
</div>
</div>
<div class="card-body">
<table class="table card-table table-border-top-0 table-border-bottom-0 data-table">
<thead>
<tr>
<td class="w-60 ps-0">
<th></th>
<th class="text-end pe-0">Indeks</th>
<th class="text-end pe-0">K1</th>
<th class="text-end pe-0">K2</th>
<th class="text-end pe-0">K3</th>
<th class="text-end pe-0">K4</th>
<th class="text-end pe-0">K5</th>
<th class="text-end pe-0">K6</th>
</tr>
</thead>
<tbody>
@foreach ($segments as $segment)
@php
$skor = $skorMultiKomponen->firstWhere('segmen', $segment['name']);
@endphp
<tr>
<td class="">
<div class="d-flex justify-content-start align-items-center">
<div class="me-2">
<i class="icon-base ti tabler-flag icon-lg text-heading"></i>
<i class="icon-base ti {{ $segment['icon'] }} icon-lg text-{{ $segment['color'] }}"></i>
</div>
<div>
<h6 class="mb-0 fw-normal text-info">Perintis (3.0 - 3.9)</h6>
<!-- <small class="text-body-secondary text-wrap">Berpotensi berkembang menjadi Penggerak dengan dukungan yang tepat</small> -->
<h6 class="mb-0 fw-normal text-{{ $segment['color'] }}">
{{ $segment['name'] }}
<small class="text-body-secondary">({{ $segment['range'] }})</small>
</h6>
</div>
</div>
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Perintis')->value('skor_kesiapan_adopsi'), 2, '.', ',') }}</span>
<span>{{ number_format($skor?->skor_kesiapan_adopsi, 2, '.', ',') }}</span>
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Perintis')->value('skor_d2_sistem_praktik_budidaya'), 2, '.', ',') }}</span>
<span>{{ number_format($skor?->skor_komp_1_d4_k3_motivasi_kapasitas_adopsi, 2, '.', ',') }}</span>
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Perintis')->value('skor_d4_sosial_ekonomi'), 2, '.', ',') }}</span>
<span>{{ number_format($skor?->skor_komp_2_d4_k2_ekonomi_rumah_tangga, 2, '.', ',') }}</span>
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Perintis')->value('skor_d6_rantai_nilai_akses_pasar'), 2, '.', ',') }}</span>
<span>{{ number_format($skor?->skor_komp_3_d2_praktik_budidaya_gap, 2, '.', ',') }}</span>
</td>
</tr>
<tr>
<td class="w-60 ps-0">
<div class="d-flex justify-content-start align-items-center">
<div class="me-2">
<i class="icon-base ti tabler-eye icon-lg text-heading text-success"></i>
</div>
<div>
<h6 class="mb-0 fw-normal text-warning">Pemerhati (2.0 - 2.9)</h6>
<!-- <small class="text-body-secondary text-wrap">Perlu melihat bukti hasil nyata dari Penggerak sebelum mau bergerak</small> -->
</div>
</div>
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Pemerhati')->value('skor_kesiapan_adopsi'), 2, '.', ',') }}</span>
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Pemerhati')->value('skor_d2_sistem_praktik_budidaya'), 2, '.', ',') }}</span>
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Pemerhati')->value('skor_d4_sosial_ekonomi'), 2, '.', ',') }}</span>
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Pemerhati')->value('skor_d6_rantai_nilai_akses_pasar'), 2, '.', ',') }}</span>
</td>
</tr>
<tr>
<td class="w-60 ps-0">
<div class="d-flex justify-content-start align-items-center">
<div class="me-2">
<i class="icon-base ti tabler-seedling icon-lg text-heading"></i>
</div>
<div>
<h6 class="mb-0 fw-normal text-danger">Pemula (1.0 - 1.9)</h6>
<!-- <small class="text-body-secondary text-wrap">Butuh penanganan hambatan dasar terlebih dahulu sebelum intervensi langsung</small> -->
</div>
</div>
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Pemula')->value('skor_kesiapan_adopsi'), 2, '.', ',') }}</span>
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Pemula')->value('skor_d2_sistem_praktik_budidaya'), 2, '.', ',') }}</span>
<span>{{ number_format($skor?->skor_komp_4_d4_k4_keterlibatan_kelembagaan, 2, '.', ',') }}</span>
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Pemula')->value('skor_d4_sosial_ekonomi'), 2, '.', ',') }}</span>
<span>{{ number_format($skor?->skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan, 2, '.', ',') }}</span>
</td>
<td class="text-end pe-0">
<span>{{ number_format($skorMultiDimensi->where('segmen', 'Pemula')->value('skor_d6_rantai_nilai_akses_pasar'), 2, '.', ',') }}</span>
<span>{{ number_format($skor?->skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar, 2, '.', ',') }}</span>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@@ -443,6 +403,13 @@
<th>D2</th>
<th>D4</th>
<th>D6</th>
<th>K1</th>
<th>K2</th>
<th>K3</th>
<th>K4</th>
<th>K5</th>
<th>K6</th>
</tr>
</thead>
<tbody>
@foreach ($petani as $item)
@@ -466,6 +433,12 @@
<td>{{ number_format($item->skor?->skor_d2_sistem_praktik_budidaya, 2, '.', ',') }}</td>
<td>{{ number_format($item->skor?->skor_d4_sosial_ekonomi, 2, '.', ',') }}</td>
<td>{{ number_format($item->skor?->skor_d6_rantai_nilai_akses_pasar, 2, '.', ',') }}</td>
<td>{{ number_format($item->skor?->skor_komp_1_d4_k3_motivasi_kapasitas_adopsi, 2, '.', ',') }}</td>
<td>{{ number_format($item->skor?->skor_komp_2_d4_k2_ekonomi_rumah_tangga, 2, '.', ',') }}</td>
<td>{{ number_format($item->skor?->skor_komp_3_d2_praktik_budidaya_gap, 2, '.', ',') }}</td>
<td>{{ number_format($item->skor?->skor_komp_4_d4_k4_keterlibatan_kelembagaan, 2, '.', ',') }}</td>
<td>{{ number_format($item->skor?->skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan, 2, '.', ',') }}</td>
<td>{{ number_format($item->skor?->skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar, 2, '.', ',') }}</td>
</tr>
@endforeach
</tbody>
@@ -484,6 +457,17 @@
<script>
$(document).ready(function() {
$('.data-table').DataTable({
"paging": false,
"lengthChange": true,
"searching": false,
"ordering": false,
"info": false,
"autoWidth": false,
"responsive": false,
"scrollX": true
});
$("#tabel-petani").DataTable({
"paging": true,
"lengthChange": true,
@@ -491,7 +475,8 @@
"ordering": false,
"info": true,
"autoWidth": false,
"responsive": true,
"responsive": false,
"scrollX": true
})
@@ -401,6 +401,10 @@
Petani
</th>
<th style="font-size:10px;letter-spacing:.7px;text-transform:uppercase">
Komoditas
</th>
<th class="text-center"
style="font-size:10px;letter-spacing:.7px;text-transform:uppercase">
Lahan
@@ -474,19 +478,28 @@
</div>
</td>
<td>
<span
class="badge bg-label-secondary text-secondary"
title="{{ $petani['komoditas'] }}"
>
{{ $petani['komoditas'] }}
</span>
</td>
@foreach ($petani['status'] as $status)
<td class="text-center">
@if ($status['done'])
<span
class="badge rounded-pill bg-label-success text-success"
title="{{ $status['label'] }} sudah diisi"
title="{{ $status['label'] }} lengkap"
>
<i class="ti tabler-check"></i>
</span>
@else
<span
class="badge rounded-pill bg-label-secondary text-secondary"
title="{{ $status['label'] }} belum diisi"
title="{{ $status['label'] }} belum lengkap"
>
<i class="ti tabler-minus"></i>
</span>
@@ -533,7 +546,7 @@
</tr>
@empty
<tr>
<td colspan="7" class="text-center text-muted py-4">
<td colspan="8" class="text-center text-muted py-4">
Belum ada data petani.
</td>
</tr>
+79 -2
View File
@@ -3,6 +3,7 @@
<a href="{{ route('home') }}" class="app-brand-link">
<span class="app-brand-logo demo">
<span>
<!-- AgroMonev -->
<svg width="32" height="24" viewBox="0 0 32 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M16 2.5C16 2.5 5 6.5 5 15.5C5 20.5 11 23.5 16 23.5V2.5Z" fill="#28a745" />
<path opacity="0.6" d="M16 2.5C16 2.5 27 6.5 27 15.5C27 20.5 21 23.5 16 23.5V2.5Z" fill="#28a745" />
@@ -10,6 +11,24 @@
<path d="M15 15L27 4" stroke="#ED1C24" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
<circle cx="27" cy="4" r="2.5" fill="#ED1C24" />
</svg>
<!-- AgroPro -->
<!-- <svg width="32" height="24" viewBox="0 0 32 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.25" d="M16 2.5C16 2.5 5 6.5 5 15.5C5 20.5 11 23.5 16 23.5V2.5Z" fill="#28A745"/>
<path opacity="0.5" d="M16 2.5C16 2.5 27 6.5 27 15.5C27 20.5 21 23.5 16 23.5V2.5Z" fill="#28A745"/>
<rect x="5.5" y="5.5" width="21" height="15" rx="2.5" fill="white" stroke="#0054A6" stroke-width="1.5"/>
<rect x="11" y="3.5" width="10" height="4" rx="2" fill="#0054A6"/>
<circle cx="9.5" cy="10" r="1.7" fill="#28A745"/>
<path d="M8.7 10L9.3 10.6L10.4 9.3" stroke="white" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 10H23" stroke="#D9E2EC" stroke-width="2" stroke-linecap="round"/>
<path d="M13 10H22" stroke="#0054A6" stroke-width="2" stroke-linecap="round"/>
<circle cx="9.5" cy="14" r="1.7" fill="#28A745"/>
<path d="M8.7 14L9.3 14.6L10.4 13.3" stroke="white" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 14H23" stroke="#D9E2EC" stroke-width="2" stroke-linecap="round"/>
<path d="M13 14H19.5" stroke="#F4B400" stroke-width="2" stroke-linecap="round"/>
<circle cx="9.5" cy="18" r="1.7" fill="white" stroke="#ED1C24" stroke-width="1.3"/>
<path d="M13 18H23" stroke="#D9E2EC" stroke-width="2" stroke-linecap="round"/>
<path d="M13 18H17" stroke="#ED1C24" stroke-width="2" stroke-linecap="round"/>
</svg> -->
</span>
</span>
@@ -36,7 +55,7 @@
<li class="menu-item active open">
<a href="javascript:void(0);" class="menu-link menu-toggle">
<i class="menu-icon icon-base ti tabler-smart-home"></i>
<div data-i18n="Dashboards">Dashboards</div>
<div data-i18n="Dashboard">Dashboard</div>
</a>
@if(in_array($userRole, ['super_admin', 'admin']) && env('APP_NAME') == 'AgroMonev')
@@ -49,6 +68,30 @@
</ul>
@endif
@if(in_array($userRole, ['super_admin', 'admin']) && env('APP_NAME') == 'AgroMonev')
<ul class="menu-sub">
<li class="menu-item {{ request()->routeIs('dashboard.indeks-petani') ? 'active' : '' }}">
<a href="{{ route('dashboard.indeks-petani') }}" class="menu-link">
<div data-i18n="Indeks Petani">Indeks Petani</div>
</a>
</li>
</ul>
<ul class="menu-sub">
<li class="menu-item {{ request()->routeIs('dashboard.demografi-petani') ? 'active' : '' }}">
<a href="{{ route('dashboard.demografi-petani') }}" class="menu-link">
<div data-i18n="Demografi Petani">Demografi Petani</div>
</a>
</li>
</ul>
<ul class="menu-sub">
<li class="menu-item {{ request()->routeIs('dashboard.indikator-pertanian') ? 'active' : '' }}">
<a href="{{ route('dashboard.indikator-pertanian') }}" class="menu-link">
<div data-i18n="Indikator Pertanian">Indikator Pertanian</div>
</a>
</li>
</ul>
@endif
@if(in_array($userRole, ['super_admin', 'admin', 'fasilitator']) && env('APP_NAME') == 'AgroMonev')
<ul class="menu-sub">
<li class="menu-item {{ request()->routeIs('dashboard.survey-petani') ? 'active' : '' }}">
@@ -57,12 +100,21 @@
</a>
</li>
</ul>
<ul class="menu-sub">
<!-- <ul class="menu-sub">
<li class="menu-item {{ request()->routeIs('dashboard.petani') ? 'active' : '' }}">
<a href="{{ route('dashboard.petani') }}" class="menu-link">
<div data-i18n="Profil Petani">Profil Petani</div>
</a>
</li>
</ul> -->
@endif
@if(in_array($userRole, ['super_admin', 'admin']) && env('APP_NAME') == 'AgroMonev-xx')
<ul class="menu-sub">
<li class="menu-item {{ request()->routeIs('dashboard.bumdes') ? 'active' : '' }}">
<a href="{{ route('dashboard.bumdes') }}" class="menu-link">
<div data-i18n="Profil Pengurus BUMDes">Profil Pengurus BUMDes</div>
</a>
</li>
</ul>
@endif
</li>
@@ -79,6 +131,31 @@
</a>
</li>
@endif
@if(in_array($userRole, ['super_admin', 'admin']) && env('APP_NAME') == 'AgroMonev')
<li class="menu-header small">
<span class="menu-header-text" data-i18n="Data BUMDes">Data BUMDes</span>
</li>
<li class="menu-item {{ request()->routeIs('bumdes.*') ? 'active' : '' }}">
<a href="{{ route('bumdes.index') }}" class="menu-link">
<i class="menu-icon icon-base ti tabler-file-description"></i>
<div data-i18n="Profil Pengurus BUMDes">Profil Pengurus BUMDes</div>
</a>
</li>
@endif
@if(in_array($userRole, ['super_admin', 'admin']) && env('APP_NAME') == 'AgroMonev')
<li class="menu-header small">
<span class="menu-header-text" data-i18n="Data Koperasi">Data Koperasi</span>
</li>
<li class="menu-item {{ request()->routeIs('koperasi.*') ? 'active' : '' }}">
<a href="{{ route('koperasi.pengurus.index') }}" class="menu-link">
<i class="menu-icon icon-base ti tabler-file-description"></i>
<div data-i18n="Profil Pengurus Koperasi">Profil Pengurus Koperasi</div>
</a>
</li>
@endif
@if(in_array($userRole, ['super_admin', 'admin']) && env('APP_NAME') == 'AgroPro')
<!-- Ekosistem -->
@@ -207,6 +207,37 @@
</div>
</div>
<h5 class="pb-3 border-bottom mb-4 mt-4">KEPERCAYAAN & PERSEPSI RISIKO</h5>
<div class="row">
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label d-block mb-2" for="tingkat-kepercayaan">Seberapa percaya Bapak/Ibu untuk menjual ke pembeli atau pasar baru, di luar pembeli yang biasa?</label>
@foreach($tingkatKepercayaanOptions as $option)
<div class="form-check mb-2">
<input class="form-check-input @error('tingkat_kepercayaan_pembeli_baru') is-invalid @enderror"
type="radio"
id="tingkat-kepercayaan-{{ $option['value'] }}"
name="tingkat_kepercayaan_pembeli_baru"
value="{{ $option['value'] }}"
{{ (string) old('tingkat_kepercayaan_pembeli_baru', $aksesPasar?->tingkat_kepercayaan_pembeli_baru) === (string) $option['value'] ? 'checked' : '' }}>
<label class="form-check-label" for="tingkat-kepercayaan-{{ $option['value'] }}">
{{ $option['label'] }}
</label>
</div>
@endforeach
@error('tingkat_kepercayaan_pembeli_baru')<div class="invalid-feedback d-block">{{ $message }}</div>@enderror
</div>
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label mb-2" for="alasan-kepercayaan">Apa yang membuat Bapak/Ibu ragu atau yakin untuk mencoba pembeli baru?</label>
<textarea class="form-control @error('alasan_kepercayaan_pembeli_baru') is-invalid @enderror"
id="alasan-kepercayaan"
name="alasan_kepercayaan_pembeli_baru"
rows="4"
placeholder="Tuliskan alasannya di sini...">{{ old('alasan_kepercayaan_pembeli_baru', $aksesPasar?->alasan_kepercayaan_pembeli_baru) }}</textarea>
@error('alasan_kepercayaan_pembeli_baru')<div class="invalid-feedback">{{ $message }}</div>@enderror
</div>
</div>
<div class="mt-4 text-end">
<button type="submit" class="btn btn-success">
<i class="icon-base ti tabler-device-floppy me-1_5 icon-sm"></i>Simpan Akses Pasar
@@ -15,8 +15,8 @@
<div class="w-25">
<x-summary-card
title="Lahan & Tanaman"
count="{{ number_format(($lahan?->sum('luas_lahan') ?? 0) / 10000, 2, ',', '.') }} ha"
subtitle="{{ $tanaman?->count() }} tanaman dari {{ $komoditas?->count() }} komoditas utama"
count="{{ number_format(($totalLuasLahan ?? 0) / 10000, 2, ',', '.') }} ha"
subtitle="{{ $totalTanaman ?? 0 }} tanaman dari {{ $komoditas?->count() }} komoditas utama"
icon="trees"
color="success"
/>
@@ -1598,42 +1598,66 @@
action="{{ route('petani.survey-produktivitas.store', $petani->id) }}"
class="form-repeater">
@csrf
<input type="hidden" name="redirect_fragment" value="section-kehilangan-hasil">
<input type="hidden" name="redirect_fragment" value="section-produktivitas">
@php
$produktivitasRows = old('produktivitas_komoditas');
if (!is_array($produktivitasRows) || count($produktivitasRows) === 0) {
$produktivitasRows = [
[
'komoditas_id' => null,
'hasil_panen_3_musim' => null,
'satuan' => null,
],
];
}
@endphp
<div data-repeater-list="produktivitas_komoditas">
@foreach ($produktivitasRows as $index => $produktivitasRow)
<div data-repeater-item>
<div class="row align-items-end">
<div class="col-md-4 mb-3">
<label class="form-label">Komoditas</label>
<select name="komoditas_id" class="form-select">
<select name="komoditas_id" class="form-select @error('produktivitas_komoditas.' . $index . '.komoditas_id') is-invalid @enderror">
<option value="">Pilih Komoditas</option>
@foreach ($komoditas as $item)
<option value="{{ $item->id }}">{{ $item->nama }}</option>
<option value="{{ $item->id }}" @selected((string) data_get($produktivitasRow, 'komoditas_id') === (string) $item->id)>{{ $item->nama }}</option>
@endforeach
</select>
@error('produktivitas_komoditas.' . $index . '.komoditas_id')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div>
<div class="col-md-3 mb-3">
<label class="form-label">Hasil</label>
<div class="input-group">
<input type="number" name="hasil_panen_3_musim" class="form-control" step="0.01" min="0">
<input type="number" name="hasil_panen_3_musim" class="form-control @error('produktivitas_komoditas.' . $index . '.hasil_panen_3_musim') is-invalid @enderror" step="0.01" min="0" value="{{ data_get($produktivitasRow, 'hasil_panen_3_musim') }}">
<span class="input-group-text">Kg</span>
</div>
@error('produktivitas_komoditas.' . $index . '.hasil_panen_3_musim')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div>
<div class="col-md-3 mb-3">
<label class="form-label d-block">Satuan</label>
<label class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="satuan" value="ha_tahun">
<input class="form-check-input @error('produktivitas_komoditas.' . $index . '.satuan') is-invalid @enderror" type="radio" name="satuan" value="ha_tahun" @checked(data_get($produktivitasRow, 'satuan') === 'ha_tahun')>
<span class="form-check-label">Ha/Tahun</span>
</label>
<label class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="satuan" value="pohon_tahun">
<input class="form-check-input @error('produktivitas_komoditas.' . $index . '.satuan') is-invalid @enderror" type="radio" name="satuan" value="pohon_tahun" @checked(data_get($produktivitasRow, 'satuan') === 'pohon_tahun')>
<span class="form-check-label">Pohon/Tahun</span>
</label>
@error('produktivitas_komoditas.' . $index . '.satuan')
<div class="invalid-feedback d-block">{{ $message }}</div>
@enderror
</div>
<div class="col-md-2 mb-3">
@@ -1645,6 +1669,7 @@
</div>
<hr>
</div>
@endforeach
</div>
<div class="mt-4 d-flex justify-content-between">
@@ -1064,6 +1064,9 @@
const pernahJualHargaLebihTinggiPernah = @json($pernahJualHargaLebihTinggiPernahValue);
const panutanPetaniSuksesTidakAda = String(@json($panutanPetaniSuksesTidakAdaValue));
const terdaftarKelompokTaniYa = @json($terdaftarKelompokTaniYaValue);
const keaktifanPertemuanKelompokLowest = String(@json($keaktifanPertemuanKelompokLowestValue));
const peranDalamKelompokLowest = String(@json($peranDalamKelompokLowestValue));
const manfaatKelompokTaniLowest = String(@json($manfaatKelompokTaniLowestValue));
const statusRekeningTidakPunya = String(@json($statusRekeningTidakPunyaValue));
const pengalamanKreditPerluAlasan = @json($pengalamanKreditPerluAlasanValues).map(String);
const alasanKreditLainnya = @json($alasanKreditLainnyaValue);
@@ -1475,12 +1478,17 @@
function syncTerdaftarKelompokFields() {
const isTerdaftar = $('input[name="terdaftar_kelompok_tani"]:checked').val() === terdaftarKelompokTaniYa;
const $fields = $('#nama-kelompok-tani, #lama-bergabung-kelompok, #kedekatan-sesama-anggota');
const $fields = $('#nama-kelompok-tani, #lama-bergabung-kelompok, #kedekatan-sesama-anggota, #topik-biasa-dibahas')
.add('input[name="keaktifan_pertemuan_kelompok"], input[name="peran_dalam_kelompok"], input[name="manfaat_kelompok_tani"]');
$fields.prop('disabled', !isTerdaftar);
if (!isTerdaftar) {
$fields.val('');
$('#nama-kelompok-tani, #lama-bergabung-kelompok, #topik-biasa-dibahas').val('');
$('input[name="keaktifan_pertemuan_kelompok"][value="' + keaktifanPertemuanKelompokLowest + '"]').prop('checked', true);
$('input[name="peran_dalam_kelompok"][value="' + peranDalamKelompokLowest + '"]').prop('checked', true);
$('input[name="manfaat_kelompok_tani"][value="' + manfaatKelompokTaniLowest + '"]').prop('checked', true);
$('#kedekatan-sesama-anggota').val('');
$('#kedekatan-sesama-anggota').trigger('change.select2');
}
}
@@ -1060,6 +1060,9 @@
const pernahJualHargaLebihTinggiPernah = @json($pernahJualHargaLebihTinggiPernahValue);
const panutanPetaniSuksesTidakAda = String(@json($panutanPetaniSuksesTidakAdaValue));
const terdaftarKelompokTaniYa = @json($terdaftarKelompokTaniYaValue);
const keaktifanPertemuanKelompokLowest = String(@json($keaktifanPertemuanKelompokLowestValue));
const peranDalamKelompokLowest = String(@json($peranDalamKelompokLowestValue));
const manfaatKelompokTaniLowest = String(@json($manfaatKelompokTaniLowestValue));
const statusRekeningTidakPunya = String(@json($statusRekeningTidakPunyaValue));
const pengalamanKreditPerluAlasan = @json($pengalamanKreditPerluAlasanValues).map(String);
const alasanKreditLainnya = @json($alasanKreditLainnyaValue);
@@ -1471,12 +1474,17 @@
function syncTerdaftarKelompokFields() {
const isTerdaftar = $('input[name="terdaftar_kelompok_tani"]:checked').val() === terdaftarKelompokTaniYa;
const $fields = $('#nama-kelompok-tani, #lama-bergabung-kelompok, #kedekatan-sesama-anggota');
const $fields = $('#nama-kelompok-tani, #lama-bergabung-kelompok, #kedekatan-sesama-anggota, #topik-biasa-dibahas')
.add('input[name="keaktifan_pertemuan_kelompok"], input[name="peran_dalam_kelompok"], input[name="manfaat_kelompok_tani"]');
$fields.prop('disabled', !isTerdaftar);
if (!isTerdaftar) {
$fields.val('');
$('#nama-kelompok-tani, #lama-bergabung-kelompok, #topik-biasa-dibahas').val('');
$('input[name="keaktifan_pertemuan_kelompok"][value="' + keaktifanPertemuanKelompokLowest + '"]').prop('checked', true);
$('input[name="peran_dalam_kelompok"][value="' + peranDalamKelompokLowest + '"]').prop('checked', true);
$('input[name="manfaat_kelompok_tani"][value="' + manfaatKelompokTaniLowest + '"]').prop('checked', true);
$('#kedekatan-sesama-anggota').val('');
$('#kedekatan-sesama-anggota').trigger('change.select2');
}
}
File diff suppressed because it is too large Load Diff
+31
View File
@@ -45,7 +45,23 @@ Route::middleware('auth')
// Dashboard
Route::get('/dashboard/petani', [App\Http\Controllers\Web\Dashboard\ProfilPetaniController::class, 'index'])->name('dashboard.petani');
Route::get('/dashboard/bumdes', [App\Http\Controllers\Web\Bumdes\PengurusBumdesController::class, 'index'])->name('dashboard.bumdes');
Route::get('/dashboard/petani/demografi', [App\Http\Controllers\Web\Dashboard\DemografiPetaniController::class, 'index'])->name('dashboard.demografi-petani');
Route::get('/dashboard/petani/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/indeks', [App\Http\Controllers\Web\Dashboard\IndeksPetaniController::class, 'index'])->name('dashboard.indeks-petani');
Route::get('/dashboard/petani/indeks/summary', [App\Http\Controllers\Web\Dashboard\IndeksPetaniController::class, 'summary'])->name('dashboard.indeks-petani.summary');
Route::get('/dashboard/petani/indeks/segmen', [App\Http\Controllers\Web\Dashboard\IndeksPetaniController::class, 'segmen'])->name('dashboard.indeks-petani.segmen');
Route::get('/dashboard/petani/indeks/segmen/komoditas', [App\Http\Controllers\Web\Dashboard\IndeksPetaniController::class, 'segmenByKomoditas'])->name('dashboard.indeks-petani.segmen.komoditas');
Route::get('/dashboard/petani/indeks/dimensi', [App\Http\Controllers\Web\Dashboard\IndeksPetaniController::class, 'dimensi'])->name('dashboard.indeks-petani.dimensi');
Route::get('/dashboard/petani/indeks/komponen', [App\Http\Controllers\Web\Dashboard\IndeksPetaniController::class, 'komponen'])->name('dashboard.indeks-petani.komponen');
Route::get('/dashboard/petani/indeks/map', [App\Http\Controllers\Web\Dashboard\IndeksPetaniController::class, 'map'])->name('dashboard.indeks-petani.map');
Route::get('/dashboard/petani/indeks/desa', [App\Http\Controllers\Web\Dashboard\IndeksPetaniController::class, 'desa'])->name('dashboard.indeks-petani.desa');
Route::get('/dashboard/petani/indeks/data', [App\Http\Controllers\Web\Dashboard\IndeksPetaniController::class, 'data'])->name('dashboard.indeks-petani.data');
Route::get('/dashboard/profil/petani/{id}',[App\Http\Controllers\Web\Produktivitas\ProfilPetaniController::class, 'index'])->name('dashboard.profil-petani');
Route::get('/user-profile', [App\Http\Controllers\Web\Konfigurasi\ProfileController::class, 'index'])->name('userProfile.index');
Route::get('/user-profile/security', [App\Http\Controllers\Web\Konfigurasi\ProfileController::class, 'security'])->name('userProfile.security');
@@ -223,4 +239,19 @@ Route::middleware('auth')
Route::resource('petani.akses-pasar', App\Http\Controllers\Web\Produktivitas\AksesPasarController::class)->whereNumber('petani');
Route::resource('petani.skor', App\Http\Controllers\Web\Produktivitas\SkorPetaniController::class)->only(['index']);
// BUMDES routes...
Route::get('bumdes/{bumdes}/kelembagaan-aktor', [App\Http\Controllers\Web\Bumdes\PengurusBumdesController::class, 'kelembagaanAktor'])->name('bumdes.kelembagaan-aktor.index');
Route::resource('bumdes', App\Http\Controllers\Web\Bumdes\PengurusBumdesController::class);
Route::delete('/koperasi/pengurus/{id}', [\App\Http\Controllers\Web\Koperasi\PengurusKoperasiController::class, 'destroy'])->name('koperasi.pengurus.destroy');
// --- KOPERASI GROUP ---
Route::prefix('koperasi')->name('koperasi.')->group(function () {
Route::resource('pengurus', App\Http\Controllers\Web\Koperasi\PengurusKoperasiController::class);
Route::get('pengurus/{id}/survey', [App\Http\Controllers\Web\Koperasi\PengurusKoperasiController::class, 'survey'])->name('pengurus.survey');
Route::post('pengurus/{id}/survey', [App\Http\Controllers\Web\Koperasi\PengurusKoperasiController::class, 'surveyStore'])->name('pengurus.survey.store');
});
});