diff --git a/app/Http/Controllers/Web/Produktivitas/AsetKelompokTaniController.php b/app/Http/Controllers/Web/Produktivitas/AsetKelompokTaniController.php index 003a92d..b69b0d4 100644 --- a/app/Http/Controllers/Web/Produktivitas/AsetKelompokTaniController.php +++ b/app/Http/Controllers/Web/Produktivitas/AsetKelompokTaniController.php @@ -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.'); } } diff --git a/app/Http/Controllers/Web/Produktivitas/BantuanKelompokTaniController.php b/app/Http/Controllers/Web/Produktivitas/BantuanKelompokTaniController.php index 929c680..035ecbd 100644 --- a/app/Http/Controllers/Web/Produktivitas/BantuanKelompokTaniController.php +++ b/app/Http/Controllers/Web/Produktivitas/BantuanKelompokTaniController.php @@ -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.'); } } diff --git a/app/Http/Controllers/Web/Produktivitas/PengurusKelompokTaniController.php b/app/Http/Controllers/Web/Produktivitas/PengurusKelompokTaniController.php index 00655e1..e89ad09 100644 --- a/app/Http/Controllers/Web/Produktivitas/PengurusKelompokTaniController.php +++ b/app/Http/Controllers/Web/Produktivitas/PengurusKelompokTaniController.php @@ -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.'); } } diff --git a/app/Http/Controllers/Web/Produktivitas/SurveyKelompokTaniController.php b/app/Http/Controllers/Web/Produktivitas/SurveyKelompokTaniController.php index b6461e1..16ba477 100644 --- a/app/Http/Controllers/Web/Produktivitas/SurveyKelompokTaniController.php +++ b/app/Http/Controllers/Web/Produktivitas/SurveyKelompokTaniController.php @@ -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.'); - } } diff --git a/app/Models/Pertanian/KelompokTani.php b/app/Models/Pertanian/KelompokTani.php index 3d813ab..76490e8 100644 --- a/app/Models/Pertanian/KelompokTani.php +++ b/app/Models/Pertanian/KelompokTani.php @@ -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 = [ diff --git a/public/assets/css/demo.css b/public/assets/css/demo.css index 83f3c69..1d0ceb8 100644 --- a/public/assets/css/demo.css +++ b/public/assets/css/demo.css @@ -185,3 +185,72 @@ 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); +} diff --git a/resources/views/produktivitas/survey-kelompok-tani/_alert.blade.php b/resources/views/produktivitas/survey-kelompok-tani/_alert.blade.php index 8f8a4b4..17be4af 100644 --- a/resources/views/produktivitas/survey-kelompok-tani/_alert.blade.php +++ b/resources/views/produktivitas/survey-kelompok-tani/_alert.blade.php @@ -1,37 +1,51 @@ -{{-- - Partial notif per-card. Dipakai di dalam tiap