Update Role fasilitator, kordinator

This commit is contained in:
2026-11-04 14:24:51 +07:00
parent b5b046ed86
commit 41da3a71ee
48 changed files with 586 additions and 164 deletions
+4 -2
View File
@@ -24,7 +24,7 @@ Baca seluruhnya sebelum membuat, mengubah, atau merefaktor kode.
| Auth web | Session | | Auth web | Session |
| Auth API | Laravel Sanctum (token) | | Auth API | Laravel Sanctum (token) |
| Dokumentasi API | dedoc/scramble | | Dokumentasi API | dedoc/scramble |
| Role | `app/Enums/Role.php` (atau lokasi Role yang sudah ada): `super_admin`, `admin`, `fasilitator`, `petani`, `offtaker` | | Role | `app/Enums/Role.php`: `super_admin`, `admin` (admin program), `koordinator` (per komoditas), `enumerator` (penginput data petani), `fasilitator` (pemateri workshop), `petani`, `offtaker` |
--- ---
@@ -452,7 +452,7 @@ Aturan turunannya:
- Kelompokkan per domain dengan `Route::middleware(...)->group()` dan komentar judul domain. - Kelompokkan per domain dengan `Route::middleware(...)->group()` dan komentar judul domain.
- Nested resource memakai `->shallow()`. Maksimal **dua tingkat** di URL (`/framework/{framework}/transformasi`). Hierarki lebih dalam (Framework → Transformasi → Aktivitas → Target) dipecah menjadi beberapa nested shallow resource berpasangan. - Nested resource memakai `->shallow()`. Maksimal **dua tingkat** di URL (`/framework/{framework}/transformasi`). Hierarki lebih dalam (Framework → Transformasi → Aktivitas → Target) dipecah menjadi beberapa nested shallow resource berpasangan.
- **Satu route hanya didaftarkan satu kali.** Sebelum menambah route, cari dulu apakah sudah ada (`php artisan route:list --name=...`). - **Satu route hanya didaftarkan satu kali.** Sebelum menambah route, cari dulu apakah sudah ada (`php artisan route:list --name=...`).
- Setelah login: `fasilitator` → Dashboard Survei Petani; role lain → Dashboard Karakteristik Petani. Jangan mengubah logika ini tanpa izin. - Setelah login: `enumerator` → Dashboard Survei Petani; `fasilitator` → Profil akun (belum ada menu khusus); role lain → Dashboard Karakteristik Petani. Jangan mengubah logika ini tanpa izin.
--- ---
@@ -544,6 +544,8 @@ Kunci flash: `success`, `error`, `warning`, `info`.
## 9. Otorisasi ## 9. Otorisasi
- Hak akses berdasarkan role pada `Role` yang sudah ada. Ikuti mekanisme pembatasan role yang sudah dipakai (middleware/policy/gate yang ada); jangan membuat sistem permission baru. - Hak akses berdasarkan role pada `Role` yang sudah ada. Ikuti mekanisme pembatasan role yang sudah dipakai (middleware/policy/gate yang ada); jangan membuat sistem permission baru.
- Penginput data petani adalah role `enumerator` (bukan `fasilitator`). `fasilitator` khusus pemateri workshop.
- Semua route `petani/...` dibungkus middleware `akses.petani` (`BatasiAksesPetani`): hanya `super_admin`, `admin`, `koordinator`, `enumerator`; koordinator hanya boleh mengakses petani dengan komoditas utama yang ditugaskan (`koordinator_komoditas`, cek lewat `User::bolehAksesPetani()`). Route tab petani baru wajib diletakkan di dalam grup ini.
- Menu sidebar hanya menampilkan item yang boleh diakses role pengguna, dan route-nya juga dilindungi di sisi server. Menyembunyikan menu saja tidak cukup. - Menu sidebar hanya menampilkan item yang boleh diakses role pengguna, dan route-nya juga dilindungi di sisi server. Menyembunyikan menu saja tidak cukup.
--- ---
+16 -1
View File
@@ -6,7 +6,22 @@ enum Role: string
{ {
case SUPER_ADMIN = 'super_admin'; case SUPER_ADMIN = 'super_admin';
case ADMIN = 'admin'; // admin program case ADMIN = 'admin'; // admin program
case FASILITATOR = 'fasilitator'; case KOORDINATOR = 'koordinator'; // koordinator per komoditas
case ENUMERATOR = 'enumerator'; // penginput data petani di lapangan
case FASILITATOR = 'fasilitator'; // pemateri workshop
case PETANI = 'petani'; case PETANI = 'petani';
case OFFTAKER = 'offtaker'; case OFFTAKER = 'offtaker';
public function label(): string
{
return match ($this) {
self::SUPER_ADMIN => 'Super Admin',
self::ADMIN => 'Admin',
self::KOORDINATOR => 'Koordinator',
self::ENUMERATOR => 'Enumerator',
self::FASILITATOR => 'Fasilitator',
self::PETANI => 'Petani',
self::OFFTAKER => 'Offtaker',
};
}
} }
@@ -20,7 +20,7 @@ class FormBasisDataController extends Controller
*/ */
public function index(Request $request, string $tabelId): JsonResponse public function index(Request $request, string $tabelId): JsonResponse
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -20,7 +20,7 @@ class TabelBasisDataController extends Controller
*/ */
public function index(Request $request): AnonymousResourceCollection public function index(Request $request): AnonymousResourceCollection
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -50,7 +50,7 @@ class TabelBasisDataController extends Controller
*/ */
public function show(Request $request, string $id): JsonResponse public function show(Request $request, string $id): JsonResponse
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -19,7 +19,7 @@ class ValueBasisDataController extends Controller
public function index(Request $request, string $tabelId): JsonResponse public function index(Request $request, string $tabelId): JsonResponse
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -213,7 +213,7 @@ class ValueBasisDataController extends Controller
public function store(Request $request, string $tabelId): JsonResponse public function store(Request $request, string $tabelId): JsonResponse
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -278,7 +278,7 @@ class ValueBasisDataController extends Controller
public function update(Request $request, string $tabelId, string $id): JsonResponse public function update(Request $request, string $tabelId, string $id): JsonResponse
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -354,7 +354,7 @@ class ValueBasisDataController extends Controller
public function destroy(Request $request, string $tabelId, string $id): JsonResponse public function destroy(Request $request, string $tabelId, string $id): JsonResponse
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -20,7 +20,7 @@ class JenisKomoditasController extends Controller
*/ */
public function index(Request $request): AnonymousResourceCollection public function index(Request $request): AnonymousResourceCollection
{ {
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) { if (!Gate::any(['petani', 'enumerator', 'super_admin'])) {
abort(403); abort(403);
} }
@@ -63,7 +63,7 @@ class JenisKomoditasController extends Controller
*/ */
public function store(Request $request): JsonResponse public function store(Request $request): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) { if (!Gate::any(['petani', 'enumerator', 'super_admin'])) {
abort(403); abort(403);
} }
@@ -85,7 +85,7 @@ class JenisKomoditasController extends Controller
*/ */
public function show(string $id): JsonResponse public function show(string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) { if (!Gate::any(['petani', 'enumerator', 'super_admin'])) {
abort(403); abort(403);
} }
@@ -107,7 +107,7 @@ class JenisKomoditasController extends Controller
*/ */
public function update(Request $request, string $id): JsonResponse public function update(Request $request, string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) { if (!Gate::any(['petani', 'enumerator', 'super_admin'])) {
abort(403); abort(403);
} }
@@ -129,7 +129,7 @@ class JenisKomoditasController extends Controller
*/ */
public function destroy(string $id): JsonResponse public function destroy(string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) { if (!Gate::any(['petani', 'enumerator', 'super_admin'])) {
abort(403); abort(403);
} }
@@ -13,7 +13,7 @@ class DistributionController extends Controller
{ {
public function index(): JsonResponse public function index(): JsonResponse
{ {
if (!Gate::any(['super_admin', 'petani','fasilitator', 'offtaker'])) { if (!Gate::any(['super_admin', 'petani','enumerator', 'offtaker'])) {
abort(403); abort(403);
} }
@@ -35,7 +35,7 @@ class DistributionController extends Controller
public function store(Request $request): JsonResponse public function store(Request $request): JsonResponse
{ {
if (!Gate::any(['super_admin', 'petani','fasilitator', 'offtaker'])) { if (!Gate::any(['super_admin', 'petani','enumerator', 'offtaker'])) {
abort(403); abort(403);
} }
@@ -60,7 +60,7 @@ class DistributionController extends Controller
public function update(Request $request, string $id): JsonResponse public function update(Request $request, string $id): JsonResponse
{ {
if (!Gate::any(['super_admin', 'petani','fasilitator', 'offtaker'])) { if (!Gate::any(['super_admin', 'petani','enumerator', 'offtaker'])) {
abort(403); abort(403);
} }
@@ -86,7 +86,7 @@ class DistributionController extends Controller
public function batchUpsert(Request $request): JsonResponse public function batchUpsert(Request $request): JsonResponse
{ {
if (!Gate::any(['super_admin', 'petani','fasilitator', 'offtaker'])) { if (!Gate::any(['super_admin', 'petani','enumerator', 'offtaker'])) {
abort(403); abort(403);
} }
@@ -21,7 +21,7 @@ class LahanController extends Controller
*/ */
public function index(Request $request): AnonymousResourceCollection public function index(Request $request): AnonymousResourceCollection
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
@@ -77,7 +77,7 @@ class LahanController extends Controller
*/ */
public function store(Request $request): JsonResponse public function store(Request $request): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
@@ -91,7 +91,7 @@ class LahanController extends Controller
$request->merge(['profile_id' => (int) $request->profile_id]); $request->merge(['profile_id' => (int) $request->profile_id]);
} }
$profileRule = $user->role === 'fasilitator' ? ['required', 'numeric', 'exists:profiles,id'] : ['nullable']; $profileRule = $user->role === 'enumerator' ? ['required', 'numeric', 'exists:profiles,id'] : ['nullable'];
$validated = $request->validate([ $validated = $request->validate([
'nama' => ['required', 'string', 'max:255'], 'nama' => ['required', 'string', 'max:255'],
@@ -105,11 +105,11 @@ class LahanController extends Controller
'foto.*' => ['image', 'mimes:jpeg,png,jpg', 'max:5120'], // Max 5MB 'foto.*' => ['image', 'mimes:jpeg,png,jpg', 'max:5120'], // Max 5MB
]); ]);
$targetProfileId = $user->role === 'fasilitator' $targetProfileId = $user->role === 'enumerator'
? $validated['profile_id'] ? $validated['profile_id']
: $profile->id; : $profile->id;
if ($user->role === 'fasilitator') { if ($user->role === 'enumerator') {
$targetProfile = Profile::findOrFail($targetProfileId); $targetProfile = Profile::findOrFail($targetProfileId);
if ($targetProfile->desa_kelurahan_id !== $profile->desa_kelurahan_id) { if ($targetProfile->desa_kelurahan_id !== $profile->desa_kelurahan_id) {
@@ -117,7 +117,7 @@ class LahanController extends Controller
} }
} }
$desaKelurahanId = $user->role === 'fasilitator' $desaKelurahanId = $user->role === 'enumerator'
? $profile->desa_kelurahan_id ? $profile->desa_kelurahan_id
: ($validated['desa_kelurahan_id'] ?? $profile->desa_kelurahan_id); : ($validated['desa_kelurahan_id'] ?? $profile->desa_kelurahan_id);
@@ -149,14 +149,14 @@ class LahanController extends Controller
*/ */
public function show(string $id): JsonResponse public function show(string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
$lahan = Lahan::with('desaKelurahan')->findOrFail($id); $lahan = Lahan::with('desaKelurahan')->findOrFail($id);
if ($lahan->profile_id !== Auth::user()->profile->id && Auth::user()->role === 'petani') { if ($lahan->profile_id !== Auth::user()->profile->id && Auth::user()->role === 'petani') {
return response()->json(['message' => 'Unauthorized'], 403); return response()->json(['message' => 'Unauthorized'], 403);
} else if (Auth::user()->role === 'fasilitator' && $lahan->desa_kelurahan_id !== Auth::user()->profile->desa_kelurahan_id) { } else if (Auth::user()->role === 'enumerator' && $lahan->desa_kelurahan_id !== Auth::user()->profile->desa_kelurahan_id) {
return response()->json(['message' => 'Unauthorized'], 403); return response()->json(['message' => 'Unauthorized'], 403);
} }
@@ -168,14 +168,14 @@ class LahanController extends Controller
*/ */
public function update(Request $request, string $id): JsonResponse public function update(Request $request, string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
$lahan = Lahan::findOrFail($id); $lahan = Lahan::findOrFail($id);
if ($lahan->profile_id !== Auth::user()->profile->id && Auth::user()->role === 'petani') { if ($lahan->profile_id !== Auth::user()->profile->id && Auth::user()->role === 'petani') {
return response()->json(['message' => 'Unauthorized'], 403); return response()->json(['message' => 'Unauthorized'], 403);
} else if (Auth::user()->role === 'fasilitator' && $lahan->desa_kelurahan_id !== Auth::user()->profile->desa_kelurahan_id) { } else if (Auth::user()->role === 'enumerator' && $lahan->desa_kelurahan_id !== Auth::user()->profile->desa_kelurahan_id) {
return response()->json(['message' => 'Unauthorized'], 403); return response()->json(['message' => 'Unauthorized'], 403);
} }
@@ -215,14 +215,14 @@ class LahanController extends Controller
*/ */
public function destroy(string $id): JsonResponse public function destroy(string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
$lahan = Lahan::findOrFail($id); $lahan = Lahan::findOrFail($id);
if ($lahan->profile_id !== Auth::user()->profile->id && Auth::user()->role === 'petani') { if ($lahan->profile_id !== Auth::user()->profile->id && Auth::user()->role === 'petani') {
return response()->json(['message' => 'Unauthorized'], 403); return response()->json(['message' => 'Unauthorized'], 403);
} else if (Auth::user()->role === 'fasilitator' && $lahan->desa_kelurahan_id !== Auth::user()->profile->desa_kelurahan_id) { } else if (Auth::user()->role === 'enumerator' && $lahan->desa_kelurahan_id !== Auth::user()->profile->desa_kelurahan_id) {
return response()->json(['message' => 'Unauthorized'], 403); return response()->json(['message' => 'Unauthorized'], 403);
} }
@@ -233,7 +233,7 @@ class LahanController extends Controller
public function batchUpsert(Request $request): JsonResponse public function batchUpsert(Request $request): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
@@ -12,7 +12,7 @@ class OfftakerController extends Controller
{ {
public function index(Request $request): JsonResponse public function index(Request $request): JsonResponse
{ {
if (!Gate::any(['super_admin', 'petani', 'fasilitator', 'admin','offtaker'])) { if (!Gate::any(['super_admin', 'petani', 'enumerator', 'admin','offtaker'])) {
abort(403, 'Unauthorized'); abort(403, 'Unauthorized');
} }
@@ -29,7 +29,7 @@ class OfftakerController extends Controller
public function store(Request $request): JsonResponse public function store(Request $request): JsonResponse
{ {
if (!Gate::any(['super_admin', 'petani', 'fasilitator', 'admin','offtaker'])) { if (!Gate::any(['super_admin', 'petani', 'enumerator', 'admin','offtaker'])) {
abort(403, 'Unauthorized'); abort(403, 'Unauthorized');
} }
@@ -58,7 +58,7 @@ class OfftakerController extends Controller
public function show(string $id): JsonResponse public function show(string $id): JsonResponse
{ {
if (!Gate::any(['super_admin', 'petani', 'fasilitator', 'admin','offtaker'])) { if (!Gate::any(['super_admin', 'petani', 'enumerator', 'admin','offtaker'])) {
abort(403, 'Unauthorized'); abort(403, 'Unauthorized');
} }
$offtaker = Offtaker::findOrFail($id); $offtaker = Offtaker::findOrFail($id);
@@ -67,7 +67,7 @@ class OfftakerController extends Controller
public function update(Request $request, string $id): JsonResponse public function update(Request $request, string $id): JsonResponse
{ {
if (!Gate::any(['super_admin', 'petani', 'fasilitator', 'admin','offtaker'])) { if (!Gate::any(['super_admin', 'petani', 'enumerator', 'admin','offtaker'])) {
abort(403, 'Unauthorized'); abort(403, 'Unauthorized');
} }
$offtaker = Offtaker::findOrFail($id); $offtaker = Offtaker::findOrFail($id);
@@ -87,7 +87,7 @@ class OfftakerController extends Controller
public function destroy(string $id): JsonResponse public function destroy(string $id): JsonResponse
{ {
if (!Gate::any(['super_admin', 'petani', 'fasilitator', 'admin','offtaker'])) { if (!Gate::any(['super_admin', 'petani', 'enumerator', 'admin','offtaker'])) {
abort(403, 'Unauthorized'); abort(403, 'Unauthorized');
} }
Offtaker::findOrFail($id)->delete(); Offtaker::findOrFail($id)->delete();
@@ -100,7 +100,7 @@ class OfftakerController extends Controller
*/ */
public function batchUpsert(Request $request): JsonResponse public function batchUpsert(Request $request): JsonResponse
{ {
if (!Gate::any(['super_admin', 'petani', 'fasilitator', 'admin','offtaker'])) { if (!Gate::any(['super_admin', 'petani', 'enumerator', 'admin','offtaker'])) {
abort(403, 'Unauthorized'); abort(403, 'Unauthorized');
} }
@@ -15,7 +15,7 @@ class PanenController extends Controller
{ {
public function index(Request $request): JsonResponse public function index(Request $request): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
@@ -30,7 +30,7 @@ class PanenController extends Controller
public function store(Request $request): JsonResponse public function store(Request $request): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) { if (!Gate::any(['petani', 'enumerator', 'super_admin'])) {
abort(403); abort(403);
} }
@@ -87,7 +87,7 @@ class PanenController extends Controller
public function show(string $id): JsonResponse public function show(string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
$panen = Panen::findOrFail($id); $panen = Panen::findOrFail($id);
@@ -96,7 +96,7 @@ class PanenController extends Controller
public function update(Request $request, string $id): JsonResponse public function update(Request $request, string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
$panen = Panen::findOrFail($id); $panen = Panen::findOrFail($id);
@@ -114,7 +114,7 @@ class PanenController extends Controller
public function destroy(string $id): JsonResponse public function destroy(string $id): JsonResponse
{ {
Gate::authorize(['petani', 'fasilitator']); Gate::authorize(['petani', 'enumerator']);
Panen::findOrFail($id)->delete(); Panen::findOrFail($id)->delete();
return response()->json(null, 204); return response()->json(null, 204);
} }
@@ -124,7 +124,7 @@ class PanenController extends Controller
*/ */
public function batchUpsert(Request $request): JsonResponse public function batchUpsert(Request $request): JsonResponse
{ {
if (Gate::authorize(['petani', 'fasilitator','super_admin'])) { if (Gate::authorize(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
$profileId = Auth::user()->profile->id; $profileId = Auth::user()->profile->id;
@@ -14,7 +14,7 @@ class PanenController01 extends Controller
{ {
public function index(Request $request): JsonResponse public function index(Request $request): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
@@ -29,7 +29,7 @@ class PanenController01 extends Controller
public function store(Request $request): JsonResponse public function store(Request $request): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
@@ -58,7 +58,7 @@ class PanenController01 extends Controller
public function show(string $id): JsonResponse public function show(string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
$panen = Panen::findOrFail($id); $panen = Panen::findOrFail($id);
@@ -67,7 +67,7 @@ class PanenController01 extends Controller
public function update(Request $request, string $id): JsonResponse public function update(Request $request, string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
$panen = Panen::findOrFail($id); $panen = Panen::findOrFail($id);
@@ -85,7 +85,7 @@ class PanenController01 extends Controller
public function destroy(string $id): JsonResponse public function destroy(string $id): JsonResponse
{ {
Gate::authorize(['petani', 'fasilitator']); Gate::authorize(['petani', 'enumerator']);
Panen::findOrFail($id)->delete(); Panen::findOrFail($id)->delete();
return response()->json(null, 204); return response()->json(null, 204);
} }
@@ -95,7 +95,7 @@ class PanenController01 extends Controller
*/ */
public function batchUpsert(Request $request): JsonResponse public function batchUpsert(Request $request): JsonResponse
{ {
if (Gate::authorize(['petani', 'fasilitator','super_admin'])) { if (Gate::authorize(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
$profileId = Auth::user()->profile->id; $profileId = Auth::user()->profile->id;
@@ -23,7 +23,7 @@ class ProfileController extends Controller
*/ */
public function index(Request $request) public function index(Request $request)
{ {
if(!Gate::any(['fasilitator', 'super_admin'])) { if(!Gate::any(['enumerator', 'super_admin'])) {
abort(403); abort(403);
} }
@@ -34,7 +34,7 @@ class ProfileController extends Controller
$profileId = $user->role === 'petani' ? $profile->id : $requestID; $profileId = $user->role === 'petani' ? $profile->id : $requestID;
$desaKelurahanId = null; $desaKelurahanId = null;
if ($profile->role === 'fasilitator' || $profile->role === 'super_admin') { if ($profile->role === 'enumerator' || $profile->role === 'super_admin') {
$desaKelurahanId = $profile->desa_kelurahan_id; $desaKelurahanId = $profile->desa_kelurahan_id;
} }
@@ -95,7 +95,7 @@ class ProfileController extends Controller
*/ */
public function store(Request $request) public function store(Request $request)
{ {
if(!Gate::any(['fasilitator','super_admin'])){ if(!Gate::any(['enumerator','super_admin'])){
abort(403); abort(403);
} }
@@ -139,7 +139,7 @@ class ProfileController extends Controller
public function show(string $id): JSONResponse public function show(string $id): JSONResponse
{ {
// attempt to access profile, only for allowed roles // attempt to access profile, only for allowed roles
if (!Gate::any(['petani', 'fasilitator', 'offtaker', 'super_admin'])) { if (!Gate::any(['petani', 'enumerator', 'offtaker', 'super_admin'])) {
abort(403); abort(403);
} }
@@ -166,7 +166,7 @@ class ProfileController extends Controller
*/ */
public function update(Request $request, string $id): JSONResponse public function update(Request $request, string $id): JSONResponse
{ {
if (!Gate::any(['petani', 'fasilitator', 'offtaker', 'super_admin'])) { if (!Gate::any(['petani', 'enumerator', 'offtaker', 'super_admin'])) {
abort(403); abort(403);
} }
@@ -177,7 +177,7 @@ class ProfileController extends Controller
$profile = Profile::findOrFail($id); $profile = Profile::findOrFail($id);
if ($user->role === 'fasilitator' && $profile->desa_kelurahan_id != $user->profile->desa_kelurahan_id) { if ($user->role === 'enumerator' && $profile->desa_kelurahan_id != $user->profile->desa_kelurahan_id) {
abort(403, 'Unauthorized'); abort(403, 'Unauthorized');
} }
@@ -239,7 +239,7 @@ class ProfileController extends Controller
public function downloadFile(string $id, string $type): JsonResponse | BinaryFileResponse public function downloadFile(string $id, string $type): JsonResponse | BinaryFileResponse
{ {
if (!Gate::any(['petani', 'fasilitator', 'super_admin'])) { if (!Gate::any(['petani', 'enumerator', 'super_admin'])) {
abort(403); abort(403);
} }
@@ -260,7 +260,7 @@ class ProfileController extends Controller
public function verify(Request $request, string $id): JSONResponse public function verify(Request $request, string $id): JSONResponse
{ {
if (!Gate::any(['fasilitator'])) { if (!Gate::any(['enumerator'])) {
abort(403); abort(403);
} }
@@ -22,7 +22,7 @@ class TanamanController extends Controller
*/ */
public function index(Request $request): AnonymousResourceCollection public function index(Request $request): AnonymousResourceCollection
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
$user = Auth::user(); $user = Auth::user();
@@ -91,7 +91,7 @@ class TanamanController extends Controller
*/ */
public function store(Request $request): JsonResponse public function store(Request $request): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
@@ -135,7 +135,7 @@ class TanamanController extends Controller
*/ */
public function show(string $id): JsonResponse public function show(string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
@@ -160,7 +160,7 @@ class TanamanController extends Controller
*/ */
public function update(Request $request, string $id): JsonResponse public function update(Request $request, string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
$tanaman = Tanaman::findOrFail($id); $tanaman = Tanaman::findOrFail($id);
@@ -195,7 +195,7 @@ class TanamanController extends Controller
*/ */
public function destroy(string $id): JsonResponse public function destroy(string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
$tanaman = Tanaman::findOrFail($id); $tanaman = Tanaman::findOrFail($id);
@@ -22,7 +22,7 @@ class TanamanController extends Controller
*/ */
public function index(Request $request): AnonymousResourceCollection public function index(Request $request): AnonymousResourceCollection
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
$user = Auth::user(); $user = Auth::user();
@@ -91,7 +91,7 @@ class TanamanController extends Controller
*/ */
public function store(Request $request): JsonResponse public function store(Request $request): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
@@ -141,7 +141,7 @@ class TanamanController extends Controller
*/ */
public function show(string $id): JsonResponse public function show(string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
@@ -166,7 +166,7 @@ class TanamanController extends Controller
*/ */
public function update(Request $request, string $id): JsonResponse public function update(Request $request, string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
$tanaman = Tanaman::findOrFail($id); $tanaman = Tanaman::findOrFail($id);
@@ -201,7 +201,7 @@ class TanamanController extends Controller
*/ */
public function destroy(string $id): JsonResponse public function destroy(string $id): JsonResponse
{ {
if (!Gate::any(['petani', 'fasilitator','super_admin'])) { if (!Gate::any(['petani', 'enumerator','super_admin'])) {
abort(403); abort(403);
} }
$tanaman = Tanaman::findOrFail($id); $tanaman = Tanaman::findOrFail($id);
@@ -18,7 +18,7 @@ class PengurusBumdesController extends Controller
{ {
public function index(Request $request) public function index(Request $request)
{ {
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -36,7 +36,7 @@ class PengurusBumdesController extends Controller
}); });
} }
if (auth()->user()->role->value === 'fasilitator') { if (auth()->user()->role->value === 'enumerator') {
$query->where('created_by', auth()->id()); $query->where('created_by', auth()->id());
} }
@@ -64,7 +64,7 @@ class PengurusBumdesController extends Controller
public function create() public function create()
{ {
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -77,7 +77,7 @@ class PengurusBumdesController extends Controller
public function store(Request $request) public function store(Request $request)
{ {
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -100,7 +100,7 @@ class PengurusBumdesController extends Controller
public function edit(string $id) public function edit(string $id)
{ {
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -116,7 +116,7 @@ class PengurusBumdesController extends Controller
public function update(Request $request, string $id) public function update(Request $request, string $id)
{ {
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -133,7 +133,7 @@ class PengurusBumdesController extends Controller
public function destroy(string $id) public function destroy(string $id)
{ {
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -146,7 +146,7 @@ class PengurusBumdesController extends Controller
public function kelembagaanAktor(string $id) public function kelembagaanAktor(string $id)
{ {
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -166,7 +166,7 @@ class PengurusBumdesController extends Controller
public function storeKelembagaanAktor(Request $request, string $id) public function storeKelembagaanAktor(Request $request, string $id)
{ {
if (! Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (! Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -191,7 +191,7 @@ class PengurusBumdesController extends Controller
{ {
$query = PengurusBumdes::query(); $query = PengurusBumdes::query();
if (auth()->user()->role->value === 'fasilitator') { if (auth()->user()->role->value === 'enumerator') {
$query->where('created_by', auth()->id()); $query->where('created_by', auth()->id());
} }
@@ -17,7 +17,7 @@ class DemografiPetaniController extends Controller
{ {
public function index(Request $request) public function index(Request $request)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -28,7 +28,7 @@ class DemografiPetaniController extends Controller
public function karakteristik(Request $request) public function karakteristik(Request $request)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -94,7 +94,7 @@ class IndeksPetaniController extends Controller
public function index(Request $request) public function index(Request $request)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -15,7 +15,7 @@ class InternationalMarketController extends Controller
{ {
public function __invoke() public function __invoke()
{ {
abort_unless(Gate::allows('super_admin') || Gate::allows('admin') || Gate::allows('fasilitator'), 403); abort_unless(Gate::allows('super_admin') || Gate::allows('admin') || Gate::allows('koordinator') || Gate::allows('enumerator'), 403);
// File cache keeps external quotes independent of the application database. // File cache keeps external quotes independent of the application database.
$cache = Cache::store('file'); $cache = Cache::store('file');
@@ -17,7 +17,7 @@ class ProfilPetaniController extends Controller
{ {
public function index() public function index()
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
$title = 'Dashboard Profil Petani'; $title = 'Dashboard Profil Petani';
@@ -37,7 +37,7 @@ class SummaryController extends Controller
public function produktivitas(Request $request) public function produktivitas(Request $request)
{ {
if (!Gate::allows('super_admin') && !Gate::allows('admin') && !Gate::allows('fasilitator')) { if (!Gate::allows('super_admin') && !Gate::allows('admin') && !Gate::allows('koordinator') && !Gate::allows('enumerator')) {
abort(403); abort(403);
} }
@@ -59,7 +59,7 @@ class SummaryController extends Controller
public function mapInformation(Request $request) public function mapInformation(Request $request)
{ {
if (!Gate::allows('super_admin') && !Gate::allows('admin') && !Gate::allows('fasilitator')) { if (!Gate::allows('super_admin') && !Gate::allows('admin') && !Gate::allows('koordinator') && !Gate::allows('enumerator')) {
abort(403); abort(403);
} }
@@ -101,7 +101,7 @@ class SummaryController extends Controller
public function pasar(Request $request) public function pasar(Request $request)
{ {
if (!Gate::allows('super_admin') && !Gate::allows('admin') && !Gate::allows('fasilitator')) { if (!Gate::allows('super_admin') && !Gate::allows('admin') && !Gate::allows('koordinator') && !Gate::allows('enumerator')) {
abort(403); abort(403);
} }
@@ -31,7 +31,7 @@ class SurveyPetaniController extends Controller
// Progres per enumerator // Progres per enumerator
$enumerators = User::query() $enumerators = User::query()
->where('role', 'fasilitator') ->where('role', 'enumerator')
->withCount([ ->withCount([
'petaniDibuat as done', 'petaniDibuat as done',
]) ])
@@ -24,7 +24,7 @@ class PetaniCPMController extends Controller
{ {
public function index(Request $request): View public function index(Request $request): View
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -60,7 +60,7 @@ class PetaniCPMController extends Controller
public function create(Request $request): View public function create(Request $request): View
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -94,7 +94,7 @@ class PetaniCPMController extends Controller
*/ */
public function store(Request $request): RedirectResponse public function store(Request $request): RedirectResponse
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -147,7 +147,7 @@ class PetaniCPMController extends Controller
*/ */
public function show(string $id): View public function show(string $id): View
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -161,7 +161,7 @@ class PetaniCPMController extends Controller
*/ */
public function edit(Request $request, string $id): View public function edit(Request $request, string $id): View
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -198,7 +198,7 @@ class PetaniCPMController extends Controller
*/ */
public function update(Request $request, string $id) public function update(Request $request, string $id)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -251,7 +251,7 @@ class PetaniCPMController extends Controller
*/ */
public function destroy(string $id) public function destroy(string $id)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -34,7 +34,7 @@ class ValueBasisDataController extends Controller
public function index(Request $request, string $tabelId): View public function index(Request $request, string $tabelId): View
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -0,0 +1,70 @@
<?php
namespace App\Http\Controllers\Web\Konfigurasi;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
/**
* Mengatur komoditas yang ditangani seorang koordinator.
*/
class KoordinatorKomoditasController extends Controller
{
public function update(Request $request, User $user)
{
abort_unless(Gate::any(['super_admin', 'admin']), 403);
if (! $user->isKoordinator()) {
return response()->json([
'success' => false,
'message' => 'Komoditas hanya bisa diatur untuk user dengan role Koordinator.',
], 422);
}
$validated = $request->validate([
'komoditas_ids' => ['required', 'array', 'min:1'],
'komoditas_ids.*' => ['integer', 'exists:master_komoditas,id'],
], [
'komoditas_ids.required' => 'Pilih minimal satu komoditas.',
'komoditas_ids.min' => 'Pilih minimal satu komoditas.',
'komoditas_ids.*.exists' => 'Komoditas yang dipilih tidak valid.',
]);
self::simpanPenugasan($user, $validated['komoditas_ids']);
return response()->json([
'success' => true,
'message' => 'Komoditas koordinator berhasil diperbarui.',
'data' => $user->komoditasKoordinatorIds(),
]);
}
/**
* Menyamakan penugasan koordinator dengan daftar komoditas yang dipilih.
* Penugasan yang dilepas di-soft delete, penugasan baru dibuat.
*/
public static function simpanPenugasan(User $user, array $komoditasIds): void
{
$komoditasIds = array_values(array_unique(array_map('intval', $komoditasIds)));
DB::transaction(function () use ($user, $komoditasIds) {
$user->koordinatorKomoditas()
->whereNotIn('komoditas_id', $komoditasIds)
->get()
->each->delete();
$sudahAda = $user->komoditasKoordinatorIds();
foreach (array_diff($komoditasIds, $sudahAda) as $komoditasId) {
$user->koordinatorKomoditas()->create([
'komoditas_id' => $komoditasId,
'created_by' => auth()->id(),
'updated_by' => auth()->id(),
]);
}
});
}
}
@@ -8,16 +8,29 @@ use App\Models\User;
use App\Models\Profile; use App\Models\Profile;
use App\Enums\Role; use App\Enums\Role;
use App\Models\Wilayah\Provinsi; use App\Models\Wilayah\Provinsi;
use App\Models\Pertanian\Komoditas;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class UserController extends Controller class UserController extends Controller
{ {
// Manajemen pengguna hanya untuk super admin & admin (sama dengan menu "Daftar Pengguna").
// Berlaku untuk semua method di controller ini.
public function __construct()
{
$this->middleware(function (Request $request, \Closure $next) {
abort_unless($request->user() && Gate::forUser($request->user())->any(['super_admin', 'admin']), 403);
return $next($request);
});
}
public function index(Request $request) public function index(Request $request)
{ {
$query = User::orderBy('created_at', 'desc'); $query = User::with('koordinatorKomoditas.komoditas:id,nama')->orderBy('created_at', 'desc');
$tab = $request->get('tab', 'active'); $tab = $request->get('tab', 'active');
if ($tab === 'pending') { if ($tab === 'pending') {
@@ -45,8 +58,15 @@ class UserController extends Controller
$pendingUsers = User::whereNull('email_verified_at')->count(); $pendingUsers = User::whereNull('email_verified_at')->count();
$roles = Role::cases(); $roles = Role::cases();
$komoditasOptions = $this->komoditasOptions();
return view('konfigurasi.user.index', compact('users', 'totalUsers', 'activeUsers', 'pendingUsers', 'roles', 'tab')); return view('konfigurasi.user.index', compact('users', 'totalUsers', 'activeUsers', 'pendingUsers', 'roles', 'tab', 'komoditasOptions'));
}
// Komoditas utama yang bisa ditugaskan ke koordinator
private function komoditasOptions()
{
return Komoditas::select('id', 'nama')->where('is_utama', true)->orderBy('nama')->get();
} }
public function verify(Request $request, $id) public function verify(Request $request, $id)
@@ -92,8 +112,9 @@ class UserController extends Controller
{ {
$roles = Role::cases(); $roles = Role::cases();
$provinsi = Provinsi::orderBy('nama', 'asc')->get(); $provinsi = Provinsi::orderBy('nama', 'asc')->get();
$komoditasOptions = $this->komoditasOptions();
return view('konfigurasi.user.add', compact('roles', 'provinsi')); return view('konfigurasi.user.add', compact('roles', 'provinsi', 'komoditasOptions'));
} }
public function store(Request $request) public function store(Request $request)
@@ -104,6 +125,8 @@ class UserController extends Controller
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'],
'password' => ['required', 'string', 'min:8', 'confirmed'], 'password' => ['required', 'string', 'min:8', 'confirmed'],
'role' => ['required', 'string'], 'role' => ['required', 'string'],
'komoditas_ids' => ['nullable', 'array', 'required_if:role,' . Role::KOORDINATOR->value],
'komoditas_ids.*' => ['integer', 'exists:master_komoditas,id'],
'telepon' => ['required', 'max:13'], 'telepon' => ['required', 'max:13'],
'desa_kelurahan_id' => ['nullable', 'exists:desa_kelurahan,id'], 'desa_kelurahan_id' => ['nullable', 'exists:desa_kelurahan,id'],
'ktp' => ['nullable', 'min:16', 'max:16'], 'ktp' => ['nullable', 'min:16', 'max:16'],
@@ -116,6 +139,7 @@ class UserController extends Controller
'password.confirmed' => 'Konfirmasi password tidak cocok!', 'password.confirmed' => 'Konfirmasi password tidak cocok!',
'password.min' => 'Password minimal 8 karakter!', 'password.min' => 'Password minimal 8 karakter!',
'email.unique' => 'Email sudah terdaftar.', 'email.unique' => 'Email sudah terdaftar.',
'komoditas_ids.required_if' => 'Pilih minimal satu komoditas untuk koordinator.',
'required' => 'Kolom :attribute wajib diisi.' 'required' => 'Kolom :attribute wajib diisi.'
]); ]);
@@ -163,6 +187,10 @@ class UserController extends Controller
'created_by' => auth()->id(), 'created_by' => auth()->id(),
'updated_by' => auth()->id(), 'updated_by' => auth()->id(),
]); ]);
if ($user->isKoordinator()) {
KoordinatorKomoditasController::simpanPenugasan($user, $request->input('komoditas_ids', []));
}
}); });
if ($request->expectsJson()) { if ($request->expectsJson()) {
@@ -27,7 +27,7 @@ class AsetKelompokTaniController extends Controller
private function authorizeAccess(): void private function authorizeAccess(): void
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
} }
@@ -16,7 +16,7 @@ class BantuanKelompokTaniController extends Controller
{ {
private function authorizeAccess(): void private function authorizeAccess(): void
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
} }
@@ -17,7 +17,7 @@ class PendidikanPelatihanController extends Controller
{ {
private function authorizeAccess(): void private function authorizeAccess(): void
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
} }
@@ -14,7 +14,7 @@ class PengurusKelompokTaniController extends Controller
{ {
private function authorizeAccess(): void private function authorizeAccess(): void
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
} }
@@ -32,13 +32,13 @@ class PetaniController extends Controller
*/ */
public function index(Request $request) public function index(Request $request)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
$provinsi = Provinsi::select('id', 'kode', 'nama')->orderBy('kode', 'asc')->get(); $provinsi = Provinsi::select('id', 'kode', 'nama')->orderBy('kode', 'asc')->get();
$userRole = auth()->user()->role->value; $userRole = auth()->user()->role->value;
$showFasilitatorColumn = in_array($userRole, ['super_admin', 'admin'], true); $showFasilitatorColumn = in_array($userRole, ['super_admin', 'admin', 'koordinator'], true);
$defaultKabupaten = KabupatenKota::select('id', 'nama') $defaultKabupaten = KabupatenKota::select('id', 'nama')
->whereRaw('lower(nama) like ?', ['%ngada%']) ->whereRaw('lower(nama) like ?', ['%ngada%'])
->orderBy('nama') ->orderBy('nama')
@@ -102,10 +102,19 @@ class PetaniController extends Controller
$query->where('desa_kelurahan_id', $request->desa_kelurahan_id); $query->where('desa_kelurahan_id', $request->desa_kelurahan_id);
} }
if ($userRole === 'fasilitator') { if ($userRole === 'enumerator') {
$query->where('created_by', auth()->user()->id); $query->where('created_by', auth()->user()->id);
} }
// Koordinator hanya melihat petani dengan komoditas yang ditugaskan
$komoditasKoordinatorIds = $userRole === 'koordinator'
? auth()->user()->komoditasKoordinatorIds()
: null;
if ($komoditasKoordinatorIds !== null) {
$query->whereIn('komoditas_id', $komoditasKoordinatorIds);
}
$totalPetani = (clone $query)->count(); $totalPetani = (clone $query)->count();
$totalPetaniLakiLaki = (clone $query)->where('jenis_kelamin', 1)->count(); $totalPetaniLakiLaki = (clone $query)->where('jenis_kelamin', 1)->count();
$totalPetaniPerempuan = (clone $query)->where('jenis_kelamin', 0)->count(); $totalPetaniPerempuan = (clone $query)->where('jenis_kelamin', 0)->count();
@@ -165,10 +174,15 @@ class PetaniController extends Controller
->orWhereRaw('lower(ktp) like ?', ["%{$search}%"]); ->orWhereRaw('lower(ktp) like ?', ["%{$search}%"]);
}); });
}) })
->when($userRole === 'fasilitator', function ($query) { ->when($userRole === 'enumerator', function ($query) {
$query->whereHas('petani', function ($petani) { $query->whereHas('petani', function ($petani) {
$petani->where('created_by', auth()->id()); $petani->where('created_by', auth()->id());
}); });
})
->when($komoditasKoordinatorIds !== null, function ($query) use ($komoditasKoordinatorIds) {
$query->whereHas('petani', function ($petani) use ($komoditasKoordinatorIds) {
$petani->whereIn('komoditas_id', $komoditasKoordinatorIds);
});
}); });
$totalLahan = (clone $lahanSummaryQuery)->count(); $totalLahan = (clone $lahanSummaryQuery)->count();
@@ -216,7 +230,7 @@ class PetaniController extends Controller
public function create() public function create()
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -288,7 +302,7 @@ class PetaniController extends Controller
public function store(Request $request) public function store(Request $request)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -320,7 +334,7 @@ class PetaniController extends Controller
public function show(string $id) public function show(string $id)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -332,7 +346,7 @@ class PetaniController extends Controller
public function edit(string $id, Request $request) public function edit(string $id, Request $request)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -353,7 +367,7 @@ class PetaniController extends Controller
public function update(Request $request, string $id) public function update(Request $request, string $id)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -395,14 +409,14 @@ class PetaniController extends Controller
public function destroy(string $id) public function destroy(string $id)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
return response()->json(['message' => 'Anda tidak memiliki akses.'], 403); return response()->json(['message' => 'Anda tidak memiliki akses.'], 403);
} }
try { try {
$petani = Petani::findOrFail($id); $petani = Petani::findOrFail($id);
if (auth()->user()->role->value === 'fasilitator' && $petani->created_by !== auth()->id()) { if (auth()->user()->role->value === 'enumerator' && $petani->created_by !== auth()->id()) {
return response()->json(['message' => 'Anda tidak memiliki akses.'], 403); return response()->json(['message' => 'Anda tidak memiliki akses.'], 403);
} }
@@ -429,7 +443,7 @@ class PetaniController extends Controller
public function showImage(string $id, string $field) public function showImage(string $id, string $field)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -32,7 +32,7 @@ class PetaniLahanController extends Controller
{ {
public function index($petaniId, Request $request) public function index($petaniId, Request $request)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -52,7 +52,7 @@ class PetaniLahanController extends Controller
public function create($petaniId) public function create($petaniId)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -194,7 +194,7 @@ class PetaniLahanController extends Controller
public function store($petaniId, Request $request) public function store($petaniId, Request $request)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -292,7 +292,7 @@ class PetaniLahanController extends Controller
public function show(string $petaniId, string $id) public function show(string $petaniId, string $id)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -310,7 +310,7 @@ class PetaniLahanController extends Controller
public function edit(string $petaniId, string $id) public function edit(string $petaniId, string $id)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -335,7 +335,7 @@ class PetaniLahanController extends Controller
public function update(Request $request, string $petaniId, string $id) public function update(Request $request, string $petaniId, string $id)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -462,7 +462,7 @@ class PetaniLahanController extends Controller
public function destroy(string $petaniId, string $id) public function destroy(string $petaniId, string $id)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -481,7 +481,7 @@ class PetaniLahanController extends Controller
public function summary(string $petaniId) public function summary(string $petaniId)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -42,7 +42,7 @@ class PetaniLahanTanamanController extends Controller
*/ */
public function show(string $petaniId, string $lahanId, string $id) public function show(string $petaniId, string $lahanId, string $id)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -63,7 +63,7 @@ class PetaniLahanTanamanController extends Controller
public function update(Request $request, string $petaniId, string $lahanId, string $id) public function update(Request $request, string $petaniId, string $lahanId, string $id)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -103,7 +103,7 @@ class PetaniLahanTanamanController extends Controller
*/ */
public function destroy(string $petaniId, string $lahanId, string $id) public function destroy(string $petaniId, string $lahanId, string $id)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -42,7 +42,7 @@ class PraktikBudidayaController extends Controller
{ {
public function index($petaniId, Request $request) public function index($petaniId, Request $request)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -139,7 +139,7 @@ class PraktikBudidayaController extends Controller
public function store($petaniId, Request $request) public function store($petaniId, Request $request)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -247,7 +247,7 @@ class PraktikBudidayaController extends Controller
public function edit($petaniId, $praktikBudidayaId) public function edit($petaniId, $praktikBudidayaId)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -340,7 +340,7 @@ class PraktikBudidayaController extends Controller
public function update($petaniId, $praktikBudidayaId, Request $request) public function update($petaniId, $praktikBudidayaId, Request $request)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -16,7 +16,7 @@ class SkorPetaniController extends Controller
*/ */
public function index(string $petaniId) public function index(string $petaniId)
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'koordinator', 'enumerator'])) {
abort(403); abort(403);
} }
@@ -19,7 +19,7 @@ class SurveyKelompokTaniController extends Controller
{ {
private function authorizeAccess(): void private function authorizeAccess(): void
{ {
if (!Gate::any(['super_admin', 'admin', 'fasilitator'])) { if (!Gate::any(['super_admin', 'admin', 'enumerator'])) {
abort(403); abort(403);
} }
} }
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace App\Http\Middleware;
use App\Enums\Role;
use App\Models\Pertanian\Petani;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Dipasang di semua route modul data petani (petani/{petani}/...).
* - Hanya role pengelola data petani yang boleh masuk.
* - Koordinator hanya boleh mengakses petani dengan komoditas utama yang ditugaskan.
*/
class BatasiAksesPetani
{
private const ROLE_DIIZINKAN = [
Role::SUPER_ADMIN,
Role::ADMIN,
Role::KOORDINATOR,
Role::ENUMERATOR,
];
public function handle(Request $request, Closure $next): Response
{
$user = $request->user();
abort_unless($user && in_array($user->role, self::ROLE_DIIZINKAN, true), 403);
$petani = $request->route('petani');
if ($petani !== null && $user->isKoordinator()) {
$petani = $petani instanceof Petani ? $petani : Petani::find($petani);
abort_unless(
$petani && $user->bolehAksesPetani($petani),
403,
'Petani ini bukan bagian dari komoditas yang Anda koordinasikan.'
);
}
return $next($request);
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Models\Concerns;
// Mengisi kolom deleted_by otomatis saat data di-soft delete (AISTANDAR 5.3).
trait HasDeletedBy
{
protected static function bootHasDeletedBy(): void
{
static::deleting(function ($model) {
if (! $model->isForceDeleting()) {
$model->deleted_by = auth()->id();
$model->saveQuietly();
}
});
}
}
@@ -0,0 +1,34 @@
<?php
namespace App\Models\Konfigurasi;
use App\Models\Concerns\HasDeletedBy;
use App\Models\Pertanian\Komoditas;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class KoordinatorKomoditas extends Model
{
use SoftDeletes, HasDeletedBy;
protected $table = 'koordinator_komoditas';
protected $fillable = [
'user_id',
'komoditas_id',
'created_by',
'updated_by',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
public function komoditas(): BelongsTo
{
return $this->belongsTo(Komoditas::class, 'komoditas_id');
}
}
+39
View File
@@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Laravel\Sanctum\HasApiTokens; use Laravel\Sanctum\HasApiTokens;
use App\Models\Konfigurasi\KoordinatorKomoditas;
use App\Models\Pertanian\Petani; use App\Models\Pertanian\Petani;
class User extends Authenticatable class User extends Authenticatable
@@ -52,4 +53,42 @@ class User extends Authenticatable
{ {
return $this->hasMany(Petani::class, 'created_by'); return $this->hasMany(Petani::class, 'created_by');
} }
protected static function booted(): void
{
static::deleting(function (User $user) {
if ($user->isForceDeleting()) {
return;
}
$user->koordinatorKomoditas()->get()->each->delete();
});
}
public function koordinatorKomoditas(): HasMany
{
return $this->hasMany(KoordinatorKomoditas::class, 'user_id');
}
public function isKoordinator(): bool
{
return $this->role === Role::KOORDINATOR;
}
// Daftar id komoditas yang ditugaskan ke koordinator ini.
public function komoditasKoordinatorIds(): array
{
return $this->koordinatorKomoditas()->pluck('komoditas_id')->all();
}
// Koordinator hanya boleh mengakses petani dengan komoditas utama yang ditugaskan.
// Role lain tidak dibatasi di sini (aturannya tetap di masing-masing controller).
public function bolehAksesPetani(Petani $petani): bool
{
if (! $this->isKoordinator()) {
return true;
}
return in_array($petani->komoditas_id, $this->komoditasKoordinatorIds());
}
} }
+8
View File
@@ -54,6 +54,14 @@ class AppServiceProvider extends ServiceProvider
return $user->role === Role::ADMIN; return $user->role === Role::ADMIN;
}); });
Gate::define('koordinator', function (User $user) {
return $user->role === Role::KOORDINATOR;
});
Gate::define('enumerator', function (User $user) {
return $user->role === Role::ENUMERATOR;
});
Gate::define('fasilitator', function (User $user) { Gate::define('fasilitator', function (User $user) {
return $user->role === Role::FASILITATOR; return $user->role === Role::FASILITATOR;
}); });
+4
View File
@@ -15,6 +15,10 @@ return Application::configure(basePath: dirname(__DIR__))
) )
->withMiddleware(function (Middleware $middleware): void { ->withMiddleware(function (Middleware $middleware): void {
$middleware->append(\App\Http\Middleware\ResolveProgram::class); $middleware->append(\App\Http\Middleware\ResolveProgram::class);
$middleware->alias([
'akses.petani' => \App\Http\Middleware\BatasiAksesPetani::class,
]);
}) })
->withExceptions(function (Exceptions $exceptions): void { ->withExceptions(function (Exceptions $exceptions): void {
// //
@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Penugasan user koordinator ke komoditas yang dia tangani.
*/
public function up(): void
{
Schema::create('koordinator_komoditas', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users');
$table->foreignId('komoditas_id')->constrained('master_komoditas');
$table->foreignId('created_by')->nullable()->constrained('users');
$table->foreignId('updated_by')->nullable()->constrained('users');
$table->timestamps();
$table->softDeletes();
$table->foreignId('deleted_by')->nullable()->constrained('users');
// Bukan unique index (sesuai AISTANDAR 5.2), cukup index untuk pencarian
$table->index(['user_id', 'komoditas_id']);
});
}
public function down(): void
{
Schema::dropIfExists('koordinator_komoditas');
}
};
+20 -1
View File
@@ -47,7 +47,7 @@
<option value="">Pilih Role</option> <option value="">Pilih Role</option>
@foreach($roles as $roleEnum) @foreach($roles as $roleEnum)
<option value="{{ $roleEnum->value }}" {{ old('role', \App\Enums\Role::PETANI->value) === $roleEnum->value ? 'selected' : '' }}> <option value="{{ $roleEnum->value }}" {{ old('role', \App\Enums\Role::PETANI->value) === $roleEnum->value ? 'selected' : '' }}>
{{ ucfirst(str_replace('_', ' ', $roleEnum->value)) }} {{ $roleEnum->label() }}
</option> </option>
@endforeach @endforeach
</select> </select>
@@ -55,6 +55,19 @@
</div> </div>
</div> </div>
{{-- Hanya untuk role Koordinator --}}
<div class="row {{ old('role') === \App\Enums\Role::KOORDINATOR->value ? '' : 'd-none' }}" id="koordinator-komoditas-wrapper">
<div class="col-md-12 mb-4 form-control-validation">
<label class="form-label" for="add-koordinator-komoditas">Komoditas yang Dikoordinasikan<span class="text-danger">*</span></label>
<select id="add-koordinator-komoditas" name="komoditas_ids[]" class="select2 form-select @error('komoditas_ids') is-invalid @enderror" multiple data-placeholder="Pilih Komoditas" style="width: 100%">
@foreach($komoditasOptions as $komoditas)
<option value="{{ $komoditas->id }}" @selected(in_array($komoditas->id, old('komoditas_ids', [])))>{{ $komoditas->nama }}</option>
@endforeach
</select>
@error('komoditas_ids')<div class="invalid-feedback d-block">{{ $message }}</div>@enderror
</div>
</div>
<div class="row"> <div class="row">
<div class="col-md-6 mb-4 form-control-validation"> <div class="col-md-6 mb-4 form-control-validation">
<label class="form-label" for="add-profile-telepon">No. Telepon<span class="text-danger">*</span></label> <label class="form-label" for="add-profile-telepon">No. Telepon<span class="text-danger">*</span></label>
@@ -183,6 +196,12 @@
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2(); $('.select2').select2();
// Pilihan komoditas hanya tampil untuk role Koordinator
$('#add-profile-role').on('change', function () {
const isKoordinator = $(this).val() === @json(\App\Enums\Role::KOORDINATOR->value);
$('#koordinator-komoditas-wrapper').toggleClass('d-none', !isKoordinator);
});
$(document).on('click', '.password-toggle', function () { $(document).on('click', '.password-toggle', function () {
const input = this.closest('.input-group').querySelector('input'); const input = this.closest('.input-group').querySelector('input');
const icon = this.querySelector('i'); const icon = this.querySelector('i');
@@ -111,7 +111,7 @@
<option value="">-- All Roles --</option> <option value="">-- All Roles --</option>
@foreach($roles as $roleEnum) @foreach($roles as $roleEnum)
<option value="{{ $roleEnum->value }}" {{ request('role') == $roleEnum->value ? 'selected' : '' }}> <option value="{{ $roleEnum->value }}" {{ request('role') == $roleEnum->value ? 'selected' : '' }}>
{{ ucfirst(str_replace('_', ' ', $roleEnum->value)) }} {{ $roleEnum->label() }}
</option> </option>
@endforeach @endforeach
</select> </select>
@@ -164,14 +164,24 @@
$roleColors = [ $roleColors = [
'super_admin' => 'bg-label-danger', 'super_admin' => 'bg-label-danger',
'admin' => 'bg-label-warning', 'admin' => 'bg-label-warning',
'fasilitator' => 'bg-label-info', 'koordinator' => 'bg-label-primary',
'enumerator' => 'bg-label-info',
'fasilitator' => 'bg-label-dark',
'petani' => 'bg-label-success', 'petani' => 'bg-label-success',
]; ];
$color = $roleColors[$roleKey] ?? 'bg-label-secondary'; $color = $roleColors[$roleKey] ?? 'bg-label-secondary';
$roleLabel = ($user->role instanceof \App\Enums\Role)
? $user->role->label()
: ucfirst(str_replace('_', ' ', (string) $roleKey));
@endphp @endphp
<span class="badge {{ $color }}"> <span class="badge {{ $color }}">
{{ ucfirst(str_replace('_', ' ', $roleKey)) }} {{ $roleLabel }}
</span> </span>
@if($roleKey === 'koordinator')
<small class="d-block text-muted mt-1">
{{ $user->koordinatorKomoditas->pluck('komoditas.nama')->filter()->join(', ') ?: 'Belum ada komoditas' }}
</small>
@endif
</td> </td>
<td> <td>
@if($user->email_verified_at) @if($user->email_verified_at)
@@ -192,6 +202,16 @@
<i class="ti tabler-trash ti-md"></i> <i class="ti tabler-trash ti-md"></i>
</a> </a>
@if($roleKey === 'koordinator')
<a href="javascript:;" class="btn btn-icon btn-text-secondary rounded-pill btn-komoditas-koordinator"
title="Atur Komoditas"
data-url="{{ route('users.komoditas.update', $user->id) }}"
data-name="{{ $user->name }}"
data-selected='@json($user->koordinatorKomoditas->pluck('komoditas_id')->values())'>
<i class="ti tabler-plant ti-md"></i>
</a>
@endif
@if(!$user->email_verified_at) @if(!$user->email_verified_at)
<button type="button" <button type="button"
class="btn btn-primary px-9 d-flex align-items-center btn-verify shadow-sm waves-effect waves-light" class="btn btn-primary px-9 d-flex align-items-center btn-verify shadow-sm waves-effect waves-light"
@@ -241,7 +261,9 @@
<option value="" selected disabled>-- Pilih Role Akses --</option> <option value="" selected disabled>-- Pilih Role Akses --</option>
<option value="super_admin">🟣 Super Admin</option> <option value="super_admin">🟣 Super Admin</option>
<option value="admin">🟣 Admin</option> <option value="admin">🟣 Admin</option>
<option value="fasilitator">🟣 Fasilitator</option> <option value="koordinator">🟣 Koordinator</option>
<option value="enumerator">🟣 Enumerator</option>
<option value="fasilitator">🟣 Fasilitator (Pemateri Workshop)</option>
<option value="offtaker">🟣 Offtaker</option> <option value="offtaker">🟣 Offtaker</option>
</select> </select>
</div> </div>
@@ -395,4 +417,67 @@
}); });
}); });
</script> </script>
<script>
// Atur komoditas yang ditangani koordinator
$(function () {
const komoditasOptions = @json($komoditasOptions);
$(document).on('click', '.btn-komoditas-koordinator', function () {
const url = $(this).data('url');
const nama = $(this).data('name');
const selected = ($(this).data('selected') || []).map(Number);
const checkboxes = komoditasOptions.map(function (k) {
const checked = selected.includes(k.id) ? 'checked' : '';
return `<div class="form-check text-start mb-2">
<input class="form-check-input komoditas-koordinator-check" type="checkbox" value="${k.id}" id="kk-${k.id}" ${checked}>
<label class="form-check-label" for="kk-${k.id}">${$('<div>').text(k.nama).html()}</label>
</div>`;
}).join('');
Swal.fire({
title: 'Komoditas Koordinator',
html: `<p class="text-muted mb-4">Pilih komoditas yang dikoordinasikan oleh <strong>${$('<div>').text(nama).html()}</strong>.</p>
<div class="px-4">${checkboxes || '<p class="text-muted">Belum ada komoditas utama.</p>'}</div>`,
showCancelButton: true,
confirmButtonText: '<i class="ti tabler-check me-1"></i> Simpan',
cancelButtonText: 'Batal',
customClass: {
popup: 'rounded-4',
confirmButton: 'btn btn-primary me-3',
cancelButton: 'btn btn-label-secondary'
},
buttonsStyling: false,
preConfirm: function () {
const ids = Array.from(Swal.getPopup().querySelectorAll('.komoditas-koordinator-check:checked')).map(el => el.value);
if (ids.length === 0) {
Swal.showValidationMessage('Pilih minimal satu komoditas.');
return false;
}
return ids;
}
}).then(function (result) {
if (!result.isConfirmed) {
return;
}
$.ajax({
url: url,
type: 'PUT',
headers: { 'X-CSRF-TOKEN': csrfToken, 'Accept': 'application/json' },
data: { komoditas_ids: result.value },
success: function (res) {
Swal.fire({ icon: 'success', title: 'Berhasil!', text: res.message, showConfirmButton: false, timer: 1500 })
.then(() => location.reload());
},
error: function (xhr) {
const res = xhr.responseJSON || {};
const errors = res.errors ? Object.values(res.errors).flat().join('<br>') : null;
Swal.fire({ icon: 'error', title: 'Gagal!', html: errors || res.message || 'Gagal menyimpan komoditas.' });
}
});
});
});
});
</script>
@endpush @endpush
+3 -2
View File
@@ -26,7 +26,8 @@
$appName = env('APP_NAME'); $appName = env('APP_NAME');
$isAdmin = in_array($userRole, ['super_admin', 'admin'], true); $isAdmin = in_array($userRole, ['super_admin', 'admin'], true);
$isSuperAdmin = $userRole === 'super_admin'; $isSuperAdmin = $userRole === 'super_admin';
$canViewDashboard = $isAdmin || $userRole === 'fasilitator'; $isKoordinator = $userRole === 'koordinator';
$canViewDashboard = $isAdmin || $isKoordinator || $userRole === 'enumerator';
@endphp @endphp
@if($canViewDashboard) @if($canViewDashboard)
@@ -36,7 +37,7 @@
<div data-i18n="Dashboard">Dashboard</div> <div data-i18n="Dashboard">Dashboard</div>
</a> </a>
@if($isAdmin && $appName === 'AgroMonev') @if(($isAdmin || $isKoordinator) && $appName === 'AgroMonev')
<ul class="menu-sub"> <ul class="menu-sub">
<li class="menu-item {{ request()->routeIs(['dashboard.karakteristik-petani', 'dashboard.demografi-petani', 'dashboard.indeks-petani']) ? 'active' : '' }}"> <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"> <a href="{{ route('dashboard.karakteristik-petani') }}" class="menu-link">
@@ -315,7 +315,7 @@
<th>Komoditas Utama</th> <th>Komoditas Utama</th>
<th>Luas Lahan</th> <th>Luas Lahan</th>
@if($showFasilitatorColumn) @if($showFasilitatorColumn)
<th>Fasilitator</th> <th>Enumerator</th>
@endif @endif
<th>Segmentasi</th> <th>Segmentasi</th>
<th>Aksi</th> <th>Aksi</th>
@@ -105,7 +105,7 @@
<p class="text-body-secondary small mb-0">Beberapa data belum tersedia. Lengkapi agar informasi profil dan penilaian lebih utuh.</p> <p class="text-body-secondary small mb-0">Beberapa data belum tersedia. Lengkapi agar informasi profil dan penilaian lebih utuh.</p>
</div> </div>
</div> </div>
@canany(['super_admin', 'admin', 'fasilitator']) @canany(['super_admin', 'admin', 'enumerator'])
<a href="{{ route('petani.edit', $petani->id) }}" class="btn btn-sm btn-primary flex-shrink-0 profile-completeness-action"> <a href="{{ route('petani.edit', $petani->id) }}" class="btn btn-sm btn-primary flex-shrink-0 profile-completeness-action">
<i class="icon-base ti tabler-edit icon-sm me-2" aria-hidden="true"></i>Lengkapi data <i class="icon-base ti tabler-edit icon-sm me-2" aria-hidden="true"></i>Lengkapi data
</a> </a>
+11 -4
View File
@@ -3,7 +3,6 @@
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
//Auth //Auth
Route::put('/users/{id}/verify', [App\Http\Controllers\Web\Konfigurasi\UserController::class, 'verify'])->name('users.verify');
Route::get('/login', [App\Http\Controllers\Web\Konfigurasi\AuthController::class, 'login'])->name('login'); Route::get('/login', [App\Http\Controllers\Web\Konfigurasi\AuthController::class, 'login'])->name('login');
Route::get('/register', [App\Http\Controllers\Web\Konfigurasi\AuthController::class, 'register'])->name('register'); Route::get('/register', [App\Http\Controllers\Web\Konfigurasi\AuthController::class, 'register'])->name('register');
Route::post('/create-account', [App\Http\Controllers\Web\Konfigurasi\AuthController::class, 'createAccount'])->name('auth.createAccount'); Route::post('/create-account', [App\Http\Controllers\Web\Konfigurasi\AuthController::class, 'createAccount'])->name('auth.createAccount');
@@ -17,10 +16,15 @@ Route::get('/', function() {
return redirect()->route('login'); return redirect()->route('login');
} }
if (auth()->user()->role->value === 'fasilitator') { if (auth()->user()->role->value === 'enumerator') {
return redirect()->route('dashboard.survey-petani'); return redirect()->route('dashboard.survey-petani');
} }
// Fasilitator (pemateri workshop) belum punya menu khusus
if (auth()->user()->role->value === 'fasilitator') {
return redirect()->route('userProfile.index');
}
return redirect()->route('dashboard.karakteristik-petani'); return redirect()->route('dashboard.karakteristik-petani');
})->name('home'); })->name('home');
@@ -82,7 +86,8 @@ Route::middleware('auth')
// User // User
Route::resource('users', App\Http\Controllers\Web\Konfigurasi\UserController::class); Route::resource('users', App\Http\Controllers\Web\Konfigurasi\UserController::class);
Route::patch('/users/{user}/verify', [App\Http\Controllers\Web\Konfigurasi\UserController::class, 'verify'])->name('users.verify'); Route::put('/users/{user}/verify', [App\Http\Controllers\Web\Konfigurasi\UserController::class, 'verify'])->name('users.verify');
Route::singleton('users.komoditas', App\Http\Controllers\Web\Konfigurasi\KoordinatorKomoditasController::class)->only(['update']);
// Provinsi // Provinsi
Route::get('/provinsi/list', [App\Http\Controllers\Web\Master\ProvinsiController::class, 'list'])->name('provinsi.list'); Route::get('/provinsi/list', [App\Http\Controllers\Web\Master\ProvinsiController::class, 'list'])->name('provinsi.list');
@@ -220,9 +225,10 @@ Route::middleware('auth')
Route::resource('realisasi', App\Http\Controllers\Web\Program\RealisasiController::class)->only('index', 'store', 'show', 'update', 'destroy'); Route::resource('realisasi', App\Http\Controllers\Web\Program\RealisasiController::class)->only('index', 'store', 'show', 'update', 'destroy');
Route::resource('verifikasi', App\Http\Controllers\Web\Program\VerifikasiController::class)->only('store', 'destroy'); Route::resource('verifikasi', App\Http\Controllers\Web\Program\VerifikasiController::class)->only('store', 'destroy');
// Petani (dibatasi per role & komoditas koordinator, lihat BatasiAksesPetani)
Route::middleware('akses.petani')->group(function () {
Route::get('petani/show-image/{id}/{field}', [App\Http\Controllers\Web\Produktivitas\PetaniController::class, 'showImage'])->name('petani.show-image'); Route::get('petani/show-image/{id}/{field}', [App\Http\Controllers\Web\Produktivitas\PetaniController::class, 'showImage'])->name('petani.show-image');
// Petani
Route::resource('petani', App\Http\Controllers\Web\Produktivitas\PetaniController::class)->whereNumber('petani'); Route::resource('petani', App\Http\Controllers\Web\Produktivitas\PetaniController::class)->whereNumber('petani');
Route::get('petani/{petani}/akses-pasar', [App\Http\Controllers\Web\Produktivitas\AksesPasarController::class, 'editByPetani'])->name('petani.akses-pasar.edit'); Route::get('petani/{petani}/akses-pasar', [App\Http\Controllers\Web\Produktivitas\AksesPasarController::class, 'editByPetani'])->name('petani.akses-pasar.edit');
Route::get('petani/{petani}/lahan-tanaman/summary', [App\Http\Controllers\Web\Produktivitas\PetaniLahanController::class, 'summary'])->name('petani.lahan-tanaman.summary'); Route::get('petani/{petani}/lahan-tanaman/summary', [App\Http\Controllers\Web\Produktivitas\PetaniLahanController::class, 'summary'])->name('petani.lahan-tanaman.summary');
@@ -243,6 +249,7 @@ Route::middleware('auth')
Route::resource('petani.akses-pasar', App\Http\Controllers\Web\Produktivitas\AksesPasarController::class)->whereNumber('petani'); Route::resource('petani.akses-pasar', App\Http\Controllers\Web\Produktivitas\AksesPasarController::class)->whereNumber('petani');
Route::resource('petani.skor', App\Http\Controllers\Web\Produktivitas\SkorPetaniController::class)->only(['index']); Route::resource('petani.skor', App\Http\Controllers\Web\Produktivitas\SkorPetaniController::class)->only(['index']);
});
Route::resource('survey/kelompok-tani', App\Http\Controllers\Web\Produktivitas\SurveyKelompokTaniController::class)->except(['show'])->names('kelompok-tani')->parameters(['kelompok-tani' => 'kelompokTani']); Route::resource('survey/kelompok-tani', App\Http\Controllers\Web\Produktivitas\SurveyKelompokTaniController::class)->except(['show'])->names('kelompok-tani')->parameters(['kelompok-tani' => 'kelompokTani']);
Route::prefix('survey/kelompok-tani/{kelompokTani}')->name('kelompok-tani.')->group(function () { Route::prefix('survey/kelompok-tani/{kelompokTani}')->name('kelompok-tani.')->group(function () {