Compare commits
40
Commits
607533a327
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53913bebe9 | ||
|
|
f455f8b878 | ||
|
|
f1112e3b68 | ||
|
|
c1370a26ca | ||
|
|
013cb7b728 | ||
|
|
4ab745cfe5 | ||
|
|
c66eed56e2 | ||
|
|
8be0906a2e | ||
|
|
fcd79b7cfe | ||
|
|
bea9be8a39 | ||
|
|
5220b10de7 | ||
|
|
24725e3df1 | ||
|
|
60d32a88e9 | ||
|
|
4370d3c3d0 | ||
|
|
7699430301 | ||
|
|
df2b5a1312 | ||
|
|
2e8271d27a | ||
|
|
9b5c0a7309 | ||
|
|
9d56b67998 | ||
|
|
f95f43999a | ||
|
|
3e172cf915 | ||
|
|
3a9b3dbd7b | ||
|
|
80f0056efe | ||
|
|
aef27704f2 | ||
|
|
f49bb0c2db | ||
|
|
8a2c0292ac | ||
|
|
9ea324e553 | ||
|
|
94c3288ea7 | ||
|
|
8539bdcec4 | ||
|
|
77de648ded | ||
|
|
393cf0d0d1 | ||
|
|
7fdf9b400d | ||
|
|
be4d46004e | ||
|
|
fef5358402 | ||
|
|
02eed6da34 | ||
|
|
94d3f2a582 | ||
|
|
471ca5ac0e | ||
|
|
8ec8f0c9d6 | ||
|
|
4d7d16e9f6 | ||
|
|
1155b9b95b |
@@ -63,3 +63,9 @@ AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
|
||||
# Optional CA bundle path for Yahoo Finance when PHP has no system CA configured.
|
||||
YAHOO_FINANCE_CA_BUNDLE=
|
||||
|
||||
|
||||
ALPHA_VANTAGE_API_KEY=
|
||||
|
||||
@@ -8,7 +8,7 @@ enum FrekuensiPemangkasanPendamping: string
|
||||
case TIDAK_RUTIN = 'Memangkas, tapi tidak rutin dan tidak tahu tekniknya';
|
||||
case SATU_KALI_TAHUN = 'Memangkas 1 kali/tahun, tahu jenis tekniknya';
|
||||
case DUA_KALI_TAHUN = 'Memangkas 2 kali/tahun, tahu jenis tekniknya';
|
||||
case LEBIH_DUA_KALI_TAHUN = 'Memangkas lebih dari 2 kali/tahun dengan teknik yang tepat dan pohon penaung juga dipangkas rutin';
|
||||
case LEBIH_DUA_KALI_TAHUN = 'Memangkas lebih dari 2 kali/tahun dengan teknik yang tepat, dan pohon penaung juga dipangkas rutin';
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
@@ -17,7 +17,7 @@ enum FrekuensiPemangkasanPendamping: string
|
||||
self::TIDAK_RUTIN => 'Memangkas, tapi tidak rutin dan tidak tahu tekniknya',
|
||||
self::SATU_KALI_TAHUN => 'Memangkas 1 kali/tahun, tahu jenis tekniknya',
|
||||
self::DUA_KALI_TAHUN => 'Memangkas 2 kali/tahun, tahu jenis tekniknya',
|
||||
self::LEBIH_DUA_KALI_TAHUN => 'Memangkas lebih dari 2 kali/tahun dengan teknik yang tepat dan pohon penaung juga dipangkas rutin',
|
||||
self::LEBIH_DUA_KALI_TAHUN => 'Memangkas lebih dari 2 kali/tahun dengan teknik yang tepat, dan pohon penaung juga dipangkas rutin',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ enum FrekuensiPemangkasanUtama: int
|
||||
self::TIDAK_RUTIN => 'Memangkas, tapi tidak rutin dan tidak tahu tekniknya',
|
||||
self::SATU_KALI_TAHUN => 'Memangkas 1 kali/tahun, tahu jenis tekniknya',
|
||||
self::DUA_KALI_TAHUN => 'Memangkas 2 kali/tahun, tahu jenis tekniknya',
|
||||
self::LEBIH_DUA_KALI_TAHUN => 'Memangkas lebih dari 2 kali/tahun dengan teknik yang tepat dan pohon penaung juga dipangkas rutin',
|
||||
self::LEBIH_DUA_KALI_TAHUN => 'Memangkas lebih dari 2 kali/tahun dengan teknik yang tepat, dan pohon penaung juga dipangkas rutin',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ enum MetodePenangananHamaPenyakit: int
|
||||
self::TIDAK_ADA => 'Tidak ada penanganan',
|
||||
self::CARA_TRADISIONAL => 'Cara tradisional (doa/ritual)',
|
||||
self::PESTISIDA_SANITASI => 'Pestisida atau sanitasi',
|
||||
self::KOMBINASI => 'Kombinasi pestisada dan sanitasi',
|
||||
self::PHT => 'Pengendalian hama terpadu(PHT)',
|
||||
self::KOMBINASI => 'Kombinasi pestisida dan sanitasi',
|
||||
self::PHT => 'Pengendalian hama terpadu (PHT)',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Models\Pertanian\Petani;
|
||||
use App\Models\Pertanian\Komoditas;
|
||||
use App\Models\Pertanian\Lahan;
|
||||
use App\Models\Pertanian\PendapatanKomoditas;
|
||||
use App\Models\Pertanian\SosialEkonomi;
|
||||
|
||||
class DemografiPetaniController extends Controller
|
||||
{
|
||||
@@ -20,7 +21,56 @@ class DemografiPetaniController extends Controller
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$petaniCounts = Petani::selectRaw("
|
||||
return redirect()->route('dashboard.karakteristik-petani', array_merge($request->query(), [
|
||||
'tab' => 'demografi-petani',
|
||||
]));
|
||||
}
|
||||
|
||||
public function karakteristik(Request $request)
|
||||
{
|
||||
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$activeTab = $request->query('tab') === 'indeks-petani'
|
||||
? 'indeks-petani'
|
||||
: 'demografi-petani';
|
||||
|
||||
$demografiData = $this->contentData($request);
|
||||
$indeksData = app(IndeksPetaniController::class)->contentData($request);
|
||||
|
||||
$tableFilters = [
|
||||
'gender' => ['Gender', ['1' => 'Laki-laki', '0' => 'Perempuan']],
|
||||
'umur' => ['Kelompok Umur', ['kurang_30' => '< 30 tahun', '30_39' => '30–39 tahun', '40_49' => '40–49 tahun', '50_59' => '50–59 tahun', '60_keatas' => '≥ 60 tahun']],
|
||||
'pendidikan' => ['Pendidikan', ['tidak_sekolah' => 'Tidak Sekolah', 'sd' => 'SD', 'smp' => 'SMP', 'sma' => 'SMA', 'diploma' => 'Diploma', 's1_keatas' => 'Sarjana ke atas']],
|
||||
'jumlah_tanggungan' => ['Jumlah Tanggungan', ['0' => '0 orang', '1_2' => '1–2 orang', '3_4' => '3–4 orang', '5_keatas' => '≥ 5 orang']],
|
||||
'kelompok_tani' => ['Keanggotaan Kelompok Tani', ['terdaftar' => 'Terdaftar', 'tidak_terdaftar' => 'Tidak Terdaftar', 'belum_terisi' => 'Belum Terisi']],
|
||||
'komoditas' => ['Komoditas', $indeksData['komoditas']->pluck('nama', 'id')->all()],
|
||||
'luas_lahan' => ['Luas Lahan', ['kurang_05' => '< 0,5 ha', '05_1' => '0,5–1 ha', '1_2' => '> 1–2 ha', 'lebih_2' => '> 2 ha']],
|
||||
'status_kepemilikan_lahan' => ['Status Kepemilikan Lahan', ['sendiri' => 'Milik Pribadi', 'sewa' => 'Sewa', 'adat' => 'Tanah Adat']],
|
||||
'penghasilan' => ['Rentang Pendapatan', ['kurang_1' => '< Rp1 juta', '1_3' => 'Rp1–3 juta', '3_5' => '> Rp3–5 juta', '5_10' => '> Rp5–10 juta', 'lebih_10' => '> Rp10 juta']],
|
||||
];
|
||||
|
||||
return view('dashboard.karakteristik-petani', [
|
||||
'activeTab' => $activeTab,
|
||||
'tableFilters' => $tableFilters,
|
||||
'dashboardFilters' => $request->only([
|
||||
'provinsi_id',
|
||||
'kabupatenKota_id',
|
||||
'kecamatan_id',
|
||||
'desaKelurahan_id',
|
||||
'komoditas_id',
|
||||
'segmen',
|
||||
]),
|
||||
'indeksFilterAction' => route('dashboard.karakteristik-petani'),
|
||||
] + $demografiData + $indeksData);
|
||||
}
|
||||
|
||||
public function contentData(Request $request): array
|
||||
{
|
||||
$filteredPetaniIds = $this->filteredPetaniIdsQuery($request);
|
||||
|
||||
$petaniCounts = Petani::whereIn('petani.id', clone $filteredPetaniIds)->selectRaw("
|
||||
COUNT(*) as total,
|
||||
COUNT(*) FILTER (WHERE jenis_kelamin = true) as laki,
|
||||
COUNT(*) FILTER (WHERE jenis_kelamin = false) as perempuan
|
||||
@@ -30,14 +80,18 @@ class DemografiPetaniController extends Controller
|
||||
$petaniLakilaki = $petaniCounts->laki;
|
||||
$petaniPerempuan = $petaniCounts->perempuan;
|
||||
|
||||
$totalKomoditas = Petani::whereNotNull('komoditas_id')
|
||||
->distinct()
|
||||
->count('komoditas_id');
|
||||
$totalKomoditas = Komoditas::query()
|
||||
->where('is_utama', true)
|
||||
->whereHas('petani', fn ($query) => $query->whereIn('petani.id', clone $filteredPetaniIds))
|
||||
->count();
|
||||
|
||||
$totalLuasLahan = Lahan::has('petani')->sum('luas_lahan');
|
||||
$totalLuasLahan = Lahan::has('petani')
|
||||
->whereIn('petani_id', clone $filteredPetaniIds)
|
||||
->sum('luas_lahan');
|
||||
$ratarataLuasLahan = $totalLuasLahan && $totalPetani ? $totalLuasLahan / $totalPetani : 0;
|
||||
|
||||
$umurCounts = Petani::whereNotNull('tanggal_lahir')->selectRaw("
|
||||
$umurCounts = Petani::whereIn('petani.id', clone $filteredPetaniIds)
|
||||
->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,
|
||||
@@ -45,7 +99,8 @@ class DemografiPetaniController extends Controller
|
||||
COUNT(*) FILTER (WHERE EXTRACT(YEAR FROM AGE(CURRENT_DATE, tanggal_lahir)) >= 60) as umur_60_keatas
|
||||
")->first();
|
||||
|
||||
$pendidikanCounts = Petani::whereNotNull('pendidikan_terakhir')
|
||||
$pendidikanCounts = Petani::whereIn('petani.id', clone $filteredPetaniIds)
|
||||
->whereNotNull('pendidikan_terakhir')
|
||||
->select('pendidikan_terakhir', DB::raw('COUNT(*) as total'))
|
||||
->groupBy('pendidikan_terakhir')
|
||||
->pluck('total', 'pendidikan_terakhir');
|
||||
@@ -59,20 +114,71 @@ class DemografiPetaniController extends Controller
|
||||
's1_keatas' => (int) ($pendidikanCounts['06_sarjana'] ?? 0) + (int) ($pendidikanCounts['07_magister'] ?? 0),
|
||||
];
|
||||
|
||||
$jumlahTanggunganBuckets = SosialEkonomi::query()
|
||||
->whereIn('petani_id', clone $filteredPetaniIds)
|
||||
->whereNotNull('jumlah_tanggungan')
|
||||
->selectRaw("
|
||||
COUNT(DISTINCT petani_id) FILTER (WHERE jumlah_tanggungan = 0) as tanggungan_0,
|
||||
COUNT(DISTINCT petani_id) FILTER (WHERE jumlah_tanggungan BETWEEN 1 AND 2) as tanggungan_1_2,
|
||||
COUNT(DISTINCT petani_id) FILTER (WHERE jumlah_tanggungan BETWEEN 3 AND 4) as tanggungan_3_4,
|
||||
COUNT(DISTINCT petani_id) FILTER (WHERE jumlah_tanggungan >= 5) as tanggungan_5_keatas
|
||||
")
|
||||
->first();
|
||||
|
||||
$jumlahTanggungan = [
|
||||
'0' => (int) ($jumlahTanggunganBuckets->tanggungan_0 ?? 0),
|
||||
'1_2' => (int) ($jumlahTanggunganBuckets->tanggungan_1_2 ?? 0),
|
||||
'3_4' => (int) ($jumlahTanggunganBuckets->tanggungan_3_4 ?? 0),
|
||||
'5_keatas' => (int) ($jumlahTanggunganBuckets->tanggungan_5_keatas ?? 0),
|
||||
];
|
||||
|
||||
$kelompokTaniBuckets = Petani::query()
|
||||
->whereIn('petani.id', clone $filteredPetaniIds)
|
||||
->leftJoin('sosial_ekonomi', function ($join) {
|
||||
$join->on('sosial_ekonomi.petani_id', '=', 'petani.id')
|
||||
->whereNull('sosial_ekonomi.deleted_at');
|
||||
})
|
||||
->selectRaw("
|
||||
COUNT(DISTINCT petani.id) FILTER (WHERE sosial_ekonomi.terdaftar_kelompok_tani = 'ya') as terdaftar,
|
||||
COUNT(DISTINCT petani.id) FILTER (WHERE sosial_ekonomi.terdaftar_kelompok_tani = 'tidak') as tidak_terdaftar,
|
||||
COUNT(DISTINCT petani.id) FILTER (WHERE sosial_ekonomi.terdaftar_kelompok_tani IS NULL OR sosial_ekonomi.terdaftar_kelompok_tani NOT IN ('ya', 'tidak')) as belum_terisi
|
||||
")
|
||||
->first();
|
||||
|
||||
$kelompokTani = [
|
||||
'terdaftar' => (int) ($kelompokTaniBuckets->terdaftar ?? 0),
|
||||
'tidak_terdaftar' => (int) ($kelompokTaniBuckets->tidak_terdaftar ?? 0),
|
||||
'belum_terisi' => (int) ($kelompokTaniBuckets->belum_terisi ?? 0),
|
||||
];
|
||||
|
||||
$desaSasaranRows = Petani::query()
|
||||
->leftJoin('master_desa_kelurahan as desa', 'petani.desa_kelurahan_id', '=', 'desa.id')
|
||||
->whereIn('petani.id', clone $filteredPetaniIds)
|
||||
->selectRaw('petani.desa_kelurahan_id as desa_id')
|
||||
->selectRaw("COALESCE(desa.nama, 'Tidak diketahui') as desa")
|
||||
->selectRaw('COUNT(*) as total')
|
||||
->groupBy('petani.desa_kelurahan_id', 'desa.nama')
|
||||
->orderByDesc('total')
|
||||
->orderBy('desa')
|
||||
->get();
|
||||
|
||||
$komoditasUtama = Komoditas::query()
|
||||
->whereHas('petani')
|
||||
->withCount('petani')
|
||||
->whereHas('petani', fn ($query) => $query->whereIn('petani.id', clone $filteredPetaniIds))
|
||||
->withCount(['petani' => fn ($query) => $query->whereIn('petani.id', clone $filteredPetaniIds)])
|
||||
->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'),
|
||||
Lahan::query()
|
||||
->whereIn('petani_id', clone $filteredPetaniIds)
|
||||
->select('petani_id')
|
||||
->selectRaw('luas_lahan'),
|
||||
'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
|
||||
COUNT(DISTINCT petani_id) FILTER (WHERE luas_lahan < 0.5) as kurang_05,
|
||||
COUNT(DISTINCT petani_id) FILTER (WHERE luas_lahan >= 0.5 AND luas_lahan <= 1) as luas_05_1,
|
||||
COUNT(DISTINCT petani_id) FILTER (WHERE luas_lahan > 1 AND luas_lahan <= 2) as luas_1_2,
|
||||
COUNT(DISTINCT petani_id) FILTER (WHERE luas_lahan > 2) as lebih_2
|
||||
")->first();
|
||||
|
||||
$luasLahan = [
|
||||
@@ -82,14 +188,27 @@ class DemografiPetaniController extends Controller
|
||||
'lebih_2' => $luasLahanBuckets->lebih_2,
|
||||
];
|
||||
|
||||
$statusKepemilikanLahanRows = Lahan::query()
|
||||
->whereIn('petani_id', clone $filteredPetaniIds)
|
||||
->whereNotNull('status_kepemilikan')
|
||||
->selectRaw('status_kepemilikan as status')
|
||||
->selectRaw('COUNT(DISTINCT petani_id) as total')
|
||||
->groupBy('status_kepemilikan')
|
||||
->orderByDesc('total')
|
||||
->get();
|
||||
|
||||
$pendapatanPerPetaniQuery = PendapatanKomoditas::query()
|
||||
->join('sosial_ekonomi', 'sosial_ekonomi.id', '=', 'pendapatan_komoditas.sosial_ekonomi_id')
|
||||
->whereNotNull('sosial_ekonomi.petani_id')
|
||||
->whereIn('sosial_ekonomi.petani_id', clone $filteredPetaniIds)
|
||||
->groupBy('sosial_ekonomi.petani_id')
|
||||
->selectRaw('SUM(pendapatan_komoditas.pendapatan) as total_pendapatan');
|
||||
|
||||
$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'),
|
||||
clone $pendapatanPerPetaniQuery,
|
||||
'agg'
|
||||
)->selectRaw("
|
||||
AVG(total_pendapatan) as rata_rata,
|
||||
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,
|
||||
@@ -97,6 +216,8 @@ class DemografiPetaniController extends Controller
|
||||
COUNT(*) FILTER (WHERE total_pendapatan > 10000000) as lebih_10
|
||||
")->first();
|
||||
|
||||
$ratarataPenghasilan = (float) ($penghasilanBuckets->rata_rata ?? 0);
|
||||
|
||||
$penghasilan = [
|
||||
'kurang_1' => $penghasilanBuckets->kurang_1,
|
||||
'1_3' => $penghasilanBuckets->p1_3,
|
||||
@@ -112,6 +233,7 @@ class DemografiPetaniController extends Controller
|
||||
'total_komoditas' => $totalKomoditas,
|
||||
'total_luas_lahan' => $totalLuasLahan,
|
||||
'ratarata_luas_lahan' => $ratarataLuasLahan,
|
||||
'ratarata_penghasilan' => $ratarataPenghasilan,
|
||||
'kelompok_umur' => [
|
||||
'kurang_30' => $umurCounts->kurang_30,
|
||||
'30_39' => $umurCounts->umur_30_39,
|
||||
@@ -120,31 +242,88 @@ class DemografiPetaniController extends Controller
|
||||
'60_keatas' => $umurCounts->umur_60_keatas,
|
||||
],
|
||||
'pendidikan' => $pendidikan,
|
||||
'jumlah_tanggungan' => $jumlahTanggungan,
|
||||
'kelompok_tani' => $kelompokTani,
|
||||
'desa_sasaran' => [
|
||||
'total' => $desaSasaranRows->count(),
|
||||
'ids' => $desaSasaranRows->pluck('desa_id')->values(),
|
||||
'labels' => $desaSasaranRows->pluck('desa')->values(),
|
||||
'series' => $desaSasaranRows->pluck('total')->map(fn ($v) => (int) $v)->values(),
|
||||
],
|
||||
'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,
|
||||
'status_kepemilikan_lahan' => [
|
||||
'keys' => $statusKepemilikanLahanRows->pluck('status')->values(),
|
||||
'labels' => $statusKepemilikanLahanRows
|
||||
->pluck('status')
|
||||
->map(fn ($status) => $this->statusLahanLabel((string) $status))
|
||||
->values(),
|
||||
'series' => $statusKepemilikanLahanRows->pluck('total')->map(fn ($v) => (int) $v)->values(),
|
||||
],
|
||||
'penghasilan' => $penghasilan,
|
||||
];
|
||||
|
||||
$daftarPetani = Petani::with(['komoditas'])
|
||||
->withSum('lahans as total_luas_lahan', 'luas_lahan')
|
||||
->orderBy('id')
|
||||
->paginate(10);
|
||||
return compact('data');
|
||||
}
|
||||
|
||||
return view('dashboard.demografi-petani', compact('data', 'daftarPetani'));
|
||||
private function filteredPetaniIdsQuery(Request $request)
|
||||
{
|
||||
$query = Petani::query()
|
||||
->leftJoin('master_desa_kelurahan as desa', 'petani.desa_kelurahan_id', '=', 'desa.id')
|
||||
->leftJoin('master_kecamatan as kecamatan', 'desa.kecamatan_id', '=', 'kecamatan.id')
|
||||
->leftJoin('master_kabupaten_kota as kabupaten', 'kecamatan.kabupaten_kota_id', '=', 'kabupaten.id')
|
||||
->when($request->filled('provinsi_id'), fn ($query) => $query->where('kabupaten.provinsi_id', $request->provinsi_id))
|
||||
->when($request->filled('kabupatenKota_id'), fn ($query) => $query->where('kecamatan.kabupaten_kota_id', $request->kabupatenKota_id))
|
||||
->when($request->filled('kecamatan_id'), fn ($query) => $query->where('desa.kecamatan_id', $request->kecamatan_id))
|
||||
->when($request->filled('desaKelurahan_id'), fn ($query) => $query->where('petani.desa_kelurahan_id', $request->desaKelurahan_id))
|
||||
->when($request->filled('komoditas_id'), fn ($query) => $query->where('petani.komoditas_id', $request->komoditas_id));
|
||||
|
||||
if ($request->filled('segmen')) {
|
||||
$query->whereHas('skor', function ($query) use ($request) {
|
||||
match ($request->segmen) {
|
||||
'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),
|
||||
'Belum Lengkap' => $query->where(function ($query) {
|
||||
$query->whereNull('skor_kesiapan_adopsi')
|
||||
->orWhere('skor_kesiapan_adopsi', '<', 1);
|
||||
}),
|
||||
default => null,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return $query->select('petani.id');
|
||||
}
|
||||
|
||||
private function statusLahanLabel(string $status): string
|
||||
{
|
||||
return match ($status) {
|
||||
'sendiri' => 'Milik Pribadi',
|
||||
'sewa' => 'Sewa',
|
||||
'adat' => 'Tanah Adat',
|
||||
default => ucwords(str_replace('_', ' ', $status)),
|
||||
};
|
||||
}
|
||||
|
||||
public function data(Request $request)
|
||||
{
|
||||
$filteredPetaniIds = $this->filteredPetaniIdsQuery($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')
|
||||
->whereIn('petani.id', clone $filteredPetaniIds)
|
||||
->withSum('lahans as total_luas_lahan', 'luas_lahan')
|
||||
->addSelect(['total_pendapatan' => $pendapatanSub]);
|
||||
|
||||
@@ -187,6 +366,45 @@ class DemografiPetaniController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->filled('jumlah_tanggungan')) {
|
||||
$tanggunganQuery = SosialEkonomi::query()
|
||||
->whereIn('petani_id', clone $filteredPetaniIds)
|
||||
->whereNotNull('jumlah_tanggungan')
|
||||
->select('petani_id')
|
||||
->distinct();
|
||||
|
||||
match ($request->jumlah_tanggungan) {
|
||||
'0' => $tanggunganQuery->where('jumlah_tanggungan', 0),
|
||||
'1_2' => $tanggunganQuery->whereBetween('jumlah_tanggungan', [1, 2]),
|
||||
'3_4' => $tanggunganQuery->whereBetween('jumlah_tanggungan', [3, 4]),
|
||||
'5_keatas' => $tanggunganQuery->where('jumlah_tanggungan', '>=', 5),
|
||||
default => null,
|
||||
};
|
||||
|
||||
$query->whereIn('petani.id', $tanggunganQuery);
|
||||
}
|
||||
|
||||
if ($request->filled('kelompok_tani')) {
|
||||
match ($request->kelompok_tani) {
|
||||
'terdaftar' => $query->whereIn('petani.id', SosialEkonomi::query()
|
||||
->whereIn('petani_id', clone $filteredPetaniIds)
|
||||
->where('terdaftar_kelompok_tani', 'ya')
|
||||
->select('petani_id')
|
||||
->distinct()),
|
||||
'tidak_terdaftar' => $query->whereIn('petani.id', SosialEkonomi::query()
|
||||
->whereIn('petani_id', clone $filteredPetaniIds)
|
||||
->where('terdaftar_kelompok_tani', 'tidak')
|
||||
->select('petani_id')
|
||||
->distinct()),
|
||||
'belum_terisi' => $query->whereNotIn('petani.id', SosialEkonomi::query()
|
||||
->whereIn('petani_id', clone $filteredPetaniIds)
|
||||
->whereIn('terdaftar_kelompok_tani', ['ya', 'tidak'])
|
||||
->select('petani_id')
|
||||
->distinct()),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
if ($request->filled('komoditas')) {
|
||||
$query->where('komoditas_id', $request->komoditas);
|
||||
}
|
||||
@@ -213,15 +431,28 @@ class DemografiPetaniController extends Controller
|
||||
}
|
||||
|
||||
if ($request->filled('luas_lahan')) {
|
||||
$lahanQuery = Lahan::query()->select('petani_id')->groupBy('petani_id');
|
||||
$lahanQuery = Lahan::query()
|
||||
->whereIn('petani_id', clone $filteredPetaniIds)
|
||||
->select('petani_id')
|
||||
->distinct();
|
||||
|
||||
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'),
|
||||
'kurang_05' => $lahanQuery->where('luas_lahan', '<', 0.5),
|
||||
'05_1' => $lahanQuery->whereBetween('luas_lahan', [0.5, 1]),
|
||||
'1_2' => $lahanQuery->where('luas_lahan', '>', 1)->where('luas_lahan', '<=', 2),
|
||||
'lebih_2' => $lahanQuery->where('luas_lahan', '>', 2),
|
||||
default => null,
|
||||
};
|
||||
$query->whereIn('id', $lahanQuery->pluck('petani_id'));
|
||||
|
||||
$query->whereIn('petani.id', $lahanQuery);
|
||||
}
|
||||
|
||||
if ($request->filled('status_kepemilikan_lahan')) {
|
||||
$query->whereIn('petani.id', Lahan::query()
|
||||
->whereIn('petani_id', clone $filteredPetaniIds)
|
||||
->where('status_kepemilikan', $request->status_kepemilikan_lahan)
|
||||
->select('petani_id')
|
||||
->distinct());
|
||||
}
|
||||
|
||||
$daftarPetani = $query->orderBy('id')->paginate(10);
|
||||
|
||||
@@ -5,21 +5,14 @@ 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 Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
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
|
||||
{
|
||||
@@ -36,13 +29,13 @@ class IndeksPetaniController extends Controller
|
||||
];
|
||||
}
|
||||
|
||||
private function petaniQuery(array $filters): \Illuminate\Database\Eloquent\Builder
|
||||
private function petaniQuery(array $filters): 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')
|
||||
->leftJoin('master_desa_kelurahan', 'petani.desa_kelurahan_id', '=', 'master_desa_kelurahan.id')
|
||||
->leftJoin('master_kecamatan', 'master_desa_kelurahan.kecamatan_id', '=', 'master_kecamatan.id')
|
||||
->leftJoin('master_kabupaten_kota', 'master_kecamatan.kabupaten_kota_id', '=', 'master_kabupaten_kota.id')
|
||||
->leftJoin('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))
|
||||
@@ -50,17 +43,21 @@ class IndeksPetaniController extends Controller
|
||||
->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));
|
||||
|
||||
if ($filters['segmen']) {
|
||||
$petaniQuery->whereHas('skor', fn (Builder $query) => $this->applySegmentFilter($query, $filters['segmen']));
|
||||
}
|
||||
|
||||
return $petaniQuery;
|
||||
}
|
||||
|
||||
private function skorPetaniQuery(array $filters, array $komoditas): \Illuminate\Database\Eloquent\Builder
|
||||
private function skorPetaniQuery(array $filters, array $komoditas): 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')
|
||||
->leftJoin('master_desa_kelurahan', 'petani.desa_kelurahan_id', '=', 'master_desa_kelurahan.id')
|
||||
->leftJoin('master_kecamatan', 'master_desa_kelurahan.kecamatan_id', '=', 'master_kecamatan.id')
|
||||
->leftJoin('master_kabupaten_kota', 'master_kecamatan.kabupaten_kota_id', '=', 'master_kabupaten_kota.id')
|
||||
->leftJoin('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))
|
||||
@@ -68,33 +65,31 @@ class IndeksPetaniController extends Controller
|
||||
->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);
|
||||
}),
|
||||
return $this->applySegmentFilter($skor, $filters['segmen']);
|
||||
}
|
||||
|
||||
default => null,
|
||||
};
|
||||
});
|
||||
private function applySegmentFilter(Builder $query, ?string $segment): Builder
|
||||
{
|
||||
$ranges = [
|
||||
'Penggerak' => [4, null],
|
||||
'Perintis' => [3, 4],
|
||||
'Pemerhati' => [2, 3],
|
||||
'Pemula' => [1, 2],
|
||||
];
|
||||
|
||||
if (isset($ranges[$segment])) {
|
||||
[$min, $max] = $ranges[$segment];
|
||||
|
||||
return $query->where('skor_kesiapan_adopsi', '>=', $min)
|
||||
->when($max !== null, fn (Builder $query) => $query->where('skor_kesiapan_adopsi', '<', $max));
|
||||
}
|
||||
|
||||
return $skor;
|
||||
if ($segment === 'Belum Lengkap') {
|
||||
$query->where(fn (Builder $query) => $query->whereNull('skor_kesiapan_adopsi')
|
||||
->orWhere('skor_kesiapan_adopsi', '<', 1));
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
@@ -103,18 +98,28 @@ class IndeksPetaniController extends Controller
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return redirect()->route('dashboard.karakteristik-petani', array_merge($request->query(), [
|
||||
'tab' => 'indeks-petani',
|
||||
]));
|
||||
}
|
||||
|
||||
public function contentData(Request $request): array
|
||||
{
|
||||
$filters = $this->filters($request);
|
||||
|
||||
$title = 'Dashboard Profil Petani';
|
||||
$segments = $this->segments();
|
||||
$colors = collect($segments)
|
||||
->pluck('color')
|
||||
->map(fn($c) => "var(--bs-{$c})")
|
||||
->values();
|
||||
|
||||
$komoditas = Komoditas::where('is_utama', true)->get();
|
||||
$petaniQuery = $this->petaniQuery($filters);
|
||||
$komoditas = Komoditas::where('is_utama', true)->get(['id', 'nama']);
|
||||
$petaniQuery = $this->petaniQuery(array_fill_keys(array_keys($filters), null));
|
||||
|
||||
$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,
|
||||
@@ -122,9 +127,15 @@ class IndeksPetaniController extends Controller
|
||||
master_provinsi.id as provinsi_id,
|
||||
master_provinsi.nama as provinsi
|
||||
")
|
||||
->whereNotNull('master_provinsi.id')
|
||||
->distinct()
|
||||
->get();
|
||||
|
||||
$provinsi = $desaKelurahan
|
||||
->unique('provinsi_id')
|
||||
->sortBy('provinsi')
|
||||
->values();
|
||||
|
||||
$wilayah = $desaKelurahan
|
||||
->groupBy('provinsi_id')
|
||||
->map(function ($provinsi) {
|
||||
@@ -147,7 +158,6 @@ class IndeksPetaniController extends Controller
|
||||
return [
|
||||
'id' => $desa->desa_kelurahan_id,
|
||||
'nama' => $desa->desa_kelurahan,
|
||||
'path' => $desa->path,
|
||||
];
|
||||
})->values(),
|
||||
];
|
||||
@@ -157,12 +167,15 @@ class IndeksPetaniController extends Controller
|
||||
];
|
||||
})->values();
|
||||
|
||||
return view('dashboard.indeks-petani', array_merge($filters,
|
||||
return array_merge($filters,
|
||||
compact(
|
||||
'komoditas',
|
||||
'desaKelurahan',
|
||||
'provinsi',
|
||||
'wilayah',
|
||||
)));
|
||||
'segments',
|
||||
'colors',
|
||||
));
|
||||
}
|
||||
|
||||
public function summary(Request $request): JsonResponse
|
||||
@@ -171,23 +184,21 @@ class IndeksPetaniController extends Controller
|
||||
$petaniQuery = $this->petaniQuery($filters);
|
||||
|
||||
$komoditas = Komoditas::where('is_utama', true)
|
||||
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get();
|
||||
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get(['id', 'nama']);
|
||||
|
||||
$countKomoditas = $petaniQuery->clone()
|
||||
->whereHas('komoditas', fn ($query) => $query->where('is_utama', true))
|
||||
->distinct()->count('komoditas_id');
|
||||
$counts = $petaniQuery->clone()
|
||||
->selectRaw('COUNT(*) as petani')
|
||||
->selectRaw('COUNT(DISTINCT petani.komoditas_id) FILTER (WHERE petani.komoditas_id IN (SELECT id FROM master_komoditas WHERE is_utama = true AND deleted_at IS NULL)) as komoditas')
|
||||
->selectRaw('COUNT(DISTINCT petani.desa_kelurahan_id) FILTER (WHERE petani.komoditas_id IN (SELECT id FROM master_komoditas WHERE is_utama = true AND deleted_at IS NULL)) as desa')
|
||||
->selectRaw('COUNT(DISTINCT master_kecamatan.id) FILTER (WHERE petani.komoditas_id IN (SELECT id FROM master_komoditas WHERE is_utama = true AND deleted_at IS NULL)) as kecamatan')
|
||||
->first();
|
||||
|
||||
$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');
|
||||
$filteredPetaniIds = $petaniQuery->clone()->select('petani.id');
|
||||
$lahan = Lahan::query()
|
||||
->whereIn('petani_id', $filteredPetaniIds)
|
||||
->whereIn('komoditas_id', $komoditas->pluck('id'))
|
||||
->selectRaw('COALESCE(SUM(luas_lahan), 0) as total, AVG(luas_lahan) as rata_rata')
|
||||
->first();
|
||||
|
||||
$skorQuery = $this->skorPetaniQuery($filters, $komoditas->pluck('id')->toArray());
|
||||
$avgSkor = $skorQuery->clone()->avg('skor_kesiapan_adopsi');
|
||||
@@ -205,12 +216,12 @@ class IndeksPetaniController extends Controller
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'petani' => $petaniQuery->clone()->count(),
|
||||
'komoditas' => $countKomoditas,
|
||||
'sum_lahan' => $sumLahan,
|
||||
'avg_lahan' => $avgLahan,
|
||||
'desa' => $countDesa,
|
||||
'kecamatan' => $countKecamatan,
|
||||
'petani' => (int) $counts->petani,
|
||||
'komoditas' => (int) $counts->komoditas,
|
||||
'sum_lahan' => (float) $lahan->total,
|
||||
'avg_lahan' => (float) $lahan->rata_rata,
|
||||
'desa' => (int) $counts->desa,
|
||||
'kecamatan' => (int) $counts->kecamatan,
|
||||
'skor' => $avgSkor,
|
||||
'segmen' => $avgSegmen
|
||||
]);
|
||||
@@ -269,7 +280,7 @@ class IndeksPetaniController extends Controller
|
||||
$case = $this->cases();
|
||||
|
||||
$komoditas = Komoditas::where('is_utama', true)
|
||||
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get();
|
||||
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get(['id', 'nama']);
|
||||
|
||||
$skorQuery = $this->skorPetaniQuery($filters, $komoditas->pluck('id')->toArray());
|
||||
|
||||
@@ -313,7 +324,7 @@ class IndeksPetaniController extends Controller
|
||||
$case = $this->cases();
|
||||
|
||||
$komoditas = Komoditas::where('is_utama', true)
|
||||
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get();
|
||||
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get(['id', 'nama']);
|
||||
|
||||
$skorQuery = $this->skorPetaniQuery($filters, $komoditas->pluck('id')->toArray());
|
||||
|
||||
@@ -355,7 +366,7 @@ class IndeksPetaniController extends Controller
|
||||
$filters = $this->filters($request);
|
||||
|
||||
$komoditas = Komoditas::where('is_utama', true)
|
||||
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get();
|
||||
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get(['id', 'nama']);
|
||||
|
||||
$skorQuery = $this->skorPetaniQuery($filters, $komoditas->pluck('id')->toArray());
|
||||
|
||||
@@ -380,7 +391,7 @@ class IndeksPetaniController extends Controller
|
||||
$filters = $this->filters($request);
|
||||
|
||||
$komoditas = Komoditas::where('is_utama', true)
|
||||
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get();
|
||||
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get(['id', 'nama']);
|
||||
|
||||
$skorQuery = $this->skorPetaniQuery($filters, $komoditas->pluck('id')->toArray());
|
||||
|
||||
@@ -405,17 +416,8 @@ class IndeksPetaniController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function map(Request $request): JsonResponse
|
||||
private function villageData(array $filters, bool $includePath = true): \Illuminate\Support\Collection
|
||||
{
|
||||
$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);
|
||||
@@ -424,7 +426,6 @@ class IndeksPetaniController extends Controller
|
||||
->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,
|
||||
@@ -432,11 +433,12 @@ class IndeksPetaniController extends Controller
|
||||
master_provinsi.id as provinsi_id,
|
||||
master_provinsi.nama as provinsi
|
||||
")
|
||||
->when($includePath, fn (Builder $query) => $query->addSelect('master_desa_kelurahan.path as path'))
|
||||
->distinct()
|
||||
->get();
|
||||
|
||||
$komoditas = Komoditas::where('is_utama', true)
|
||||
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get();
|
||||
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get(['id', 'nama']);
|
||||
|
||||
$skorQuery = $this->skorPetaniQuery($filters, $komoditas->pluck('id')->toArray());
|
||||
|
||||
@@ -464,17 +466,21 @@ class IndeksPetaniController extends Controller
|
||||
->groupBy('petani.desa_kelurahan_id')
|
||||
->get();
|
||||
|
||||
$segmenDominan = $mapSegmentasi
|
||||
->groupBy('desa_kelurahan_id')
|
||||
$segmentasiGroups = $mapSegmentasi->groupBy('desa_kelurahan_id');
|
||||
$jumlahSegmenByDesa = $segmentasiGroups
|
||||
->map(fn ($items) => $items->pluck('total', 'segmen'));
|
||||
|
||||
$segmenDominan = $segmentasiGroups
|
||||
->map(function ($items) {
|
||||
return $items->sortByDesc('total')->first();
|
||||
return $items->reduce(fn ($largest, $item) => $largest === null || $item->total > $largest->total ? $item : $largest);
|
||||
});
|
||||
|
||||
$mapAvgSkorSegmentasi->each(function ($item) use ($segmenDominan) {
|
||||
$mapAvgSkorSegmentasi->each(function ($item) use ($segmenDominan, $jumlahSegmenByDesa) {
|
||||
$dominan = $segmenDominan->get($item->desa_kelurahan_id);
|
||||
|
||||
$item->segmen_dominan = $dominan?->segmen;
|
||||
$item->jumlah_dominan = $dominan?->total;
|
||||
$item->jumlah_segmen = $jumlahSegmenByDesa->get($item->desa_kelurahan_id, collect());
|
||||
});
|
||||
|
||||
$segmentasiByDesa = $mapAvgSkorSegmentasi->keyBy('desa_kelurahan_id');
|
||||
@@ -490,97 +496,22 @@ class IndeksPetaniController extends Controller
|
||||
return $desa;
|
||||
})->filter()->values();
|
||||
|
||||
return response()->json($mapDesaSegmentasi);
|
||||
return $mapDesaSegmentasi;
|
||||
}
|
||||
|
||||
public function map(Request $request): JsonResponse
|
||||
{
|
||||
return response()->json($this->villageData($this->filters($request)));
|
||||
}
|
||||
|
||||
public function desa(Request $request): JsonResponse
|
||||
{
|
||||
$filters = $this->filters($request);
|
||||
$segments = $this->segments();
|
||||
$segmentStyles = collect($this->segments())->keyBy('name');
|
||||
$mapDesaSegmentasi = $this->villageData($this->filters($request), false);
|
||||
|
||||
$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) {
|
||||
$segmentNames = ['Pemula', 'Perintis', 'Pemerhati', 'Penggerak', 'Belum Lengkap'];
|
||||
$rows = $mapDesaSegmentasi->map(function ($desa) use ($segmentStyles, $segmentNames) {
|
||||
$total = (int) $desa->segmentasi->jumlah_petani;
|
||||
|
||||
$style = $segmentStyles[$desa->segmentasi->segmen] ?? ['color' => 'secondary'];
|
||||
$styleDominan = $segmentStyles[$desa->segmentasi->segmen_dominan] ?? ['color' => 'secondary'];
|
||||
@@ -594,6 +525,16 @@ class IndeksPetaniController extends Controller
|
||||
'jumlah_dominan' => $desa->segmentasi->jumlah_dominan,
|
||||
'color' => $style['color'],
|
||||
'color_dominan' => $styleDominan['color'],
|
||||
'segments' => collect($segmentNames)
|
||||
->mapWithKeys(function ($name) use ($desa, $segmentStyles, $total) {
|
||||
$count = (int) $desa->segmentasi->jumlah_segmen->get($name, 0);
|
||||
|
||||
return [strtolower(str_replace(' ', '_', $name)) => [
|
||||
'count' => $count,
|
||||
'percentage' => $total > 0 ? round($count / $total * 100, 2) : 0,
|
||||
'color' => $segmentStyles[$name]['color'] ?? 'secondary',
|
||||
]];
|
||||
})->all(),
|
||||
];
|
||||
});
|
||||
|
||||
@@ -605,30 +546,21 @@ class IndeksPetaniController extends Controller
|
||||
$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();
|
||||
->when($filters['komoditas_id'], fn($q, $id) => $q->where('id', $id))->get(['id', 'nama']);
|
||||
|
||||
$daftarPetani = $petaniQuery->clone()
|
||||
->select('petani.*')
|
||||
->with(['desaKelurahan', 'komoditas', 'skor'])
|
||||
->withSum('lahans as total_luas_lahan', 'luas_lahan')
|
||||
->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()
|
||||
@@ -642,7 +574,7 @@ class IndeksPetaniController extends Controller
|
||||
'desa' => $item->desaKelurahan?->nama,
|
||||
'desa_kelurahan_id' => $item->desa_kelurahan_id,
|
||||
'komoditas' => $item->komoditas?->nama,
|
||||
'luas_lahan' => $item->lahans?->sum('luas_lahan') ?? 0,
|
||||
'luas_lahan' => (float) ($item->total_luas_lahan ?? 0),
|
||||
'skor' => $item->skor
|
||||
? $item->skor->skor_kesiapan_adopsi
|
||||
: null,
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Web\Dashboard;
|
||||
|
||||
use App\Enums\KeparahanSerangan;
|
||||
use App\Enums\PenyakitTanaman;
|
||||
use App\Enums\PraktikBudidaya\KondisiPengelolaanAir;
|
||||
use App\Enums\PraktikBudidaya\PemberianPupuk;
|
||||
use App\Enums\SeranganHama;
|
||||
use App\Enums\SosialEkonomi\StatusKepemilikanLahanSosialEkonomi;
|
||||
@@ -13,12 +14,14 @@ use App\Models\Pertanian\Lahan;
|
||||
use App\Models\Pertanian\Petani;
|
||||
use App\Models\Pertanian\PraktikBudidaya;
|
||||
use App\Models\Pertanian\Produksi;
|
||||
use App\Models\Pertanian\SurveyPanen;
|
||||
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 App\Models\Wilayah\Provinsi;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
@@ -95,11 +98,180 @@ class IndikatorPertanianController extends Controller
|
||||
private function dashboardPayload(array $filters): array
|
||||
{
|
||||
return [
|
||||
'overviewData' => $this->overviewData($filters),
|
||||
'landProfileData' => $this->landProfileData($filters),
|
||||
'summaryData' => $this->summaryData($filters),
|
||||
'chartData' => $this->chartData($filters),
|
||||
];
|
||||
}
|
||||
|
||||
private function overviewData(array $filters): array
|
||||
{
|
||||
$totalPetani = $this->applyPetaniFilters(Petani::query(), $filters)->count();
|
||||
$totalLahan = $this->applyLahanFilters(Lahan::query(), $filters)->count();
|
||||
$totalLuasLahan = (float) $this->applyLahanFilters(Lahan::has('petani'), $filters)->sum('luas_lahan');
|
||||
$jumlahDesa = $this->applyLahanFilters(Lahan::query(), $filters)
|
||||
->whereNotNull('desa_kelurahan_id')
|
||||
->distinct('desa_kelurahan_id')
|
||||
->count('desa_kelurahan_id');
|
||||
$jumlahKecamatan = $this->applyLahanFilters(Lahan::query(), $filters)
|
||||
->leftJoin('master_desa_kelurahan as d', 'd.id', '=', 'map_lahan.desa_kelurahan_id')
|
||||
->whereNotNull('d.kecamatan_id')
|
||||
->distinct('d.kecamatan_id')
|
||||
->count('d.kecamatan_id');
|
||||
$totalProduksiKg = $this->totalProduksiKg($filters);
|
||||
$rataRataProduktivitas = $totalLuasLahan > 0 ? $totalProduksiKg / $totalLuasLahan : 0;
|
||||
$tanamanProduktif = $this->tanamanProduktifCount($filters);
|
||||
$batchPanen = $this->batchPanenCount($filters);
|
||||
|
||||
return [
|
||||
'kpiCards' => [
|
||||
[
|
||||
'value' => number_format($totalPetani, 0, ',', '.'),
|
||||
'label' => 'Jumlah Petani',
|
||||
'description' => 'Petani yang tercakup dalam filter aktif',
|
||||
'icon' => 'tabler-users',
|
||||
'variant' => 'primary',
|
||||
],
|
||||
[
|
||||
'value' => number_format($totalLahan, 0, ',', '.'),
|
||||
'label' => 'Jumlah Kebun/Lahan',
|
||||
'description' => 'Kebun atau lahan terdata',
|
||||
'icon' => 'tabler-trees',
|
||||
'variant' => 'success',
|
||||
],
|
||||
[
|
||||
'value' => number_format($totalLuasLahan, 2, ',', '.').' ha',
|
||||
'label' => 'Luas Lahan',
|
||||
'description' => 'Akumulasi luas lahan terdata',
|
||||
'icon' => 'tabler-ruler-measure',
|
||||
'variant' => 'warning',
|
||||
],
|
||||
[
|
||||
'value' => number_format($jumlahDesa, 0, ',', '.').' desa',
|
||||
'label' => 'Desa/Kecamatan',
|
||||
'description' => number_format($jumlahKecamatan, 0, ',', '.').' kecamatan tercakup',
|
||||
'icon' => 'tabler-map-pin',
|
||||
'variant' => 'info',
|
||||
],
|
||||
[
|
||||
'value' => number_format($totalProduksiKg, 0, ',', '.').' kg',
|
||||
'label' => 'Total Produksi',
|
||||
'description' => $this->productionSummaryDescription($filters),
|
||||
'icon' => 'tabler-wheat',
|
||||
'variant' => 'primary',
|
||||
],
|
||||
[
|
||||
'value' => number_format($rataRataProduktivitas, 0, ',', '.').' kg/ha',
|
||||
'label' => 'Produktivitas Rata-rata',
|
||||
'description' => 'Berdasarkan produksi dan luas lahan terdata',
|
||||
'icon' => 'tabler-chart-bar',
|
||||
'variant' => 'success',
|
||||
],
|
||||
[
|
||||
'value' => number_format($tanamanProduktif, 0, ',', '.'),
|
||||
'label' => 'Tanaman Produktif',
|
||||
'description' => 'Tanaman dengan kondisi baik',
|
||||
'icon' => 'tabler-plant-2',
|
||||
'variant' => 'warning',
|
||||
],
|
||||
[
|
||||
'value' => number_format($batchPanen, 0, ',', '.'),
|
||||
'label' => 'Batch Panen Tercatat',
|
||||
'description' => 'Catatan produksi dan survey panen',
|
||||
'icon' => 'tabler-basket',
|
||||
'variant' => 'info',
|
||||
],
|
||||
],
|
||||
'commodityFocus' => $this->commodityFocusData($filters),
|
||||
'map' => [
|
||||
'features' => $this->farmMapFeatures($filters),
|
||||
'levels' => $this->mapLevelData($filters),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function landProfileData(array $filters): array
|
||||
{
|
||||
$totalTanaman = (int) $this->applyLahanFilters(Lahan::query(), $filters)->sum('jumlah_tanaman');
|
||||
$rataRataUmurTanaman = (float) $this->applyLahanFilters(Lahan::query(), $filters)
|
||||
->whereNotNull('umur_tanaman')
|
||||
->avg('umur_tanaman');
|
||||
$komoditasCount = (int) $this->applyLahanFilters(Lahan::query(), $filters)
|
||||
->whereNotNull('komoditas_id')
|
||||
->distinct('komoditas_id')
|
||||
->count('komoditas_id');
|
||||
$statusLahanCount = (int) $this->applyLahanFilters(Lahan::query(), $filters)
|
||||
->whereNotNull('status_kepemilikan')
|
||||
->distinct('status_kepemilikan')
|
||||
->count('status_kepemilikan');
|
||||
$tanamanPenaung = $this->shadowTreeSummary($filters);
|
||||
$tanamanPendamping = $this->companionPlantSummary($filters);
|
||||
$diversityAverage = $this->plantDiversityAverage($filters);
|
||||
|
||||
return [
|
||||
'conditionCards' => [
|
||||
[
|
||||
'value' => number_format($komoditasCount, 0, ',', '.'),
|
||||
'label' => 'Luas Lahan per Komoditas',
|
||||
'description' => 'Komoditas dengan luas lahan terdata',
|
||||
'icon' => 'tabler-chart-treemap',
|
||||
'variant' => 'primary',
|
||||
],
|
||||
[
|
||||
'value' => number_format($totalTanaman, 0, ',', '.'),
|
||||
'label' => 'Jumlah Tanaman',
|
||||
'description' => 'Akumulasi jumlah tanaman pada lahan',
|
||||
'icon' => 'tabler-trees',
|
||||
'variant' => 'success',
|
||||
],
|
||||
[
|
||||
'value' => number_format($rataRataUmurTanaman, 1, ',', '.').' tahun',
|
||||
'label' => 'Umur Tanaman',
|
||||
'description' => 'Rata-rata umur tanaman terdata',
|
||||
'icon' => 'tabler-calendar-stats',
|
||||
'variant' => 'warning',
|
||||
],
|
||||
[
|
||||
'value' => number_format($statusLahanCount, 0, ',', '.'),
|
||||
'label' => 'Status Lahan',
|
||||
'description' => 'Jumlah kategori status kepemilikan lahan',
|
||||
'icon' => 'tabler-certificate',
|
||||
'variant' => 'info',
|
||||
],
|
||||
],
|
||||
'agroecologyItems' => [
|
||||
['label' => 'Sumber Air Lahan', 'value' => $this->sourceWaterSummary($filters), 'status' => 'available'],
|
||||
['label' => 'Pengelolaan Air & Irigasi', 'value' => $this->waterManagementSummary($filters), 'status' => 'available'],
|
||||
['label' => 'Kebutuhan Air per Musim', 'value' => $this->seasonalWaterNeedSummary($filters), 'status' => 'available'],
|
||||
['label' => 'Praktik Konservasi Tanah', 'value' => $this->soilConservationSummary($filters), 'status' => 'available'],
|
||||
],
|
||||
'agroforestryCards' => [
|
||||
[
|
||||
'value' => number_format($tanamanPenaung['total'], 0, ',', '.'),
|
||||
'label' => 'Jumlah Tanaman Penaung',
|
||||
'description' => $tanamanPenaung['types'].' jenis tanaman penaung',
|
||||
'icon' => 'tabler-leaf',
|
||||
'variant' => 'success',
|
||||
],
|
||||
[
|
||||
'value' => number_format($tanamanPendamping['types'], 0, ',', '.'),
|
||||
'label' => 'Jenis Tanaman Pendamping',
|
||||
'description' => number_format($tanamanPendamping['total'], 0, ',', '.').' entri tanaman pendamping',
|
||||
'icon' => 'tabler-flower',
|
||||
'variant' => 'warning',
|
||||
],
|
||||
[
|
||||
'value' => number_format(round($diversityAverage), 0, ',', '.'),
|
||||
'label' => 'Tingkat Campuran Tanaman',
|
||||
'description' => 'Rata-rata kombinasi tanaman utama, pendamping, dan penaung',
|
||||
'icon' => 'tabler-binary-tree',
|
||||
'variant' => 'primary',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function summaryData(array $filters): array
|
||||
{
|
||||
$totalPetani = $this->applyPetaniFilters(Petani::query(), $filters)->count();
|
||||
@@ -166,6 +338,10 @@ class IndikatorPertanianController extends Controller
|
||||
'statusPanen' => $this->statusPanenChart($filters),
|
||||
'surveyProduktivitasHa' => $this->surveyProduktivitasByUnitChart($filters, 'ha_tahun'),
|
||||
'surveyProduktivitasPohon' => $this->surveyProduktivitasByUnitChart($filters, 'pohon_tahun'),
|
||||
'luasLahanPerKomoditas' => $this->luasLahanPerKomoditasChart($filters),
|
||||
'jumlahTanamanPerKomoditas' => $this->jumlahTanamanPerKomoditasChart($filters),
|
||||
'umurTanaman' => $this->umurTanamanChart($filters),
|
||||
'sumberAir' => $this->sumberAirChart($filters),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -355,6 +531,82 @@ class IndikatorPertanianController extends Controller
|
||||
];
|
||||
}
|
||||
|
||||
private function luasLahanPerKomoditasChart(array $filters): array
|
||||
{
|
||||
$rows = $this->applyLahanFilters(Lahan::query(), $filters)
|
||||
->leftJoin('master_komoditas as k', 'k.id', '=', 'map_lahan.komoditas_id')
|
||||
->whereNotNull('map_lahan.luas_lahan')
|
||||
->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
|
||||
->selectRaw('SUM(map_lahan.luas_lahan) as total')
|
||||
->groupBy('k.id', 'k.nama')
|
||||
->havingRaw('SUM(map_lahan.luas_lahan) > 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 jumlahTanamanPerKomoditasChart(array $filters): array
|
||||
{
|
||||
$rows = $this->applyLahanFilters(Lahan::query(), $filters)
|
||||
->leftJoin('master_komoditas as k', 'k.id', '=', 'map_lahan.komoditas_id')
|
||||
->whereNotNull('map_lahan.jumlah_tanaman')
|
||||
->selectRaw("COALESCE(k.nama, 'Belum ditentukan') as label")
|
||||
->selectRaw('SUM(map_lahan.jumlah_tanaman) as total')
|
||||
->groupBy('k.id', 'k.nama')
|
||||
->havingRaw('SUM(map_lahan.jumlah_tanaman) > 0')
|
||||
->orderByDesc('total')
|
||||
->get();
|
||||
|
||||
return [
|
||||
'labels' => $rows->pluck('label')->values()->all(),
|
||||
'series' => $rows->pluck('total')->map(fn ($value) => (int) $value)->values()->all(),
|
||||
];
|
||||
}
|
||||
|
||||
private function umurTanamanChart(array $filters): array
|
||||
{
|
||||
$bucketOrder = ['< 3 tahun', '3-7 tahun', '8-15 tahun', '> 15 tahun'];
|
||||
$rows = $this->applyLahanFilters(Lahan::query(), $filters)
|
||||
->whereNotNull('umur_tanaman')
|
||||
->pluck('umur_tanaman')
|
||||
->map(function ($age) {
|
||||
$age = (float) $age;
|
||||
|
||||
return match (true) {
|
||||
$age < 3 => '< 3 tahun',
|
||||
$age <= 7 => '3-7 tahun',
|
||||
$age <= 15 => '8-15 tahun',
|
||||
default => '> 15 tahun',
|
||||
};
|
||||
})
|
||||
->countBy()
|
||||
->sortBy(fn ($value, $label) => array_search($label, $bucketOrder, true));
|
||||
|
||||
return [
|
||||
'labels' => $rows->keys()->values()->all(),
|
||||
'series' => $rows->values()->map(fn ($value) => (int) $value)->values()->all(),
|
||||
];
|
||||
}
|
||||
|
||||
private function sumberAirChart(array $filters): array
|
||||
{
|
||||
$rows = $this->applyPraktikBudidayaFilters(PraktikBudidaya::query(), $filters)
|
||||
->get(['sumber_air'])
|
||||
->flatMap(fn (PraktikBudidaya $praktik) => $this->arrayValues($praktik->sumber_air))
|
||||
->filter()
|
||||
->countBy()
|
||||
->sortDesc();
|
||||
|
||||
return [
|
||||
'labels' => $rows->keys()->values()->all(),
|
||||
'series' => $rows->values()->map(fn ($value) => (int) $value)->values()->all(),
|
||||
];
|
||||
}
|
||||
|
||||
private function kondisiTanamanChart(array $filters): array
|
||||
{
|
||||
$rows = $this->applyTanamanTableFilters(DB::table('map_tanaman'), $filters)
|
||||
@@ -572,6 +824,415 @@ class IndikatorPertanianController extends Controller
|
||||
];
|
||||
}
|
||||
|
||||
private function tanamanProduktifCount(array $filters): int
|
||||
{
|
||||
return (int) $this->applyTanamanTableFilters(DB::table('map_tanaman'), $filters)
|
||||
->whereNull('map_tanaman.deleted_at')
|
||||
->where('map_tanaman.kondisi_tanaman', 'baik')
|
||||
->count('map_tanaman.id');
|
||||
}
|
||||
|
||||
private function batchPanenCount(array $filters): int
|
||||
{
|
||||
$produksiCount = $this->applyProduksiFilters(Produksi::query(), $filters)->count();
|
||||
$surveyPanenCount = $this->applySurveyPanenFilters(SurveyPanen::query(), $filters)->count();
|
||||
|
||||
return (int) $produksiCount + (int) $surveyPanenCount;
|
||||
}
|
||||
|
||||
private function productionSummaryDescription(array $filters): string
|
||||
{
|
||||
$chart = $this->produksiPerKomoditasChart($filters);
|
||||
|
||||
if (empty($chart['series'])) {
|
||||
return 'Belum ada produksi aktual per komoditas';
|
||||
}
|
||||
|
||||
return collect($chart['labels'])
|
||||
->map(fn ($label, $index) => $label.': '.number_format((float) ($chart['series'][$index] ?? 0), 0, ',', '.').' kg')
|
||||
->take(3)
|
||||
->implode(' | ');
|
||||
}
|
||||
|
||||
private function commodityFocusData(array $filters): array
|
||||
{
|
||||
$focusCommodities = ['Kopi', 'Kakao', 'Vanili', 'Kemiri'];
|
||||
$rows = $this->applyLahanFilters(Lahan::query(), $filters)
|
||||
->leftJoin('master_komoditas as k', 'k.id', '=', 'map_lahan.komoditas_id')
|
||||
->whereIn(DB::raw('LOWER(k.nama)'), collect($focusCommodities)->map(fn ($name) => strtolower($name))->all())
|
||||
->selectRaw('k.nama as label')
|
||||
->selectRaw('COUNT(map_lahan.id) as total')
|
||||
->groupBy('k.nama')
|
||||
->get()
|
||||
->mapWithKeys(fn ($row) => [strtolower((string) $row->label) => (int) $row->total]);
|
||||
|
||||
return collect($focusCommodities)
|
||||
->map(fn ($name) => [
|
||||
'label' => $name,
|
||||
'total' => $rows[strtolower($name)] ?? 0,
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
private function farmMapFeatures(array $filters): array
|
||||
{
|
||||
return $this->applyLahanFilters(
|
||||
Lahan::query()->with(['desaKelurahan.kecamatan', 'komoditas']),
|
||||
$filters
|
||||
)
|
||||
->where(function ($query) {
|
||||
$query
|
||||
->whereNotNull('lat')
|
||||
->orWhereNotNull('long')
|
||||
->orWhereNotNull('path');
|
||||
})
|
||||
->limit(250)
|
||||
->get()
|
||||
->map(fn (Lahan $lahan) => [
|
||||
'id' => $lahan->id,
|
||||
'name' => $lahan->nama,
|
||||
'commodity' => $lahan->komoditas?->nama ?? 'Belum ditentukan',
|
||||
'area' => (float) $lahan->luas_lahan,
|
||||
'village' => $lahan->desaKelurahan?->nama,
|
||||
'district' => $lahan->desaKelurahan?->kecamatan?->nama,
|
||||
'lat' => is_numeric($lahan->lat) ? (float) $lahan->lat : null,
|
||||
'lng' => is_numeric($lahan->long) ? (float) $lahan->long : null,
|
||||
'path' => is_array($lahan->path) ? $lahan->path : null,
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
private function mapLevelData(array $filters): array
|
||||
{
|
||||
$lahanSummaryByDesa = $this->applyLahanFilters(Lahan::query(), $filters)
|
||||
->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();
|
||||
|
||||
$desaIds = $lahanSummaryByDesa->pluck('desa_kelurahan_id')->filter()->unique()->values();
|
||||
|
||||
if ($filters['desa'] !== 'all') {
|
||||
$desaIds->push($filters['desa']);
|
||||
}
|
||||
|
||||
$desaList = DesaKelurahan::query()
|
||||
->select('id', 'nama', 'path', 'kecamatan_id')
|
||||
->when($desaIds->isNotEmpty(), fn ($query) => $query->whereIn('id', $desaIds->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');
|
||||
|
||||
$geoProvinsi = [];
|
||||
$geoKabupaten = [];
|
||||
$geoKecamatan = [];
|
||||
$geoDesa = [];
|
||||
$geoLahan = [];
|
||||
|
||||
foreach ($lahanSummaryByDesa as $summary) {
|
||||
$desa = $desaList->get($summary->desa_kelurahan_id);
|
||||
$kec = $desa?->kecamatan;
|
||||
$kab = $kec?->kabupatenKota;
|
||||
$prov = $kab?->provinsi;
|
||||
$totalLahan = (int) $summary->total_lahan;
|
||||
$totalLuas = (float) $summary->total_luas;
|
||||
|
||||
if ($prov && $prov->path) {
|
||||
$this->accumulateGeoLevel($geoProvinsi, $prov->id, null, $prov->nama, $prov->path, $totalLahan, $totalLuas);
|
||||
}
|
||||
|
||||
if ($kab && $kab->path) {
|
||||
$this->accumulateGeoLevel($geoKabupaten, $kab->id, $prov?->id, $kab->nama, $kab->path, $totalLahan, $totalLuas);
|
||||
}
|
||||
|
||||
if ($kec && $kec->path) {
|
||||
$this->accumulateGeoLevel($geoKecamatan, $kec->id, $kab?->id, $kec->nama, $kec->path, $totalLahan, $totalLuas);
|
||||
}
|
||||
|
||||
if ($desa && $desa->path) {
|
||||
$this->accumulateGeoLevel($geoDesa, $desa->id, $kec?->id, $desa->nama, $desa->path, $totalLahan, $totalLuas);
|
||||
}
|
||||
}
|
||||
|
||||
$this->ensureSelectedRegionOnMap($filters, $geoProvinsi, $geoKabupaten, $geoKecamatan, $geoDesa);
|
||||
|
||||
$mapLahan = $this->applyLahanFilters(
|
||||
Lahan::query()->with([
|
||||
'profile:id,nama',
|
||||
'petani:id,nama',
|
||||
'komoditas:id,nama',
|
||||
'tanaman:id,lahan_id,komoditas_id',
|
||||
'tanaman.komoditas:id,nama',
|
||||
]),
|
||||
$filters
|
||||
)
|
||||
->whereNotNull('path')
|
||||
->orderByDesc('updated_at')
|
||||
->limit(500)
|
||||
->get();
|
||||
|
||||
foreach ($mapLahan as $lahan) {
|
||||
if (! $this->hasMapPolygon($lahan->path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tanaman = $lahan->tanaman
|
||||
->map(fn ($tanaman) => $tanaman->komoditas?->nama)
|
||||
->filter()
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
if ($tanaman->isEmpty() && $lahan->komoditas?->nama) {
|
||||
$tanaman = collect([$lahan->komoditas->nama]);
|
||||
}
|
||||
|
||||
$geoLahan[] = [
|
||||
'id' => $lahan->id,
|
||||
'parent_id' => $lahan->desa_kelurahan_id,
|
||||
'polygon' => $lahan->path,
|
||||
'nama_petani' => $lahan->petani?->nama ?? $lahan->profile?->nama ?? 'N/A',
|
||||
'luas' => (float) $lahan->luas_lahan,
|
||||
'tanaman' => $tanaman,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'provinsi' => array_values($geoProvinsi),
|
||||
'kabupaten' => array_values($geoKabupaten),
|
||||
'kecamatan' => array_values($geoKecamatan),
|
||||
'desa' => array_values($geoDesa),
|
||||
'lahan' => $geoLahan,
|
||||
];
|
||||
}
|
||||
|
||||
private function accumulateGeoLevel(array &$bucket, int|string $id, int|string|null $parentId, string $name, mixed $polygon, int $totalLahan, float $totalLuas): void
|
||||
{
|
||||
if (! isset($bucket[$id])) {
|
||||
$bucket[$id] = [
|
||||
'id' => $id,
|
||||
'parent_id' => $parentId,
|
||||
'nama' => $name,
|
||||
'polygon' => $polygon,
|
||||
'total_lahan' => 0,
|
||||
'total_luas' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$bucket[$id]['total_lahan'] += $totalLahan;
|
||||
$bucket[$id]['total_luas'] += $totalLuas;
|
||||
}
|
||||
|
||||
private function ensureSelectedRegionOnMap(array $filters, array &$geoProvinsi, array &$geoKabupaten, array &$geoKecamatan, array &$geoDesa): void
|
||||
{
|
||||
$kabupaten = $filters['kabupaten'] !== 'all'
|
||||
? KabupatenKota::query()->with('provinsi:id,nama,path')->find($filters['kabupaten'])
|
||||
: null;
|
||||
|
||||
if ($kabupaten && $kabupaten->path && ! isset($geoKabupaten[$kabupaten->id])) {
|
||||
$this->accumulateGeoLevel($geoKabupaten, $kabupaten->id, $kabupaten->provinsi_id, $kabupaten->nama, $kabupaten->path, 0, 0);
|
||||
}
|
||||
|
||||
if ($kabupaten?->provinsi && $kabupaten->provinsi->path && ! isset($geoProvinsi[$kabupaten->provinsi->id])) {
|
||||
$this->accumulateGeoLevel($geoProvinsi, $kabupaten->provinsi->id, null, $kabupaten->provinsi->nama, $kabupaten->provinsi->path, 0, 0);
|
||||
}
|
||||
|
||||
if ($filters['kecamatan'] !== 'all') {
|
||||
$kecamatan = Kecamatan::query()->find($filters['kecamatan']);
|
||||
|
||||
if ($kecamatan && $kecamatan->path && ! isset($geoKecamatan[$kecamatan->id])) {
|
||||
$this->accumulateGeoLevel($geoKecamatan, $kecamatan->id, $kecamatan->kabupaten_kota_id, $kecamatan->nama, $kecamatan->path, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if ($filters['desa'] !== 'all') {
|
||||
$desa = DesaKelurahan::query()->find($filters['desa']);
|
||||
|
||||
if ($desa && $desa->path && ! isset($geoDesa[$desa->id])) {
|
||||
$this->accumulateGeoLevel($geoDesa, $desa->id, $desa->kecamatan_id, $desa->nama, $desa->path, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function hasMapPolygon(mixed $path): bool
|
||||
{
|
||||
if (is_array($path)) {
|
||||
return ! empty($path);
|
||||
}
|
||||
|
||||
if (! is_string($path) || blank($path) || strtolower($path) === 'null') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$decoded = json_decode($path, true);
|
||||
|
||||
return json_last_error() === JSON_ERROR_NONE && ! empty($decoded) && $decoded !== 'null';
|
||||
}
|
||||
|
||||
private function shadowTreeSummary(array $filters): array
|
||||
{
|
||||
$items = $this->applyLahanFilters(Lahan::query(), $filters)
|
||||
->get(['tanaman_penaung'])
|
||||
->flatMap(fn (Lahan $lahan) => $this->arrayValues($lahan->tanaman_penaung))
|
||||
->filter()
|
||||
->values();
|
||||
|
||||
return [
|
||||
'total' => $items->count(),
|
||||
'types' => $items->unique(fn ($item) => strtolower((string) $item))->count(),
|
||||
];
|
||||
}
|
||||
|
||||
private function companionPlantSummary(array $filters): array
|
||||
{
|
||||
$items = $this->applyLahanFilters(Lahan::query(), $filters)
|
||||
->get(['id', 'komoditas_lain'])
|
||||
->flatMap(fn (Lahan $lahan) => $this->arrayValues($lahan->komoditas_lain))
|
||||
->filter()
|
||||
->values();
|
||||
|
||||
return [
|
||||
'total' => $items->count(),
|
||||
'types' => $items->unique(fn ($item) => strtolower((string) $item))->count(),
|
||||
];
|
||||
}
|
||||
|
||||
private function plantDiversityAverage(array $filters): float
|
||||
{
|
||||
$lahan = $this->applyLahanFilters(Lahan::query(), $filters)
|
||||
->get(['komoditas_id', 'komoditas_lain', 'tanaman_penaung']);
|
||||
|
||||
if ($lahan->isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (float) $lahan->avg(function (Lahan $item) {
|
||||
$total = $item->komoditas_id ? 1 : 0;
|
||||
$total += count($this->arrayValues($item->komoditas_lain));
|
||||
$total += count($this->arrayValues($item->tanaman_penaung));
|
||||
|
||||
return $total;
|
||||
});
|
||||
}
|
||||
|
||||
private function sourceWaterSummary(array $filters): string
|
||||
{
|
||||
$chart = $this->sumberAirChart($filters);
|
||||
|
||||
if (empty($chart['labels'])) {
|
||||
return 'Belum ada data';
|
||||
}
|
||||
|
||||
return collect($chart['labels'])
|
||||
->map(fn ($label, $index) => $label.' ('.number_format((int) ($chart['series'][$index] ?? 0), 0, ',', '.').')')
|
||||
->take(2)
|
||||
->implode(', ');
|
||||
}
|
||||
|
||||
private function waterManagementSummary(array $filters): string
|
||||
{
|
||||
$rows = $this->applyPraktikBudidayaFilters(PraktikBudidaya::query(), $filters)
|
||||
->whereNotNull('kondisi_pengelolaan_air')
|
||||
->pluck('kondisi_pengelolaan_air')
|
||||
->map(fn ($value) => $this->waterManagementShortLabel((int) $value))
|
||||
->countBy()
|
||||
->sortDesc();
|
||||
|
||||
return $this->topCountSummary($rows);
|
||||
}
|
||||
|
||||
private function seasonalWaterNeedSummary(array $filters): string
|
||||
{
|
||||
$rows = $this->applyPraktikBudidayaFilters(PraktikBudidaya::query(), $filters)
|
||||
->whereNotNull('kebutuhan_air_permusim')
|
||||
->pluck('kebutuhan_air_permusim')
|
||||
->map(fn ($value) => $this->shortEnumLabel((string) $value))
|
||||
->countBy()
|
||||
->sortDesc();
|
||||
|
||||
return $this->topCountSummary($rows);
|
||||
}
|
||||
|
||||
private function soilConservationSummary(array $filters): string
|
||||
{
|
||||
$rows = $this->applyPraktikBudidayaFilters(PraktikBudidaya::query(), $filters)
|
||||
->get(['kebiasaan_konservasi_tanah'])
|
||||
->flatMap(fn (PraktikBudidaya $praktik) => $this->arrayValues($praktik->kebiasaan_konservasi_tanah))
|
||||
->filter()
|
||||
->countBy()
|
||||
->sortDesc();
|
||||
|
||||
return $this->topCountSummary($rows);
|
||||
}
|
||||
|
||||
private function soilConservationScoreSummary(array $filters): string
|
||||
{
|
||||
$average = $this->applyPraktikBudidayaFilters(PraktikBudidaya::query(), $filters)
|
||||
->whereNotNull('nilai_konservasi_tanah')
|
||||
->avg('nilai_konservasi_tanah');
|
||||
|
||||
if ($average === null) {
|
||||
return 'Belum ada data';
|
||||
}
|
||||
|
||||
return 'Rata-rata '.number_format((float) $average, 1, ',', '.');
|
||||
}
|
||||
|
||||
private function topCountSummary($rows): string
|
||||
{
|
||||
if ($rows->isEmpty()) {
|
||||
return 'Belum ada data';
|
||||
}
|
||||
|
||||
return $rows
|
||||
->map(fn ($count, $label) => $label.' ('.number_format((int) $count, 0, ',', '.').')')
|
||||
->take(2)
|
||||
->implode(', ');
|
||||
}
|
||||
|
||||
private function waterManagementShortLabel(int $value): string
|
||||
{
|
||||
return match (KondisiPengelolaanAir::tryFrom($value)) {
|
||||
KondisiPengelolaanAir::KURANG_TIDAK_ADA_UPAYA => 'Kurang, tanpa upaya',
|
||||
KondisiPengelolaanAir::KURANG_TIDAK_SUDAH_BERUPAYA => 'Kurang, ada upaya',
|
||||
KondisiPengelolaanAir::CUKUP_DARI_HUJAN => 'Cukup dari hujan',
|
||||
KondisiPengelolaanAir::CUKUP_IRIGASI_PINJAM => 'Cukup, irigasi pinjam',
|
||||
KondisiPengelolaanAir::CUKUP_IRIGASI_SENDIRI => 'Cukup, irigasi sendiri',
|
||||
default => 'Belum ditentukan',
|
||||
};
|
||||
}
|
||||
|
||||
private function arrayValues(mixed $value): array
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return collect($value)
|
||||
->flatten()
|
||||
->filter(fn ($item) => filled($item))
|
||||
->map(fn ($item) => is_scalar($item) ? (string) $item : json_encode($item))
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
if (is_string($value) && filled($value) && strtolower($value) !== 'null') {
|
||||
$decoded = json_decode($value, true);
|
||||
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
return $this->arrayValues($decoded);
|
||||
}
|
||||
|
||||
return [$value];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function chartFromRows($rows, callable $labelResolver): array
|
||||
{
|
||||
return [
|
||||
@@ -676,6 +1337,13 @@ class IndikatorPertanianController extends Controller
|
||||
->when($filters['komoditas'] !== 'all', fn ($query) => $query->where('komoditas_id', $filters['komoditas']));
|
||||
}
|
||||
|
||||
private function applySurveyPanenFilters($query, array $filters)
|
||||
{
|
||||
return $query->whereHas('petani', function ($petani) use ($filters) {
|
||||
$this->applyPetaniFilters($petani, $filters);
|
||||
});
|
||||
}
|
||||
|
||||
private function applyTanamanTableFilters($query, array $filters)
|
||||
{
|
||||
return $query
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Web\Dashboard;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\AlphaVantageMarket;
|
||||
use Illuminate\Contracts\Cache\Repository;
|
||||
use Illuminate\Http\Client\Pool;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Throwable;
|
||||
|
||||
class InternationalMarketController extends Controller
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
abort_unless(Gate::allows('super_admin') || Gate::allows('admin') || Gate::allows('fasilitator'), 403);
|
||||
|
||||
// File cache keeps external quotes independent of the application database.
|
||||
$cache = Cache::store('file');
|
||||
$marketData = $cache->remember('market.yahoo.quotes.v3', 300, function () use ($cache) {
|
||||
$commodities = [
|
||||
['name' => 'Kopi Arabica', 'symbol' => 'KC=F'],
|
||||
['name' => 'Kakao (Cocoa)', 'symbol' => 'CC=F'],
|
||||
];
|
||||
|
||||
try {
|
||||
$responses = Http::pool(function (Pool $pool) use ($commodities) {
|
||||
return array_map(fn ($commodity) => $pool->as($commodity['symbol'])
|
||||
->acceptJson()->withHeaders(['User-Agent' => 'Mozilla/5.0'])
|
||||
->withOptions(['verify' => config('services.yahoo_finance.ca_bundle') ?: true])
|
||||
->connectTimeout(3)->timeout(8)
|
||||
->get('https://query1.finance.yahoo.com/v8/finance/chart/'.rawurlencode($commodity['symbol']), [
|
||||
'interval' => '1d', 'range' => '5d',
|
||||
]), [...$commodities, ['symbol' => 'IDR=X']]);
|
||||
});
|
||||
} catch (Throwable $exception) {
|
||||
report($exception);
|
||||
$responses = [];
|
||||
}
|
||||
|
||||
$exchangeRate = $cache->get('market.yahoo.usd_idr');
|
||||
if (is_array($exchangeRate)) {
|
||||
$exchangeRate['stale'] = true;
|
||||
}
|
||||
$rateResponse = $responses['IDR=X'] ?? null;
|
||||
if ($rateResponse instanceof \Illuminate\Http\Client\Response && $rateResponse->successful()) {
|
||||
$meta = $rateResponse->json('chart.result.0.meta');
|
||||
if (is_array($meta) && ($meta['symbol'] ?? null) === 'IDR=X'
|
||||
&& ($meta['currency'] ?? null) === 'IDR'
|
||||
&& is_numeric($meta['regularMarketPrice'] ?? null) && $meta['regularMarketPrice'] > 0
|
||||
&& is_numeric($meta['regularMarketTime'] ?? null)) {
|
||||
$exchangeRate = [
|
||||
'value' => (float) $meta['regularMarketPrice'],
|
||||
'quoted_at' => gmdate('c', (int) $meta['regularMarketTime']),
|
||||
'source' => 'Yahoo Finance',
|
||||
'source_url' => 'https://finance.yahoo.com/quote/IDR%3DX/',
|
||||
'stale' => false,
|
||||
];
|
||||
$cache->put('market.yahoo.usd_idr', $exchangeRate, now()->addDays(7));
|
||||
}
|
||||
}
|
||||
|
||||
$quotes = array_map(function ($commodity) use ($responses, $cache) {
|
||||
$symbol = $commodity['symbol'];
|
||||
$key = 'market.yahoo.last.'.rawurlencode($symbol);
|
||||
$quote = $commodity + [
|
||||
'source' => 'Yahoo Finance',
|
||||
'source_url' => 'https://finance.yahoo.com/quote/'.rawurlencode($symbol).'/',
|
||||
'price' => null, 'currency' => null, 'unit' => null,
|
||||
'quoted_at' => null, 'stale' => false,
|
||||
];
|
||||
|
||||
try {
|
||||
$response = $responses[$symbol] ?? null;
|
||||
if (!$response instanceof \Illuminate\Http\Client\Response || !$response->successful()) {
|
||||
throw new \RuntimeException('Quote unavailable');
|
||||
}
|
||||
$meta = $response->json('chart.result.0.meta');
|
||||
if ($response->json('chart.error') || !is_array($meta)
|
||||
|| ($meta['symbol'] ?? null) !== $symbol
|
||||
|| !is_numeric($meta['regularMarketPrice'] ?? null)
|
||||
|| (float) $meta['regularMarketPrice'] <= 0
|
||||
|| !is_numeric($meta['regularMarketTime'] ?? null)
|
||||
|| !is_string($meta['currency'] ?? null)) {
|
||||
throw new \RuntimeException('Invalid quote');
|
||||
}
|
||||
|
||||
$quote['price'] = (float) $meta['regularMarketPrice'];
|
||||
$quote['currency'] = $meta['currency'];
|
||||
// Yahoo quotes Arabica in US cents/lb; cocoa and robusta in USD/tonne.
|
||||
// Apply the unit only when the API currency matches that convention.
|
||||
$quote['unit'] = match (true) {
|
||||
$symbol === 'KC=F' && $meta['currency'] === 'USX' => 'sen AS / lb',
|
||||
$symbol === 'CC=F' && $meta['currency'] === 'USD' => 'USD / ton metrik',
|
||||
default => $meta['currency'].' (satuan mengikuti sumber)',
|
||||
};
|
||||
$quote['quoted_at'] = gmdate('c', (int) $meta['regularMarketTime']);
|
||||
$cache->put($key, $quote, now()->addDays(7));
|
||||
|
||||
return $quote;
|
||||
} catch (Throwable $exception) {
|
||||
$previous = $cache->get($key);
|
||||
if (is_array($previous)) {
|
||||
$previous['stale'] = true;
|
||||
return $previous;
|
||||
}
|
||||
return $quote;
|
||||
}
|
||||
}, $commodities);
|
||||
|
||||
return ['quotes' => $quotes, 'exchange_rate' => $exchangeRate];
|
||||
});
|
||||
|
||||
$quotes = $marketData['quotes'];
|
||||
$exchangeRate = $marketData['exchange_rate'];
|
||||
foreach ($quotes as &$quote) {
|
||||
$quote['id'] = 'yahoo:'.$quote['symbol'];
|
||||
$quote['description'] = 'Estimasi konversi harga kontrak berjangka internasional.';
|
||||
$quote['period'] = $quote['quoted_at'];
|
||||
$quote['frequency'] = 'market';
|
||||
}
|
||||
unset($quote);
|
||||
$quotes = array_merge($quotes, $this->imfQuotes($cache), app(AlphaVantageMarket::class)->quotes($cache));
|
||||
$quotes = array_values(array_filter($quotes, fn ($quote) => is_numeric($quote['price']) && is_finite((float) $quote['price']) && $quote['price'] > 0));
|
||||
foreach ($quotes as &$quote) {
|
||||
// All active providers quote either US cents/lb or USD/metric tonne.
|
||||
$usdPerKg = match ($quote['unit']) {
|
||||
'sen AS / lb' => $quote['price'] / 100 / 0.45359237,
|
||||
'USD / ton metrik' => $quote['price'] / 1000,
|
||||
default => null,
|
||||
};
|
||||
$quote['price_idr_per_kg'] = $exchangeRate && $usdPerKg !== null
|
||||
? round($usdPerKg * $exchangeRate['value'], 2) : null;
|
||||
unset($quote['usd_per_kg']); // Old Alpha cache entries may contain this field.
|
||||
}
|
||||
unset($quote);
|
||||
|
||||
return response()->json([
|
||||
'quotes' => $quotes,
|
||||
'exchange_rate' => $exchangeRate,
|
||||
])->header('Cache-Control', 'no-store');
|
||||
}
|
||||
|
||||
private function imfQuotes(Repository $cache): array
|
||||
{
|
||||
// IMF observations are monthly averages; fetch at most once a day.
|
||||
return $cache->remember('market.imf.monthly.v1', 86400, function () use ($cache) {
|
||||
try {
|
||||
$response = Http::withHeaders(['Accept' => 'text/csv', 'User-Agent' => 'Mozilla/5.0'])
|
||||
->withOptions(['verify' => config('services.yahoo_finance.ca_bundle') ?: true])
|
||||
->connectTimeout(3)->timeout(10)
|
||||
->get('https://api.imf.org/external/sdmx/2.1/data/IMF.RES,PCPS/G001.PCOFFOTM+PCOFFROB+PCOCO.USD.M', [
|
||||
'startPeriod' => now()->subYears(2)->format('Y-m'), 'format' => 'csvfile',
|
||||
])->throw();
|
||||
$stream = fopen('php://memory', 'r+');
|
||||
try {
|
||||
fwrite($stream, $response->body());
|
||||
rewind($stream);
|
||||
$headers = fgetcsv($stream, 0, ',', '"', '');
|
||||
if (!$headers || array_diff(['COUNTRY', 'INDICATOR', 'DATA_TRANSFORMATION', 'FREQUENCY', 'TIME_PERIOD', 'OBS_VALUE', 'SCALE'], $headers)) {
|
||||
throw new \RuntimeException('Unexpected IMF CSV format');
|
||||
}
|
||||
$latest = [];
|
||||
while (($values = fgetcsv($stream, 0, ',', '"', '')) !== false) {
|
||||
if (count($values) !== count($headers)) continue;
|
||||
$row = array_combine($headers, $values);
|
||||
if ($row['COUNTRY'] !== 'G001' || $row['DATA_TRANSFORMATION'] !== 'USD' || $row['FREQUENCY'] !== 'M'
|
||||
|| $row['SCALE'] !== '0' || !is_numeric($row['OBS_VALUE']) || $row['OBS_VALUE'] <= 0
|
||||
|| !preg_match('/^\d{4}-M(0[1-9]|1[0-2])$/', $row['TIME_PERIOD'])) continue;
|
||||
$indicator = $row['INDICATOR'];
|
||||
if (!isset($latest[$indicator]) || $row['TIME_PERIOD'] > $latest[$indicator]['TIME_PERIOD']) {
|
||||
$latest[$indicator] = $row;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fclose($stream);
|
||||
}
|
||||
$definitions = [
|
||||
'PCOFFOTM' => ['Kopi Arabica', 'USX', 'sen AS / lb', 'Biji kopi Arabica (Other Milds). Estimasi konversi harga acuan rata-rata bulanan IMF.'],
|
||||
'PCOFFROB' => ['Kopi Robusta', 'USX', 'sen AS / lb', 'Biji kopi Robusta. Estimasi konversi harga acuan rata-rata bulanan IMF.'],
|
||||
'PCOCO' => ['Kakao (Cocoa)', 'USD', 'USD / ton metrik', 'Biji kakao. Estimasi konversi harga acuan rata-rata bulanan IMF.'],
|
||||
];
|
||||
$quotes = [];
|
||||
foreach ($definitions as $indicator => [$name, $currency, $unit, $description]) {
|
||||
if (!isset($latest[$indicator])) continue;
|
||||
$row = $latest[$indicator];
|
||||
$quotes[] = [
|
||||
'id' => 'imf:'.$indicator, 'symbol' => $indicator, 'name' => $name,
|
||||
'price' => (float) $row['OBS_VALUE'], 'currency' => $currency, 'unit' => $unit,
|
||||
'source' => 'IMF · PCPS', 'source_url' => 'https://data.imf.org/Datasets/PCPS',
|
||||
'description' => $description, 'frequency' => 'monthly',
|
||||
'period' => str_replace('-M', '-', $row['TIME_PERIOD']), 'stale' => false,
|
||||
];
|
||||
}
|
||||
if (!$quotes) throw new \RuntimeException('No valid IMF observations');
|
||||
$cache->put('market.imf.last.v1', $quotes, now()->addDays(30));
|
||||
return $quotes;
|
||||
} catch (Throwable $exception) {
|
||||
report($exception);
|
||||
return array_map(function ($quote) {
|
||||
$quote['stale'] = true;
|
||||
return $quote;
|
||||
}, $cache->get('market.imf.last.v1', []));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -489,34 +489,34 @@ class SurveyPetaniController extends Controller
|
||||
{
|
||||
return [
|
||||
[
|
||||
'color' => '#28a745',
|
||||
'bg' => 'rgba(40,167,69,.15)',
|
||||
'tx' => '#28a745',
|
||||
'color' => '#6FD68F',
|
||||
'bg' => 'rgba(111,214,143,.24)',
|
||||
'tx' => '#6FD68F',
|
||||
],
|
||||
[
|
||||
'color' => '#007bff',
|
||||
'bg' => 'rgba(0,123,255,.15)',
|
||||
'tx' => '#007bff',
|
||||
'color' => '#5FC7D6',
|
||||
'bg' => 'rgba(95,199,214,.24)',
|
||||
'tx' => '#5FC7D6',
|
||||
],
|
||||
[
|
||||
'color' => '#fd7e14',
|
||||
'bg' => 'rgba(253,126,20,.15)',
|
||||
'tx' => '#fd7e14',
|
||||
'color' => '#FFB46F',
|
||||
'bg' => 'rgba(255,180,111,.26)',
|
||||
'tx' => '#FFB46F',
|
||||
],
|
||||
[
|
||||
'color' => '#dc3545',
|
||||
'bg' => 'rgba(220,53,69,.15)',
|
||||
'tx' => '#dc3545',
|
||||
'color' => '#F47C86',
|
||||
'bg' => 'rgba(244,124,134,.26)',
|
||||
'tx' => '#F47C86',
|
||||
],
|
||||
[
|
||||
'color' => '#6f42c1',
|
||||
'bg' => 'rgba(111,66,193,.15)',
|
||||
'tx' => '#6f42c1',
|
||||
'color' => '#9D8EF0',
|
||||
'bg' => 'rgba(157,142,240,.26)',
|
||||
'tx' => '#9D8EF0',
|
||||
],
|
||||
[
|
||||
'color' => '#20c997',
|
||||
'bg' => 'rgba(32,201,151,.15)',
|
||||
'tx' => '#20c997',
|
||||
'color' => '#65D8BD',
|
||||
'bg' => 'rgba(101,216,189,.26)',
|
||||
'tx' => '#65D8BD',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class AksesPasarController extends Controller
|
||||
{
|
||||
$petani = Petani::findOrFail($petaniId);
|
||||
|
||||
$aksesPasar = AksesPasar::where('petani_id', $petaniId)->first();
|
||||
$aksesPasar = AksesPasar::where('petani_id', $petaniId)->latest()->first();
|
||||
$hargaJualPanen = HargaJualPanen::where('petani_id', $petaniId)->get();
|
||||
$hargaJualRows = collect([
|
||||
'Buah segar hasil petik',
|
||||
@@ -50,32 +50,40 @@ class AksesPasarController extends Controller
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $this->validateData($request);
|
||||
{
|
||||
$validated = $this->validateData($request);
|
||||
|
||||
DB::beginTransaction();
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
$aksesPasarData = $this->aksesPasarPayload($validated);
|
||||
try {
|
||||
$aksesPasarData = $this->aksesPasarPayload($validated);
|
||||
|
||||
$existingAksesPasar = AksesPasar::where('petani_id', $validated['petani_id'])->first();
|
||||
|
||||
if ($existingAksesPasar) {
|
||||
$aksesPasarData['updated_by'] = Auth::id();
|
||||
$existingAksesPasar->update($aksesPasarData);
|
||||
$aksesPasar = $existingAksesPasar;
|
||||
} else {
|
||||
$aksesPasarData['uid'] = 'AKP-' . Str::uuid();
|
||||
$aksesPasarData['created_by'] = Auth::id();
|
||||
$aksesPasarData['updated_by'] = Auth::id();
|
||||
|
||||
$aksesPasar = AksesPasar::create($aksesPasarData);
|
||||
|
||||
if ($request->has('harga_jual')) {
|
||||
$this->syncHargaJual($request->petani_id, $validated['harga_jual'] ?? [], Auth::id());
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
return redirect()->back()->with('success', 'Data Rantai Nilai dan Akses Pasar berhasil disimpan.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return redirect()->back()->withErrors(['error' => 'Gagal menyimpan data: ' . $e->getMessage()])->withInput();
|
||||
}
|
||||
|
||||
if ($request->has('harga_jual')) {
|
||||
$this->syncHargaJual($request->petani_id, $validated['harga_jual'] ?? [], Auth::id());
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
return redirect()->back()->with('success', 'Data Rantai Nilai dan Akses Pasar berhasil disimpan.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return redirect()->back()->withErrors(['error' => 'Gagal menyimpan data: ' . $e->getMessage()])->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Request $request, $petaniId, $id)
|
||||
{
|
||||
@@ -118,7 +126,8 @@ class AksesPasarController extends Controller
|
||||
'skorPengalamanBerhentiValues' => [
|
||||
SkorPengalaman::PERNAH_BERHENTI_TIDAK_ADA_AKSES->value,
|
||||
SkorPengalaman::PERNAH_BERHENTI_MUNGKIN_KEMBALI->value
|
||||
]
|
||||
],
|
||||
'sumberInfoLainnyaValue' => 'Lainnya',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -128,6 +137,17 @@ class AksesPasarController extends Controller
|
||||
|
||||
return $request->validate([
|
||||
'petani_id' => ['required', 'exists:petani,id'],
|
||||
|
||||
'tujuan_penjualan' => ['nullable', 'array'],
|
||||
'tujuan_penjualan.*' => ['string'],
|
||||
'tujuan_penjualan_lainnya' => [
|
||||
Rule::requiredIf(fn () => in_array('Lainnya', $request->input('tujuan_penjualan', []), true)),
|
||||
'nullable', 'string', 'max:255'
|
||||
],
|
||||
'sifat_hubungan_bisnis' => ['nullable', 'string', 'max:255'],
|
||||
'bentuk_penjualan' => ['nullable', 'string', 'max:255'],
|
||||
'frekuensi_penjualan' => ['nullable', 'string', 'max:255'],
|
||||
|
||||
'skor_pengalaman' => ['nullable', 'integer', Rule::in(SkorPengalaman::values())],
|
||||
'alasan_berhenti' => [
|
||||
Rule::requiredIf(fn () => in_array((int)$request->input('skor_pengalaman'), [
|
||||
@@ -144,6 +164,14 @@ class AksesPasarController extends Controller
|
||||
'nullable', 'string', 'max:255'
|
||||
],
|
||||
|
||||
'tahu_harga_sebelumnya' => ['nullable', 'string', 'max:255'],
|
||||
'sumber_info_harga' => ['nullable', 'array'],
|
||||
'sumber_info_harga.*' => ['string'],
|
||||
'sumber_info_harga_lainnya' => [
|
||||
Rule::requiredIf(fn () => in_array('Lainnya', $request->input('sumber_info_harga', []), true)),
|
||||
'nullable', 'string', 'max:255'
|
||||
],
|
||||
|
||||
'praktik_fermentasi' => ['nullable', 'string', Rule::in(PraktikFermentasi::values())],
|
||||
'proses_fermentasi' => [
|
||||
Rule::requiredIf(fn () => filled($request->input('praktik_fermentasi')) && $request->input('praktik_fermentasi') !== PraktikFermentasi::BELUM_PERNAH->value),
|
||||
@@ -168,6 +196,7 @@ class AksesPasarController extends Controller
|
||||
], [
|
||||
'petani_id.required' => 'Petani harus dipilih.',
|
||||
'petani_id.exists' => 'Petani tidak ditemukan.',
|
||||
'tujuan_penjualan_lainnya.required' => 'Keterangan saluran penjualan lainnya harus diisi.',
|
||||
'skor_pengalaman.integer' => 'Skor pengalaman harus berupa angka.',
|
||||
'skor_pengalaman.in' => 'Skor pengalaman tidak valid.',
|
||||
'alasan_berhenti.required' => 'Alasan berhenti harus diisi.',
|
||||
@@ -204,6 +233,10 @@ class AksesPasarController extends Controller
|
||||
{
|
||||
$payload = collect($validated)->except(['harga_jual'])->all();
|
||||
|
||||
if (!in_array('Lainnya', $payload['tujuan_penjualan'] ?? [], true)) {
|
||||
$payload['tujuan_penjualan_lainnya'] = null;
|
||||
}
|
||||
|
||||
if (!in_array((int)($payload['skor_pengalaman'] ?? 0), [
|
||||
SkorPengalaman::PERNAH_BERHENTI_TIDAK_ADA_AKSES->value,
|
||||
SkorPengalaman::PERNAH_BERHENTI_MUNGKIN_KEMBALI->value
|
||||
@@ -215,6 +248,10 @@ class AksesPasarController extends Controller
|
||||
$payload['saluran_terdahulu_lainnya'] = null;
|
||||
}
|
||||
|
||||
if (!in_array('Lainnya', $payload['sumber_info_harga'] ?? [], true)) {
|
||||
$payload['sumber_info_harga_lainnya'] = null;
|
||||
}
|
||||
|
||||
if (($payload['praktik_fermentasi'] ?? null) === PraktikFermentasi::BELUM_PERNAH->value) {
|
||||
$payload['proses_fermentasi'] = null;
|
||||
$payload['model_fermentasi_memungkinkan'] = null;
|
||||
@@ -223,7 +260,6 @@ class AksesPasarController extends Controller
|
||||
return $payload;
|
||||
}
|
||||
|
||||
|
||||
private function syncHargaJual($petaniId, array $hargaJualData, $createdBy, $updatedBy = null): void
|
||||
{
|
||||
HargaJualPanen::where('petani_id', $petaniId)->delete();
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Web\Produktivitas;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
@@ -31,21 +32,30 @@ class AsetKelompokTaniController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
private function redirectToSection(string $url, string $section, string $message)
|
||||
private function redirectToSection(string $url, string $section, string $message, ?string $nextSection = null)
|
||||
{
|
||||
return redirect($url . '#section-' . $section)->with([
|
||||
return redirect($url . '#section-' . ($nextSection ?? $section))->with([
|
||||
'success' => $message,
|
||||
'success_section' => $section,
|
||||
]);
|
||||
}
|
||||
|
||||
private function kegiatanUrl(KelompokTani $kelompokTani): string
|
||||
{
|
||||
return route('kelompok-tani.edit', $kelompokTani->id) . '?section=aktivitas-fungsi';
|
||||
}
|
||||
|
||||
private function nextAssetSection(KelompokTani $kelompokTani): string
|
||||
{
|
||||
return $kelompokTani->aset()->where('jenis_aset', 'Kas/simpanan kelompok')->where('status_aset', true)->exists()
|
||||
? 'kas' : 'status-bantuan';
|
||||
}
|
||||
|
||||
public function index(KelompokTani $kelompokTani)
|
||||
{
|
||||
$this->authorizeAccess();
|
||||
|
||||
$kelompokTani->load('aset');
|
||||
|
||||
return view('produktivitas.survey-kelompok-tani.aset-keuangan', compact('kelompokTani'));
|
||||
return redirect($this->kegiatanUrl($kelompokTani) . '#section-aset-baku');
|
||||
}
|
||||
|
||||
public function update(Request $request, KelompokTani $kelompokTani)
|
||||
@@ -56,11 +66,26 @@ class AsetKelompokTaniController extends Controller
|
||||
'aset' => ['required', 'array'],
|
||||
'aset.*.jenis_aset' => ['required', Rule::in(self::JENIS_ASET_BAKU)],
|
||||
'aset.*.status_aset' => ['nullable', 'boolean'],
|
||||
'aset.*.status_pengelolaan_aset' => ['nullable', 'string', 'max:100'],
|
||||
'aset.*.kondisi_aset' => ['nullable', 'string', 'max:100'],
|
||||
'aset.*.status_pengelolaan_aset' => ['required_if_accepted:aset.*.status_aset', 'nullable', 'string', 'max:100'],
|
||||
'aset.*.kondisi_aset' => ['required_if_accepted:aset.*.status_aset', 'nullable', 'string', 'max:100'],
|
||||
...$this->asetLainnyaRules(),
|
||||
], [
|
||||
'aset.*.status_pengelolaan_aset.required_if_accepted' => 'Status Pengelolaan wajib diisi jika aset dipilih Ada.',
|
||||
'aset.*.kondisi_aset.required_if_accepted' => 'Kondisi wajib diisi jika aset dipilih Ada.',
|
||||
'aset_lainnya.*.jenis_aset.not_in' => 'Jenis aset ini sudah ada di daftar baku di atas.',
|
||||
]);
|
||||
|
||||
foreach ($validated['aset'] as $item) {
|
||||
DB::transaction(function () use ($kelompokTani, $validated) {
|
||||
$this->saveAsetBaku($kelompokTani, $validated['aset']);
|
||||
$this->createAsetLainnya($kelompokTani, $validated['aset_lainnya'] ?? []);
|
||||
});
|
||||
|
||||
return $this->redirectToSection($this->kegiatanUrl($kelompokTani), 'aset-baku', 'Data Operasional berhasil disimpan.', $this->nextAssetSection($kelompokTani));
|
||||
}
|
||||
|
||||
private function saveAsetBaku(KelompokTani $kelompokTani, array $items): void
|
||||
{
|
||||
foreach ($items as $item) {
|
||||
$jenisAset = $item['jenis_aset'];
|
||||
$ada = (bool) ($item['status_aset'] ?? false);
|
||||
|
||||
@@ -82,8 +107,8 @@ class AsetKelompokTaniController extends Controller
|
||||
|
||||
$existing->update([
|
||||
'status_aset' => true,
|
||||
'status_pengelolaan_aset' => $item['status_pengelolaan_aset'] ?? $existing->status_pengelolaan_aset,
|
||||
'kondisi_aset' => $item['kondisi_aset'] ?? $existing->kondisi_aset,
|
||||
'status_pengelolaan_aset' => $item['status_pengelolaan_aset'],
|
||||
'kondisi_aset' => $item['kondisi_aset'],
|
||||
'deleted_by' => null,
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
@@ -94,30 +119,40 @@ class AsetKelompokTaniController extends Controller
|
||||
'uid' => 'AKT-' . strtoupper(Str::random(10)),
|
||||
'jenis_aset' => $jenisAset,
|
||||
'status_aset' => true,
|
||||
'status_pengelolaan_aset' => $item['status_pengelolaan_aset'] ?? '-',
|
||||
'kondisi_aset' => $item['kondisi_aset'] ?? '-',
|
||||
'status_pengelolaan_aset' => $item['status_pengelolaan_aset'],
|
||||
'kondisi_aset' => $item['kondisi_aset'],
|
||||
'created_by' => auth()->id(),
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->redirectToSection(route('kelompok-tani.aset-keuangan.index', $kelompokTani->id), 'aset-baku', 'Data aset produktif berhasil disimpan.');
|
||||
private function asetLainnyaRules(): array
|
||||
{
|
||||
return [
|
||||
'aset_lainnya' => ['nullable', 'array'],
|
||||
'aset_lainnya.*.jenis_aset' => ['nullable', 'string', 'max:255', Rule::notIn(self::JENIS_ASET_BAKU)],
|
||||
'aset_lainnya.*.status_pengelolaan_aset' => ['nullable', 'string', 'max:100'],
|
||||
'aset_lainnya.*.kondisi_aset' => ['nullable', 'string', 'max:100'],
|
||||
];
|
||||
}
|
||||
|
||||
public function store(Request $request, KelompokTani $kelompokTani)
|
||||
{
|
||||
$this->authorizeAccess();
|
||||
|
||||
$validated = $request->validate([
|
||||
'aset_lainnya' => ['nullable', 'array'],
|
||||
'aset_lainnya.*.jenis_aset' => ['nullable', 'string', 'max:255', Rule::notIn(self::JENIS_ASET_BAKU)],
|
||||
'aset_lainnya.*.status_pengelolaan_aset' => ['nullable', 'string', 'max:100'],
|
||||
'aset_lainnya.*.kondisi_aset' => ['nullable', 'string', 'max:100'],
|
||||
], [
|
||||
$validated = $request->validate($this->asetLainnyaRules(), [
|
||||
'aset_lainnya.*.jenis_aset.not_in' => 'Jenis aset ini sudah ada di daftar baku di atas.',
|
||||
]);
|
||||
|
||||
foreach ($validated['aset_lainnya'] ?? [] as $item) {
|
||||
DB::transaction(fn () => $this->createAsetLainnya($kelompokTani, $validated['aset_lainnya'] ?? []));
|
||||
|
||||
return $this->redirectToSection($this->kegiatanUrl($kelompokTani), 'aset-lainnya', 'Data Operasional berhasil ditambahkan.', $this->nextAssetSection($kelompokTani));
|
||||
}
|
||||
|
||||
private function createAsetLainnya(KelompokTani $kelompokTani, array $items): void
|
||||
{
|
||||
foreach ($items as $item) {
|
||||
$jenisAset = trim($item['jenis_aset'] ?? '');
|
||||
|
||||
if ($jenisAset === '') {
|
||||
@@ -134,8 +169,6 @@ class AsetKelompokTaniController extends Controller
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->redirectToSection(route('kelompok-tani.aset-keuangan.index', $kelompokTani->id), 'aset-lainnya', 'Aset lainnya berhasil ditambahkan.');
|
||||
}
|
||||
|
||||
public function destroy(KelompokTani $kelompokTani, AsetKelompokTani $aset)
|
||||
@@ -143,11 +176,11 @@ class AsetKelompokTaniController extends Controller
|
||||
$this->authorizeAccess();
|
||||
|
||||
abort_unless($aset->survey_kelompok_tani_id === $kelompokTani->id, 404);
|
||||
abort_if(in_array($aset->jenis_aset, self::JENIS_ASET_BAKU), 422, 'Aset baku diatur lewat checklist, bukan dihapus langsung.');
|
||||
abort_if(in_array($aset->jenis_aset, self::JENIS_ASET_BAKU), 422, 'Operasional: gunakan pilihan Ada/Tidak pada tabel untuk menghapus aset baku.');
|
||||
|
||||
$aset->update(['deleted_by' => auth()->id()]);
|
||||
$aset->delete();
|
||||
|
||||
return $this->redirectToSection(route('kelompok-tani.aset-keuangan.index', $kelompokTani->id), 'aset-lainnya', 'Aset lainnya berhasil dihapus.');
|
||||
return $this->redirectToSection($this->kegiatanUrl($kelompokTani), 'aset-lainnya', 'Data Operasional berhasil dihapus.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ namespace App\Http\Controllers\Web\Produktivitas;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
use App\Models\Pertanian\KelompokTani;
|
||||
use App\Models\Pertanian\BantuanKelompokTani;
|
||||
@@ -19,9 +21,9 @@ class BantuanKelompokTaniController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
private function redirectToSection(string $url, string $section, string $message)
|
||||
private function redirectToSection(string $url, string $section, string $message, ?string $nextSection = null)
|
||||
{
|
||||
return redirect($url . '#section-' . $section)->with([
|
||||
return redirect($url . '#section-' . ($nextSection ?? $section))->with([
|
||||
'success' => $message,
|
||||
'success_section' => $section,
|
||||
]);
|
||||
@@ -29,7 +31,7 @@ class BantuanKelompokTaniController extends Controller
|
||||
|
||||
private function redirectToSectionWithError(string $url, string $section, string $message)
|
||||
{
|
||||
return redirect($url . '#section-' . $section)->with([
|
||||
return redirect($url . '#kelompok-tani-notifications')->with([
|
||||
'error' => $message,
|
||||
'error_section' => $section,
|
||||
]);
|
||||
@@ -39,9 +41,72 @@ class BantuanKelompokTaniController extends Controller
|
||||
{
|
||||
$this->authorizeAccess();
|
||||
|
||||
$kelompokTani->load('bantuan');
|
||||
return redirect($this->operasionalUrl($kelompokTani) . '#section-status-bantuan');
|
||||
}
|
||||
|
||||
return view('produktivitas.survey-kelompok-tani.bantuan-penilaian', compact('kelompokTani'));
|
||||
private function operasionalUrl(KelompokTani $kelompokTani): string
|
||||
{
|
||||
return route('kelompok-tani.edit', $kelompokTani->id) . '?section=aktivitas-fungsi';
|
||||
}
|
||||
|
||||
private function hasRecordedAid(KelompokTani $kelompokTani): bool
|
||||
{
|
||||
return $kelompokTani->bantuan()->whereRaw("TRIM(nama_program) <> ''")->exists();
|
||||
}
|
||||
|
||||
private function bantuanRules(bool $pernah = true, bool $requireFirstRow = false): array
|
||||
{
|
||||
$exclude = Rule::excludeIf(!$pernah);
|
||||
|
||||
return [
|
||||
'bantuan' => [$exclude, Rule::requiredIf($requireFirstRow), 'nullable', 'array'],
|
||||
'bantuan.*' => [$exclude, 'array'],
|
||||
'bantuan.*.nama_program' => [$exclude, 'required_with:bantuan.*.penyelenggara,bantuan.*.bentuk_bantuan,bantuan.*.tahun', 'nullable', 'string', 'max:255'],
|
||||
'bantuan.*.penyelenggara' => [$exclude, 'nullable', 'string', 'max:255'],
|
||||
'bantuan.*.bentuk_bantuan' => [$exclude, 'nullable', 'string', 'max:255'],
|
||||
'bantuan.*.tahun' => [$exclude, 'nullable', 'integer', 'min:1900', 'max:' . date('Y')],
|
||||
'bantuan.0.nama_program' => [$exclude, Rule::requiredIf($requireFirstRow), 'required_with:bantuan.0.penyelenggara,bantuan.0.bentuk_bantuan,bantuan.0.tahun', 'nullable', 'string', 'max:255'],
|
||||
'bantuan.0.penyelenggara' => [$exclude, Rule::requiredIf($requireFirstRow), 'nullable', 'string', 'max:255'],
|
||||
'bantuan.0.bentuk_bantuan' => [$exclude, Rule::requiredIf($requireFirstRow), 'nullable', 'string', 'max:255'],
|
||||
'bantuan.0.tahun' => [$exclude, Rule::requiredIf($requireFirstRow), 'nullable', 'integer', 'min:1900', 'max:' . date('Y')],
|
||||
];
|
||||
}
|
||||
|
||||
private function bantuanMessages(): array
|
||||
{
|
||||
return [
|
||||
'pernah_mendapatkan_bantuan.required' => 'Pengalaman Akses Bantuan/Program wajib diisi.',
|
||||
'pernah_mendapatkan_bantuan.boolean' => 'Pilih Pernah atau Belum Pernah untuk Pengalaman Akses Bantuan/Program.',
|
||||
'bantuan.required' => 'Isi minimal satu data bantuan jika memilih Pernah.',
|
||||
'bantuan.0.nama_program.required' => 'Nama Program/Bantuan pada baris pertama wajib diisi.',
|
||||
'bantuan.0.penyelenggara.required' => 'Penyelenggara pada baris pertama wajib diisi.',
|
||||
'bantuan.0.bentuk_bantuan.required' => 'Bentuk Bantuan pada baris pertama wajib diisi.',
|
||||
'bantuan.0.tahun.required' => 'Tahun pada baris pertama wajib diisi.',
|
||||
'bantuan.*.nama_program.required_with' => 'Nama Program/Bantuan wajib diisi jika baris bantuan diisi.',
|
||||
];
|
||||
}
|
||||
|
||||
public function update(Request $request, KelompokTani $kelompokTani)
|
||||
{
|
||||
$this->authorizeAccess();
|
||||
|
||||
$validated = $request->validate([
|
||||
'pernah_mendapatkan_bantuan' => ['required', 'boolean'],
|
||||
...$this->bantuanRules(
|
||||
$request->boolean('pernah_mendapatkan_bantuan'),
|
||||
$request->boolean('pernah_mendapatkan_bantuan') && !$this->hasRecordedAid($kelompokTani)
|
||||
),
|
||||
], $this->bantuanMessages());
|
||||
|
||||
DB::transaction(function () use ($kelompokTani, $validated) {
|
||||
$kelompokTani->update([
|
||||
'pernah_mendapatkan_bantuan' => $validated['pernah_mendapatkan_bantuan'],
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
$this->createBantuan($kelompokTani, $validated['bantuan'] ?? []);
|
||||
});
|
||||
|
||||
return $this->redirectToSection($this->operasionalUrl($kelompokTani), 'status-bantuan', 'Data Operasional berhasil disimpan.');
|
||||
}
|
||||
|
||||
public function store(Request $request, KelompokTani $kelompokTani)
|
||||
@@ -50,37 +115,37 @@ class BantuanKelompokTaniController extends Controller
|
||||
|
||||
if (!$kelompokTani->pernah_mendapatkan_bantuan) {
|
||||
return $this->redirectToSectionWithError(
|
||||
route('kelompok-tani.bantuan-penilaian.index', $kelompokTani->id),
|
||||
$this->operasionalUrl($kelompokTani),
|
||||
'status-bantuan',
|
||||
'Set status jadi "Pernah" dulu sebelum menambahkan daftar bantuan/program.'
|
||||
'Operasional: pilih jawaban "Pernah" terlebih dahulu sebelum menambahkan data bantuan/program.'
|
||||
);
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'bantuan' => ['nullable', 'array'],
|
||||
'bantuan.*.nama_program' => ['nullable', 'string', 'max:255'],
|
||||
'bantuan.*.penyelenggara' => ['nullable', 'string', 'max:255'],
|
||||
'bantuan.*.bentuk_bantuan' => ['nullable', 'string', 'max:255'],
|
||||
'bantuan.*.tahun' => ['nullable', 'integer', 'min:1900', 'max:' . date('Y')],
|
||||
]);
|
||||
$validated = $request->validate($this->bantuanRules(true, !$this->hasRecordedAid($kelompokTani)), $this->bantuanMessages());
|
||||
|
||||
foreach ($validated['bantuan'] ?? [] as $item) {
|
||||
if (empty($item['nama_program']) && empty($item['penyelenggara']) && empty($item['bentuk_bantuan'])) {
|
||||
DB::transaction(fn () => $this->createBantuan($kelompokTani, $validated['bantuan'] ?? []));
|
||||
|
||||
return $this->redirectToSection($this->operasionalUrl($kelompokTani), 'status-bantuan', 'Data Operasional berhasil ditambahkan.');
|
||||
}
|
||||
|
||||
private function createBantuan(KelompokTani $kelompokTani, array $items): void
|
||||
{
|
||||
foreach ($items as $item) {
|
||||
$namaProgram = trim($item['nama_program'] ?? '');
|
||||
if ($namaProgram === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$kelompokTani->bantuan()->create([
|
||||
'uid' => 'BKT-' . strtoupper(Str::random(10)),
|
||||
'nama_program' => $item['nama_program'] ?? null,
|
||||
'penyelenggara' => $item['penyelenggara'] ?? null,
|
||||
'bentuk_bantuan' => $item['bentuk_bantuan'] ?? null,
|
||||
'nama_program' => $namaProgram,
|
||||
'penyelenggara' => $item['penyelenggara'] ?? '',
|
||||
'bentuk_bantuan' => $item['bentuk_bantuan'] ?? '',
|
||||
'tahun' => $item['tahun'] ?? null,
|
||||
'created_by' => auth()->id(),
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->redirectToSection(route('kelompok-tani.bantuan-penilaian.index', $kelompokTani->id), 'bantuan', 'Data bantuan berhasil ditambahkan.');
|
||||
}
|
||||
|
||||
public function destroy(KelompokTani $kelompokTani, BantuanKelompokTani $bantuan)
|
||||
@@ -89,9 +154,18 @@ class BantuanKelompokTaniController extends Controller
|
||||
|
||||
abort_unless($bantuan->survey_kelompok_tani_id === $kelompokTani->id, 404);
|
||||
|
||||
if ($kelompokTani->pernah_mendapatkan_bantuan && !$kelompokTani->bantuan()
|
||||
->where('id', '!=', $bantuan->id)->whereRaw("TRIM(nama_program) <> ''")->exists()) {
|
||||
return $this->redirectToSectionWithError(
|
||||
$this->operasionalUrl($kelompokTani),
|
||||
'status-bantuan',
|
||||
'Operasional: minimal satu data bantuan harus tersedia saat jawaban masih Pernah.'
|
||||
);
|
||||
}
|
||||
|
||||
$bantuan->update(['deleted_by' => auth()->id()]);
|
||||
$bantuan->delete();
|
||||
|
||||
return $this->redirectToSection(route('kelompok-tani.bantuan-penilaian.index', $kelompokTani->id), 'bantuan', 'Data bantuan berhasil dihapus.');
|
||||
return $this->redirectToSection($this->operasionalUrl($kelompokTani), 'status-bantuan', 'Data Operasional berhasil dihapus.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,7 @@ class PengurusKelompokTaniController extends Controller
|
||||
{
|
||||
$this->authorizeAccess();
|
||||
|
||||
$kelompokTani->load('pengurus');
|
||||
|
||||
return view('produktivitas.survey-kelompok-tani.pengurus-anggota', compact('kelompokTani'));
|
||||
return redirect(route('kelompok-tani.edit', $kelompokTani->id) . '#section-ringkasan');
|
||||
}
|
||||
|
||||
public function store(Request $request, KelompokTani $kelompokTani)
|
||||
@@ -64,7 +62,7 @@ class PengurusKelompokTaniController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->redirectToSection(route('kelompok-tani.pengurus-anggota.index', $kelompokTani->id), 'pengurus', 'Data pengurus berhasil ditambahkan.');
|
||||
return $this->redirectToSection(route('kelompok-tani.edit', $kelompokTani->id), 'pengurus', 'Data Profil berhasil ditambahkan.');
|
||||
}
|
||||
|
||||
public function destroy(KelompokTani $kelompokTani, PengurusKelompokTani $pengurus)
|
||||
@@ -76,6 +74,6 @@ class PengurusKelompokTaniController extends Controller
|
||||
$pengurus->update(['deleted_by' => auth()->id()]);
|
||||
$pengurus->delete();
|
||||
|
||||
return $this->redirectToSection(route('kelompok-tani.pengurus-anggota.index', $kelompokTani->id), 'pengurus', 'Data pengurus berhasil dihapus.');
|
||||
return $this->redirectToSection(route('kelompok-tani.edit', $kelompokTani->id), 'pengurus', 'Data Profil berhasil dihapus.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
use App\Models\Wilayah\Provinsi;
|
||||
use App\Models\Wilayah\KabupatenKota;
|
||||
use App\Models\Wilayah\Kecamatan;
|
||||
use App\Models\Pertanian\Petani;
|
||||
use App\Models\Wilayah\DesaKelurahan;
|
||||
use App\Models\Pertanian\Komoditas;
|
||||
@@ -35,6 +37,31 @@ class PetaniController extends Controller
|
||||
}
|
||||
|
||||
$provinsi = Provinsi::select('id', 'kode', 'nama')->orderBy('kode', 'asc')->get();
|
||||
$userRole = auth()->user()->role->value;
|
||||
$showFasilitatorColumn = in_array($userRole, ['super_admin', 'admin'], true);
|
||||
$defaultKabupaten = KabupatenKota::select('id', 'nama')
|
||||
->whereRaw('lower(nama) like ?', ['%ngada%'])
|
||||
->orderBy('nama')
|
||||
->first();
|
||||
$selectedKabupatenId = $request->query('kabupaten_kota_id', $defaultKabupaten?->id);
|
||||
$allowedSorts = ['nama_asc', 'nama_desc', 'luas_lahan_desc', 'luas_lahan_asc'];
|
||||
$requestedSort = $request->query('sort_by')
|
||||
?? match ($request->query('sort_nama')) {
|
||||
'asc' => 'nama_asc',
|
||||
'desc' => 'nama_desc',
|
||||
default => null,
|
||||
};
|
||||
|
||||
$filters = [
|
||||
'search' => $request->query('search'),
|
||||
'komoditas_id' => $request->query('komoditas_id'),
|
||||
'kabupaten_kota_id' => $selectedKabupatenId,
|
||||
'kecamatan_id' => $request->query('kecamatan_id'),
|
||||
'desa_kelurahan_id' => $request->query('desa_kelurahan_id'),
|
||||
'sort_by' => in_array($requestedSort, $allowedSorts, true)
|
||||
? $requestedSort
|
||||
: null,
|
||||
];
|
||||
|
||||
$query = Petani::with([
|
||||
'desaKelurahan:id,nama,kecamatan_id',
|
||||
@@ -43,29 +70,114 @@ class PetaniController extends Controller
|
||||
'desaKelurahan.kecamatan.kabupatenKota.provinsi:id,nama',
|
||||
'komoditas:id,nama',
|
||||
'skor:id,petani_id,skor_kesiapan_adopsi',
|
||||
'creator:id,name,role',
|
||||
])
|
||||
->orderBy('created_at', 'desc');
|
||||
->withSum('lahans as total_luas_lahan', 'luas_lahan');
|
||||
|
||||
if ($request->search) {
|
||||
$query->where(function ($query) use ($request) {
|
||||
$query->where('nama', 'like', '%' . $request->search . '%')
|
||||
->orWhere('ktp', 'like', '%' . $request->search . '%');
|
||||
$search = $request->filled('search') ? Str::lower($request->search) : null;
|
||||
if ($search) {
|
||||
$query->where(function ($query) use ($search) {
|
||||
$query->whereRaw('lower(nama) like ?', ["%{$search}%"])
|
||||
->orWhereRaw('lower(ktp) like ?', ["%{$search}%"]);
|
||||
});
|
||||
}
|
||||
|
||||
if (auth()->user()->role->value === 'fasilitator') {
|
||||
if ($request->filled('komoditas_id')) {
|
||||
$query->where('komoditas_id', $request->komoditas_id);
|
||||
}
|
||||
|
||||
if ($filters['kabupaten_kota_id']) {
|
||||
$query->whereHas('desaKelurahan.kecamatan', function ($query) use ($filters) {
|
||||
$query->where('kabupaten_kota_id', $filters['kabupaten_kota_id']);
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->filled('kecamatan_id')) {
|
||||
$query->whereHas('desaKelurahan', function ($query) use ($request) {
|
||||
$query->where('kecamatan_id', $request->kecamatan_id);
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->filled('desa_kelurahan_id')) {
|
||||
$query->where('desa_kelurahan_id', $request->desa_kelurahan_id);
|
||||
}
|
||||
|
||||
if ($userRole === 'fasilitator') {
|
||||
$query->where('created_by', auth()->user()->id);
|
||||
}
|
||||
|
||||
$totalPetani = (clone $query)->count();
|
||||
$totalPetaniLakiLaki = (clone $query)->where('jenis_kelamin', 1)->count();
|
||||
$totalPetaniPerempuan = (clone $query)->where('jenis_kelamin', 0)->count();
|
||||
|
||||
match ($filters['sort_by']) {
|
||||
'nama_asc' => $query->orderBy('nama', 'asc')->orderBy('created_at', 'desc'),
|
||||
'nama_desc' => $query->orderBy('nama', 'desc')->orderBy('created_at', 'desc'),
|
||||
'luas_lahan_desc' => $query->orderByDesc(
|
||||
Lahan::selectRaw('COALESCE(SUM(luas_lahan), 0)')
|
||||
->whereColumn('map_lahan.petani_id', 'petani.id')
|
||||
->whereNull('map_lahan.deleted_at')
|
||||
)->orderBy('created_at', 'desc'),
|
||||
'luas_lahan_asc' => $query->orderBy(
|
||||
Lahan::selectRaw('COALESCE(SUM(luas_lahan), 0)')
|
||||
->whereColumn('map_lahan.petani_id', 'petani.id')
|
||||
->whereNull('map_lahan.deleted_at')
|
||||
)->orderBy('created_at', 'desc'),
|
||||
default => $query->orderBy('created_at', 'desc'),
|
||||
};
|
||||
|
||||
$summaryPetaniIds = (clone $query)->pluck('id');
|
||||
$petani = $query->paginate($request->per_page ?? 10);
|
||||
|
||||
$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();
|
||||
$komoditasOptions = Komoditas::select('id', 'nama')->orderBy('nama')->get();
|
||||
$kabupatenOptions = KabupatenKota::select('id', 'nama')->orderBy('nama')->get();
|
||||
$selectedKabupaten = $filters['kabupaten_kota_id']
|
||||
? KabupatenKota::select('id', 'nama')->find($filters['kabupaten_kota_id'])
|
||||
: null;
|
||||
$kecamatanOptions = $filters['kabupaten_kota_id']
|
||||
? Kecamatan::select('id', 'nama')->where('kabupaten_kota_id', $filters['kabupaten_kota_id'])->orderBy('nama')->get()
|
||||
: collect();
|
||||
$desaOptions = $request->filled('kecamatan_id')
|
||||
? DesaKelurahan::select('id', 'nama')->where('kecamatan_id', $request->kecamatan_id)->orderBy('nama')->get()
|
||||
: collect();
|
||||
$lahanSummaryQuery = Lahan::query()
|
||||
->whereHas('petani')
|
||||
->when($request->filled('komoditas_id'), function ($query) use ($request) {
|
||||
$query->where('komoditas_id', $request->komoditas_id);
|
||||
})
|
||||
->when($filters['kabupaten_kota_id'], function ($query) use ($filters) {
|
||||
$query->whereHas('desaKelurahan.kecamatan', function ($kecamatan) use ($filters) {
|
||||
$kecamatan->where('kabupaten_kota_id', $filters['kabupaten_kota_id']);
|
||||
});
|
||||
})
|
||||
->when($request->filled('kecamatan_id'), function ($query) use ($request) {
|
||||
$query->whereHas('desaKelurahan', function ($desa) use ($request) {
|
||||
$desa->where('kecamatan_id', $request->kecamatan_id);
|
||||
});
|
||||
})
|
||||
->when($request->filled('desa_kelurahan_id'), function ($query) use ($request) {
|
||||
$query->where('desa_kelurahan_id', $request->desa_kelurahan_id);
|
||||
})
|
||||
->when($request->filled('search'), function ($query) use ($search) {
|
||||
$query->whereHas('petani', function ($petani) use ($search) {
|
||||
$petani->whereRaw('lower(nama) like ?', ["%{$search}%"])
|
||||
->orWhereRaw('lower(ktp) like ?', ["%{$search}%"]);
|
||||
});
|
||||
})
|
||||
->when($userRole === 'fasilitator', function ($query) {
|
||||
$query->whereHas('petani', function ($petani) {
|
||||
$petani->where('created_by', auth()->id());
|
||||
});
|
||||
});
|
||||
|
||||
$totalLahan = (clone $lahanSummaryQuery)->count();
|
||||
$totalLuasLahan = (clone $lahanSummaryQuery)->sum('luas_lahan');
|
||||
$totalTanaman = Tanaman::whereIn('lahan_id', (clone $lahanSummaryQuery)->select('id'))
|
||||
->when($request->filled('komoditas_id'), function ($query) use ($request) {
|
||||
$query->where('komoditas_id', $request->komoditas_id);
|
||||
})
|
||||
->count();
|
||||
|
||||
$case = "
|
||||
CASE
|
||||
@@ -81,7 +193,25 @@ class PetaniController extends Controller
|
||||
->groupByRaw($case)
|
||||
->get();
|
||||
|
||||
return view('produktivitas.petani.index', compact('petani', 'provinsi', 'komoditas', 'skor', 'totalLuasLahan', 'totalTanaman'));
|
||||
return view('produktivitas.petani.index', compact(
|
||||
'petani',
|
||||
'provinsi',
|
||||
'komoditas',
|
||||
'komoditasOptions',
|
||||
'kabupatenOptions',
|
||||
'selectedKabupaten',
|
||||
'kecamatanOptions',
|
||||
'desaOptions',
|
||||
'filters',
|
||||
'skor',
|
||||
'totalLahan',
|
||||
'totalLuasLahan',
|
||||
'totalTanaman',
|
||||
'totalPetani',
|
||||
'totalPetaniLakiLaki',
|
||||
'totalPetaniPerempuan',
|
||||
'showFasilitatorColumn'
|
||||
));
|
||||
}
|
||||
|
||||
public function create()
|
||||
|
||||
@@ -49,20 +49,43 @@ class ProfilPetaniController extends Controller
|
||||
$skorPetani = SkorPetani::where('petani_id', $id)->first();
|
||||
$enumerator = Profile::where('user_id', $petani->created_by)->first();
|
||||
$komoditasUtama = Komoditas::where('id', $petani->komoditas_id)->first();
|
||||
$lahan = Lahan::where('petani_id', $id )->get();
|
||||
$lahan = Lahan::query()
|
||||
->select(['id', 'petani_id', 'nama', 'path', 'luas_lahan', 'komoditas_id', 'desa_kelurahan_id'])
|
||||
->with([
|
||||
'komoditas:id,nama',
|
||||
'desaKelurahan:id,nama,path,kecamatan_id',
|
||||
'desaKelurahan.kecamatan:id,nama,path',
|
||||
])
|
||||
->where('petani_id', $id)
|
||||
->get();
|
||||
|
||||
$desaKelurahan = DesaKelurahan::with('kecamatan')
|
||||
->where('id', $petani->desa_kelurahan_id)
|
||||
->first();
|
||||
|
||||
$ktpMasked = $petani->ktp ? Str::mask($petani->ktp, '*', 4, -4) : '-';
|
||||
$profiling = $skorPetani ? $this->buildProfiling($skorPetani) : null;
|
||||
$assessmentFields = [
|
||||
'skor_kesiapan_adopsi',
|
||||
'skor_d2_sistem_praktik_budidaya',
|
||||
'skor_d4_sosial_ekonomi',
|
||||
'skor_d6_rantai_nilai_akses_pasar',
|
||||
'skor_komp_1_d4_k3_motivasi_kapasitas_adopsi',
|
||||
'skor_komp_2_d4_k2_ekonomi_rumah_tangga',
|
||||
'skor_komp_3_d2_praktik_budidaya_gap',
|
||||
'skor_komp_4_d4_k4_keterlibatan_kelembagaan',
|
||||
'skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan',
|
||||
'skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar',
|
||||
];
|
||||
$hasAssessment = $skorPetani !== null && collect($assessmentFields)->every(
|
||||
fn (string $field) => $skorPetani->{$field} !== null
|
||||
);
|
||||
$profiling = $hasAssessment ? $this->buildProfiling($skorPetani) : null;
|
||||
|
||||
$tanaman = Tanaman::where('petani_id', $petani->id)
|
||||
->where('komoditas_id', $petani->komoditas_id)
|
||||
->first();
|
||||
|
||||
if ($tanaman) {
|
||||
if ($tanaman?->updated_at && $tanaman->umur_tanaman !== null) {
|
||||
$tahunInputTanaman = $tanaman->updated_at->year;
|
||||
$umurTanaman = (int) $tanaman->umur_tanaman;
|
||||
$tahunSekarang = now()->year;
|
||||
@@ -162,8 +185,8 @@ class ProfilPetaniController extends Controller
|
||||
|
||||
$d4SosialEkonomi = null;
|
||||
|
||||
$terdaftarKelompok = $sosialEkonomi->terdaftar_kelompok_tani;
|
||||
$peranKelompok = $sosialEkonomi->peran_dalam_kelompok;
|
||||
$terdaftarKelompok = $sosialEkonomi?->terdaftar_kelompok_tani;
|
||||
$peranKelompok = $sosialEkonomi?->peran_dalam_kelompok;
|
||||
|
||||
if ($sosialEkonomi) {
|
||||
|
||||
@@ -207,8 +230,8 @@ class ProfilPetaniController extends Controller
|
||||
'icon' => 'tabler-minus',
|
||||
],
|
||||
default => [
|
||||
'label' => Str::headline($status),
|
||||
'short' => Str::headline($status),
|
||||
'label' => filled($status) ? Str::headline($status) : 'Belum diisi',
|
||||
'short' => filled($status) ? Str::headline($status) : 'Belum diisi',
|
||||
'color' => 'secondary',
|
||||
'icon' => 'tabler-help-circle',
|
||||
],
|
||||
@@ -434,12 +457,61 @@ class ProfilPetaniController extends Controller
|
||||
],
|
||||
];
|
||||
|
||||
// Report missing profile data without treating an unanswered field as zero.
|
||||
$missingSections = [];
|
||||
$identityFields = [
|
||||
'nama' => 'Nama', 'ktp' => 'NIK', 'tanggal_lahir' => 'Tanggal lahir',
|
||||
'jenis_kelamin' => 'Jenis kelamin', 'pendidikan_terakhir' => 'Pendidikan terakhir',
|
||||
'status_perkawinan' => 'Status perkawinan',
|
||||
];
|
||||
foreach ($identityFields as $field => $label) {
|
||||
if (blank($petani->{$field})) {
|
||||
$missingSections[] = $label;
|
||||
}
|
||||
}
|
||||
if (!$desaKelurahan?->nama || !$desaKelurahan?->kecamatan?->nama) {
|
||||
$missingSections[] = 'Wilayah desa/kecamatan';
|
||||
}
|
||||
if (!$komoditasUtama?->nama) {
|
||||
$missingSections[] = 'Komoditas utama';
|
||||
}
|
||||
if ($lahan->isEmpty()) {
|
||||
$missingSections[] = 'Data lahan';
|
||||
}
|
||||
if ($tahunTanam === null) {
|
||||
$missingSections[] = 'Umur tanaman';
|
||||
}
|
||||
if (!$praktikBudidaya || $indikatorOnFarm->contains(fn ($item) => $item['skor'] === null)) {
|
||||
$missingSections[] = 'Praktik budidaya';
|
||||
}
|
||||
if ($surveyPanen?->hasil_komoditas_utama_permusim === null) {
|
||||
$missingSections[] = 'Survei panen';
|
||||
}
|
||||
$socialFields = [
|
||||
'jumlah_tanggungan', 'skor_kecukupan_penghasilan', 'skor_kemampuan_menabung',
|
||||
'status_kepemilikan_lahan', 'terdaftar_kelompok_tani', 'status_rekening_tabungan',
|
||||
'pengalaman_kredit_kebun', 'keterikatan_hutang_panen', 'praktik_ijon',
|
||||
'kebebasan_memilih_pembeli',
|
||||
];
|
||||
if (!$sosialEkonomi || collect($socialFields)->contains(fn ($field) => blank($sosialEkonomi->{$field}))) {
|
||||
$missingSections[] = 'Sosial ekonomi';
|
||||
}
|
||||
if (!$aksesPasar || blank($aksesPasar->saluran_terdahulu)) {
|
||||
$missingSections[] = 'Akses pasar';
|
||||
}
|
||||
if (!$hasAssessment) {
|
||||
$missingSections[] = 'Penilaian kesiapan adopsi belum lengkap';
|
||||
}
|
||||
|
||||
$data = [
|
||||
'missingSections' => $missingSections,
|
||||
'hasAssessment' => $hasAssessment,
|
||||
'petani' => $petani,
|
||||
'skorPetani' => $skorPetani,
|
||||
'enumerator' => $enumerator,
|
||||
'komoditasUtama' => $komoditasUtama,
|
||||
'lahan' => $lahan,
|
||||
'farmerLandMap' => $this->farmerLandMapData($lahan),
|
||||
'desaKelurahan' => $desaKelurahan,
|
||||
'ktpMasked' => $ktpMasked,
|
||||
'profiling' => $profiling,
|
||||
@@ -447,7 +519,6 @@ class ProfilPetaniController extends Controller
|
||||
'umurTanaman' => $umurTanaman,
|
||||
'surveyPanen' => $surveyPanen,
|
||||
'praktikBudidaya' => $praktikBudidaya,
|
||||
'praktikBudidaya' => $praktikBudidaya,
|
||||
'indikatorOnFarm' => $indikatorOnFarm,
|
||||
'detailOnFarm' => $detailOnFarm,
|
||||
'sosialEkonomi' => $sosialEkonomi,
|
||||
@@ -464,6 +535,35 @@ class ProfilPetaniController extends Controller
|
||||
return view('produktivitas.profil-petani.index', $data);
|
||||
}
|
||||
|
||||
private function farmerLandMapData(\Illuminate\Support\Collection $lands): array
|
||||
{
|
||||
$regions = ['desa' => [], 'kecamatan' => []];
|
||||
$plots = [];
|
||||
|
||||
foreach ($lands as $land) {
|
||||
$plot = [
|
||||
'id' => $land->id,
|
||||
'nama' => $land->nama,
|
||||
'path' => $land->path,
|
||||
'luas' => $land->luas_lahan,
|
||||
'komoditas' => $land->komoditas?->nama,
|
||||
];
|
||||
foreach (['desa' => $land->desaKelurahan, 'kecamatan' => $land->desaKelurahan?->kecamatan] as $level => $region) {
|
||||
$plot[$level . '_id'] = $region?->id;
|
||||
if ($region && !isset($regions[$level][$region->id])) {
|
||||
$regions[$level][$region->id] = [
|
||||
'id' => $region->id,
|
||||
'nama' => $region->nama,
|
||||
'path' => $region->path,
|
||||
];
|
||||
}
|
||||
}
|
||||
$plots[] = $plot;
|
||||
}
|
||||
|
||||
return ['lands' => $plots, 'regions' => $regions];
|
||||
}
|
||||
|
||||
private function getScoreColor(?int $score): string
|
||||
{
|
||||
if ($score === null) {
|
||||
|
||||
@@ -6,9 +6,13 @@ use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
use App\Models\Pertanian\KelompokTani;
|
||||
use App\Models\Wilayah\Provinsi;
|
||||
use App\Models\Wilayah\KabupatenKota;
|
||||
use App\Models\Wilayah\Kecamatan;
|
||||
use App\Models\Wilayah\DesaKelurahan;
|
||||
use App\Models\Pertanian\Komoditas;
|
||||
|
||||
class SurveyKelompokTaniController extends Controller
|
||||
@@ -20,9 +24,9 @@ class SurveyKelompokTaniController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
private function redirectToSection(string $url, string $section, string $message)
|
||||
private function redirectToSection(string $url, string $section, string $message, ?string $nextSection = null)
|
||||
{
|
||||
return redirect($url . '#section-' . $section)->with([
|
||||
return redirect($url . '#section-' . ($nextSection ?? $section))->with([
|
||||
'success' => $message,
|
||||
'success_section' => $section,
|
||||
]);
|
||||
@@ -30,13 +34,90 @@ class SurveyKelompokTaniController extends Controller
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$kelompokTani = KelompokTani::query()
|
||||
->when($request->search, fn ($query, $search) => $query->where('nama_kelompok', 'like', "%{$search}%"))
|
||||
->latest()
|
||||
->paginate($request->per_page ?? 10)
|
||||
->appends($request->query());
|
||||
$validated = $request->validate([
|
||||
'search' => ['nullable', 'string', 'max:255'],
|
||||
'kabupaten_kota_id' => ['nullable', 'integer', 'exists:master_kabupaten_kota,id'],
|
||||
'kecamatan_id' => ['nullable', 'integer', 'exists:master_kecamatan,id'],
|
||||
'desa_kelurahan_id' => ['nullable', 'integer', 'exists:master_desa_kelurahan,id'],
|
||||
'komoditas_id' => ['nullable', 'integer', 'exists:master_komoditas,id'],
|
||||
'jenis_kelompok' => ['nullable', 'string', 'max:50'],
|
||||
'sort_by' => ['nullable', 'string'],
|
||||
'per_page' => ['nullable', 'integer', 'in:10,25'],
|
||||
]);
|
||||
|
||||
return view('produktivitas.survey-kelompok-tani.index', compact('kelompokTani'));
|
||||
$defaultKabupaten = KabupatenKota::select('id', 'nama')
|
||||
->whereRaw('lower(nama) like ?', ['%ngada%'])->orderBy('nama')->first();
|
||||
$allowedSorts = ['nama_asc', 'nama_desc', 'anggota_desc', 'anggota_asc'];
|
||||
$filters = [
|
||||
'search' => $validated['search'] ?? null,
|
||||
'kabupaten_kota_id' => $validated['kabupaten_kota_id'] ?? $defaultKabupaten?->id,
|
||||
'kecamatan_id' => $validated['kecamatan_id'] ?? null,
|
||||
'desa_kelurahan_id' => $validated['desa_kelurahan_id'] ?? null,
|
||||
'komoditas_id' => $validated['komoditas_id'] ?? null,
|
||||
'jenis_kelompok' => $validated['jenis_kelompok'] ?? null,
|
||||
'sort_by' => in_array($validated['sort_by'] ?? null, $allowedSorts, true)
|
||||
? $validated['sort_by'] : null,
|
||||
];
|
||||
|
||||
$query = KelompokTani::query()
|
||||
->when(filled($filters['search']), function ($query) use ($filters) {
|
||||
$search = '%' . Str::lower($filters['search']) . '%';
|
||||
$query->where(function ($query) use ($search) {
|
||||
$query->whereRaw('lower(nama_kelompok) like ?', [$search])
|
||||
->orWhereRaw('lower(nama_responden) like ?', [$search]);
|
||||
});
|
||||
})
|
||||
->when($filters['kabupaten_kota_id'], function ($query, $id) {
|
||||
$query->whereHas('desaKelurahan.kecamatan', fn ($query) => $query->where('kabupaten_kota_id', $id));
|
||||
})
|
||||
->when($filters['kecamatan_id'], function ($query, $id) {
|
||||
$query->whereHas('desaKelurahan', fn ($query) => $query->where('kecamatan_id', $id));
|
||||
})
|
||||
->when($filters['desa_kelurahan_id'], fn ($query, $id) => $query->where('desa_kelurahan_id', $id))
|
||||
->when($filters['komoditas_id'], function ($query, $id) {
|
||||
$query->where(function ($query) use ($id) {
|
||||
$query->whereJsonContains('komoditas_id', (string) $id)
|
||||
->orWhereJsonContains('komoditas_id', (int) $id);
|
||||
});
|
||||
})
|
||||
->when($filters['jenis_kelompok'], fn ($query, $jenis) => $query->where('jenis_kelompok', $jenis));
|
||||
|
||||
$summary = (clone $query)->selectRaw('
|
||||
COUNT(*) AS total_kelompok,
|
||||
COUNT(DISTINCT jenis_kelompok) AS total_jenis,
|
||||
COALESCE(SUM(jumlah_pengurus), 0) AS total_pengurus,
|
||||
COALESCE(SUM(jumlah_pengurus_aktif), 0) AS pengurus_aktif,
|
||||
COALESCE(SUM(jumlah_anggota_terdaftar), 0) AS total_anggota,
|
||||
COALESCE(SUM(jumlah_anggota_aktif), 0) AS anggota_aktif
|
||||
')->first();
|
||||
|
||||
match ($filters['sort_by']) {
|
||||
'nama_asc' => $query->orderBy('nama_kelompok'),
|
||||
'nama_desc' => $query->orderByDesc('nama_kelompok'),
|
||||
'anggota_desc' => $query->orderByRaw('COALESCE(jumlah_anggota_terdaftar, 0) DESC'),
|
||||
'anggota_asc' => $query->orderByRaw('COALESCE(jumlah_anggota_terdaftar, 0) ASC'),
|
||||
default => $query->latest(),
|
||||
};
|
||||
|
||||
$perPage = (int) ($validated['per_page'] ?? 10);
|
||||
$kelompokTani = $query->with('desaKelurahan.kecamatan')->orderByDesc('id')
|
||||
->paginate($perPage)->appends([...$filters, 'per_page' => $perPage]);
|
||||
$selectedKabupaten = $filters['kabupaten_kota_id']
|
||||
? KabupatenKota::select('id', 'nama')->find($filters['kabupaten_kota_id']) : null;
|
||||
$kecamatanOptions = $filters['kabupaten_kota_id']
|
||||
? Kecamatan::select('id', 'nama')->where('kabupaten_kota_id', $filters['kabupaten_kota_id'])->orderBy('nama')->get()
|
||||
: collect();
|
||||
$desaOptions = $filters['kecamatan_id']
|
||||
? DesaKelurahan::select('id', 'nama')->where('kecamatan_id', $filters['kecamatan_id'])->orderBy('nama')->get()
|
||||
: collect();
|
||||
$komoditasOptions = Komoditas::select('id', 'nama')->orderBy('nama')->get();
|
||||
$jenisKelompokOptions = KelompokTani::query()->whereNotNull('jenis_kelompok')
|
||||
->where('jenis_kelompok', '!=', '')->distinct()->orderBy('jenis_kelompok')->pluck('jenis_kelompok');
|
||||
|
||||
return view('produktivitas.survey-kelompok-tani.index', compact(
|
||||
'kelompokTani', 'summary', 'filters', 'selectedKabupaten',
|
||||
'kecamatanOptions', 'desaOptions', 'komoditasOptions', 'jenisKelompokOptions'
|
||||
));
|
||||
}
|
||||
|
||||
public function create()
|
||||
@@ -46,7 +127,24 @@ class SurveyKelompokTaniController extends Controller
|
||||
$provinsi = Provinsi::orderBy('kode', 'asc')->get();
|
||||
$komoditas = Komoditas::where('is_utama', true)->get();
|
||||
|
||||
return view('produktivitas.survey-kelompok-tani.add', compact('provinsi', 'komoditas'));
|
||||
return view('produktivitas.survey-kelompok-tani.add', [
|
||||
...compact('provinsi', 'komoditas'),
|
||||
...$this->profileLocationOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function profileLocationOptions(?KelompokTani $kelompokTani = null): array
|
||||
{
|
||||
$locationId = static fn ($value) => is_scalar($value) && filter_var($value, FILTER_VALIDATE_INT) !== false ? (int) $value : null;
|
||||
$provinsiId = $locationId(old('provinsi_id', $kelompokTani?->desaKelurahan?->kecamatan?->kabupatenKota?->provinsi_id));
|
||||
$kabupatenId = $locationId(old('kabupaten_id', $kelompokTani?->desaKelurahan?->kecamatan?->kabupaten_kota_id));
|
||||
$kecamatanId = $locationId(old('kecamatan_id', $kelompokTani?->desaKelurahan?->kecamatan_id));
|
||||
|
||||
return [
|
||||
'kabupatenOptions' => filled($provinsiId) ? KabupatenKota::where('provinsi_id', $provinsiId)->orderBy('nama')->get() : collect(),
|
||||
'kecamatanOptions' => filled($kabupatenId) ? Kecamatan::where('kabupaten_kota_id', $kabupatenId)->orderBy('nama')->get() : collect(),
|
||||
'desaOptions' => filled($kecamatanId) ? DesaKelurahan::where('kecamatan_id', $kecamatanId)->orderBy('nama')->get() : collect(),
|
||||
];
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
@@ -67,7 +165,7 @@ class SurveyKelompokTaniController extends Controller
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
return $this->redirectToSection(route('kelompok-tani.edit', $kelompokTani->id), 'identitas', 'Data kelompok tani berhasil ditambahkan.');
|
||||
return $this->redirectToSection(route('kelompok-tani.edit', $kelompokTani->id), 'identitas', 'Data Profil berhasil ditambahkan.', 'pengurus');
|
||||
}
|
||||
|
||||
private function editUrl(KelompokTani $kelompokTani, string $section = 'identitas'): string
|
||||
@@ -83,12 +181,15 @@ class SurveyKelompokTaniController extends Controller
|
||||
|
||||
return match ($request->query('section', 'identitas')) {
|
||||
'kelembagaan' => view('produktivitas.survey-kelompok-tani.kelembagaan', compact('kelompokTani')),
|
||||
'aktivitas-fungsi' => view('produktivitas.survey-kelompok-tani.aktivitas-fungsi', compact('kelompokTani')),
|
||||
'aktivitas-fungsi' => view('produktivitas.survey-kelompok-tani.aktivitas-fungsi', [
|
||||
'kelompokTani' => $kelompokTani->loadMissing(['aset', 'bantuan']),
|
||||
]),
|
||||
'sosial-konflik' => view('produktivitas.survey-kelompok-tani.sosial-konflik', compact('kelompokTani')),
|
||||
default => view('produktivitas.survey-kelompok-tani.edit', [
|
||||
'kelompokTani' => $kelompokTani,
|
||||
'kelompokTani' => $kelompokTani->loadMissing('pengurus'),
|
||||
'provinsi' => Provinsi::orderBy('kode', 'asc')->get(),
|
||||
'komoditas' => Komoditas::where('is_utama', true)->get(),
|
||||
...$this->profileLocationOptions($kelompokTani),
|
||||
]),
|
||||
};
|
||||
}
|
||||
@@ -103,8 +204,8 @@ class SurveyKelompokTaniController extends Controller
|
||||
'aktivitas-fungsi' => $this->updateAktivitasFungsi($request, $kelompokTani),
|
||||
'kas' => $this->updateKasSimpanPinjam($request, $kelompokTani),
|
||||
'sosial-konflik' => $this->updateSosialKonflik($request, $kelompokTani),
|
||||
'status-bantuan' => $this->updateStatusBantuan($request, $kelompokTani),
|
||||
'penilaian' => $this->updatePenilaian($request, $kelompokTani),
|
||||
'status-bantuan' => app(BantuanKelompokTaniController::class)->update($request, $kelompokTani),
|
||||
'penilaian' => redirect($this->editUrl($kelompokTani, 'aktivitas-fungsi') . '#section-status-bantuan'),
|
||||
default => $this->updateIdentitas($request, $kelompokTani),
|
||||
};
|
||||
}
|
||||
@@ -122,7 +223,7 @@ class SurveyKelompokTaniController extends Controller
|
||||
|
||||
private function validateIdentitas(Request $request)
|
||||
{
|
||||
return $request->validate([
|
||||
$validated = $request->validate([
|
||||
// Data Responden
|
||||
'nama_responden' => ['required', 'string', 'max:255'],
|
||||
'jabatan_responden' => ['nullable', 'string', 'max:255'],
|
||||
@@ -130,7 +231,10 @@ class SurveyKelompokTaniController extends Controller
|
||||
|
||||
// Identitas Kelompok Tani
|
||||
'nama_kelompok' => ['required', 'string', 'max:255'],
|
||||
'desa_kelurahan_id' => ['required', 'exists:master_desa_kelurahan,id'],
|
||||
'provinsi_id' => ['bail', 'required', 'integer', 'exists:master_provinsi,id'],
|
||||
'kabupaten_id' => ['bail', 'required', 'integer', 'exists:master_kabupaten_kota,id'],
|
||||
'kecamatan_id' => ['bail', 'required', 'integer', 'exists:master_kecamatan,id'],
|
||||
'desa_kelurahan_id' => ['bail', 'required', 'integer', 'exists:master_desa_kelurahan,id'],
|
||||
'dusun' => ['nullable', 'string', 'max:255'],
|
||||
'tahun_berdiri' => ['nullable', 'digits:4', 'integer', 'min:1900', 'max:' . date('Y')],
|
||||
'jenis_kelompok' => ['required', 'string', 'max:50'],
|
||||
@@ -138,15 +242,30 @@ class SurveyKelompokTaniController extends Controller
|
||||
'nama_gapoktan' => ['nullable', 'string', 'max:255'],
|
||||
'komoditas_id' => ['nullable', 'array'],
|
||||
'komoditas_id.*' => ['exists:master_komoditas,id'],
|
||||
...$this->keanggotaanRules(),
|
||||
], [
|
||||
'nama_responden.required' => 'Nama responden wajib diisi.',
|
||||
'nama_kelompok.required' => 'Nama kelompok wajib diisi.',
|
||||
'provinsi_id.required' => 'Provinsi wajib diisi.',
|
||||
'kabupaten_id.required' => 'Kabupaten/Kota wajib diisi.',
|
||||
'kecamatan_id.required' => 'Kecamatan wajib diisi.',
|
||||
'provinsi_id.integer' => 'Pilihan Provinsi tidak valid.',
|
||||
'kabupaten_id.integer' => 'Pilihan Kabupaten/Kota tidak valid.',
|
||||
'kecamatan_id.integer' => 'Pilihan Kecamatan tidak valid.',
|
||||
'desa_kelurahan_id.integer' => 'Pilihan Desa/Kelurahan tidak valid.',
|
||||
'provinsi_id.exists' => 'Provinsi tidak ditemukan.',
|
||||
'kabupaten_id.exists' => 'Kabupaten/Kota tidak ditemukan.',
|
||||
'kecamatan_id.exists' => 'Kecamatan tidak ditemukan.',
|
||||
'desa_kelurahan_id.required' => 'Desa/Kelurahan wajib dipilih.',
|
||||
'desa_kelurahan_id.exists' => 'Desa/Kelurahan tidak ditemukan.',
|
||||
'jenis_kelompok.required' => 'Jenis kelompok wajib dipilih.',
|
||||
'jenis_kelompok_lainnya.required_if' => 'Sebutkan jenis kelompok lainnya.',
|
||||
'jenis_kelompok.required' => 'Terdaftar/Dikenal Sebagai wajib diisi.',
|
||||
'jenis_kelompok_lainnya.required_if' => 'Sebutkan Jenis Kelompok wajib diisi.',
|
||||
'tahun_berdiri.digits' => 'Tahun berdiri harus 4 digit.',
|
||||
]);
|
||||
|
||||
unset($validated['provinsi_id'], $validated['kabupaten_id'], $validated['kecamatan_id']);
|
||||
|
||||
return $validated;
|
||||
}
|
||||
|
||||
private function updateIdentitas(Request $request, KelompokTani $kelompokTani)
|
||||
@@ -163,12 +282,12 @@ class SurveyKelompokTaniController extends Controller
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
return $this->redirectToSection(route('kelompok-tani.edit', $kelompokTani->id), 'identitas', 'Data kelompok tani berhasil diperbarui.');
|
||||
return $this->redirectToSection(route('kelompok-tani.edit', $kelompokTani->id), 'identitas', 'Data Profil berhasil disimpan.', 'pengurus');
|
||||
}
|
||||
|
||||
private function updateRingkasanPengurus(Request $request, KelompokTani $kelompokTani)
|
||||
private function keanggotaanRules(): array
|
||||
{
|
||||
$validated = $request->validate([
|
||||
return [
|
||||
'jumlah_pengurus' => ['nullable', 'integer', 'min:0'],
|
||||
'jumlah_pengurus_aktif' => ['nullable', 'integer', 'min:0'],
|
||||
'jumlah_anggota_terdaftar' => ['nullable', 'integer', 'min:0'],
|
||||
@@ -179,18 +298,24 @@ class SurveyKelompokTaniController extends Controller
|
||||
'jumlah_anggota_perempuan' => ['nullable', 'integer', 'min:0'],
|
||||
'jumlah_anggota_pemuda_laki_laki' => ['nullable', 'integer', 'min:0'],
|
||||
'jumlah_anggota_pemuda_perempuan' => ['nullable', 'integer', 'min:0'],
|
||||
]);
|
||||
];
|
||||
}
|
||||
|
||||
private function updateRingkasanPengurus(Request $request, KelompokTani $kelompokTani)
|
||||
{
|
||||
$validated = $request->validate($this->keanggotaanRules());
|
||||
|
||||
$kelompokTani->update([
|
||||
...$validated,
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
return $this->redirectToSection(route('kelompok-tani.pengurus-anggota.index', $kelompokTani->id), 'ringkasan', 'Ringkasan pengurus & anggota berhasil disimpan.');
|
||||
return $this->redirectToSection($this->editUrl($kelompokTani), 'identitas', 'Data Profil berhasil disimpan.', 'pengurus');
|
||||
}
|
||||
|
||||
private function updateKelembagaan(Request $request, KelompokTani $kelompokTani)
|
||||
{
|
||||
$hasSk = in_array($request->input('status_legalitas'), KelompokTani::STATUS_LEGALITAS_DENGAN_SK, true);
|
||||
$validated = $request->validate([
|
||||
// Cakupan Wilayah Kelompok
|
||||
'cakupan_wilayah' => ['nullable', 'string', 'max:50'],
|
||||
@@ -198,17 +323,9 @@ class SurveyKelompokTaniController extends Controller
|
||||
|
||||
// Status Legalitas Kelompok
|
||||
'status_legalitas' => ['nullable', 'string', 'max:50'],
|
||||
'nomor_sk' => ['nullable', 'string', 'max:100'],
|
||||
'tanggal_tahun_penerbitan_sk' => ['nullable', 'string', 'max:30'],
|
||||
'nomor_sk' => [Rule::excludeIf(!$hasSk), 'nullable', 'string', 'max:100'],
|
||||
'tanggal_tahun_penerbitan_sk' => [Rule::excludeIf(!$hasSk), 'nullable', 'string', 'max:30'],
|
||||
|
||||
// Kualitas Kepemimpinan
|
||||
'tingkat_keaktifan_ketua' => ['nullable', 'string', 'max:30'],
|
||||
'status_pembagian_peran' => ['nullable', 'string', 'max:30'],
|
||||
'status_dokumentasi_rapat' => ['nullable', 'string', 'max:30'],
|
||||
|
||||
// Frekuensi Pertemuan Rutin
|
||||
'frekuensi_pertemuan' => ['nullable', 'string', 'max:50'],
|
||||
'tanggal_pertemuan_terakhir' => ['nullable', 'date'],
|
||||
]);
|
||||
|
||||
$kelompokTani->update([
|
||||
@@ -216,7 +333,7 @@ class SurveyKelompokTaniController extends Controller
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
return $this->redirectToSection($this->editUrl($kelompokTani, 'kelembagaan'), 'kelembagaan', 'Data kelembagaan berhasil disimpan.');
|
||||
return $this->redirectToSection($this->editUrl($kelompokTani, 'kelembagaan'), 'kelembagaan', 'Data Kelembagaan berhasil disimpan.');
|
||||
}
|
||||
|
||||
private function resolveLainnya(?array $selected, ?string $lainnyaText): ?array
|
||||
@@ -233,6 +350,10 @@ class SurveyKelompokTaniController extends Controller
|
||||
private function updateAktivitasFungsi(Request $request, KelompokTani $kelompokTani)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
// Frekuensi Pertemuan Rutin
|
||||
'frekuensi_pertemuan' => ['nullable', 'string', 'max:50'],
|
||||
'tanggal_pertemuan_terakhir' => ['nullable', 'date'],
|
||||
|
||||
// Kegiatan Konkret yang Sudah Berjalan
|
||||
'kegiatan_terlaksana' => ['nullable', 'array'],
|
||||
'kegiatan_terlaksana.*' => ['string', 'max:255'],
|
||||
@@ -270,7 +391,7 @@ class SurveyKelompokTaniController extends Controller
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
return $this->redirectToSection($this->editUrl($kelompokTani, 'aktivitas-fungsi'), 'aktivitas-fungsi', 'Data aktivitas & fungsi kelompok berhasil disimpan.');
|
||||
return $this->redirectToSection($this->editUrl($kelompokTani, 'aktivitas-fungsi'), 'aktivitas-fungsi', 'Data Operasional berhasil disimpan.', 'aset-baku');
|
||||
}
|
||||
|
||||
private function updateKasSimpanPinjam(Request $request, KelompokTani $kelompokTani)
|
||||
@@ -287,22 +408,28 @@ class SurveyKelompokTaniController extends Controller
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
return $this->redirectToSection(route('kelompok-tani.aset-keuangan.index', $kelompokTani->id), 'kas', 'Data kas/simpan-pinjam berhasil disimpan.');
|
||||
return $this->redirectToSection($this->editUrl($kelompokTani, 'aktivitas-fungsi'), 'kas', 'Data Operasional berhasil disimpan.', 'status-bantuan');
|
||||
}
|
||||
|
||||
private function updateSosialKonflik(Request $request, KelompokTani $kelompokTani)
|
||||
{
|
||||
$pernahKonflik = in_array($request->input('konflik_kelompok'), [
|
||||
'Pernah, sudah selesai', 'Pernah, masih berlanjut/belum selesai',
|
||||
], true);
|
||||
|
||||
$validated = $request->validate([
|
||||
// Keterlibatan Perempuan
|
||||
'partisipasi_perempuan' => ['nullable', 'string', 'max:255'],
|
||||
|
||||
// Riwayat Konflik Internal Kelompok
|
||||
'konflik_kelompok' => ['nullable', 'string', 'max:255'],
|
||||
'topik_konflik' => ['nullable', 'string', 'max:255'],
|
||||
'topik_konflik' => [Rule::requiredIf($pernahKonflik), 'nullable', 'string', 'max:255'],
|
||||
'penyelesaian_konflik' => ['nullable', 'string'],
|
||||
], [
|
||||
'topik_konflik.required' => 'Jika Pernah, Soal Apa? wajib diisi jika pernah terjadi konflik internal.',
|
||||
]);
|
||||
|
||||
if (!in_array($validated['konflik_kelompok'] ?? null, ['Pernah, sudah selesai', 'Pernah, masih berlanjut/belum selesai'])) {
|
||||
if (!$pernahKonflik) {
|
||||
$validated['topik_konflik'] = null;
|
||||
$validated['penyelesaian_konflik'] = null;
|
||||
}
|
||||
@@ -312,35 +439,7 @@ class SurveyKelompokTaniController extends Controller
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
return $this->redirectToSection($this->editUrl($kelompokTani, 'sosial-konflik'), 'sosial-konflik', 'Data sosial & konflik berhasil disimpan.');
|
||||
return $this->redirectToSection($this->editUrl($kelompokTani, 'sosial-konflik'), 'sosial-konflik', 'Data Sosial berhasil disimpan.');
|
||||
}
|
||||
|
||||
private function updateStatusBantuan(Request $request, KelompokTani $kelompokTani)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'pernah_mendapatkan_bantuan' => ['required', 'boolean'],
|
||||
]);
|
||||
|
||||
$kelompokTani->update([
|
||||
...$validated,
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
return $this->redirectToSection(route('kelompok-tani.bantuan-penilaian.index', $kelompokTani->id), 'status-bantuan', 'Status akses bantuan berhasil disimpan.');
|
||||
}
|
||||
|
||||
private function updatePenilaian(Request $request, KelompokTani $kelompokTani)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'potensi_kelompok' => ['nullable', 'string', 'max:50'],
|
||||
'alasan_penilaian_potensi' => ['nullable', 'string'],
|
||||
]);
|
||||
|
||||
$kelompokTani->update([
|
||||
...$validated,
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
return $this->redirectToSection(route('kelompok-tani.bantuan-penilaian.index', $kelompokTani->id), 'penilaian', 'Penilaian potensi kelompok berhasil disimpan.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,18 @@ class AksesPasar extends Model
|
||||
protected $fillable = [
|
||||
'uid',
|
||||
'petani_id',
|
||||
'tujuan_penjualan',
|
||||
'tujuan_penjualan_lainnya',
|
||||
'sifat_hubungan_bisnis',
|
||||
'bentuk_penjualan',
|
||||
'frekuensi_penjualan',
|
||||
'skor_pengalaman',
|
||||
'saluran_terdahulu',
|
||||
'saluran_terdahulu_lainnya',
|
||||
'alasan_berhenti',
|
||||
'tahu_harga_sebelumnya',
|
||||
'sumber_info_harga',
|
||||
'sumber_info_harga_lainnya',
|
||||
'praktik_fermentasi',
|
||||
'proses_fermentasi',
|
||||
'model_fermentasi_memungkinkan',
|
||||
@@ -44,7 +52,9 @@ class AksesPasar extends Model
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'tujuan_penjualan' => 'array',
|
||||
'saluran_terdahulu' => 'array',
|
||||
'sumber_info_harga' => 'array',
|
||||
];
|
||||
|
||||
protected static function booted(): void
|
||||
|
||||
@@ -14,6 +14,11 @@ class KelompokTani extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
public const STATUS_LEGALITAS_DENGAN_SK = [
|
||||
'Sudah ada SK dari desa/kecamatan',
|
||||
'Sudah ada SK dan terdaftar di Dinas/Kabupaten',
|
||||
];
|
||||
|
||||
protected $table = 'survey_kelompok_tani';
|
||||
|
||||
protected $fillable = [
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Contracts\Cache\Repository;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Throwable;
|
||||
|
||||
class AlphaVantageMarket
|
||||
{
|
||||
public function quotes(Repository $cache): array
|
||||
{
|
||||
if (!$key = config('services.alpha_vantage.api_key')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Retain existing cache keys so cleanup does not spend another API request.
|
||||
$cacheKey = 'market.additional.v1.alpha:COFFEE';
|
||||
$lastKey = 'market.additional.last.v1.alpha:COFFEE';
|
||||
if (is_array($quotes = $cache->get($cacheKey))) {
|
||||
return $quotes;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::acceptJson()
|
||||
->withOptions(['verify' => config('services.yahoo_finance.ca_bundle') ?: true])
|
||||
->connectTimeout(3)->timeout(6)
|
||||
->get('https://www.alphavantage.co/query', [
|
||||
'function' => 'COFFEE', 'interval' => 'monthly', 'apikey' => $key,
|
||||
])->throw();
|
||||
$data = $response->json();
|
||||
if (!is_array($data) || ($data['interval'] ?? null) !== 'monthly'
|
||||
|| strtolower($data['unit'] ?? '') !== 'cents per pound' || !is_array($data['data'] ?? null)) {
|
||||
throw new \RuntimeException('Invalid Alpha Vantage response');
|
||||
}
|
||||
$latest = null;
|
||||
foreach ($data['data'] as $row) {
|
||||
if (!is_array($row) || !is_numeric($row['value'] ?? null) || !is_finite((float) $row['value']) || $row['value'] <= 0
|
||||
|| !is_string($row['date'] ?? null) || !preg_match('/^\d{4}-(0[1-9]|1[0-2])-01$/', $row['date'])) {
|
||||
continue;
|
||||
}
|
||||
if (!$latest || $row['date'] > $latest['date']) $latest = $row;
|
||||
}
|
||||
if (!$latest) throw new \RuntimeException('No valid Alpha Vantage price');
|
||||
$quote = [
|
||||
'id' => 'alpha:COFFEE', 'name' => 'Kopi Arabica',
|
||||
'source' => 'Alpha Vantage (data IMF via FRED)',
|
||||
'source_url' => 'https://www.alphavantage.co/documentation/#coffee',
|
||||
'price' => (float) $latest['value'], 'currency' => 'USX', 'unit' => 'sen AS / lb',
|
||||
'period' => substr($latest['date'], 0, 7), 'frequency' => 'monthly', 'stale' => false,
|
||||
'description' => 'Biji kopi Arabica (Other Milds). Estimasi konversi harga acuan rata-rata bulanan IMF melalui Alpha Vantage.',
|
||||
'update_info' => 'Data Alpha Vantage diperiksa setiap hari untuk menjaga kuota API. Harga merupakan rata-rata bulanan dari IMF via FRED.',
|
||||
];
|
||||
$cache->put($lastKey, $quote, now()->addDays(30));
|
||||
$cache->put($cacheKey, [$quote], 86400);
|
||||
return [$quote];
|
||||
} catch (Throwable) {
|
||||
// Request exceptions can contain the API key in their URL; do not log them.
|
||||
$quotes = [];
|
||||
if (is_array($last = $cache->get($lastKey))) {
|
||||
$last['stale'] = true;
|
||||
$quotes[] = $last;
|
||||
}
|
||||
$cache->put($cacheKey, $quotes, 3600);
|
||||
return $quotes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,12 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'alpha_vantage' => ['api_key' => env('ALPHA_VANTAGE_API_KEY')],
|
||||
|
||||
'yahoo_finance' => [
|
||||
'ca_bundle' => env('YAHOO_FINANCE_CA_BUNDLE'),
|
||||
],
|
||||
|
||||
'postmark' => [
|
||||
'key' => env('POSTMARK_API_KEY'),
|
||||
],
|
||||
|
||||
@@ -61,7 +61,7 @@ return new class extends Migration
|
||||
$table->integer('harga_tahun_lalu')->nullable();
|
||||
$table->integer('harga_dua_tahun_lalu')->nullable();
|
||||
|
||||
$table ->foreignId('created_by')->nullable()->constrained('users')->onDelete('set null');
|
||||
$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');
|
||||
|
||||
|
||||
@@ -0,0 +1,479 @@
|
||||
<?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::create('riwayat_petani', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_pencatatan')->comment('Penanda waktu snapshot, misal: Q1 2026');
|
||||
$table->string('uid')->nullable();
|
||||
|
||||
$table->foreignId('petani_id')->constrained('petani')->onDelete('cascade');
|
||||
|
||||
$table->string('nama');
|
||||
$table->boolean('jenis_kelamin');
|
||||
$table->string('tempat_lahir')->nullable();
|
||||
$table->date('tanggal_lahir');
|
||||
$table->string('pendidikan_terakhir')->nullable();
|
||||
|
||||
$table->string('alamat')->nullable();
|
||||
$table->foreignId('desa_kelurahan_id')->nullable()->constrained('master_desa_kelurahan');
|
||||
$table->string('nomor_telepon')->nullable();
|
||||
$table->string('foto', 500)->nullable();
|
||||
$table->string('status_perkawinan')->nullable();
|
||||
|
||||
$table->string('kk')->nullable();
|
||||
$table->string('ktp')->nullable();
|
||||
$table->string('path_kk', 500)->nullable();
|
||||
$table->string('file_kk')->nullable();
|
||||
$table->string('path_ktp', 500)->nullable();
|
||||
$table->string('file_ktp')->nullable();
|
||||
|
||||
$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();
|
||||
});
|
||||
|
||||
Schema::create('riwayat_map_lahan', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_pencatatan')->comment('Penanda waktu snapshot, misal: Q1 2026');
|
||||
$table->string('uid')->nullable();
|
||||
$table->string('nama');
|
||||
$table->string('status_kepemilikan');
|
||||
|
||||
$table->foreignId('profile_id')->constrained('profiles');
|
||||
$table->foreignId('keluarga_pengelola_id')->nullable()->constrained('keluarga')->onDelete('set null');
|
||||
$table->foreignId('adat_pemilik_id')->nullable()->constrained('adat')->onDelete('set null');
|
||||
$table->foreignId('desa_kelurahan_id')->nullable()->constrained('master_desa_kelurahan');
|
||||
|
||||
$table->string('lat')->nullable();
|
||||
$table->string('long')->nullable();
|
||||
$table->jsonb('path')->nullable();
|
||||
$table->boolean('sertifikat')->default(false);
|
||||
$table->boolean('konflik')->default(false);
|
||||
$table->text('deskripsi')->nullable();
|
||||
$table->float('luas_lahan', 8)->nullable();
|
||||
|
||||
$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();
|
||||
});
|
||||
|
||||
Schema::create('riwayat_sertifikat_lahan', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_pencatatan');
|
||||
$table->string('uid')->nullable();
|
||||
$table->string('nomor_sertifikat');
|
||||
$table->date('tanggal_terbit');
|
||||
$table->date('tanggal_kadaluarsa')->nullable();
|
||||
$table->string('lembaga_penerbit')->nullable();
|
||||
|
||||
$table->foreignId('lahan_id')->constrained('map_lahan')->onDelete('cascade');
|
||||
|
||||
$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();
|
||||
});
|
||||
|
||||
Schema::create('riwayat_map_tanaman', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_pencatatan');
|
||||
$table->string('uid')->nullable();
|
||||
$table->string('nama');
|
||||
|
||||
$table->foreignId('komoditas_id')->constrained('master_komoditas')->onDelete('set null');
|
||||
$table->foreignId('jenis_komoditas_id')->constrained('jenis_komoditas')->onDelete('set null');
|
||||
$table->foreignId('varietas_id')->constrained('master_varietas')->onDelete('set null');
|
||||
$table->foreignId('profile_id')->constrained('profiles')->onDelete('set null');
|
||||
$table->foreignId('lahan_id')->constrained('map_lahan')->onDelete('set null');
|
||||
|
||||
$table->float('latitude')->nullable();
|
||||
$table->float('longitude')->nullable();
|
||||
$table->string('kondisi_tanaman')->nullable();
|
||||
$table->string('metode_pengumpulan')->nullable();
|
||||
$table->text('deskripsi')->nullable();
|
||||
|
||||
$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();
|
||||
});
|
||||
|
||||
Schema::create('riwayat_praktik_budidaya', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_pencatatan');
|
||||
$table->string('uid')->nullable();
|
||||
$table->foreignID('petani_id')->constrained('petani')->onDelete('cascade');
|
||||
|
||||
$table->string('tenaga_kerja')->nullable();
|
||||
$table->integer('frekuensi_pemangkasan_utama')->nullable();
|
||||
$table->jsonb('teknik_pemangkasan')->nullable();
|
||||
$table->string('frekuensi_pemangkasan_penaung')->nullable();
|
||||
$table->string('penyerbukan_manual')->nullable();
|
||||
$table->string('deskripsi_penyerbukan_manual')->nullable();
|
||||
$table->string('tingkat_keberhasilan_penyerbukan')->nullable();
|
||||
|
||||
$table->integer('pemberian_pupuk')->nullable();
|
||||
$table->string('jenis_pupuk')->nullable();
|
||||
$table->string('frekuensi_pemberian_pupuk')->nullable();
|
||||
$table->decimal('estimasi_dosis_pupuk_organik', 10, 2)->nullable();
|
||||
$table->decimal('estimasi_dosis_pupuk_anorganik', 10, 2)->nullable();
|
||||
$table->string('waktu_pemberian_pupuk')->nullable();
|
||||
$table->string('cara_pemberian_pupuk')->nullable();
|
||||
|
||||
$table->integer('metode_penanganan_opt')->nullable();
|
||||
$table->jsonb('jenis_hama')->nullable();
|
||||
$table->jsonb('jenis_penyakit')->nullable();
|
||||
$table->string('intensitas_serangan')->nullable();
|
||||
$table->string('waktu_kemunculan_terparah')->nullable();
|
||||
$table->string('metode_paling_sering_dilakukan')->nullable();
|
||||
$table->decimal('volume_obat_obatan_padat_permusim', 10, 2)->nullable();
|
||||
$table->decimal('volume_obat_obatan_cair_permusim', 10, 2)->nullable();
|
||||
|
||||
$table->integer('frekuensi_sanitasi_lahan')->nullable();
|
||||
$table->string('penanganan_sisa_buah_ranting')->nullable();
|
||||
$table->integer('penanganan_gulma')->nullable();
|
||||
|
||||
$table->integer('kondisi_pengelolaan_air')->nullable();
|
||||
$table->jsonb('sumber_air')->nullable();
|
||||
$table->string('sistem_irigasi')->nullable();
|
||||
$table->string('kebutuhan_air_permusim')->nullable();
|
||||
|
||||
$table->jsonb('kebiasaan_konservasi_tanah')->nullable();
|
||||
$table->string('pemahaman_manfaat_konservasi_tanah')->nullable();
|
||||
$table->integer('nilai_konservasi_tanah')->nullable();
|
||||
|
||||
$table->decimal('upah_tenaga_kerja', 13, 2)->nullable();
|
||||
$table->decimal('biaya_bibit', 13, 2)->nullable();
|
||||
$table->decimal('biaya_pupuk', 13, 2)->nullable();
|
||||
$table->decimal('biaya_obat_obatan', 13, 2)->nullable();
|
||||
$table->decimal('biaya_sewa_alat_produksi', 13, 2)->nullable();
|
||||
$table->decimal('biaya_penyediaan_air', 13, 2)->nullable();
|
||||
$table->decimal('biaya_produksi_lainnya', 13, 2)->nullable();
|
||||
$table->decimal('total_estimasi_biaya_produksi', 13, 2)->nullable();
|
||||
|
||||
$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();
|
||||
});
|
||||
|
||||
Schema::create('riwayat_survey_panen', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_pencatatan');
|
||||
$table->string('uid')->nullable();
|
||||
$table->foreignId('petani_id')->nullable()->constrained('petani')->onDelete('set null');
|
||||
|
||||
$table->integer('dasar_penentuan_panen')->nullable();
|
||||
$table->integer('frekuensi_panen_pertahun')->nullable();
|
||||
$table->string('bulan_panen_utama')->nullable();
|
||||
|
||||
$table->decimal('hasil_komoditas_utama_permusim', 10, 2)->nullable();
|
||||
$table->decimal('hasil_komoditas_lainnya_permusim', 10, 2)->nullable();
|
||||
$table->boolean('disimpan_untuk_sendiri')->nullable();
|
||||
$table->decimal('hasil_panen_disimpan', 10, 2)->nullable();
|
||||
$table->string('tujuan_disimpan')->nullable();
|
||||
|
||||
$table->integer('praktik_sortasi')->nullable();
|
||||
$table->string('bentuk_sortasi')->nullable();
|
||||
|
||||
$table->integer('hasil_panen_reject')->nullable();
|
||||
$table->string('jumlah_panen_reject')->nullable();
|
||||
$table->string('tindakan_panen_reject')->nullable();
|
||||
|
||||
$table->decimal('upah_tenaga_panen', 13, 2)->nullable();
|
||||
$table->decimal('biaya_pengangkutan_panen', 13, 2)->nullable();
|
||||
$table->decimal('biaya_wadah_Panen', 13, 2)->nullable();
|
||||
$table->decimal('biaya_sewa_alat_panen', 13, 2)->nullable();
|
||||
$table->decimal('biaya_panen_lainnya', 13, 2)->nullable();
|
||||
$table->decimal('total_estimasi_biaya_panen', 13, 2)->nullable();
|
||||
|
||||
$table->string('estimasi_kehilangan_akibat_opt')->nullable();
|
||||
$table->string('estimasi_kehilangan_akibat_hama_penyakit')->nullable();
|
||||
|
||||
$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();
|
||||
});
|
||||
|
||||
Schema::create('riwayat_survey_produktivitas', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_pencatatan');
|
||||
$table->string('uid')->nullable();
|
||||
$table->foreignId('petani_id')->nullable()->constrained('petani')->onDelete('set null');
|
||||
$table->foreignId('komoditas_id')->nullable()->constrained('master_komoditas')->onDelete('set null');
|
||||
|
||||
$table->decimal('kuantitas')->nullable();
|
||||
$table->string('satuan')->nullable();
|
||||
|
||||
$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();
|
||||
});
|
||||
|
||||
Schema::create('riwayat_sosial_ekonomi', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_pencatatan');
|
||||
$table->string('uid')->nullable();
|
||||
$table->foreignId('petani_id')->nullable()->constrained('petani')->onDelete('cascade');
|
||||
|
||||
$table->integer('jumlah_tanggungan')->nullable();
|
||||
$table->integer('anak_masih_sekolah')->nullable();
|
||||
$table->integer('yang_ikut_bekerja')->nullable();
|
||||
|
||||
$table->tinyInteger('skor_periode_paceklik')->nullable();
|
||||
$table->string('bulan_paceklik')->nullable();
|
||||
$table->text('sumber_penghasilan_paceklik')->nullable();
|
||||
|
||||
$table->unsignedBigInteger('total_estimasi_biaya_kebun_per_musim')->nullable();
|
||||
$table->tinyInteger('skor_kemampuan_menabung')->nullable();
|
||||
$table->unsignedBigInteger('estimasi_tabungan_per_bulan')->nullable();
|
||||
|
||||
$table->tinyInteger('skor_kecukupan_penghasilan')->nullable();
|
||||
$table->text('alasan_kecukupan_penghasilan')->nullable();
|
||||
|
||||
$table->string('status_kepemilikan_lahan')->nullable();
|
||||
$table->tinyInteger('skor_kejelasan_status_lahan')->nullable();
|
||||
$table->boolean('ada_sertifikat_dokumen')->nullable();
|
||||
$table->boolean('ada_sengketa')->nullable();
|
||||
$table->text('keterangan_sengketa')->nullable();
|
||||
$table->string('pihak_membantu_konflik')->nullable();
|
||||
$table->string('pihak_membantu_konflik_nama')->nullable();
|
||||
$table->year('tahun_bantuan_konflik')->nullable();
|
||||
$table->string('status_konflik')->nullable();
|
||||
|
||||
$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();
|
||||
});
|
||||
|
||||
Schema::create('riwayat_pendapatan_komoditas', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_pencatatan');
|
||||
$table->string('uid')->nullable();
|
||||
|
||||
$table->foreignId('sosial_ekonomi_id')->constrained('sosial_ekonomi')->onDelete('cascade');
|
||||
|
||||
$table->string('kategori')->comment('utama / lainnya');
|
||||
$table->string('komoditas')->nullable();
|
||||
$table->unsignedBigInteger('pendapatan')->nullable();
|
||||
$table->string('periode')->nullable()->comment('musim / tahun');
|
||||
|
||||
$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();
|
||||
});
|
||||
|
||||
Schema::create('riwayat_pendapatan_luar_pertanian', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_pencatatan');
|
||||
$table->string('uid')->nullable();
|
||||
$table->foreignId('sosial_ekonomi_id')->constrained('sosial_ekonomi')->onDelete('cascade');
|
||||
|
||||
$table->string('jenis_pekerjaan')->nullable();
|
||||
$table->unsignedBigInteger('estimasi_per_bulan')->nullable();
|
||||
|
||||
$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();
|
||||
});
|
||||
|
||||
Schema::create('riwayat_akses_pasar', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_pencatatan');
|
||||
$table->string('uid')->nullable();
|
||||
$table->foreignId('petani_id')->nullable()->constrained('petani')->onDelete('cascade');
|
||||
|
||||
$table->tinyInteger('skor_pengalaman')->nullable();
|
||||
$table->json('saluran_terdahulu')->nullable();
|
||||
$table->string('saluran_terdahulu_lainnya')->nullable();
|
||||
$table->string('alasan_berhenti')->nullable();
|
||||
|
||||
$table->string('praktik_fermentasi')->nullable();
|
||||
$table->text('proses_fermentasi')->nullable();
|
||||
$table->string('model_fermentasi_memungkinkan')->nullable();
|
||||
|
||||
$table->tinyInteger('skor_kesadaran_premium')->nullable();
|
||||
$table->string('sumber_info_premium')->nullable();
|
||||
|
||||
$table->time('waktu_mulai_wawancara')->nullable();
|
||||
$table->time('waktu_selesai_wawancara')->nullable();
|
||||
|
||||
$table->tinyInteger('skor_kepercayaan_responden')->nullable();
|
||||
$table->string('kehadiran_orang_lain')->nullable();
|
||||
$table->tinyInteger('skor_pengaruh_orang_lain')->nullable();
|
||||
|
||||
$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();
|
||||
});
|
||||
|
||||
Schema::create('riwayat_harga_jual_panen', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_pencatatan');
|
||||
$table->string('uid')->nullable();
|
||||
$table->foreignId('petani_id')->constrained('petani')->onDelete('cascade');
|
||||
|
||||
$table->string('bentuk_produk');
|
||||
$table->integer('harga_panen_terakhir')->nullable();
|
||||
$table->integer('harga_tahun_lalu')->nullable();
|
||||
$table->integer('harga_dua_tahun_lalu')->nullable();
|
||||
|
||||
$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();
|
||||
});
|
||||
|
||||
Schema::create('riwayat_survey_pelatihan', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_pencatatan');
|
||||
$table->string('uid')->nullable();
|
||||
$table->foreignId('petani_id')->nullable()->constrained('petani')->onDelete('set null');
|
||||
|
||||
$table->string('jenis_pelatihan');
|
||||
$table->string('penyelenggara');
|
||||
$table->integer('tahun_pelaksanaan');
|
||||
|
||||
$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();
|
||||
});
|
||||
|
||||
Schema::create('riwayat_survey_sertifikasi', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_pencatatan');
|
||||
$table->string('uid')->nullable();
|
||||
$table->foreignId('petani_id')->nullable()->constrained('petani')->onDelete('set null');
|
||||
|
||||
$table->string('jenis_sertifikasi');
|
||||
$table->string('lembaga_penerbit');
|
||||
$table->integer('tahun_pelaksanaan');
|
||||
$table->boolean('is_aktif')->default(false);
|
||||
|
||||
$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();
|
||||
});
|
||||
|
||||
Schema::create('riwayat_petani_skor', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('periode_pencatatan');
|
||||
$table->string('uid')->nullable(); // Unique dihilangkan
|
||||
|
||||
$table->foreignId('petani_id')->nullable()->constrained('petani')->onDelete('cascade');
|
||||
|
||||
$table->float('skor_kesiapan_adopsi', 10)->nullable();
|
||||
|
||||
$table->float('skor_d2_sistem_praktik_budidaya', 10)->nullable();
|
||||
$table->float('skor_d4_sosial_ekonomi', 10)->nullable();
|
||||
$table->float('skor_d6_rantai_nilai_akses_pasar', 10)->nullable();
|
||||
|
||||
$table->float('skor_komp_1_d4_k3_motivasi_kapasitas_adopsi', 10)->nullable();
|
||||
$table->float('skor_komp_2_d4_k2_ekonomi_rumah_tangga', 10)->nullable();
|
||||
$table->float('skor_komp_3_d2_praktik_budidaya_gap', 10)->nullable();
|
||||
$table->float('skor_komp_4_d4_k4_keterlibatan_kelembagaan', 10)->nullable();
|
||||
$table->float('skor_komp_5_d4_k5_k6_akses_kebebasan_keuangan', 10)->nullable();
|
||||
$table->float('skor_komp_6_d6_k1_k4_k5_rantai_nilai_akses_pasar', 10)->nullable();
|
||||
|
||||
$table->float('skor_d4_k3_1_praktik_baru', 10)->nullable();
|
||||
$table->float('skor_d4_k3_4_pemahaman_mutu_harga', 10)->nullable();
|
||||
$table->float('skor_d4_k3_5_role_model', 10)->nullable();
|
||||
$table->float('skor_d4_k3_6_kemampuan_keterlibatan_program', 10)->nullable();
|
||||
$table->float('skor_d4_k3_7_orientasi_jangka_panjang', 10)->nullable();
|
||||
|
||||
$table->float('skor_d4_k2_5_stabilitas_penghasilan', 10)->nullable();
|
||||
$table->float('skor_d4_k2_8_kemampuan_menabung', 10)->nullable();
|
||||
$table->float('skor_d4_k2_9_kecukupan_penghasilan', 10)->nullable();
|
||||
$table->float('skor_d4_k2_3_k2_7_rasio_biaya_produksi_pendapatan', 10)->nullable();
|
||||
$table->float('skor_d4_k2_11_hak_kelola_lahan', 10)->nullable();
|
||||
|
||||
$table->float('skor_d2_k2_3_praktik_pemangkasan', 10)->nullable();
|
||||
$table->float('skor_d2_k2_4_praktik_pemupukan', 10)->nullable();
|
||||
$table->float('skor_d2_k2_5_pengendalian_opt', 10)->nullable();
|
||||
$table->float('skor_d2_k2_6_sanitasi_kebun', 10)->nullable();
|
||||
$table->float('skor_d2_k2_6b_pengelolaan_gulma', 10)->nullable();
|
||||
$table->float('skor_d2_k2_7_penglolaan_air_irigasi', 10)->nullable();
|
||||
$table->float('skor_d2_k2_8_praktik_konservasi_tanah', 10)->nullable();
|
||||
$table->float('skor_d2_k3_1_penentuan_pematangan', 10)->nullable();
|
||||
$table->float('skor_d2_k3_5_sortasi_awal_pemisahan_kualitas', 10)->nullable();
|
||||
|
||||
$table->float('skor_d4_k4_2_keaktifan_pertemuan', 10)->nullable();
|
||||
$table->float('skor_d4_k4_3_peran_dalam_kelompok', 10)->nullable();
|
||||
$table->float('skor_d4_k4_4_manfaat_dirasakan', 10)->nullable();
|
||||
|
||||
$table->float('skor_d4_k5_1_kepemilikan_rekening', 10)->nullable();
|
||||
$table->float('skor_d4_k5_2_pengalaman_kredit_formal', 10)->nullable();
|
||||
$table->float('skor_d4_k5_3_kepemilikan_aset_produktif', 10)->nullable();
|
||||
$table->float('skor_d4_k5_4_akses_program_bantuan', 10)->nullable();
|
||||
|
||||
$table->float('skor_d4_k6_3_keberadaan_hutang_terkait_panen', 10)->nullable();
|
||||
$table->float('skor_d4_k6_5a_praktik_ijon', 10)->nullable();
|
||||
$table->float('skor_d4_k6_5b_kebebasan_memilih_pembeli', 10)->nullable();
|
||||
|
||||
$table->float('skor_d6_k1_6_pengalaman_menjual_ke_saluran_berbeda', 10)->nullable();
|
||||
$table->float('skor_d6_k4_1_kesadaran_akses_pasar_premium', 10)->nullable();
|
||||
$table->float('skor_d6_k5_1_hambatan_mutu', 10)->nullable();
|
||||
|
||||
$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();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('riwayat_petani_skor');
|
||||
Schema::dropIfExists('riwayat_survey_sertifikasi');
|
||||
Schema::dropIfExists('riwayat_survey_pelatihan');
|
||||
Schema::dropIfExists('riwayat_harga_jual_panen');
|
||||
Schema::dropIfExists('riwayat_akses_pasar');
|
||||
Schema::dropIfExists('riwayat_pendapatan_luar_pertanian');
|
||||
Schema::dropIfExists('riwayat_pendapatan_komoditas');
|
||||
Schema::dropIfExists('riwayat_sosial_ekonomi');
|
||||
Schema::dropIfExists('riwayat_survey_produktivitas');
|
||||
Schema::dropIfExists('riwayat_survey_panen');
|
||||
Schema::dropIfExists('riwayat_praktik_budidaya');
|
||||
Schema::dropIfExists('riwayat_map_tanaman');
|
||||
Schema::dropIfExists('riwayat_sertifikat_lahan');
|
||||
Schema::dropIfExists('riwayat_map_lahan');
|
||||
}
|
||||
};
|
||||
+38
@@ -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
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('akses_pasar', function (Blueprint $table) {
|
||||
$table->text('tujuan_penjualan')->nullable();
|
||||
$table->string('tujuan_penjualan_lainnya')->nullable();
|
||||
$table->text('sifat_hubungan_bisnis')->nullable();
|
||||
$table->text('bentuk_penjualan')->nullable();
|
||||
$table->string('frekuensi_penjualan')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('akses_pasar', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'tujuan_penjualan',
|
||||
'tujuan_penjualan_lainnya',
|
||||
'sifat_hubungan_bisnis',
|
||||
'bentuk_penjualan',
|
||||
'frekuensi_penjualan'
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?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('akses_pasar', function (Blueprint $table) {
|
||||
$table->string('tahu_harga_sebelumnya')->nullable();
|
||||
$table->text('sumber_info_harga')->nullable();
|
||||
$table->string('sumber_info_harga_lainnya')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('akses_pasar', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'tahu_harga_sebelumnya',
|
||||
'sumber_info_harga',
|
||||
'sumber_info_harga_lainnya',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -103,3 +103,154 @@
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* Sidebar icon tiles: keep the icon mask on the inner element. */
|
||||
#layout-menu .menu-inner > .menu-item > .menu-link > .sidebar-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 30px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 9px;
|
||||
color: var(--sidebar-icon-color, var(--bs-secondary));
|
||||
background: var(--sidebar-icon-bg, rgba(var(--bs-secondary-rgb), 0.1));
|
||||
transition: background-color 180ms ease, color 180ms ease, transform 180ms ease;
|
||||
}
|
||||
|
||||
#layout-menu .sidebar-icon > .ti {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
#layout-menu .sidebar-icon:has(.tabler-dashboard),
|
||||
#layout-menu .sidebar-icon:has(.tabler-database) {
|
||||
--sidebar-icon-color: var(--bs-primary);
|
||||
--sidebar-icon-bg: rgba(var(--bs-primary-rgb), 0.12);
|
||||
}
|
||||
|
||||
#layout-menu .sidebar-icon:has(.tabler-user-circle),
|
||||
#layout-menu .sidebar-icon:has(.tabler-id-badge-2),
|
||||
#layout-menu .sidebar-icon:has(.tabler-plant) {
|
||||
--sidebar-icon-color: var(--bs-success);
|
||||
--sidebar-icon-bg: rgba(var(--bs-success-rgb), 0.12);
|
||||
}
|
||||
|
||||
#layout-menu .sidebar-icon:has(.tabler-users-group),
|
||||
#layout-menu .sidebar-icon:has(.tabler-sitemap) {
|
||||
--sidebar-icon-color: var(--bs-info);
|
||||
--sidebar-icon-bg: rgba(var(--bs-info-rgb), 0.12);
|
||||
}
|
||||
|
||||
#layout-menu .sidebar-icon:has(.tabler-building-community),
|
||||
#layout-menu .sidebar-icon:has(.tabler-heart-handshake) {
|
||||
--sidebar-icon-color: var(--bs-warning);
|
||||
--sidebar-icon-bg: rgba(var(--bs-warning-rgb), 0.12);
|
||||
}
|
||||
|
||||
#layout-menu .sidebar-icon:has(.tabler-map-2),
|
||||
#layout-menu .sidebar-icon:has(.tabler-user-shield),
|
||||
#layout-menu .sidebar-icon:has(.tabler-route) {
|
||||
--sidebar-icon-color: var(--bs-primary);
|
||||
--sidebar-icon-bg: rgba(var(--bs-primary-rgb), 0.12);
|
||||
}
|
||||
|
||||
#layout-menu .menu-item > .menu-link:hover > .sidebar-icon,
|
||||
#layout-menu .menu-item > .menu-link:focus-visible > .sidebar-icon {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
#layout-menu .menu-inner > .menu-item.active > .menu-link > .sidebar-icon {
|
||||
color: inherit;
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#layout-menu .menu-inner > .menu-item > .menu-link > .sidebar-icon {
|
||||
transition: none;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Submenu icons replace the template's decorative bullets. */
|
||||
#layout-menu .menu-sub > .menu-item > .menu-link::before { display: none; }
|
||||
#layout-menu .menu-sub .sidebar-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 22px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
color: inherit;
|
||||
}
|
||||
#layout-menu .menu-sub .sidebar-icon > .ti { width: 19px; height: 19px; font-size: 19px; }
|
||||
|
||||
/* Profil Kelompok Tani */
|
||||
.kelompok-tani-page {
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
}
|
||||
|
||||
.kelompok-tani-page :is(.form-control, .form-select, .form-check-label, .select2-container) {
|
||||
font-family: var(--bs-body-font-family);
|
||||
}
|
||||
|
||||
.kelompok-tani-page select.is-invalid + .select2-container .select2-selection {
|
||||
border-color: var(--bs-form-invalid-border-color);
|
||||
}
|
||||
|
||||
.kelompok-tani-page .kelompok-tani-summary-name {
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.kelompok-tani-page .kelompok-tani-summary-note {
|
||||
font-size: .75rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.kelompok-tani-page .kelompok-tani-help {
|
||||
margin-top: -.75rem;
|
||||
}
|
||||
|
||||
.kelompok-tani-page #section-kas[hidden],
|
||||
.kelompok-tani-page #section-bantuan[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.kelompok-tani-page #kelompok-tani-notifications,
|
||||
.kelompok-tani-page [id^="section-"] {
|
||||
scroll-margin-top: 7rem;
|
||||
}
|
||||
|
||||
.kelompok-tani-page .select2-selection {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.kelompok-tani-page .select2-selection::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
inset-inline-end: 1rem;
|
||||
width: .5rem;
|
||||
height: .5rem;
|
||||
border-right: 2px solid currentColor;
|
||||
border-bottom: 2px solid currentColor;
|
||||
color: var(--bs-body-color);
|
||||
transform: translateY(-75%) rotate(45deg);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.kelompok-tani-page .select2-selection__arrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.kelompok-tani-page .select2-container .select2-selection--multiple .select2-selection__rendered {
|
||||
padding-inline-end: 2.5rem;
|
||||
}
|
||||
|
||||
.kelompok-tani-page .select2-container--disabled .select2-selection::after {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
@@ -37,10 +37,73 @@ window.config = {
|
||||
danger: window.Helpers.getCssVar('danger-bg-subtle'),
|
||||
dark: window.Helpers.getCssVar('dark-bg-subtle')
|
||||
},
|
||||
chartColors: {
|
||||
series: ['#9D8EF0', '#6FD68F', '#FFB46F', '#5FC7D6', '#F47C86', '#9EA6B0', '#65D8BD', '#63C7E0'],
|
||||
semantic: {
|
||||
primary: '#9D8EF0',
|
||||
secondary: '#9EA6B0',
|
||||
success: '#6FD68F',
|
||||
info: '#5FC7D6',
|
||||
warning: '#FFB46F',
|
||||
danger: '#F47C86',
|
||||
purple: '#9D8EF0',
|
||||
teal: '#65D8BD',
|
||||
pink: '#F47C86',
|
||||
gray: '#9EA6B0'
|
||||
},
|
||||
text: {
|
||||
primary: '#7367F0',
|
||||
secondary: '#808390',
|
||||
success: '#28C76F',
|
||||
info: '#00BAD1',
|
||||
warning: '#FF9F43',
|
||||
danger: '#FF4C51',
|
||||
purple: '#7367F0',
|
||||
teal: '#20C997',
|
||||
pink: '#FF4C51',
|
||||
gray: '#808390'
|
||||
},
|
||||
categories: {
|
||||
coffee: '#6FD68F',
|
||||
vanilla: '#FFB46F',
|
||||
cacao: '#9D8EF0',
|
||||
candlenut: '#9EA6B0',
|
||||
shade: '#65D8BD',
|
||||
policyPurple: '#9D8EF0',
|
||||
policyPink: '#F47C86',
|
||||
marketGreen: '#6FD68F',
|
||||
marketRed: '#F47C86',
|
||||
marketBlue: '#5FC7D6'
|
||||
},
|
||||
grid: '#ECF0F6'
|
||||
},
|
||||
fontFamily: window.Helpers.getCssVar('font-family-base'),
|
||||
enableMenuLocalStorage: true // Enable menu state with local storage support
|
||||
};
|
||||
|
||||
// Choose the label color with the higher contrast against each chart slice.
|
||||
window.config.chartTextColor = function (color) {
|
||||
let hex = String(color || '').trim().replace(/^#/, '');
|
||||
if (/^[0-9a-f]{3}$/i.test(hex)) {
|
||||
hex = hex.split('').map(value => value + value).join('');
|
||||
}
|
||||
if (!/^[0-9a-f]{6}$/i.test(hex)) return '#212529';
|
||||
|
||||
const luminance = function (value) {
|
||||
const channels = value.match(/../g).map(channel => {
|
||||
const srgb = parseInt(channel, 16) / 255;
|
||||
return srgb <= 0.04045 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);
|
||||
});
|
||||
return channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722;
|
||||
};
|
||||
const background = luminance(hex);
|
||||
const dark = luminance('212529');
|
||||
const darkContrast = (Math.max(background, dark) + 0.05) / (Math.min(background, dark) + 0.05);
|
||||
const lightContrast = 1.05 / (background + 0.05);
|
||||
|
||||
return darkContrast >= lightContrast ? '#212529' : '#ffffff';
|
||||
};
|
||||
|
||||
window.assetsPath = document.documentElement.getAttribute('data-assets-path');
|
||||
window.templateName = document.documentElement.getAttribute('data-template');
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
cardColor = config.colors.cardColor;
|
||||
labelColor = config.colors.textMuted;
|
||||
headingColor = config.colors.headingColor;
|
||||
const pastelChart = config.chartColors || {};
|
||||
const pastelSemantic = pastelChart.semantic || {};
|
||||
const pastelSeries = pastelChart.series || ['#9D8EF0', '#6FD68F', '#FFB46F', '#5FC7D6', '#F47C86', '#9EA6B0', '#65D8BD', '#63C7E0'];
|
||||
|
||||
// swiper loop and autoplay
|
||||
// --------------------------------------------------------------------
|
||||
@@ -48,16 +51,10 @@
|
||||
grid: {
|
||||
show: false
|
||||
},
|
||||
colors: [config.colors.success],
|
||||
colors: [pastelSemantic.success || '#6FD68F'],
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shadeIntensity: 1,
|
||||
opacityFrom: 0.4,
|
||||
gradientToColors: [config.colors.cardColor],
|
||||
opacityTo: 0.1,
|
||||
stops: [0, 100]
|
||||
}
|
||||
type: 'solid',
|
||||
opacity: 0.22
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
@@ -151,13 +148,13 @@
|
||||
}
|
||||
},
|
||||
colors: [
|
||||
config.colors_label.primary,
|
||||
config.colors_label.primary,
|
||||
config.colors_label.primary,
|
||||
config.colors_label.primary,
|
||||
config.colors.primary,
|
||||
config.colors_label.primary,
|
||||
config.colors_label.primary
|
||||
pastelSemantic.secondary || '#9EA6B0',
|
||||
pastelSemantic.secondary || '#9EA6B0',
|
||||
pastelSemantic.secondary || '#9EA6B0',
|
||||
pastelSemantic.secondary || '#9EA6B0',
|
||||
pastelSemantic.primary || '#9D8EF0',
|
||||
pastelSemantic.secondary || '#9EA6B0',
|
||||
pastelSemantic.secondary || '#9EA6B0'
|
||||
],
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
@@ -262,18 +259,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
colors: [config.colors.primary],
|
||||
colors: [pastelSemantic.primary || '#9D8EF0'],
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shade: 'dark',
|
||||
shadeIntensity: 0.5,
|
||||
gradientToColors: [config.colors.primary],
|
||||
inverseColors: true,
|
||||
opacityFrom: 1,
|
||||
opacityTo: 0.6,
|
||||
stops: [30, 70, 100]
|
||||
}
|
||||
type: 'solid',
|
||||
opacity: 0.9
|
||||
},
|
||||
stroke: {
|
||||
dashArray: 10
|
||||
@@ -356,7 +345,10 @@
|
||||
}
|
||||
},
|
||||
|
||||
colors: [config.colors.primary, config.colors.secondary],
|
||||
colors: [
|
||||
pastelSeries[0] || '#9D8EF0',
|
||||
pastelSeries[1] || '#6FD68F'
|
||||
],
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
@@ -369,7 +361,10 @@
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
colors: [config.colors.primary, config.colors.secondary],
|
||||
colors: [
|
||||
pastelSeries[0] || '#9D8EF0',
|
||||
pastelSeries[1] || '#6FD68F'
|
||||
],
|
||||
fill: {
|
||||
opacity: 1
|
||||
},
|
||||
|
||||
@@ -238,6 +238,10 @@
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const pastelChart = config.chartColors || {};
|
||||
const pastelSemantic = pastelChart.semantic || {};
|
||||
const pastelCategory = pastelChart.categories || {};
|
||||
const gridColor = pastelChart.grid || '#E8EDF5';
|
||||
|
||||
// 1. Inisialisasi Icons
|
||||
if(typeof lucide !== 'undefined') {
|
||||
@@ -255,7 +259,7 @@
|
||||
{
|
||||
label: 'Desa Mengadopsi (Realisasi)',
|
||||
data: [0, 3, 8, 15], // Kumulatif: 3 Desa Inti + 12 Baru
|
||||
backgroundColor: '#8b5cf6', // Purple-500
|
||||
backgroundColor: pastelCategory.policyPurple || '#9D8EF0',
|
||||
borderRadius: 4,
|
||||
order: 2
|
||||
},
|
||||
@@ -263,12 +267,12 @@
|
||||
label: 'Target Perluasan ToC',
|
||||
type: 'line',
|
||||
data: [3, 5, 10, 15],
|
||||
borderColor: '#d946ef', // Fuchsia-500
|
||||
borderColor: pastelCategory.policyPink || '#F47C86',
|
||||
backgroundColor: 'transparent',
|
||||
borderWidth: 3,
|
||||
borderDash: [5, 5],
|
||||
tension: 0.3,
|
||||
pointBackgroundColor: '#d946ef',
|
||||
pointBackgroundColor: pastelCategory.policyPink || '#F47C86',
|
||||
order: 1
|
||||
}
|
||||
]
|
||||
@@ -280,7 +284,7 @@
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: '#f3f4f6' },
|
||||
grid: { color: gridColor },
|
||||
ticks: { stepSize: 5 }
|
||||
},
|
||||
x: { grid: { display: false } }
|
||||
@@ -309,10 +313,10 @@
|
||||
datasets: [{
|
||||
data: [4, 2, 1, 1],
|
||||
backgroundColor: [
|
||||
'#f59e0b', // Amber-500
|
||||
'#10b981', // Emerald-500
|
||||
'#3b82f6', // Blue-500
|
||||
'#8b5cf6' // Purple-500
|
||||
pastelSemantic.warning || '#FFB46F',
|
||||
pastelSemantic.success || '#6FD68F',
|
||||
pastelSemantic.info || '#5FC7D6',
|
||||
pastelCategory.policyPurple || '#9D8EF0'
|
||||
],
|
||||
borderWidth: 2,
|
||||
borderColor: '#ffffff',
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
transition: transform .2s ease;
|
||||
}
|
||||
.tk-stat:hover { transform: translateX(4px); }
|
||||
.tk-stat.green { border-left-color: #28a745; }
|
||||
.tk-stat.blue { border-left-color: #007bff; }
|
||||
.tk-stat.amber { border-left-color: #fd7e14; }
|
||||
.tk-stat.green { border-left-color: #6FD68F; }
|
||||
.tk-stat.blue { border-left-color: #5FC7D6; }
|
||||
.tk-stat.amber { border-left-color: #FFB46F; }
|
||||
|
||||
.tk-stat-lbl {
|
||||
font-size: 10px; text-transform: uppercase;
|
||||
@@ -88,38 +88,38 @@
|
||||
'new_month' => 12,
|
||||
|
||||
'enumerators' => [
|
||||
['name'=>'Enumerator Ngada 1','done'=>72,'target'=>80,'color'=>'#28a745','bg'=>'rgba(40,167,69,.15)','tx'=>'#28a745'],
|
||||
['name'=>'Enumerator Ngada 2','done'=>58,'target'=>80,'color'=>'#007bff','bg'=>'rgba(0,123,255,.15)','tx'=>'#007bff'],
|
||||
['name'=>'Enumerator Ngada 3','done'=>48,'target'=>80,'color'=>'#fd7e14','bg'=>'rgba(253,126,20,.15)','tx'=>'#fd7e14'],
|
||||
['name'=>'Enumerator Ngada 4','done'=>36,'target'=>80,'color'=>'#dc3545','bg'=>'rgba(220,53,69,.15)','tx'=>'#dc3545'],
|
||||
['name'=>'Enumerator Ngada 1','done'=>72,'target'=>80,'color'=>'#6FD68F','bg'=>'rgba(111,214,143,.24)','tx'=>'#6FD68F'],
|
||||
['name'=>'Enumerator Ngada 2','done'=>58,'target'=>80,'color'=>'#5FC7D6','bg'=>'rgba(95,199,214,.24)','tx'=>'#5FC7D6'],
|
||||
['name'=>'Enumerator Ngada 3','done'=>48,'target'=>80,'color'=>'#FFB46F','bg'=>'rgba(255,180,111,.26)','tx'=>'#FFB46F'],
|
||||
['name'=>'Enumerator Ngada 4','done'=>36,'target'=>80,'color'=>'#F47C86','bg'=>'rgba(244,124,134,.26)','tx'=>'#F47C86'],
|
||||
],
|
||||
|
||||
'recent' => [
|
||||
['code'=>'TKD-2026-0178','name'=>'Markus Bela', 'init'=>'MB','desa'=>'Wangka', 'date'=>'24 Jun 2026','pct'=>100,'status'=>'completed', 'bg'=>'rgba(40,167,69,.15)','tx'=>'#28a745'],
|
||||
['code'=>'TKD-2026-0177','name'=>'Theresia Nuka','init'=>'TN','desa'=>'Langa', 'date'=>'23 Jun 2026','pct'=>67, 'status'=>'in_progress','bg'=>'rgba(0,123,255,.15)','tx'=>'#007bff'],
|
||||
['code'=>'TKD-2026-0176','name'=>'Yosef Meo', 'init'=>'YM','desa'=>'Bobo', 'date'=>'22 Jun 2026','pct'=>100,'status'=>'completed', 'bg'=>'rgba(40,167,69,.15)','tx'=>'#28a745'],
|
||||
['code'=>'TKD-2026-0175','name'=>'Maria Seda', 'init'=>'MS','desa'=>'Tana Wolo','date'=>'21 Jun 2026','pct'=>22, 'status'=>'draft', 'bg'=>'rgba(253,126,20,.15)','tx'=>'#fd7e14'],
|
||||
['code'=>'TKD-2026-0174','name'=>'Rafael Bolo', 'init'=>'RB','desa'=>'Wangka', 'date'=>'20 Jun 2026','pct'=>11, 'status'=>'draft', 'bg'=>'rgba(220,53,69,.15)','tx'=>'#dc3545'],
|
||||
['code'=>'TKD-2026-0178','name'=>'Markus Bela', 'init'=>'MB','desa'=>'Wangka', 'date'=>'24 Jun 2026','pct'=>100,'status'=>'completed', 'bg'=>'rgba(111,214,143,.24)','tx'=>'#6FD68F'],
|
||||
['code'=>'TKD-2026-0177','name'=>'Theresia Nuka','init'=>'TN','desa'=>'Langa', 'date'=>'23 Jun 2026','pct'=>67, 'status'=>'in_progress','bg'=>'rgba(95,199,214,.24)','tx'=>'#5FC7D6'],
|
||||
['code'=>'TKD-2026-0176','name'=>'Yosef Meo', 'init'=>'YM','desa'=>'Bobo', 'date'=>'22 Jun 2026','pct'=>100,'status'=>'completed', 'bg'=>'rgba(111,214,143,.24)','tx'=>'#6FD68F'],
|
||||
['code'=>'TKD-2026-0175','name'=>'Maria Seda', 'init'=>'MS','desa'=>'Tana Wolo','date'=>'21 Jun 2026','pct'=>22, 'status'=>'draft', 'bg'=>'rgba(255,180,111,.26)','tx'=>'#FFB46F'],
|
||||
['code'=>'TKD-2026-0174','name'=>'Rafael Bolo', 'init'=>'RB','desa'=>'Wangka', 'date'=>'20 Jun 2026','pct'=>11, 'status'=>'draft', 'bg'=>'rgba(244,124,134,.26)','tx'=>'#F47C86'],
|
||||
],
|
||||
|
||||
'komoditas' => [
|
||||
['label'=>'Kopi', 'total'=>148,'pct'=>60,'color'=>'#28a745','arc'=>174],
|
||||
['label'=>'Kakao', 'total'=>82, 'pct'=>33,'color'=>'#007bff','arc'=>96],
|
||||
['label'=>'Vanili','total'=>33, 'pct'=>13,'color'=>'#fd7e14','arc'=>38],
|
||||
['label'=>'Kemiri','total'=>22, 'pct'=>9, 'color'=>'#6f42c1','arc'=>26],
|
||||
['label'=>'Kopi', 'total'=>148,'pct'=>60,'color'=>'#6FD68F','arc'=>174],
|
||||
['label'=>'Kakao', 'total'=>82, 'pct'=>33,'color'=>'#9D8EF0','arc'=>96],
|
||||
['label'=>'Vanili','total'=>33, 'pct'=>13,'color'=>'#FFB46F','arc'=>38],
|
||||
['label'=>'Kemiri','total'=>22, 'pct'=>9, 'color'=>'#9EA6B0','arc'=>26],
|
||||
],
|
||||
|
||||
'kecamatan' => [
|
||||
['name'=>'Bajawa','total'=>89,'pct'=>78,'color'=>'#28a745'],
|
||||
['name'=>'Soa', 'total'=>68,'pct'=>59,'color'=>'#007bff'],
|
||||
['name'=>'Golewa','total'=>57,'pct'=>50,'color'=>'#fd7e14'],
|
||||
['name'=>'Bajawa','total'=>89,'pct'=>78,'color'=>'#6FD68F'],
|
||||
['name'=>'Soa', 'total'=>68,'pct'=>59,'color'=>'#5FC7D6'],
|
||||
['name'=>'Golewa','total'=>57,'pct'=>50,'color'=>'#FFB46F'],
|
||||
],
|
||||
|
||||
'pending' => [
|
||||
['name'=>'Theresia Nuka','section'=>'D4-K3','pct'=>67,'bg'=>'rgba(0,123,255,.15)','tx'=>'#007bff'],
|
||||
['name'=>'Maria Seda', 'section'=>'D2-K2','pct'=>22,'bg'=>'rgba(253,126,20,.15)','tx'=>'#fd7e14'],
|
||||
['name'=>'Rafael Bolo', 'section'=>'D2-K1','pct'=>11,'bg'=>'rgba(220,53,69,.15)', 'tx'=>'#dc3545'],
|
||||
['name'=>'Antonius Wea', 'section'=>'D2-K1','pct'=>0, 'bg'=>'rgba(220,53,69,.15)', 'tx'=>'#dc3545'],
|
||||
['name'=>'Theresia Nuka','section'=>'D4-K3','pct'=>67,'bg'=>'rgba(95,199,214,.24)','tx'=>'#5FC7D6'],
|
||||
['name'=>'Maria Seda', 'section'=>'D2-K2','pct'=>22,'bg'=>'rgba(255,180,111,.26)','tx'=>'#FFB46F'],
|
||||
['name'=>'Rafael Bolo', 'section'=>'D2-K1','pct'=>11,'bg'=>'rgba(244,124,134,.26)', 'tx'=>'#F47C86'],
|
||||
['name'=>'Antonius Wea', 'section'=>'D2-K1','pct'=>0, 'bg'=>'rgba(244,124,134,.26)', 'tx'=>'#F47C86'],
|
||||
],
|
||||
'pending_more' => 32,
|
||||
];
|
||||
@@ -258,10 +258,10 @@
|
||||
@foreach($d['recent'] as $s)
|
||||
@php
|
||||
$bc = match(true) {
|
||||
$s['pct'] === 100 => '#28a745',
|
||||
$s['pct'] >= 50 => '#007bff',
|
||||
$s['pct'] >= 20 => '#fd7e14',
|
||||
default => '#dc3545',
|
||||
$s['pct'] === 100 => '#6FD68F',
|
||||
$s['pct'] >= 50 => '#5FC7D6',
|
||||
$s['pct'] >= 20 => '#FFB46F',
|
||||
default => '#F47C86',
|
||||
};
|
||||
@endphp
|
||||
<tr>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -208,6 +208,10 @@
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const pastelChart = config.chartColors || {};
|
||||
const pastelSemantic = pastelChart.semantic || {};
|
||||
const pastelCategory = pastelChart.categories || {};
|
||||
const gridColor = pastelChart.grid || '#E8EDF5';
|
||||
|
||||
// 1. Inisialisasi Icons
|
||||
if(typeof lucide !== 'undefined') {
|
||||
@@ -225,7 +229,7 @@
|
||||
{
|
||||
label: 'Mentah/Gelondong (Rp)',
|
||||
data: [35000, 200000, 25000, 15000],
|
||||
backgroundColor: '#9ca3af', // Gray
|
||||
backgroundColor: pastelSemantic.gray || '#9EA6B0',
|
||||
borderRadius: { topLeft: 4, topRight: 4, bottomLeft: 0, bottomRight: 0 },
|
||||
barPercentage: 0.6,
|
||||
categoryPercentage: 0.8
|
||||
@@ -233,7 +237,7 @@
|
||||
{
|
||||
label: 'Produk Olahan (Rp)',
|
||||
data: [120000, 1500000, 80000, 45000],
|
||||
backgroundColor: '#d97706', // Amber-600
|
||||
backgroundColor: pastelSemantic.warning || '#FFB46F',
|
||||
borderRadius: { topLeft: 4, topRight: 4, bottomLeft: 0, bottomRight: 0 },
|
||||
barPercentage: 0.6,
|
||||
categoryPercentage: 0.8
|
||||
@@ -246,7 +250,7 @@
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: '#f3f4f6' },
|
||||
grid: { color: gridColor },
|
||||
ticks: {
|
||||
callback: function(value) {
|
||||
if (value >= 1000000) return 'Rp ' + (value/1000000) + ' Jt';
|
||||
@@ -282,8 +286,8 @@
|
||||
{
|
||||
label: 'Kopi Bubuk/Roasted',
|
||||
data: [12, 15, 18, 25, 30, 45],
|
||||
borderColor: '#6F4E37',
|
||||
backgroundColor: 'rgba(111, 78, 55, 0.1)',
|
||||
borderColor: pastelCategory.coffee || '#6FD68F',
|
||||
backgroundColor: 'rgba(104, 193, 124, 0.16)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
borderWidth: 2
|
||||
@@ -291,7 +295,7 @@
|
||||
{
|
||||
label: 'Kakao Fermentasi',
|
||||
data: [5, 8, 12, 15, 22, 28],
|
||||
borderColor: '#D2691E',
|
||||
borderColor: pastelCategory.cacao || '#9D8EF0',
|
||||
backgroundColor: 'transparent',
|
||||
tension: 0.4,
|
||||
borderWidth: 2
|
||||
@@ -305,7 +309,7 @@
|
||||
scales: {
|
||||
y: {
|
||||
title: { display: true, text: 'Volume (Ton)' },
|
||||
grid: { color: '#f3f4f6' }
|
||||
grid: { color: gridColor }
|
||||
},
|
||||
x: { grid: { display: false } }
|
||||
},
|
||||
|
||||
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
@@ -212,6 +212,9 @@
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const pastelChart = config.chartColors || {};
|
||||
const pastelCategory = pastelChart.categories || {};
|
||||
const gridColor = pastelChart.grid || '#E8EDF5';
|
||||
|
||||
// 1. Inisialisasi Icons
|
||||
if(typeof lucide !== 'undefined') {
|
||||
@@ -230,8 +233,8 @@
|
||||
label: 'Total Produksi Lahan (Ton)',
|
||||
type: 'line',
|
||||
data: [100, 110, 125, 145],
|
||||
borderColor: '#10b981', // Emerald-500
|
||||
backgroundColor: '#10b981',
|
||||
borderColor: pastelCategory.marketGreen || '#6FD68F',
|
||||
backgroundColor: pastelCategory.marketGreen || '#6FD68F',
|
||||
borderWidth: 3,
|
||||
tension: 0.3,
|
||||
fill: false
|
||||
@@ -239,14 +242,14 @@
|
||||
{
|
||||
label: 'Tengkulak / Pasar Bebas',
|
||||
data: [80, 65, 40, 25],
|
||||
backgroundColor: '#ef4444', // Red-500
|
||||
backgroundColor: pastelCategory.marketRed || '#F47C86',
|
||||
stack: 'Stack 0',
|
||||
borderRadius: { topLeft: 0, topRight: 0, bottomLeft: 4, bottomRight: 4 }
|
||||
},
|
||||
{
|
||||
label: 'Kelembagaan (Koperasi/BUMDes)',
|
||||
data: [20, 45, 85, 120],
|
||||
backgroundColor: '#3b82f6', // Blue-500
|
||||
backgroundColor: pastelCategory.marketBlue || '#5FC7D6',
|
||||
stack: 'Stack 0',
|
||||
borderRadius: { topLeft: 4, topRight: 4, bottomLeft: 0, bottomRight: 0 }
|
||||
}
|
||||
@@ -260,7 +263,7 @@
|
||||
y: {
|
||||
stacked: true,
|
||||
beginAtZero: true,
|
||||
grid: { color: '#f3f4f6' }
|
||||
grid: { color: gridColor }
|
||||
},
|
||||
x: {
|
||||
stacked: true,
|
||||
@@ -292,20 +295,20 @@
|
||||
{
|
||||
label: 'Kopi & Kakao',
|
||||
data: [150, 280, 320, 500, 650, 850],
|
||||
borderColor: '#6F4E37',
|
||||
borderColor: pastelCategory.coffee || '#6FD68F',
|
||||
backgroundColor: 'transparent',
|
||||
tension: 0.4,
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#6F4E37'
|
||||
pointBackgroundColor: pastelCategory.coffee || '#6FD68F'
|
||||
},
|
||||
{
|
||||
label: 'Vanili & Kemiri',
|
||||
data: [50, 120, 210, 350, 420, 600],
|
||||
borderColor: '#eab308',
|
||||
borderColor: pastelCategory.vanilla || '#FFB46F',
|
||||
backgroundColor: 'transparent',
|
||||
tension: 0.4,
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#eab308'
|
||||
pointBackgroundColor: pastelCategory.vanilla || '#FFB46F'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -316,7 +319,7 @@
|
||||
scales: {
|
||||
y: {
|
||||
title: { display: false },
|
||||
grid: { color: '#f3f4f6' },
|
||||
grid: { color: gridColor },
|
||||
ticks: {
|
||||
callback: function(value) {
|
||||
return 'Rp ' + value + ' Jt';
|
||||
|
||||
@@ -0,0 +1,965 @@
|
||||
@extends('layout.main')
|
||||
|
||||
@push('style')
|
||||
<style>
|
||||
.pasar-filter-card .filter-item {
|
||||
min-width: 190px;
|
||||
}
|
||||
|
||||
.pasar-kpi-value {
|
||||
color: var(--bs-heading-color);
|
||||
font-size: 1.6rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.pasar-kpi-label {
|
||||
color: var(--bs-heading-color);
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.pasar-kpi-desc {
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.chart-group-header-toggle {
|
||||
cursor: pointer;
|
||||
background-color: rgba(var(--bs-primary-rgb, 115, 103, 240), 0.08);
|
||||
border-inline-start: 4px solid var(--bs-primary, #9D8EF0);
|
||||
box-shadow: none;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.chart-group-header-toggle:hover {
|
||||
background-color: rgba(var(--bs-primary-rgb, 115, 103, 240), 0.13);
|
||||
}
|
||||
|
||||
.chart-group-header-toggle .card-body {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.chart-group-header-toggle h5 {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.chart-group-header-toggle:focus-visible {
|
||||
outline: 2px solid var(--bs-primary);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.chart-group-icon-toggle {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chart-group-icon-toggle .icon-base {
|
||||
font-size: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.chart-group-panel.show,
|
||||
.chart-group-panel.collapsing {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.pasar-chart-card .card-title h5 {
|
||||
color: var(--bs-heading-color);
|
||||
font-size: 1.125rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.35;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.pasar-chart-card .card-title small,
|
||||
.chart-group-header-toggle p {
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.pasar-chart {
|
||||
min-height: 330px;
|
||||
}
|
||||
|
||||
.pasar-chart-empty {
|
||||
min-height: 330px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: 0.8125rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pasar-chart-card .apexcharts-tooltip {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
overflow: visible !important;
|
||||
white-space: normal;
|
||||
max-width: 260px;
|
||||
}
|
||||
|
||||
.pasar-chart-tooltip {
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, .15);
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.pasar-trend-tooltip-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
.pasar-filter-card .filter-item {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.market-info-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex: 0 0 20px;
|
||||
padding: 0;
|
||||
border: 1px solid var(--bs-secondary-color);
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
.market-info-button:hover, .market-info-button:focus-visible {
|
||||
color: var(--bs-primary);
|
||||
border-color: var(--bs-primary);
|
||||
outline: 2px solid rgba(var(--bs-primary-rgb), .2);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.international-provider-logo {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.international-provider-logo[hidden] { display: none; }
|
||||
.international-provider-logo img { object-fit: contain; }
|
||||
.international-market-track {
|
||||
display: flex;
|
||||
gap: 1.25rem;
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: none;
|
||||
scrollbar-width: none;
|
||||
padding: 2px 2px 16px;
|
||||
}
|
||||
.international-market-track::-webkit-scrollbar { display: none; }
|
||||
.international-market-card {
|
||||
flex: 0 0 calc((100% - 2.5rem) / 3);
|
||||
min-width: 0;
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
@media (max-width: 991.98px) {
|
||||
.international-market-card { flex-basis: calc((100% - 1.25rem) / 2); }
|
||||
}
|
||||
@media (max-width: 575.98px) {
|
||||
.international-market-card { flex-basis: 100%; }
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="container-xxl flex-grow-1 container-p-y">
|
||||
<div class="row g-4 mb-4">
|
||||
<form id="form-filter" class="col-12" method="GET" action="{{ route('dashboard.pasar') }}">
|
||||
<div class="card card-border-shadow-primary pasar-filter-card">
|
||||
<div class="card-body py-3">
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<div class="d-flex align-items-center me-2">
|
||||
<i class="icon-base ti tabler-map-2 me-2"></i>
|
||||
<span class="fw-medium">Wilayah</span>
|
||||
</div>
|
||||
|
||||
<div class="filter-item">
|
||||
<select id="filter-provinsi" class="form-select form-select-sm" name="provinsi_id" data-default="{{ $provinsi_id }}">
|
||||
<option value="">Semua Provinsi</option>
|
||||
@foreach($provinsi as $p)
|
||||
<option value="{{ $p->provinsi_id }}" {{ old('provinsi_id', $provinsi_id) == $p->provinsi_id ? 'selected' : '' }}>
|
||||
{{ $p->provinsi }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-item">
|
||||
<select id="filter-kabkota" class="form-select form-select-sm" name="kabupatenKota_id" data-default="{{ $kabupatenKota_id }}" disabled>
|
||||
<option value="">Semua Kabupaten/Kota</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-item">
|
||||
<select id="filter-kecamatan" class="form-select form-select-sm" name="kecamatan_id" data-default="{{ $kecamatan_id }}" disabled>
|
||||
<option value="">Semua Kecamatan</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-item">
|
||||
<select id="filter-desa" class="form-select form-select-sm" name="desaKelurahan_id" data-default="{{ $desaKelurahan_id }}" disabled>
|
||||
<option value="">Semua Desa/Kelurahan</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-3">
|
||||
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<div class="d-flex align-items-center me-2">
|
||||
<i class="icon-base ti tabler-adjustments-horizontal me-2"></i>
|
||||
<span class="fw-medium">Data</span>
|
||||
</div>
|
||||
|
||||
<div class="filter-item">
|
||||
<select id="filter-komoditas" class="form-select form-select-sm" name="komoditas_id">
|
||||
<option value="">Semua Komoditas</option>
|
||||
@foreach ($komoditas as $item)
|
||||
<option value="{{ $item->id }}" {{ old('komoditas_id', $komoditas_id) == $item->id ? 'selected' : '' }}>
|
||||
{{ $item->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-item">
|
||||
<select id="filter-segmen" class="form-select form-select-sm" name="segmen">
|
||||
<option value="">Semua Segmen</option>
|
||||
@foreach ($segments as $segment)
|
||||
<option value="{{ $segment['name'] }}" {{ old('segmen', $segmen) == $segment['name'] ? 'selected' : '' }}>
|
||||
{{ $segment['name'] }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="icon-base ti tabler-filter me-1"></i>
|
||||
Filter
|
||||
</button>
|
||||
<button type="button" id="btn-reset-dashboard-filter" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="icon-base ti tabler-refresh me-1"></i>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
@foreach ($priceCards as $priceCard)
|
||||
<div class="col-12 col-md-6 col-xl-3">
|
||||
<div class="card card-border-shadow-{{ $priceCard['variant'] }} h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-column align-items-start gap-3">
|
||||
<div class="avatar avatar-lg">
|
||||
<span class="avatar-initial rounded bg-label-{{ $priceCard['variant'] }}">
|
||||
<i class="icon-base ti {{ $priceCard['icon'] }} icon-28px"></i>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="d-flex align-items-baseline gap-2 flex-wrap">
|
||||
<h4 class="mb-0 pasar-kpi-value">{{ $priceCard['value'] }}</h4>
|
||||
<small class="text-body-secondary">{{ $priceCard['unit'] }}</small>
|
||||
</div>
|
||||
|
||||
<p class="mb-0 mt-1 pasar-kpi-label">{{ $priceCard['label'] }}</p>
|
||||
<small class="text-body-secondary pasar-kpi-desc">{{ $priceCard['description'] }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 pt-3 border-top d-flex align-items-center justify-content-between gap-2 flex-wrap">
|
||||
<span class="badge bg-label-{{ $priceCard['variant'] }}">{{ $priceCard['records'] > 0 ? 'Rata-rata harga' : 'Belum ada data' }}</span>
|
||||
<small class="text-body-secondary">{{ number_format($priceCard['records'], 0, ',', '.') }} data harga</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="card chart-group-header-toggle mb-4" role="button" tabindex="0"
|
||||
data-bs-toggle="collapse" data-bs-target="#saluran-harga-charts"
|
||||
aria-expanded="false" aria-controls="saluran-harga-charts" aria-label="Tampilkan chart">
|
||||
<div class="card-body d-flex justify-content-between align-items-center gap-3">
|
||||
<div>
|
||||
<h5>Saluran & Harga</h5>
|
||||
<p class="mb-0 text-body-secondary">Saluran penjualan, hubungan dengan pembeli, waktu penjualan, akses ke saluran alternatif, dan tren harga.</p>
|
||||
</div>
|
||||
<span class="btn btn-icon btn-label-secondary btn-sm chart-group-icon-toggle" aria-hidden="true">
|
||||
<i class="icon-base ti tabler-chevron-down"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@php
|
||||
$chartDefinitions = [
|
||||
'saluranPenjualan' => ['title' => 'Saluran Penjualan Utama', 'description' => 'Kepada siapa petani biasanya menjual hasil panen?', 'variant' => 'success'],
|
||||
'hubunganPembeli' => ['title' => 'Sifat Hubungan dengan Pembeli', 'description' => 'Hubungan jual petani dengan pembeli utama saat ini.', 'variant' => 'primary'],
|
||||
'waktuPenjualan' => ['title' => 'Waktu Penjualan Hasil Panen', 'description' => 'Kapan petani biasanya menjual hasil panen?', 'variant' => 'warning'],
|
||||
'aksesSaluranLain' => ['title' => 'Pengalaman & Akses ke Saluran Lain', 'description' => 'Pengalaman menjual ke tempat lain dan akses yang masih dimiliki.', 'variant' => 'info'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
<div class="collapse chart-group-panel" id="saluran-harga-charts">
|
||||
<div class="row g-4 mb-4">
|
||||
@foreach ($chartDefinitions as $key => $definition)
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card pasar-chart-card h-100">
|
||||
<div class="card-header d-flex align-items-start justify-content-between gap-2">
|
||||
<div class="card-title mb-0">
|
||||
<h5 class="m-0 me-2" id="title-{{ $key }}">{{ $definition['title'] }}</h5>
|
||||
<small class="text-body-secondary">{{ $definition['description'] }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div id="chart-{{ $key }}" class="pasar-chart" role="img" aria-labelledby="title-{{ $key }}" aria-describedby="summary-{{ $key }}"></div>
|
||||
<span id="summary-{{ $key }}" class="visually-hidden">
|
||||
@foreach ($marketCharts[$key]['labels'] as $index => $label)
|
||||
{{ $label }}: {{ $marketCharts[$key]['series'][$index] }} responden ({{ $marketCharts[$key]['percentages'][$index] }}%).
|
||||
@endforeach
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@forelse ($priceTrend['commodities'] as $commodity)
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card pasar-chart-card h-100">
|
||||
<div class="card-header">
|
||||
<div class="card-title mb-0">
|
||||
<h5 class="m-0 me-2" id="title-tren-harga-{{ $commodity['id'] }}">Tren Harga 3 Musim — {{ $commodity['name'] }}</h5>
|
||||
<small class="text-body-secondary">Rata-rata harga jual per bentuk produk selama tiga periode panen (Rp/kg).</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="chart-tren-harga-{{ $commodity['id'] }}" class="pasar-chart" role="img" aria-labelledby="title-tren-harga-{{ $commodity['id'] }}" aria-describedby="summary-tren-harga-{{ $commodity['id'] }}"></div>
|
||||
<span id="summary-tren-harga-{{ $commodity['id'] }}" class="visually-hidden"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="col-12">
|
||||
<div class="card pasar-chart-card">
|
||||
<div class="card-header">
|
||||
<div class="card-title mb-0"><h5 class="m-0">Tren Harga 3 Musim</h5></div>
|
||||
</div>
|
||||
<div class="card-body pasar-chart-empty">Belum ada komoditas untuk filter yang dipilih.</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card chart-group-header-toggle mb-4" role="button" tabindex="0"
|
||||
data-bs-toggle="collapse" data-bs-target="#kesiapan-premium-charts"
|
||||
aria-expanded="false" aria-controls="kesiapan-premium-charts" aria-label="Tampilkan chart">
|
||||
<div class="card-body d-flex justify-content-between align-items-center gap-3">
|
||||
<div>
|
||||
<h5>Kesiapan Pasar Premium</h5>
|
||||
<p class="mb-0 text-body-secondary">Kesadaran petani tentang pasar premium, persyaratan masuk, dan pengalaman mengaksesnya.</p>
|
||||
</div>
|
||||
<span class="btn btn-icon btn-label-secondary btn-sm chart-group-icon-toggle" aria-hidden="true">
|
||||
<i class="icon-base ti tabler-chevron-down"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="collapse chart-group-panel" id="kesiapan-premium-charts">
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card pasar-chart-card">
|
||||
<div class="card-header">
|
||||
<div class="card-title mb-0">
|
||||
<h5 class="m-0 me-2" id="title-kesadaranPremium">Kesadaran Pasar Premium</h5>
|
||||
<small class="text-body-secondary">Pengetahuan tentang pasar premium dan persyaratan yang dibutuhkan untuk masuk ke pasar tersebut.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="chart-kesadaranPremium" class="pasar-chart" role="img" aria-labelledby="title-kesadaranPremium" aria-describedby="summary-kesadaranPremium"></div>
|
||||
<span id="summary-kesadaranPremium" class="visually-hidden">
|
||||
@foreach ($premiumAwareness['fullLabels'] as $index => $label)
|
||||
{{ $label }}: {{ $premiumAwareness['series'][$index] }} responden ({{ $premiumAwareness['percentages'][$index] }}%).
|
||||
@endforeach
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="mt-4" aria-labelledby="international-market-title" id="international-market">
|
||||
<div class="d-flex align-items-center justify-content-between gap-3 flex-wrap mb-2">
|
||||
<h5 id="international-market-title" class="mb-0">Harga Komoditas Internasional</h5>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-market-prev aria-label="Harga sebelumnya">←</button>
|
||||
<button type="button" class="btn btn-sm btn-icon btn-outline-secondary" data-market-pause aria-pressed="false" aria-label="Jeda slider" title="Jeda slider"><i class="icon-base ti tabler-player-pause" aria-hidden="true"></i></button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-market-next aria-label="Harga berikutnya">→</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-body-secondary small mb-2">Setiap kartu menampilkan satu komoditas dari satu sumber. Periode dan jenis harga mengikuti penyedia; tidak mengikuti filter wilayah.</p>
|
||||
<p class="small text-body-secondary" data-market-status role="status">Memuat harga internasional...</p>
|
||||
<div class="international-market-track" tabindex="0" role="region" aria-roledescription="carousel" aria-label="Harga komoditas menurut sumber"></div>
|
||||
<template id="international-market-card">
|
||||
<article class="card card-border-shadow-info international-market-card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start gap-3 mb-3">
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<h6 class="mb-0" data-name></h6>
|
||||
<button type="button" class="market-info-button" data-market-info data-update-info aria-label="Informasi pembaruan harga" aria-expanded="false">!</button>
|
||||
</div>
|
||||
<div class="small">Sumber: <a data-source target="_blank" rel="noopener noreferrer"></a></div>
|
||||
</div>
|
||||
<span class="international-provider-logo flex-shrink-0" data-provider-logo-wrap hidden>
|
||||
<img data-provider-logo width="32" height="32" alt="" decoding="async">
|
||||
</span>
|
||||
</div>
|
||||
<div class="pasar-kpi-value" data-price></div>
|
||||
<div class="small text-body-secondary mt-1" data-unit></div>
|
||||
<div class="d-flex align-items-center gap-2 mt-3 pt-3 border-top">
|
||||
<div class="fw-semibold fs-5" data-rupiah></div>
|
||||
<button type="button" class="market-info-button" data-market-info data-fx-info aria-label="Informasi kurs konversi" aria-expanded="false">!</button>
|
||||
</div>
|
||||
<p class="small text-body-secondary mt-2 mb-2" data-description></p>
|
||||
<div class="small text-body-secondary" data-period></div>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
</section>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
const marketSection = document.getElementById('international-market');
|
||||
const marketTrack = marketSection.querySelector('.international-market-track');
|
||||
const marketStatus = marketSection.querySelector('[data-market-status]');
|
||||
const pauseButton = marketSection.querySelector('[data-market-pause]');
|
||||
let activeMarketInfo = null;
|
||||
function closeMarketInfo() {
|
||||
if (!activeMarketInfo) return;
|
||||
activeMarketInfo.button.setAttribute('aria-expanded', 'false');
|
||||
activeMarketInfo.popover.dispose();
|
||||
activeMarketInfo = null;
|
||||
}
|
||||
document.addEventListener('click', event => {
|
||||
const button = event.target.closest('[data-market-info]');
|
||||
if (button && marketSection.contains(button)) {
|
||||
const same = activeMarketInfo?.button === button;
|
||||
closeMarketInfo();
|
||||
if (same) return;
|
||||
const popover = new bootstrap.Popover(button, {
|
||||
container: 'body', placement: 'auto', trigger: 'manual', html: false,
|
||||
title: button.hasAttribute('data-fx-info') ? 'Kurs konversi' : 'Pembaruan harga',
|
||||
content: button.dataset.info || 'Informasi belum tersedia.'
|
||||
});
|
||||
popover.show();
|
||||
button.setAttribute('aria-expanded', 'true');
|
||||
activeMarketInfo = { button, popover };
|
||||
} else if (!event.target.closest('.popover')) closeMarketInfo();
|
||||
});
|
||||
document.addEventListener('keydown', event => {
|
||||
if (event.key === 'Escape') closeMarketInfo();
|
||||
});
|
||||
marketTrack.addEventListener('focusout', event => {
|
||||
if (activeMarketInfo?.button === event.target) closeMarketInfo();
|
||||
});
|
||||
|
||||
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||
let marketPaused = reducedMotion.matches;
|
||||
let marketSignature = '';
|
||||
const quoteDateFormat = new Intl.DateTimeFormat('id-ID', {
|
||||
dateStyle: 'medium', timeStyle: 'long', timeZone: 'Asia/Jakarta'
|
||||
});
|
||||
const rupiahFormat = new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency', currency: 'IDR', maximumFractionDigits: 2
|
||||
});
|
||||
function syncMarketPause() {
|
||||
const label = marketPaused ? 'Putar slider' : 'Jeda slider';
|
||||
pauseButton.querySelector('i').className = 'icon-base ti ' + (marketPaused ? 'tabler-player-play' : 'tabler-player-pause');
|
||||
pauseButton.setAttribute('aria-label', label);
|
||||
pauseButton.title = label;
|
||||
pauseButton.setAttribute('aria-pressed', String(marketPaused));
|
||||
}
|
||||
syncMarketPause();
|
||||
pauseButton.addEventListener('click', () => { marketPaused = !marketPaused; syncMarketPause(); });
|
||||
let marketCycleWidth = 0;
|
||||
let lastMarketFrame = null;
|
||||
function measureMarketCycle() {
|
||||
const first = marketTrack.firstElementChild;
|
||||
const duplicate = marketTrack.querySelector('[data-market-clone]');
|
||||
marketCycleWidth = first && duplicate ? duplicate.offsetLeft - first.offsetLeft : 0;
|
||||
}
|
||||
function slideMarket(direction) {
|
||||
if (!marketTrack.firstElementChild) return;
|
||||
const step = marketTrack.firstElementChild.getBoundingClientRect().width + 20;
|
||||
if (direction < 0 && marketTrack.scrollLeft < step && marketCycleWidth) {
|
||||
marketTrack.scrollLeft += marketCycleWidth;
|
||||
}
|
||||
marketTrack.scrollLeft += step * direction;
|
||||
}
|
||||
marketSection.querySelector('[data-market-prev]').addEventListener('click', () => slideMarket(-1));
|
||||
marketSection.querySelector('[data-market-next]').addEventListener('click', () => slideMarket(1));
|
||||
window.addEventListener('resize', measureMarketCycle);
|
||||
function animateMarket(timestamp) {
|
||||
const elapsed = lastMarketFrame === null ? 0 : Math.min(timestamp - lastMarketFrame, 64);
|
||||
lastMarketFrame = timestamp;
|
||||
if (marketCycleWidth > 0) {
|
||||
if (!marketPaused && !document.hidden && !marketTrack.contains(document.activeElement)) {
|
||||
// Keep subpixel progress independently: scrollLeft may round tiny per-frame changes.
|
||||
marketScrollPosition += elapsed * 0.028;
|
||||
marketTrack.scrollLeft = marketScrollPosition;
|
||||
} else {
|
||||
marketScrollPosition = marketTrack.scrollLeft;
|
||||
}
|
||||
if (marketTrack.scrollLeft >= marketCycleWidth) {
|
||||
marketScrollPosition = marketTrack.scrollLeft - marketCycleWidth;
|
||||
marketTrack.scrollLeft = marketScrollPosition;
|
||||
}
|
||||
}
|
||||
window.requestAnimationFrame(animateMarket);
|
||||
}
|
||||
let marketScrollPosition = 0;
|
||||
marketTrack.addEventListener('scroll', () => {
|
||||
if (Math.abs(marketTrack.scrollLeft - marketScrollPosition) > 2) marketScrollPosition = marketTrack.scrollLeft;
|
||||
}, { passive: true });
|
||||
window.requestAnimationFrame(animateMarket);
|
||||
|
||||
async function refreshInternationalPrices() {
|
||||
try {
|
||||
const response = await fetch(@json(route('dashboard.pasar.international')), {
|
||||
headers: { 'Accept': 'application/json' }, credentials: 'same-origin',
|
||||
signal: AbortSignal.timeout(25000)
|
||||
});
|
||||
if (!response.ok) throw new Error('Harga tidak tersedia');
|
||||
const payload = await response.json();
|
||||
if (!Array.isArray(payload.quotes)) throw new Error('Data tidak valid');
|
||||
const quotes = payload.quotes.filter(quote => typeof quote.price === 'number' && Number.isFinite(quote.price) && quote.price > 0);
|
||||
const signature = JSON.stringify([quotes, payload.exchange_rate]);
|
||||
if (signature !== marketSignature) {
|
||||
const previousScroll = marketTrack.scrollLeft;
|
||||
const fragment = document.createDocumentFragment();
|
||||
quotes.forEach(quote => {
|
||||
const card = document.getElementById('international-market-card').content.cloneNode(true);
|
||||
card.querySelector('[data-name]').textContent = quote.name;
|
||||
card.querySelector('[data-update-info]').dataset.info = quote.update_info || (quote.frequency === 'monthly'
|
||||
? 'IMF menyediakan harga acuan rata-rata bulanan. Data terbaru diperiksa setiap hari. Periode harga tercantum di kartu.'
|
||||
: 'Harga Yahoo Finance diperiksa setiap 5 menit. Data dapat tertunda mengikuti penyedia dan jam perdagangan.');
|
||||
const rate = payload.exchange_rate;
|
||||
let rateInfo = 'Kurs USD/IDR belum tersedia. Harga asli tetap ditampilkan.';
|
||||
if (rate && typeof rate.value === 'number') {
|
||||
rateInfo = '1 USD = ' + rupiahFormat.format(rate.value) + '. Sumber: ' + rate.source + '.';
|
||||
const rateDate = new Date(rate.quoted_at);
|
||||
if (!Number.isNaN(rateDate.getTime())) rateInfo += ' Waktu kurs: ' + quoteDateFormat.format(rateDate) + '.';
|
||||
if (rate.stale) rateInfo += ' Menggunakan kurs tersimpan.';
|
||||
if (quote.frequency === 'monthly') rateInfo += ' Konversi memakai kurs ini, bukan kurs historis bulan harga tersebut.';
|
||||
}
|
||||
card.querySelector('[data-fx-info]').dataset.info = rateInfo;
|
||||
|
||||
|
||||
const providerLogos = {
|
||||
yahoo: @json(asset('assets/img/providers/yahoo-finance.ico')),
|
||||
imf: @json(asset('assets/img/providers/imf.ico')),
|
||||
alpha: @json(asset('assets/img/providers/alpha-vantage.ico'))
|
||||
};
|
||||
const logoUrl = providerLogos[(quote.id || '').split(':')[0]];
|
||||
if (logoUrl) {
|
||||
const logo = card.querySelector('[data-provider-logo]');
|
||||
logo.src = logoUrl;
|
||||
logo.alt = 'Logo ' + quote.source;
|
||||
card.querySelector('[data-provider-logo-wrap]').hidden = false;
|
||||
}
|
||||
|
||||
const source = card.querySelector('[data-source]');
|
||||
source.textContent = quote.source;
|
||||
if (quote.source_url?.startsWith('https://')) source.href = quote.source_url;
|
||||
card.querySelector('[data-price]').textContent = quote.price.toLocaleString('id-ID', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
card.querySelector('[data-unit]').textContent = quote.unit;
|
||||
card.querySelector('[data-rupiah]').textContent = typeof quote.price_idr_per_kg === 'number'
|
||||
? rupiahFormat.format(quote.price_idr_per_kg) + ' / kg' : 'Konversi rupiah belum tersedia';
|
||||
card.querySelector('[data-description]').textContent = quote.description;
|
||||
let period = '-';
|
||||
if (quote.frequency === 'monthly' && /^\d{4}-\d{2}$/.test(quote.period || '')) {
|
||||
period = 'Rata-rata ' + new Intl.DateTimeFormat('id-ID', { month: 'long', year: 'numeric', timeZone: 'UTC' }).format(new Date(quote.period + '-01T00:00:00Z'));
|
||||
} else if (quote.period) {
|
||||
const date = new Date(quote.period);
|
||||
if (!Number.isNaN(date.getTime())) period = 'Waktu harga: ' + quoteDateFormat.format(date);
|
||||
}
|
||||
card.querySelector('[data-period]').textContent = period + (quote.stale ? ' · Data tersimpan' : '');
|
||||
fragment.append(card);
|
||||
});
|
||||
closeMarketInfo();
|
||||
marketTrack.replaceChildren(fragment);
|
||||
if (quotes.length > 1) {
|
||||
// Repeat the cards so the end joins the start without a visible rewind.
|
||||
const originals = [...marketTrack.children];
|
||||
[...originals, ...originals].forEach(original => {
|
||||
const clone = original.cloneNode(true);
|
||||
clone.dataset.marketClone = 'true';
|
||||
marketTrack.append(clone);
|
||||
});
|
||||
}
|
||||
measureMarketCycle();
|
||||
marketScrollPosition = marketCycleWidth ? previousScroll % marketCycleWidth : 0;
|
||||
marketTrack.scrollLeft = marketScrollPosition;
|
||||
marketSignature = signature;
|
||||
}
|
||||
marketStatus.textContent = quotes.length
|
||||
? '' : 'Harga belum tersedia. Pembaruan akan dicoba kembali otomatis.';
|
||||
marketStatus.hidden = quotes.length > 0;
|
||||
[pauseButton, ...marketSection.querySelectorAll('[data-market-prev], [data-market-next]')].forEach(button => button.disabled = quotes.length < 2);
|
||||
} catch (error) {
|
||||
marketStatus.hidden = false;
|
||||
marketStatus.textContent = marketTrack.childElementCount
|
||||
? 'Pembaruan gagal. Menampilkan harga sebelumnya dan akan mencoba kembali.'
|
||||
: 'Harga belum dapat dimuat. Akan dicoba kembali otomatis.';
|
||||
} finally {
|
||||
window.setTimeout(() => {
|
||||
if (!document.hidden) refreshInternationalPrices();
|
||||
else waitForVisible();
|
||||
}, 60000);
|
||||
}
|
||||
}
|
||||
function waitForVisible() {
|
||||
document.addEventListener('visibilitychange', function onVisible() {
|
||||
if (document.hidden) return;
|
||||
document.removeEventListener('visibilitychange', onVisible);
|
||||
refreshInternationalPrices();
|
||||
});
|
||||
}
|
||||
refreshInternationalPrices();
|
||||
const wilayah = @json($wilayah);
|
||||
const marketCharts = @json($marketCharts);
|
||||
const chartDefinitions = @json($chartDefinitions);
|
||||
const priceTrend = @json($priceTrend);
|
||||
const premiumAwareness = @json($premiumAwareness);
|
||||
|
||||
function escapeHtml(value) {
|
||||
const element = document.createElement('span');
|
||||
element.textContent = String(value);
|
||||
return element.innerHTML;
|
||||
}
|
||||
|
||||
function formatNumber(value, digits = 0) {
|
||||
return Number(value).toLocaleString('id-ID', { maximumFractionDigits: digits });
|
||||
}
|
||||
|
||||
const compactLabels = {
|
||||
saluranPenjualan: ['Tengkulak/pedagang lokal', 'Koperasi', 'BUMDes', 'Pembeli langsung/eksportir', 'Lainnya'],
|
||||
waktuPenjualan: ['Langsung setelah panen', 'Ditumpuk beberapa waktu', 'Saat butuh uang mendesak'],
|
||||
aksesSaluranLain: ['Tidak pernah', 'Berhenti, tanpa akses', 'Berhenti, bisa kembali', 'Sesekali masih menjual', 'Akses alternatif aktif'],
|
||||
kesadaranPremium: ['Belum pernah mendengar', 'Belum paham persyaratan', 'Sedikit paham persyaratan', 'Paham, belum mengakses', 'Akses premium konsisten'],
|
||||
};
|
||||
const mobileLabels = {
|
||||
saluranPenjualan: ['Tengkulak/lokal', 'Koperasi', 'BUMDes', 'Pembeli/eksportir', 'Lainnya'],
|
||||
hubunganPembeli: ['Bebas pindah', 'Langganan tetap', 'Berbasis hutang', 'Titip jual', 'Komitmen volume', 'Semi-kontrak'],
|
||||
waktuPenjualan: ['Setelah panen', 'Ditumpuk dulu', 'Uang mendesak'],
|
||||
aksesSaluranLain: ['Tidak pernah', 'Tanpa akses', 'Bisa kembali', 'Sesekali jual', 'Akses aktif'],
|
||||
kesadaranPremium: ['Belum mendengar', 'Belum paham syarat', 'Sedikit paham', 'Paham, belum akses', 'Akses konsisten'],
|
||||
};
|
||||
|
||||
function hasTrendData(commodity) {
|
||||
return commodity && commodity.series.some(function (series) {
|
||||
return series.data.some(function (value) { return value !== null && Number(value) > 0; });
|
||||
});
|
||||
}
|
||||
|
||||
function formatPrice(value) {
|
||||
return value === null || value === undefined ? 'Belum ada data' : 'Rp ' + formatNumber(value) + '/kg';
|
||||
}
|
||||
|
||||
function renderPriceTrend(commodity) {
|
||||
const element = document.getElementById('chart-tren-harga-' + commodity.id);
|
||||
const summary = document.getElementById('summary-tren-harga-' + commodity.id);
|
||||
|
||||
if (!hasTrendData(commodity) || typeof ApexCharts === 'undefined') {
|
||||
element.classList.add('pasar-chart-empty');
|
||||
element.textContent = !hasTrendData(commodity)
|
||||
? 'Belum ada data harga untuk komoditas dan filter yang dipilih.'
|
||||
: 'Chart belum dapat dimuat. Silakan muat ulang halaman.';
|
||||
return;
|
||||
}
|
||||
|
||||
summary.textContent = commodity.name + '. ' + commodity.series.map(function (series) {
|
||||
return series.name + ': ' + series.data.map(function (value, index) {
|
||||
return priceTrend.periods[index] + ' ' + formatPrice(value);
|
||||
}).join(', ');
|
||||
}).join('. ');
|
||||
|
||||
const colors = config.chartColors.series.slice(0, 4);
|
||||
const labelStyle = { colors: config.colors.bodyColor, fontSize: '12px', fontFamily: config.fontFamily };
|
||||
new ApexCharts(element, {
|
||||
chart: { type: 'line', height: 360, fontFamily: config.fontFamily, foreColor: config.colors.bodyColor, toolbar: { show: false }, zoom: { enabled: false } },
|
||||
series: commodity.series,
|
||||
colors: colors,
|
||||
stroke: { width: 3, curve: 'straight' },
|
||||
markers: { size: 5, strokeWidth: 2, hover: { sizeOffset: 2 } },
|
||||
dataLabels: { enabled: false },
|
||||
xaxis: {
|
||||
categories: priceTrend.periods,
|
||||
tickPlacement: 'between',
|
||||
labels: { rotate: 0, style: labelStyle },
|
||||
axisBorder: { color: config.chartColors.grid },
|
||||
axisTicks: { color: config.chartColors.grid },
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
title: { text: 'Harga (Rp/kg)', style: { color: config.colors.bodyColor, fontFamily: config.fontFamily, fontWeight: 400 } },
|
||||
labels: { style: labelStyle, formatter: function (value) { return formatNumber(value); } },
|
||||
},
|
||||
grid: { borderColor: config.chartColors.grid, strokeDashArray: 4, padding: { left: 8, right: 20 } },
|
||||
legend: { position: 'bottom', fontSize: '13px', fontFamily: config.fontFamily, labels: { colors: config.colors.bodyColor }, itemMargin: { horizontal: 12, vertical: 6 } },
|
||||
tooltip: {
|
||||
shared: true,
|
||||
intersect: false,
|
||||
custom: function ({ dataPointIndex }) {
|
||||
const rows = commodity.series.map(function (series, index) {
|
||||
return `<div class="pasar-trend-tooltip-row"><span style="color:${colors[index]}">${escapeHtml(series.name)}</span><strong>${formatPrice(series.data[dataPointIndex])}</strong></div>`;
|
||||
}).join('');
|
||||
return `<div class="pasar-chart-tooltip" style="background:${config.colors.cardColor};color:${config.colors.headingColor}"><strong>${escapeHtml(commodity.name)} — ${escapeHtml(priceTrend.periods[dataPointIndex])}</strong>${rows}</div>`;
|
||||
},
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 576,
|
||||
options: {
|
||||
chart: { height: 420 },
|
||||
legend: { fontSize: '12px', itemMargin: { horizontal: 6, vertical: 4 } },
|
||||
xaxis: {
|
||||
labels: {
|
||||
hideOverlappingLabels: false,
|
||||
style: { fontSize: '11px' },
|
||||
formatter: function (value) {
|
||||
return { 'Dua Tahun Lalu': '2 thn lalu', 'Tahun Lalu': 'Tahun lalu', 'Panen Terakhir': 'Terakhir' }[value] || value;
|
||||
},
|
||||
},
|
||||
},
|
||||
yaxis: {
|
||||
title: { text: undefined },
|
||||
labels: { style: { fontSize: '11px' }, formatter: function (value) { return formatNumber(value); } },
|
||||
},
|
||||
},
|
||||
}],
|
||||
}).render();
|
||||
}
|
||||
|
||||
function renderResponseChart(key, data, definition) {
|
||||
const element = document.getElementById('chart-' + key);
|
||||
if (!data.respondents || typeof ApexCharts === 'undefined') {
|
||||
element.classList.add('pasar-chart-empty');
|
||||
element.textContent = !data.respondents
|
||||
? 'Belum ada data jawaban untuk filter yang dipilih.'
|
||||
: 'Chart belum dapat dimuat. Silakan muat ulang halaman.';
|
||||
return;
|
||||
}
|
||||
|
||||
const color = config.chartColors.semantic[definition.variant];
|
||||
const borderColor = config.chartColors.grid;
|
||||
const labelStyle = { colors: config.colors.bodyColor, fontSize: '12px', fontFamily: config.fontFamily };
|
||||
const maxCount = Math.max(...data.series);
|
||||
const tickCount = Math.min(4, maxCount);
|
||||
const axisMax = Math.ceil(maxCount * 1.25 / tickCount) * tickCount;
|
||||
|
||||
new ApexCharts(element, {
|
||||
chart: { type: 'bar', height: 330, fontFamily: config.fontFamily, foreColor: config.colors.bodyColor, toolbar: { show: false } },
|
||||
series: [{ name: 'Responden', data: data.series }],
|
||||
colors: [color],
|
||||
plotOptions: { bar: { horizontal: true, borderRadius: 5, barHeight: '62%', dataLabels: { position: 'top' } } },
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
textAnchor: 'start',
|
||||
offsetX: 6,
|
||||
style: { fontSize: '12px', fontFamily: config.fontFamily, fontWeight: 500, colors: [config.colors.headingColor] },
|
||||
formatter: function (value, options) {
|
||||
return `${formatNumber(value)} (${formatNumber(data.percentages[options.dataPointIndex], 1)}%)`;
|
||||
},
|
||||
},
|
||||
xaxis: {
|
||||
categories: compactLabels[key] || data.labels,
|
||||
min: 0,
|
||||
max: axisMax,
|
||||
tickAmount: tickCount,
|
||||
labels: { style: labelStyle, formatter: function (value) { return formatNumber(value); } },
|
||||
axisBorder: { color: borderColor },
|
||||
axisTicks: { color: borderColor },
|
||||
},
|
||||
yaxis: { labels: { maxWidth: 185, style: labelStyle } },
|
||||
grid: { borderColor: borderColor, strokeDashArray: 4, padding: { right: 20 } },
|
||||
legend: { show: false },
|
||||
tooltip: {
|
||||
theme: document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'dark' : 'light',
|
||||
custom: function ({ dataPointIndex }) {
|
||||
return `<div class="pasar-chart-tooltip" style="background:${color};color:${config.colors.headingColor};font-family:${config.fontFamily}">${escapeHtml(data.fullLabels[dataPointIndex])}: <strong>${formatNumber(data.series[dataPointIndex])} responden (${formatNumber(data.percentages[dataPointIndex], 1)}%)</strong></div>`;
|
||||
},
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 576,
|
||||
options: {
|
||||
xaxis: { categories: mobileLabels[key], tickAmount: 2 },
|
||||
yaxis: { labels: { maxWidth: 125, style: { fontSize: '11px' } } },
|
||||
grid: { padding: { right: 10 } },
|
||||
dataLabels: { style: { fontSize: '11px' }, formatter: function (value) { return formatNumber(value); } },
|
||||
},
|
||||
}],
|
||||
}).render();
|
||||
}
|
||||
|
||||
function renderMarketCharts() {
|
||||
Object.entries(marketCharts).forEach(function ([key, data]) {
|
||||
renderResponseChart(key, data, chartDefinitions[key]);
|
||||
});
|
||||
priceTrend.commodities.forEach(renderPriceTrend);
|
||||
}
|
||||
|
||||
function setupChartPanel(panelId, renderCharts) {
|
||||
const chartPanel = document.getElementById(panelId);
|
||||
const chartControl = document.querySelector('.chart-group-header-toggle[aria-controls="' + chartPanel.id + '"]');
|
||||
const chartIcon = chartControl.querySelector('.chart-group-icon-toggle i');
|
||||
let rendered = false;
|
||||
|
||||
function syncChartPanelState() {
|
||||
const expanded = chartPanel.classList.contains('show');
|
||||
const label = expanded ? 'Sembunyikan chart' : 'Tampilkan chart';
|
||||
chartControl.setAttribute('aria-expanded', String(expanded));
|
||||
chartControl.setAttribute('aria-label', label);
|
||||
chartControl.title = label;
|
||||
chartIcon.classList.toggle('tabler-chevron-up', expanded);
|
||||
chartIcon.classList.toggle('tabler-chevron-down', !expanded);
|
||||
}
|
||||
|
||||
chartControl.addEventListener('keydown', function (event) {
|
||||
if (event.target !== chartControl || !['Enter', ' '].includes(event.key)) return;
|
||||
event.preventDefault();
|
||||
chartControl.click();
|
||||
});
|
||||
|
||||
chartPanel.addEventListener('shown.bs.collapse', function (event) {
|
||||
if (event.target !== chartPanel) return;
|
||||
syncChartPanelState();
|
||||
if (!rendered) {
|
||||
renderCharts();
|
||||
rendered = true;
|
||||
}
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
});
|
||||
|
||||
chartPanel.addEventListener('hidden.bs.collapse', function (event) {
|
||||
if (event.target === chartPanel) syncChartPanelState();
|
||||
});
|
||||
|
||||
syncChartPanelState();
|
||||
}
|
||||
|
||||
setupChartPanel('saluran-harga-charts', renderMarketCharts);
|
||||
setupChartPanel('kesiapan-premium-charts', function () {
|
||||
renderResponseChart('kesadaranPremium', premiumAwareness, { variant: 'primary' });
|
||||
});
|
||||
|
||||
function fillSelect($select, items, placeholder) {
|
||||
$select.empty().append(new Option(placeholder, ''));
|
||||
items.forEach(function (item) {
|
||||
$select.append(new Option(item.nama, item.id));
|
||||
});
|
||||
}
|
||||
|
||||
function selectedProvinsi() {
|
||||
const id = $('#filter-provinsi').val();
|
||||
return wilayah.find(function (item) {
|
||||
return String(item.id) === String(id);
|
||||
});
|
||||
}
|
||||
|
||||
function selectedKabupaten() {
|
||||
const provinsi = selectedProvinsi();
|
||||
const id = $('#filter-kabkota').val();
|
||||
return (provinsi?.kabupaten || []).find(function (item) {
|
||||
return String(item.id) === String(id);
|
||||
});
|
||||
}
|
||||
|
||||
function selectedKecamatan() {
|
||||
const kabupaten = selectedKabupaten();
|
||||
const id = $('#filter-kecamatan').val();
|
||||
return (kabupaten?.kecamatan || []).find(function (item) {
|
||||
return String(item.id) === String(id);
|
||||
});
|
||||
}
|
||||
|
||||
$('#filter-provinsi, #filter-kabkota, #filter-kecamatan, #filter-desa, #filter-komoditas, #filter-segmen').select2({ width: '100%' });
|
||||
|
||||
$('#filter-provinsi').on('change', function () {
|
||||
const provinsi = selectedProvinsi();
|
||||
fillSelect($('#filter-kabkota'), provinsi?.kabupaten || [], 'Semua Kabupaten/Kota');
|
||||
fillSelect($('#filter-kecamatan'), [], 'Semua Kecamatan');
|
||||
fillSelect($('#filter-desa'), [], 'Semua Desa/Kelurahan');
|
||||
$('#filter-kabkota').prop('disabled', !provinsi).trigger('change.select2');
|
||||
$('#filter-kecamatan, #filter-desa').prop('disabled', true).trigger('change.select2');
|
||||
});
|
||||
|
||||
$('#filter-kabkota').on('change', function () {
|
||||
const kabupaten = selectedKabupaten();
|
||||
fillSelect($('#filter-kecamatan'), kabupaten?.kecamatan || [], 'Semua Kecamatan');
|
||||
fillSelect($('#filter-desa'), [], 'Semua Desa/Kelurahan');
|
||||
$('#filter-kecamatan').prop('disabled', !kabupaten).trigger('change.select2');
|
||||
$('#filter-desa').prop('disabled', true).trigger('change.select2');
|
||||
});
|
||||
|
||||
$('#filter-kecamatan').on('change', function () {
|
||||
const kecamatan = selectedKecamatan();
|
||||
fillSelect($('#filter-desa'), kecamatan?.desa || [], 'Semua Desa/Kelurahan');
|
||||
$('#filter-desa').prop('disabled', !kecamatan).trigger('change.select2');
|
||||
});
|
||||
|
||||
const defaultProvinsi = String($('#filter-provinsi').data('default') || '');
|
||||
const defaultKabupaten = String($('#filter-kabkota').data('default') || '');
|
||||
const defaultKecamatan = String($('#filter-kecamatan').data('default') || '');
|
||||
const defaultDesa = String($('#filter-desa').data('default') || '');
|
||||
|
||||
if (defaultProvinsi) {
|
||||
$('#filter-provinsi').val(defaultProvinsi).trigger('change');
|
||||
if (defaultKabupaten) {
|
||||
$('#filter-kabkota').val(defaultKabupaten).trigger('change');
|
||||
if (defaultKecamatan) {
|
||||
$('#filter-kecamatan').val(defaultKecamatan).trigger('change');
|
||||
if (defaultDesa) {
|
||||
$('#filter-desa').val(defaultDesa).trigger('change.select2');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$('#btn-reset-dashboard-filter').on('click', function () {
|
||||
window.location.href = @json(route('dashboard.pasar'));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@@ -493,6 +493,7 @@ $segments = [
|
||||
legendColor = config.colors.bodyColor;
|
||||
borderColor = config.colors.borderColor;
|
||||
headingColor = config.colors.headingColor;
|
||||
const pastelSemantic = config.chartColors?.semantic || {};
|
||||
|
||||
var radarDimensi = new ApexCharts($("#chart-dimensi")[0],
|
||||
options = {
|
||||
@@ -542,7 +543,7 @@ $segments = [
|
||||
highlightDataSeries: false
|
||||
}
|
||||
},
|
||||
colors: [config.colors.primary, config.colors.info],
|
||||
colors: [pastelSemantic.primary || '#9D8EF0', pastelSemantic.info || '#5FC7D6'],
|
||||
fill: {
|
||||
opacity: 0.85
|
||||
},
|
||||
@@ -640,7 +641,7 @@ $segments = [
|
||||
highlightDataSeries: false
|
||||
}
|
||||
},
|
||||
colors: [config.colors.primary, config.colors.info],
|
||||
colors: [pastelSemantic.primary || '#9D8EF0', pastelSemantic.info || '#5FC7D6'],
|
||||
fill: {
|
||||
opacity: 0.85
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -144,10 +144,10 @@
|
||||
<div class="absolute bottom-3 left-3 bg-white/95 p-3 rounded-lg text-xs border border-gray-200 shadow-sm backdrop-blur-sm">
|
||||
<div class="font-bold text-gray-700 mb-2">Legenda Tanaman:</div>
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-2">
|
||||
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#6F4E37] rounded-full shadow-sm"></div> Kopi</div>
|
||||
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#eab308] rounded-full shadow-sm"></div> Vanili</div>
|
||||
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#D2691E] rounded-full shadow-sm"></div> Kakao</div>
|
||||
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#9ca3af] rounded-full shadow-sm"></div> Kemiri</div>
|
||||
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#6FD68F] rounded-full shadow-sm"></div> Kopi</div>
|
||||
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#FFB46F] rounded-full shadow-sm"></div> Vanili</div>
|
||||
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#9D8EF0] rounded-full shadow-sm"></div> Kakao</div>
|
||||
<div class="flex items-center gap-2"><div class="w-3 h-3 bg-[#9EA6B0] rounded-full shadow-sm"></div> Kemiri</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -253,6 +253,9 @@
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const pastelChart = config.chartColors || {};
|
||||
const pastelCategory = pastelChart.categories || {};
|
||||
const gridColor = pastelChart.grid || '#E8EDF5';
|
||||
|
||||
// 1. Inisialisasi Icons
|
||||
if(typeof lucide !== 'undefined') {
|
||||
@@ -263,19 +266,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const spatialData = [
|
||||
{
|
||||
id: 'L-001', owner: 'Yosef Neta', familySize: 5, area: '1.2 Ha', village: 'Radabata',
|
||||
polygon: "15,25 75,15 90,85 20,70", fill: "rgba(34, 197, 94, 0.15)", stroke: "#16a34a",
|
||||
polygon: "15,25 75,15 90,85 20,70", fill: "rgba(111, 214, 143, 0.24)", stroke: "#6FD68F",
|
||||
plants: [
|
||||
{ id: 'P-01', type: 'Kopi', cx: 35, cy: 35, planted: 'Mei 2021', status: 'Produktif', intervensi: 'Pemupukan Organik (Okt 2023)', panen: '45 kg/musim', color: '#6F4E37' },
|
||||
{ id: 'P-02', type: 'Vanili', cx: 65, cy: 45, planted: 'Jan 2022', status: 'Berbunga', intervensi: 'Pelatihan Polinasi (Feb 2024)', panen: 'Belum panen', color: '#eab308' },
|
||||
{ id: 'P-03', type: 'Kopi', cx: 50, cy: 65, planted: 'Mei 2021', status: 'Produktif', intervensi: 'Pemangkasan (Nov 2023)', panen: '40 kg/musim', color: '#6F4E37' },
|
||||
{ id: 'P-01', type: 'Kopi', cx: 35, cy: 35, planted: 'Mei 2021', status: 'Produktif', intervensi: 'Pemupukan Organik (Okt 2023)', panen: '45 kg/musim', color: pastelCategory.coffee || '#6FD68F' },
|
||||
{ id: 'P-02', type: 'Vanili', cx: 65, cy: 45, planted: 'Jan 2022', status: 'Berbunga', intervensi: 'Pelatihan Polinasi (Feb 2024)', panen: 'Belum panen', color: pastelCategory.vanilla || '#FFB46F' },
|
||||
{ id: 'P-03', type: 'Kopi', cx: 50, cy: 65, planted: 'Mei 2021', status: 'Produktif', intervensi: 'Pemangkasan (Nov 2023)', panen: '40 kg/musim', color: pastelCategory.coffee || '#6FD68F' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'L-002', owner: 'Maria Goreti', familySize: 3, area: '0.8 Ha', village: 'Ubedolumolo',
|
||||
polygon: "110,40 170,15 190,65 125,85", fill: "rgba(59, 130, 246, 0.15)", stroke: "#2563eb",
|
||||
polygon: "110,40 170,15 190,65 125,85", fill: "rgba(95, 199, 214, 0.20)", stroke: "#5FC7D6",
|
||||
plants: [
|
||||
{ id: 'P-04', type: 'Kemiri', cx: 140, cy: 45, planted: 'Agu 2018', status: 'Produktif', intervensi: 'Pembersihan Gulma', panen: '120 kg/musim', color: '#9ca3af' },
|
||||
{ id: 'P-05', type: 'Kakao', cx: 165, cy: 55, planted: 'Okt 2020', status: 'Terserang Hama', intervensi: 'Penyemprotan Bio-pestisida (Jan 2024)', panen: '20 kg/musim', color: '#D2691E' },
|
||||
{ id: 'P-04', type: 'Kemiri', cx: 140, cy: 45, planted: 'Agu 2018', status: 'Produktif', intervensi: 'Pembersihan Gulma', panen: '120 kg/musim', color: pastelCategory.candlenut || '#9EA6B0' },
|
||||
{ id: 'P-05', type: 'Kakao', cx: 165, cy: 55, planted: 'Okt 2020', status: 'Terserang Hama', intervensi: 'Penyemprotan Bio-pestisida (Jan 2024)', panen: '20 kg/musim', color: pastelCategory.cacao || '#9D8EF0' },
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -440,24 +443,24 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
{
|
||||
label: 'Kopi',
|
||||
data: [0.5, 0.7, 1.1, 1.5],
|
||||
borderColor: '#6F4E37',
|
||||
backgroundColor: '#6F4E37',
|
||||
borderColor: pastelCategory.coffee || '#6FD68F',
|
||||
backgroundColor: pastelCategory.coffee || '#6FD68F',
|
||||
tension: 0.3,
|
||||
borderWidth: 2
|
||||
},
|
||||
{
|
||||
label: 'Vanili',
|
||||
data: [0.1, 0.15, 0.3, 0.5],
|
||||
borderColor: '#eab308',
|
||||
backgroundColor: '#eab308',
|
||||
borderColor: pastelCategory.vanilla || '#FFB46F',
|
||||
backgroundColor: pastelCategory.vanilla || '#FFB46F',
|
||||
tension: 0.3,
|
||||
borderWidth: 2
|
||||
},
|
||||
{
|
||||
label: 'Kakao',
|
||||
data: [0.4, 0.5, 0.8, 1.2],
|
||||
borderColor: '#D2691E',
|
||||
backgroundColor: '#D2691E',
|
||||
borderColor: pastelCategory.cacao || '#9D8EF0',
|
||||
backgroundColor: pastelCategory.cacao || '#9D8EF0',
|
||||
tension: 0.3,
|
||||
borderWidth: 2
|
||||
}
|
||||
@@ -467,7 +470,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
responsive: true, maintainAspectRatio: false,
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
scales: {
|
||||
y: { title: { display: true, text: 'Ton per Hektar (Ton/Ha)' }, grid: { color: '#f3f4f6' } },
|
||||
y: { title: { display: true, text: 'Ton per Hektar (Ton/Ha)' }, grid: { color: gridColor } },
|
||||
x: { grid: { display: false } }
|
||||
},
|
||||
plugins: {
|
||||
@@ -489,11 +492,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
label: 'Jumlah Bibit Didistribusikan',
|
||||
data: [15000, 5200, 8000, 7000, 10000],
|
||||
backgroundColor: [
|
||||
'rgba(111, 78, 55, 0.8)', // Kopi
|
||||
'rgba(234, 179, 8, 0.8)', // Vanili
|
||||
'rgba(210, 105, 30, 0.8)', // Kakao
|
||||
'rgba(156, 163, 175, 0.8)', // Kemiri
|
||||
'rgba(34, 197, 94, 0.8)' // Pelindung (Hijau)
|
||||
'rgba(111, 214, 143, 0.86)', // Kopi
|
||||
'rgba(255, 180, 111, 0.86)', // Vanili
|
||||
'rgba(157, 142, 240, 0.86)', // Kakao
|
||||
'rgba(179, 181, 188, 0.86)', // Kemiri
|
||||
'rgba(101, 216, 189, 0.86)' // Pelindung
|
||||
],
|
||||
borderRadius: 4
|
||||
}
|
||||
@@ -502,7 +505,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
options: {
|
||||
responsive: true, maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: { beginAtZero: true, grid: { color: '#f3f4f6' } },
|
||||
y: { beginAtZero: true, grid: { color: gridColor } },
|
||||
x: { grid: { display: false } }
|
||||
},
|
||||
plugins: {
|
||||
|
||||
@@ -219,6 +219,10 @@
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const pastelChart = config.chartColors || {};
|
||||
const pastelSemantic = pastelChart.semantic || {};
|
||||
const pastelCategory = pastelChart.categories || {};
|
||||
const gridColor = pastelChart.grid || '#E8EDF5';
|
||||
|
||||
// Inisialisasi Icons (Khusus Mockup, Vuexy mungkin pakai library icon sendiri seperti Tabler/FontAwesome)
|
||||
if(typeof lucide !== 'undefined') {
|
||||
@@ -236,7 +240,7 @@
|
||||
{
|
||||
label: 'Baseline (Tanpa Intervensi)',
|
||||
data: [12, 12.5, 13, 13.5],
|
||||
borderColor: '#9ca3af',
|
||||
borderColor: pastelSemantic.gray || '#9EA6B0',
|
||||
backgroundColor: 'transparent',
|
||||
borderDash: [5, 5],
|
||||
fill: false,
|
||||
@@ -245,11 +249,11 @@
|
||||
{
|
||||
label: 'Pendapatan Aktual',
|
||||
data: [12, 15, 22, 31],
|
||||
borderColor: '#10b981',
|
||||
backgroundColor: 'rgba(16, 185, 129, 0.15)', // Light green fill
|
||||
borderColor: pastelCategory.marketGreen || '#6FD68F',
|
||||
backgroundColor: 'rgba(111, 214, 143, 0.20)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointBackgroundColor: '#10b981',
|
||||
pointBackgroundColor: pastelCategory.marketGreen || '#6FD68F',
|
||||
pointBorderWidth: 2,
|
||||
pointRadius: 4
|
||||
}
|
||||
@@ -265,7 +269,7 @@
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
grid: { color: '#f3f4f6' },
|
||||
grid: { color: gridColor },
|
||||
ticks: { callback: function(val) { return 'Rp ' + val + 'M'; } }
|
||||
},
|
||||
x: {
|
||||
@@ -296,7 +300,12 @@
|
||||
labels: ['Kopi', 'Vanili', 'Kakao', 'Kemiri'],
|
||||
datasets: [{
|
||||
data: [45, 25, 15, 15],
|
||||
backgroundColor: ['#6F4E37', '#eab308', '#D2691E', '#9ca3af'],
|
||||
backgroundColor: [
|
||||
pastelCategory.coffee || '#6FD68F',
|
||||
pastelCategory.vanilla || '#FFB46F',
|
||||
pastelCategory.cacao || '#9D8EF0',
|
||||
pastelCategory.candlenut || '#9EA6B0'
|
||||
],
|
||||
borderWidth: 2,
|
||||
borderColor: '#ffffff',
|
||||
hoverOffset: 4
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
box-shadow: 0 .25rem 1rem rgba(34, 48, 62, .08);
|
||||
}
|
||||
|
||||
.tk-stat.green { border-left-color: #28a745; }
|
||||
.tk-stat.blue { border-left-color: #007bff; }
|
||||
.tk-stat.amber { border-left-color: #fd7e14; }
|
||||
.tk-stat.green { border-left-color: #6FD68F; }
|
||||
.tk-stat.blue { border-left-color: #5FC7D6; }
|
||||
.tk-stat.amber { border-left-color: #FFB46F; }
|
||||
|
||||
.tk-stat-lbl {
|
||||
font-size: 10px;
|
||||
@@ -444,10 +444,10 @@
|
||||
@forelse ($progressPetani as $petani)
|
||||
@php
|
||||
$barColor = match (true) {
|
||||
$petani['percent'] === 100 => '#28a745',
|
||||
$petani['percent'] >= 50 => '#007bff',
|
||||
$petani['percent'] >= 20 => '#fd7e14',
|
||||
default => '#dc3545',
|
||||
$petani['percent'] === 100 => '#6FD68F',
|
||||
$petani['percent'] >= 50 => '#5FC7D6',
|
||||
$petani['percent'] >= 20 => '#FFB46F',
|
||||
default => '#F47C86',
|
||||
};
|
||||
|
||||
$initial = collect(explode(' ', trim($petani['name'])))
|
||||
@@ -465,8 +465,8 @@
|
||||
style="
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: rgba(40,167,69,.15);
|
||||
color: #28a745;
|
||||
background: rgba(111,214,143,.24);
|
||||
color: #6FD68F;
|
||||
"
|
||||
>
|
||||
{{ $initial ?: 'PT' }}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<script>
|
||||
document.write(new Date().getFullYear());
|
||||
</script>
|
||||
, by <a href="#" target="_blank" class="footer-link">Yayasan Bhumi Loka Lestari</a>
|
||||
, by <a href="#" target="_blank" class="footer-link">PT Republik Solusi</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<link rel="stylesheet" href="{{ asset('assets/vendor/libs/pickr/pickr-themes.css') }}" />
|
||||
|
||||
<link rel="stylesheet" href="{{ asset('assets/vendor/css/core.css') }}" />
|
||||
<link rel="stylesheet" href="{{ asset('assets/css/demo.css') }}" />
|
||||
<link rel="stylesheet" href="{{ asset('assets/css/demo.css') }}?v={{ filemtime(public_path('assets/css/demo.css')) }}" />
|
||||
|
||||
<link rel="stylesheet" href="{{ asset('assets/vendor/libs/perfect-scrollbar/perfect-scrollbar.css') }}" />
|
||||
|
||||
@@ -90,6 +90,6 @@
|
||||
|
||||
<script src="{{ asset('assets/vendor/js/template-customizer.js') }}"></script>
|
||||
|
||||
<script src="{{ asset('assets/js/config.js') }}"></script>
|
||||
<script src="{{ asset('assets/js/config.js') }}?v={{ filemtime(public_path('assets/js/config.js')) }}"></script>
|
||||
@stack('scripts-head')
|
||||
</head>
|
||||
@@ -19,73 +19,69 @@
|
||||
<div class="menu-inner-shadow"></div>
|
||||
|
||||
<ul class="menu-inner py-1">
|
||||
<!-- Dashboards -->
|
||||
|
||||
@php
|
||||
$userRole = auth()->check() ? (auth()->user()->role instanceof \App\Enums\Role ? auth()->user()->role->value : auth()->user()->role) : null;
|
||||
$user = auth()->user();
|
||||
$userRole = $user?->role instanceof \App\Enums\Role ? $user->role->value : $user?->role;
|
||||
$appName = env('APP_NAME');
|
||||
$isAdmin = in_array($userRole, ['super_admin', 'admin'], true);
|
||||
$isSuperAdmin = $userRole === 'super_admin';
|
||||
$canViewDashboard = $isAdmin || $userRole === 'fasilitator';
|
||||
@endphp
|
||||
|
||||
@if(in_array($userRole, ['super_admin', 'admin', 'fasilitator']) )
|
||||
<li class="menu-item active open">
|
||||
@if($canViewDashboard)
|
||||
<li class="menu-item {{ request()->routeIs('dashboard.*') ? 'active open' : '' }}">
|
||||
<a href="javascript:void(0);" class="menu-link menu-toggle">
|
||||
<i class="menu-icon icon-base ti tabler-smart-home"></i>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-dashboard"></i></span>
|
||||
<div data-i18n="Dashboard">Dashboard</div>
|
||||
</a>
|
||||
|
||||
@if(in_array($userRole, ['super_admin', 'admin']) && env('APP_NAME') == 'AgroMonev')
|
||||
@if($isAdmin && $appName === 'AgroMonev')
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item {{ request()->routeIs(['dashboard.karakteristik-petani', 'dashboard.demografi-petani', 'dashboard.indeks-petani']) ? 'active' : '' }}">
|
||||
<a href="{{ route('dashboard.karakteristik-petani') }}" class="menu-link">
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-user-circle"></i></span><div data-i18n="Karakteristik Petani">Karakteristik Petani</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item {{ request()->routeIs('dashboard.produktivitas') ? 'active' : '' }}">
|
||||
<a href="{{ route('dashboard.produktivitas') }}" class="menu-link">
|
||||
<div data-i18n="Produktivitas">Produktivitas</div>
|
||||
</a>
|
||||
</li>
|
||||
</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>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-chart-bar"></i></span><div data-i18n="Produksi">Produksi</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>
|
||||
<li class="menu-item {{ request()->routeIs('dashboard.pasar') ? 'active' : '' }}">
|
||||
<a href="{{ route('dashboard.pasar') }}" class="menu-link">
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-building-store"></i></span><div data-i18n="Pasar">Pasar</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>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-chart-line"></i></span><div data-i18n="Indikator Petani">Indikator Petani</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@endif
|
||||
|
||||
@if(in_array($userRole, ['super_admin', 'admin', 'fasilitator']) && env('APP_NAME') == 'AgroMonev')
|
||||
@if($canViewDashboard && $appName === 'AgroMonev')
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item {{ request()->routeIs('dashboard.survey-petani') ? 'active' : '' }}">
|
||||
<a href="{{ route('dashboard.survey-petani') }}" class="menu-link">
|
||||
<div data-i18n="Survey Petani">Survey Petani</div>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-clipboard-list"></i></span><div data-i18n="Survei Petani">Survei Petani</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- <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')
|
||||
@if($isAdmin && $appName === '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>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-building-community"></i></span><div data-i18n="Profil Pengurus BUMDes">Profil Pengurus BUMDes</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -93,210 +89,201 @@
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if(in_array($userRole, ['fasilitator', 'super_admin', 'admin']) && env('APP_NAME') == 'AgroMonev')
|
||||
<li class="menu-header small">
|
||||
<span class="menu-header-text" data-i18n="Data Pertanian">Data Pertanian</span>
|
||||
</li>
|
||||
@if($canViewDashboard && $appName === 'AgroMonev')
|
||||
<li class="menu-item {{ request()->routeIs(['petani.*', 'kelompok-tani.*', 'bumdes.*', 'koperasi.*']) ? 'active open' : '' }}">
|
||||
<a href="javascript:void(0);" class="menu-link menu-toggle">
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-id-badge-2"></i></span>
|
||||
<div data-i18n="Profil">Profil</div>
|
||||
</a>
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item {{ request()->routeIs('petani.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('petani.index') }}" class="menu-link">
|
||||
<i class="menu-icon icon-base ti tabler-file-description"></i>
|
||||
<div data-i18n="Profil Petani">Profil Petani</div>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-user-circle"></i></span>
|
||||
<div data-i18n="Petani">Petani</div>
|
||||
</a>
|
||||
</li>
|
||||
@if(in_array($userRole, ['super_admin', 'admin']) && env('APP_NAME') == 'AgroMonev')
|
||||
@if($isAdmin && $appName === 'AgroMonev')
|
||||
<li class="menu-item {{ request()->routeIs('kelompok-tani.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('kelompok-tani.index') }}" class="menu-link">
|
||||
<i class="menu-icon icon-base ti tabler-file-description"></i>
|
||||
<div data-i18n="Profil Kelompok Tani">Profil Kelompok Tani</div>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-users-group"></i></span>
|
||||
<div data-i18n="Kelompok Tani">Kelompok Tani</div>
|
||||
</a>
|
||||
</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 BUMDes">Profil BUMDes</div>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-building-community"></i></span>
|
||||
<div data-i18n="BUMDes">BUMDes</div>
|
||||
</a>
|
||||
</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>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-heart-handshake"></i></span>
|
||||
<div data-i18n="Pengurus Koperasi">Pengurus Koperasi</div>
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if(in_array($userRole, ['super_admin', 'admin']) && env('APP_NAME') == 'AgroPro')
|
||||
<!-- Ekosistem -->
|
||||
<li class="menu-header small">
|
||||
<span class="menu-header-text" data-i18n="Ekosistem">Ekosistem</span>
|
||||
</li>
|
||||
<li class="menu-item {{ request()->routeIs(['profile.*', 'produksi.*', 'lahan.*', 'tanaman.*']) ? 'active open' : '' }}">
|
||||
@if($isAdmin && $appName === 'AgroPro')
|
||||
|
||||
<li class="menu-item {{ request()->routeIs(['petani.*', 'profile.*', 'produksi.*', 'lahan.*', 'tanaman.*', 'petani-cpm.*', 'lahan-cpcl.*', 'tabel.data.*', 'realisasi.*', 'kelompok-belajar-tani.*', 'workshop.*']) ? 'active open' : '' }}">
|
||||
<a href="javascript:void(0);" class="menu-link menu-toggle">
|
||||
<i class="menu-icon icon-base ti tabler-file-description"></i>
|
||||
<div data-i18n="Data Pertanian">Data Pertanian</div>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-layers-linked"></i></span>
|
||||
<div data-i18n="Ekosistem">Ekosistem</div>
|
||||
</a>
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item {{ request()->routeIs(['petani.*', 'profile.*', 'produksi.*', 'lahan.*', 'tanaman.*']) ? 'active open' : '' }}">
|
||||
<a href="javascript:void(0);" class="menu-link menu-toggle">
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-id-badge-2"></i></span>
|
||||
<div data-i18n="Profil">Profil</div>
|
||||
</a>
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item {{ request()->routeIs('petani.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('petani.index') }}" class="menu-link"><div data-i18n="Profil Petani">Profil Petani</div></a>
|
||||
<a href="{{ route('petani.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-user-circle"></i></span><div data-i18n="Petani">Petani</div></a>
|
||||
</li>
|
||||
<li class="menu-item {{ request()->routeIs('produksi.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('produksi.index') }}" class="menu-link"><div data-i18n="Hasil Panen">Hasil Panen</div></a>
|
||||
<a href="{{ route('produksi.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-wheat"></i></span><div data-i18n="Hasil Panen">Hasil Panen</div></a>
|
||||
</li>
|
||||
<li class="menu-item {{ request()->routeIs('lahan.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('lahan.index') }}" class="menu-link"><div data-i18n="Pemantauan Lahan">Pemantauan Lahan</div></a>
|
||||
<a href="{{ route('lahan.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-map"></i></span><div data-i18n="Pemantauan Lahan">Pemantauan Lahan</div></a>
|
||||
</li>
|
||||
<li class="menu-item {{ request()->routeIs('tanaman.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('tanaman.index') }}" class="menu-link"><div data-i18n="Pemantauan Tanaman">Pemantauan Tanaman</div></a>
|
||||
<a href="{{ route('tanaman.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-plant"></i></span><div data-i18n="Pemantauan Tanaman">Pemantauan Tanaman</div></a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="menu-item {{ request()->routeIs(['petani-cpm.*', 'lahan-cpcl.*', 'tabel.data.*']) ? 'active open' : '' }}">
|
||||
<a href="javascript:void(0);" class="menu-link menu-toggle">
|
||||
<i class="menu-icon icon-base ti tabler-folder-root"></i>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-database"></i></span>
|
||||
<div data-i18n="Basis Data Ekosistem">Basis Data Ekosistem</div>
|
||||
</a>
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item {{ request()->routeIs('petani-cpm.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('petani-cpm.index') }}" class="menu-link"><div data-i18n="Petani CPM">Petani CPM</div></a>
|
||||
<a href="{{ route('petani-cpm.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-users-group"></i></span><div data-i18n="Petani CPM">Petani CPM</div></a>
|
||||
</li>
|
||||
<li class="menu-item {{ request()->routeIs('lahan-cpcl.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('lahan-cpcl.index') }}" class="menu-link"><div data-i18n="Lahan CPCL">Lahan CPCL</div></a>
|
||||
<a href="{{ route('lahan-cpcl.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-map-2"></i></span><div data-i18n="Lahan CPCL">Lahan CPCL</div></a>
|
||||
</li>
|
||||
</ul>
|
||||
@foreach ($tabel_menu ?? [] as $tbl)
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item {{ request()->routeIs('tabel.data.*') && request()->route('tabel') == $tbl->id ? 'active' : '' }}">
|
||||
<a href="{{ route('tabel.data.index', $tbl->id) }}" class="menu-link">
|
||||
<div data-i18n="{{ $tbl->menu }}">{{ $tbl->menu }}</div>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-table"></i></span><div data-i18n="{{ $tbl->menu }}">{{ $tbl->menu }}</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@endforeach
|
||||
</li>
|
||||
<!-- <li class="menu-item">
|
||||
<a href="javascript:void(0);" class="menu-link menu-toggle">
|
||||
<i class="menu-icon icon-base ti tabler-line-scan"></i>
|
||||
<div data-i18n="Tracebility">Tracebility</div>
|
||||
</a>
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item">
|
||||
<a href="#" class="menu-link">
|
||||
<div data-i18n="Manajemen Batch & Kode">Manajemen Batch & Kode</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item">
|
||||
<a href="#" class="menu-link">
|
||||
<div data-i18n="Penelusuran Product">Penelusuran Product</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li> -->
|
||||
|
||||
<li class="menu-item {{ request()->routeIs(['realisasi.*', 'kelompok-belajar-tani.*', 'workshop.*']) ? 'active open' : '' }}">
|
||||
<a href="javascript:void(0);" class="menu-link menu-toggle">
|
||||
<i class="menu-icon icon-base ti tabler-webhook"></i>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-users-group"></i></span>
|
||||
<div data-i18n="Program & Komunitas">Program & Komunitas</div>
|
||||
</a>
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item {{ request()->routeIs('realisasi.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('realisasi.index') }}" class="menu-link"><div data-i18n="Capaian Program">Capaian Program</div></a>
|
||||
<a href="{{ route('realisasi.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-target"></i></span><div data-i18n="Capaian Program">Capaian Program</div></a>
|
||||
</li>
|
||||
<li class="menu-item {{ request()->routeIs('kelompok-belajar-tani.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('kelompok-belajar-tani.index') }}" class="menu-link"><div data-i18n="Kelompok Belajar Petani">Kelompok Belajar Petani</div></a>
|
||||
<a href="{{ route('kelompok-belajar-tani.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-school"></i></span><div data-i18n="Kelompok Belajar Petani">Kelompok Belajar Petani</div></a>
|
||||
</li>
|
||||
<li class="menu-item {{ request()->routeIs('workshop.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('workshop.index') }}" class="menu-link"><div data-i18n="Workshop & Lokakarya">Workshop & Lokakarya</div></a>
|
||||
<a href="{{ route('workshop.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-presentation"></i></span><div data-i18n="Workshop & Lokakarya">Workshop & Lokakarya</div></a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if(in_array($userRole, ['super_admin']))
|
||||
<!-- Master Data -->
|
||||
<li class="menu-header small">
|
||||
<span class="menu-header-text" data-i18n="Master Data">Master Data</span>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
@if($isSuperAdmin)
|
||||
|
||||
<li class="menu-item {{ request()->routeIs(['provinsi.*', 'kabupatenkota.*', 'kecamatan.*', 'desakelurahan.*', 'adat.*', 'keluarga.*', 'komoditas.*', 'jenis-komoditas.*', 'varietas.*']) ? 'active open' : '' }}">
|
||||
<a href="javascript:void(0);" class="menu-link menu-toggle">
|
||||
<i class="menu-icon icon-base ti tabler-world"></i>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-database"></i></span>
|
||||
<div data-i18n="Master Data">Master Data</div>
|
||||
</a>
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item {{ request()->routeIs(['provinsi.*', 'kabupatenkota.*', 'kecamatan.*', 'desakelurahan.*']) ? 'active open' : '' }}">
|
||||
<a href="javascript:void(0);" class="menu-link menu-toggle">
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-map-2"></i></span>
|
||||
<div data-i18n="Wilayah">Wilayah</div>
|
||||
</a>
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item"><a href="{{ route('provinsi.index') }}" class="menu-link"><div data-i18n="Provinsi">Provinsi</div></a></li>
|
||||
<li class="menu-item"><a href="{{ route('kabupatenkota.index') }}" class="menu-link"><div data-i18n="Kabupaten Kota">Kabupaten Kota</div></a></li>
|
||||
<li class="menu-item"><a href="{{ route('kecamatan.index') }}" class="menu-link"><div data-i18n="Kecamatan">Kecamatan</div></a></li>
|
||||
<li class="menu-item"><a href="{{ route('desakelurahan.index') }}" class="menu-link"><div data-i18n="Desa Kelurahan">Desa Kelurahan</div></a></li>
|
||||
<li class="menu-item"><a href="{{ route('provinsi.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-map"></i></span><div data-i18n="Provinsi">Provinsi</div></a></li>
|
||||
<li class="menu-item"><a href="{{ route('kabupatenkota.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-building"></i></span><div data-i18n="Kabupaten Kota">Kabupaten Kota</div></a></li>
|
||||
<li class="menu-item"><a href="{{ route('kecamatan.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-map-pin"></i></span><div data-i18n="Kecamatan">Kecamatan</div></a></li>
|
||||
<li class="menu-item"><a href="{{ route('desakelurahan.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-home"></i></span><div data-i18n="Desa Kelurahan">Desa Kelurahan</div></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="{{ route('adat.index') }}" class="menu-link"><i class="menu-icon icon-base ti tabler-matrix"></i><div data-i18n="Adat">Adat</div></a>
|
||||
<li class="menu-item {{ request()->routeIs('adat.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('adat.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-heart-handshake"></i></span><div data-i18n="Adat">Adat</div></a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="{{ route('keluarga.index') }}" class="menu-link"><i class="menu-icon icon-base ti tabler-sitemap"></i><div data-i18n="Keluarga">Keluarga</div></a>
|
||||
<li class="menu-item {{ request()->routeIs('keluarga.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('keluarga.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-sitemap"></i></span><div data-i18n="Keluarga">Keluarga</div></a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<li class="menu-item {{ request()->routeIs(['komoditas.*', 'jenis-komoditas.*', 'varietas.*']) ? 'active open' : '' }}">
|
||||
<a href="javascript:void(0);" class="menu-link menu-toggle">
|
||||
<i class="menu-icon icon-base ti tabler-plant"></i><div data-i18n="Tanaman">Tanaman</div>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-plant"></i></span><div data-i18n="Tanaman">Tanaman</div>
|
||||
</a>
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item"><a href="{{ route('komoditas.index') }}" class="menu-link"><div data-i18n="Komoditas">Komoditas</div></a></li>
|
||||
<li class="menu-item"><a href="{{ route('jenis-komoditas.index') }}" class="menu-link"><div data-i18n="Jenis Komoditas">Jenis Komoditas</div></a></li>
|
||||
<li class="menu-item"><a href="{{ route('varietas.index') }}" class="menu-link"><div data-i18n="Varietas">Varietas</div></a></li>
|
||||
<li class="menu-item"><a href="{{ route('komoditas.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-plant"></i></span><div data-i18n="Komoditas">Komoditas</div></a></li>
|
||||
<li class="menu-item"><a href="{{ route('jenis-komoditas.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-layout-grid"></i></span><div data-i18n="Jenis Komoditas">Jenis Komoditas</div></a></li>
|
||||
<li class="menu-item"><a href="{{ route('varietas.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-leaf"></i></span><div data-i18n="Varietas">Varietas</div></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if(in_array($userRole, ['super_admin', 'admin']))
|
||||
<li class="menu-header small">
|
||||
<span class="menu-header-text" data-i18n="Konfigurasi">Konfigurasi</span>
|
||||
</li>
|
||||
@if($isAdmin)
|
||||
<li class="menu-item {{ request()->routeIs(['users.*', 'program.*', 'framework.*', 'group.*']) || (request()->routeIs('tabel.*') && !request()->routeIs('tabel.data.*')) ? 'active open' : '' }}">
|
||||
<a href="javascript:void(0);" class="menu-link menu-toggle">
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-settings"></i></span>
|
||||
<div data-i18n="Konfigurasi">Konfigurasi</div>
|
||||
</a>
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item {{ request()->routeIs('users.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('users.index') }}" class="menu-link">
|
||||
<i class="menu-icon icon-base ti tabler-users"></i><div data-i18n="Daftar Pengguna">Daftar Pengguna</div>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-user-shield"></i></span><div data-i18n="Daftar Pengguna">Daftar Pengguna</div>
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if(in_array($userRole, ['super_admin', 'admin']) && env('APP_NAME') == 'AgroPro')
|
||||
@if($isAdmin && $appName === 'AgroPro')
|
||||
<li class="menu-item {{ request()->routeIs(['program.*', 'framework.*']) ? 'active open' : '' }}">
|
||||
<a href="javascript:void(0);" class="menu-link menu-toggle">
|
||||
<i class="menu-icon icon-base ti tabler-transform"></i><div data-i18n="Program Transformasi">Program Transformasi</div>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-route"></i></span><div data-i18n="Program Transformasi">Program Transformasi</div>
|
||||
</a>
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item {{ request()->routeIs('program.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('program.index') }}" class="menu-link"><div data-i18n="Program Transformasi">Program Transformasi</div></a>
|
||||
<a href="{{ route('program.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-route"></i></span><div data-i18n="Program Transformasi">Program Transformasi</div></a>
|
||||
</li>
|
||||
<li class="menu-item {{ request()->routeIs('framework.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('framework.index') }}" class="menu-link"><div data-i18n="Framework">Framework</div></a>
|
||||
<a href="{{ route('framework.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-sitemap"></i></span><div data-i18n="Framework">Framework</div></a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu-item {{ request()->routeIs(['group.*', 'tabel.*']) && !request()->routeIs('tabel.data.*') ? 'active open' : '' }}">
|
||||
<a href="javascript:void(0);" class="menu-link menu-toggle">
|
||||
<i class="menu-icon icon-base ti tabler-database"></i><div data-i18n="Basis Data Ekosistem">Basis Data Ekosistem</div>
|
||||
<span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-database"></i></span><div data-i18n="Basis Data Ekosistem">Basis Data Ekosistem</div>
|
||||
</a>
|
||||
<ul class="menu-sub">
|
||||
<li class="menu-item {{ request()->routeIs('group.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('group.index') }}" class="menu-link"><div data-i18n="Kelompok">Kelompok</div></a>
|
||||
<a href="{{ route('group.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-users-group"></i></span><div data-i18n="Kelompok">Kelompok</div></a>
|
||||
</li>
|
||||
<li class="menu-item {{ request()->routeIs('tabel.*') && !request()->routeIs('tabel.data.*') ? 'active' : '' }}">
|
||||
<a href="{{ route('tabel.index') }}" class="menu-link"><div data-i18n="Tabel">Tabel</div></a>
|
||||
<a href="{{ route('tabel.index') }}" class="menu-link"><span class="menu-icon sidebar-icon" aria-hidden="true"><i class="icon-base ti tabler-table"></i></span><div data-i18n="Tabel">Tabel</div></a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
<!-- Misc -->
|
||||
<!-- <li class="menu-header small">
|
||||
<span class="menu-header-text" data-i18n="Misc">Misc</span>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="https://docs.google.com/spreadsheets/d/1nbh4xW-K_GwRW34-cD81p9B-LnLvkvro/edit?usp=sharing&ouid=103442063105125817060&rtpof=true&sd=true" target="_blank" class="menu-link">
|
||||
<i class="menu-icon icon-base ti tabler-file-description"></i><div data-i18n="Documentation">Documentation</div>
|
||||
</a>
|
||||
</li> -->
|
||||
@endif
|
||||
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
@@ -234,7 +234,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}" class="btn btn-secondary me-2 wave-effect wave-light">Kembali</a>
|
||||
<a href="{{ route('petani.index') }}#data-petani" 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>
|
||||
|
||||
|
||||
@@ -30,11 +30,110 @@
|
||||
|
||||
<div class="card mb-6">
|
||||
<div class="card-body pt-12">
|
||||
<h5 class="pb-3 border-bottom mb-4">Pengalaman Saluran Lain</h5>
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4">Saluran Penjualan & Akses Pasar</h5>
|
||||
|
||||
<h5 class="mb-4 mt-4">Saluran Penjualan Utama</h5>
|
||||
<div class="mb-4">
|
||||
<p class="form-label d-block">Kepada siapa Bapak/Ibu biasanya menjual hasil panen? (boleh pilih lebih dari satu)</p>
|
||||
<div class="row">
|
||||
@php
|
||||
$tujuanOptions = ['Tengkulak/pedagang lokal', 'Koperasi', 'BUMDes', 'Langsung ke pembeli/eksportir'];
|
||||
@endphp
|
||||
@foreach($tujuanOptions as $tujuan)
|
||||
<div class="col-md-6">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" name="tujuan_penjualan[]" value="{{ $tujuan }}" id="tujuan-{{ Str::slug($tujuan) }}" {{ in_array($tujuan, (array) old('tujuan_penjualan', $aksesPasar?->tujuan_penjualan ?? []), true) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="tujuan-{{ Str::slug($tujuan) }}">{{ $tujuan }}</label>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<div class="col-12 mt-2">
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<div class="form-check mb-0">
|
||||
<input class="form-check-input" type="checkbox" name="tujuan_penjualan[]" value="Lainnya" id="tujuan-lainnya" {{ in_array('Lainnya', (array) old('tujuan_penjualan', $aksesPasar?->tujuan_penjualan ?? []), true) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="tujuan-lainnya">Lainnya:</label>
|
||||
</div>
|
||||
<input type="text" class="form-control w-auto flex-grow-1 max-w-400" style="max-width: 400px;" name="tujuan_penjualan_lainnya" value="{{ old('tujuan_penjualan_lainnya', $aksesPasar?->tujuan_penjualan_lainnya) }}" placeholder="Sebutkan lainnya...">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="mb-4 mt-4">Sifat Hubungan Dengan Pembeli</h5>
|
||||
<div class="mb-4">
|
||||
<p class="form-label d-block">Bagaimana sifat hubungan jual Bapak/Ibu dengan pembeli utama saat ini?</p>
|
||||
<div class="row">
|
||||
@php
|
||||
$sifatHubunganOptions = [
|
||||
'Bebas, bisa pindah pembeli kapan saja',
|
||||
'Langganan tetap, tapi tidak terikat',
|
||||
'Berbasis hutang, harus jual ke sana karena ada pinjaman',
|
||||
'Titip jual,dibayar setelah produk terjual',
|
||||
'Ada komitmen volume, sudah ada kesepakatan jumlah',
|
||||
'Semi-kontrak, ada kesepakatan harga tertentu'
|
||||
];
|
||||
@endphp
|
||||
@foreach($sifatHubunganOptions as $sifat)
|
||||
<div class="col-md-6">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="sifat_hubungan_bisnis" value="{{ $sifat }}" id="sifat-{{ Str::slug($sifat) }}" {{ old('sifat_hubungan_bisnis', $aksesPasar?->sifat_hubungan_bisnis) === $sifat ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="sifat-{{ Str::slug($sifat) }}">{{ $sifat }}</label>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="mb-4 mt-4">Bentuk Produk Yang Dijual</h5>
|
||||
<div class="mb-4">
|
||||
<p class="form-label d-block">Dalam bentuk apa produk dijual?</p>
|
||||
<div class="row">
|
||||
@php
|
||||
$bentukPenjualanOptions = [
|
||||
'Buah segar hasil petik (gelondongan)',
|
||||
'Biji/polong basah (belum dikeringkan)',
|
||||
'Biji/polong kering/biji kupas',
|
||||
'Terfermentasi'
|
||||
];
|
||||
@endphp
|
||||
@foreach($bentukPenjualanOptions as $bentuk)
|
||||
<div class="col-md-6">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="bentuk_penjualan" value="{{ $bentuk }}" id="bentuk-{{ Str::slug($bentuk) }}" {{ old('bentuk_penjualan', $aksesPasar?->bentuk_penjualan) === $bentuk ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="bentuk-{{ Str::slug($bentuk) }}">{{ $bentuk }}</label>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="mb-4 mt-4">Frekuensi Penjualan</h5>
|
||||
<div class="mb-4">
|
||||
<p class="form-label d-block">Kapan biasanya menjual hasil panen?</p>
|
||||
<div class="d-flex flex-column">
|
||||
@php
|
||||
$frekuensiOptions = [
|
||||
'Langsung setelah panen',
|
||||
'Ditumpuk dulu beberapa waktu',
|
||||
'Saat butuh uang mendesak'
|
||||
];
|
||||
@endphp
|
||||
@foreach($frekuensiOptions as $freq)
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="frekuensi_penjualan" value="{{ $freq }}" id="freq-{{ Str::slug($freq) }}" {{ old('frekuensi_penjualan', $aksesPasar?->frekuensi_penjualan) === $freq ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="freq-{{ Str::slug($freq) }}">{{ $freq }}</label>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4 mt-4">Pengalaman Saluran Lain</h5>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label d-block fw-bold">Pengalaman & akses ke saluran lain</label>
|
||||
<div class="col-md-12 mb-4 form-control-validation">
|
||||
<label class="form-label d-block">Apakah pernah menjual ke tempat lain sebelumnya?</label>
|
||||
@foreach($skorPengalamanOptions as $option)
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input @error('skor_pengalaman') is-invalid @enderror" type="radio"
|
||||
@@ -46,41 +145,67 @@
|
||||
@error('skor_pengalaman')<div class="invalid-feedback d-block">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-4 form-control-validation">
|
||||
<label class="form-label" for="alasan-berhenti">Alasan berhenti</label>
|
||||
<input type="text" class="form-control @error('alasan_berhenti') is-invalid @enderror" id="alasan-berhenti" name="alasan_berhenti" value="{{ old('alasan_berhenti', $aksesPasar?->alasan_berhenti) }}" placeholder="Opsional (jika sudah berhenti)...">
|
||||
@error('alasan_berhenti')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-12" id="blok-saluran-lanjutan">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label d-block mb-2" for="saluran-terdahulu">Dulu pernah jual ke?</label>
|
||||
@foreach($saluranTerdahuluOptions as $option)
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input @error('saluran_terdahulu') is-invalid @enderror"
|
||||
type="checkbox"
|
||||
id="saluran-terdahulu-{{ Str::slug($option['value']) }}"
|
||||
name="saluran_terdahulu[]"
|
||||
value="{{ $option['value'] }}"
|
||||
{{ in_array($option['value'], (array) old('saluran_terdahulu', $aksesPasar?->saluran_terdahulu ?? []), true) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="saluran-terdahulu-{{ Str::slug($option['value']) }}">
|
||||
{{ $option['label'] }}
|
||||
</label>
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label" for="alasan-berhenti">Alasan berhenti</label>
|
||||
<input type="text" class="form-control @error('alasan_berhenti') is-invalid @enderror" id="alasan-berhenti" name="alasan_berhenti" value="{{ old('alasan_berhenti', $aksesPasar?->alasan_berhenti) }}" placeholder="Opsional (jika sudah berhenti)...">
|
||||
@error('alasan_berhenti')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
@endforeach
|
||||
@error('saluran_terdahulu')<div class="invalid-feedback d-block">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-4 form-control-validation">
|
||||
<label class="form-label" for="saluran-lainnya">Lainnya</label>
|
||||
<input type="text" class="form-control @error('saluran_terdahulu_lainnya') is-invalid @enderror" id="saluran-lainnya" name="saluran_terdahulu_lainnya" value="{{ old('saluran_terdahulu_lainnya', $aksesPasar?->saluran_terdahulu_lainnya) }}" placeholder="Sebutkan saluran lainnya...">
|
||||
@error('saluran_terdahulu_lainnya')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
<div class="col-md-6 mb-4"></div>
|
||||
|
||||
<div class="col-md-12 mb-4 form-control-validation">
|
||||
<label class="form-label d-block" for="saluran-terdahulu">Jika pernah, dulu menjual ke mana? (boleh pilih lebih dari satu)</label>
|
||||
<div class="row">
|
||||
@foreach($saluranTerdahuluOptions as $option)
|
||||
@if($option['value'] !== $saluranLainnyaValue)
|
||||
<div class="col-md-6">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input @error('saluran_terdahulu') is-invalid @enderror"
|
||||
type="checkbox"
|
||||
id="saluran-terdahulu-{{ Str::slug($option['value']) }}"
|
||||
name="saluran_terdahulu[]"
|
||||
value="{{ $option['value'] }}"
|
||||
{{ in_array($option['value'], (array) old('saluran_terdahulu', $aksesPasar?->saluran_terdahulu ?? []), true) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="saluran-terdahulu-{{ Str::slug($option['value']) }}">
|
||||
{{ $option['label'] }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
<div class="col-12 mt-2">
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<div class="form-check mb-0">
|
||||
<input class="form-check-input @error('saluran_terdahulu') is-invalid @enderror"
|
||||
type="checkbox"
|
||||
id="saluran-terdahulu-lainnya-checkbox"
|
||||
name="saluran_terdahulu[]"
|
||||
value="{{ $saluranLainnyaValue }}"
|
||||
{{ in_array($saluranLainnyaValue, (array) old('saluran_terdahulu', $aksesPasar?->saluran_terdahulu ?? []), true) ? 'checked' : '' }}>
|
||||
<label class="form-check-label text-nowrap" for="saluran-terdahulu-lainnya-checkbox">
|
||||
Lainnya:
|
||||
</label>
|
||||
</div>
|
||||
<input type="text" class="form-control w-auto flex-grow-1 @error('saluran_terdahulu_lainnya') is-invalid @enderror" style="max-width: 400px;" id="saluran-lainnya" name="saluran_terdahulu_lainnya" value="{{ old('saluran_terdahulu_lainnya', $aksesPasar?->saluran_terdahulu_lainnya) }}" placeholder="Sebutkan saluran lainnya...">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@error('saluran_terdahulu')<div class="invalid-feedback d-block">{{ $message }}</div>@enderror
|
||||
@error('saluran_terdahulu_lainnya')<div class="invalid-feedback d-block">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="pb-3 border-bottom mb-4 mt-4">HARGA DI SETIAP TITIK</h5>
|
||||
<h6 class="mb-2">Harga Jual Panen Terakhir</h6>
|
||||
<p class="mb-1">Harga jual komoditas tiga musim panen terakhir:</p>
|
||||
<p class="mb-4">Dalam tiga musim panen terakhir, berapa harga jual yang Bapak/Ibu terima untuk komoditas ini sesuai dengan bentuk produk yang dijual?</p>
|
||||
<h5 class="pb-4 border-bottom mb-4 mt-4">Harga Di Setiap Titik</h5>
|
||||
<h5 class="mb-4 mt-4">Harga Jual Panen Terakhir</h5>
|
||||
<p class="form-label d-block">Harga jual komoditas tiga musim panen terakhir:</p>
|
||||
<p class="form-label d-block">Dalam tiga musim panen terakhir, berapa harga jual yang Bapak/Ibu terima untuk komoditas ini sesuai dengan bentuk produk yang dijual?</p>
|
||||
|
||||
<div class="table-responsive mb-4">
|
||||
<table class="table table-bordered align-middle">
|
||||
@@ -97,7 +222,7 @@
|
||||
<tr>
|
||||
<td>
|
||||
<input type="hidden" name="harga_jual[{{ $row['index'] }}][bentuk_produk]" value="{{ $row['bentuk'] }}">
|
||||
<strong>{{ $row['bentuk'] }}:</strong>
|
||||
{{ $row['bentuk'] }}:
|
||||
</td>
|
||||
<td>
|
||||
<div class="input-group">
|
||||
@@ -126,18 +251,87 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h5 class="pb-3 border-bottom mb-4 mt-4">PELUANG NILAI TAMBAH</h5>
|
||||
<h5 class="mb-4 mt-4">Sumber Informasi Harga</h5>
|
||||
<div class="mb-4">
|
||||
|
||||
<div class="border rounded p-4 mb-4">
|
||||
<div class="mb-4">
|
||||
<h6 class="mb-1 text-primary">Detail Praktik Fermentasi</h6>
|
||||
<p class="text-muted small mb-0">Apakah sudah ada praktik fermentasi atau pengolahan pascapanen yang Ibu/Bapak lakukan?</p>
|
||||
<p class="form-label d-block">Sebelum menjual, apakah Bapak/Ibu sudah tahu harga yang akan diterima?</p>
|
||||
<div class="row">
|
||||
@php
|
||||
$tahuHargaOptions = [
|
||||
'Selalu tahu sebelum pergi jual',
|
||||
'Kadang tahu, kadang tidak',
|
||||
'Baru tahu saat sudah di tempat pembeli',
|
||||
'Tidak pernah tahu sebelumnya — langsung jual saja'
|
||||
];
|
||||
@endphp
|
||||
@foreach($tahuHargaOptions as $tahu)
|
||||
<div class="col-md-6">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input @error('tahu_harga_sebelumnya') is-invalid @enderror" type="radio" name="tahu_harga_sebelumnya" value="{{ $tahu }}" id="tahu-{{ Str::slug($tahu) }}" {{ old('tahu_harga_sebelumnya', $aksesPasar?->tahu_harga_sebelumnya) === $tahu ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="tahu-{{ Str::slug($tahu) }}">{{ $tahu }}</label>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@error('tahu_harga_sebelumnya')<div class="invalid-feedback d-block">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-0">
|
||||
<p class="form-label d-block">Dari mana biasanya Bapak/Ibu tahu harga hasil panen sebelum menjual? (boleh pilih lebih dari satu)</p>
|
||||
<div class="row">
|
||||
@php
|
||||
$sumberInfoHargaOptions = [
|
||||
'Tengkulak/pedagang yang datang langsung',
|
||||
'Sesama petani/tetangga',
|
||||
'PPL/petugas dinas',
|
||||
'HP/WhatsApp/internet',
|
||||
'Radio/TV',
|
||||
'Facebook',
|
||||
'Tiktok',
|
||||
'Instagram',
|
||||
'Tidak pernah cek — langsung jual saja'
|
||||
];
|
||||
$sumberInfoLainnyaValue = 'Lainnya';
|
||||
@endphp
|
||||
|
||||
@foreach($sumberInfoHargaOptions as $sumber)
|
||||
<div class="col-md-4">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input @error('sumber_info_harga') is-invalid @enderror" type="checkbox" name="sumber_info_harga[]" value="{{ $sumber }}" id="sumber-{{ Str::slug($sumber) }}" {{ in_array($sumber, (array) old('sumber_info_harga', $aksesPasar?->sumber_info_harga ?? []), true) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="sumber-{{ Str::slug($sumber) }}">{{ $sumber }}</label>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<div class="col-12 mt-2">
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<div class="form-check mb-0">
|
||||
<input class="form-check-input @error('sumber_info_harga') is-invalid @enderror" type="checkbox" name="sumber_info_harga[]" value="{{ $sumberInfoLainnyaValue }}" id="sumber-lainnya-checkbox" {{ in_array($sumberInfoLainnyaValue, (array) old('sumber_info_harga', $aksesPasar?->sumber_info_harga ?? []), true) ? 'checked' : '' }}>
|
||||
<label class="form-check-label text-nowrap" for="sumber-lainnya-checkbox">Lainnya:</label>
|
||||
</div>
|
||||
<input type="text" class="form-control w-auto flex-grow-1 @error('sumber_info_harga_lainnya') is-invalid @enderror" style="max-width: 400px;" id="sumber-info-lainnya-input" name="sumber_info_harga_lainnya" value="{{ old('sumber_info_harga_lainnya', $aksesPasar?->sumber_info_harga_lainnya) }}" placeholder="Sebutkan sumber lainnya...">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@error('sumber_info_harga')<div class="invalid-feedback d-block">{{ $message }}</div>@enderror
|
||||
@error('sumber_info_harga_lainnya')<div class="invalid-feedback d-block">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4 mt-4">Peluang Nilai Tambah</h5>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="mb-4">
|
||||
<h5 class="mb-4 mt-4">Detail Praktik Fermentasi</h5>
|
||||
<p class="form-label d-block">Apakah sudah ada praktik fermentasi atau pengolahan pascapanen yang Ibu/Bapak lakukan?</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4 form-control-validation d-flex flex-column">
|
||||
<div class="mb-2 d-flex align-items-end">
|
||||
<label class="form-label mb-0" for="praktik-fermentasi">Praktik fermentasi yang dilakukan</label>
|
||||
<div class="d-flex align-items-end">
|
||||
<label class="form-label" for="praktik-fermentasi">Praktik fermentasi yang dilakukan</label>
|
||||
</div>
|
||||
<div>
|
||||
@foreach($praktikFermentasiOptions as $option)
|
||||
@@ -158,8 +352,8 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-4 form-control-validation d-flex flex-column">
|
||||
<div class="mb-2 d-flex align-items-end">
|
||||
<label class="form-label mb-0" for="proses-fermentasi">Jika ya, bagaimana prosesnya?</label>
|
||||
<div class="d-flex align-items-end">
|
||||
<label class="form-label" for="proses-fermentasi">Jika ya, bagaimana prosesnya?</label>
|
||||
</div>
|
||||
<div>
|
||||
<textarea class="form-control @error('proses_fermentasi') is-invalid @enderror" id="proses-fermentasi" name="proses_fermentasi" rows="2" placeholder="Ceritakan prosesnya di sini...">{{ old('proses_fermentasi', $aksesPasar?->proses_fermentasi) }}</textarea>
|
||||
@@ -168,8 +362,8 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-4 form-control-validation d-flex flex-column">
|
||||
<div class="mb-2 d-flex align-items-end">
|
||||
<label class="form-label mb-0" for="model-fermentasi">Menurut Bapak/Ibu, apa model yang paling memungkinkan?</label>
|
||||
<div class="d-flex align-items-end">
|
||||
<label class="form-label" for="model-fermentasi">Menurut Bapak/Ibu, apa model yang paling memungkinkan?</label>
|
||||
</div>
|
||||
<div>
|
||||
<textarea class="form-control @error('model_fermentasi_memungkinkan') is-invalid @enderror" id="model-fermentasi" name="model_fermentasi_memungkinkan" rows="2" placeholder="Model fermentasinya...">{{ old('model_fermentasi_memungkinkan', $aksesPasar?->model_fermentasi_memungkinkan) }}</textarea>
|
||||
@@ -179,11 +373,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="pb-3 border-bottom mb-4 mt-4">AKSES PASAR PREMIUM</h5>
|
||||
<h6 class="mb-3">Kesadaran Pasar Premium*</h6>
|
||||
<h5 class="pb-4 border-bottom mb-4 mt-4">Akses Pasar Premium</h5>
|
||||
<h5 class="mb-4 mt-4">Kesadaran Pasar Premium*</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label d-block mb-2" for="skor-premium">Pengetahuan & akses pasar premium</label>
|
||||
<label class="form-label d-block" for="skor-premium">Pengetahuan & akses pasar premium</label>
|
||||
@foreach($skorKesadaranPremiumOptions as $option)
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input @error('skor_kesadaran_premium') is-invalid @enderror"
|
||||
@@ -201,16 +395,16 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label mb-2" for="sumber-info">Sumber informasi utama</label>
|
||||
<label class="form-label" for="sumber-info">Sumber informasi utama</label>
|
||||
<input type="text" class="form-control @error('sumber_info_premium') is-invalid @enderror" id="sumber-info" name="sumber_info_premium" value="{{ old('sumber_info_premium', $aksesPasar?->sumber_info_premium) }}">
|
||||
@error('sumber_info_premium')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="pb-3 border-bottom mb-4 mt-4">KEPERCAYAAN & PERSEPSI RISIKO</h5>
|
||||
<h5 class="pb-4 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>
|
||||
<label class="form-label d-block" 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"
|
||||
@@ -228,7 +422,7 @@
|
||||
</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>
|
||||
<label class="form-label" 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"
|
||||
@@ -239,8 +433,9 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-device-floppy me-1_5 icon-sm"></i>Simpan Akses Pasar
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>Simpan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -258,6 +453,8 @@
|
||||
const saluranLainnyaValue = @json($saluranLainnyaValue);
|
||||
const praktikBelumPernahValue = String(@json($praktikBelumPernahValue));
|
||||
const skorPengalamanBerhentiValues = @json($skorPengalamanBerhentiValues ?? [2,3]).map(Number);
|
||||
const skorBelumPernahValue = "1";
|
||||
const sumberInfoLainnyaValue = @json($sumberInfoLainnyaValue ?? 'Lainnya');
|
||||
|
||||
function onlyDigits(value) {
|
||||
return String(value || '').replace(/\D/g, '');
|
||||
@@ -265,10 +462,25 @@
|
||||
|
||||
function formatRupiah(value) {
|
||||
const digits = onlyDigits(value);
|
||||
|
||||
return digits.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
||||
}
|
||||
|
||||
function toggleSaluranLanjutan() {
|
||||
const selectedRadio = $('input[name="skor_pengalaman"]:checked');
|
||||
const val = selectedRadio.val();
|
||||
const labelText = selectedRadio.parent().text().toLowerCase();
|
||||
|
||||
const isBelumPernah = !val || labelText.includes('belum pernah') || val === skorBelumPernahValue;
|
||||
|
||||
if (isBelumPernah) {
|
||||
$('#blok-saluran-lanjutan').addClass('d-none');
|
||||
$('#blok-saluran-lanjutan input[type="text"]').val('');
|
||||
$('#blok-saluran-lanjutan input[type="checkbox"]').prop('checked', false);
|
||||
} else {
|
||||
$('#blok-saluran-lanjutan').removeClass('d-none');
|
||||
}
|
||||
}
|
||||
|
||||
function syncAlasanBerhenti() {
|
||||
const val = Number($('input[name="skor_pengalaman"]:checked').val());
|
||||
const isBerhenti = skorPengalamanBerhentiValues.includes(val);
|
||||
@@ -282,6 +494,12 @@
|
||||
if (!isLainnya) $('#saluran-lainnya').val('');
|
||||
}
|
||||
|
||||
function syncSumberInfoLainnya() {
|
||||
const isLainnya = $('input[name="sumber_info_harga[]"][value="' + sumberInfoLainnyaValue + '"]').is(':checked');
|
||||
$('#sumber-info-lainnya-input').prop('disabled', !isLainnya);
|
||||
if (!isLainnya) $('#sumber-info-lainnya-input').val('');
|
||||
}
|
||||
|
||||
function syncPraktikFermentasi() {
|
||||
const isBelumPernah = $('input[name="praktik_fermentasi"]:checked').val() === praktikBelumPernahValue;
|
||||
$('#proses-fermentasi, #model-fermentasi').prop('disabled', isBelumPernah);
|
||||
@@ -290,9 +508,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
$('input[name="skor_pengalaman"]').on('change', syncAlasanBerhenti);
|
||||
$('input[name="skor_pengalaman"]').on('change', function () {
|
||||
toggleSaluranLanjutan();
|
||||
syncAlasanBerhenti();
|
||||
});
|
||||
|
||||
$('input[name="saluran_terdahulu[]"]').on('change', syncSaluranLainnya);
|
||||
$('input[name="sumber_info_harga[]"]').on('change', syncSumberInfoLainnya);
|
||||
$('input[name="praktik_fermentasi"]').on('change', syncPraktikFermentasi);
|
||||
|
||||
$(document).on('input', '.rupiah-input', function () {
|
||||
const cursorAtEnd = this.selectionStart === this.value.length;
|
||||
this.value = formatRupiah(this.value);
|
||||
@@ -301,12 +525,15 @@
|
||||
this.setSelectionRange(this.value.length, this.value.length);
|
||||
}
|
||||
});
|
||||
|
||||
$('.rupiah-input').each(function () {
|
||||
this.value = formatRupiah(this.value);
|
||||
});
|
||||
|
||||
toggleSaluranLanjutan();
|
||||
syncAlasanBerhenti();
|
||||
syncSaluranLainnya();
|
||||
syncSumberInfoLainnya();
|
||||
syncPraktikFermentasi();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/croppie/2.6.5/croppie.min.js"></script>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="container-xxl flex-grow-1 container-p-y">
|
||||
@@ -43,13 +44,18 @@
|
||||
<div class="col-md-3">
|
||||
<div class="user-avatar-section">
|
||||
<div class=" d-flex align-items-center flex-column">
|
||||
<img id="fotoPreview"
|
||||
class="mb-3 shadow-sm rounded-3"
|
||||
<button type="button" class="border-0 bg-transparent p-0 mb-3 rounded-3"
|
||||
data-bs-toggle="modal" data-bs-target="#fotoProfilModal"
|
||||
aria-label="Lihat foto profil petani" title="Lihat foto profil petani">
|
||||
<img id="fotoPreview"
|
||||
class="shadow-sm rounded-3"
|
||||
src="{{ $petani->foto ? route('petani.show-image', ['id' => $petani->id, 'field' => 'foto']) : asset('assets/img/avatars/3.jpg') }}"
|
||||
alt="Foto profil petani"
|
||||
data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
data-bs-original-title="Klik untuk mengganti foto profil petani"
|
||||
style="width: 120px !important; height: 120px !important; cursor:pointer; object-fit:cover; display:block; margin: 0 auto;" />
|
||||
</button>
|
||||
<button type="button" id="btnGantiFoto" class="btn btn-sm btn-outline-primary mb-3">
|
||||
Ganti Foto
|
||||
</button>
|
||||
|
||||
<button type="button" id="btnHapusFoto" class="btn btn-sm btn-outline-danger mb-4" style="display: {{ $petani->foto ? 'block' : 'none' }};">
|
||||
Hapus Foto
|
||||
@@ -113,7 +119,7 @@
|
||||
<select class="form-control @error('pendidikan_terakhir') is-invalid @enderror" id="edit-petani-pendidikan-terakhir" name="pendidikan_terakhir">
|
||||
<option value="">Pilih Pendidikan Terakhir</option>
|
||||
@foreach($pendidikan as $value)
|
||||
<option value="{{ $value['value'] }}" {{ old('pendidikan_terakhir', $petani->pendidikan_terakhir->value) == $value['value'] ? 'selected' : '' }}>{{ $value['label'] }}</option>
|
||||
<option value="{{ $value['value'] }}" {{ old('pendidikan_terakhir', $petani->pendidikan_terakhir?->value) == $value['value'] ? 'selected' : '' }}>{{ $value['label'] }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('pendidikan_terakhir')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
@@ -123,7 +129,7 @@
|
||||
<select class="form-control @error('status_perkawinan') is-invalid @enderror" id="edit-petani-status-pernikahan" name="status_perkawinan">
|
||||
<option value="">Pilih Status Pernikahan</option>
|
||||
@foreach($pernikahan as $value)
|
||||
<option value="{{ $value['value'] }}" {{ old('status_perkawinan', $petani->status_perkawinan->value) == $value['value'] ? 'selected' : '' }}>{{ $value['label'] }}</option>
|
||||
<option value="{{ $value['value'] }}" {{ old('status_perkawinan', $petani->status_perkawinan?->value) == $value['value'] ? 'selected' : '' }}>{{ $value['label'] }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('status_perkawinan')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
@@ -159,7 +165,7 @@
|
||||
<select class="form-control @error('provinsi_id') is-invalid @enderror" id="edit-petani-provinsi" name="provinsi_id">
|
||||
<option value="">Pilih Provinsi</option>
|
||||
@foreach($provinsi as $p)
|
||||
<option value="{{ $p->id }}" {{ old('provinsi_id', $petani->desaKelurahan->kecamatan->kabupatenKota->provinsi_id) == $p->id ? 'selected' : '' }}>{{ $p->nama }}</option>
|
||||
<option value="{{ $p->id }}" {{ old('provinsi_id', $petani->desaKelurahan?->kecamatan?->kabupatenKota?->provinsi_id) == $p->id ? 'selected' : '' }}>{{ $p->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('provinsi_id')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
@@ -168,7 +174,9 @@
|
||||
<label class="form-label" for="edit-petani-kabupaten">Kabupaten/Kota<span class="text-danger">*</span></label>
|
||||
<select class="form-control @error('kabupaten_id') is-invalid @enderror" id="edit-petani-kabupaten" name="kabupaten_id" >
|
||||
<option value="">Pilih Kabupaten/Kota</option>
|
||||
@if($petani->desaKelurahan?->kecamatan?->kabupatenKota)
|
||||
<option value="{{ $petani->desaKelurahan->kecamatan->kabupatenKota->id }}" selected>{{ $petani->desaKelurahan->kecamatan->kabupatenKota->nama }}</option>
|
||||
@endif
|
||||
</select>
|
||||
@error('kabupaten_id')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
@@ -176,7 +184,9 @@
|
||||
<label class="form-label" for="edit-petani-kecamatan">Kecamatan<span class="text-danger">*</span></label>
|
||||
<select class="form-control @error('kecamatan_id') is-invalid @enderror" id="edit-petani-kecamatan" name="kecamatan_id" >
|
||||
<option value="">Pilih Kecamatan</option>
|
||||
@if($petani->desaKelurahan?->kecamatan)
|
||||
<option value="{{ $petani->desaKelurahan->kecamatan->id }}" selected>{{ $petani->desaKelurahan->kecamatan->nama }}</option>
|
||||
@endif
|
||||
</select>
|
||||
@error('kecamatan_id')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
@@ -184,7 +194,9 @@
|
||||
<label class="form-label" for="edit-petani-desa">Desa/Kelurahan<span class="text-danger">*</span></label>
|
||||
<select class="form-control @error('desa_kelurahan_id') is-invalid @enderror" id="edit-petani-desa" name="desa_kelurahan_id" >
|
||||
<option value="">Pilih Desa/Kelurahan</option>
|
||||
@if($petani->desaKelurahan)
|
||||
<option value="{{ $petani->desaKelurahan->id }}" selected>{{ $petani->desaKelurahan->nama }}</option>
|
||||
@endif
|
||||
</select>
|
||||
@error('desa_kelurahan_id')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
@@ -242,7 +254,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}" class="btn btn-secondary me-2 wave-effect wave-light">Kembali</a>
|
||||
<a href="{{ route('petani.index') }}#data-petani" 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>
|
||||
|
||||
@@ -253,6 +265,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="fotoProfilModal" tabindex="-1" aria-labelledby="fotoProfilModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="fotoProfilModalLabel">Foto Profil Petani</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Tutup"></button>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
<img id="fotoProfilBesar" class="img-fluid rounded-3" alt="Foto profil petani" style="max-height: 75vh; object-fit: contain;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="cropModal" tabindex="-1" aria-labelledby="cropModalLabel" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
@@ -344,7 +370,11 @@
|
||||
|
||||
let croppieInstance = null;
|
||||
|
||||
$('#fotoPreview').on('click', function () {
|
||||
$('#fotoProfilModal').on('show.bs.modal', function () {
|
||||
$('#fotoProfilBesar').attr('src', $('#fotoPreview').attr('src'));
|
||||
});
|
||||
|
||||
$('#btnGantiFoto').on('click', function () {
|
||||
$('#foto').val('');
|
||||
$('#foto').click();
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
<hr class="my-2"> <h6 class="mb-1" style="font-size: 0.85rem;">Domisili</h6>
|
||||
<p class="mb-0 text-muted" style="font-size: 0.75rem;">Alamat: {{ isset($petani) ? $petani->alamat : 'Alamat' }}</p>
|
||||
<p class="mb-0 text-muted" style="font-size: 0.75rem; line-height: 1.3;">{{ isset($petani) ? $petani->desaKelurahan->nama : 'Desa/Kelurahan' }}, {{ $petani ? $petani->desaKelurahan->kecamatan->nama : 'Kecamatan' }}, {{ $petani ? $petani->desaKelurahan->kecamatan->kabupatenKota->nama : 'Kabupaten/Kota' }}, {{ $petani ? $petani->desaKelurahan->kecamatan->kabupatenKota->provinsi->nama : 'Provinsi' }}</p>
|
||||
<p class="mb-0 text-muted" style="font-size: 0.75rem; line-height: 1.3;">{{ isset($petani) ? $petani->desaKelurahan?->nama ?? 'Belum diisi' : 'Desa/Kelurahan' }}, {{ $petani ? $petani->desaKelurahan?->kecamatan?->nama ?? 'Belum diisi' : 'Kecamatan' }}, {{ $petani ? $petani->desaKelurahan?->kecamatan?->kabupatenKota?->nama ?? 'Belum diisi' : 'Kabupaten/Kota' }}, {{ $petani ? $petani->desaKelurahan?->kecamatan?->kabupatenKota?->provinsi?->nama ?? 'Belum diisi' : 'Provinsi' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,33 +66,11 @@
|
||||
<div class="col-md-8 d-flex flex-column">
|
||||
@php
|
||||
$skorLahanTanaman = isset($petani) ? $petani->skorLahanTanaman() : null;
|
||||
$skorPraktikBudidaya = isset($petani) ? $petani->skorPraktikBudidaya() : null;
|
||||
$skorPendidikan = isset($petani) ? $petani->skorPendidikan() : null;
|
||||
$skorSosialEkonomiSegmen = isset($petani) ? $petani->skorSosialEkonomi() : null;
|
||||
$skorAksesPasarSegmen = isset($petani) ? $petani->skorAksesPasar() : null;
|
||||
|
||||
$skorSegmenPetani = collect([
|
||||
$skorLahanTanaman,
|
||||
$skorPraktikBudidaya,
|
||||
$skorPendidikan,
|
||||
$skorSosialEkonomiSegmen,
|
||||
$skorAksesPasarSegmen,
|
||||
])->filter(fn ($value) => !is_null($value))->sum();
|
||||
|
||||
$skorSegmenPetani = $skorSegmenPetani > 0 ? round($skorSegmenPetani, 1) : null;
|
||||
$segmenPetani = is_null($skorSegmenPetani) ? null : match (true) {
|
||||
$skorSegmenPetani >= 4.0 => 'Penggerak',
|
||||
$skorSegmenPetani >= 3.0 => 'Perintis',
|
||||
$skorSegmenPetani >= 2.0 => 'Pemerhati',
|
||||
$skorSegmenPetani >= 1.0 => 'Pemula',
|
||||
default => 'Belum tersegmentasi',
|
||||
};
|
||||
|
||||
$lahanPetani = isset($petani) && \Illuminate\Support\Facades\Schema::hasTable('map_lahan')
|
||||
? $petani->lahans()->get()
|
||||
: collect();
|
||||
$totalLuasLahan = $petani ? $lahanPetani->where('komoditas_id', $petani->komoditas_id)->sum('luas_lahan') : 0;
|
||||
$totalLuasLahanHa = $totalLuasLahan / 10000;
|
||||
$totalLuasLahanHa = $petani ? $lahanPetani->where('komoditas_id', $petani->komoditas_id)->sum('luas_lahan') : 0;
|
||||
$lahanKomoditasUtamaIds = $petani
|
||||
? $lahanPetani->where('komoditas_id', $petani->komoditas_id)->pluck('id')
|
||||
: collect();
|
||||
@@ -100,7 +78,6 @@
|
||||
? \App\Models\Pertanian\Tanaman::whereIn('lahan_id', $lahanKomoditasUtamaIds)->count()
|
||||
: 0;
|
||||
$pendidikanLabel = isset($petani) ? $petani->pendidikan_terakhir?->label() : null;
|
||||
|
||||
@endphp
|
||||
|
||||
<div class="card mb-2 flex-grow-1">
|
||||
@@ -112,8 +89,8 @@
|
||||
<p class="mb-0 text-muted" style="font-size: 0.75rem;">Segmen Petani</p>
|
||||
<h5 class="mb-0">{{ $petani?->segmen() ?? '-' }}</h5>
|
||||
<p class="mb-0" style="font-size: 0.75rem;">
|
||||
@if(!is_null($petani?->skor))
|
||||
<span class="me-1">Skor {{ number_format($petani?->skor->skor_kesiapan_adopsi, 1) }}</span>
|
||||
@if(!is_null($petani?->skor?->skor_kesiapan_adopsi))
|
||||
<span class="me-1">Skor {{ number_format($petani->skor->skor_kesiapan_adopsi, 1, ',', '.') }}/5</span>
|
||||
@else
|
||||
<span class="me-1">Belum terisi</span>
|
||||
@endif
|
||||
@@ -139,7 +116,7 @@
|
||||
</h5>
|
||||
<p class="mb-0" style="font-size: 0.75rem;">
|
||||
@if(!is_null($skorLahanTanaman))
|
||||
<span class="me-1">{{ number_format($totalTanaman, 0, ',', '.') }} tanaman {{ $petani?->komoditas->nama }}</span>
|
||||
<span class="me-1">{{ number_format($totalTanaman, 0, ',', '.') }} tanaman {{ $petani?->komoditas?->nama ?? 'Belum diisi' }}</span>
|
||||
@else
|
||||
<span class="me-1">Belum terisi</span>
|
||||
@endif
|
||||
@@ -187,35 +164,19 @@
|
||||
<div class="d-flex justify-content-between align-items-center card-widget-1 border-end h-100 pe-3">
|
||||
@php
|
||||
$skorPraktik = isset($petani?->skor) ? $petani?->skor->skor_d2_sistem_praktik_budidaya : null;
|
||||
if (!is_null($skorPraktik)) {
|
||||
if ($skorPraktik >= 4) {
|
||||
$praktik = 'Menjalankan GAP (' . number_format($skorPraktik, 1, ',', '.') . ')';
|
||||
} else if ($skorPraktik >= 2) {
|
||||
$praktik = 'Menjalankan sebagian GAP (' . number_format($skorPraktik, 1, ',', '.') . ')';
|
||||
} else {
|
||||
$praktik = 'Belum menjalankan GAP (' . number_format($skorPraktik, 1, ',', '.') . ')';
|
||||
}
|
||||
}
|
||||
@endphp
|
||||
@php
|
||||
$produktivitas = isset($petani?->surveyProduktivitas) ? $petani?->surveyProduktivitas : null;
|
||||
if (!is_null($produktivitas)) {
|
||||
$produktivitas = $produktivitas->where('komoditas_id', $petani->komoditas_id);
|
||||
}
|
||||
$labelPraktik = match (true) {
|
||||
is_null($skorPraktik) => null,
|
||||
$skorPraktik >= 4 => 'Penuh GAP',
|
||||
$skorPraktik >= 2 => 'Sebagian GAP',
|
||||
default => 'Belum GAP',
|
||||
};
|
||||
@endphp
|
||||
<div>
|
||||
<p class="mb-0 text-muted" style="font-size: 0.75rem;">Praktik Budidaya</p>
|
||||
<h5 class="mb-0">
|
||||
@if($produktivitas && count($produktivitas) > 0)
|
||||
{{ number_format($produktivitas->sum('kuantitas'), 0, ',', '.') }}
|
||||
<small>{{ $produktivitas->first()->satuan == 'ha_tahun' ? 'Ha/Tahun' : 'Pohon/Tahun' }}</small>
|
||||
@else
|
||||
-
|
||||
@endif
|
||||
</h5>
|
||||
<h5 class="mb-0">{{ $labelPraktik ?? '-' }}</h5>
|
||||
<p class="mb-0" style="font-size: 0.75rem;">
|
||||
@if(!is_null($skorPraktik))
|
||||
<span class="me-1">{{ $praktik }}</span>
|
||||
<span class="me-1">Skor {{ number_format($skorPraktik, 1, ',', '.') }}/5</span>
|
||||
@else
|
||||
<span class="me-1">Belum terisi</span>
|
||||
@endif
|
||||
@@ -241,13 +202,15 @@
|
||||
$pendapatanKomoditas = isset($sosialEkonomi) ? $sosialEkonomi->pendapatanKomoditas : null;
|
||||
@endphp
|
||||
@php
|
||||
$rekeningValue = $sosialEkonomi?->status_rekening_tabungan?->value;
|
||||
$kreditValue = $sosialEkonomi?->pengalaman_kredit_kebun?->value;
|
||||
$ekonomi = null;
|
||||
if ($sosialEkonomi?->status_rekening_tabungan && $sosialEkonomi?->pengalaman_kredit_kebun) {
|
||||
if ($sosialEkonomi?->status_rekening_tabungan > 1 && $sosialEkonomi?->pengalaman_kredit_kebun > 3) {
|
||||
if ($rekeningValue && $kreditValue) {
|
||||
if ($rekeningValue > 1 && $kreditValue > 3) {
|
||||
$ekonomi = 'Memiliki rekening dan pengalaman kredit';
|
||||
} elseif ($sosialEkonomi?->status_rekening_tabungan > 1 && $sosialEkonomi?->pengalaman_kredit_kebun <= 3) {
|
||||
} elseif ($rekeningValue > 1 && $kreditValue <= 3) {
|
||||
$ekonomi = 'Memiliki rekening';
|
||||
} elseif ($sosialEkonomi?->status_rekening_tabungan == 1 && $sosialEkonomi?->pengalaman_kredit_kebun > 3) {
|
||||
} elseif ($rekeningValue == 1 && $kreditValue > 3) {
|
||||
$ekonomi = 'Memiliki pengalaman kredit';
|
||||
} else {
|
||||
$ekonomi = 'Tidak memiliki akses keuangan';
|
||||
@@ -257,9 +220,9 @@
|
||||
}
|
||||
@endphp
|
||||
<h5 class="mb-0">
|
||||
@if(!is_null($pendapatanKomoditas))
|
||||
{{ number_format($pendapatanKomoditas->sum('pendapatan'), 0, ',', '.') }}
|
||||
<small>pendapatan (Rp)</small>
|
||||
@if($pendapatanKomoditas && $pendapatanKomoditas->isNotEmpty())
|
||||
Rp {{ number_format($pendapatanKomoditas->sum('pendapatan'), 0, ',', '.') }}
|
||||
<small>/musim</small>
|
||||
@else
|
||||
-
|
||||
@endif
|
||||
@@ -303,30 +266,35 @@
|
||||
@endphp
|
||||
@php
|
||||
$pengalaman = null;
|
||||
if(!is_null($aksesPasar?->skor_pengalaman) && !is_null($aksesPasar->skor_kesadaran_premium)){
|
||||
if($aksesPasar->skor_pengalaman > 1){
|
||||
if ($aksesPasar->skor_kesadaran_premium == 5) {
|
||||
$pengalaman = 'Akses saluran lain dan pasar premium';
|
||||
} else {
|
||||
$pengalaman = 'Akses saluran lain';
|
||||
}
|
||||
} else {
|
||||
$pengalaman = 'Belum akses';
|
||||
if (!is_null($aksesPasar?->skor_pengalaman) && !is_null($aksesPasar->skor_kesadaran_premium)) {
|
||||
$pengalaman = match ($aksesPasar->skor_pengalaman) {
|
||||
1 => 'Belum pernah coba saluran lain',
|
||||
2 => 'Pernah, kini tidak ada akses',
|
||||
3 => 'Pernah, sempat berhenti',
|
||||
4 => 'Pernah, sesekali masih jual',
|
||||
5 => 'Pernah & masih punya akses',
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (!is_null($pengalaman) && $aksesPasar->skor_kesadaran_premium == 5) {
|
||||
$pengalaman .= ' · kenal pasar premium';
|
||||
}
|
||||
}
|
||||
@endphp
|
||||
<h5 class="mb-0">
|
||||
@if(!is_null($hargaJual))
|
||||
{{ number_format($hargaJual->harga_panen_terakhir, 0, ',', '.') }}
|
||||
<small>{{ $hargaJual->bentuk_produk }}</small>
|
||||
Rp {{ number_format($hargaJual->harga_panen_terakhir, 0, ',', '.') }}
|
||||
@else
|
||||
-
|
||||
@endif
|
||||
</h5>
|
||||
<p class="mb-0" style="font-size: 0.75rem;">
|
||||
<p class="mb-0 text-truncate" style="font-size: 0.75rem;" title="{{ $hargaJual?->bentuk_produk }} — {{ $pengalaman }}">
|
||||
@if(!is_null($hargaJual))
|
||||
<span class="me-1">{{ $hargaJual->bentuk_produk }}</span>
|
||||
@endif
|
||||
@if(!is_null($aksesPasar?->skor_pengalaman) && !is_null($aksesPasar->skor_kesadaran_premium))
|
||||
<span class="me-1">{{ $pengalaman }}</span>
|
||||
@else
|
||||
<span class="me-1">{{ $hargaJual ? '·' : '' }} {{ $pengalaman }}</span>
|
||||
@elseif(is_null($hargaJual))
|
||||
<span class="me-1">Belum terisi</span>
|
||||
@endif
|
||||
</p>
|
||||
|
||||
@@ -1,44 +1,292 @@
|
||||
@extends('layout.main')
|
||||
|
||||
@push('style')
|
||||
<style>
|
||||
#data-petani {
|
||||
scroll-margin-top: 7rem;
|
||||
}
|
||||
|
||||
.petani-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.indicator-filter-row {
|
||||
padding-bottom: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
}
|
||||
|
||||
.indicator-filter-bar {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.indicator-filter-line {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: .5rem;
|
||||
}
|
||||
|
||||
.indicator-filter-section-label {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-weight: 500;
|
||||
margin-right: .5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.indicator-filter-bar .form-select,
|
||||
.indicator-filter-bar .btn {
|
||||
min-height: 31px;
|
||||
}
|
||||
|
||||
.indicator-filter-bar .form-select {
|
||||
background-color: var(--bs-paper-bg);
|
||||
border-color: var(--bs-border-color);
|
||||
border-radius: var(--bs-border-radius);
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
.indicator-filter-bar .btn {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.indicator-filter-bar .form-select:focus {
|
||||
border-color: rgba(40, 167, 69, .55);
|
||||
box-shadow: 0 0 0 .2rem rgba(40, 167, 69, .12);
|
||||
}
|
||||
|
||||
.indicator-filter-static {
|
||||
align-items: center;
|
||||
background-color: var(--bs-paper-bg);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: var(--bs-border-radius);
|
||||
color: var(--bs-body-color);
|
||||
display: flex;
|
||||
font-size: .8125rem;
|
||||
min-height: 31px;
|
||||
padding: .25rem .75rem;
|
||||
}
|
||||
|
||||
.indicator-filter-bar .input-group-sm {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.indicator-filter-line {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.indicator-filter-section-label {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.indicator-filter-bar .btn {
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@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">
|
||||
<div class="container-fluid flex-grow-1 container-p-y">
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12 col-md-4">
|
||||
<x-summary-card
|
||||
title="Petani"
|
||||
count="{{ $petani->count() }}"
|
||||
subtitle="{{ $petani->where('jenis_kelamin', 1)->count() }} Laki-laki, {{ $petani->where('jenis_kelamin', 0)->count() }} Perempuan"
|
||||
count="{{ number_format($totalPetani ?? 0, 0, ',', '.') }}"
|
||||
subtitle="{{ number_format($totalPetaniLakiLaki ?? 0, 0, ',', '.') }} Laki-laki, {{ number_format($totalPetaniPerempuan ?? 0, 0, ',', '.') }} Perempuan"
|
||||
icon="user-check"
|
||||
color="primary"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-25">
|
||||
<div class="col-12 col-md-4">
|
||||
<x-summary-card
|
||||
title="Lahan & Tanaman"
|
||||
count="{{ number_format(($totalLuasLahan ?? 0) / 10000, 2, ',', '.') }} ha"
|
||||
subtitle="{{ $totalTanaman ?? 0 }} tanaman dari {{ $komoditas?->count() }} komoditas utama"
|
||||
count="{{ number_format($totalLuasLahan ?? 0, 2, ',', '.') }} ha"
|
||||
subtitle="{{ number_format($totalLahan ?? 0, 0, ',', '.') }} lahan, {{ number_format($totalTanaman ?? 0, 0, ',', '.') }} tanaman"
|
||||
icon="trees"
|
||||
color="success"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-25">
|
||||
<div class="col-12 col-md-4">
|
||||
<x-summary-card
|
||||
title="Segmentasi"
|
||||
count="{{ $skor->where('segmen', 'Penggerak')->count() }} Penggerak"
|
||||
subtitle="{{ $skor->where('segmen', 'Perintis')->count() }} Perintis, {{ $skor->where('segmen', 'Pemerhati')->count() }} Pemerhati, {{ $skor->where('segmen', 'Pemula')->count() }} Pemula"
|
||||
count="{{ number_format((int) ($skor->firstWhere('segmen', 'Penggerak')->total ?? 0), 0, ',', '.') }} Penggerak"
|
||||
subtitle="{{ number_format((int) ($skor->firstWhere('segmen', 'Perintis')->total ?? 0), 0, ',', '.') }} Perintis, {{ number_format((int) ($skor->firstWhere('segmen', 'Pemerhati')->total ?? 0), 0, ',', '.') }} Pemerhati, {{ number_format((int) ($skor->firstWhere('segmen', 'Pemula')->total ?? 0), 0, ',', '.') }} Pemula"
|
||||
icon="star"
|
||||
color="info"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="row g-6 indicator-filter-row">
|
||||
<div class="col-12">
|
||||
<div class="card card-border-shadow-primary">
|
||||
<div class="card-body py-3">
|
||||
<form method="GET" action="{{ route('petani.index') }}" id="petani-filter-form">
|
||||
<input type="hidden" name="per_page" value="{{ request('per_page', 10) }}">
|
||||
|
||||
<div class="indicator-filter-bar" id="indicator-filter-bar" aria-label="Filter data petani">
|
||||
<div class="indicator-filter-line">
|
||||
<div class="indicator-filter-section-label">
|
||||
<i class="icon-base ti tabler-map-2 me-2"></i>
|
||||
<span>Wilayah</span>
|
||||
</div>
|
||||
|
||||
<div class="filter-item">
|
||||
<input
|
||||
type="hidden"
|
||||
name="kabupaten_kota_id"
|
||||
value="{{ $filters['kabupaten_kota_id'] }}"
|
||||
>
|
||||
<div
|
||||
id="filter-kabupaten"
|
||||
class="form-control form-control-sm indicator-filter-static"
|
||||
data-filter-key="kabupaten"
|
||||
data-filter-value="{{ $filters['kabupaten_kota_id'] }}"
|
||||
aria-label="Filter Kabupaten"
|
||||
role="text"
|
||||
>
|
||||
{{ preg_replace('/^(kabupaten|kota)\s+/i', '', $selectedKabupaten?->nama ?? 'Ngada') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-item">
|
||||
<select
|
||||
name="kecamatan_id"
|
||||
id="filter-kecamatan"
|
||||
class="form-select form-select-sm indicator-filter-select"
|
||||
data-filter-key="kecamatan"
|
||||
aria-label="Filter Kecamatan"
|
||||
@disabled(empty($filters['kabupaten_kota_id']))
|
||||
>
|
||||
<option value="">Semua Kecamatan</option>
|
||||
@foreach($kecamatanOptions as $option)
|
||||
<option value="{{ $option->id }}" @selected((string) $filters['kecamatan_id'] === (string) $option->id)>
|
||||
{{ $option->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-item">
|
||||
<select
|
||||
name="desa_kelurahan_id"
|
||||
id="filter-desa"
|
||||
class="form-select form-select-sm indicator-filter-select"
|
||||
data-filter-key="desa"
|
||||
aria-label="Filter Desa"
|
||||
@disabled(empty($filters['kecamatan_id']))
|
||||
>
|
||||
<option value="">Semua Desa</option>
|
||||
@foreach($desaOptions as $option)
|
||||
<option value="{{ $option->id }}" @selected((string) $filters['desa_kelurahan_id'] === (string) $option->id)>
|
||||
{{ $option->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-3">
|
||||
|
||||
<div class="indicator-filter-line">
|
||||
<div class="indicator-filter-section-label">
|
||||
<i class="icon-base ti tabler-adjustments-horizontal me-2"></i>
|
||||
<span>Data</span>
|
||||
</div>
|
||||
|
||||
<div class="filter-item">
|
||||
<select
|
||||
name="komoditas_id"
|
||||
id="filter-komoditas"
|
||||
class="form-select form-select-sm indicator-filter-select"
|
||||
data-filter-key="komoditas"
|
||||
aria-label="Filter Komoditas"
|
||||
>
|
||||
<option value="">Semua Komoditas</option>
|
||||
@foreach($komoditasOptions as $option)
|
||||
<option value="{{ $option->id }}" @selected((string) $filters['komoditas_id'] === (string) $option->id)>
|
||||
{{ $option->nama }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-item">
|
||||
<select
|
||||
name="sort_by"
|
||||
id="filter-sort-by"
|
||||
class="form-select form-select-sm indicator-filter-select"
|
||||
data-filter-key="sort_by"
|
||||
aria-label="Urutkan data petani"
|
||||
>
|
||||
<option value="">Urut Terbaru</option>
|
||||
<option value="nama_asc" @selected($filters['sort_by'] === 'nama_asc')>Nama A-Z</option>
|
||||
<option value="nama_desc" @selected($filters['sort_by'] === 'nama_desc')>Nama Z-A</option>
|
||||
<option value="luas_lahan_desc" @selected($filters['sort_by'] === 'luas_lahan_desc')>Luas Lahan Terbesar</option>
|
||||
<option value="luas_lahan_asc" @selected($filters['sort_by'] === 'luas_lahan_asc')>Luas Lahan Terkecil</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-item">
|
||||
<div class="input-group input-group-sm">
|
||||
<input
|
||||
type="text"
|
||||
name="search"
|
||||
class="form-control"
|
||||
placeholder="Cari nama / KTP..."
|
||||
value="{{ $filters['search'] }}"
|
||||
aria-label="Cari nama atau KTP"
|
||||
>
|
||||
<button type="submit" class="btn btn-outline-secondary" aria-label="Cari data petani">
|
||||
<i class="icon-base ti tabler-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-outline-secondary btn-sm"
|
||||
aria-label="Terapkan filter data petani"
|
||||
>
|
||||
<i class="icon-base ti tabler-filter me-1"></i>
|
||||
Filter
|
||||
</button>
|
||||
|
||||
<a
|
||||
href="{{ route('petani.index') }}"
|
||||
class="btn btn-outline-secondary btn-sm"
|
||||
aria-label="Reset filter data petani"
|
||||
>
|
||||
<i class="icon-base ti tabler-refresh me-1"></i>
|
||||
Reset
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" id="data-petani">
|
||||
<div class="card-header border-bottom">
|
||||
<h5 class="card-title mb-0">Data Petani</h5>
|
||||
</div>
|
||||
|
||||
<div class="card-body border-bottom pt-4">
|
||||
<form method="GET" action="{{ route('petani.index') }}" class="d-flex justify-content-between align-items-center row gap-3 gap-md-0">
|
||||
<form method="GET" action="{{ route('petani.index') }}" class="row g-3 align-items-end">
|
||||
<input type="hidden" name="komoditas_id" value="{{ $filters['komoditas_id'] }}">
|
||||
<input type="hidden" name="kabupaten_kota_id" value="{{ $filters['kabupaten_kota_id'] }}">
|
||||
<input type="hidden" name="kecamatan_id" value="{{ $filters['kecamatan_id'] }}">
|
||||
<input type="hidden" name="desa_kelurahan_id" value="{{ $filters['desa_kelurahan_id'] }}">
|
||||
<input type="hidden" name="search" value="{{ $filters['search'] }}">
|
||||
<input type="hidden" name="sort_by" value="{{ $filters['sort_by'] }}">
|
||||
|
||||
<div class="col-md-auto">
|
||||
<div class="d-flex align-items-center">
|
||||
<label class="me-2 text-nowrap">Show</label>
|
||||
@@ -49,11 +297,7 @@
|
||||
<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>
|
||||
<div class="col-md-auto ms-md-auto">
|
||||
<a href="{{ route('petani.create') }}" class="btn btn-sm btn-primary">
|
||||
<i class="ti tabler-plus me-1"></i> Tambah
|
||||
</a>
|
||||
@@ -67,23 +311,39 @@
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>Nama</th>
|
||||
<th>Jenis Kelamin</th>
|
||||
<th>Usia</th>
|
||||
<th>Desa/Kelurahan</th>
|
||||
<th>Komoditas Utama</th>
|
||||
<th>Luas Lahan</th>
|
||||
@if($showFasilitatorColumn)
|
||||
<th>Fasilitator</th>
|
||||
@endif
|
||||
<th>Segmentasi</th>
|
||||
<th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($petani as $index => $item)
|
||||
<tr>
|
||||
<tr class="petani-row">
|
||||
<td>{{ $loop->iteration }}</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->umur }} tahun</span></td>
|
||||
<td>
|
||||
<a href="{{ route('petani.edit', $item->id) }}" class="text-heading d-block">{{ $item->nama }}</a>
|
||||
<small class="text-muted">
|
||||
{{ $item->jenis_kelamin ? 'Laki-laki' : 'Perempuan' }}
|
||||
@if(! is_null($item->umur))
|
||||
, {{ $item->umur }} tahun
|
||||
@endif
|
||||
</small>
|
||||
</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->komoditas?->nama }}</td>
|
||||
<td>
|
||||
<span class="text-muted">
|
||||
{{ number_format((float) ($item->total_luas_lahan ?? 0), 2, ',', '.') }} ha
|
||||
</span>
|
||||
</td>
|
||||
@if($showFasilitatorColumn)
|
||||
<td><span class="text-muted">{{ $item->creator?->name ?? '-' }}</span></td>
|
||||
@endif
|
||||
<td>
|
||||
@if($item->segmen() == 'Penggerak')
|
||||
<span class="badge bg-label-success">Penggerak</span>
|
||||
@@ -104,7 +364,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="6" class="text-center py-4">Tidak ada data ditemukan.</td></tr>
|
||||
<tr><td colspan="{{ $showFasilitatorColumn ? 9 : 8 }}" class="text-center py-4">Tidak ada data ditemukan.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -122,6 +382,63 @@
|
||||
const csrfToken = "{{ csrf_token() }}";
|
||||
|
||||
$(document).ready(function () {
|
||||
const kecamatanUrlTemplate = '{{ route("kecamatan.bykabupatenkota", ":id") }}';
|
||||
const desaUrlTemplate = '{{ route("desakelurahan.bykecamatan", ":id") }}';
|
||||
const $kabupatenFilter = $('#filter-kabupaten');
|
||||
const $kecamatanFilter = $('#filter-kecamatan');
|
||||
const $desaFilter = $('#filter-desa');
|
||||
|
||||
function resetSelect($select, placeholder, disabled = true) {
|
||||
$select.empty().append(`<option value="">${placeholder}</option>`);
|
||||
$select.prop('disabled', disabled);
|
||||
}
|
||||
|
||||
function fillSelect($select, data, selectedValue, placeholder) {
|
||||
resetSelect($select, placeholder, false);
|
||||
|
||||
data.forEach(function (item) {
|
||||
const option = new Option(item.nama, item.id, false, String(selectedValue || '') === String(item.id));
|
||||
$select.append(option);
|
||||
});
|
||||
}
|
||||
|
||||
$kabupatenFilter.on('change', function () {
|
||||
const kabupatenId = $(this).val();
|
||||
|
||||
resetSelect($kecamatanFilter, 'Semua Kecamatan');
|
||||
resetSelect($desaFilter, 'Semua Desa');
|
||||
|
||||
if (!kabupatenId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.get(kecamatanUrlTemplate.replace(':id', kabupatenId), function (data) {
|
||||
fillSelect($kecamatanFilter, data, '', 'Semua Kecamatan');
|
||||
});
|
||||
});
|
||||
|
||||
$kecamatanFilter.on('change', function () {
|
||||
const kecamatanId = $(this).val();
|
||||
|
||||
resetSelect($desaFilter, 'Semua Desa');
|
||||
|
||||
if (!kecamatanId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.get(desaUrlTemplate.replace(':id', kecamatanId), function (data) {
|
||||
fillSelect($desaFilter, data, '', 'Semua Desa');
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.petani-row', function (event) {
|
||||
if ($(event.target).closest('a, button, input, select, textarea, label').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = this.querySelector('.edit-record').href;
|
||||
});
|
||||
|
||||
$(document).on('click', '.delete-record', function () {
|
||||
const id = $(this).data('id');
|
||||
const name = $(this).data('nama');
|
||||
|
||||
@@ -517,7 +517,7 @@
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 text-end">
|
||||
<a href="{{ route('petani.index') }}" class="btn btn-secondary me-2 wave-effect wave-light">Kembali</a>
|
||||
<a href="{{ route('petani.index') }}#data-petani" 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 Lahan</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -406,8 +406,8 @@
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 text-end">
|
||||
<a href="{{ route('petani.index') }}" class="btn btn-secondary me-2 wave-effect wave-light">Kembali</a>
|
||||
<button id="delete-lahan"type="button" class="btn btn-danger data-submit wave-effect wave-light"><i class="ti tabler-trash me-2"></i> Hapus Lahan</button>
|
||||
<a href="{{ route('petani.index') }}#data-petani" 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 Lahan</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-primary">Simpan Perubahan</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
|
||||
@@ -167,10 +167,13 @@
|
||||
Tambah Pelatihan
|
||||
</button>
|
||||
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan Riwayat Pelatihan
|
||||
</button>
|
||||
<div class="d-flex flex-wrap justify-content-end gap-2">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan Riwayat Pelatihan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -347,10 +350,13 @@
|
||||
Tambah Sertifikasi
|
||||
</button>
|
||||
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan Sertifikasi Keahlian
|
||||
</button>
|
||||
<div class="d-flex flex-wrap justify-content-end gap-2">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan Sertifikasi Keahlian
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -499,6 +499,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
Simpan
|
||||
</button>
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
@@ -1039,6 +1040,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
@@ -1352,6 +1354,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
@@ -1542,6 +1545,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
@@ -1678,9 +1682,12 @@
|
||||
Tambah Komoditas
|
||||
</button>
|
||||
|
||||
<button type="submit" class="btn btn-success">
|
||||
Simpan Produktivitas
|
||||
</button>
|
||||
<div class="d-flex flex-wrap justify-content-end gap-2">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
Simpan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -1725,6 +1732,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
|
||||
@@ -399,6 +399,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
@@ -567,6 +568,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
@@ -696,6 +698,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
@@ -915,6 +918,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
@@ -1043,6 +1047,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
|
||||
@@ -398,6 +398,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
@@ -566,6 +567,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
@@ -694,6 +696,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
@@ -912,6 +915,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
@@ -1039,6 +1043,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-secondary me-2">Kembali</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i>
|
||||
Simpan
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}" class="btn btn-label-secondary me-2">Kembali</a>
|
||||
<a href="{{ route('petani.index') }}#data-petani" class="btn btn-label-secondary me-2">Kembali</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,37 +1,51 @@
|
||||
{{--
|
||||
Partial notif per-card. Dipakai di dalam tiap <div class="card"> supaya notif sukses/gagal
|
||||
muncul tepat di atas card yang barusan disubmit, bukan di paling atas halaman.
|
||||
@php
|
||||
$notificationTypes = [
|
||||
'success' => 'success',
|
||||
'error' => 'danger',
|
||||
'warning' => 'warning',
|
||||
'info' => 'info',
|
||||
'status' => 'info',
|
||||
'message' => 'info',
|
||||
];
|
||||
$hasNotifications = $errors->any() || collect(array_keys($notificationTypes))->contains(fn ($key) => session()->has($key));
|
||||
@endphp
|
||||
|
||||
Params:
|
||||
- $section (string) kunci section, harus sama dengan yang dipakai controller di redirectToSection().
|
||||
- $errorFields (array, optional) daftar nama field/wildcard (mis. 'aset.*') milik card ini,
|
||||
dipakai buat nentuin apakah error validasi yang ada punya card ini.
|
||||
--}}
|
||||
@if(session('success') && session('success_section') === $section)
|
||||
<div class="alert alert-success alert-dismissible" role="alert">
|
||||
{{ session('success') }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
@endif
|
||||
@if($hasNotifications)
|
||||
<div id="kelompok-tani-notifications">
|
||||
@foreach($notificationTypes as $key => $type)
|
||||
@if(session()->has($key))
|
||||
<div class="alert alert-{{ $type }} alert-dismissible" role="alert">
|
||||
{{ session($key) }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Tutup"></button>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
@if(session('error') && session('error_section') === $section)
|
||||
<div class="alert alert-danger alert-dismissible" role="alert">
|
||||
{{ session('error') }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(!empty($errorFields) && $errors->has($errorFields))
|
||||
<div class="alert alert-danger alert-dismissible" role="alert">
|
||||
<ul class="mb-0">
|
||||
@foreach($errorFields as $field)
|
||||
@foreach($errors->get($field) as $messages)
|
||||
@foreach((array) $messages as $message)
|
||||
<li>{{ $message }}</li>
|
||||
@if($errors->any())
|
||||
<div class="alert alert-danger alert-dismissible" role="alert">
|
||||
<ul class="mb-0">
|
||||
@foreach($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
@endforeach
|
||||
@endforeach
|
||||
</ul>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</ul>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Tutup"></button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
window.addEventListener('load', function () {
|
||||
const hasErrors = @json($errors->any() || session()->has('error'));
|
||||
if (!hasErrors && window.location.hash.startsWith('#section-')) {
|
||||
const target = document.getElementById(window.location.hash.substring(1));
|
||||
if (target && target.closest('.kelompok-tani-page') && target.getClientRects().length) {
|
||||
target.scrollIntoView({ block: 'start' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
document.getElementById('kelompok-tani-notifications')?.scrollIntoView({ block: 'start' });
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@endif
|
||||
|
||||
@@ -1,31 +1,21 @@
|
||||
@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
|
||||
|
||||
<div class="container-xxl flex-grow-1 container-p-y kelompok-tani-page">
|
||||
@include('produktivitas.survey-kelompok-tani.header', ['kelompokTani' => null])
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card mb-6">
|
||||
<div class="card-body pt-6">
|
||||
<form id="addKelompokTaniForm" action="{{ route('kelompok-tani.store') }}" method="POST">
|
||||
<form novalidate id="addKelompokTaniForm" action="{{ route('kelompok-tani.store') }}" method="POST">
|
||||
@csrf
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4">Data Responden</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4 form-control-validation">
|
||||
<label class="form-label" for="add-kt-nama-responden">Nama Responden<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control @error('nama_responden') is-invalid @enderror" id="add-kt-nama-responden" name="nama_responden"
|
||||
<input type="text" class="form-control @error('nama_responden') is-invalid @enderror" id="add-kt-nama-responden" name="nama_responden" required
|
||||
placeholder="Cth: John Doe" value="{{ old('nama_responden') }}" />
|
||||
@error('nama_responden')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
@@ -47,7 +37,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label" for="add-kt-nama-kelompok">Nama Kelompok Tani<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control @error('nama_kelompok') is-invalid @enderror" id="add-kt-nama-kelompok" name="nama_kelompok"
|
||||
<input type="text" class="form-control @error('nama_kelompok') is-invalid @enderror" id="add-kt-nama-kelompok" name="nama_kelompok" required
|
||||
placeholder="Cth: Kelompok Tani Maju Bersama" value="{{ old('nama_kelompok') }}" />
|
||||
@error('nama_kelompok')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
@@ -68,38 +58,50 @@
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="add-kt-provinsi">Provinsi<span class="text-danger">*</span></label>
|
||||
<select class="form-control @error('provinsi_id') is-invalid @enderror" id="add-kt-provinsi" name="provinsi_id">
|
||||
<select class="form-select @error('provinsi_id') is-invalid @enderror" id="add-kt-provinsi" name="provinsi_id" required>
|
||||
<option value="">Pilih Provinsi</option>
|
||||
@foreach($provinsi as $p)
|
||||
<option value="{{ $p->id }}" {{ old('provinsi_id') == $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="add-kt-kabupaten">Kabupaten/Kota<span class="text-danger">*</span></label>
|
||||
<select class="form-control @error('kabupaten_id') is-invalid @enderror" id="add-kt-kabupaten" name="kabupaten_id" disabled>
|
||||
<select class="form-select @error('kabupaten_id') is-invalid @enderror" id="add-kt-kabupaten" name="kabupaten_id" required @disabled(!old('provinsi_id'))>
|
||||
<option value="">Pilih Kabupaten/Kota</option>
|
||||
@foreach($kabupatenOptions ?? [] as $option)
|
||||
<option value="{{ $option->id }}" @selected(old('kabupaten_id') == $option->id)>{{ $option->nama }}</option>
|
||||
@endforeach
|
||||
</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="add-kt-kecamatan">Kecamatan<span class="text-danger">*</span></label>
|
||||
<select class="form-control @error('kecamatan_id') is-invalid @enderror" id="add-kt-kecamatan" name="kecamatan_id" disabled>
|
||||
<select class="form-select @error('kecamatan_id') is-invalid @enderror" id="add-kt-kecamatan" name="kecamatan_id" required @disabled(!old('kabupaten_id'))>
|
||||
<option value="">Pilih Kecamatan</option>
|
||||
@foreach($kecamatanOptions ?? [] as $option)
|
||||
<option value="{{ $option->id }}" @selected(old('kecamatan_id') == $option->id)>{{ $option->nama }}</option>
|
||||
@endforeach
|
||||
</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="add-kt-desa">Desa/Kelurahan<span class="text-danger">*</span></label>
|
||||
<select class="form-control @error('desa_kelurahan_id') is-invalid @enderror" id="add-kt-desa" name="desa_kelurahan_id" disabled>
|
||||
<select class="form-select @error('desa_kelurahan_id') is-invalid @enderror" id="add-kt-desa" name="desa_kelurahan_id" required @disabled(!old('kecamatan_id'))>
|
||||
<option value="">Pilih Desa/Kelurahan</option>
|
||||
@foreach($desaOptions ?? [] as $option)
|
||||
<option value="{{ $option->id }}" @selected(old('desa_kelurahan_id') == $option->id)>{{ $option->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('desa_kelurahan_id')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4 form-control-validation">
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label" for="add-kt-jenis-kelompok">Terdaftar/Dikenal Sebagai<span class="text-danger">*</span></label>
|
||||
<select class="form-control @error('jenis_kelompok') is-invalid @enderror" id="add-kt-jenis-kelompok" name="jenis_kelompok">
|
||||
<select class="form-select @error('jenis_kelompok') is-invalid @enderror" id="add-kt-jenis-kelompok" name="jenis_kelompok" required>
|
||||
<option value="">Pilih Jenis Kelompok</option>
|
||||
<option value="Kelompok Tani (Poktan)" {{ old('jenis_kelompok') == 'Kelompok Tani (Poktan)' ? 'selected' : '' }}>Kelompok Tani (Poktan)</option>
|
||||
<option value="Kelompok Wanita Tani (KWT)" {{ old('jenis_kelompok') == 'Kelompok Wanita Tani (KWT)' ? 'selected' : '' }}>Kelompok Wanita Tani (KWT)</option>
|
||||
@@ -111,7 +113,7 @@
|
||||
@error('jenis_kelompok')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-4 mb-4 form-control-validation" id="add-kt-jenis-kelompok-lainnya-wrapper" style="display: none;">
|
||||
<label class="form-label" for="add-kt-jenis-kelompok-lainnya">Sebutkan Jenis Kelompok</label>
|
||||
<label class="form-label" for="add-kt-jenis-kelompok-lainnya">Sebutkan Jenis Kelompok<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control @error('jenis_kelompok_lainnya') is-invalid @enderror" id="add-kt-jenis-kelompok-lainnya" name="jenis_kelompok_lainnya"
|
||||
value="{{ old('jenis_kelompok_lainnya') }}" />
|
||||
@error('jenis_kelompok_lainnya')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
@@ -136,9 +138,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@include('produktivitas.survey-kelompok-tani.pengurus-anggota', ['bagianKeanggotaan' => 'ringkasan', 'kelompokTani' => null])
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('kelompok-tani.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 & Lanjutkan</button>
|
||||
<div class="d-inline-flex flex-wrap justify-content-end gap-2">
|
||||
<a href="{{ route('kelompok-tani.index') }}" class="btn btn-secondary wave-effect wave-light">
|
||||
Kembali
|
||||
</a>
|
||||
<button type="submit" class="btn btn-success data-submit wave-effect wave-light">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm" aria-hidden="true"></i> Simpan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -157,6 +167,7 @@
|
||||
|
||||
function toggleJenisKelompokFields() {
|
||||
var jenisKelompok = $('#add-kt-jenis-kelompok').val();
|
||||
$('#add-kt-jenis-kelompok-lainnya').prop('required', jenisKelompok === 'Lainnya').prop('disabled', jenisKelompok !== 'Lainnya');
|
||||
|
||||
if (jenisKelompok === 'Lainnya') {
|
||||
$('#add-kt-jenis-kelompok-lainnya-wrapper').show();
|
||||
@@ -236,52 +247,6 @@
|
||||
});
|
||||
|
||||
$('#add-kt-desa').select2();
|
||||
|
||||
$('#addKelompokTaniForm').validate({
|
||||
rules: {
|
||||
nama_responden: { required: true },
|
||||
nama_kelompok: { required: true },
|
||||
provinsi_id: { required: true },
|
||||
kabupaten_id: { required: true },
|
||||
kecamatan_id: { required: true },
|
||||
desa_kelurahan_id: { required: true },
|
||||
jenis_kelompok: { required: true },
|
||||
jenis_kelompok_lainnya: {
|
||||
required: function () {
|
||||
return $('#add-kt-jenis-kelompok').val() === 'Lainnya';
|
||||
}
|
||||
},
|
||||
tahun_berdiri: { min: 1900, max: new Date().getFullYear() }
|
||||
},
|
||||
messages: {
|
||||
nama_responden: { required: 'Nama responden wajib diisi' },
|
||||
nama_kelompok: { required: 'Nama kelompok wajib diisi' },
|
||||
provinsi_id: { required: 'Provinsi wajib diisi' },
|
||||
kabupaten_id: { required: 'Kabupaten/Kota wajib diisi' },
|
||||
kecamatan_id: { required: 'Kecamatan wajib diisi' },
|
||||
desa_kelurahan_id: { required: 'Desa/Kelurahan wajib diisi' },
|
||||
jenis_kelompok: { required: 'Jenis kelompok wajib dipilih' },
|
||||
jenis_kelompok_lainnya: { required: 'Sebutkan jenis kelompoknya' }
|
||||
},
|
||||
errorElement: 'div',
|
||||
errorPlacement: function (error, element) {
|
||||
error.addClass('invalid-feedback');
|
||||
if (element.hasClass('select2-hidden-accessible')) {
|
||||
element.closest('.form-control-validation').append(error);
|
||||
} else {
|
||||
element.closest('.mb-4, .form-control-validation').append(error);
|
||||
}
|
||||
},
|
||||
highlight: function (element) {
|
||||
$(element).addClass('is-invalid');
|
||||
},
|
||||
unhighlight: function (element) {
|
||||
$(element).removeClass('is-invalid');
|
||||
},
|
||||
submitHandler: function (form) {
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@extends('layout.main')
|
||||
|
||||
@section('content')
|
||||
<div class="container-xxl flex-grow-1 container-p-y">
|
||||
<div class="container-xxl flex-grow-1 container-p-y kelompok-tani-page">
|
||||
@include('produktivitas.survey-kelompok-tani.header', ['kelompokTani' => $kelompokTani])
|
||||
|
||||
@php
|
||||
@@ -41,18 +41,39 @@
|
||||
|
||||
<div class="card mb-6" id="section-aktivitas-fungsi">
|
||||
<div class="card-body pt-6">
|
||||
@include('produktivitas.survey-kelompok-tani._alert', [
|
||||
'section' => 'aktivitas-fungsi',
|
||||
'errorFields' => ['kegiatan_terlaksana.*', 'kegiatan_terlaksana_lainnya', 'estimasi_panen_minimal', 'estimasi_panen_maksimal', 'satuan_hasil_panen', 'tindakan_hasil_panen.*', 'fungsi_kelompok_bagi_anggota.*', 'fungsi_kelompok_lainnya'],
|
||||
])
|
||||
|
||||
<form method="POST" action="{{ route('kelompok-tani.update', $kelompokTani->id) }}">
|
||||
<form novalidate method="POST" action="{{ route('kelompok-tani.update', $kelompokTani->id) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<input type="hidden" name="section" value="aktivitas-fungsi">
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4">Kegiatan Konkret yang Sudah Berjalan</h5>
|
||||
<p class="text-muted" style="font-size: 0.8rem; margin-top: -0.75rem;">
|
||||
<h5 class="pb-4 border-bottom mb-4">Frekuensi Pertemuan Rutin</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label" for="af-frekuensi-pertemuan">Frekuensi Pertemuan</label>
|
||||
<select class="form-select @error('frekuensi_pertemuan') is-invalid @enderror" id="af-frekuensi-pertemuan" name="frekuensi_pertemuan">
|
||||
<option value="">Pilih Frekuensi Pertemuan</option>
|
||||
@foreach([
|
||||
'Tidak pernah/sudah tidak aktif',
|
||||
'Tidak rutin/tidak terjadwal',
|
||||
'1x per 2-3 bulan',
|
||||
'1x per bulan',
|
||||
'Lebih dari 1x per bulan',
|
||||
] as $option)
|
||||
<option value="{{ $option }}" {{ old('frekuensi_pertemuan', $kelompokTani->frekuensi_pertemuan) == $option ? 'selected' : '' }}>{{ $option }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('frekuensi_pertemuan')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label" for="af-tanggal-pertemuan-terakhir">Tanggal Pertemuan Terakhir</label>
|
||||
<input type="text" class="form-control @error('tanggal_pertemuan_terakhir') is-invalid @enderror" id="af-tanggal-pertemuan-terakhir" name="tanggal_pertemuan_terakhir"
|
||||
value="{{ old('tanggal_pertemuan_terakhir', $kelompokTani->tanggal_pertemuan_terakhir?->format('d-m-Y')) }}" />
|
||||
@error('tanggal_pertemuan_terakhir')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4 mt-4">Kegiatan Konkret yang Sudah Berjalan</h5>
|
||||
<p class="text-muted small kelompok-tani-help">
|
||||
Kegiatan yang sudah benar-benar dilaksanakan, bukan rencana. Boleh pilih lebih dari satu.
|
||||
</p>
|
||||
<div class="row mb-2">
|
||||
@@ -99,7 +120,7 @@
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="af-satuan-panen">Satuan</label>
|
||||
<select class="form-control @error('satuan_hasil_panen') is-invalid @enderror" id="af-satuan-panen" name="satuan_hasil_panen">
|
||||
<select class="form-select @error('satuan_hasil_panen') is-invalid @enderror" id="af-satuan-panen" name="satuan_hasil_panen">
|
||||
<option value="">Pilih Satuan</option>
|
||||
<option value="kg" {{ old('satuan_hasil_panen', $kelompokTani->satuan_hasil_panen) == 'kg' ? 'selected' : '' }}>Kg</option>
|
||||
<option value="ton" {{ old('satuan_hasil_panen', $kelompokTani->satuan_hasil_panen) == 'ton' ? 'selected' : '' }}>Ton</option>
|
||||
@@ -123,7 +144,7 @@
|
||||
</div>
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4 mt-4">Peran dan Fungsi Kelompok</h5>
|
||||
<p class="text-muted" style="font-size: 0.8rem; margin-top: -0.75rem;">
|
||||
<p class="text-muted small kelompok-tani-help">
|
||||
Fungsi yang melekat secara struktural pada kelompok ini, terlepas dari seberapa sering kegiatannya berjalan.
|
||||
</p>
|
||||
<div class="row mb-2">
|
||||
@@ -153,13 +174,20 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<button type="submit" class="btn btn-success wave-effect wave-light">
|
||||
<i class="ti tabler-device-floppy me-2"></i> Simpan Aktivitas & Fungsi
|
||||
</button>
|
||||
<div class="d-inline-flex flex-wrap justify-content-end gap-2">
|
||||
<a href="{{ route('kelompok-tani.index') }}" class="btn btn-secondary wave-effect wave-light">
|
||||
Kembali
|
||||
</a>
|
||||
<button type="submit" class="btn btn-success wave-effect wave-light">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm" aria-hidden="true"></i> Simpan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@include('produktivitas.survey-kelompok-tani.aset-keuangan')
|
||||
@include('produktivitas.survey-kelompok-tani.bantuan-penilaian')
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@@ -167,6 +195,10 @@
|
||||
<script>
|
||||
'use strict';
|
||||
$(document).ready(function () {
|
||||
$('#af-frekuensi-pertemuan').select2();
|
||||
$('#af-tanggal-pertemuan-terakhir').flatpickr({
|
||||
dateFormat: 'd-m-Y'
|
||||
});
|
||||
|
||||
function toggleLainnya(checkboxSelector, wrapperSelector, textInputSelector) {
|
||||
var $checkbox = $(checkboxSelector);
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
@extends('layout.main')
|
||||
|
||||
@section('content')
|
||||
<div class="container-xxl flex-grow-1 container-p-y">
|
||||
@include('produktivitas.survey-kelompok-tani.header', ['kelompokTani' => $kelompokTani])
|
||||
|
||||
@php
|
||||
$jenisAsetBaku = \App\Http\Controllers\Web\Produktivitas\AsetKelompokTaniController::JENIS_ASET_BAKU;
|
||||
$asetTersimpan = $kelompokTani->aset->keyBy('jenis_aset');
|
||||
@@ -11,19 +5,18 @@
|
||||
|
||||
$statusPengelolaanOptions = ['Swadaya', 'Hibah', 'Pinjaman', 'Sewa'];
|
||||
$kondisiOptions = ['Berfungsi', 'Perlu perbaikan/evaluasi', 'Rusak/tidak bisa dipakai', 'Mangkrak'];
|
||||
$asetLainnyaBaru = old('aset_lainnya') ?: [[]];
|
||||
$indexKas = array_search('Kas/simpanan kelompok', $jenisAsetBaku, true);
|
||||
$memilikiKas = (bool) old("aset.$indexKas.status_aset", $asetTersimpan->get('Kas/simpanan kelompok')?->status_aset ?? false);
|
||||
@endphp
|
||||
|
||||
{{-- ASET PRODUKTIF --}}
|
||||
<div class="card mb-6" id="section-aset-baku">
|
||||
<div class="card-body pt-6">
|
||||
@include('produktivitas.survey-kelompok-tani._alert', [
|
||||
'section' => 'aset-baku',
|
||||
'errorFields' => ['aset.*'],
|
||||
])
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4">Aset Produktif Milik Kelompok</h5>
|
||||
<p class="text-muted small"><span class="text-danger">*</span> Wajib diisi untuk aset yang dipilih Ada.</p>
|
||||
|
||||
<form method="POST" action="{{ route('kelompok-tani.aset-keuangan.update', $kelompokTani->id) }}">
|
||||
<form novalidate id="aset-produktif-form" method="POST" action="{{ route('kelompok-tani.aset-keuangan.update', $kelompokTani->id) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
@@ -33,43 +26,56 @@
|
||||
<tr>
|
||||
<th style="min-width: 180px;">Jenis Aset</th>
|
||||
<th style="width: 140px;">Ada/Tidak</th>
|
||||
<th style="min-width: 180px;">Status Pengelolaan</th>
|
||||
<th style="min-width: 200px;">Kondisi</th>
|
||||
<th style="min-width: 180px;">Status Pengelolaan <span class="text-danger" aria-hidden="true">*</span></th>
|
||||
<th style="min-width: 200px;">Kondisi <span class="text-danger" aria-hidden="true">*</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($jenisAsetBaku as $index => $jenis)
|
||||
@php $row = $asetTersimpan->get($jenis); @endphp
|
||||
<tr>
|
||||
@php
|
||||
$row = $asetTersimpan->get($jenis);
|
||||
$ada = (bool) old("aset.$index.status_aset", $row?->status_aset ?? false);
|
||||
$statusPengelolaan = old("aset.$index.status_pengelolaan_aset", $row?->status_pengelolaan_aset);
|
||||
$kondisi = old("aset.$index.kondisi_aset", $row?->kondisi_aset);
|
||||
@endphp
|
||||
<tr @if($index === $indexKas) data-aset-kas @endif>
|
||||
<td>
|
||||
<input type="hidden" name="aset[{{ $index }}][jenis_aset]" value="{{ $jenis }}">
|
||||
{{ $jenis }}
|
||||
</td>
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input aset-status-radio" type="radio" name="aset[{{ $index }}][status_aset]" value="1" id="aset-{{ $index }}-ada" data-row="{{ $index }}" {{ $row?->status_aset ? 'checked' : '' }}>
|
||||
<input class="form-check-input aset-status-radio" type="radio" name="aset[{{ $index }}][status_aset]" value="1" id="aset-{{ $index }}-ada" data-row="{{ $index }}" @checked($ada)>
|
||||
<label class="form-check-label" for="aset-{{ $index }}-ada">Ada</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input aset-status-radio" type="radio" name="aset[{{ $index }}][status_aset]" value="0" id="aset-{{ $index }}-tidak" data-row="{{ $index }}" {{ !$row || !$row->status_aset ? 'checked' : '' }}>
|
||||
<input class="form-check-input aset-status-radio" type="radio" name="aset[{{ $index }}][status_aset]" value="0" id="aset-{{ $index }}-tidak" data-row="{{ $index }}" @checked(!$ada)>
|
||||
<label class="form-check-label" for="aset-{{ $index }}-tidak">Tidak</label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm aset-detail-select" name="aset[{{ $index }}][status_pengelolaan_aset]" {{ !$row?->status_aset ? 'disabled' : '' }}>
|
||||
<option value="">-</option>
|
||||
<select class="form-select aset-detail-select @error("aset.$index.status_pengelolaan_aset") is-invalid @enderror" id="aset-{{ $index }}-status-pengelolaan" name="aset[{{ $index }}][status_pengelolaan_aset]" aria-label="Status Pengelolaan {{ $jenis }}" @disabled(!$ada) @required($ada)
|
||||
@error("aset.$index.status_pengelolaan_aset") aria-invalid="true" aria-describedby="aset-{{ $index }}-status-pengelolaan-error" @enderror>
|
||||
<option value="">Pilih status pengelola</option>
|
||||
@foreach($statusPengelolaanOptions as $option)
|
||||
<option value="{{ $option }}" {{ $row?->status_pengelolaan_aset == $option ? 'selected' : '' }}>{{ $option }}</option>
|
||||
<option value="{{ $option }}" @selected($statusPengelolaan == $option)>{{ $option }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="invalid-feedback" id="aset-{{ $index }}-status-pengelolaan-error" role="alert">
|
||||
@error("aset.$index.status_pengelolaan_aset") {{ $message }} @else Status Pengelolaan wajib diisi jika aset dipilih Ada. @enderror
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm aset-detail-select" name="aset[{{ $index }}][kondisi_aset]" {{ !$row?->status_aset ? 'disabled' : '' }}>
|
||||
<option value="">-</option>
|
||||
<select class="form-select aset-detail-select @error("aset.$index.kondisi_aset") is-invalid @enderror" id="aset-{{ $index }}-kondisi" name="aset[{{ $index }}][kondisi_aset]" aria-label="Kondisi {{ $jenis }}" @disabled(!$ada) @required($ada)
|
||||
@error("aset.$index.kondisi_aset") aria-invalid="true" aria-describedby="aset-{{ $index }}-kondisi-error" @enderror>
|
||||
<option value="">Pilih kondisi</option>
|
||||
@foreach($kondisiOptions as $option)
|
||||
<option value="{{ $option }}" {{ $row?->kondisi_aset == $option ? 'selected' : '' }}>{{ $option }}</option>
|
||||
<option value="{{ $option }}" @selected($kondisi == $option)>{{ $option }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="invalid-feedback" id="aset-{{ $index }}-kondisi-error" role="alert">
|
||||
@error("aset.$index.kondisi_aset") {{ $message }} @else Kondisi wajib diisi jika aset dipilih Ada. @enderror
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@@ -77,129 +83,121 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-success wave-effect wave-light">
|
||||
<i class="ti tabler-device-floppy me-2"></i> Simpan Aset Produktif
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="section-aset-lainnya" class="mt-4">
|
||||
<h6 class="pb-3 border-bottom mb-4">Aset Lainnya</h6>
|
||||
|
||||
{{-- ASET LAINNYA (di luar 8 jenis baku, bisa lebih dari satu) --}}
|
||||
<div class="card mb-6" id="section-aset-lainnya">
|
||||
<div class="card-body pt-6">
|
||||
@include('produktivitas.survey-kelompok-tani._alert', [
|
||||
'section' => 'aset-lainnya',
|
||||
'errorFields' => ['aset_lainnya.*'],
|
||||
])
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4">Aset Lainnya</h5>
|
||||
|
||||
{{-- DATA LAMA --}}
|
||||
@if($asetLainnyaList->isNotEmpty())
|
||||
@foreach($asetLainnyaList as $row)
|
||||
<div class="row align-items-end border-bottom pb-3 mb-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Jenis Aset</label>
|
||||
<input type="text" class="form-control" value="{{ $row->jenis_aset }}" disabled>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Status Pengelolaan</label>
|
||||
<input type="text" class="form-control" value="{{ $row->status_pengelolaan_aset }}" disabled>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Kondisi</label>
|
||||
<input type="text" class="form-control" value="{{ $row->kondisi_aset }}" disabled>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<form method="POST" action="{{ route('kelompok-tani.aset-keuangan.destroy', [$kelompokTani->id, $row->id]) }}" class="delete-db-form">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-outline-danger">
|
||||
<i class="icon-base ti tabler-trash icon-sm"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@else
|
||||
<p class="text-muted mb-4">Belum ada aset lainnya di luar daftar baku.</p>
|
||||
@endif
|
||||
|
||||
{{-- TOMBOL PEMBUKA — form tambah disembunyikan dulu karena aset lainnya sifatnya opsional --}}
|
||||
@php $bukaFormAsetLainnya = old('aset_lainnya') || $errors->has('aset_lainnya.*'); @endphp
|
||||
<button type="button" class="btn btn-outline-success" id="btn-toggle-aset-lainnya" style="display: {{ $bukaFormAsetLainnya ? 'none' : 'inline-block' }};">
|
||||
<i class="icon-base ti tabler-plus me-1_5 icon-sm"></i> Tambahkan Aset Lainnya
|
||||
</button>
|
||||
|
||||
{{-- FORM TAMBAH DATA BARU — tetap kebuka kalau baru gagal validasi, biar isian & errornya kelihatan --}}
|
||||
<div id="aset-lainnya-form-wrapper" style="display: {{ $bukaFormAsetLainnya ? 'block' : 'none' }};" class="mt-4">
|
||||
<form method="POST" action="{{ route('kelompok-tani.aset-keuangan.store', $kelompokTani->id) }}" class="form-repeater-aset-lainnya">
|
||||
@csrf
|
||||
|
||||
<div data-repeater-list="aset_lainnya">
|
||||
<div data-repeater-item>
|
||||
<div class="row align-items-end">
|
||||
<div class="col-md-4 mb-3">
|
||||
{{-- DATA LAMA --}}
|
||||
@if($asetLainnyaList->isNotEmpty())
|
||||
@foreach($asetLainnyaList as $row)
|
||||
<div class="row align-items-end border-bottom pb-3 mb-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Jenis Aset</label>
|
||||
<input type="text" class="form-control aset-lainnya-input" name="jenis_aset" placeholder="Cth: Mesin pengering">
|
||||
<input type="text" class="form-control" value="{{ $row->jenis_aset }}" disabled>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Status Pengelolaan</label>
|
||||
<select class="form-select aset-lainnya-select" name="status_pengelolaan_aset">
|
||||
<option value="">-</option>
|
||||
@foreach($statusPengelolaanOptions as $option)
|
||||
<option value="{{ $option }}">{{ $option }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<input type="text" class="form-control" value="{{ $row->status_pengelolaan_aset }}" disabled>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Kondisi</label>
|
||||
<select class="form-select aset-lainnya-select" name="kondisi_aset">
|
||||
<option value="">-</option>
|
||||
@foreach($kondisiOptions as $option)
|
||||
<option value="{{ $option }}">{{ $option }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<input type="text" class="form-control" value="{{ $row->kondisi_aset }}" disabled>
|
||||
</div>
|
||||
<div class="col-md-1 mb-3">
|
||||
<button type="button" class="btn btn-outline-danger btn-delete-aset-lainnya" data-repeater-delete disabled>
|
||||
<div class="col-md-1">
|
||||
<button type="submit" form="delete-aset-{{ $row->id }}" class="btn btn-outline-danger" aria-label="Hapus aset {{ $row->jenis_aset }}">
|
||||
<i class="icon-base ti tabler-trash icon-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
@endforeach
|
||||
@else
|
||||
<p class="text-muted mb-4">Belum ada aset lainnya di luar daftar baku.</p>
|
||||
@endif
|
||||
|
||||
{{-- TOMBOL PEMBUKA — form tambah disembunyikan dulu karena aset lainnya sifatnya opsional --}}
|
||||
@php $bukaFormAsetLainnya = old('aset_lainnya') || $errors->has('aset_lainnya.*'); @endphp
|
||||
<button type="button" class="btn btn-outline-success" id="btn-toggle-aset-lainnya" style="display: {{ $bukaFormAsetLainnya ? 'none' : 'inline-block' }};">
|
||||
<i class="icon-base ti tabler-plus me-1_5 icon-sm"></i> Tambahkan Aset Lainnya
|
||||
</button>
|
||||
|
||||
{{-- FORM TAMBAH DATA BARU — tetap kebuka kalau baru gagal validasi, biar isian & errornya kelihatan --}}
|
||||
<div id="aset-lainnya-form-wrapper" style="display: {{ $bukaFormAsetLainnya ? 'block' : 'none' }};" class="mt-4">
|
||||
<div class="form-repeater-aset-lainnya">
|
||||
<div data-repeater-list="aset_lainnya">
|
||||
@foreach($asetLainnyaBaru as $index => $item)
|
||||
<div data-repeater-item>
|
||||
<div class="row align-items-end">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Jenis Aset</label>
|
||||
<input type="text" class="form-control aset-lainnya-input" name="jenis_aset" placeholder="Contoh: Mesin pengering" value="{{ $item['jenis_aset'] ?? '' }}">
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label">Status Pengelolaan</label>
|
||||
<select class="form-select aset-lainnya-select" name="status_pengelolaan_aset">
|
||||
<option value="">Pilih status pengelola</option>
|
||||
@foreach($statusPengelolaanOptions as $option)
|
||||
<option value="{{ $option }}" @selected(($item['status_pengelolaan_aset'] ?? '') == $option)>{{ $option }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Kondisi</label>
|
||||
<select class="form-select aset-lainnya-select" name="kondisi_aset">
|
||||
<option value="">Pilih kondisi</option>
|
||||
@foreach($kondisiOptions as $option)
|
||||
<option value="{{ $option }}" @selected(($item['kondisi_aset'] ?? '') == $option)>{{ $option }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-1 mb-3">
|
||||
<button type="button" class="btn btn-outline-danger btn-delete-aset-lainnya" data-repeater-delete disabled>
|
||||
<i class="icon-base ti tabler-trash icon-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="mt-4 d-flex flex-wrap justify-content-between gap-2">
|
||||
<button type="button" class="btn btn-outline-success" id="btn-tambah-baris-aset-lainnya" data-repeater-create disabled>
|
||||
<i class="icon-base ti tabler-plus me-1_5 icon-sm"></i> Tambah Baris
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 d-flex justify-content-between">
|
||||
<button type="button" class="btn btn-outline-success" id="btn-tambah-baris-aset-lainnya" data-repeater-create disabled>
|
||||
<i class="icon-base ti tabler-plus me-1_5 icon-sm"></i> Tambah Baris
|
||||
</button>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i> Simpan Aset Lainnya
|
||||
<div class="mt-4 text-end">
|
||||
<div class="d-inline-flex flex-wrap justify-content-end gap-2">
|
||||
<a href="{{ route('kelompok-tani.index') }}" class="btn btn-secondary wave-effect wave-light">
|
||||
Kembali
|
||||
</a>
|
||||
<button type="submit" class="btn btn-success wave-effect wave-light">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm" aria-hidden="true"></i> Simpan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@foreach($asetLainnyaList as $row)
|
||||
<form id="delete-aset-{{ $row->id }}" method="POST" action="{{ route('kelompok-tani.aset-keuangan.destroy', [$kelompokTani->id, $row->id]) }}" class="delete-db-form" hidden>
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
</form>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- KAS / SIMPAN-PINJAM --}}
|
||||
<div class="card mb-6" id="section-kas">
|
||||
<div class="card mb-6" id="section-kas" @if(!$memilikiKas) hidden @endif>
|
||||
<div class="card-body pt-6">
|
||||
@include('produktivitas.survey-kelompok-tani._alert', [
|
||||
'section' => 'kas',
|
||||
'errorFields' => ['pembukuan_kas', 'pengelola_kas', 'masalah_pengelolaan_kas_simpan_pinjam', 'deskripsi_masalah_pengelolaan_kas_simpan_pinjam'],
|
||||
])
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4">Kapasitas Pengelolaan Kas/Simpan-Pinjam Kelompok</h5>
|
||||
<p class="text-muted" style="font-size: 0.8rem; margin-top: -0.75rem;">
|
||||
<p class="text-muted small kelompok-tani-help">
|
||||
Hanya isi bagian ini jika kelompok memiliki kas/simpan-pinjam (lihat baris "Kas/simpanan kelompok" di tabel aset).
|
||||
</p>
|
||||
|
||||
<form method="POST" action="{{ route('kelompok-tani.update', $kelompokTani->id) }}">
|
||||
<form novalidate method="POST" action="{{ route('kelompok-tani.update', $kelompokTani->id) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<input type="hidden" name="section" value="kas">
|
||||
@@ -207,7 +205,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4 form-control-validation">
|
||||
<label class="form-label" for="ak-pembukuan-kas">Pembukuan/Pencatatan Keuangan Kas</label>
|
||||
<select class="form-control @error('pembukuan_kas') is-invalid @enderror" id="ak-pembukuan-kas" name="pembukuan_kas">
|
||||
<select class="form-select @error('pembukuan_kas') is-invalid @enderror" id="ak-pembukuan-kas" name="pembukuan_kas">
|
||||
<option value="">Pilih Status Pembukuan</option>
|
||||
@foreach([
|
||||
'Tidak ada pencatatan sama sekali',
|
||||
@@ -227,7 +225,7 @@
|
||||
</div>
|
||||
<div class="col-md-4 mb-4 form-control-validation">
|
||||
<label class="form-label" for="ak-masalah-kas">Pernah Ada Masalah Pengelolaan Kas?</label>
|
||||
<select class="form-control @error('masalah_pengelolaan_kas_simpan_pinjam') is-invalid @enderror" id="ak-masalah-kas" name="masalah_pengelolaan_kas_simpan_pinjam">
|
||||
<select class="form-select @error('masalah_pengelolaan_kas_simpan_pinjam') is-invalid @enderror" id="ak-masalah-kas" name="masalah_pengelolaan_kas_simpan_pinjam">
|
||||
<option value="">Pilih Status</option>
|
||||
@foreach([
|
||||
'Tidak pernah ada masalah',
|
||||
@@ -250,20 +248,51 @@
|
||||
</div>
|
||||
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-success wave-effect wave-light">
|
||||
<i class="ti tabler-device-floppy me-2"></i> Simpan Kas/Simpan-Pinjam
|
||||
</button>
|
||||
<div class="d-inline-flex flex-wrap justify-content-end gap-2">
|
||||
<a href="{{ route('kelompok-tani.index') }}" class="btn btn-secondary wave-effect wave-light">
|
||||
Kembali
|
||||
</a>
|
||||
<button type="submit" class="btn btn-success wave-effect wave-light">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm" aria-hidden="true"></i> Simpan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
'use strict';
|
||||
$(document).ready(function () {
|
||||
var assetForm = document.getElementById('aset-produktif-form');
|
||||
assetForm.addEventListener('invalid', function (event) {
|
||||
if (!event.target.matches('.aset-detail-select')) return;
|
||||
|
||||
event.preventDefault();
|
||||
var field = event.target;
|
||||
$(field).addClass('is-invalid').attr({
|
||||
'aria-invalid': 'true',
|
||||
'aria-describedby': field.id + '-error'
|
||||
});
|
||||
if (field === assetForm.querySelector('.aset-detail-select:invalid')) {
|
||||
field.focus();
|
||||
}
|
||||
}, true);
|
||||
|
||||
$('.aset-detail-select').on('change', function () {
|
||||
if (this.validity.valid) {
|
||||
$(this).removeClass('is-invalid').removeAttr('aria-invalid aria-describedby');
|
||||
}
|
||||
});
|
||||
|
||||
function toggleKasSection() {
|
||||
var memilikiKas = $('[data-aset-kas] .aset-status-radio[value="1"]').is(':checked');
|
||||
$('#section-kas').prop('hidden', !memilikiKas);
|
||||
}
|
||||
toggleKasSection();
|
||||
$('[data-aset-kas] .aset-status-radio').on('change', toggleKasSection);
|
||||
|
||||
function toggleDeskripsiMasalah() {
|
||||
var val = $('#ak-masalah-kas').val();
|
||||
$('#ak-deskripsi-masalah-wrapper').toggle(val === 'Pernah, sudah selesai' || val === 'Pernah, masih berlanjut/belum selesai');
|
||||
@@ -277,9 +306,9 @@
|
||||
var $row = $(this).closest('tr');
|
||||
var ada = $(this).val() === '1';
|
||||
|
||||
$row.find('.aset-detail-select').prop('disabled', !ada);
|
||||
$row.find('.aset-detail-select').prop({ disabled: !ada, required: ada });
|
||||
if (!ada) {
|
||||
$row.find('.aset-detail-select').val('');
|
||||
$row.find('.aset-detail-select').val('').removeClass('is-invalid').removeAttr('aria-invalid aria-describedby');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -298,7 +327,7 @@
|
||||
}
|
||||
|
||||
function rowAdaIsi($row) {
|
||||
return String($row.find('input[name="jenis_aset"]').val() || '').trim() !== '';
|
||||
return String($row.find('.aset-lainnya-input').val() || '').trim() !== '';
|
||||
}
|
||||
|
||||
function updateButtons() {
|
||||
@@ -346,10 +375,10 @@
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('input keyup change', '.form-repeater-aset-lainnya input[name="jenis_aset"]', updateButtons);
|
||||
$(document).on('input keyup change', '.form-repeater-aset-lainnya .aset-lainnya-input', updateButtons);
|
||||
updateButtons();
|
||||
|
||||
$(document).on('submit', '.delete-db-form', function (e) {
|
||||
$(document).on('submit', '#section-aset-baku .delete-db-form', function (e) {
|
||||
e.preventDefault();
|
||||
var form = this;
|
||||
|
||||
|
||||
@@ -1,253 +1,190 @@
|
||||
@extends('layout.main')
|
||||
@php
|
||||
$pernahBantuan = old('pernah_mendapatkan_bantuan', $kelompokTani->pernah_mendapatkan_bantuan);
|
||||
$tampilkanBantuan = (bool) $pernahBantuan;
|
||||
$hasSavedAid = $kelompokTani->bantuan->contains(fn ($bantuan) => filled($bantuan->nama_program));
|
||||
$requireFirstRow = $tampilkanBantuan && !$hasSavedAid;
|
||||
$bantuanBaru = old('bantuan') ?: [[]];
|
||||
$bantuanBaru = is_array($bantuanBaru) ? $bantuanBaru : [[]];
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
<div class="container-xxl flex-grow-1 container-p-y">
|
||||
@include('produktivitas.survey-kelompok-tani.header', ['kelompokTani' => $kelompokTani])
|
||||
<div class="card mb-6" id="section-status-bantuan">
|
||||
<div class="card-body pt-6">
|
||||
<h5 class="pb-4 border-bottom mb-4">Pengalaman Akses Bantuan/Program</h5>
|
||||
|
||||
{{-- STATUS AKSES BANTUAN --}}
|
||||
<div class="card mb-6" id="section-status-bantuan">
|
||||
<div class="card-body pt-6">
|
||||
@include('produktivitas.survey-kelompok-tani._alert', [
|
||||
'section' => 'status-bantuan',
|
||||
'errorFields' => ['pernah_mendapatkan_bantuan'],
|
||||
])
|
||||
<form novalidate id="pengalaman-bantuan-form" method="POST" action="{{ route('kelompok-tani.update', $kelompokTani->id) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<input type="hidden" name="section" value="status-bantuan">
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4">Pengalaman Akses Bantuan/Program</h5>
|
||||
|
||||
<form method="POST" action="{{ route('kelompok-tani.update', $kelompokTani->id) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<input type="hidden" name="section" value="status-bantuan">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label d-block">Apakah kelompok ini pernah mendapatkan bantuan atau akses program dari pemerintah/lembaga lain?</label>
|
||||
@php $pernahBantuan = old('pernah_mendapatkan_bantuan', $kelompokTani->pernah_mendapatkan_bantuan); @endphp
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="pernah_mendapatkan_bantuan" value="1" id="sb-pernah" {{ $pernahBantuan ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="sb-pernah">Pernah</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="pernah_mendapatkan_bantuan" value="0" id="sb-belum" {{ !is_null($pernahBantuan) && !$pernahBantuan ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="sb-belum">Belum Pernah</label>
|
||||
</div>
|
||||
@error('pernah_mendapatkan_bantuan')<div class="error invalid-feedback d-block">{{ $message }}</div>@enderror
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label d-block">Apakah kelompok ini pernah mendapatkan bantuan atau akses program dari pemerintah/lembaga lain?<span class="text-danger">*</span></label>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input @error('pernah_mendapatkan_bantuan') is-invalid @enderror" type="radio" name="pernah_mendapatkan_bantuan" value="1" id="sb-pernah" @checked($tampilkanBantuan) required>
|
||||
<label class="form-check-label" for="sb-pernah">Pernah</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input @error('pernah_mendapatkan_bantuan') is-invalid @enderror" type="radio" name="pernah_mendapatkan_bantuan" value="0" id="sb-belum" @checked(!is_null($pernahBantuan) && !$tampilkanBantuan) required>
|
||||
<label class="form-check-label" for="sb-belum">Belum Pernah</label>
|
||||
</div>
|
||||
@error('pernah_mendapatkan_bantuan')<div class="error invalid-feedback d-block">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-success wave-effect wave-light">
|
||||
<i class="ti tabler-device-floppy me-2"></i> Simpan Status
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="section-bantuan" @if(!$tampilkanBantuan) hidden @endif>
|
||||
<h6 class="pb-3 border-bottom mb-4">Daftar Program/Bantuan yang Pernah Diterima</h6>
|
||||
<p class="text-muted small mb-4">Minimal satu data bantuan wajib diisi.</p>
|
||||
|
||||
{{-- DAFTAR PROGRAM / BANTUAN --}}
|
||||
<div class="card mb-6" id="section-bantuan">
|
||||
<div class="card-body pt-6">
|
||||
@include('produktivitas.survey-kelompok-tani._alert', [
|
||||
'section' => 'bantuan',
|
||||
'errorFields' => ['bantuan.*'],
|
||||
])
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4">Daftar Program/Bantuan yang Pernah Diterima</h5>
|
||||
|
||||
<p class="text-muted mb-0" id="bantuan-list-placeholder" style="display: none;">
|
||||
Set status di atas jadi <strong>"Pernah"</strong> dulu untuk mengisi daftar program/bantuan.
|
||||
</p>
|
||||
|
||||
<div id="bantuan-list-content">
|
||||
{{-- DATA LAMA --}}
|
||||
@if($kelompokTani->bantuan->count() > 0)
|
||||
@foreach($kelompokTani->bantuan as $row)
|
||||
@forelse($kelompokTani->bantuan as $row)
|
||||
<div class="row align-items-end border-bottom pb-3 mb-3">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Nama Program/Bantuan</label>
|
||||
<label class="form-label">Nama Program/Bantuan @if($loop->first)<span class="text-danger bantuan-saved-required-mark" aria-hidden="true">*</span>@endif</label>
|
||||
<input type="text" class="form-control" value="{{ $row->nama_program }}" disabled>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Penyelenggara</label>
|
||||
<label class="form-label">Penyelenggara @if($loop->first)<span class="text-danger bantuan-saved-required-mark" aria-hidden="true">*</span>@endif</label>
|
||||
<input type="text" class="form-control" value="{{ $row->penyelenggara }}" disabled>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Bentuk Bantuan</label>
|
||||
<label class="form-label">Bentuk Bantuan @if($loop->first)<span class="text-danger bantuan-saved-required-mark" aria-hidden="true">*</span>@endif</label>
|
||||
<input type="text" class="form-control" value="{{ $row->bentuk_bantuan }}" disabled>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Tahun</label>
|
||||
<label class="form-label">Tahun @if($loop->first)<span class="text-danger bantuan-saved-required-mark" aria-hidden="true">*</span>@endif</label>
|
||||
<input type="text" class="form-control" value="{{ $row->tahun }}" disabled>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<form method="POST" action="{{ route('kelompok-tani.bantuan-penilaian.destroy', [$kelompokTani->id, $row->id]) }}" class="delete-db-form">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-outline-danger">
|
||||
<i class="icon-base ti tabler-trash icon-sm"></i>
|
||||
</button>
|
||||
</form>
|
||||
<button type="submit" form="delete-bantuan-{{ $row->id }}" class="btn btn-outline-danger" aria-label="Hapus bantuan {{ $row->nama_program }}">
|
||||
<i class="icon-base ti tabler-trash icon-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@else
|
||||
<p class="text-muted mb-4">Belum ada data program/bantuan.</p>
|
||||
@endif
|
||||
@empty
|
||||
<p class="text-muted mb-4">Belum ada data program/bantuan.</p>
|
||||
@endforelse
|
||||
|
||||
{{-- FORM TAMBAH DATA BARU --}}
|
||||
<form method="POST" action="{{ route('kelompok-tani.bantuan-penilaian.store', $kelompokTani->id) }}" class="form-repeater-bantuan">
|
||||
@csrf
|
||||
|
||||
<div data-repeater-list="bantuan">
|
||||
<div data-repeater-item>
|
||||
<div class="row align-items-end">
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label">Nama Program/Bantuan</label>
|
||||
<input type="text" class="form-control bantuan-input" name="nama_program" placeholder="Cth: Bantuan Bibit Kakao">
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label">Penyelenggara</label>
|
||||
<input type="text" class="form-control bantuan-input" name="penyelenggara" placeholder="Cth: Dinas Pertanian">
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label">Bentuk Bantuan</label>
|
||||
<input type="text" class="form-control bantuan-input" name="bentuk_bantuan" placeholder="Cth: Bibit, pupuk, pelatihan">
|
||||
</div>
|
||||
<div class="col-md-2 mb-3">
|
||||
<label class="form-label">Tahun</label>
|
||||
<input type="number" class="form-control" name="tahun" placeholder="Cth: 2023" min="1900" max="{{ date('Y') }}">
|
||||
</div>
|
||||
<div class="col-md-1 mb-3">
|
||||
<button type="button" class="btn btn-outline-danger btn-delete-bantuan" data-repeater-delete disabled>
|
||||
<i class="icon-base ti tabler-trash icon-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
<fieldset id="bantuan-fields" @disabled(!$tampilkanBantuan)>
|
||||
<div class="form-repeater-bantuan" data-has-saved-bantuan="{{ $hasSavedAid ? '1' : '0' }}">
|
||||
<div data-repeater-list="bantuan">
|
||||
@foreach($bantuanBaru as $index => $item)
|
||||
@php
|
||||
$item = is_array($item) ? $item : [];
|
||||
$required = $loop->first && $requireFirstRow;
|
||||
@endphp
|
||||
<div data-repeater-item>
|
||||
<div class="row align-items-end">
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label">Nama Program/Bantuan <span class="text-danger bantuan-required-mark" @if(!$required) hidden @endif>*</span></label>
|
||||
<input type="text" class="form-control bantuan-input @error("bantuan.$index.nama_program") is-invalid @enderror" name="nama_program" placeholder="Contoh: Bantuan Bibit Kakao" value="{{ $item['nama_program'] ?? '' }}" @required($required)>
|
||||
@error("bantuan.$index.nama_program")<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label">Penyelenggara <span class="text-danger bantuan-required-mark" @if(!$required) hidden @endif>*</span></label>
|
||||
<input type="text" class="form-control bantuan-input @error("bantuan.$index.penyelenggara") is-invalid @enderror" name="penyelenggara" placeholder="Contoh: Dinas Pertanian" value="{{ $item['penyelenggara'] ?? '' }}" @required($required)>
|
||||
@error("bantuan.$index.penyelenggara")<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label">Bentuk Bantuan <span class="text-danger bantuan-required-mark" @if(!$required) hidden @endif>*</span></label>
|
||||
<input type="text" class="form-control bantuan-input @error("bantuan.$index.bentuk_bantuan") is-invalid @enderror" name="bentuk_bantuan" placeholder="Contoh: Bibit, pupuk, pelatihan" value="{{ $item['bentuk_bantuan'] ?? '' }}" @required($required)>
|
||||
@error("bantuan.$index.bentuk_bantuan")<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-2 mb-3">
|
||||
<label class="form-label">Tahun <span class="text-danger bantuan-required-mark" @if(!$required) hidden @endif>*</span></label>
|
||||
<input type="number" class="form-control bantuan-year-input @error("bantuan.$index.tahun") is-invalid @enderror" name="tahun" placeholder="Contoh: 2023" min="1900" max="{{ date('Y') }}" value="{{ $item['tahun'] ?? '' }}" @required($required)>
|
||||
@error("bantuan.$index.tahun")<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-1 mb-3">
|
||||
<button type="button" class="btn btn-outline-danger btn-delete-bantuan" data-repeater-delete disabled>
|
||||
<i class="icon-base ti tabler-trash icon-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 d-flex justify-content-between">
|
||||
<button type="button" class="btn btn-outline-success" id="btn-tambah-bantuan" data-repeater-create disabled>
|
||||
<i class="icon-base ti tabler-plus me-1_5 icon-sm"></i> Tambah Baris
|
||||
</button>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i> Simpan Daftar Bantuan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<button type="button" class="btn btn-outline-success" id="btn-tambah-bantuan" data-repeater-create disabled>
|
||||
<i class="icon-base ti tabler-plus me-1_5 icon-sm"></i> Tambah Baris
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- POTENSI MITRA AGREGASI --}}
|
||||
<div class="card mb-6" id="section-penilaian">
|
||||
<div class="card-body pt-6">
|
||||
@include('produktivitas.survey-kelompok-tani._alert', [
|
||||
'section' => 'penilaian',
|
||||
'errorFields' => ['potensi_kelompok', 'alasan_penilaian_potensi'],
|
||||
])
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4">Potensi sebagai Mitra Agregasi/Pengolahan Kolektif</h5>
|
||||
<p class="text-muted" style="font-size: 0.8rem; margin-top: -0.75rem;">
|
||||
Ini penilaian enumerator, bukan pernyataan langsung dari responden — dasarkan pada kombinasi kegiatan, fungsi, dan aset yang sudah tergali di tab-tab sebelumnya.
|
||||
</p>
|
||||
|
||||
<form method="POST" action="{{ route('kelompok-tani.update', $kelompokTani->id) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<input type="hidden" name="section" value="penilaian">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4 form-control-validation">
|
||||
<label class="form-label" for="pn-potensi-kelompok">Tingkat Potensi</label>
|
||||
<select class="form-control @error('potensi_kelompok') is-invalid @enderror" id="pn-potensi-kelompok" name="potensi_kelompok">
|
||||
<option value="">Pilih Tingkat Potensi</option>
|
||||
<option value="Rendah" {{ old('potensi_kelompok', $kelompokTani->potensi_kelompok) == 'Rendah' ? 'selected' : '' }}>Rendah — belum ada kapasitas/aktivitas yang mendukung</option>
|
||||
<option value="Sedang" {{ old('potensi_kelompok', $kelompokTani->potensi_kelompok) == 'Sedang' ? 'selected' : '' }}>Sedang — ada sebagian kapasitas, perlu penguatan lebih jauh</option>
|
||||
<option value="Tinggi" {{ old('potensi_kelompok', $kelompokTani->potensi_kelompok) == 'Tinggi' ? 'selected' : '' }}>Tinggi — kapasitas dan aktivitas sudah mendukung peran ini</option>
|
||||
</select>
|
||||
@error('potensi_kelompok')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-8 mb-4 form-control-validation">
|
||||
<label class="form-label" for="pn-alasan-penilaian">Alasan Penilaian</label>
|
||||
<textarea class="form-control @error('alasan_penilaian_potensi') is-invalid @enderror" id="pn-alasan-penilaian" name="alasan_penilaian_potensi" rows="2">{{ old('alasan_penilaian_potensi', $kelompokTani->alasan_penilaian_potensi) }}</textarea>
|
||||
@error('alasan_penilaian_potensi')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-success wave-effect wave-light">
|
||||
<i class="ti tabler-device-floppy me-2"></i> Simpan Penilaian
|
||||
<div class="mt-4 text-end">
|
||||
<div class="d-inline-flex flex-wrap justify-content-end gap-2">
|
||||
<a href="{{ route('kelompok-tani.index') }}" class="btn btn-secondary wave-effect wave-light">
|
||||
Kembali
|
||||
</a>
|
||||
<button type="submit" class="btn btn-success wave-effect wave-light">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm" aria-hidden="true"></i> Simpan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@foreach($kelompokTani->bantuan as $row)
|
||||
<form id="delete-bantuan-{{ $row->id }}" method="POST" action="{{ route('kelompok-tani.bantuan-penilaian.destroy', [$kelompokTani->id, $row->id]) }}" class="delete-db-form" hidden>
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
</form>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
$(function () {
|
||||
$('#pn-potensi-kelompok').select2();
|
||||
|
||||
// Daftar bantuan cuma relevan kalau status-nya "Pernah" — biar nggak kontradiktif
|
||||
// (jawab "Belum Pernah" tapi daftar bantuannya keisi).
|
||||
function toggleDaftarBantuan() {
|
||||
var pernah = $('#sb-pernah').is(':checked');
|
||||
$('#bantuan-list-content').toggle(pernah);
|
||||
$('#bantuan-list-placeholder').toggle(!pernah);
|
||||
$('#section-bantuan').prop('hidden', !pernah);
|
||||
$('#bantuan-fields').prop('disabled', !pernah);
|
||||
updateButtons();
|
||||
}
|
||||
toggleDaftarBantuan();
|
||||
$('#sb-pernah, #sb-belum').on('change', toggleDaftarBantuan);
|
||||
|
||||
var $form = $('.form-repeater-bantuan');
|
||||
var $repeater = $('.form-repeater-bantuan');
|
||||
var $btnTambah = $('#btn-tambah-bantuan');
|
||||
|
||||
function getRows() {
|
||||
return $form.find('[data-repeater-item]');
|
||||
}
|
||||
|
||||
function rowAdaIsi($row) {
|
||||
return $row.find('.bantuan-input').toArray().some(function (input) {
|
||||
return $row.find('.bantuan-input, .bantuan-year-input').toArray().some(function (input) {
|
||||
return String($(input).val() || '').trim() !== '';
|
||||
});
|
||||
}
|
||||
|
||||
function rowLengkap($row) {
|
||||
var $inputs = $row.find('.bantuan-input');
|
||||
return $inputs.toArray().every(function (input) {
|
||||
return $row.find('.bantuan-input, .bantuan-year-input[required]').toArray().every(function (input) {
|
||||
return String($(input).val() || '').trim() !== '';
|
||||
});
|
||||
}
|
||||
|
||||
function updateButtons() {
|
||||
var $rows = getRows();
|
||||
var $lastRow = $rows.last();
|
||||
|
||||
$rows.each(function () {
|
||||
var $row = $(this);
|
||||
$row.find('.btn-delete-bantuan').prop('disabled', !rowAdaIsi($row));
|
||||
var $rows = $repeater.find('[data-repeater-item]');
|
||||
var needsFirst = $('#sb-pernah').is(':checked') && $repeater.attr('data-has-saved-bantuan') !== '1';
|
||||
$rows.each(function (index) {
|
||||
var required = needsFirst && index === 0;
|
||||
$(this).find('.bantuan-input, .bantuan-year-input').prop('required', required);
|
||||
$(this).find('.bantuan-required-mark').prop('hidden', !required);
|
||||
$(this).find('.btn-delete-bantuan').prop('disabled', $rows.length === 1 && (required || !rowAdaIsi($(this))));
|
||||
});
|
||||
|
||||
$btnTambah.prop('disabled', !rowLengkap($lastRow));
|
||||
$btnTambah.prop('disabled', !rowLengkap($rows.last()));
|
||||
}
|
||||
|
||||
$form.repeater({
|
||||
$repeater.repeater({
|
||||
initEmpty: false,
|
||||
show: function () {
|
||||
var $row = $(this);
|
||||
$row.find('.bantuan-input, input[name="tahun"]').val('');
|
||||
$row.find('.btn-delete-bantuan').prop('disabled', true);
|
||||
$row.slideDown();
|
||||
setTimeout(updateButtons, 50);
|
||||
$(this).find('.bantuan-input, .bantuan-year-input').val('');
|
||||
$(this).find('.is-invalid').removeClass('is-invalid');
|
||||
$(this).find('.invalid-feedback').remove();
|
||||
$(this).slideDown();
|
||||
updateButtons();
|
||||
},
|
||||
hide: function (deleteElement) {
|
||||
var $row = $(this);
|
||||
if (!rowAdaIsi($row)) {
|
||||
deleteElement();
|
||||
updateButtons();
|
||||
return;
|
||||
}
|
||||
Swal.fire({
|
||||
@@ -261,22 +198,20 @@ $(function () {
|
||||
buttonsStyling: false
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
$row.slideUp(deleteElement);
|
||||
setTimeout(updateButtons, 300);
|
||||
$row.slideUp(function () { deleteElement(); updateButtons(); });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
$(document).on('input change', '.form-repeater-bantuan .bantuan-input, .form-repeater-bantuan .bantuan-year-input', updateButtons);
|
||||
toggleDaftarBantuan();
|
||||
$('#sb-pernah, #sb-belum').on('change', toggleDaftarBantuan);
|
||||
|
||||
$(document).on('input keyup change', '.form-repeater-bantuan .bantuan-input', updateButtons);
|
||||
updateButtons();
|
||||
|
||||
$(document).on('submit', '.delete-db-form', function (e) {
|
||||
e.preventDefault();
|
||||
$(document).on('submit', '#section-status-bantuan .delete-db-form', function (event) {
|
||||
event.preventDefault();
|
||||
var form = this;
|
||||
|
||||
Swal.fire({
|
||||
title: 'Hapus data ini?',
|
||||
title: 'Hapus data bantuan ini?',
|
||||
text: 'Data yang sudah dihapus tidak bisa dikembalikan.',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
@@ -285,9 +220,7 @@ $(function () {
|
||||
customClass: { confirmButton: 'btn btn-danger me-3', cancelButton: 'btn btn-label-secondary' },
|
||||
buttonsStyling: false
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
form.submit();
|
||||
}
|
||||
if (result.isConfirmed) form.submit();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
@extends('layout.main')
|
||||
|
||||
@section('content')
|
||||
<div class="container-xxl flex-grow-1 container-p-y">
|
||||
<div class="container-xxl flex-grow-1 container-p-y kelompok-tani-page">
|
||||
@include('produktivitas.survey-kelompok-tani.header', ['kelompokTani' => $kelompokTani])
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card mb-6" id="section-identitas">
|
||||
<div class="card-body pt-6">
|
||||
@include('produktivitas.survey-kelompok-tani._alert', [
|
||||
'section' => 'identitas',
|
||||
'errorFields' => ['nama_responden', 'jabatan_responden', 'kontak_responden', 'nama_kelompok', 'desa_kelurahan_id', 'dusun', 'tahun_berdiri', 'jenis_kelompok', 'jenis_kelompok_lainnya', 'nama_gapoktan', 'komoditas_id.*'],
|
||||
])
|
||||
|
||||
<form id="editKelompokTaniForm" action="{{ route('kelompok-tani.update', $kelompokTani->id) }}" method="POST">
|
||||
<form novalidate id="editKelompokTaniForm" action="{{ route('kelompok-tani.update', $kelompokTani->id) }}" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
@@ -21,7 +16,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4 form-control-validation">
|
||||
<label class="form-label" for="edit-kt-nama-responden">Nama Responden<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control @error('nama_responden') is-invalid @enderror" id="edit-kt-nama-responden" name="nama_responden"
|
||||
<input type="text" class="form-control @error('nama_responden') is-invalid @enderror" id="edit-kt-nama-responden" name="nama_responden" required
|
||||
placeholder="Cth: John Doe" value="{{ old('nama_responden', $kelompokTani->nama_responden) }}" />
|
||||
@error('nama_responden')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
@@ -43,7 +38,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label" for="edit-kt-nama-kelompok">Nama Kelompok Tani<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control @error('nama_kelompok') is-invalid @enderror" id="edit-kt-nama-kelompok" name="nama_kelompok"
|
||||
<input type="text" class="form-control @error('nama_kelompok') is-invalid @enderror" id="edit-kt-nama-kelompok" name="nama_kelompok" required
|
||||
placeholder="Cth: Kelompok Tani Maju Bersama" value="{{ old('nama_kelompok', $kelompokTani->nama_kelompok) }}" />
|
||||
@error('nama_kelompok')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
@@ -64,45 +59,48 @@
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="edit-kt-provinsi">Provinsi<span class="text-danger">*</span></label>
|
||||
<select class="form-control @error('provinsi_id') is-invalid @enderror" id="edit-kt-provinsi" name="provinsi_id">
|
||||
<select class="form-select @error('provinsi_id') is-invalid @enderror" id="edit-kt-provinsi" name="provinsi_id" required>
|
||||
<option value="">Pilih Provinsi</option>
|
||||
@foreach($provinsi as $p)
|
||||
<option value="{{ $p->id }}" {{ old('provinsi_id', $kelompokTani->desaKelurahan?->kecamatan?->kabupatenKota?->provinsi_id) == $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="edit-kt-kabupaten">Kabupaten/Kota<span class="text-danger">*</span></label>
|
||||
<select class="form-control @error('kabupaten_id') is-invalid @enderror" id="edit-kt-kabupaten" name="kabupaten_id">
|
||||
<select class="form-select @error('kabupaten_id') is-invalid @enderror" id="edit-kt-kabupaten" name="kabupaten_id" required>
|
||||
<option value="">Pilih Kabupaten/Kota</option>
|
||||
@if($kelompokTani->desaKelurahan?->kecamatan?->kabupatenKota)
|
||||
<option value="{{ $kelompokTani->desaKelurahan->kecamatan->kabupatenKota->id }}" selected>{{ $kelompokTani->desaKelurahan->kecamatan->kabupatenKota->nama }}</option>
|
||||
@endif
|
||||
@foreach($kabupatenOptions ?? [] as $option)
|
||||
<option value="{{ $option->id }}" @selected(old('kabupaten_id', $kelompokTani->desaKelurahan?->kecamatan?->kabupaten_kota_id) == $option->id)>{{ $option->nama }}</option>
|
||||
@endforeach
|
||||
</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="edit-kt-kecamatan">Kecamatan<span class="text-danger">*</span></label>
|
||||
<select class="form-control @error('kecamatan_id') is-invalid @enderror" id="edit-kt-kecamatan" name="kecamatan_id">
|
||||
<select class="form-select @error('kecamatan_id') is-invalid @enderror" id="edit-kt-kecamatan" name="kecamatan_id" required>
|
||||
<option value="">Pilih Kecamatan</option>
|
||||
@if($kelompokTani->desaKelurahan?->kecamatan)
|
||||
<option value="{{ $kelompokTani->desaKelurahan->kecamatan->id }}" selected>{{ $kelompokTani->desaKelurahan->kecamatan->nama }}</option>
|
||||
@endif
|
||||
@foreach($kecamatanOptions ?? [] as $option)
|
||||
<option value="{{ $option->id }}" @selected(old('kecamatan_id', $kelompokTani->desaKelurahan?->kecamatan_id) == $option->id)>{{ $option->nama }}</option>
|
||||
@endforeach
|
||||
</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="edit-kt-desa">Desa/Kelurahan<span class="text-danger">*</span></label>
|
||||
<select class="form-control @error('desa_kelurahan_id') is-invalid @enderror" id="edit-kt-desa" name="desa_kelurahan_id">
|
||||
<select class="form-select @error('desa_kelurahan_id') is-invalid @enderror" id="edit-kt-desa" name="desa_kelurahan_id" required>
|
||||
<option value="">Pilih Desa/Kelurahan</option>
|
||||
@if($kelompokTani->desaKelurahan)
|
||||
<option value="{{ $kelompokTani->desaKelurahan->id }}" selected>{{ $kelompokTani->desaKelurahan->nama }}</option>
|
||||
@endif
|
||||
@foreach($desaOptions ?? [] as $option)
|
||||
<option value="{{ $option->id }}" @selected(old('desa_kelurahan_id', $kelompokTani->desa_kelurahan_id) == $option->id)>{{ $option->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('desa_kelurahan_id')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4 form-control-validation">
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label" for="edit-kt-jenis-kelompok">Terdaftar/Dikenal Sebagai<span class="text-danger">*</span></label>
|
||||
@php
|
||||
$jenisKelompokOptions = [
|
||||
@@ -115,7 +113,7 @@
|
||||
$currentJenisKelompok = old('jenis_kelompok', $kelompokTani->jenis_kelompok);
|
||||
$isJenisKelompokLainnya = $currentJenisKelompok && !in_array($currentJenisKelompok, $jenisKelompokOptions);
|
||||
@endphp
|
||||
<select class="form-control @error('jenis_kelompok') is-invalid @enderror" id="edit-kt-jenis-kelompok" name="jenis_kelompok">
|
||||
<select class="form-select @error('jenis_kelompok') is-invalid @enderror" id="edit-kt-jenis-kelompok" name="jenis_kelompok" required>
|
||||
<option value="">Pilih Jenis Kelompok</option>
|
||||
@foreach($jenisKelompokOptions as $option)
|
||||
<option value="{{ $option }}" {{ $currentJenisKelompok == $option ? 'selected' : '' }}>{{ $option }}</option>
|
||||
@@ -125,7 +123,7 @@
|
||||
@error('jenis_kelompok')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-4 mb-4 form-control-validation" id="edit-kt-jenis-kelompok-lainnya-wrapper" style="display: none;">
|
||||
<label class="form-label" for="edit-kt-jenis-kelompok-lainnya">Sebutkan Jenis Kelompok</label>
|
||||
<label class="form-label" for="edit-kt-jenis-kelompok-lainnya">Sebutkan Jenis Kelompok<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control @error('jenis_kelompok_lainnya') is-invalid @enderror" id="edit-kt-jenis-kelompok-lainnya" name="jenis_kelompok_lainnya"
|
||||
value="{{ old('jenis_kelompok_lainnya', $isJenisKelompokLainnya ? $currentJenisKelompok : null) }}" />
|
||||
@error('jenis_kelompok_lainnya')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
@@ -150,15 +148,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@include('produktivitas.survey-kelompok-tani.pengurus-anggota', ['bagianKeanggotaan' => 'ringkasan'])
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('kelompok-tani.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</button>
|
||||
<div class="d-inline-flex flex-wrap justify-content-end gap-2">
|
||||
<a href="{{ route('kelompok-tani.index') }}" class="btn btn-secondary wave-effect wave-light">
|
||||
Kembali
|
||||
</a>
|
||||
<button type="submit" class="btn btn-success data-submit wave-effect wave-light">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm" aria-hidden="true"></i> Simpan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@include('produktivitas.survey-kelompok-tani.pengurus-anggota')
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@@ -171,6 +178,7 @@
|
||||
|
||||
function toggleJenisKelompokFields() {
|
||||
var jenisKelompok = $('#edit-kt-jenis-kelompok').val();
|
||||
$('#edit-kt-jenis-kelompok-lainnya').prop('required', jenisKelompok === 'Lainnya').prop('disabled', jenisKelompok !== 'Lainnya');
|
||||
|
||||
if (jenisKelompok === 'Lainnya') {
|
||||
$('#edit-kt-jenis-kelompok-lainnya-wrapper').show();
|
||||
@@ -247,52 +255,6 @@
|
||||
});
|
||||
|
||||
$('#edit-kt-desa').select2();
|
||||
|
||||
$('#editKelompokTaniForm').validate({
|
||||
rules: {
|
||||
nama_responden: { required: true },
|
||||
nama_kelompok: { required: true },
|
||||
provinsi_id: { required: true },
|
||||
kabupaten_id: { required: true },
|
||||
kecamatan_id: { required: true },
|
||||
desa_kelurahan_id: { required: true },
|
||||
jenis_kelompok: { required: true },
|
||||
jenis_kelompok_lainnya: {
|
||||
required: function () {
|
||||
return $('#edit-kt-jenis-kelompok').val() === 'Lainnya';
|
||||
}
|
||||
},
|
||||
tahun_berdiri: { min: 1900, max: new Date().getFullYear() }
|
||||
},
|
||||
messages: {
|
||||
nama_responden: { required: 'Nama responden wajib diisi' },
|
||||
nama_kelompok: { required: 'Nama kelompok wajib diisi' },
|
||||
provinsi_id: { required: 'Provinsi wajib diisi' },
|
||||
kabupaten_id: { required: 'Kabupaten/Kota wajib diisi' },
|
||||
kecamatan_id: { required: 'Kecamatan wajib diisi' },
|
||||
desa_kelurahan_id: { required: 'Desa/Kelurahan wajib diisi' },
|
||||
jenis_kelompok: { required: 'Jenis kelompok wajib dipilih' },
|
||||
jenis_kelompok_lainnya: { required: 'Sebutkan jenis kelompoknya' }
|
||||
},
|
||||
errorElement: 'div',
|
||||
errorPlacement: function (error, element) {
|
||||
error.addClass('invalid-feedback');
|
||||
if (element.hasClass('select2-hidden-accessible')) {
|
||||
element.closest('.form-control-validation').append(error);
|
||||
} else {
|
||||
element.closest('.mb-4, .form-control-validation').append(error);
|
||||
}
|
||||
},
|
||||
highlight: function (element) {
|
||||
$(element).addClass('is-invalid');
|
||||
},
|
||||
unhighlight: function (element) {
|
||||
$(element).removeClass('is-invalid');
|
||||
},
|
||||
submitHandler: function (form) {
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@include('produktivitas.survey-kelompok-tani._alert')
|
||||
|
||||
<style>
|
||||
@media (min-width: 768px) {
|
||||
.nav-pills-custom-spacing {
|
||||
@@ -25,7 +27,7 @@
|
||||
padding-right: 0.5rem !important;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
font-size: 0.9375rem !important;
|
||||
font-size: var(--bs-body-font-size) !important;
|
||||
}
|
||||
|
||||
.nav-pills-custom-spacing .nav-link i {
|
||||
@@ -45,33 +47,53 @@
|
||||
: null;
|
||||
@endphp
|
||||
|
||||
<div class="card mb-6">
|
||||
<div class="card mb-6" id="kelompok-tani-summary">
|
||||
<div class="card-body p-3">
|
||||
<div class="d-flex justify-content-start align-items-center flex-wrap row-gap-2">
|
||||
<div class="avatar avatar-lg me-3">
|
||||
<span class="avatar-initial rounded bg-label-primary">
|
||||
<i class="icon-base ti tabler-users-group icon-28px"></i>
|
||||
</span>
|
||||
<div class="row align-items-center gy-3">
|
||||
<div class="{{ isset($kelompokTani) ? 'col-lg-8' : 'col-12' }} d-flex align-items-center">
|
||||
<div class="avatar avatar-lg me-3">
|
||||
<span class="avatar-initial rounded bg-label-primary">
|
||||
<i class="icon-base ti tabler-users-group icon-28px"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex flex-column text-break" style="min-width: 0;">
|
||||
<h6 class="mb-0 kelompok-tani-summary-name">{{ isset($kelompokTani) ? $kelompokTani->nama_kelompok : 'Tambah Kelompok Tani Baru' }}</h6>
|
||||
<span class="text-muted kelompok-tani-summary-note">
|
||||
{{ $lokasiKelompok ?: 'Lengkapi profil untuk memulai' }}
|
||||
</span>
|
||||
@if(isset($kelompokTani) && ($kelompokTani->jenis_kelompok || $namaKomoditas->isNotEmpty() || $kelompokTani->nama_gapoktan))
|
||||
<div class="d-flex flex-wrap gap-1 mt-2">
|
||||
@if($kelompokTani->jenis_kelompok)
|
||||
<span class="badge bg-label-secondary">{{ $kelompokTani->jenis_kelompok }}</span>
|
||||
@endif
|
||||
@if($kelompokTani->nama_gapoktan)
|
||||
<span class="badge bg-label-info">Gapoktan: {{ $kelompokTani->nama_gapoktan }}</span>
|
||||
@endif
|
||||
@foreach($namaKomoditas as $nama)
|
||||
<span class="badge bg-label-success">{{ $nama }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-column">
|
||||
<h5 class="mb-0">{{ isset($kelompokTani) ? $kelompokTani->nama_kelompok : 'Tambah Kelompok Tani Baru' }}</h5>
|
||||
<span class="text-muted" style="font-size: 0.8rem;">
|
||||
{{ $lokasiKelompok ?: 'Lengkapi identitas & data responden untuk memulai' }}
|
||||
</span>
|
||||
@if(isset($kelompokTani) && ($kelompokTani->jenis_kelompok || $namaKomoditas->isNotEmpty() || $kelompokTani->nama_gapoktan))
|
||||
<div class="d-flex flex-wrap gap-1 mt-2">
|
||||
@if($kelompokTani->jenis_kelompok)
|
||||
<span class="badge bg-label-secondary">{{ $kelompokTani->jenis_kelompok }}</span>
|
||||
@endif
|
||||
@if($kelompokTani->nama_gapoktan)
|
||||
<span class="badge bg-label-info">Gapoktan: {{ $kelompokTani->nama_gapoktan }}</span>
|
||||
@endif
|
||||
@foreach($namaKomoditas as $nama)
|
||||
<span class="badge bg-label-success">{{ $nama }}</span>
|
||||
@if(isset($kelompokTani))
|
||||
<div class="col-lg-4">
|
||||
<p class="mb-2 fw-medium">Jumlah Pengurus & Anggota</p>
|
||||
<div class="row g-2">
|
||||
@foreach([
|
||||
'jumlah_pengurus' => 'Total Pengurus',
|
||||
'jumlah_anggota_terdaftar' => 'Total Anggota',
|
||||
] as $field => $label)
|
||||
<div class="col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<p class="mb-1 text-muted kelompok-tani-summary-note">{{ $label }}</p>
|
||||
<h5 class="mb-0">{{ $kelompokTani->{$field} !== null ? number_format($kelompokTani->{$field}, 0, ',', '.') : '-' }}</h5>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if(isset($kelompokTani))
|
||||
@@ -84,8 +106,8 @@
|
||||
</span>
|
||||
</span>
|
||||
<div>
|
||||
<p class="mb-0 text-muted" style="font-size: 0.75rem;">Nama Responden</p>
|
||||
<span class="fw-medium" style="font-size: 0.85rem;">{{ $kelompokTani->nama_responden ?: '-' }}</span>
|
||||
<p class="mb-0 text-muted kelompok-tani-summary-note">Nama Responden</p>
|
||||
<span class="fw-medium small">{{ $kelompokTani->nama_responden ?: '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-3 d-flex align-items-center">
|
||||
@@ -95,8 +117,8 @@
|
||||
</span>
|
||||
</span>
|
||||
<div>
|
||||
<p class="mb-0 text-muted" style="font-size: 0.75rem;">Jabatan Responden</p>
|
||||
<span class="fw-medium" style="font-size: 0.85rem;">{{ $kelompokTani->jabatan_responden ?: '-' }}</span>
|
||||
<p class="mb-0 text-muted kelompok-tani-summary-note">Jabatan Responden</p>
|
||||
<span class="fw-medium small">{{ $kelompokTani->jabatan_responden ?: '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-3 d-flex align-items-center">
|
||||
@@ -106,8 +128,8 @@
|
||||
</span>
|
||||
</span>
|
||||
<div>
|
||||
<p class="mb-0 text-muted" style="font-size: 0.75rem;">Kontak Responden</p>
|
||||
<span class="fw-medium" style="font-size: 0.85rem;">{{ $kelompokTani->kontak_responden ?: '-' }}</span>
|
||||
<p class="mb-0 text-muted kelompok-tani-summary-note">Kontak Responden</p>
|
||||
<span class="fw-medium small">{{ $kelompokTani->kontak_responden ?: '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-3 d-flex align-items-center">
|
||||
@@ -117,8 +139,8 @@
|
||||
</span>
|
||||
</span>
|
||||
<div>
|
||||
<p class="mb-0 text-muted" style="font-size: 0.75rem;">Tahun Berdiri</p>
|
||||
<span class="fw-medium" style="font-size: 0.85rem;">{{ $kelompokTani->tahun_berdiri ?: '-' }}</span>
|
||||
<p class="mb-0 text-muted kelompok-tani-summary-note">Tahun Berdiri</p>
|
||||
<span class="fw-medium small">{{ $kelompokTani->tahun_berdiri ?: '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -131,22 +153,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{ request()->routeIs('kelompok-tani.create') || (request()->routeIs('kelompok-tani.edit') && !request()->query('section')) ? 'active' : '' }}"
|
||||
href="{{ isset($kelompokTani) ? route('kelompok-tani.edit', $kelompokTani->id) : route('kelompok-tani.create') }}">
|
||||
<i class="icon-base ti tabler-id-badge-2 me-1_5 icon-sm"></i>Identitas & Responden
|
||||
<i class="icon-base ti tabler-user-check me-1_5 icon-sm"></i>Profil
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
@if(isset($kelompokTani))
|
||||
<a class="nav-link {{ request()->routeIs('kelompok-tani.pengurus-anggota.*') ? 'active' : '' }}"
|
||||
href="{{ route('kelompok-tani.pengurus-anggota.index', $kelompokTani->id) }}">
|
||||
<i class="icon-base ti tabler-users-group me-1_5 icon-sm"></i>Pengurus & Anggota
|
||||
</a>
|
||||
@else
|
||||
<a class="nav-link" href="#" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
data-bs-original-title="Simpan Identitas & Responden terlebih dahulu">
|
||||
<i class="icon-base ti tabler-users-group me-1_5 icon-sm"></i>Pengurus & Anggota
|
||||
</a>
|
||||
@endif
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
@if(isset($kelompokTani))
|
||||
<a class="nav-link {{ request()->routeIs('kelompok-tani.edit') && request()->query('section') === 'kelembagaan' ? 'active' : '' }}"
|
||||
@@ -155,7 +164,7 @@
|
||||
</a>
|
||||
@else
|
||||
<a class="nav-link" href="#" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
data-bs-original-title="Simpan Identitas & Responden terlebih dahulu">
|
||||
data-bs-original-title="Simpan Profil terlebih dahulu">
|
||||
<i class="icon-base ti tabler-building-community me-1_5 icon-sm"></i>Kelembagaan
|
||||
</a>
|
||||
@endif
|
||||
@@ -164,25 +173,12 @@
|
||||
@if(isset($kelompokTani))
|
||||
<a class="nav-link {{ request()->routeIs('kelompok-tani.edit') && request()->query('section') === 'aktivitas-fungsi' ? 'active' : '' }}"
|
||||
href="{{ route('kelompok-tani.edit', $kelompokTani->id) }}?section=aktivitas-fungsi">
|
||||
<i class="icon-base ti tabler-clipboard-list me-1_5 icon-sm"></i>Aktivitas & Fungsi
|
||||
<i class="icon-base ti tabler-clipboard-list me-1_5 icon-sm"></i>Operasional
|
||||
</a>
|
||||
@else
|
||||
<a class="nav-link" href="#" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
data-bs-original-title="Simpan Identitas & Responden terlebih dahulu">
|
||||
<i class="icon-base ti tabler-clipboard-list me-1_5 icon-sm"></i>Aktivitas & Fungsi
|
||||
</a>
|
||||
@endif
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
@if(isset($kelompokTani))
|
||||
<a class="nav-link {{ request()->routeIs('kelompok-tani.aset-keuangan.*') ? 'active' : '' }}"
|
||||
href="{{ route('kelompok-tani.aset-keuangan.index', $kelompokTani->id) }}">
|
||||
<i class="icon-base ti tabler-wallet me-1_5 icon-sm"></i>Aset & Keuangan
|
||||
</a>
|
||||
@else
|
||||
<a class="nav-link" href="#" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
data-bs-original-title="Simpan Identitas & Responden terlebih dahulu">
|
||||
<i class="icon-base ti tabler-wallet me-1_5 icon-sm"></i>Aset & Keuangan
|
||||
data-bs-original-title="Simpan Profil terlebih dahulu">
|
||||
<i class="icon-base ti tabler-clipboard-list me-1_5 icon-sm"></i>Operasional
|
||||
</a>
|
||||
@endif
|
||||
</li>
|
||||
@@ -190,25 +186,12 @@
|
||||
@if(isset($kelompokTani))
|
||||
<a class="nav-link {{ request()->routeIs('kelompok-tani.edit') && request()->query('section') === 'sosial-konflik' ? 'active' : '' }}"
|
||||
href="{{ route('kelompok-tani.edit', $kelompokTani->id) }}?section=sosial-konflik">
|
||||
<i class="icon-base ti tabler-heart-handshake me-1_5 icon-sm"></i>Sosial & Konflik
|
||||
<i class="icon-base ti tabler-heart-handshake me-1_5 icon-sm"></i>Sosial
|
||||
</a>
|
||||
@else
|
||||
<a class="nav-link" href="#" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
data-bs-original-title="Simpan Identitas & Responden terlebih dahulu">
|
||||
<i class="icon-base ti tabler-heart-handshake me-1_5 icon-sm"></i>Sosial & Konflik
|
||||
</a>
|
||||
@endif
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
@if(isset($kelompokTani))
|
||||
<a class="nav-link {{ request()->routeIs('kelompok-tani.bantuan-penilaian.*') ? 'active' : '' }}"
|
||||
href="{{ route('kelompok-tani.bantuan-penilaian.index', $kelompokTani->id) }}">
|
||||
<i class="icon-base ti tabler-report me-1_5 icon-sm"></i>Bantuan & Penilaian
|
||||
</a>
|
||||
@else
|
||||
<a class="nav-link" href="#" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
data-bs-original-title="Simpan Identitas & Responden terlebih dahulu">
|
||||
<i class="icon-base ti tabler-report me-1_5 icon-sm"></i>Bantuan & Penilaian
|
||||
data-bs-original-title="Simpan Profil terlebih dahulu">
|
||||
<i class="icon-base ti tabler-heart-handshake me-1_5 icon-sm"></i>Sosial
|
||||
</a>
|
||||
@endif
|
||||
</li>
|
||||
|
||||
@@ -1,29 +1,179 @@
|
||||
@extends('layout.main')
|
||||
|
||||
|
||||
@push('style')
|
||||
<style>
|
||||
.kelompok-tani-row { cursor: pointer; }
|
||||
.kelompok-filter-row { padding-block: 1.5rem; }
|
||||
.kelompok-filter-line { display: flex; align-items: center; flex-wrap: wrap; gap: .5rem; }
|
||||
.kelompok-filter-label { display: flex; align-items: center; font-weight: 500; margin-right: .5rem; white-space: nowrap; }
|
||||
.kelompok-filter-line .form-select,
|
||||
.kelompok-filter-line .btn { min-height: 31px; }
|
||||
.kelompok-filter-line .form-select {
|
||||
background-color: var(--bs-paper-bg);
|
||||
border-color: var(--bs-border-color);
|
||||
border-radius: var(--bs-border-radius);
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
.kelompok-filter-line .form-select:focus {
|
||||
border-color: rgba(40, 167, 69, .55);
|
||||
box-shadow: 0 0 0 .2rem rgba(40, 167, 69, .12);
|
||||
}
|
||||
.kelompok-filter-line .btn { white-space: nowrap; }
|
||||
.kelompok-filter-static {
|
||||
display: flex; align-items: center; min-height: 31px;
|
||||
padding: .25rem .75rem; font-size: .8125rem;
|
||||
background-color: var(--bs-paper-bg);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: var(--bs-border-radius);
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
.kelompok-filter-line .input-group-sm { width: 240px; }
|
||||
@media (max-width: 767.98px) {
|
||||
.kelompok-filter-line { display: grid; grid-template-columns: minmax(0, 1fr); width: 100%; }
|
||||
.kelompok-filter-label { margin-right: 0; }
|
||||
.kelompok-filter-line .btn { justify-content: center; width: 100%; }
|
||||
.kelompok-filter-line .input-group-sm { width: 100%; }
|
||||
.kelompok-filter-line .input-group .btn { width: auto; }
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="container-xxl flex-grow-1 container-p-y">
|
||||
<div class="container-fluid flex-grow-1 container-p-y kelompok-tani-page">
|
||||
@include('produktivitas.survey-kelompok-tani._alert')
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12 col-md-4">
|
||||
<x-summary-card
|
||||
title="Kelompok Tani"
|
||||
count="{{ number_format($summary->total_kelompok, 0, ',', '.') }}"
|
||||
subtitle="{{ number_format($summary->total_jenis, 0, ',', '.') }} jenis kelompok"
|
||||
icon="users-group"
|
||||
color="primary"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<x-summary-card
|
||||
title="Total Pengurus"
|
||||
count="{{ number_format($summary->total_pengurus, 0, ',', '.') }}"
|
||||
subtitle="{{ number_format($summary->pengurus_aktif, 0, ',', '.') }} pengurus aktif"
|
||||
icon="user-star"
|
||||
color="success"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<x-summary-card
|
||||
title="Total Anggota"
|
||||
count="{{ number_format($summary->total_anggota, 0, ',', '.') }}"
|
||||
subtitle="{{ number_format($summary->anggota_aktif, 0, ',', '.') }} anggota aktif"
|
||||
icon="users"
|
||||
color="info"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-6 kelompok-filter-row">
|
||||
<div class="col-12">
|
||||
<div class="card card-border-shadow-primary">
|
||||
<div class="card-body py-3">
|
||||
<form method="GET" action="{{ route('kelompok-tani.index') }}" id="kelompok-filter-form" aria-label="Filter data kelompok tani">
|
||||
<input type="hidden" name="per_page" value="{{ $kelompokTani->perPage() }}">
|
||||
<div class="kelompok-filter-line">
|
||||
<div class="kelompok-filter-label">
|
||||
<i class="icon-base ti tabler-map-2 me-2"></i><span>Wilayah</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="hidden" name="kabupaten_kota_id" value="{{ $filters['kabupaten_kota_id'] }}">
|
||||
<div class="kelompok-filter-static" aria-label="Kabupaten/Kota" role="text">
|
||||
{{ preg_replace('/^(kabupaten|kota)\s+/i', '', $selectedKabupaten?->nama ?? 'Semua Kabupaten/Kota') }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<select name="kecamatan_id" id="filter-kecamatan" class="form-select form-select-sm" aria-label="Filter Kecamatan" @disabled(empty($filters['kabupaten_kota_id']))>
|
||||
<option value="">Semua Kecamatan</option>
|
||||
@foreach($kecamatanOptions as $option)
|
||||
<option value="{{ $option->id }}" @selected((string) $filters['kecamatan_id'] === (string) $option->id)>{{ $option->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<select name="desa_kelurahan_id" id="filter-desa" class="form-select form-select-sm" aria-label="Filter Desa" @disabled(empty($filters['kecamatan_id']))>
|
||||
<option value="">Semua Desa</option>
|
||||
@foreach($desaOptions as $option)
|
||||
<option value="{{ $option->id }}" @selected((string) $filters['desa_kelurahan_id'] === (string) $option->id)>{{ $option->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-3">
|
||||
|
||||
<div class="kelompok-filter-line">
|
||||
<div class="kelompok-filter-label">
|
||||
<i class="icon-base ti tabler-adjustments-horizontal me-2"></i><span>Data</span>
|
||||
</div>
|
||||
<div>
|
||||
<select name="komoditas_id" class="form-select form-select-sm" aria-label="Filter Komoditas">
|
||||
<option value="">Semua Komoditas</option>
|
||||
@foreach($komoditasOptions as $option)
|
||||
<option value="{{ $option->id }}" @selected((string) $filters['komoditas_id'] === (string) $option->id)>{{ $option->nama }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<select name="jenis_kelompok" class="form-select form-select-sm" aria-label="Filter Jenis Kelompok">
|
||||
<option value="">Semua Jenis Kelompok</option>
|
||||
@foreach($jenisKelompokOptions as $jenis)
|
||||
<option value="{{ $jenis }}" @selected($filters['jenis_kelompok'] === $jenis)>{{ $jenis }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<select name="sort_by" class="form-select form-select-sm" aria-label="Urutkan data kelompok tani">
|
||||
<option value="">Urut Terbaru</option>
|
||||
<option value="nama_asc" @selected($filters['sort_by'] === 'nama_asc')>Nama A-Z</option>
|
||||
<option value="nama_desc" @selected($filters['sort_by'] === 'nama_desc')>Nama Z-A</option>
|
||||
<option value="anggota_desc" @selected($filters['sort_by'] === 'anggota_desc')>Anggota Terbanyak</option>
|
||||
<option value="anggota_asc" @selected($filters['sort_by'] === 'anggota_asc')>Anggota Tersedikit</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" name="search" class="form-control" placeholder="Cari kelompok / responden..." value="{{ $filters['search'] }}" aria-label="Cari nama kelompok atau responden">
|
||||
<button type="submit" class="btn btn-outline-secondary" aria-label="Cari data kelompok tani"><i class="icon-base ti tabler-search"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-outline-secondary btn-sm"><i class="icon-base ti tabler-filter me-1"></i>Filter</button>
|
||||
<a href="{{ route('kelompok-tani.index') }}" class="btn btn-outline-secondary btn-sm"><i class="icon-base ti tabler-refresh me-1"></i>Reset</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header border-bottom">
|
||||
<h5 class="card-title mb-0">Data Kelompok Tani</h5>
|
||||
</div>
|
||||
|
||||
<div class="card-body border-bottom pt-4">
|
||||
<form method="GET" action="{{ route('kelompok-tani.index') }}" class="d-flex justify-content-between align-items-center row gap-3 gap-md-0">
|
||||
<form method="GET" action="{{ route('kelompok-tani.index') }}" class="row g-3 align-items-end">
|
||||
@foreach($filters as $field => $value)
|
||||
<input type="hidden" name="{{ $field }}" value="{{ $value }}">
|
||||
@endforeach
|
||||
<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>
|
||||
<option value="10" @selected($kelompokTani->perPage() === 10)>10</option>
|
||||
<option value="25" @selected($kelompokTani->perPage() === 25)>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>
|
||||
<div class="col-md-auto ms-md-auto">
|
||||
<a href="{{ route('kelompok-tani.create') }}" class="btn btn-sm btn-primary">
|
||||
<i class="ti tabler-plus me-1"></i> Tambah
|
||||
</a>
|
||||
@@ -46,9 +196,9 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($kelompokTani as $index => $item)
|
||||
<tr>
|
||||
<td>{{ $loop->iteration }}</td>
|
||||
<td><span class="text-heading">{{ $item->nama_kelompok }}</span></td>
|
||||
<tr class="kelompok-tani-row">
|
||||
<td>{{ $kelompokTani->firstItem() + $loop->index }}</td>
|
||||
<td><a href="{{ route('kelompok-tani.edit', $item->id) }}" class="text-heading d-block">{{ $item->nama_kelompok }}</a></td>
|
||||
<td><span class="text-muted">{{ $item->desaKelurahan?->nama }}</span></td>
|
||||
<td><span class="text-muted">{{ $item->jenis_kelompok }}</span></td>
|
||||
<td><span class="text-muted">{{ $item->tahun_berdiri }}</span></td>
|
||||
@@ -67,7 +217,7 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-center justify-content-md-end border-top">
|
||||
{{ $kelompokTani->appends(request()->query())->links('pagination::bootstrap-5') }}
|
||||
{{ $kelompokTani->links('pagination::bootstrap-5') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,6 +229,37 @@
|
||||
const csrfToken = "{{ csrf_token() }}";
|
||||
|
||||
$(document).ready(function () {
|
||||
const $kecamatanFilter = $('#filter-kecamatan');
|
||||
const $desaFilter = $('#filter-desa');
|
||||
const desaUrlTemplate = '{{ route("desakelurahan.bykecamatan", ":id") }}';
|
||||
let desaRequest;
|
||||
|
||||
$kecamatanFilter.on('change', function () {
|
||||
if (desaRequest) desaRequest.abort();
|
||||
$desaFilter.empty().append(new Option('Semua Desa', '')).prop('disabled', true);
|
||||
const kecamatanId = $(this).val();
|
||||
if (!kecamatanId) return;
|
||||
|
||||
desaRequest = $.get(desaUrlTemplate.replace(':id', kecamatanId), function (data) {
|
||||
data.forEach(function (desa) {
|
||||
$desaFilter.append(new Option(desa.nama, desa.id));
|
||||
});
|
||||
$desaFilter.prop('disabled', false);
|
||||
}).fail(function (xhr, status) {
|
||||
if (status !== 'abort') {
|
||||
$desaFilter.empty().append(new Option('Gagal memuat desa', ''));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.kelompok-tani-row', function (event) {
|
||||
if ($(event.target).closest('a, button, input, select, textarea, label').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = this.querySelector('.edit-record').href;
|
||||
});
|
||||
|
||||
$(document).on('click', '.delete-record', function () {
|
||||
const id = $(this).data('id');
|
||||
const name = $(this).data('nama');
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
@extends('layout.main')
|
||||
|
||||
@section('content')
|
||||
<div class="container-xxl flex-grow-1 container-p-y">
|
||||
<div class="container-xxl flex-grow-1 container-p-y kelompok-tani-page">
|
||||
@include('produktivitas.survey-kelompok-tani.header', ['kelompokTani' => $kelompokTani])
|
||||
|
||||
<div class="card mb-6" id="section-kelembagaan">
|
||||
<div class="card-body pt-6">
|
||||
@include('produktivitas.survey-kelompok-tani._alert', [
|
||||
'section' => 'kelembagaan',
|
||||
'errorFields' => ['cakupan_wilayah', 'nama_wilayah_cakupan', 'status_legalitas', 'nomor_sk', 'tanggal_tahun_penerbitan_sk', 'tingkat_keaktifan_ketua', 'status_pembagian_peran', 'status_dokumentasi_rapat', 'frekuensi_pertemuan', 'tanggal_pertemuan_terakhir'],
|
||||
])
|
||||
|
||||
<form method="POST" action="{{ route('kelompok-tani.update', $kelompokTani->id) }}">
|
||||
<form novalidate method="POST" action="{{ route('kelompok-tani.update', $kelompokTani->id) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<input type="hidden" name="section" value="kelembagaan">
|
||||
@@ -20,7 +15,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4 form-control-validation">
|
||||
<label class="form-label" for="klb-cakupan-wilayah">Cakupan Wilayah Keanggotaan</label>
|
||||
<select class="form-control @error('cakupan_wilayah') is-invalid @enderror" id="klb-cakupan-wilayah" name="cakupan_wilayah">
|
||||
<select class="form-select @error('cakupan_wilayah') is-invalid @enderror" id="klb-cakupan-wilayah" name="cakupan_wilayah">
|
||||
<option value="">Pilih Cakupan Wilayah</option>
|
||||
@foreach(['RT', 'RW', 'Desa', 'Kecamatan', 'Kabupaten'] as $option)
|
||||
<option value="{{ $option }}" {{ old('cakupan_wilayah', $kelompokTani->cakupan_wilayah) == $option ? 'selected' : '' }}>{{ $option }}</option>
|
||||
@@ -37,11 +32,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@php
|
||||
$statusDenganSk = \App\Models\Pertanian\KelompokTani::STATUS_LEGALITAS_DENGAN_SK;
|
||||
$skAktif = in_array(old('status_legalitas', $kelompokTani->status_legalitas), $statusDenganSk, true);
|
||||
@endphp
|
||||
<h5 class="pb-4 border-bottom mb-4 mt-4">Status Legalitas Kelompok</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label" for="klb-status-legalitas">Status SK/Dokumen Pendaftaran</label>
|
||||
<select class="form-control @error('status_legalitas') is-invalid @enderror" id="klb-status-legalitas" name="status_legalitas">
|
||||
<select class="form-select @error('status_legalitas') is-invalid @enderror" id="klb-status-legalitas" name="status_legalitas">
|
||||
<option value="">Pilih Status Legalitas</option>
|
||||
@foreach([
|
||||
'Belum ada SK/dokumen apa pun',
|
||||
@@ -56,95 +55,28 @@
|
||||
@error('status_legalitas')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="klb-nomor-sk">Nomor SK (jika ada)</label>
|
||||
<label class="form-label" for="klb-nomor-sk">Nomor SK</label>
|
||||
<input type="text" class="form-control @error('nomor_sk') is-invalid @enderror" id="klb-nomor-sk" name="nomor_sk"
|
||||
value="{{ old('nomor_sk', $kelompokTani->nomor_sk) }}" />
|
||||
value="{{ old('nomor_sk', $kelompokTani->nomor_sk) }}" @disabled(!$skAktif) />
|
||||
@error('nomor_sk')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="klb-tanggal-penerbitan-sk">Tanggal/Tahun Penerbitan</label>
|
||||
<input type="text" class="form-control @error('tanggal_tahun_penerbitan_sk') is-invalid @enderror" id="klb-tanggal-penerbitan-sk" name="tanggal_tahun_penerbitan_sk"
|
||||
placeholder="Cth: 12-01-2020 atau 2020" value="{{ old('tanggal_tahun_penerbitan_sk', $kelompokTani->tanggal_tahun_penerbitan_sk) }}" />
|
||||
placeholder="Cth: 12-01-2020 atau 2020" value="{{ old('tanggal_tahun_penerbitan_sk', $kelompokTani->tanggal_tahun_penerbitan_sk) }}" @disabled(!$skAktif) />
|
||||
@error('tanggal_tahun_penerbitan_sk')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4 mt-4">Kualitas Kepemimpinan</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4 form-control-validation">
|
||||
<label class="form-label" for="klb-keaktifan-ketua">Keaktifan Ketua Kelompok</label>
|
||||
<select class="form-control @error('tingkat_keaktifan_ketua') is-invalid @enderror" id="klb-keaktifan-ketua" name="tingkat_keaktifan_ketua">
|
||||
<option value="">Pilih Tingkat Keaktifan</option>
|
||||
@foreach([
|
||||
'Tidak aktif/jarang hadir',
|
||||
'Cukup aktif',
|
||||
'Sangat aktif dan menjalankan tanggung jawab dengan baik',
|
||||
] as $option)
|
||||
<option value="{{ $option }}" {{ old('tingkat_keaktifan_ketua', $kelompokTani->tingkat_keaktifan_ketua) == $option ? 'selected' : '' }}>{{ $option }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('tingkat_keaktifan_ketua')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-4 mb-4 form-control-validation">
|
||||
<label class="form-label" for="klb-pembagian-peran">Pembagian Peran Pengurus</label>
|
||||
<select class="form-control @error('status_pembagian_peran') is-invalid @enderror" id="klb-pembagian-peran" name="status_pembagian_peran">
|
||||
<option value="">Pilih Status Pembagian Peran</option>
|
||||
@foreach([
|
||||
'Tidak ada pembagian peran',
|
||||
'Ada tapi tidak berjalan',
|
||||
'Ada dan berjalan dengan baik',
|
||||
] as $option)
|
||||
<option value="{{ $option }}" {{ old('status_pembagian_peran', $kelompokTani->status_pembagian_peran) == $option ? 'selected' : '' }}>{{ $option }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('status_pembagian_peran')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-4 mb-4 form-control-validation">
|
||||
<label class="form-label" for="klb-dokumentasi-rapat">Dokumentasi Rapat (Notulen/Daftar Hadir)</label>
|
||||
<select class="form-control @error('status_dokumentasi_rapat') is-invalid @enderror" id="klb-dokumentasi-rapat" name="status_dokumentasi_rapat">
|
||||
<option value="">Pilih Status Dokumentasi</option>
|
||||
@foreach([
|
||||
'Tidak ada',
|
||||
'Ada, tidak rutin',
|
||||
'Ada, rutin setiap pertemuan',
|
||||
] as $option)
|
||||
<option value="{{ $option }}" {{ old('status_dokumentasi_rapat', $kelompokTani->status_dokumentasi_rapat) == $option ? 'selected' : '' }}>{{ $option }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('status_dokumentasi_rapat')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4 mt-4">Frekuensi Pertemuan Rutin</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label" for="klb-frekuensi-pertemuan">Frekuensi Pertemuan</label>
|
||||
<select class="form-control @error('frekuensi_pertemuan') is-invalid @enderror" id="klb-frekuensi-pertemuan" name="frekuensi_pertemuan">
|
||||
<option value="">Pilih Frekuensi Pertemuan</option>
|
||||
@foreach([
|
||||
'Tidak pernah/sudah tidak aktif',
|
||||
'Tidak rutin/tidak terjadwal',
|
||||
'1x per 2-3 bulan',
|
||||
'1x per bulan',
|
||||
'Lebih dari 1x per bulan',
|
||||
] as $option)
|
||||
<option value="{{ $option }}" {{ old('frekuensi_pertemuan', $kelompokTani->frekuensi_pertemuan) == $option ? 'selected' : '' }}>{{ $option }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('frekuensi_pertemuan')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label" for="klb-tanggal-pertemuan-terakhir">Tanggal Pertemuan Terakhir</label>
|
||||
<input type="text" class="form-control @error('tanggal_pertemuan_terakhir') is-invalid @enderror" id="klb-tanggal-pertemuan-terakhir" name="tanggal_pertemuan_terakhir"
|
||||
value="{{ old('tanggal_pertemuan_terakhir', $kelompokTani->tanggal_pertemuan_terakhir?->format('Y-m-d')) }}" />
|
||||
@error('tanggal_pertemuan_terakhir')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<button type="submit" class="btn btn-success wave-effect wave-light">
|
||||
<i class="ti tabler-device-floppy me-2"></i> Simpan Kelembagaan
|
||||
</button>
|
||||
<div class="d-inline-flex flex-wrap justify-content-end gap-2">
|
||||
<a href="{{ route('kelompok-tani.index') }}" class="btn btn-secondary wave-effect wave-light">
|
||||
Kembali
|
||||
</a>
|
||||
<button type="submit" class="btn btn-success wave-effect wave-light">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm" aria-hidden="true"></i> Simpan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -156,12 +88,15 @@
|
||||
<script>
|
||||
'use strict';
|
||||
$(document).ready(function () {
|
||||
$('#klb-cakupan-wilayah, #klb-status-legalitas, #klb-keaktifan-ketua, #klb-pembagian-peran, #klb-dokumentasi-rapat, #klb-frekuensi-pertemuan').select2();
|
||||
$('#klb-tanggal-pertemuan-terakhir').flatpickr({
|
||||
dateFormat: 'Y-m-d',
|
||||
altInput: true,
|
||||
altFormat: 'd-m-Y'
|
||||
});
|
||||
$('#klb-cakupan-wilayah, #klb-status-legalitas').select2();
|
||||
const statusDenganSk = @json($statusDenganSk);
|
||||
function toggleSkFields() {
|
||||
const skAktif = statusDenganSk.includes($('#klb-status-legalitas').val());
|
||||
$('#klb-nomor-sk, #klb-tanggal-penerbitan-sk').prop('disabled', !skAktif);
|
||||
}
|
||||
$('#klb-status-legalitas').on('change', toggleSkFields);
|
||||
toggleSkFields();
|
||||
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@@ -1,5 +1,65 @@
|
||||
@extends('layout.main')
|
||||
@if(($bagianKeanggotaan ?? 'pengurus') === 'ringkasan')
|
||||
<div id="section-ringkasan" class="mt-4">
|
||||
<h5 class="pb-4 border-bottom mb-4">Jumlah Pengurus & Anggota</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="pa-jumlah-pengurus">Jumlah Pengurus (total)</label>
|
||||
<input type="number" min="0" class="form-control @error('jumlah_pengurus') is-invalid @enderror" id="pa-jumlah-pengurus" name="jumlah_pengurus"
|
||||
value="{{ old('jumlah_pengurus', $kelompokTani?->jumlah_pengurus) }}" />
|
||||
@error('jumlah_pengurus')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="pa-jumlah-pengurus-aktif">Jumlah Pengurus Aktif</label>
|
||||
<input type="number" min="0" class="form-control @error('jumlah_pengurus_aktif') is-invalid @enderror" id="pa-jumlah-pengurus-aktif" name="jumlah_pengurus_aktif"
|
||||
value="{{ old('jumlah_pengurus_aktif', $kelompokTani?->jumlah_pengurus_aktif) }}" />
|
||||
@error('jumlah_pengurus_aktif')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="pa-jumlah-anggota-terdaftar">Jumlah Anggota Terdaftar</label>
|
||||
<input type="number" min="0" class="form-control @error('jumlah_anggota_terdaftar') is-invalid @enderror" id="pa-jumlah-anggota-terdaftar" name="jumlah_anggota_terdaftar"
|
||||
value="{{ old('jumlah_anggota_terdaftar', $kelompokTani?->jumlah_anggota_terdaftar) }}" />
|
||||
@error('jumlah_anggota_terdaftar')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="pa-jumlah-anggota-aktif">Jumlah Anggota Aktif <small class="text-muted">(hadir pertemuan rutin)</small></label>
|
||||
<input type="number" min="0" class="form-control @error('jumlah_anggota_aktif') is-invalid @enderror" id="pa-jumlah-anggota-aktif" name="jumlah_anggota_aktif"
|
||||
value="{{ old('jumlah_anggota_aktif', $kelompokTani?->jumlah_anggota_aktif) }}" />
|
||||
@error('jumlah_anggota_aktif')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4 mt-4">Komposisi Gender</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4">
|
||||
<label class="form-label d-block">Jumlah Pengurus (Laki-laki / Perempuan)</label>
|
||||
<div class="d-flex gap-2">
|
||||
<input type="number" min="0" class="form-control @error('jumlah_pengurus_laki_laki') is-invalid @enderror" name="jumlah_pengurus_laki_laki"
|
||||
placeholder="Laki-laki" value="{{ old('jumlah_pengurus_laki_laki', $kelompokTani?->jumlah_pengurus_laki_laki) }}" />
|
||||
<input type="number" min="0" class="form-control @error('jumlah_pengurus_perempuan') is-invalid @enderror" name="jumlah_pengurus_perempuan"
|
||||
placeholder="Perempuan" value="{{ old('jumlah_pengurus_perempuan', $kelompokTani?->jumlah_pengurus_perempuan) }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<label class="form-label d-block">Jumlah Anggota (Laki-laki / Perempuan)</label>
|
||||
<div class="d-flex gap-2">
|
||||
<input type="number" min="0" class="form-control @error('jumlah_anggota_laki_laki') is-invalid @enderror" name="jumlah_anggota_laki_laki"
|
||||
placeholder="Laki-laki" value="{{ old('jumlah_anggota_laki_laki', $kelompokTani?->jumlah_anggota_laki_laki) }}" />
|
||||
<input type="number" min="0" class="form-control @error('jumlah_anggota_perempuan') is-invalid @enderror" name="jumlah_anggota_perempuan"
|
||||
placeholder="Perempuan" value="{{ old('jumlah_anggota_perempuan', $kelompokTani?->jumlah_anggota_perempuan) }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<label class="form-label d-block">Jumlah Anggota Pemuda (16-30 th) (Laki-laki / Perempuan)</label>
|
||||
<div class="d-flex gap-2">
|
||||
<input type="number" min="0" class="form-control @error('jumlah_anggota_pemuda_laki_laki') is-invalid @enderror" name="jumlah_anggota_pemuda_laki_laki"
|
||||
placeholder="Laki-laki" value="{{ old('jumlah_anggota_pemuda_laki_laki', $kelompokTani?->jumlah_anggota_pemuda_laki_laki) }}" />
|
||||
<input type="number" min="0" class="form-control @error('jumlah_anggota_pemuda_perempuan') is-invalid @enderror" name="jumlah_anggota_pemuda_perempuan"
|
||||
placeholder="Perempuan" value="{{ old('jumlah_anggota_pemuda_perempuan', $kelompokTani?->jumlah_anggota_pemuda_perempuan) }}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
@push('style')
|
||||
<style>
|
||||
.status-switch-row {
|
||||
@@ -26,99 +86,9 @@
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="container-xxl flex-grow-1 container-p-y">
|
||||
@include('produktivitas.survey-kelompok-tani.header', ['kelompokTani' => $kelompokTani])
|
||||
|
||||
{{-- RINGKASAN JUMLAH & KOMPOSISI GENDER --}}
|
||||
<div class="card mb-6" id="section-ringkasan">
|
||||
<div class="card-body pt-6">
|
||||
@include('produktivitas.survey-kelompok-tani._alert', [
|
||||
'section' => 'ringkasan',
|
||||
'errorFields' => ['jumlah_pengurus', 'jumlah_pengurus_aktif', 'jumlah_anggota_terdaftar', 'jumlah_anggota_aktif', 'jumlah_pengurus_laki_laki', 'jumlah_pengurus_perempuan', 'jumlah_anggota_laki_laki', 'jumlah_anggota_perempuan', 'jumlah_anggota_pemuda_laki_laki', 'jumlah_anggota_pemuda_perempuan'],
|
||||
])
|
||||
|
||||
<form method="POST" action="{{ route('kelompok-tani.update', $kelompokTani->id) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<input type="hidden" name="section" value="ringkasan-pengurus">
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4">Jumlah Pengurus & Anggota</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="pa-jumlah-pengurus">Jumlah Pengurus (total)</label>
|
||||
<input type="number" min="0" class="form-control @error('jumlah_pengurus') is-invalid @enderror" id="pa-jumlah-pengurus" name="jumlah_pengurus"
|
||||
value="{{ old('jumlah_pengurus', $kelompokTani->jumlah_pengurus) }}" />
|
||||
@error('jumlah_pengurus')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="pa-jumlah-pengurus-aktif">Jumlah Pengurus Aktif</label>
|
||||
<input type="number" min="0" class="form-control @error('jumlah_pengurus_aktif') is-invalid @enderror" id="pa-jumlah-pengurus-aktif" name="jumlah_pengurus_aktif"
|
||||
value="{{ old('jumlah_pengurus_aktif', $kelompokTani->jumlah_pengurus_aktif) }}" />
|
||||
@error('jumlah_pengurus_aktif')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="pa-jumlah-anggota-terdaftar">Jumlah Anggota Terdaftar</label>
|
||||
<input type="number" min="0" class="form-control @error('jumlah_anggota_terdaftar') is-invalid @enderror" id="pa-jumlah-anggota-terdaftar" name="jumlah_anggota_terdaftar"
|
||||
value="{{ old('jumlah_anggota_terdaftar', $kelompokTani->jumlah_anggota_terdaftar) }}" />
|
||||
@error('jumlah_anggota_terdaftar')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="pa-jumlah-anggota-aktif">Jumlah Anggota Aktif <small class="text-muted">(hadir pertemuan rutin)</small></label>
|
||||
<input type="number" min="0" class="form-control @error('jumlah_anggota_aktif') is-invalid @enderror" id="pa-jumlah-anggota-aktif" name="jumlah_anggota_aktif"
|
||||
value="{{ old('jumlah_anggota_aktif', $kelompokTani->jumlah_anggota_aktif) }}" />
|
||||
@error('jumlah_anggota_aktif')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4 mt-4">Komposisi Gender</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4">
|
||||
<label class="form-label d-block">Jumlah Pengurus (Laki-laki / Perempuan)</label>
|
||||
<div class="d-flex gap-2">
|
||||
<input type="number" min="0" class="form-control @error('jumlah_pengurus_laki_laki') is-invalid @enderror" name="jumlah_pengurus_laki_laki"
|
||||
placeholder="Laki-laki" value="{{ old('jumlah_pengurus_laki_laki', $kelompokTani->jumlah_pengurus_laki_laki) }}" />
|
||||
<input type="number" min="0" class="form-control @error('jumlah_pengurus_perempuan') is-invalid @enderror" name="jumlah_pengurus_perempuan"
|
||||
placeholder="Perempuan" value="{{ old('jumlah_pengurus_perempuan', $kelompokTani->jumlah_pengurus_perempuan) }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<label class="form-label d-block">Jumlah Anggota (Laki-laki / Perempuan)</label>
|
||||
<div class="d-flex gap-2">
|
||||
<input type="number" min="0" class="form-control @error('jumlah_anggota_laki_laki') is-invalid @enderror" name="jumlah_anggota_laki_laki"
|
||||
placeholder="Laki-laki" value="{{ old('jumlah_anggota_laki_laki', $kelompokTani->jumlah_anggota_laki_laki) }}" />
|
||||
<input type="number" min="0" class="form-control @error('jumlah_anggota_perempuan') is-invalid @enderror" name="jumlah_anggota_perempuan"
|
||||
placeholder="Perempuan" value="{{ old('jumlah_anggota_perempuan', $kelompokTani->jumlah_anggota_perempuan) }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<label class="form-label d-block">Jumlah Anggota Pemuda (16-30 th) <small class="text-muted">L / P</small></label>
|
||||
<div class="d-flex gap-2">
|
||||
<input type="number" min="0" class="form-control @error('jumlah_anggota_pemuda_laki_laki') is-invalid @enderror" name="jumlah_anggota_pemuda_laki_laki"
|
||||
placeholder="Laki-laki" value="{{ old('jumlah_anggota_pemuda_laki_laki', $kelompokTani->jumlah_anggota_pemuda_laki_laki) }}" />
|
||||
<input type="number" min="0" class="form-control @error('jumlah_anggota_pemuda_perempuan') is-invalid @enderror" name="jumlah_anggota_pemuda_perempuan"
|
||||
placeholder="Perempuan" value="{{ old('jumlah_anggota_pemuda_perempuan', $kelompokTani->jumlah_anggota_pemuda_perempuan) }}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<button type="submit" class="btn btn-success wave-effect wave-light">
|
||||
<i class="ti tabler-device-floppy me-2"></i> Simpan Ringkasan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- DAFTAR PENGURUS --}}
|
||||
<div class="card mb-6" id="section-pengurus">
|
||||
<div class="card-body pt-6">
|
||||
@include('produktivitas.survey-kelompok-tani._alert', [
|
||||
'section' => 'pengurus',
|
||||
'errorFields' => ['pengurus.*'],
|
||||
])
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4">Daftar Pengurus</h5>
|
||||
|
||||
{{-- DATA LAMA --}}
|
||||
@@ -159,7 +129,7 @@
|
||||
@endif
|
||||
|
||||
{{-- FORM TAMBAH DATA BARU --}}
|
||||
<form method="POST" action="{{ route('kelompok-tani.pengurus-anggota.store', $kelompokTani->id) }}" class="form-repeater-pengurus">
|
||||
<form novalidate method="POST" action="{{ route('kelompok-tani.pengurus-anggota.store', $kelompokTani->id) }}" class="form-repeater-pengurus">
|
||||
@csrf
|
||||
|
||||
<div data-repeater-list="pengurus">
|
||||
@@ -200,19 +170,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 d-flex justify-content-between">
|
||||
<div class="mt-4 d-flex flex-wrap justify-content-between gap-2">
|
||||
<button type="button" class="btn btn-outline-success" id="btn-tambah-pengurus" data-repeater-create disabled>
|
||||
<i class="icon-base ti tabler-plus me-1_5 icon-sm"></i> Tambah Pengurus
|
||||
</button>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm"></i> Simpan Daftar Pengurus
|
||||
</button>
|
||||
<div class="d-inline-flex flex-wrap justify-content-end gap-2">
|
||||
<a href="{{ route('kelompok-tani.index') }}" class="btn btn-secondary wave-effect wave-light">
|
||||
Kembali
|
||||
</a>
|
||||
<button type="submit" class="btn btn-success wave-effect wave-light">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm" aria-hidden="true"></i> Simpan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
@@ -316,3 +289,4 @@ $(function () {
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@endif
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
@extends('layout.main')
|
||||
|
||||
@section('content')
|
||||
<div class="container-xxl flex-grow-1 container-p-y">
|
||||
<div class="container-xxl flex-grow-1 container-p-y kelompok-tani-page">
|
||||
@include('produktivitas.survey-kelompok-tani.header', ['kelompokTani' => $kelompokTani])
|
||||
|
||||
@php
|
||||
$pernahKonflik = in_array(old('konflik_kelompok', $kelompokTani->konflik_kelompok), [
|
||||
'Pernah, sudah selesai', 'Pernah, masih berlanjut/belum selesai',
|
||||
], true);
|
||||
@endphp
|
||||
|
||||
<div class="card mb-6" id="section-sosial-konflik">
|
||||
<div class="card-body pt-6">
|
||||
@include('produktivitas.survey-kelompok-tani._alert', [
|
||||
'section' => 'sosial-konflik',
|
||||
'errorFields' => ['partisipasi_perempuan', 'konflik_kelompok', 'topik_konflik', 'penyelesaian_konflik'],
|
||||
])
|
||||
|
||||
<form method="POST" action="{{ route('kelompok-tani.update', $kelompokTani->id) }}">
|
||||
<form novalidate method="POST" action="{{ route('kelompok-tani.update', $kelompokTani->id) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<input type="hidden" name="section" value="sosial-konflik">
|
||||
@@ -20,7 +21,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label" for="sk-partisipasi-perempuan">Apakah Perempuan Terlibat Aktif dalam Kegiatan & Pengambilan Keputusan?</label>
|
||||
<select class="form-control @error('partisipasi_perempuan') is-invalid @enderror" id="sk-partisipasi-perempuan" name="partisipasi_perempuan">
|
||||
<select class="form-select @error('partisipasi_perempuan') is-invalid @enderror" id="sk-partisipasi-perempuan" name="partisipasi_perempuan">
|
||||
<option value="">Pilih Status</option>
|
||||
@foreach([
|
||||
'Tidak terlibat',
|
||||
@@ -35,13 +36,10 @@
|
||||
</div>
|
||||
|
||||
<h5 class="pb-4 border-bottom mb-4 mt-4">Riwayat Konflik Internal Kelompok</h5>
|
||||
<p class="text-muted" style="font-size: 0.8rem; margin-top: -0.75rem;">
|
||||
Topik ini cukup sensitif. Kalau responden tidak nyaman menjawab, pilih "Tidak bersedia menjawab".
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label" for="sk-konflik-kelompok">Pernah Terjadi Konflik Internal?</label>
|
||||
<select class="form-control @error('konflik_kelompok') is-invalid @enderror" id="sk-konflik-kelompok" name="konflik_kelompok">
|
||||
<select class="form-select @error('konflik_kelompok') is-invalid @enderror" id="sk-konflik-kelompok" name="konflik_kelompok">
|
||||
<option value="">Pilih Status</option>
|
||||
@foreach([
|
||||
'Tidak pernah',
|
||||
@@ -56,11 +54,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" id="sk-detail-konflik-wrapper" style="display: none;">
|
||||
<div class="row" id="sk-detail-konflik-wrapper" @if(!$pernahKonflik) style="display: none;" @endif>
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
<label class="form-label" for="sk-topik-konflik">Jika Pernah, Soal Apa?</label>
|
||||
<label class="form-label" for="sk-topik-konflik">Jika Pernah, Soal Apa? <span class="text-danger" id="sk-topik-konflik-required" @if(!$pernahKonflik) hidden @endif>*</span></label>
|
||||
<input type="text" class="form-control @error('topik_konflik') is-invalid @enderror" id="sk-topik-konflik" name="topik_konflik"
|
||||
placeholder="Cth: Kepemimpinan, pembagian hasil" value="{{ old('topik_konflik', $kelompokTani->topik_konflik) }}" />
|
||||
placeholder="Contoh: Kepemimpinan, pembagian hasil" value="{{ old('topik_konflik', $kelompokTani->topik_konflik) }}" @required($pernahKonflik) @disabled(!$pernahKonflik) />
|
||||
@error('topik_konflik')<div class="error invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-md-6 mb-4 form-control-validation">
|
||||
@@ -71,9 +69,14 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<button type="submit" class="btn btn-success wave-effect wave-light">
|
||||
<i class="ti tabler-device-floppy me-2"></i> Simpan Sosial & Konflik
|
||||
</button>
|
||||
<div class="d-inline-flex flex-wrap justify-content-end gap-2">
|
||||
<a href="{{ route('kelompok-tani.index') }}" class="btn btn-secondary wave-effect wave-light">
|
||||
Kembali
|
||||
</a>
|
||||
<button type="submit" class="btn btn-success wave-effect wave-light">
|
||||
<i class="icon-base ti tabler-check me-1_5 icon-sm" aria-hidden="true"></i> Simpan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -89,7 +92,10 @@
|
||||
|
||||
function toggleDetailKonflik() {
|
||||
var val = $('#sk-konflik-kelompok').val();
|
||||
$('#sk-detail-konflik-wrapper').toggle(val === 'Pernah, sudah selesai' || val === 'Pernah, masih berlanjut/belum selesai');
|
||||
var pernah = val === 'Pernah, sudah selesai' || val === 'Pernah, masih berlanjut/belum selesai';
|
||||
$('#sk-detail-konflik-wrapper').toggle(pernah);
|
||||
$('#sk-topik-konflik').prop('required', pernah).prop('disabled', !pernah);
|
||||
$('#sk-topik-konflik-required').prop('hidden', !pernah);
|
||||
}
|
||||
toggleDetailKonflik();
|
||||
$('#sk-konflik-kelompok').on('change', toggleDetailKonflik);
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
@extends('layout.main')
|
||||
|
||||
@section('content')
|
||||
<div class="container-xxl flex-grow-1 container-p-y">
|
||||
<div class="card">
|
||||
<div class="card-header border-bottom mb-4">
|
||||
<h5 class="card-title mb-0">Edit Petani</h5>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<form id="viewPetaniForm">
|
||||
@if ($errors->any())
|
||||
<div class="alert alert-danger">
|
||||
<ul class="mb-0">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<h6 class="mb-3">Informasi Petani</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="edit-petani-nama">Nama Petani</label>
|
||||
<input type="text" class="form-control" id="edit-petani-nama" name="nama"
|
||||
placeholder="Cth: John Doe" value="{{ old('nama', $petani->nama) }}" readonly/>
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="edit-petani-jenis-kelamin">Jenis Kelamin</label>
|
||||
<input type="text" class="form-control" id="edit-petani-jenis-kelamin" name="jenis_kelamin"
|
||||
value="{{ old('jenis_kelamin', $petani->jenis_kelamin) == '1' ? 'Laki-laki' : 'Perempuan' }}" readonly/>
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="edit-petani-tempat-lahir">Tempat Lahir</label>
|
||||
<input type="text" class="form-control" id="edit-petani-tempat-lahir" name="tempat_lahir" placeholder="Cth: Ngada"
|
||||
value="{{ old('tempat_lahir', $petani->tempat_lahir) }}" />
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="edit-petani-tanggal-lahir">Tanggal Lahir</label>
|
||||
<input type="date" class="form-control" id="edit-petani-tanggal-lahir" name="tanggal_lahir"
|
||||
value="{{ old('tanggal_lahir', $petani->tanggal_lahir?->format('d-m-Y')) }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="edit-petani-pendidikan-terakhir">Pendidikan Terakhir</label>
|
||||
<input type="text" class="form-control" id="edit-petani-pendidikan-terakhir" name="pendidikan_terakhir"
|
||||
value="{{ old('pendidikan_terakhir', $petani->pendidikan_terakhir?->label()) }}" readonly/>
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="edit-petani-status-pernikahan">Status Pernikahan<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="edit-petani-status-pernikahan" name="status_perkawinan"
|
||||
value="{{ old('status_perkawinan', $petani->status_perkawinan?->label()) }}" readonly/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-4 form-control-validation">
|
||||
<label class="form-label" for="edit-petani-alamat">Alamat</label>
|
||||
<input type="text" class="form-control" id="edit-petani-alamat" name="alamat"
|
||||
placeholder="Cth: Alamat lengkap petani" value="{{ old('alamat', $petani->alamat) }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="edit-petani-provinsi">Provinsi</label>
|
||||
<input type="text" class="form-control" id="edit-petani-provinsi" name="provinsi"
|
||||
value="{{ old('provinsi', $petani->desaKelurahan?->kecamatan->kabupatenKota->provinsi->nama) }}" readonly/>
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="edit-petani-kabupaten">Kabupaten/Kota</label>
|
||||
<input type="text" class="form-control" id="edit-petani-kabupaten" name="kabupaten"
|
||||
value="{{ old('kabupaten', $petani->desaKelurahan?->kecamatan->kabupatenKota->nama) }}" readonly/>
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="edit-petani-kecamatan">Kecamatan</label>
|
||||
<input type="text" class="form-control" id="edit-petani-kecamatan" name="kecamatan"
|
||||
value="{{ old('kecamatan', $petani->desaKelurahan?->kecamatan->nama) }}" readonly/>
|
||||
</div>
|
||||
<div class="col-md-3 mb-4 form-control-validation">
|
||||
<label class="form-label" for="edit-petani-desa">Desa/Kelurahan</label>
|
||||
<input type="text" class="form-control" id="edit-petani-desa" name="desa"
|
||||
value="{{ old('desa', $petani->desaKelurahan->nama) }}" readonly/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="{{ route('petani.index') }}" class="btn btn-label-secondary me-2">Kembali</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
'use strict';
|
||||
$(document).ready(function () {
|
||||
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
+5
-1
@@ -21,7 +21,7 @@ Route::get('/', function() {
|
||||
return redirect()->route('dashboard.survey-petani');
|
||||
}
|
||||
|
||||
return redirect()->route('dashboard.produktivitas');
|
||||
return redirect()->route('dashboard.karakteristik-petani');
|
||||
})->name('home');
|
||||
|
||||
Route::middleware('auth')
|
||||
@@ -41,11 +41,15 @@ Route::middleware('auth')
|
||||
|
||||
// Home
|
||||
|
||||
Route::get('/dashboard/produktivitas/map-information', [App\Http\Controllers\Web\Dashboard\SummaryController::class, 'mapInformation'])->name('dashboard.produktivitas.map-information');
|
||||
Route::get('/dashboard/produktivitas', [App\Http\Controllers\Web\Dashboard\SummaryController::class, 'produktivitas'])->name('dashboard.produktivitas');
|
||||
Route::get('/dashboard/pasar/harga-internasional', App\Http\Controllers\Web\Dashboard\InternationalMarketController::class)->name('dashboard.pasar.international');
|
||||
Route::get('/dashboard/pasar', [App\Http\Controllers\Web\Dashboard\SummaryController::class, 'pasar'])->name('dashboard.pasar');
|
||||
|
||||
// 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/karakteristik-petani', [App\Http\Controllers\Web\Dashboard\DemografiPetaniController::class, 'karakteristik'])->name('dashboard.karakteristik-petani');
|
||||
Route::get('/dashboard/petani/demografi', [App\Http\Controllers\Web\Dashboard\DemografiPetaniController::class, 'index'])->name('dashboard.demografi-petani');
|
||||
Route::get('/dashboard/petani/demografi/table',[App\Http\Controllers\Web\Dashboard\DemografiPetaniController::class, 'data'])->name('dashboard.demografi-petani.data');
|
||||
Route::get('/dashboard/petani/indikator-pertanian/data', [App\Http\Controllers\Web\Dashboard\IndikatorPertanianController::class, 'data'])->name('dashboard.indikator-pertanian.data');
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Web\Dashboard\SummaryController;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
beforeEach(function () {
|
||||
Schema::create('master_komoditas', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama');
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::create('master_kabupaten_kota', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('provinsi_id');
|
||||
});
|
||||
Schema::create('master_kecamatan', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('kabupaten_kota_id');
|
||||
});
|
||||
Schema::create('master_desa_kelurahan', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('kecamatan_id');
|
||||
});
|
||||
Schema::create('petani', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('komoditas_id');
|
||||
$table->integer('desa_kelurahan_id');
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::create('map_lahan', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('petani_id');
|
||||
$table->decimal('luas_lahan', 12, 4);
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::create('survey_panen', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('petani_id');
|
||||
$table->decimal('hasil_komoditas_utama_permusim', 12, 2)->nullable();
|
||||
$table->decimal('hasil_komoditas_lainnya_permusim', 12, 2)->nullable();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
DB::table('master_komoditas')->insert([
|
||||
['id' => 1, 'nama' => 'Kakao'],
|
||||
['id' => 2, 'nama' => 'Vanili'],
|
||||
['id' => 3, 'nama' => 'Kemiri'],
|
||||
['id' => 4, 'nama' => 'Kopi'],
|
||||
]);
|
||||
DB::table('master_kabupaten_kota')->insert(['id' => 1, 'provinsi_id' => 1]);
|
||||
DB::table('master_kecamatan')->insert(['id' => 1, 'kabupaten_kota_id' => 1]);
|
||||
DB::table('master_desa_kelurahan')->insert([
|
||||
['id' => 1, 'kecamatan_id' => 1],
|
||||
['id' => 2, 'kecamatan_id' => 1],
|
||||
]);
|
||||
DB::table('petani')->insert([
|
||||
['id' => 1, 'komoditas_id' => 1, 'desa_kelurahan_id' => 1, 'deleted_at' => null],
|
||||
['id' => 2, 'komoditas_id' => 1, 'desa_kelurahan_id' => 1, 'deleted_at' => null],
|
||||
['id' => 3, 'komoditas_id' => 4, 'desa_kelurahan_id' => 2, 'deleted_at' => null],
|
||||
['id' => 4, 'komoditas_id' => 1, 'desa_kelurahan_id' => 1, 'deleted_at' => '2026-01-01'],
|
||||
['id' => 5, 'komoditas_id' => 2, 'desa_kelurahan_id' => 1, 'deleted_at' => null],
|
||||
]);
|
||||
DB::table('map_lahan')->insert([
|
||||
['petani_id' => 1, 'luas_lahan' => 0.5, 'deleted_at' => null],
|
||||
['petani_id' => 1, 'luas_lahan' => 1.5, 'deleted_at' => null],
|
||||
['petani_id' => 2, 'luas_lahan' => 3, 'deleted_at' => null],
|
||||
['petani_id' => 3, 'luas_lahan' => 2, 'deleted_at' => null],
|
||||
['petani_id' => 4, 'luas_lahan' => 50, 'deleted_at' => null],
|
||||
['petani_id' => 1, 'luas_lahan' => 100, 'deleted_at' => '2026-01-01'],
|
||||
['petani_id' => 5, 'luas_lahan' => 0, 'deleted_at' => null],
|
||||
]);
|
||||
DB::table('survey_panen')->insert([
|
||||
['petani_id' => 1, 'hasil_komoditas_utama_permusim' => 300, 'deleted_at' => null],
|
||||
['petani_id' => 2, 'hasil_komoditas_utama_permusim' => 600, 'deleted_at' => null],
|
||||
['petani_id' => 3, 'hasil_komoditas_utama_permusim' => 400, 'deleted_at' => null],
|
||||
['petani_id' => 4, 'hasil_komoditas_utama_permusim' => 10000, 'deleted_at' => null],
|
||||
['petani_id' => 1, 'hasil_komoditas_utama_permusim' => 20000, 'deleted_at' => '2026-01-01'],
|
||||
['petani_id' => 5, 'hasil_komoditas_utama_permusim' => 10, 'deleted_at' => null],
|
||||
]);
|
||||
DB::table('survey_panen')->where('petani_id', 1)->update(['hasil_komoditas_lainnya_permusim' => 500]);
|
||||
|
||||
$controller = app(SummaryController::class);
|
||||
$method = new ReflectionMethod($controller, 'actualProductivityByCommodityChart');
|
||||
$this->chart = fn (array $filters = []) => $method->invoke($controller, new Request($filters));
|
||||
});
|
||||
|
||||
test('actual productivity uses seasonal survey harvest and counts each plot once', function () {
|
||||
$chart = ($this->chart)();
|
||||
|
||||
expect($chart['labels'])->toBe(['Kakao', 'Vanili', 'Kemiri', 'Kopi']);
|
||||
expect($chart['series'])->toEqual([180, 0, 0, 200]);
|
||||
});
|
||||
|
||||
test('actual productivity respects region and commodity filters', function () {
|
||||
expect(($this->chart)(['desaKelurahan_id' => 1])['series'])->toEqual([180, 0, 0, 0]);
|
||||
expect(($this->chart)(['desaKelurahan_id' => 2, 'komoditas_id' => 4])['series'])->toEqual([0, 0, 0, 200]);
|
||||
expect(($this->chart)(['desaKelurahan_id' => 1, 'komoditas_id' => 4])['series'])->toEqual([0, 0, 0, 0]);
|
||||
expect(($this->chart)(['provinsi_id' => 99])['series'])->toEqual([0, 0, 0, 0]);
|
||||
});
|
||||
|
||||
test('actual productivity rounds kg per hectare to two decimals', function () {
|
||||
DB::table('map_lahan')->where('petani_id', 5)->update(['luas_lahan' => 0.3]);
|
||||
|
||||
expect(($this->chart)()['series'][1])->toBe(33.33);
|
||||
});
|
||||
|
||||
test('productivity cards display the same values and commodities as the chart', function () {
|
||||
DB::table('map_lahan')->where('petani_id', 5)->update(['luas_lahan' => 0.3]);
|
||||
$controller = app(SummaryController::class);
|
||||
$method = new ReflectionMethod($controller, 'productivityKpiCards');
|
||||
|
||||
foreach ([[], ['desaKelurahan_id' => 2], ['komoditas_id' => 1]] as $filters) {
|
||||
$chart = ($this->chart)($filters);
|
||||
$cards = $method->invoke($controller, $chart);
|
||||
|
||||
expect(array_column($cards, 'label'))->toBe(array_map(
|
||||
fn ($name) => 'Produktivitas Aktual '.$name,
|
||||
$chart['labels'],
|
||||
));
|
||||
expect(array_column($cards, 'value'))->toBe(array_map(
|
||||
fn ($value) => number_format($value, 2, ',', '.'),
|
||||
$chart['series'],
|
||||
));
|
||||
expect(array_column($cards, 'unit'))->toBe(array_fill(0, count($chart['labels']), 'kg/ha'));
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\AksesPasar\PraktikFermentasi;
|
||||
use App\Http\Controllers\Web\Dashboard\SummaryController;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
beforeEach(function () {
|
||||
Schema::create('master_komoditas', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama');
|
||||
});
|
||||
foreach ([
|
||||
'master_kabupaten_kota' => 'provinsi_id',
|
||||
'master_kecamatan' => 'kabupaten_kota_id',
|
||||
'master_desa_kelurahan' => 'kecamatan_id',
|
||||
] as $name => $parent) {
|
||||
Schema::create($name, function (Blueprint $table) use ($parent) {
|
||||
$table->id();
|
||||
$table->integer($parent);
|
||||
});
|
||||
}
|
||||
Schema::create('petani', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('komoditas_id')->nullable();
|
||||
$table->integer('desa_kelurahan_id');
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::create('akses_pasar', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('petani_id');
|
||||
$table->string('praktik_fermentasi')->nullable();
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::create('petani_skor', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('petani_id');
|
||||
$table->float('skor_kesiapan_adopsi');
|
||||
$table->softDeletes();
|
||||
});
|
||||
DB::table('master_komoditas')->insert([
|
||||
['id' => 1, 'nama' => 'Kakao'], ['id' => 2, 'nama' => 'Kopi'], ['id' => 3, 'nama' => 'Vanili'],
|
||||
]);
|
||||
DB::table('master_kabupaten_kota')->insert(['id' => 1, 'provinsi_id' => 1]);
|
||||
DB::table('master_kecamatan')->insert(['id' => 1, 'kabupaten_kota_id' => 1]);
|
||||
DB::table('master_desa_kelurahan')->insert([
|
||||
['id' => 1, 'kecamatan_id' => 1], ['id' => 2, 'kecamatan_id' => 1],
|
||||
]);
|
||||
|
||||
foreach ([
|
||||
[1, 1, 1, PraktikFermentasi::RUTIN_SENDIRI->value],
|
||||
[2, 1, 1, PraktikFermentasi::RUTIN_KELOMPOK->value],
|
||||
[3, 2, 2, PraktikFermentasi::PERNAH_SENDIRI->value],
|
||||
[4, 2, 2, PraktikFermentasi::PERNAH_KELOMPOK->value],
|
||||
[5, 3, 2, PraktikFermentasi::BELUM_PERNAH->value],
|
||||
[6, 3, 2, null],
|
||||
[7, 1, 1, ''],
|
||||
[8, 2, 2, PraktikFermentasi::RUTIN_SENDIRI->value],
|
||||
[9, null, 2, PraktikFermentasi::BELUM_PERNAH->value],
|
||||
[10, 1, 1, PraktikFermentasi::RUTIN_SENDIRI->value],
|
||||
[11, 1, 1, 'Tidak dikenal'],
|
||||
] as [$id, $commodity, $village, $practice]) {
|
||||
DB::table('petani')->insert([
|
||||
'id' => $id, 'komoditas_id' => $commodity, 'desa_kelurahan_id' => $village,
|
||||
'deleted_at' => $id === 10 ? '2026-01-01' : null,
|
||||
]);
|
||||
DB::table('akses_pasar')->insert([
|
||||
'petani_id' => $id, 'praktik_fermentasi' => $practice,
|
||||
'deleted_at' => $id === 8 ? '2026-01-01' : null,
|
||||
]);
|
||||
}
|
||||
DB::table('petani_skor')->insert([
|
||||
['petani_id' => 1, 'skor_kesiapan_adopsi' => 4.1, 'deleted_at' => null],
|
||||
['petani_id' => 2, 'skor_kesiapan_adopsi' => 2.6, 'deleted_at' => null],
|
||||
['petani_id' => 3, 'skor_kesiapan_adopsi' => 4.4, 'deleted_at' => '2026-01-01'],
|
||||
]);
|
||||
|
||||
$controller = app(SummaryController::class);
|
||||
$query = new ReflectionMethod($controller, 'filteredAksesPasar');
|
||||
$chart = new ReflectionMethod($controller, 'fermentationPracticeByCommodityChart');
|
||||
$this->fermentationChart = fn (array $filters = []) => $chart->invoke(
|
||||
$controller, $query->invoke($controller, new Request($filters)),
|
||||
);
|
||||
});
|
||||
|
||||
test('fermentation groups individual and collective responses by commodity', function () {
|
||||
expect(($this->fermentationChart)())->toBe([
|
||||
'labels' => ['Belum ditentukan', 'Kakao', 'Kopi', 'Vanili'],
|
||||
'series' => [
|
||||
['name' => 'Ya, rutin', 'data' => [0, 2, 0, 0]],
|
||||
['name' => 'Pernah mencoba', 'data' => [0, 0, 2, 0]],
|
||||
['name' => 'Belum pernah', 'data' => [1, 0, 0, 1]],
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('fermentation respects region and commodity filters', function () {
|
||||
$chart = ($this->fermentationChart)(['desaKelurahan_id' => 2, 'komoditas_id' => 2]);
|
||||
expect($chart['labels'])->toBe(['Kopi']);
|
||||
expect(array_column($chart['series'], 'data'))->toBe([[0], [2], [0]]);
|
||||
expect(($this->fermentationChart)(['desaKelurahan_id' => 1])['labels'])->toBe(['Kakao']);
|
||||
});
|
||||
|
||||
test('fermentation respects segment filters and excludes deleted scores', function () {
|
||||
$chart = ($this->fermentationChart)(['segmen' => 'Pemerhati']);
|
||||
expect($chart['labels'])->toBe(['Kakao']);
|
||||
expect(array_column($chart['series'], 'data'))->toBe([[1], [0], [0]]);
|
||||
expect(($this->fermentationChart)(['segmen' => 'Penggerak'])['labels'])->toBe(['Kakao']);
|
||||
});
|
||||
|
||||
test('fermentation returns empty grouped series when no matching responses exist', function () {
|
||||
$chart = ($this->fermentationChart)(['provinsi_id' => 99]);
|
||||
expect($chart['labels'])->toBe([]);
|
||||
expect(array_column($chart['series'], 'data'))->toBe([[], [], []]);
|
||||
});
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Web\Produktivitas\SurveyKelompokTaniController;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
beforeEach(function () {
|
||||
foreach ([
|
||||
'master_kabupaten_kota' => null,
|
||||
'master_kecamatan' => 'kabupaten_kota_id',
|
||||
'master_desa_kelurahan' => 'kecamatan_id',
|
||||
'master_komoditas' => null,
|
||||
] as $name => $parent) {
|
||||
Schema::create($name, function (Blueprint $table) use ($parent) {
|
||||
$table->id();
|
||||
$table->string('nama');
|
||||
if ($parent) $table->integer($parent);
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
Schema::create('survey_kelompok_tani', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama_kelompok');
|
||||
$table->string('nama_responden')->nullable();
|
||||
$table->integer('desa_kelurahan_id');
|
||||
$table->string('jenis_kelompok')->nullable();
|
||||
$table->json('komoditas_id')->nullable();
|
||||
foreach (['jumlah_pengurus', 'jumlah_pengurus_aktif', 'jumlah_anggota_terdaftar', 'jumlah_anggota_aktif'] as $field) {
|
||||
$table->integer($field)->nullable();
|
||||
}
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
DB::table('master_kabupaten_kota')->insert([
|
||||
['id' => 1, 'nama' => 'Kabupaten Ngada'], ['id' => 2, 'nama' => 'Kabupaten Lain'],
|
||||
]);
|
||||
DB::table('master_kecamatan')->insert([
|
||||
['id' => 1, 'nama' => 'Kecamatan A', 'kabupaten_kota_id' => 1],
|
||||
['id' => 2, 'nama' => 'Kecamatan B', 'kabupaten_kota_id' => 1],
|
||||
['id' => 3, 'nama' => 'Kecamatan C', 'kabupaten_kota_id' => 2],
|
||||
]);
|
||||
DB::table('master_desa_kelurahan')->insert([
|
||||
['id' => 1, 'nama' => 'Desa A', 'kecamatan_id' => 1],
|
||||
['id' => 2, 'nama' => 'Desa B', 'kecamatan_id' => 1],
|
||||
['id' => 3, 'nama' => 'Desa C', 'kecamatan_id' => 2],
|
||||
['id' => 4, 'nama' => 'Desa D', 'kecamatan_id' => 3],
|
||||
]);
|
||||
DB::table('master_komoditas')->insert([
|
||||
['id' => 1, 'nama' => 'Kakao'], ['id' => 2, 'nama' => 'Kopi'], ['id' => 11, 'nama' => 'Vanili'],
|
||||
]);
|
||||
foreach ([
|
||||
[1, 'Alpha', 'Budi', 1, 'Poktan', ['1', '2'], 3, 2, 30, 25, null],
|
||||
[2, 'Beta', 'Siti', 2, 'KWT', [1], 5, 4, 40, 35, null],
|
||||
[3, 'Citra', 'Ani', 3, 'Poktan', [2, 11], null, null, 0, 0, null],
|
||||
[4, 'Diluar', 'Toni', 4, 'Poktan', [1], 99, 99, 900, 900, null],
|
||||
[5, 'Dihapus', 'Dewi', 1, 'Poktan', [1], 99, 99, 900, 900, '2026-01-01'],
|
||||
] as [$id, $nama, $responden, $desa, $jenis, $komoditas, $pengurus, $pengurusAktif, $anggota, $anggotaAktif, $deletedAt]) {
|
||||
DB::table('survey_kelompok_tani')->insert([
|
||||
'id' => $id, 'nama_kelompok' => $nama, 'nama_responden' => $responden,
|
||||
'desa_kelurahan_id' => $desa, 'jenis_kelompok' => $jenis,
|
||||
'komoditas_id' => json_encode($komoditas), 'jumlah_pengurus' => $pengurus,
|
||||
'jumlah_pengurus_aktif' => $pengurusAktif, 'jumlah_anggota_terdaftar' => $anggota,
|
||||
'jumlah_anggota_aktif' => $anggotaAktif, 'created_at' => "2026-01-0{$id}", 'deleted_at' => $deletedAt,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->listKelompok = function (array $filters = []) {
|
||||
$request = Request::create('/survey/kelompok-tani', 'GET', $filters);
|
||||
$this->app->instance('request', $request);
|
||||
|
||||
return app(SurveyKelompokTaniController::class)->index($request)->getData();
|
||||
};
|
||||
});
|
||||
|
||||
test('kelompok cards summarize the default region and exclude deleted groups', function () {
|
||||
$data = ($this->listKelompok)();
|
||||
|
||||
expect($data['kelompokTani']->pluck('id')->all())->toBe([3, 2, 1]);
|
||||
expect((int) $data['summary']->total_kelompok)->toBe(3)
|
||||
->and((int) $data['summary']->total_jenis)->toBe(2)
|
||||
->and((int) $data['summary']->total_pengurus)->toBe(8)
|
||||
->and((int) $data['summary']->pengurus_aktif)->toBe(6)
|
||||
->and((int) $data['summary']->total_anggota)->toBe(70)
|
||||
->and((int) $data['summary']->anggota_aktif)->toBe(60);
|
||||
expect($data['kecamatanOptions']->pluck('id')->all())->toBe([1, 2]);
|
||||
});
|
||||
|
||||
test('kelompok region filters constrain rows cards and dependent options', function () {
|
||||
$data = ($this->listKelompok)(['kecamatan_id' => 1, 'desa_kelurahan_id' => 2]);
|
||||
expect($data['kelompokTani']->pluck('id')->all())->toBe([2]);
|
||||
expect((int) $data['summary']->total_anggota)->toBe(40);
|
||||
expect($data['desaOptions']->pluck('id')->all())->toBe([1, 2]);
|
||||
expect(($this->listKelompok)(['kecamatan_id' => 2])['kelompokTani']->pluck('id')->all())->toBe([3]);
|
||||
expect(($this->listKelompok)(['kabupaten_kota_id' => 2])['kelompokTani']->pluck('id')->all())->toBe([4]);
|
||||
});
|
||||
|
||||
test('kelompok commodity filter matches string and numeric JSON ids exactly', function () {
|
||||
$data = ($this->listKelompok)(['komoditas_id' => '1']);
|
||||
expect($data['kelompokTani']->pluck('id')->all())->toBe([2, 1]);
|
||||
expect((int) $data['summary']->total_kelompok)->toBe(2);
|
||||
expect(($this->listKelompok)(['komoditas_id' => 2, 'jenis_kelompok' => 'Poktan'])['kelompokTani']->pluck('id')->all())->toBe([3, 1]);
|
||||
expect(($this->listKelompok)(['jenis_kelompok' => 'KWT'])['kelompokTani']->pluck('id')->all())->toBe([2]);
|
||||
});
|
||||
|
||||
test('kelompok search matches group and respondent names without escaping region filters', function () {
|
||||
expect(($this->listKelompok)(['search' => 'aLpHa'])['kelompokTani']->pluck('id')->all())->toBe([1]);
|
||||
expect(($this->listKelompok)(['search' => 'sITI'])['kelompokTani']->pluck('id')->all())->toBe([2]);
|
||||
$data = ($this->listKelompok)(['search' => 'Toni']);
|
||||
expect($data['kelompokTani']->total())->toBe(0);
|
||||
expect((int) $data['summary']->total_pengurus)->toBe(0)
|
||||
->and((int) $data['summary']->total_anggota)->toBe(0);
|
||||
});
|
||||
|
||||
test('kelompok sorting supports names membership totals and safe fallback', function () {
|
||||
foreach ([
|
||||
'nama_asc' => [1, 2, 3], 'nama_desc' => [3, 2, 1],
|
||||
'anggota_desc' => [2, 1, 3], 'anggota_asc' => [3, 1, 2], 'unknown' => [3, 2, 1],
|
||||
] as $sort => $ids) {
|
||||
expect(($this->listKelompok)(['sort_by' => $sort])['kelompokTani']->pluck('id')->all())->toBe($ids);
|
||||
}
|
||||
});
|
||||
|
||||
test('kelompok totals cover every result and pagination preserves all filters', function () {
|
||||
for ($index = 0; $index < 12; $index++) {
|
||||
DB::table('survey_kelompok_tani')->insert([
|
||||
'nama_kelompok' => 'Kelompok tambahan ' . $index, 'desa_kelurahan_id' => 1,
|
||||
'jenis_kelompok' => 'Poktan', 'komoditas_id' => '["1"]', 'jumlah_anggota_terdaftar' => 10,
|
||||
]);
|
||||
}
|
||||
$data = ($this->listKelompok)(['komoditas_id' => 1, 'sort_by' => 'nama_asc', 'page' => 2, 'per_page' => '10']);
|
||||
expect($data['kelompokTani']->count())->toBe(4);
|
||||
expect($data['kelompokTani']->perPage())->toBe(10);
|
||||
expect((int) $data['summary']->total_kelompok)->toBe(14)
|
||||
->and((int) $data['summary']->total_anggota)->toBe(190);
|
||||
parse_str(parse_url($data['kelompokTani']->url(1), PHP_URL_QUERY), $params);
|
||||
expect($params)->toMatchArray(['komoditas_id' => '1', 'sort_by' => 'nama_asc', 'per_page' => '10']);
|
||||
});
|
||||
|
||||
test('kelompok filters reject malformed ids and unsupported page sizes', function (array $filters) {
|
||||
expect(fn () => ($this->listKelompok)($filters))->toThrow(ValidationException::class);
|
||||
})->with([
|
||||
[['kecamatan_id' => 'invalid']], [['komoditas_id' => 999]], [['per_page' => 0]], [['search' => ['invalid']]],
|
||||
]);
|
||||
@@ -0,0 +1,942 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Web\Produktivitas\PengurusKelompokTaniController;
|
||||
use App\Http\Controllers\Web\Produktivitas\AsetKelompokTaniController;
|
||||
use App\Http\Controllers\Web\Produktivitas\BantuanKelompokTaniController;
|
||||
use App\Http\Controllers\Web\Produktivitas\SurveyKelompokTaniController;
|
||||
use App\Models\Pertanian\KelompokTani;
|
||||
use App\Models\Pertanian\AsetKelompokTani;
|
||||
use App\Models\Pertanian\BantuanKelompokTani;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\ViewErrorBag;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
beforeEach(function () {
|
||||
foreach ([
|
||||
'master_provinsi' => null,
|
||||
'master_kabupaten_kota' => 'provinsi_id',
|
||||
'master_kecamatan' => 'kabupaten_kota_id',
|
||||
'master_desa_kelurahan' => 'kecamatan_id',
|
||||
'master_komoditas' => null,
|
||||
] as $name => $parent) {
|
||||
Schema::create($name, function (Blueprint $table) use ($parent) {
|
||||
$table->id();
|
||||
$table->string('nama');
|
||||
$table->string('kode')->nullable();
|
||||
$table->boolean('is_utama')->default(true);
|
||||
if ($parent) $table->integer($parent);
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
Schema::create('survey_kelompok_tani', function (Blueprint $table) {
|
||||
$table->id();
|
||||
foreach ([
|
||||
'uid', 'nama_kelompok', 'nama_responden', 'jabatan_responden', 'kontak_responden',
|
||||
'dusun', 'jenis_kelompok', 'nama_gapoktan', 'cakupan_wilayah', 'nama_wilayah_cakupan',
|
||||
'status_legalitas', 'nomor_sk', 'tanggal_tahun_penerbitan_sk', 'frekuensi_pertemuan',
|
||||
'tanggal_pertemuan_terakhir',
|
||||
'pembukuan_kas', 'pengelola_kas', 'masalah_pengelolaan_kas_simpan_pinjam', 'deskripsi_masalah_pengelolaan_kas_simpan_pinjam',
|
||||
'potensi_kelompok', 'alasan_penilaian_potensi', 'konflik_kelompok', 'topik_konflik', 'penyelesaian_konflik', 'partisipasi_perempuan',
|
||||
] as $field) {
|
||||
$table->string($field)->nullable();
|
||||
}
|
||||
foreach ([
|
||||
'desa_kelurahan_id', 'tahun_berdiri', 'jumlah_pengurus', 'jumlah_pengurus_aktif',
|
||||
'jumlah_anggota_terdaftar', 'jumlah_anggota_aktif', 'jumlah_pengurus_laki_laki',
|
||||
'jumlah_pengurus_perempuan', 'jumlah_anggota_laki_laki', 'jumlah_anggota_perempuan',
|
||||
'jumlah_anggota_pemuda_laki_laki', 'jumlah_anggota_pemuda_perempuan',
|
||||
'created_by', 'updated_by', 'deleted_by',
|
||||
] as $field) {
|
||||
$table->integer($field)->nullable();
|
||||
}
|
||||
$table->json('komoditas_id')->nullable();
|
||||
$table->json('kegiatan_terlaksana')->nullable();
|
||||
$table->json('fungsi_kelompok_bagi_anggota')->nullable();
|
||||
$table->boolean('pernah_mendapatkan_bantuan')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::create('survey_pengurus_kelompok_tani', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uid');
|
||||
$table->integer('survey_kelompok_tani_id');
|
||||
$table->string('nama_pengurus');
|
||||
$table->string('jabatan');
|
||||
$table->string('kontak');
|
||||
$table->boolean('status');
|
||||
$table->integer('created_by')->nullable();
|
||||
$table->integer('updated_by')->nullable();
|
||||
$table->integer('deleted_by')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::create('survey_aset_kelompok_tani', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uid');
|
||||
$table->integer('survey_kelompok_tani_id');
|
||||
$table->string('jenis_aset');
|
||||
$table->boolean('status_aset');
|
||||
$table->string('status_pengelolaan_aset');
|
||||
$table->string('kondisi_aset');
|
||||
$table->integer('created_by')->nullable();
|
||||
$table->integer('updated_by')->nullable();
|
||||
$table->integer('deleted_by')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::create('survey_bantuan_kelompok_tani', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uid');
|
||||
$table->integer('survey_kelompok_tani_id');
|
||||
$table->string('nama_program');
|
||||
$table->string('penyelenggara');
|
||||
$table->string('bentuk_bantuan');
|
||||
$table->integer('tahun')->nullable();
|
||||
$table->integer('created_by')->nullable();
|
||||
$table->integer('updated_by')->nullable();
|
||||
$table->integer('deleted_by')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
DB::table('master_provinsi')->insert(['id' => 1, 'nama' => 'NTT']);
|
||||
DB::table('master_kabupaten_kota')->insert(['id' => 1, 'nama' => 'Ngada', 'provinsi_id' => 1]);
|
||||
DB::table('master_kecamatan')->insert(['id' => 1, 'nama' => 'Kecamatan A', 'kabupaten_kota_id' => 1]);
|
||||
DB::table('master_desa_kelurahan')->insert(['id' => 1, 'nama' => 'Desa A', 'kecamatan_id' => 1]);
|
||||
|
||||
$user = new User(['name' => 'Admin Test', 'email' => 'admin@example.test', 'role' => 'admin']);
|
||||
$user->id = 7;
|
||||
$this->be($user);
|
||||
Event::forget('composing: *');
|
||||
view()->share('errors', new ViewErrorBag());
|
||||
|
||||
$this->kelompok = fn (array $attributes = []) => KelompokTani::create([
|
||||
'nama_kelompok' => 'Kelompok A', 'nama_responden' => 'Responden A',
|
||||
'desa_kelurahan_id' => 1, 'jenis_kelompok' => 'Kelompok Tani (Poktan)', ...$attributes,
|
||||
]);
|
||||
$this->profileRequest = function (array $input = [], string $method = 'PUT') {
|
||||
$request = Request::create('/survey/kelompok-tani/1/edit', $method, $input);
|
||||
$request->setLaravelSession(app('session')->driver());
|
||||
$this->app->instance('request', $request);
|
||||
|
||||
return $request;
|
||||
};
|
||||
$this->profileDom = function ($view) {
|
||||
$dom = new DOMDocument();
|
||||
$previous = libxml_use_internal_errors(true);
|
||||
$dom->loadHTML($view->render());
|
||||
libxml_clear_errors();
|
||||
libxml_use_internal_errors($previous);
|
||||
|
||||
return new DOMXPath($dom);
|
||||
};
|
||||
});
|
||||
|
||||
test('profile saves identity and membership together on creation and update', function () {
|
||||
$controller = app(SurveyKelompokTaniController::class);
|
||||
$input = [
|
||||
'nama_kelompok' => 'Kelompok Baru', 'nama_responden' => 'Budi',
|
||||
'desa_kelurahan_id' => 1, 'jenis_kelompok' => 'Kelompok Tani (Poktan)',
|
||||
'jumlah_pengurus' => 4, 'jumlah_pengurus_aktif' => 3, 'jumlah_anggota_terdaftar' => 20,
|
||||
'jumlah_anggota_aktif' => 18, 'jumlah_pengurus_laki_laki' => 2, 'jumlah_pengurus_perempuan' => 2,
|
||||
'jumlah_anggota_laki_laki' => 12, 'jumlah_anggota_perempuan' => 8,
|
||||
'jumlah_anggota_pemuda_laki_laki' => 5, 'jumlah_anggota_pemuda_perempuan' => 3,
|
||||
];
|
||||
$locations = ['provinsi_id' => 1, 'kabupaten_id' => 1, 'kecamatan_id' => 1];
|
||||
$controller->store(($this->profileRequest)([...$input, ...$locations], 'POST'));
|
||||
$group = KelompokTani::firstOrFail();
|
||||
foreach ($input as $field => $value) expect($group->{$field})->toBe($value);
|
||||
|
||||
$response = $controller->update(($this->profileRequest)([
|
||||
...$input, ...$locations, 'nama_kelompok' => 'Kelompok Diubah', 'jumlah_anggota_terdaftar' => 25,
|
||||
]), $group);
|
||||
expect($group->refresh()->nama_kelompok)->toBe('Kelompok Diubah')
|
||||
->and($group->jumlah_anggota_terdaftar)->toBe(25);
|
||||
expect($response->getTargetUrl())->toBe(route('kelompok-tani.edit', $group->id) . '#section-pengurus');
|
||||
});
|
||||
|
||||
test('invalid membership prevents partial profile saves', function () {
|
||||
$group = ($this->kelompok)(['jumlah_anggota_terdaftar' => 20]);
|
||||
$request = ($this->profileRequest)([
|
||||
'nama_kelompok' => 'Tidak boleh tersimpan', 'nama_responden' => 'Budi',
|
||||
'provinsi_id' => 1, 'kabupaten_id' => 1, 'kecamatan_id' => 1,
|
||||
'desa_kelurahan_id' => 1, 'jenis_kelompok' => 'Kelompok Tani (Poktan)',
|
||||
'jumlah_anggota_terdaftar' => -1,
|
||||
]);
|
||||
|
||||
expect(fn () => app(SurveyKelompokTaniController::class)->update($request, $group))
|
||||
->toThrow(ValidationException::class);
|
||||
expect($group->refresh()->nama_kelompok)->toBe('Kelompok A')
|
||||
->and($group->jumlah_anggota_terdaftar)->toBe(20);
|
||||
});
|
||||
|
||||
test('membership fields belong to the profile form on create and edit', function () {
|
||||
$controller = app(SurveyKelompokTaniController::class);
|
||||
($this->profileRequest)([], 'GET');
|
||||
$create = ($this->profileDom)($controller->create());
|
||||
expect($create->query('//form[@id="addKelompokTaniForm"]//input[@name="jumlah_anggota_terdaftar"]')->length)->toBe(1);
|
||||
|
||||
$group = ($this->kelompok)(['jumlah_anggota_terdaftar' => 20]);
|
||||
$group->pengurus()->create([
|
||||
'uid' => 'PKT-TEST', 'nama_pengurus' => 'Ketua A', 'jabatan' => 'Ketua', 'kontak' => '08123456789', 'status' => true,
|
||||
]);
|
||||
$edit = ($this->profileDom)($controller->edit(($this->profileRequest)([], 'GET'), $group));
|
||||
expect($edit->query('//form[@id="editKelompokTaniForm"]//input[@name="jumlah_anggota_terdaftar" and @value="20"]')->length)->toBe(1);
|
||||
expect($edit->query('//div[@id="section-pengurus"]//input[@value="Ketua A"]')->length)->toBe(1);
|
||||
expect($edit->query('//form[contains(@class,"form-repeater-pengurus")]')->length)->toBe(1);
|
||||
expect($edit->query('//ul[contains(@class,"nav-pills")]//a[normalize-space()="Keanggotaan"]')->length)->toBe(0);
|
||||
});
|
||||
|
||||
test('SK fields render enabled only for statuses with an existing SK', function (?string $status, bool $enabled) {
|
||||
$group = ($this->kelompok)(['status_legalitas' => $status]);
|
||||
$view = app(SurveyKelompokTaniController::class)->edit(
|
||||
($this->profileRequest)(['section' => 'kelembagaan'], 'GET'), $group
|
||||
);
|
||||
$dom = ($this->profileDom)($view);
|
||||
foreach (['klb-nomor-sk', 'klb-tanggal-penerbitan-sk'] as $id) {
|
||||
expect($dom->query('//input[@id="' . $id . '"]')->item(0)->hasAttribute('disabled'))->toBe(!$enabled);
|
||||
}
|
||||
expect($dom->query('//label[@for="klb-nomor-sk"]')->item(0)->textContent)->toBe('Nomor SK');
|
||||
})->with([
|
||||
[null, false], ['Belum ada SK/dokumen apa pun', false], ['Sedang proses pengajuan', false],
|
||||
['Tidak tahu', false], ['Sudah ada SK dari desa/kecamatan', true],
|
||||
['Sudah ada SK dan terdaftar di Dinas/Kabupaten', true],
|
||||
]);
|
||||
|
||||
test('inactive SK input is ignored and existing values survive status changes', function () {
|
||||
$group = ($this->kelompok)(['nomor_sk' => 'SK-LAMA', 'tanggal_tahun_penerbitan_sk' => '2020']);
|
||||
$controller = app(SurveyKelompokTaniController::class);
|
||||
$controller->update(($this->profileRequest)([
|
||||
'section' => 'kelembagaan', 'status_legalitas' => 'Tidak tahu',
|
||||
'nomor_sk' => ['invalid'], 'tanggal_tahun_penerbitan_sk' => str_repeat('x', 100),
|
||||
]), $group);
|
||||
expect($group->refresh()->nomor_sk)->toBe('SK-LAMA')
|
||||
->and($group->tanggal_tahun_penerbitan_sk)->toBe('2020');
|
||||
|
||||
$controller->update(($this->profileRequest)([
|
||||
'section' => 'kelembagaan', 'status_legalitas' => 'Sudah ada SK dari desa/kecamatan',
|
||||
'nomor_sk' => 'SK-BARU', 'tanggal_tahun_penerbitan_sk' => '2026',
|
||||
]), $group);
|
||||
expect($group->refresh()->nomor_sk)->toBe('SK-BARU')
|
||||
->and($group->tanggal_tahun_penerbitan_sk)->toBe('2026');
|
||||
});
|
||||
|
||||
test('pengurus actions and the former membership URL return to profile', function () {
|
||||
$group = ($this->kelompok)();
|
||||
$controller = app(PengurusKelompokTaniController::class);
|
||||
$response = $controller->index($group);
|
||||
expect($response->getTargetUrl())->toBe(route('kelompok-tani.edit', $group->id) . '#section-ringkasan');
|
||||
$response = $controller->store(($this->profileRequest)(['pengurus' => [[
|
||||
'nama_pengurus' => 'Ketua A', 'jabatan' => 'Ketua', 'kontak' => '08123456789', 'status' => 1,
|
||||
]]], 'POST'), $group);
|
||||
expect($response->getTargetUrl())->toBe(route('kelompok-tani.edit', $group->id) . '#section-pengurus');
|
||||
$pengurus = $group->pengurus()->firstOrFail();
|
||||
$response = $controller->destroy($group, $pengurus);
|
||||
expect($response->getTargetUrl())->toBe(route('kelompok-tani.edit', $group->id) . '#section-pengurus');
|
||||
expect($group->pengurus()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('meeting fields are shown in Kegiatan with existing values and removed from Kelembagaan', function () {
|
||||
$group = ($this->kelompok)([
|
||||
'frekuensi_pertemuan' => '1x per bulan', 'tanggal_pertemuan_terakhir' => '2026-10-20',
|
||||
]);
|
||||
$controller = app(SurveyKelompokTaniController::class);
|
||||
$kelembagaan = ($this->profileDom)($controller->edit(
|
||||
($this->profileRequest)(['section' => 'kelembagaan'], 'GET'), $group
|
||||
));
|
||||
expect($kelembagaan->query('//select[@name="frekuensi_pertemuan"] | //input[@name="tanggal_pertemuan_terakhir"]')->length)->toBe(0);
|
||||
|
||||
$aktivitas = ($this->profileDom)($controller->edit(
|
||||
($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET'), $group
|
||||
));
|
||||
expect($aktivitas->query('//form//select[@name="frekuensi_pertemuan"]/option[@selected]')->item(0)->getAttribute('value'))->toBe('1x per bulan');
|
||||
expect($aktivitas->query('//form//input[@name="tanggal_pertemuan_terakhir"]')->item(0)->getAttribute('value'))->toBe('20-10-2026');
|
||||
expect($aktivitas->query('//ul[contains(@class,"nav-pills")]//a[normalize-space()="Operasional"]')->length)->toBe(1);
|
||||
expect($aktivitas->query('//input[@name="tanggal_pertemuan_terakhir"]')->item(0)->hasAttribute('placeholder'))->toBeFalse();
|
||||
});
|
||||
|
||||
test('Kegiatan saves meeting details and rejects invalid dates without partial updates', function () {
|
||||
$group = ($this->kelompok)();
|
||||
$controller = app(SurveyKelompokTaniController::class);
|
||||
$response = $controller->update(($this->profileRequest)([
|
||||
'section' => 'aktivitas-fungsi', 'frekuensi_pertemuan' => '1x per bulan',
|
||||
'tanggal_pertemuan_terakhir' => '20-10-2026', 'kegiatan_terlaksana' => ['Pertemuan/diskusi rutin'],
|
||||
]), $group);
|
||||
expect($group->refresh()->frekuensi_pertemuan)->toBe('1x per bulan')
|
||||
->and($group->tanggal_pertemuan_terakhir->format('Y-m-d'))->toBe('2026-10-20')
|
||||
->and($group->kegiatan_terlaksana)->toBe(['Pertemuan/diskusi rutin']);
|
||||
expect($response->getTargetUrl())->toBe(route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi#section-aset-baku');
|
||||
|
||||
$invalid = ($this->profileRequest)([
|
||||
'section' => 'aktivitas-fungsi', 'frekuensi_pertemuan' => 'Tidak rutin/tidak terjadwal',
|
||||
'tanggal_pertemuan_terakhir' => 'invalid-date',
|
||||
]);
|
||||
expect(fn () => $controller->update($invalid, $group))->toThrow(ValidationException::class);
|
||||
expect($group->refresh()->frekuensi_pertemuan)->toBe('1x per bulan')
|
||||
->and($group->tanggal_pertemuan_terakhir->format('Y-m-d'))->toBe('2026-10-20');
|
||||
|
||||
$controller->update(($this->profileRequest)([
|
||||
'section' => 'kelembagaan', 'cakupan_wilayah' => 'Desa', 'frekuensi_pertemuan' => 'Diabaikan',
|
||||
'tanggal_pertemuan_terakhir' => 'invalid-date',
|
||||
]), $group);
|
||||
expect($group->refresh()->cakupan_wilayah)->toBe('Desa')
|
||||
->and($group->frekuensi_pertemuan)->toBe('1x per bulan');
|
||||
});
|
||||
|
||||
test('all group tabs show Simpan with a check and Kembali to the group list', function () {
|
||||
($this->profileRequest)([], 'GET');
|
||||
$group = ($this->kelompok)();
|
||||
foreach (['pengurus', 'aset', 'bantuan'] as $relation) $group->setRelation($relation, collect());
|
||||
|
||||
foreach (['add', 'edit', 'kelembagaan', 'aktivitas-fungsi', 'sosial-konflik'] as $template) {
|
||||
$dom = ($this->profileDom)(view('produktivitas.survey-kelompok-tani.' . $template, [
|
||||
'kelompokTani' => $template === 'add' ? null : $group,
|
||||
'provinsi' => collect(), 'komoditas' => collect(),
|
||||
]));
|
||||
$buttons = $dom->query('//div[contains(@class,"kelompok-tani-page")]//button[@type="submit" and contains(@class,"btn-success")]');
|
||||
expect($buttons->length)->toBeGreaterThan(0);
|
||||
foreach ($buttons as $button) {
|
||||
$form = $dom->query('ancestor::form', $button)->item(0);
|
||||
expect($form->hasAttribute('novalidate'))->toBeTrue();
|
||||
expect(trim($button->textContent))->toBe('Simpan');
|
||||
expect($dom->query('.//i[contains(@class,"tabler-check")]', $button)->length)->toBe(1);
|
||||
$back = $dom->query('./a[normalize-space()="Kembali"]', $button->parentNode);
|
||||
expect($back->length)->toBe(1);
|
||||
expect($back->item(0)->getAttribute('href'))->toBe(route('kelompok-tani.index'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('flash notifications appear once above the group summary regardless of section', function (string $key) {
|
||||
($this->profileRequest)([], 'GET');
|
||||
session()->flash($key, 'Pesan pemeriksaan');
|
||||
session()->flash($key . '_section', 'kas');
|
||||
$group = ($this->kelompok)();
|
||||
$group->setRelation('pengurus', collect());
|
||||
$dom = ($this->profileDom)(view('produktivitas.survey-kelompok-tani.edit', [
|
||||
'kelompokTani' => $group, 'provinsi' => collect(), 'komoditas' => collect(),
|
||||
]));
|
||||
|
||||
expect($dom->query('//div[@id="kelompok-tani-notifications"]')->length)->toBe(1);
|
||||
expect($dom->query('//div[@id="kelompok-tani-summary"]/preceding-sibling::div[@id="kelompok-tani-notifications"]')->length)->toBe(1);
|
||||
expect($dom->query('//div[@id="kelompok-tani-notifications"]//div[@role="alert"]')->length)->toBe(1);
|
||||
expect($dom->query('//div[@id="kelompok-tani-notifications"]')->item(0)->textContent)->toContain('Pesan pemeriksaan');
|
||||
})->with(['success', 'error', 'warning', 'info', 'status', 'message']);
|
||||
|
||||
test('validation errors are summarized above the group summary', function () {
|
||||
($this->profileRequest)([], 'GET');
|
||||
$errors = new ViewErrorBag();
|
||||
$errors->put('default', new Illuminate\Support\MessageBag(['nomor_sk' => 'Nomor SK tidak valid.']));
|
||||
view()->share('errors', $errors);
|
||||
$dom = ($this->profileDom)(view('produktivitas.survey-kelompok-tani.kelembagaan', [
|
||||
'kelompokTani' => ($this->kelompok)(),
|
||||
]));
|
||||
|
||||
expect($dom->query('//div[@id="kelompok-tani-summary"]/preceding-sibling::div[@id="kelompok-tani-notifications"]')->length)->toBe(1);
|
||||
expect($dom->query('//div[@id="kelompok-tani-notifications"]//li')->item(0)->textContent)->toBe('Nomor SK tidak valid.');
|
||||
});
|
||||
|
||||
test('one asset submission saves standard and additional assets and skips empty rows', function () {
|
||||
$group = ($this->kelompok)();
|
||||
$controller = app(AsetKelompokTaniController::class);
|
||||
$response = $controller->update(($this->profileRequest)([
|
||||
'aset' => [[
|
||||
'jenis_aset' => 'Timbangan', 'status_aset' => 1,
|
||||
'status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => 'Berfungsi',
|
||||
]],
|
||||
'aset_lainnya' => [
|
||||
['jenis_aset' => 'Mesin pengering', 'status_pengelolaan_aset' => 'Swadaya', 'kondisi_aset' => 'Berfungsi'],
|
||||
['jenis_aset' => '', 'status_pengelolaan_aset' => '', 'kondisi_aset' => ''],
|
||||
],
|
||||
]), $group);
|
||||
|
||||
expect($group->aset()->count())->toBe(2);
|
||||
expect($group->aset()->where('jenis_aset', 'Timbangan')->firstOrFail()->status_pengelolaan_aset)->toBe('Hibah');
|
||||
expect($group->aset()->where('jenis_aset', 'Mesin pengering')->firstOrFail()->kondisi_aset)->toBe('Berfungsi');
|
||||
expect($response->getTargetUrl())->toBe(route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi#section-status-bantuan');
|
||||
});
|
||||
|
||||
test('invalid additional assets prevent standard assets from being changed', function () {
|
||||
$group = ($this->kelompok)();
|
||||
$existing = $group->aset()->create([
|
||||
'uid' => 'AKT-TEST', 'jenis_aset' => 'Timbangan', 'status_aset' => true,
|
||||
'status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => 'Berfungsi',
|
||||
]);
|
||||
$request = ($this->profileRequest)([
|
||||
'aset' => [['jenis_aset' => 'Timbangan', 'status_aset' => 0]],
|
||||
'aset_lainnya' => [['jenis_aset' => 'Timbangan']],
|
||||
]);
|
||||
expect(fn () => app(AsetKelompokTaniController::class)->update($request, $group))->toThrow(ValidationException::class);
|
||||
expect($existing->fresh()->trashed())->toBeFalse();
|
||||
expect($group->aset()->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('combined asset save restores and removes standard assets while keeping additional assets', function () {
|
||||
$group = ($this->kelompok)();
|
||||
$controller = app(AsetKelompokTaniController::class);
|
||||
$existing = $group->aset()->create([
|
||||
'uid' => 'AKT-TEST', 'jenis_aset' => 'Timbangan', 'status_aset' => true,
|
||||
'status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => 'Berfungsi',
|
||||
]);
|
||||
$existing->delete();
|
||||
$controller->update(($this->profileRequest)([
|
||||
'aset' => [[
|
||||
'jenis_aset' => 'Timbangan', 'status_aset' => 1,
|
||||
'status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => 'Berfungsi',
|
||||
]],
|
||||
'aset_lainnya' => [['jenis_aset' => 'Mesin pengering']],
|
||||
]), $group);
|
||||
expect($existing->fresh()->trashed())->toBeFalse();
|
||||
expect($group->aset()->withTrashed()->where('jenis_aset', 'Timbangan')->count())->toBe(1);
|
||||
|
||||
$controller->update(($this->profileRequest)([
|
||||
'aset' => [['jenis_aset' => 'Timbangan', 'status_aset' => 0]],
|
||||
]), $group);
|
||||
expect($existing->fresh()->trashed())->toBeTrue();
|
||||
expect($group->aset()->pluck('jenis_aset')->all())->toBe(['Mesin pengering']);
|
||||
});
|
||||
|
||||
test('asset save rolls back standard changes if additional asset creation fails', function () {
|
||||
$group = ($this->kelompok)();
|
||||
$existing = $group->aset()->create([
|
||||
'uid' => 'AKT-TEST', 'jenis_aset' => 'Timbangan', 'status_aset' => true,
|
||||
'status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => 'Berfungsi',
|
||||
]);
|
||||
Event::listen('eloquent.creating: ' . AsetKelompokTani::class, function ($asset) {
|
||||
if ($asset->jenis_aset === 'Mesin gagal') throw new RuntimeException('Simulated failure');
|
||||
});
|
||||
$request = ($this->profileRequest)([
|
||||
'aset' => [['jenis_aset' => 'Timbangan', 'status_aset' => 0]],
|
||||
'aset_lainnya' => [['jenis_aset' => 'Mesin gagal']],
|
||||
]);
|
||||
expect(fn () => app(AsetKelompokTaniController::class)->update($request, $group))->toThrow(RuntimeException::class);
|
||||
expect($existing->fresh()->trashed())->toBeFalse();
|
||||
expect($group->aset()->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('asset card has one shared form and preserves inputs after validation errors', function () {
|
||||
$group = ($this->kelompok)();
|
||||
$existing = $group->aset()->create([
|
||||
'uid' => 'AKT-TEST', 'jenis_aset' => 'Mesin lama', 'status_aset' => true,
|
||||
'status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => 'Berfungsi',
|
||||
]);
|
||||
($this->profileRequest)([], 'GET');
|
||||
session()->flashInput([
|
||||
'aset' => [['status_aset' => '1', 'status_pengelolaan_aset' => 'Pinjaman', 'kondisi_aset' => 'Mangkrak']],
|
||||
'aset_lainnya' => [['jenis_aset' => 'Mesin baru', 'status_pengelolaan_aset' => 'Sewa', 'kondisi_aset' => 'Berfungsi']],
|
||||
]);
|
||||
$dom = ($this->profileDom)(app(SurveyKelompokTaniController::class)->edit(($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET'), $group));
|
||||
expect($dom->query('//div[@id="section-aset-baku"]//form[@id="aset-produktif-form"]')->length)->toBe(1);
|
||||
expect($dom->query('//form[@id="aset-produktif-form"]//div[@id="section-aset-lainnya"]')->length)->toBe(1);
|
||||
expect($dom->query('//form[@id="aset-produktif-form"]//button[contains(@class,"btn-success")]')->length)->toBe(1);
|
||||
expect($dom->query('//form[@id="aset-produktif-form"]//form')->length)->toBe(0);
|
||||
expect($dom->query('//form[@id="aset-produktif-form"]//input[@value="Mesin baru"]')->length)->toBe(1);
|
||||
expect($dom->query('//form[@id="aset-produktif-form"]//select[@name="aset[0][status_pengelolaan_aset]"]/option[@selected]')->item(0)->getAttribute('value'))->toBe('Pinjaman');
|
||||
expect($dom->query('//form[@id="aset-produktif-form"]//select[@name="status_pengelolaan_aset"]/option[@selected]')->item(0)->getAttribute('value'))->toBe('Sewa');
|
||||
expect($dom->query('//button[@form="delete-aset-' . $existing->id . '"]')->length)->toBe(1);
|
||||
expect($dom->query('//form[@id="delete-aset-' . $existing->id . '"]')->length)->toBe(1);
|
||||
});
|
||||
|
||||
test('cash management is visible only when the cash asset is present or selected in old input', function (?bool $savedStatus, bool $deleted, ?string $oldStatus, bool $visible) {
|
||||
$group = ($this->kelompok)();
|
||||
if ($savedStatus !== null) {
|
||||
$asset = $group->aset()->create([
|
||||
'uid' => 'AKT-KAS', 'jenis_aset' => 'Kas/simpanan kelompok', 'status_aset' => $savedStatus,
|
||||
'status_pengelolaan_aset' => 'Swadaya', 'kondisi_aset' => 'Berfungsi',
|
||||
]);
|
||||
if ($deleted) $asset->delete();
|
||||
}
|
||||
($this->profileRequest)([], 'GET');
|
||||
if ($oldStatus !== null) {
|
||||
$cashIndex = array_search('Kas/simpanan kelompok', AsetKelompokTaniController::JENIS_ASET_BAKU, true);
|
||||
session()->flashInput(['aset' => [$cashIndex => ['status_aset' => $oldStatus]]]);
|
||||
}
|
||||
$dom = ($this->profileDom)(app(SurveyKelompokTaniController::class)->edit(($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET'), $group));
|
||||
expect($dom->query('//div[@id="section-kas"]')->item(0)->hasAttribute('hidden'))->toBe(!$visible);
|
||||
expect($dom->query('//tr[@data-aset-kas]//input[@value="1"]')->item(0)->hasAttribute('checked'))->toBe($visible);
|
||||
})->with([
|
||||
[null, false, null, false],
|
||||
[true, false, null, true],
|
||||
[false, false, null, false],
|
||||
[true, true, null, false],
|
||||
[true, false, '0', false],
|
||||
[null, false, '1', true],
|
||||
]);
|
||||
|
||||
test('the combined tab contains activities assets and cash management with one header', function () {
|
||||
$group = ($this->kelompok)();
|
||||
$group->aset()->create([
|
||||
'uid' => 'AKT-KAS', 'jenis_aset' => 'Kas/simpanan kelompok', 'status_aset' => true,
|
||||
'status_pengelolaan_aset' => 'Swadaya', 'kondisi_aset' => 'Berfungsi',
|
||||
]);
|
||||
$request = ($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET');
|
||||
$route = (new Illuminate\Routing\Route('GET', '/survey/kelompok-tani/{kelompokTani}/edit', fn () => null))->name('kelompok-tani.edit');
|
||||
$request->setRouteResolver(fn () => $route);
|
||||
$dom = ($this->profileDom)(app(SurveyKelompokTaniController::class)->edit($request, $group));
|
||||
|
||||
expect($dom->query('//div[@id="kelompok-tani-summary"]')->length)->toBe(1);
|
||||
expect($dom->query('//div[@id="section-aktivitas-fungsi"]')->length)->toBe(1);
|
||||
expect($dom->query('//div[@id="section-aset-baku"]')->length)->toBe(1);
|
||||
expect($dom->query('//div[@id="section-kas"]')->item(0)->hasAttribute('hidden'))->toBeFalse();
|
||||
expect($dom->query('//div[@id="section-status-bantuan"]')->length)->toBe(1);
|
||||
expect($dom->query('//div[@id="section-penilaian"]')->length)->toBe(0);
|
||||
expect($dom->query('//ul[contains(@class,"nav-pills")]//a[normalize-space()="Bantuan & Penilaian"]')->length)->toBe(0);
|
||||
expect($dom->query('//ul[contains(@class,"nav-pills")]//a[contains(@class,"active") and normalize-space()="Operasional"]')->length)->toBe(1);
|
||||
expect($dom->query('//ul[contains(@class,"nav-pills")]//a[normalize-space()="Aset & Keuangan"]')->length)->toBe(0);
|
||||
});
|
||||
|
||||
test('asset actions and cash saves return to the combined tab', function () {
|
||||
$group = ($this->kelompok)();
|
||||
$controller = app(AsetKelompokTaniController::class);
|
||||
$url = route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi';
|
||||
expect($controller->index($group)->getTargetUrl())->toBe($url . '#section-aset-baku');
|
||||
|
||||
$response = $controller->store(($this->profileRequest)([
|
||||
'aset_lainnya' => [['jenis_aset' => 'Mesin pengering']],
|
||||
], 'POST'), $group);
|
||||
expect($response->getTargetUrl())->toBe($url . '#section-status-bantuan');
|
||||
$response = $controller->destroy($group, $group->aset()->firstOrFail());
|
||||
expect($response->getTargetUrl())->toBe($url . '#section-aset-lainnya');
|
||||
|
||||
$response = app(SurveyKelompokTaniController::class)->update(($this->profileRequest)([
|
||||
'section' => 'kas', 'pengelola_kas' => 'Bendahara kelompok',
|
||||
]), $group);
|
||||
expect($response->getTargetUrl())->toBe($url . '#section-status-bantuan');
|
||||
expect($group->refresh()->pengelola_kas)->toBe('Bendahara kelompok');
|
||||
});
|
||||
|
||||
test('present assets require both management status and condition before saving any assets', function ($status) {
|
||||
$group = ($this->kelompok)();
|
||||
$request = ($this->profileRequest)([
|
||||
'aset' => [['jenis_aset' => 'Timbangan', 'status_aset' => $status]],
|
||||
'aset_lainnya' => [['jenis_aset' => 'Mesin pengering']],
|
||||
]);
|
||||
try {
|
||||
app(AsetKelompokTaniController::class)->update($request, $group);
|
||||
$this->fail('An asset marked Ada must require management status and condition.');
|
||||
} catch (ValidationException $exception) {
|
||||
expect(array_keys($exception->errors()))->toBe([
|
||||
'aset.0.status_pengelolaan_aset', 'aset.0.kondisi_aset',
|
||||
]);
|
||||
expect($exception->errors()['aset.0.kondisi_aset'][0])->toBe('Kondisi wajib diisi jika aset dipilih Ada.');
|
||||
}
|
||||
expect($group->aset()->count())->toBe(0);
|
||||
})->with(['1', 1, true]);
|
||||
|
||||
test('asset requirements follow each row and reject blank values', function (array $details, string $missingField) {
|
||||
$group = ($this->kelompok)();
|
||||
$request = ($this->profileRequest)([
|
||||
'aset' => [
|
||||
['jenis_aset' => 'Timbangan', 'status_aset' => '0'],
|
||||
['jenis_aset' => 'Gudang/tempat simpan', 'status_aset' => '1', ...$details],
|
||||
],
|
||||
]);
|
||||
try {
|
||||
app(AsetKelompokTaniController::class)->update($request, $group);
|
||||
$this->fail('The selected asset has a missing detail.');
|
||||
} catch (ValidationException $exception) {
|
||||
expect(array_keys($exception->errors()))->toBe(['aset.1.' . $missingField]);
|
||||
}
|
||||
expect($group->aset()->count())->toBe(0);
|
||||
})->with([
|
||||
[['status_pengelolaan_aset' => '', 'kondisi_aset' => 'Berfungsi'], 'status_pengelolaan_aset'],
|
||||
[['status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => null], 'kondisi_aset'],
|
||||
[['status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => ' '], 'kondisi_aset'],
|
||||
]);
|
||||
|
||||
test('asset dropdowns are required only on rows selected Ada', function (bool $present) {
|
||||
$group = ($this->kelompok)();
|
||||
$group->aset()->create([
|
||||
'uid' => 'AKT-TEST', 'jenis_aset' => 'Timbangan', 'status_aset' => $present,
|
||||
'status_pengelolaan_aset' => 'Hibah', 'kondisi_aset' => 'Berfungsi',
|
||||
]);
|
||||
$request = ($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET');
|
||||
$dom = ($this->profileDom)(app(SurveyKelompokTaniController::class)->edit($request, $group));
|
||||
$index = array_search('Timbangan', AsetKelompokTaniController::JENIS_ASET_BAKU, true);
|
||||
foreach (['status_pengelolaan_aset', 'kondisi_aset'] as $field) {
|
||||
$select = $dom->query('//select[@name="aset[' . $index . '][' . $field . ']"]')->item(0);
|
||||
expect($select->hasAttribute('required'))->toBe($present);
|
||||
expect($select->hasAttribute('disabled'))->toBe(!$present);
|
||||
}
|
||||
})->with([true, false]);
|
||||
|
||||
test('asset validation errors appear beside the marked required dropdowns', function () {
|
||||
$group = ($this->kelompok)();
|
||||
$request = ($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET');
|
||||
session()->flashInput(['aset' => [['status_aset' => '1']]]);
|
||||
$messages = [
|
||||
'aset.0.status_pengelolaan_aset' => 'Status Pengelolaan wajib diisi jika aset dipilih Ada.',
|
||||
'aset.0.kondisi_aset' => 'Kondisi wajib diisi jika aset dipilih Ada.',
|
||||
];
|
||||
$errors = new ViewErrorBag();
|
||||
$errors->put('default', new Illuminate\Support\MessageBag($messages));
|
||||
view()->share('errors', $errors);
|
||||
$dom = ($this->profileDom)(app(SurveyKelompokTaniController::class)->edit($request, $group));
|
||||
|
||||
expect($dom->query('//form[@id="aset-produktif-form"]//th/span[contains(@class,"text-danger")]')->length)->toBe(2);
|
||||
foreach (['status_pengelolaan_aset' => 'status-pengelolaan', 'kondisi_aset' => 'kondisi'] as $field => $idPart) {
|
||||
$id = 'aset-0-' . $idPart;
|
||||
$select = $dom->query('//select[@id="' . $id . '"]')->item(0);
|
||||
expect($select->getAttribute('class'))->toContain('is-invalid');
|
||||
expect($select->getAttribute('aria-describedby'))->toBe($id . '-error');
|
||||
expect(trim($dom->query('//div[@id="' . $id . '-error"]')->item(0)->textContent))->toBe($messages['aset.0.' . $field]);
|
||||
}
|
||||
});
|
||||
|
||||
test('saving assets advances to cash only when the cash section is available', function (bool $hasCash) {
|
||||
$group = ($this->kelompok)();
|
||||
$controller = app(AsetKelompokTaniController::class);
|
||||
$input = ['aset' => [[
|
||||
'jenis_aset' => 'Kas/simpanan kelompok', 'status_aset' => $hasCash,
|
||||
'status_pengelolaan_aset' => 'Swadaya', 'kondisi_aset' => 'Berfungsi',
|
||||
]]];
|
||||
$response = $controller->update(($this->profileRequest)($input), $group);
|
||||
$url = route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi';
|
||||
expect($response->getTargetUrl())->toBe($url . ($hasCash ? '#section-kas' : '#section-status-bantuan'));
|
||||
|
||||
$input['aset'][0]['status_aset'] = false;
|
||||
$response = $controller->update(($this->profileRequest)($input), $group);
|
||||
expect($response->getTargetUrl())->toBe($url . '#section-status-bantuan');
|
||||
})->with([true, false]);
|
||||
|
||||
test('saving aid status stays on the combined aid card in Operasional', function (bool $hasAid) {
|
||||
$group = ($this->kelompok)();
|
||||
$response = app(SurveyKelompokTaniController::class)->update(($this->profileRequest)([
|
||||
'section' => 'status-bantuan', 'pernah_mendapatkan_bantuan' => $hasAid,
|
||||
'bantuan' => $hasAid ? [['nama_program' => 'Program A', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit', 'tahun' => 2026]] : [],
|
||||
]), $group);
|
||||
$url = route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi';
|
||||
expect($response->getTargetUrl())->toBe($url . '#section-status-bantuan');
|
||||
})->with([true, false]);
|
||||
|
||||
test('legacy aid saves return to Operasional and failed saves retain the notification target', function () {
|
||||
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => true]);
|
||||
$controller = app(BantuanKelompokTaniController::class);
|
||||
$response = $controller->store(($this->profileRequest)(['bantuan' => [[
|
||||
'nama_program' => 'Bibit Kakao', 'penyelenggara' => 'Dinas Pertanian', 'bentuk_bantuan' => 'Bibit', 'tahun' => 2026,
|
||||
]]], 'POST'), $group);
|
||||
$url = route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi';
|
||||
expect($response->getTargetUrl())->toBe($url . '#section-status-bantuan');
|
||||
|
||||
$group->update(['pernah_mendapatkan_bantuan' => false]);
|
||||
$response = $controller->store(($this->profileRequest)([], 'POST'), $group);
|
||||
expect($response->getTargetUrl())->toBe($url . '#kelompok-tani-notifications');
|
||||
expect($group->bantuan()->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('single and final section saves stay on their own box', function () {
|
||||
$group = ($this->kelompok)();
|
||||
$controller = app(SurveyKelompokTaniController::class);
|
||||
foreach ([
|
||||
'kelembagaan' => ['cakupan_wilayah' => 'Desa'],
|
||||
'sosial-konflik' => ['konflik_kelompok' => 'Tidak pernah'],
|
||||
] as $section => $input) {
|
||||
$response = $controller->update(($this->profileRequest)(['section' => $section, ...$input]), $group);
|
||||
expect(parse_url($response->getTargetUrl(), PHP_URL_FRAGMENT))->toBe('section-' . $section);
|
||||
}
|
||||
});
|
||||
|
||||
test('invalid required fields return Indonesian errors above the summary and beside each field', function (string $section, array $payload, array $messages) {
|
||||
$group = ($this->kelompok)();
|
||||
$url = route('kelompok-tani.edit', $group->id) . ($section === 'identitas' ? '' : '?section=' . ($section === 'status-bantuan' ? 'aktivitas-fungsi' : $section));
|
||||
$response = $this->from($url)->put(route('kelompok-tani.update', $group->id), ['section' => $section, ...$payload]);
|
||||
$response->assertRedirect($url)->assertSessionHasErrors(array_keys($messages));
|
||||
$page = $this->get($url)->assertOk();
|
||||
expect(strpos($page->getContent(), 'id="kelompok-tani-notifications"'))->toBeLessThan(strpos($page->getContent(), 'id="kelompok-tani-summary"'));
|
||||
|
||||
$dom = new DOMDocument();
|
||||
libxml_use_internal_errors(true);
|
||||
$dom->loadHTML($page->getContent());
|
||||
libxml_clear_errors();
|
||||
$xpath = new DOMXPath($dom);
|
||||
$summary = $xpath->query('//div[@id="kelompok-tani-notifications"]')->item(0)->textContent;
|
||||
$inlineMessages = collect($xpath->query('//div[contains(@class,"invalid-feedback")]'))->map(fn ($node) => trim($node->textContent))->all();
|
||||
foreach ($messages as $message) {
|
||||
expect($summary)->toContain($message);
|
||||
expect($inlineMessages)->toContain($message);
|
||||
}
|
||||
expect($xpath->query('//input[contains(@class,"is-invalid")] | //select[contains(@class,"is-invalid")]')->length)->toBeGreaterThan(0);
|
||||
expect($group->refresh()->nama_kelompok)->toBe('Kelompok A');
|
||||
})->with([
|
||||
['identitas', [], [
|
||||
'nama_responden' => 'Nama responden wajib diisi.', 'nama_kelompok' => 'Nama kelompok wajib diisi.',
|
||||
'provinsi_id' => 'Provinsi wajib diisi.', 'kabupaten_id' => 'Kabupaten/Kota wajib diisi.',
|
||||
'kecamatan_id' => 'Kecamatan wajib diisi.', 'desa_kelurahan_id' => 'Desa/Kelurahan wajib dipilih.',
|
||||
'jenis_kelompok' => 'Terdaftar/Dikenal Sebagai wajib diisi.',
|
||||
]],
|
||||
['identitas', ['jenis_kelompok' => 'Lainnya'], ['jenis_kelompok_lainnya' => 'Sebutkan Jenis Kelompok wajib diisi.']],
|
||||
['status-bantuan', [], ['pernah_mendapatkan_bantuan' => 'Pengalaman Akses Bantuan/Program wajib diisi.']],
|
||||
['status-bantuan', ['pernah_mendapatkan_bantuan' => '1', 'bantuan' => [[]]], [
|
||||
'bantuan.0.nama_program' => 'Nama Program/Bantuan pada baris pertama wajib diisi.',
|
||||
'bantuan.0.penyelenggara' => 'Penyelenggara pada baris pertama wajib diisi.',
|
||||
'bantuan.0.bentuk_bantuan' => 'Bentuk Bantuan pada baris pertama wajib diisi.',
|
||||
'bantuan.0.tahun' => 'Tahun pada baris pertama wajib diisi.',
|
||||
]],
|
||||
['sosial-konflik', ['konflik_kelompok' => 'Pernah, sudah selesai'], [
|
||||
'topik_konflik' => 'Jika Pernah, Soal Apa? wajib diisi jika pernah terjadi konflik internal.',
|
||||
]],
|
||||
]);
|
||||
|
||||
test('asset errors show together above the summary and beside both conditional dropdowns', function () {
|
||||
$group = ($this->kelompok)();
|
||||
$url = route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi';
|
||||
$this->from($url)->put(route('kelompok-tani.aset-keuangan.update', $group->id), [
|
||||
'aset' => [['jenis_aset' => AsetKelompokTaniController::JENIS_ASET_BAKU[0], 'status_aset' => '1']],
|
||||
])->assertRedirect($url)->assertSessionHasErrors(['aset.0.status_pengelolaan_aset', 'aset.0.kondisi_aset']);
|
||||
$page = $this->get($url)->assertOk();
|
||||
foreach (['Status Pengelolaan wajib diisi jika aset dipilih Ada.', 'Kondisi wajib diisi jika aset dipilih Ada.'] as $message) {
|
||||
expect(substr_count($page->getContent(), $message))->toBeGreaterThanOrEqual(2);
|
||||
}
|
||||
expect($group->aset()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('profile location choices survive a failed save on both create and edit', function (bool $creating) {
|
||||
$group = ($this->kelompok)();
|
||||
$url = $creating ? route('kelompok-tani.create') : route('kelompok-tani.edit', $group->id);
|
||||
$input = ['provinsi_id' => 1, 'kabupaten_id' => 1, 'kecamatan_id' => 1, 'desa_kelurahan_id' => 1, 'jenis_kelompok' => 'Kelompok Tani (Poktan)'];
|
||||
$response = $creating
|
||||
? $this->from($url)->post(route('kelompok-tani.store'), $input)
|
||||
: $this->from($url)->put(route('kelompok-tani.update', $group->id), $input);
|
||||
$response->assertRedirect($url)->assertSessionHasErrors(['nama_responden', 'nama_kelompok']);
|
||||
$page = $this->get($url)->assertOk();
|
||||
$dom = new DOMDocument();
|
||||
libxml_use_internal_errors(true);
|
||||
$dom->loadHTML($page->getContent());
|
||||
libxml_clear_errors();
|
||||
$xpath = new DOMXPath($dom);
|
||||
foreach (array_keys($input) as $field) {
|
||||
expect($xpath->query('//select[@name="' . $field . '"]/option[@selected]')->length)->toBe(1);
|
||||
}
|
||||
expect($xpath->query('//select[@name="desa_kelurahan_id" and @disabled]')->length)->toBe(0);
|
||||
})->with([true, false]);
|
||||
|
||||
test('invalid location input returns feedback without breaking the form reload', function ($invalidId) {
|
||||
$url = route('kelompok-tani.create');
|
||||
$this->from($url)->post(route('kelompok-tani.store'), [
|
||||
'provinsi_id' => $invalidId, 'kabupaten_id' => $invalidId, 'kecamatan_id' => $invalidId, 'desa_kelurahan_id' => $invalidId,
|
||||
])->assertRedirect($url)->assertSessionHasErrors(['provinsi_id', 'kabupaten_id', 'kecamatan_id', 'desa_kelurahan_id']);
|
||||
$this->get($url)->assertOk()->assertSee('Pilihan Provinsi tidak valid.');
|
||||
})->with([[['invalid']], ['1.5'], ['invalid']]);
|
||||
|
||||
test('aid experience and program entries are saved together in Operasional', function () {
|
||||
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => false]);
|
||||
$response = app(SurveyKelompokTaniController::class)->update(($this->profileRequest)([
|
||||
'section' => 'status-bantuan', 'pernah_mendapatkan_bantuan' => '1',
|
||||
'bantuan' => [
|
||||
['nama_program' => 'Bibit Kakao', 'penyelenggara' => 'Dinas Pertanian', 'bentuk_bantuan' => 'Bibit', 'tahun' => 2026],
|
||||
['nama_program' => '', 'penyelenggara' => '', 'bentuk_bantuan' => '', 'tahun' => null],
|
||||
],
|
||||
]), $group);
|
||||
expect($group->refresh()->pernah_mendapatkan_bantuan)->toBeTrue();
|
||||
expect($group->bantuan()->pluck('nama_program')->all())->toBe(['Bibit Kakao']);
|
||||
expect($response->getTargetUrl())->toBe(route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi#section-status-bantuan');
|
||||
expect(session('success'))->toBe('Data Operasional berhasil disimpan.');
|
||||
});
|
||||
|
||||
test('answering Belum Pernah ignores hidden program payloads and preserves existing history', function ($payload) {
|
||||
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => true]);
|
||||
$group->bantuan()->create([
|
||||
'uid' => 'BKT-TEST', 'nama_program' => 'Program Lama', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit',
|
||||
]);
|
||||
app(SurveyKelompokTaniController::class)->update(($this->profileRequest)([
|
||||
'section' => 'status-bantuan', 'pernah_mendapatkan_bantuan' => '0', 'bantuan' => $payload,
|
||||
]), $group);
|
||||
expect($group->refresh()->pernah_mendapatkan_bantuan)->toBeFalse();
|
||||
expect($group->bantuan()->pluck('nama_program')->all())->toBe(['Program Lama']);
|
||||
})->with([
|
||||
['malformed'],
|
||||
[[['nama_program' => ['invalid'], 'tahun' => 'invalid']]],
|
||||
]);
|
||||
|
||||
test('invalid aid entries cannot partially update the experience answer', function () {
|
||||
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => false]);
|
||||
$request = ($this->profileRequest)([
|
||||
'section' => 'status-bantuan', 'pernah_mendapatkan_bantuan' => '1',
|
||||
'bantuan' => [['nama_program' => 'Program A', 'tahun' => date('Y') + 1]],
|
||||
]);
|
||||
expect(fn () => app(SurveyKelompokTaniController::class)->update($request, $group))->toThrow(ValidationException::class);
|
||||
expect($group->refresh()->pernah_mendapatkan_bantuan)->toBeFalse();
|
||||
expect($group->bantuan()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('aid creation failures roll back the experience answer', function () {
|
||||
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => false]);
|
||||
Event::listen('eloquent.creating: ' . BantuanKelompokTani::class, function () {
|
||||
throw new RuntimeException('Simulated aid failure');
|
||||
});
|
||||
$request = ($this->profileRequest)([
|
||||
'section' => 'status-bantuan', 'pernah_mendapatkan_bantuan' => '1',
|
||||
'bantuan' => [['nama_program' => 'Program A', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit', 'tahun' => 2026]],
|
||||
]);
|
||||
expect(fn () => app(SurveyKelompokTaniController::class)->update($request, $group))->toThrow(RuntimeException::class);
|
||||
expect($group->refresh()->pernah_mendapatkan_bantuan)->toBeFalse();
|
||||
expect($group->bantuan()->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('aid list shares the experience box and is only visible for Pernah', function (?bool $savedAnswer, ?string $oldAnswer, bool $visible) {
|
||||
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => $savedAnswer]);
|
||||
$request = ($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET');
|
||||
if ($oldAnswer !== null) {
|
||||
session()->flashInput([
|
||||
'pernah_mendapatkan_bantuan' => $oldAnswer,
|
||||
'bantuan' => [['nama_program' => 'Program belum disimpan', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit', 'tahun' => 2026]],
|
||||
]);
|
||||
}
|
||||
$dom = ($this->profileDom)(app(SurveyKelompokTaniController::class)->edit($request, $group));
|
||||
expect($dom->query('//form[@id="pengalaman-bantuan-form"]//div[@id="section-bantuan"]')->length)->toBe(1);
|
||||
expect($dom->query('//div[@id="section-bantuan"]')->item(0)->hasAttribute('hidden'))->toBe(!$visible);
|
||||
expect($dom->query('//fieldset[@id="bantuan-fields"]')->item(0)->hasAttribute('disabled'))->toBe(!$visible);
|
||||
expect($dom->query('//form[@id="pengalaman-bantuan-form"]//button[contains(@class,"btn-success")]')->length)->toBe(1);
|
||||
expect($dom->query('//form[@id="pengalaman-bantuan-form"]//form')->length)->toBe(0);
|
||||
expect($dom->query('//div[@id="section-penilaian"] | //input[@name="potensi_kelompok"] | //textarea[@name="alasan_penilaian_potensi"]')->length)->toBe(0);
|
||||
if ($oldAnswer !== null) {
|
||||
expect($dom->query('//form[@id="pengalaman-bantuan-form"]//input[@value="Program belum disimpan"]')->length)->toBe(1);
|
||||
}
|
||||
})->with([
|
||||
[null, null, false], [false, null, false], [true, null, true], [true, '0', false], [false, '1', true],
|
||||
]);
|
||||
|
||||
test('old aid URLs redirect to Operasional and removed assessment fields cannot be saved', function () {
|
||||
$group = ($this->kelompok)(['potensi_kelompok' => 'Rendah']);
|
||||
$url = route('kelompok-tani.edit', $group->id) . '?section=aktivitas-fungsi#section-status-bantuan';
|
||||
expect(app(BantuanKelompokTaniController::class)->index($group)->getTargetUrl())->toBe($url);
|
||||
$response = app(SurveyKelompokTaniController::class)->update(($this->profileRequest)([
|
||||
'section' => 'penilaian', 'potensi_kelompok' => 'Tinggi', 'alasan_penilaian_potensi' => 'Diabaikan',
|
||||
]), $group);
|
||||
expect($response->getTargetUrl())->toBe($url);
|
||||
expect($group->refresh()->potensi_kelompok)->toBe('Rendah');
|
||||
});
|
||||
|
||||
test('Pernah requires every first-row aid field before saving', function (string $missing) {
|
||||
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => false]);
|
||||
$row = ['nama_program' => 'Program A', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit', 'tahun' => 2026];
|
||||
$row[$missing] = ' ';
|
||||
$request = ($this->profileRequest)(['pernah_mendapatkan_bantuan' => '1', 'bantuan' => [$row]]);
|
||||
try {
|
||||
app(BantuanKelompokTaniController::class)->update($request, $group);
|
||||
$this->fail('An incomplete first row must be rejected.');
|
||||
} catch (ValidationException $exception) {
|
||||
expect($exception->errors())->toHaveKey('bantuan.0.' . $missing);
|
||||
}
|
||||
expect($group->refresh()->pernah_mendapatkan_bantuan)->toBeFalse();
|
||||
expect($group->bantuan()->count())->toBe(0);
|
||||
})->with(['nama_program', 'penyelenggara', 'bentuk_bantuan', 'tahun']);
|
||||
|
||||
test('Pernah rejects missing first aid rows including attempts to skip the first index', function ($rows) {
|
||||
$group = ($this->kelompok)();
|
||||
$request = ($this->profileRequest)(['pernah_mendapatkan_bantuan' => '1', 'bantuan' => $rows]);
|
||||
expect(fn () => app(BantuanKelompokTaniController::class)->update($request, $group))->toThrow(ValidationException::class);
|
||||
expect($group->bantuan()->count())->toBe(0);
|
||||
})->with([
|
||||
[null], [[]], [[[]]],
|
||||
[[1 => ['nama_program' => 'Program A', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit', 'tahun' => 2026]]],
|
||||
]);
|
||||
|
||||
test('recorded aid meets the minimum without requiring a duplicate entry', function () {
|
||||
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => true]);
|
||||
$group->bantuan()->create(['uid' => 'BKT-EXISTING', 'nama_program' => 'Program Lama', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit']);
|
||||
app(BantuanKelompokTaniController::class)->update(($this->profileRequest)(['pernah_mendapatkan_bantuan' => '1']), $group);
|
||||
expect($group->bantuan()->count())->toBe(1);
|
||||
|
||||
app(BantuanKelompokTaniController::class)->update(($this->profileRequest)([
|
||||
'pernah_mendapatkan_bantuan' => '1', 'bantuan' => [['nama_program' => 'Program Tambahan'], []],
|
||||
]), $group);
|
||||
expect($group->bantuan()->count())->toBe(2);
|
||||
});
|
||||
|
||||
test('an optional aid row with details still needs a program name', function () {
|
||||
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => true]);
|
||||
$group->bantuan()->create(['uid' => 'BKT-EXISTING', 'nama_program' => 'Program Lama', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit']);
|
||||
$request = ($this->profileRequest)(['pernah_mendapatkan_bantuan' => '1', 'bantuan' => [['penyelenggara' => 'Dinas']]]);
|
||||
expect(fn () => app(BantuanKelompokTaniController::class)->update($request, $group))->toThrow(ValidationException::class);
|
||||
expect($group->bantuan()->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('required aid markers only apply to the first unsaved row for Pernah', function (bool $pernah, bool $saved) {
|
||||
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => $pernah]);
|
||||
if ($saved) {
|
||||
$group->bantuan()->create(['uid' => 'BKT-EXISTING', 'nama_program' => 'Program Lama', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit']);
|
||||
}
|
||||
$request = ($this->profileRequest)(['section' => 'aktivitas-fungsi'], 'GET');
|
||||
session()->flashInput(['bantuan' => [[], []]]);
|
||||
$dom = ($this->profileDom)(app(SurveyKelompokTaniController::class)->edit($request, $group));
|
||||
$expected = $pernah && !$saved ? 4 : 0;
|
||||
expect($dom->query('//fieldset[@id="bantuan-fields"]//input[@required]')->length)->toBe($expected);
|
||||
expect($dom->query('//fieldset[@id="bantuan-fields"]//span[contains(@class,"bantuan-required-mark") and not(@hidden)]')->length)->toBe($expected);
|
||||
expect($dom->query('//div[@data-repeater-list="bantuan"]/div[2]//input[@required]')->length)->toBe(0);
|
||||
expect($dom->query('//div[@id="section-bantuan"]/p[normalize-space()="Minimal satu data bantuan wajib diisi."]')->length)->toBe(1);
|
||||
$firstRow = $saved
|
||||
? '//div[@id="section-bantuan"]/div[1]'
|
||||
: '//div[@data-repeater-list="bantuan"]/div[1]';
|
||||
foreach (['Nama Program/Bantuan', 'Penyelenggara', 'Bentuk Bantuan', 'Tahun'] as $label) {
|
||||
$marks = $dom->query($firstRow . '//label[starts-with(normalize-space(),"' . $label . '")]/span[contains(@class,"text-danger") and not(@hidden)]');
|
||||
expect($marks->length)->toBe($saved || $pernah ? 1 : 0);
|
||||
}
|
||||
})->with([[true, false], [false, false], [true, true]]);
|
||||
|
||||
test('conflict topics are required for both Pernah answers without partial saves', function (string $answer, $topic) {
|
||||
$group = ($this->kelompok)(['konflik_kelompok' => 'Tidak pernah', 'partisipasi_perempuan' => 'Hadir tapi pasif']);
|
||||
$request = ($this->profileRequest)([
|
||||
'section' => 'sosial-konflik', 'konflik_kelompok' => $answer,
|
||||
'topik_konflik' => $topic, 'partisipasi_perempuan' => 'Tidak terlibat',
|
||||
]);
|
||||
try {
|
||||
app(SurveyKelompokTaniController::class)->update($request, $group);
|
||||
$this->fail('A conflict topic is required.');
|
||||
} catch (ValidationException $exception) {
|
||||
expect($exception->errors())->toHaveKey('topik_konflik');
|
||||
}
|
||||
expect($group->refresh()->konflik_kelompok)->toBe('Tidak pernah');
|
||||
expect($group->partisipasi_perempuan)->toBe('Hadir tapi pasif');
|
||||
})->with(['Pernah, sudah selesai', 'Pernah, masih berlanjut/belum selesai'])->with([null, '', ' ']);
|
||||
|
||||
test('social saves accept conflict topics and require none for other answers', function ($answer, bool $required) {
|
||||
$group = ($this->kelompok)();
|
||||
$request = ($this->profileRequest)([
|
||||
'section' => 'sosial-konflik', 'konflik_kelompok' => $answer,
|
||||
...($required ? ['topik_konflik' => 'Pembagian hasil'] : []),
|
||||
]);
|
||||
app(SurveyKelompokTaniController::class)->update($request, $group);
|
||||
expect($group->refresh()->topik_konflik)->toBe($required ? 'Pembagian hasil' : null);
|
||||
expect(session('success'))->toBe('Data Sosial berhasil disimpan.');
|
||||
|
||||
($this->profileRequest)(['section' => 'sosial-konflik'], 'GET');
|
||||
$dom = ($this->profileDom)(view('produktivitas.survey-kelompok-tani.sosial-konflik', ['kelompokTani' => $group]));
|
||||
$input = $dom->query('//input[@id="sk-topik-konflik"]')->item(0);
|
||||
expect($input->hasAttribute('required'))->toBe($required);
|
||||
expect($input->hasAttribute('disabled'))->toBe(!$required);
|
||||
expect($dom->query('//label[@for="sk-topik-konflik"]/span[not(@hidden)]')->length)->toBe($required ? 1 : 0);
|
||||
})->with([
|
||||
['Pernah, sudah selesai', true], ['Pernah, masih berlanjut/belum selesai', true],
|
||||
['Tidak pernah', false], ['Tidak bersedia menjawab', false], [null, false],
|
||||
]);
|
||||
|
||||
test('conflict topic requirements and input follow the previous submission after validation', function () {
|
||||
$group = ($this->kelompok)(['konflik_kelompok' => 'Tidak pernah']);
|
||||
($this->profileRequest)([], 'GET');
|
||||
session()->flashInput(['konflik_kelompok' => 'Pernah, sudah selesai', 'topik_konflik' => 'Pembagian hasil']);
|
||||
$dom = ($this->profileDom)(view('produktivitas.survey-kelompok-tani.sosial-konflik', ['kelompokTani' => $group]));
|
||||
$input = $dom->query('//input[@id="sk-topik-konflik"]')->item(0);
|
||||
expect($input->hasAttribute('required'))->toBeTrue();
|
||||
expect($input->getAttribute('value'))->toBe('Pembagian hasil');
|
||||
expect($dom->query('//div[@id="sk-detail-konflik-wrapper"]')->item(0)->hasAttribute('style'))->toBeFalse();
|
||||
});
|
||||
|
||||
test('the last recorded aid cannot be deleted while the answer remains Pernah', function () {
|
||||
$group = ($this->kelompok)(['pernah_mendapatkan_bantuan' => true]);
|
||||
$aid = $group->bantuan()->create(['uid' => 'BKT-EXISTING', 'nama_program' => 'Program Lama', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit']);
|
||||
$controller = app(BantuanKelompokTaniController::class);
|
||||
$response = $controller->destroy($group, $aid);
|
||||
expect($response->getTargetUrl())->toEndWith('#kelompok-tani-notifications');
|
||||
expect($group->bantuan()->count())->toBe(1);
|
||||
$other = $group->bantuan()->create(['uid' => 'BKT-OTHER', 'nama_program' => 'Program Lain', 'penyelenggara' => 'Dinas', 'bentuk_bantuan' => 'Bibit']);
|
||||
$controller->destroy($group, $aid);
|
||||
expect($group->bantuan()->count())->toBe(1);
|
||||
$group->update(['pernah_mendapatkan_bantuan' => false]);
|
||||
$controller->destroy($group, $other);
|
||||
expect($group->bantuan()->count())->toBe(0);
|
||||
});
|
||||
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Web\Dashboard\SummaryController;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
beforeEach(function () {
|
||||
Schema::create('master_komoditas', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama');
|
||||
$table->softDeletes();
|
||||
});
|
||||
foreach ([
|
||||
'master_kabupaten_kota' => 'provinsi_id',
|
||||
'master_kecamatan' => 'kabupaten_kota_id',
|
||||
'master_desa_kelurahan' => 'kecamatan_id',
|
||||
] as $name => $parent) {
|
||||
Schema::create($name, function (Blueprint $table) use ($parent) {
|
||||
$table->id();
|
||||
$table->integer($parent);
|
||||
});
|
||||
}
|
||||
Schema::create('petani', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('komoditas_id');
|
||||
$table->integer('desa_kelurahan_id');
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::create('harga_jual_panen', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('petani_id');
|
||||
$table->string('bentuk_produk');
|
||||
$table->integer('harga_panen_terakhir')->nullable();
|
||||
$table->integer('harga_tahun_lalu')->nullable();
|
||||
$table->integer('harga_dua_tahun_lalu')->nullable();
|
||||
});
|
||||
Schema::create('petani_skor', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('petani_id');
|
||||
$table->float('skor_kesiapan_adopsi');
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::create('akses_pasar', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('petani_id');
|
||||
$table->text('tujuan_penjualan')->nullable();
|
||||
$table->text('sifat_hubungan_bisnis')->nullable();
|
||||
$table->string('frekuensi_penjualan')->nullable();
|
||||
$table->integer('skor_pengalaman')->nullable();
|
||||
$table->integer('skor_kesadaran_premium')->nullable();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
DB::table('master_komoditas')->insert([
|
||||
['id' => 1, 'nama' => 'Kakao'], ['id' => 2, 'nama' => 'Vanili'],
|
||||
['id' => 3, 'nama' => 'Kopi'], ['id' => 4, 'nama' => 'Kemiri'],
|
||||
]);
|
||||
DB::table('master_kabupaten_kota')->insert(['id' => 1, 'provinsi_id' => 1]);
|
||||
DB::table('master_kecamatan')->insert(['id' => 1, 'kabupaten_kota_id' => 1]);
|
||||
DB::table('master_desa_kelurahan')->insert([
|
||||
['id' => 1, 'kecamatan_id' => 1], ['id' => 2, 'kecamatan_id' => 1],
|
||||
]);
|
||||
DB::table('petani')->insert([
|
||||
['id' => 1, 'komoditas_id' => 1, 'desa_kelurahan_id' => 1, 'deleted_at' => null],
|
||||
['id' => 2, 'komoditas_id' => 1, 'desa_kelurahan_id' => 2, 'deleted_at' => null],
|
||||
['id' => 3, 'komoditas_id' => 2, 'desa_kelurahan_id' => 1, 'deleted_at' => null],
|
||||
['id' => 4, 'komoditas_id' => 3, 'desa_kelurahan_id' => 1, 'deleted_at' => null],
|
||||
['id' => 5, 'komoditas_id' => 4, 'desa_kelurahan_id' => 1, 'deleted_at' => null],
|
||||
['id' => 6, 'komoditas_id' => 1, 'desa_kelurahan_id' => 1, 'deleted_at' => '2026-01-01'],
|
||||
]);
|
||||
foreach ([
|
||||
[1, 'Biji kering/Biji Kupas', 10000], [2, 'Biji kering/Biji Kupas', 20000],
|
||||
[1, 'Biji kering/Biji Kupas', 0], [1, 'Biji kering/Biji Kupas', null],
|
||||
[1, 'Biji kering/Biji Kupas', -1000], [1, 'Biji basah', 90000],
|
||||
[1, 'Buah segar hasil petik', 5000],
|
||||
[3, 'Buah segar hasil petik', 30000], [3, 'Biji kering/Biji Kupas', 500000],
|
||||
[3, 'Terfermentasi (Rp/kg)', 900000], [4, 'Buah segar hasil petik', 12000],
|
||||
[5, 'Buah segar hasil petik', 8000], [5, 'Biji kering/Biji Kupas', 40000],
|
||||
[6, 'Biji kering/Biji Kupas', 1000000],
|
||||
] as [$farmer, $product, $price]) {
|
||||
DB::table('harga_jual_panen')->insert([
|
||||
'petani_id' => $farmer, 'bentuk_produk' => $product, 'harga_panen_terakhir' => $price,
|
||||
]);
|
||||
}
|
||||
DB::table('petani_skor')->insert([
|
||||
['petani_id' => 1, 'skor_kesiapan_adopsi' => 4.2, 'deleted_at' => null],
|
||||
['petani_id' => 2, 'skor_kesiapan_adopsi' => 2.5, 'deleted_at' => null],
|
||||
['petani_id' => 3, 'skor_kesiapan_adopsi' => 4.5, 'deleted_at' => '2026-01-01'],
|
||||
]);
|
||||
|
||||
$controller = app(SummaryController::class);
|
||||
$method = new ReflectionMethod($controller, 'marketDashboardData');
|
||||
$this->marketCards = fn (array $filters = []) => $method->invoke($controller, new Request($filters))['priceCards'];
|
||||
$this->marketCharts = fn (array $filters = []) => $method->invoke($controller, new Request($filters))['marketCharts'];
|
||||
$this->priceTrend = fn (array $filters = []) => $method->invoke($controller, new Request($filters))['priceTrend'];
|
||||
$this->premiumAwareness = fn (array $filters = []) => $method->invoke($controller, new Request($filters))['premiumAwareness'];
|
||||
});
|
||||
|
||||
test('market shows all four prices using the appropriate product and positive active farmer prices', function () {
|
||||
$cards = ($this->marketCards)();
|
||||
|
||||
expect(array_column($cards, 'label'))->toBe([
|
||||
'Kakao - Harga Biji Kering', 'Vanili - Harga Buah Segar',
|
||||
'Kopi - Harga Ceri Merah', 'Kemiri - Harga Gelondong',
|
||||
]);
|
||||
expect(array_column($cards, 'value'))->toBe(['Rp 15.000', 'Rp 30.000', 'Rp 12.000', 'Rp 8.000']);
|
||||
expect(array_column($cards, 'records'))->toBe([2, 1, 1, 1]);
|
||||
});
|
||||
|
||||
test('market prices respect each region level and commodity selection', function () {
|
||||
foreach (['provinsi_id', 'kabupatenKota_id', 'kecamatan_id', 'desaKelurahan_id'] as $region) {
|
||||
expect(($this->marketCards)([$region => 1])[0]['value'])
|
||||
->toBe($region === 'desaKelurahan_id' ? 'Rp 10.000' : 'Rp 15.000');
|
||||
expect(array_column(($this->marketCards)([$region => 99]), 'records'))->toBe([0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
$cards = ($this->marketCards)(['komoditas_id' => 2]);
|
||||
expect(array_column($cards, 'records'))->toBe([0, 1, 0, 0]);
|
||||
expect(array_column($cards, 'value'))->toBe(['Rp 0', 'Rp 30.000', 'Rp 0', 'Rp 0']);
|
||||
});
|
||||
|
||||
test('market prices respect segment selection and ignore deleted scores', function () {
|
||||
$cards = ($this->marketCards)(['segmen' => 'Penggerak']);
|
||||
expect(array_column($cards, 'records'))->toBe([1, 0, 0, 0]);
|
||||
expect($cards[0]['value'])->toBe('Rp 10.000');
|
||||
expect(($this->marketCards)(['segmen' => 'Pemerhati'])[0]['value'])->toBe('Rp 20.000');
|
||||
});
|
||||
|
||||
test('market keeps four empty cards when no prices or active commodities are available', function () {
|
||||
DB::table('master_komoditas')->update(['deleted_at' => '2026-01-01']);
|
||||
|
||||
$cards = ($this->marketCards)();
|
||||
expect(array_column($cards, 'records'))->toBe([0, 0, 0, 0]);
|
||||
expect(array_column($cards, 'value'))->toBe(['Rp 0', 'Rp 0', 'Rp 0', 'Rp 0']);
|
||||
});
|
||||
|
||||
test('market charts count multiple channels once per respondent and use answered question denominators', function () {
|
||||
DB::table('akses_pasar')->insert([
|
||||
[
|
||||
'petani_id' => 1,
|
||||
'tujuan_penjualan' => json_encode(['Koperasi', 'BUMDes', 'Koperasi', 'Lainnya']),
|
||||
'sifat_hubungan_bisnis' => 'Bebas, bisa pindah pembeli kapan saja',
|
||||
'frekuensi_penjualan' => 'Langsung setelah panen',
|
||||
'skor_pengalaman' => 5,
|
||||
],
|
||||
[
|
||||
'petani_id' => 2,
|
||||
'tujuan_penjualan' => json_encode(['Koperasi']),
|
||||
'sifat_hubungan_bisnis' => 'Titip jual,dibayar setelah produk terjual',
|
||||
'frekuensi_penjualan' => null,
|
||||
'skor_pengalaman' => 1,
|
||||
],
|
||||
[
|
||||
'petani_id' => 3,
|
||||
'tujuan_penjualan' => '[]',
|
||||
'sifat_hubungan_bisnis' => '',
|
||||
'frekuensi_penjualan' => 'Saat butuh uang mendesak',
|
||||
'skor_pengalaman' => 3,
|
||||
],
|
||||
]);
|
||||
|
||||
$charts = ($this->marketCharts)();
|
||||
expect($charts['saluranPenjualan']['series'])->toBe([0, 2, 1, 0, 1]);
|
||||
expect($charts['saluranPenjualan']['respondents'])->toBe(2);
|
||||
expect($charts['saluranPenjualan']['percentages'])->toBe([0.0, 100.0, 50.0, 0.0, 50.0]);
|
||||
expect($charts['hubunganPembeli']['series'])->toBe([1, 0, 0, 1, 0, 0]);
|
||||
expect($charts['hubunganPembeli']['respondents'])->toBe(2);
|
||||
expect($charts['waktuPenjualan']['series'])->toBe([1, 0, 1]);
|
||||
expect($charts['waktuPenjualan']['respondents'])->toBe(2);
|
||||
expect($charts['aksesSaluranLain']['series'])->toBe([1, 0, 1, 0, 1]);
|
||||
expect($charts['aksesSaluranLain']['percentages'])->toBe([33.3, 0.0, 33.3, 0.0, 33.3]);
|
||||
});
|
||||
|
||||
test('market charts respect all dashboard filters and exclude deleted farmers and responses', function () {
|
||||
foreach ([1, 2, 3, 6] as $farmer) {
|
||||
DB::table('akses_pasar')->insert([
|
||||
'petani_id' => $farmer,
|
||||
'tujuan_penjualan' => json_encode(['Koperasi']),
|
||||
'sifat_hubungan_bisnis' => 'Langganan tetap, tapi tidak terikat',
|
||||
'frekuensi_penjualan' => 'Ditumpuk dulu beberapa waktu',
|
||||
'skor_pengalaman' => 4,
|
||||
]);
|
||||
}
|
||||
DB::table('akses_pasar')->insert([
|
||||
'petani_id' => 4, 'tujuan_penjualan' => json_encode(['BUMDes']), 'deleted_at' => '2026-01-01',
|
||||
]);
|
||||
|
||||
foreach (['provinsi_id', 'kabupatenKota_id', 'kecamatan_id', 'desaKelurahan_id'] as $region) {
|
||||
foreach (($this->marketCharts)([$region => 1]) as $chart) {
|
||||
expect($chart['respondents'])->toBe($region === 'desaKelurahan_id' ? 2 : 3);
|
||||
}
|
||||
foreach (($this->marketCharts)([$region => 99]) as $chart) {
|
||||
expect($chart['respondents'])->toBe(0);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ([['komoditas_id' => 1], ['komoditas_id' => 2], ['segmen' => 'Penggerak'], ['segmen' => 'Pemerhati']] as $filters) {
|
||||
foreach (($this->marketCharts)($filters) as $chart) {
|
||||
expect($chart['respondents'])->toBe($filters === ['komoditas_id' => 1] ? 2 : 1);
|
||||
}
|
||||
}
|
||||
foreach (($this->marketCharts)(['komoditas_id' => 2, 'segmen' => 'Penggerak']) as $chart) {
|
||||
expect($chart['respondents'])->toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
test('market charts keep ordered zero categories when answers are missing or invalid', function () {
|
||||
DB::table('akses_pasar')->insert([
|
||||
'petani_id' => 1, 'tujuan_penjualan' => '[null, {}, "Unknown"]',
|
||||
'sifat_hubungan_bisnis' => 'Unknown', 'frekuensi_penjualan' => '', 'skor_pengalaman' => 9,
|
||||
]);
|
||||
|
||||
$charts = ($this->marketCharts)();
|
||||
expect(array_map(fn ($chart) => count($chart['series']), $charts))->toBe([
|
||||
'saluranPenjualan' => 5, 'hubunganPembeli' => 6, 'waktuPenjualan' => 3, 'aksesSaluranLain' => 5,
|
||||
]);
|
||||
foreach ($charts as $chart) {
|
||||
expect($chart['respondents'])->toBe(0);
|
||||
expect(array_sum($chart['series']))->toBe(0);
|
||||
expect(array_sum($chart['percentages']))->toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
test('price trend averages each product and commodity chronologically without treating missing prices as zero', function () {
|
||||
DB::table('harga_jual_panen')->delete();
|
||||
foreach ([
|
||||
[1, 'Buah segar hasil petik', 10000, 8000, 6000],
|
||||
[2, 'Buah segar hasil petik', 20000, 12000, null],
|
||||
[3, 'Buah segar hasil petik', 30000, 20000, 10000],
|
||||
[1, 'Biji basah', 0, 7000, 4000],
|
||||
[1, 'Biji kering/Biji Kupas', -1000, null, 0],
|
||||
[1, 'Terfermentasi (Rp/kg)', null, 20000, 10000],
|
||||
[6, 'Buah segar hasil petik', 1000000, 1000000, 1000000],
|
||||
] as [$farmer, $product, $latest, $lastYear, $twoYearsAgo]) {
|
||||
DB::table('harga_jual_panen')->insert([
|
||||
'petani_id' => $farmer, 'bentuk_produk' => $product,
|
||||
'harga_panen_terakhir' => $latest, 'harga_tahun_lalu' => $lastYear, 'harga_dua_tahun_lalu' => $twoYearsAgo,
|
||||
]);
|
||||
}
|
||||
|
||||
$trend = ($this->priceTrend)();
|
||||
$commodities = array_column($trend['commodities'], null, 'id');
|
||||
expect($trend['periods'])->toBe(['Dua Tahun Lalu', 'Tahun Lalu', 'Panen Terakhir']);
|
||||
expect(array_column($commodities[1]['series'], 'name'))->toBe([
|
||||
'Buah segar hasil petik', 'Biji basah', 'Biji kering/Biji Kupas', 'Terfermentasi',
|
||||
]);
|
||||
expect(array_column($commodities[1]['series'], 'data'))->toBe([
|
||||
[6000.0, 10000.0, 15000.0], [4000.0, 7000.0, null], [null, null, null], [10000.0, 20000.0, null],
|
||||
]);
|
||||
expect($commodities[2]['series'][0]['data'])->toBe([10000.0, 20000.0, 30000.0]);
|
||||
});
|
||||
|
||||
test('price trend respects dashboard region commodity and segment filters', function () {
|
||||
// Historical prices must follow the same filters as the latest harvest.
|
||||
DB::table('harga_jual_panen')->where('harga_panen_terakhir', '>', 0)
|
||||
->update(['harga_tahun_lalu' => DB::raw('harga_panen_terakhir / 2')]);
|
||||
|
||||
foreach (['provinsi_id', 'kabupatenKota_id', 'kecamatan_id', 'desaKelurahan_id'] as $region) {
|
||||
$commodities = array_column(($this->priceTrend)([$region => 1])['commodities'], null, 'id');
|
||||
$price = $region === 'desaKelurahan_id' ? 10000.0 : 15000.0;
|
||||
expect($commodities[1]['series'][2]['data'])->toBe([null, $price / 2, $price]);
|
||||
foreach (($this->priceTrend)([$region => 99])['commodities'] as $commodity) {
|
||||
expect(array_column($commodity['series'], 'data'))->toBe(array_fill(0, 4, [null, null, null]));
|
||||
}
|
||||
}
|
||||
|
||||
$trend = ($this->priceTrend)(['komoditas_id' => 2]);
|
||||
expect(array_column($trend['commodities'], 'id'))->toBe([2]);
|
||||
expect($trend['commodities'][0]['series'][0]['data'])->toBe([null, 15000.0, 30000.0]);
|
||||
|
||||
foreach (['Penggerak' => 10000.0, 'Pemerhati' => 20000.0] as $segment => $price) {
|
||||
$commodities = array_column(($this->priceTrend)(['segmen' => $segment])['commodities'], null, 'id');
|
||||
expect($commodities[1]['series'][2]['data'])->toBe([null, $price / 2, $price]);
|
||||
expect($commodities[2]['series'][0]['data'])->toBe([null, null, null]);
|
||||
}
|
||||
});
|
||||
|
||||
test('price trend retains missing points and handles empty or deleted commodity selections', function () {
|
||||
DB::table('harga_jual_panen')->delete();
|
||||
$trend = ($this->priceTrend)();
|
||||
expect($trend['commodities'])->toHaveCount(4);
|
||||
foreach ($trend['commodities'] as $commodity) {
|
||||
expect(array_column($commodity['series'], 'data'))->toBe(array_fill(0, 4, [null, null, null]));
|
||||
}
|
||||
|
||||
expect(($this->priceTrend)(['komoditas_id' => 99])['commodities'])->toBe([]);
|
||||
DB::table('master_komoditas')->where('id', 1)->update(['deleted_at' => '2026-01-01']);
|
||||
expect(($this->priceTrend)(['komoditas_id' => 1])['commodities'])->toBe([]);
|
||||
});
|
||||
|
||||
test('premium awareness counts each active farmer once and preserves the five ordered levels', function () {
|
||||
DB::table('akses_pasar')->insert([
|
||||
'petani_id' => 4, 'skor_kesadaran_premium' => 4, 'deleted_at' => '2026-01-01',
|
||||
]);
|
||||
foreach ([1 => 1, 2 => 3, 3 => 3, 4 => 5, 5 => null, 6 => 5] as $farmer => $score) {
|
||||
DB::table('akses_pasar')->insert(['petani_id' => $farmer, 'skor_kesadaran_premium' => $score]);
|
||||
}
|
||||
DB::table('akses_pasar')->insert(['petani_id' => 1, 'skor_kesadaran_premium' => 2]);
|
||||
|
||||
$chart = ($this->premiumAwareness)();
|
||||
expect($chart['series'])->toBe([1, 0, 2, 0, 1]);
|
||||
expect($chart['percentages'])->toBe([25.0, 0.0, 50.0, 0.0, 25.0]);
|
||||
expect($chart['respondents'])->toBe(4);
|
||||
expect($chart['fullLabels'][0])->toBe('Belum pernah mendengar sama sekali');
|
||||
expect($chart['fullLabels'][4])->toBe('Sudah mengakses dan mendapat harga premium secara konsisten');
|
||||
});
|
||||
|
||||
test('premium awareness respects all dashboard region commodity and segment filters', function () {
|
||||
foreach ([1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 5] as $farmer => $score) {
|
||||
DB::table('akses_pasar')->insert(['petani_id' => $farmer, 'skor_kesadaran_premium' => $score]);
|
||||
}
|
||||
|
||||
foreach (['provinsi_id', 'kabupatenKota_id', 'kecamatan_id', 'desaKelurahan_id'] as $region) {
|
||||
$chart = ($this->premiumAwareness)([$region => 1]);
|
||||
expect($chart['respondents'])->toBe($region === 'desaKelurahan_id' ? 4 : 5);
|
||||
expect(($this->premiumAwareness)([$region => 99])['respondents'])->toBe(0);
|
||||
}
|
||||
expect(($this->premiumAwareness)(['komoditas_id' => 1])['series'])->toBe([1, 1, 0, 0, 0]);
|
||||
expect(($this->premiumAwareness)(['komoditas_id' => 2])['series'])->toBe([0, 0, 1, 0, 0]);
|
||||
expect(($this->premiumAwareness)(['segmen' => 'Penggerak'])['series'])->toBe([1, 0, 0, 0, 0]);
|
||||
expect(($this->premiumAwareness)(['segmen' => 'Pemerhati'])['series'])->toBe([0, 1, 0, 0, 0]);
|
||||
expect(($this->premiumAwareness)(['komoditas_id' => 2, 'segmen' => 'Penggerak'])['respondents'])->toBe(0);
|
||||
});
|
||||
|
||||
test('premium awareness retains empty categories and excludes missing or invalid scores', function () {
|
||||
$emptyChart = ($this->premiumAwareness)();
|
||||
foreach ([1 => 0, 2 => 6, 3 => null, 4 => -1, 5 => null] as $farmer => $score) {
|
||||
DB::table('akses_pasar')->insert(['petani_id' => $farmer, 'skor_kesadaran_premium' => $score]);
|
||||
}
|
||||
$chart = ($this->premiumAwareness)();
|
||||
expect($chart)->toBe($emptyChart);
|
||||
expect($chart['series'])->toBe([0, 0, 0, 0, 0]);
|
||||
expect($chart['respondents'])->toBe(0);
|
||||
expect($chart['labels'])->toHaveCount(5);
|
||||
expect(array_sum($chart['percentages']))->toBe(0);
|
||||
});
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\PraktikBudidaya\FrekuensiPemangkasanUtama;
|
||||
use App\Enums\PraktikBudidaya\PemberianPupuk;
|
||||
use App\Http\Controllers\Web\Dashboard\SummaryController;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
beforeEach(function () {
|
||||
foreach ([
|
||||
'master_provinsi' => null,
|
||||
'master_kabupaten_kota' => 'provinsi_id',
|
||||
'master_kecamatan' => 'kabupaten_kota_id',
|
||||
'master_desa_kelurahan' => 'kecamatan_id',
|
||||
] as $name => $parent) {
|
||||
Schema::create($name, function (Blueprint $table) use ($parent) {
|
||||
$table->id();
|
||||
$table->string('nama');
|
||||
$table->text('path')->nullable();
|
||||
if ($parent) {
|
||||
$table->integer($parent);
|
||||
}
|
||||
$table->softDeletes();
|
||||
});
|
||||
DB::table($name)->insert(['id' => 1, 'nama' => 'Wilayah'] + ($parent ? [$parent => 1] : []));
|
||||
}
|
||||
Schema::create('master_komoditas', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama');
|
||||
});
|
||||
foreach (['petani', 'profiles'] as $name) {
|
||||
Schema::create($name, function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nama');
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
foreach (['map_lahan', 'map_tanaman'] as $name) {
|
||||
Schema::create($name, function (Blueprint $table) use ($name) {
|
||||
$table->id();
|
||||
$table->integer('komoditas_id');
|
||||
$table->integer('umur_tanaman')->nullable();
|
||||
foreach (['serangan_hama', 'penyakit_tanaman', 'jenis_hama', 'jenis_penyakit'] as $column) {
|
||||
$table->text($column)->nullable();
|
||||
}
|
||||
if ($name === 'map_lahan') {
|
||||
$table->integer('petani_id')->nullable();
|
||||
$table->integer('profile_id')->nullable();
|
||||
$table->integer('desa_kelurahan_id');
|
||||
$table->text('path');
|
||||
$table->float('luas_lahan');
|
||||
$table->integer('jumlah_tanaman');
|
||||
} else {
|
||||
$table->integer('lahan_id');
|
||||
}
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
Schema::create('praktik_budidaya', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('petani_id');
|
||||
$table->integer('frekuensi_pemangkasan_utama');
|
||||
$table->integer('pemberian_pupuk');
|
||||
$table->text('kebiasaan_konservasi_tanah');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
DB::table('master_komoditas')->insert(['id' => 1, 'nama' => 'Kakao']);
|
||||
DB::table('petani')->insert(['id' => 1, 'nama' => 'Petani A']);
|
||||
DB::table('profiles')->insert(['id' => 1, 'nama' => 'Pemilik B']);
|
||||
$land = [
|
||||
'komoditas_id' => 1, 'desa_kelurahan_id' => 1, 'luas_lahan' => 1.5,
|
||||
'jumlah_tanaman' => 50, 'umur_tanaman' => 8,
|
||||
'path' => '[[[-8.1,121.1],[-8.2,121.1],[-8.1,121.2]]]',
|
||||
'jenis_hama' => '["Penggerek buah"]',
|
||||
];
|
||||
DB::table('map_lahan')->insert($land + ['id' => 1, 'petani_id' => 1]);
|
||||
DB::table('map_lahan')->insert($land + ['id' => 2, 'profile_id' => 1]);
|
||||
DB::table('map_lahan')->insert($land + ['id' => 3, 'petani_id' => 1, 'deleted_at' => '2026-01-01']);
|
||||
DB::table('map_tanaman')->insert([
|
||||
'lahan_id' => 1, 'komoditas_id' => 1, 'umur_tanaman' => 4,
|
||||
'jenis_penyakit' => '["Busuk buah"]',
|
||||
]);
|
||||
foreach ([
|
||||
['2026-01-01', 1, null],
|
||||
['2026-02-01', 4, null],
|
||||
['2026-03-01', 5, '2026-03-02'],
|
||||
] as [$updatedAt, $value, $deletedAt]) {
|
||||
DB::table('praktik_budidaya')->insert([
|
||||
'petani_id' => 1, 'frekuensi_pemangkasan_utama' => $value, 'pemberian_pupuk' => $value,
|
||||
'kebiasaan_konservasi_tanah' => '["Menumpuk daun kering/sisa tanaman di sekitar pohon"]',
|
||||
'updated_at' => $updatedAt, 'deleted_at' => $deletedAt,
|
||||
]);
|
||||
}
|
||||
$controller = app(SummaryController::class);
|
||||
$method = new ReflectionMethod($controller, 'productionMapLevelData');
|
||||
$this->mapLevels = fn (array $filters = []) => $method->invoke($controller, new Request($filters));
|
||||
});
|
||||
|
||||
test('land map combines plants and OPT with the latest active cultivation practice', function () {
|
||||
$lands = collect(($this->mapLevels)()['lahan'])->keyBy('id');
|
||||
|
||||
expect($lands->keys()->all())->toBe([1, 2]);
|
||||
expect($lands[1])->toMatchArray([
|
||||
'nama_petani' => 'Petani A',
|
||||
'luas' => 1.5,
|
||||
'komoditas' => [['nama' => 'Kakao', 'jumlah' => 1, 'umur' => 4.0]],
|
||||
'opt' => ['Busuk Buah', 'Penggerek Buah'],
|
||||
'pemangkasan' => FrekuensiPemangkasanUtama::DUA_KALI_TAHUN->label(),
|
||||
'pemupukan' => PemberianPupuk::JENIS_DOSIS_SESUAI_WAKTU_CUKUP_TEPAT->label(),
|
||||
'praktik_baik' => ['Menumpuk daun kering/sisa tanaman di sekitar pohon'],
|
||||
]);
|
||||
expect($lands[2])->toMatchArray([
|
||||
'nama_petani' => 'Pemilik B',
|
||||
'komoditas' => [['nama' => 'Kakao', 'jumlah' => 50, 'umur' => 8.0]],
|
||||
'pemangkasan' => 'Belum ada data',
|
||||
'pemupukan' => 'Belum ada data',
|
||||
'praktik_baik' => [],
|
||||
]);
|
||||
});
|
||||
|
||||
test('combined map respects region and commodity filters', function () {
|
||||
expect(($this->mapLevels)(['desaKelurahan_id' => 1, 'komoditas_id' => 1])['lahan'])->toHaveCount(2);
|
||||
expect(($this->mapLevels)(['desaKelurahan_id' => 99])['lahan'])->toBe([]);
|
||||
expect(($this->mapLevels)(['komoditas_id' => 99])['lahan'])->toBe([]);
|
||||
});
|
||||
Reference in New Issue
Block a user