From 722115674fcde802cabf2b5ecb997a55170455eb Mon Sep 17 00:00:00 2001 From: marszayn Date: Wed, 10 Sep 2025 23:50:13 +0700 Subject: [PATCH] update: roles permissions --- app/Console/Commands/CheckPerizinanData.php | 6 +- app/Console/Commands/SyncPerizinanData.php | 2 +- app/Console/Commands/TestProductionSetup.php | 2 +- app/Helpers/DashboardHelper.php | 62 +++--- .../Admin/PermissionController.php | 55 +++++ app/Http/Controllers/Admin/RoleController.php | 67 ++++++ app/Http/Controllers/AuthController.php | 106 +++++++++ app/Http/Controllers/WebAuthController.php | 50 +++++ app/Http/Requests/Auth/LoginRequest.php | 36 ++++ app/Models/FastestPermohonan.php | 37 ++-- app/Models/PerizinanStatus.php | 37 ++-- app/Models/TerakhirTerbit.php | 35 +-- app/Models/User.php | 5 +- app/Services/PerizinanApiService.php | 52 ++--- bootstrap/app.php | 11 +- composer.json | 4 +- composer.lock | 149 ++++++++++++- config/permission.php | 202 ++++++++++++++++++ ...01_create_personal_access_tokens_table.php | 33 +++ ...4_015824_create_perizinan_status_table.php | 22 +- ...024445_create_fastest_permohonan_table.php | 24 +-- ...14_033547_create_terakhir_terbit_table.php | 22 +- ..._10_000000_add_username_to_users_table.php | 24 +++ ..._09_10_152414_create_permission_tables.php | 136 ++++++++++++ database/seeders/DatabaseSeeder.php | 11 +- .../seeders/RolesAndPermissionsSeeder.php | 76 +++++++ database/seeders/UserSeeder.php | 47 ++++ .../views/admin/permissions/create.blade.php | 24 +++ .../views/admin/permissions/edit.blade.php | 25 +++ .../views/admin/permissions/index.blade.php | 45 ++++ resources/views/admin/roles/create.blade.php | 62 ++++++ resources/views/admin/roles/edit.blade.php | 63 ++++++ resources/views/admin/roles/index.blade.php | 112 ++++++++++ resources/views/auth/signin.blade.php | 97 +++++++-- .../components/frontend/navbar.blade.php | 30 ++- resources/views/components/head.blade.php | 1 + resources/views/components/navbar.blade.php | 48 ++++- resources/views/components/sidebar.blade.php | 24 ++- resources/views/dashboard/index.blade.php | 28 +-- resources/views/errors/403.blade.php | 120 +++++++++++ resources/views/pengguna/index_user.blade.php | 123 +++++++---- routes/api.php | 13 ++ routes/web.php | 98 +++++---- 43 files changed, 1938 insertions(+), 288 deletions(-) create mode 100644 app/Http/Controllers/Admin/PermissionController.php create mode 100644 app/Http/Controllers/Admin/RoleController.php create mode 100644 app/Http/Controllers/AuthController.php create mode 100644 app/Http/Controllers/WebAuthController.php create mode 100644 app/Http/Requests/Auth/LoginRequest.php create mode 100644 config/permission.php create mode 100644 database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php create mode 100644 database/migrations/2025_09_10_000000_add_username_to_users_table.php create mode 100644 database/migrations/2025_09_10_152414_create_permission_tables.php create mode 100644 database/seeders/RolesAndPermissionsSeeder.php create mode 100644 database/seeders/UserSeeder.php create mode 100644 resources/views/admin/permissions/create.blade.php create mode 100644 resources/views/admin/permissions/edit.blade.php create mode 100644 resources/views/admin/permissions/index.blade.php create mode 100644 resources/views/admin/roles/create.blade.php create mode 100644 resources/views/admin/roles/edit.blade.php create mode 100644 resources/views/admin/roles/index.blade.php create mode 100644 resources/views/errors/403.blade.php create mode 100644 routes/api.php diff --git a/app/Console/Commands/CheckPerizinanData.php b/app/Console/Commands/CheckPerizinanData.php index 3e90039..df73ebc 100644 --- a/app/Console/Commands/CheckPerizinanData.php +++ b/app/Console/Commands/CheckPerizinanData.php @@ -14,21 +14,21 @@ class CheckPerizinanData extends Command { $this->info('📊 Checking synced perizinan data...'); - $data = PerizinanStatus::orderBy('kategori')->orderBy('status_id')->get(); + $data = PerizinanStatus::orderBy('Kategori_ID')->orderBy('StatusID')->get(); if ($data->isEmpty()) { $this->warn('No data found in database'); return; } - $groupedData = $data->groupBy('kategori'); + $groupedData = $data->groupBy('Kategori_ID'); foreach ($groupedData as $kategori => $items) { $this->newLine(); $this->info("=== {$kategori} ==="); foreach ($items as $item) { - $this->line(" {$item->status_id}: {$item->label} = {$item->value}"); + $this->line(" {$item->StatusID}: {$item->Label} = {$item->Value}"); } } diff --git a/app/Console/Commands/SyncPerizinanData.php b/app/Console/Commands/SyncPerizinanData.php index f548d29..07ff32b 100644 --- a/app/Console/Commands/SyncPerizinanData.php +++ b/app/Console/Commands/SyncPerizinanData.php @@ -123,7 +123,7 @@ class SyncPerizinanData extends Command } $endTime = Carbon::now(); - $duration = $endTime->diffInSeconds($startTime); + $duration = $endTime->diffInSeconds($startTime, true); $this->info("🎉 Data synchronization completed in {$duration} seconds"); diff --git a/app/Console/Commands/TestProductionSetup.php b/app/Console/Commands/TestProductionSetup.php index bd865c6..37f86a7 100644 --- a/app/Console/Commands/TestProductionSetup.php +++ b/app/Console/Commands/TestProductionSetup.php @@ -35,7 +35,7 @@ class TestProductionSetup extends Command $this->info('2. Testing Database Table...'); try { $count = PerizinanStatus::count(); - $this->line(" ✅ perizinan_status table exists with {$count} records"); + $this->line(" ✅ PerizinanStatus table exists with {$count} records"); } catch (\Exception $e) { $this->error(' ❌ Database table issue: ' . $e->getMessage()); $allPassed = false; diff --git a/app/Helpers/DashboardHelper.php b/app/Helpers/DashboardHelper.php index 148d630..12a3fee 100644 --- a/app/Helpers/DashboardHelper.php +++ b/app/Helpers/DashboardHelper.php @@ -64,10 +64,10 @@ class DashboardHelper foreach ($dbData as $item) { $data[] = [ - 'id' => $item->status_id, - 'label' => $item->label, - 'value' => $item->value, - 'color' => $colorMap[$item->status_id] ?? 'secondary' + 'id' => $item->StatusID, + 'label' => $item->Label, + 'value' => $item->Value, + 'color' => $colorMap[$item->StatusID] ?? 'secondary' ]; } @@ -281,10 +281,10 @@ class DashboardHelper $index = 1; foreach ($dbData as $item) { $data[] = [ - 'nama' => $item->nama, - 'total' => $item->total, - 'durasi_pemohon' => $item->durasi_pemohon, - 'durasi_petugas' => $item->durasi_petugas, + 'Nama' => $item->Nama, + 'Total' => $item->Total, + 'DurasiPemohon' => $item->DurasiPemohon, + 'DurasiPetugas' => $item->DurasiPetugas, 'rank_order' => $index++ // Generate rank_order dinamis untuk display ]; } @@ -302,18 +302,18 @@ class DashboardHelper { $fallbackData = [ 'pertek' => [ - ['nama' => 'Izin Pengelolaan Limbah B3', 'total' => 45, 'durasi_pemohon' => '2 Hari 3 Jam', 'durasi_petugas' => '5 Hari 2 Jam', 'rank_order' => 1], - ['nama' => 'Izin Emisi Kendaraan', 'total' => 67, 'durasi_pemohon' => '3 Hari 1 Jam', 'durasi_petugas' => '7 Hari 4 Jam', 'rank_order' => 2], - ['nama' => 'SPPL Industri', 'total' => 23, 'durasi_pemohon' => '4 Hari 2 Jam', 'durasi_petugas' => '8 Hari 1 Jam', 'rank_order' => 3], - ['nama' => 'Izin Penyimpanan B3', 'total' => 34, 'durasi_pemohon' => '5 Hari', 'durasi_petugas' => '9 Hari 3 Jam', 'rank_order' => 4], - ['nama' => 'Izin Pengolahan Limbah', 'total' => 19, 'durasi_pemohon' => '6 Hari 4 Jam', 'durasi_petugas' => '10 Hari 2 Jam', 'rank_order' => 5], + ['Nama' => 'Izin Pengelolaan Limbah B3', 'Total' => 45, 'DurasiPemohon' => '2 Hari 3 Jam', 'DurasiPetugas' => '5 Hari 2 Jam', 'rank_order' => 1], + ['Nama' => 'Izin Emisi Kendaraan', 'Total' => 67, 'DurasiPemohon' => '3 Hari 1 Jam', 'DurasiPetugas' => '7 Hari 4 Jam', 'rank_order' => 2], + ['Nama' => 'SPPL Industri', 'Total' => 23, 'DurasiPemohon' => '4 Hari 2 Jam', 'DurasiPetugas' => '8 Hari 1 Jam', 'rank_order' => 3], + ['Nama' => 'Izin Penyimpanan B3', 'Total' => 34, 'DurasiPemohon' => '5 Hari', 'DurasiPetugas' => '9 Hari 3 Jam', 'rank_order' => 4], + ['Nama' => 'Izin Pengolahan Limbah', 'Total' => 19, 'DurasiPemohon' => '6 Hari 4 Jam', 'DurasiPetugas' => '10 Hari 2 Jam', 'rank_order' => 5], ], 'amdal' => [ - ['nama' => 'AMDAL Bangunan Tinggi', 'total' => 12, 'durasi_pemohon' => '15 Hari', 'durasi_petugas' => '30 Hari', 'rank_order' => 1], - ['nama' => 'AMDAL Infrastruktur', 'total' => 8, 'durasi_pemohon' => '18 Hari', 'durasi_petugas' => '35 Hari', 'rank_order' => 2], - ['nama' => 'AMDAL Industri', 'total' => 15, 'durasi_pemohon' => '20 Hari', 'durasi_petugas' => '40 Hari', 'rank_order' => 3], - ['nama' => 'AMDAL Perumahan', 'total' => 25, 'durasi_pemohon' => '22 Hari', 'durasi_petugas' => '42 Hari', 'rank_order' => 4], - ['nama' => 'AMDAL Komersial', 'total' => 18, 'durasi_pemohon' => '25 Hari', 'durasi_petugas' => '45 Hari', 'rank_order' => 5], + ['Nama' => 'AMDAL Bangunan Tinggi', 'Total' => 12, 'DurasiPemohon' => '15 Hari', 'DurasiPetugas' => '30 Hari', 'rank_order' => 1], + ['Nama' => 'AMDAL Infrastruktur', 'Total' => 8, 'DurasiPemohon' => '18 Hari', 'DurasiPetugas' => '35 Hari', 'rank_order' => 2], + ['Nama' => 'AMDAL Industri', 'Total' => 15, 'DurasiPemohon' => '20 Hari', 'DurasiPetugas' => '40 Hari', 'rank_order' => 3], + ['Nama' => 'AMDAL Perumahan', 'Total' => 25, 'DurasiPemohon' => '22 Hari', 'DurasiPetugas' => '42 Hari', 'rank_order' => 4], + ['Nama' => 'AMDAL Komersial', 'Total' => 18, 'DurasiPemohon' => '25 Hari', 'DurasiPetugas' => '45 Hari', 'rank_order' => 5], ] ]; @@ -377,9 +377,9 @@ class DashboardHelper $index = 1; foreach ($dbData as $item) { $data[] = [ - 'nama_izin' => $item->nama_izin, - 'pemohon' => $item->pemohon, - 'tanggal_terbit' => $item->tanggal_terbit, + 'NamaIzin' => $item->NamaIzin, + 'Pemohon' => $item->Pemohon, + 'TanggalTerbit' => $item->TanggalTerbit, 'rank_order' => $index++ // Generate rank_order dinamis untuk display ]; } @@ -397,18 +397,18 @@ class DashboardHelper { $fallbackData = [ 'pertek' => [ - ['nama_izin' => 'SERTIFIKAT LAIK OPERASI - PEMENUHAN BAKU MUTU EMISI', 'pemohon' => 'PT. MAJU BERSAMA', 'tanggal_terbit' => Carbon::parse('2025-07-15'), 'rank_order' => 1], - ['nama_izin' => 'PERSETUJUAN TEKNIS - PEMENUHAN BAKU MUTU AIR LIMBAH', 'pemohon' => 'CV. KARYA MANDIRI', 'tanggal_terbit' => Carbon::parse('2025-07-14'), 'rank_order' => 2], - ['nama_izin' => 'SERTIFIKAT LAIK OPERASI - PEMENUHAN BAKU MUTU AIR LIMBAH', 'pemohon' => 'PT. INDUSTRI SEJAHTERA', 'tanggal_terbit' => Carbon::parse('2025-07-13'), 'rank_order' => 3], - ['nama_izin' => 'PERSETUJUAN TEKNIS - PEMENUHAN BAKU MUTU AIR LIMBAH', 'pemohon' => 'PT. TEKNOLOGI MODERN', 'tanggal_terbit' => Carbon::parse('2025-07-12'), 'rank_order' => 4], - ['nama_izin' => 'SERTIFIKAT LAIK OPERASI - PEMENUHAN BAKU MUTU EMISI', 'pemohon' => 'CV. BERKAH JAYA', 'tanggal_terbit' => Carbon::parse('2025-07-11'), 'rank_order' => 5], + ['NamaIzin' => 'SERTIFIKAT LAIK OPERASI - PEMENUHAN BAKU MUTU EMISI', 'Pemohon' => 'PT. MAJU BERSAMA', 'TanggalTerbit' => Carbon::parse('2025-07-15'), 'rank_order' => 1], + ['NamaIzin' => 'PERSETUJUAN TEKNIS - PEMENUHAN BAKU MUTU AIR LIMBAH', 'Pemohon' => 'CV. KARYA MANDIRI', 'TanggalTerbit' => Carbon::parse('2025-07-14'), 'rank_order' => 2], + ['NamaIzin' => 'SERTIFIKAT LAIK OPERASI - PEMENUHAN BAKU MUTU AIR LIMBAH', 'Pemohon' => 'PT. INDUSTRI SEJAHTERA', 'TanggalTerbit' => Carbon::parse('2025-07-13'), 'rank_order' => 3], + ['NamaIzin' => 'PERSETUJUAN TEKNIS - PEMENUHAN BAKU MUTU AIR LIMBAH', 'Pemohon' => 'PT. TEKNOLOGI MODERN', 'TanggalTerbit' => Carbon::parse('2025-07-12'), 'rank_order' => 4], + ['NamaIzin' => 'SERTIFIKAT LAIK OPERASI - PEMENUHAN BAKU MUTU EMISI', 'Pemohon' => 'CV. BERKAH JAYA', 'TanggalTerbit' => Carbon::parse('2025-07-11'), 'rank_order' => 5], ], 'amdal' => [ - ['nama_izin' => 'AMDAL BANGUNAN TINGGI', 'pemohon' => 'PT. KONSTRUKSI PRIMA', 'tanggal_terbit' => Carbon::parse('2025-07-10'), 'rank_order' => 1], - ['nama_izin' => 'AMDAL INFRASTRUKTUR', 'pemohon' => 'CV. INFRATEK', 'tanggal_terbit' => Carbon::parse('2025-07-09'), 'rank_order' => 2], - ['nama_izin' => 'AMDAL INDUSTRI', 'pemohon' => 'PT. INDUSTRI MODERN', 'tanggal_terbit' => Carbon::parse('2025-07-08'), 'rank_order' => 3], - ['nama_izin' => 'AMDAL PERUMAHAN', 'pemohon' => 'PT. PROPERTI SEJAHTERA', 'tanggal_terbit' => Carbon::parse('2025-07-07'), 'rank_order' => 4], - ['nama_izin' => 'AMDAL KOMERSIAL', 'pemohon' => 'CV. KOMERSIAL JAYA', 'tanggal_terbit' => Carbon::parse('2025-07-06'), 'rank_order' => 5], + ['NamaIzin' => 'AMDAL BANGUNAN TINGGI', 'Pemohon' => 'PT. KONSTRUKSI PRIMA', 'TanggalTerbit' => Carbon::parse('2025-07-10'), 'rank_order' => 1], + ['NamaIzin' => 'AMDAL INFRASTRUKTUR', 'Pemohon' => 'CV. INFRATEK', 'TanggalTerbit' => Carbon::parse('2025-07-09'), 'rank_order' => 2], + ['NamaIzin' => 'AMDAL INDUSTRI', 'Pemohon' => 'PT. INDUSTRI MODERN', 'TanggalTerbit' => Carbon::parse('2025-07-08'), 'rank_order' => 3], + ['NamaIzin' => 'AMDAL PERUMAHAN', 'Pemohon' => 'PT. PROPERTI SEJAHTERA', 'TanggalTerbit' => Carbon::parse('2025-07-07'), 'rank_order' => 4], + ['NamaIzin' => 'AMDAL KOMERSIAL', 'Pemohon' => 'CV. KOMERSIAL JAYA', 'TanggalTerbit' => Carbon::parse('2025-07-06'), 'rank_order' => 5], ] ]; diff --git a/app/Http/Controllers/Admin/PermissionController.php b/app/Http/Controllers/Admin/PermissionController.php new file mode 100644 index 0000000..85e3e9f --- /dev/null +++ b/app/Http/Controllers/Admin/PermissionController.php @@ -0,0 +1,55 @@ +paginate(20); + return view('admin.permissions.index', compact('permissions')); + } + + public function create() + { + return view('admin.permissions.create'); + } + + public function store(Request $request) + { + $validated = $request->validate([ + 'name' => ['required','string','max:150','unique:permissions,name'], + ]); + + Permission::create(['name' => $validated['name']]); + + return redirect()->route('admin.permissions.index')->with('success', 'Permission berhasil dibuat.'); + } + + public function edit(Permission $permission) + { + return view('admin.permissions.edit', compact('permission')); + } + + public function update(Request $request, Permission $permission) + { + $validated = $request->validate([ + 'name' => ['required','string','max:150','unique:permissions,name,'.$permission->id], + ]); + + $permission->update(['name' => $validated['name']]); + + return redirect()->route('admin.permissions.index')->with('success', 'Permission berhasil diperbarui.'); + } + + public function destroy(Permission $permission) + { + $permission->delete(); + return redirect()->route('admin.permissions.index')->with('success', 'Permission berhasil dihapus.'); + } +} + diff --git a/app/Http/Controllers/Admin/RoleController.php b/app/Http/Controllers/Admin/RoleController.php new file mode 100644 index 0000000..d0e38d6 --- /dev/null +++ b/app/Http/Controllers/Admin/RoleController.php @@ -0,0 +1,67 @@ +orderBy('name')->paginate(15); + return view('admin.roles.index', compact('roles')); + } + + public function create() + { + $permissions = Permission::orderBy('name')->get(); + return view('admin.roles.create', compact('permissions')); + } + + public function store(Request $request) + { + $validated = $request->validate([ + 'name' => ['required','string','max:100','unique:roles,name'], + 'permissions' => ['array'], + 'permissions.*' => ['string','exists:permissions,name'], + ]); + + $role = Role::create(['name' => $validated['name']]); + if (!empty($validated['permissions'])) { + $role->syncPermissions($validated['permissions']); + } + + return redirect()->route('admin.roles.index')->with('success', 'Role berhasil dibuat.'); + } + + public function edit(Role $role) + { + $permissions = Permission::orderBy('name')->get(); + $rolePermissions = $role->permissions->pluck('name')->toArray(); + return view('admin.roles.edit', compact('role','permissions','rolePermissions')); + } + + public function update(Request $request, Role $role) + { + $validated = $request->validate([ + 'name' => ['required','string','max:100','unique:roles,name,'.$role->id], + 'permissions' => ['array'], + 'permissions.*' => ['string','exists:permissions,name'], + ]); + + $role->update(['name' => $validated['name']]); + $role->syncPermissions($validated['permissions'] ?? []); + + return redirect()->route('admin.roles.index')->with('success', 'Role berhasil diperbarui.'); + } + + public function destroy(Role $role) + { + $role->delete(); + return redirect()->route('admin.roles.index')->with('success', 'Role berhasil dihapus.'); + } +} + diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php new file mode 100644 index 0000000..292f195 --- /dev/null +++ b/app/Http/Controllers/AuthController.php @@ -0,0 +1,106 @@ +validated(); + $identifier = $data['identifier']; + $password = $data['password']; + $device = $data['device_name'] ?? $request->header('User-Agent') ?? 'api'; + + $user = User::where('email', $identifier)->first(); + if (!$user) { + $user = User::where('username', $identifier)->first(); + } + if (!$user || !Hash::check($password, $user->password)) { + throw ValidationException::withMessages([ + 'identifier' => ['email/username/password yang diberikan salah.'], + ]); + } + + $user->tokens()->where('name', $device)->delete(); + $token = $user->createToken($device)->plainTextToken; + + return response()->json([ + 'success' => true, + 'token' => $token, + 'token_type' => 'Bearer', + 'user' => [ + 'id' => $user->id, + 'name' => $user->name, + 'email' => $user->email, + 'username' => $user->username, + ], + ]); + } + + public function me(Request $request) + { + return response()->json([ + 'success' => true, + 'user' => $request->user(), + ]); + } + + public function logout(Request $request) + { + $request->user()->currentAccessToken()->delete(); + return response()->json(['success' => true]); + } + + public function logoutAll(Request $request) + { + $request->user()->tokens()->delete(); + return response()->json(['success' => true]); + } + + // =============== Web (Session Guard) =============== + public function sessionLogin(LoginRequest $request) + { + $data = $request->validated(); + $identifier = $data['identifier']; + $password = $data['password']; + + $user = User::where('email', $identifier)->first(); + if (!$user) { + $user = User::where('username', $identifier)->first(); + } + + if (!$user || !Hash::check($password, $user->password)) { + throw ValidationException::withMessages([ + 'identifier' => ['email/username/password yang diberikan salah.'], + ]); + } + + Auth::login($user, true); + $request->session()->regenerate(); + + return response()->json(['success' => true]); + } + + public function sessionLogout(Request $request) + { + Auth::logout(); + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + if ($request->expectsJson()) { + return response()->json(['success' => true]); + } + + return redirect()->route('login.index'); + } +} + diff --git a/app/Http/Controllers/WebAuthController.php b/app/Http/Controllers/WebAuthController.php new file mode 100644 index 0000000..5fe7300 --- /dev/null +++ b/app/Http/Controllers/WebAuthController.php @@ -0,0 +1,50 @@ +validated(); + $identifier = $data['identifier']; + $password = $data['password']; + + $user = User::where('email', $identifier)->first(); + if (!$user) { + $user = User::where('username', $identifier)->first(); + } + + if (!$user || !Hash::check($password, $user->password)) { + throw ValidationException::withMessages([ + 'identifier' => ['email/username/password yang diberikan salah.'], + ]); + } + + Auth::login($user, true); + request()->session()->regenerate(); + + return response()->json(['success' => true]); + } + public function logout(Request $request) + { + Auth::logout(); + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + if ($request->expectsJson()) { + return response()->json(['success' => true]); + } + + return redirect()->route('login.index'); + } +} + + diff --git a/app/Http/Requests/Auth/LoginRequest.php b/app/Http/Requests/Auth/LoginRequest.php new file mode 100644 index 0000000..5fc4ab3 --- /dev/null +++ b/app/Http/Requests/Auth/LoginRequest.php @@ -0,0 +1,36 @@ + ['required', 'string'], + 'password' => [ + 'required', + 'string', + 'min:8', + 'regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/', + ], + 'device_name' => ['nullable', 'string'], + ]; + } + + public function messages(): array + { + return [ + 'password.min' => 'Password minimal 8 karakter.', + 'password.regex' => 'Password harus mengandung huruf besar, huruf kecil, angka, dan simbol khusus.', + ]; + } +} + diff --git a/app/Models/FastestPermohonan.php b/app/Models/FastestPermohonan.php index 4e87eae..b41661c 100644 --- a/app/Models/FastestPermohonan.php +++ b/app/Models/FastestPermohonan.php @@ -7,22 +7,23 @@ use Carbon\Carbon; class FastestPermohonan extends Model { - protected $table = 'fastest_permohonan'; + protected $table = 'FastestPermohonan'; + protected $primaryKey = 'FastestPermohonanID'; protected $fillable = [ - 'kategori', - 'nama', - 'total', - 'durasi_pemohon', - 'durasi_petugas', - 'api_last_updated', - 'sync_date' + 'Kategori_ID', + 'Nama', + 'Total', + 'DurasiPemohon', + 'DurasiPetugas', + 'ApiLastUpdated', + 'SyncDate' ]; protected $casts = [ - 'api_last_updated' => 'datetime', - 'sync_date' => 'date', - 'total' => 'integer' + 'ApiLastUpdated' => 'datetime', + 'SyncDate' => 'date', + 'Total' => 'integer' ]; /** @@ -32,15 +33,15 @@ class FastestPermohonan extends Model */ public static function getLatestByKategori($kategori) { - $latestSyncDate = self::where('kategori', $kategori)->max('sync_date'); + $latestSyncDate = self::where('Kategori_ID', $kategori)->max('SyncDate'); if (!$latestSyncDate) { return collect(); } - return self::where('kategori', $kategori) - ->whereDate('sync_date', $latestSyncDate) - ->orderBy('id') // Keep API order by using insertion order + return self::where('Kategori_ID', $kategori) + ->whereDate('SyncDate', $latestSyncDate) + ->orderBy('FastestPermohonanID') // Keep API order by using insertion order ->limit(5) ->get(); } @@ -52,9 +53,9 @@ class FastestPermohonan extends Model { $date = $date ?: Carbon::today(); - return self::where('kategori', $kategori) - ->whereDate('sync_date', $date) - ->orderBy('id') // Keep API order by using insertion order + return self::where('Kategori_ID', $kategori) + ->whereDate('SyncDate', $date) + ->orderBy('FastestPermohonanID') // Keep API order by using insertion order ->limit(5) ->get(); } diff --git a/app/Models/PerizinanStatus.php b/app/Models/PerizinanStatus.php index 79ff757..7bfec64 100644 --- a/app/Models/PerizinanStatus.php +++ b/app/Models/PerizinanStatus.php @@ -7,21 +7,22 @@ use Carbon\Carbon; class PerizinanStatus extends Model { - protected $table = 'perizinan_status'; + protected $table = 'PerizinanStatus'; + protected $primaryKey = 'PerizinanStatusID'; protected $fillable = [ - 'kategori', - 'status_id', - 'label', - 'value', - 'api_last_updated', - 'sync_date' + 'Kategori_ID', + 'StatusID', + 'Label', + 'Value', + 'ApiLastUpdated', + 'SyncDate' ]; protected $casts = [ - 'api_last_updated' => 'datetime', - 'sync_date' => 'date', - 'value' => 'integer' + 'ApiLastUpdated' => 'datetime', + 'SyncDate' => 'date', + 'Value' => 'integer' ]; /** @@ -30,18 +31,18 @@ class PerizinanStatus extends Model */ public static function getLatestByKategori($kategori) { - // Find the most recent sync_date for the given kategori - $latestSyncDate = self::where('kategori', $kategori)->max('sync_date'); + // Find the most recent SyncDate for the given kategori + $latestSyncDate = self::where('Kategori_ID', $kategori)->max('SyncDate'); if (!$latestSyncDate) { // No data at all for this kategori return collect(); } - return self::where('kategori', $kategori) - ->whereDate('sync_date', $latestSyncDate) + return self::where('Kategori_ID', $kategori) + ->whereDate('SyncDate', $latestSyncDate) ->get() - ->keyBy('status_id'); + ->keyBy('StatusID'); } /** @@ -51,9 +52,9 @@ class PerizinanStatus extends Model { $date = $date ?: Carbon::today(); - return self::where('kategori', $kategori) - ->whereDate('sync_date', $date) + return self::where('Kategori_ID', $kategori) + ->whereDate('SyncDate', $date) ->get() - ->keyBy('status_id'); + ->keyBy('StatusID'); } } diff --git a/app/Models/TerakhirTerbit.php b/app/Models/TerakhirTerbit.php index 74bd3c2..9698b7c 100644 --- a/app/Models/TerakhirTerbit.php +++ b/app/Models/TerakhirTerbit.php @@ -7,21 +7,22 @@ use Carbon\Carbon; class TerakhirTerbit extends Model { - protected $table = 'terakhir_terbit'; + protected $table = 'TerakhirTerbit'; + protected $primaryKey = 'TerakhirTerbitID'; protected $fillable = [ - 'kategori', - 'nama_izin', - 'pemohon', - 'tanggal_terbit', - 'api_last_updated', - 'sync_date' + 'Kategori_ID', + 'NamaIzin', + 'Pemohon', + 'TanggalTerbit', + 'ApiLastUpdated', + 'SyncDate' ]; protected $casts = [ - 'tanggal_terbit' => 'date', - 'api_last_updated' => 'datetime', - 'sync_date' => 'date' + 'TanggalTerbit' => 'date', + 'ApiLastUpdated' => 'datetime', + 'SyncDate' => 'date' ]; /** @@ -30,15 +31,15 @@ class TerakhirTerbit extends Model */ public static function getLatestByKategori($kategori) { - $latestSyncDate = self::where('kategori', $kategori)->max('sync_date'); + $latestSyncDate = self::where('Kategori_ID', $kategori)->max('SyncDate'); if (!$latestSyncDate) { return collect(); } - return self::where('kategori', $kategori) - ->whereDate('sync_date', $latestSyncDate) - ->orderBy('tanggal_terbit', 'desc') + return self::where('Kategori_ID', $kategori) + ->whereDate('SyncDate', $latestSyncDate) + ->orderBy('TanggalTerbit', 'desc') ->limit(5) ->get(); } @@ -48,9 +49,9 @@ class TerakhirTerbit extends Model */ public static function getByKategoriAndDate($kategori, $date) { - return self::where('kategori', $kategori) - ->whereDate('sync_date', $date) - ->orderBy('tanggal_terbit', 'desc') + return self::where('Kategori_ID', $kategori) + ->whereDate('SyncDate', $date) + ->orderBy('TanggalTerbit', 'desc') ->limit(5) ->get(); } diff --git a/app/Models/User.php b/app/Models/User.php index 749c7b7..b61a608 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,13 +4,15 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Laravel\Sanctum\HasApiTokens; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Spatie\Permission\Traits\HasRoles; class User extends Authenticatable { /** @use HasFactory<\Database\Factories\UserFactory> */ - use HasFactory, Notifiable; + use HasApiTokens, HasFactory, Notifiable, HasRoles; /** * The attributes that are mass assignable. @@ -20,6 +22,7 @@ class User extends Authenticatable protected $fillable = [ 'name', 'email', + 'username', 'password', ]; diff --git a/app/Services/PerizinanApiService.php b/app/Services/PerizinanApiService.php index 1a2a89f..e856036 100644 --- a/app/Services/PerizinanApiService.php +++ b/app/Services/PerizinanApiService.php @@ -94,19 +94,19 @@ class PerizinanApiService foreach ($apiData['data'] as $item) { // Check if data already exists for today - $exists = PerizinanStatus::where('kategori', $kategori) - ->where('status_id', $item['id']) - ->whereDate('sync_date', $syncDate) + $exists = PerizinanStatus::where('Kategori_ID', $kategori) + ->where('StatusID', $item['id']) + ->whereDate('SyncDate', $syncDate) ->exists(); if (!$exists) { PerizinanStatus::create([ - 'kategori' => $kategori, - 'status_id' => $item['id'], - 'label' => $item['label'], - 'value' => $item['value'], - 'api_last_updated' => $apiLastUpdated, - 'sync_date' => $syncDate + 'Kategori_ID' => $kategori, + 'StatusID' => $item['id'], + 'Label' => $item['label'], + 'Value' => $item['value'], + 'ApiLastUpdated' => $apiLastUpdated, + 'SyncDate' => $syncDate ]); $syncedCount++; @@ -199,19 +199,19 @@ class PerizinanApiService $syncedCount = 0; // Delete existing data for today to ensure we have fresh data - FastestPermohonan::where('kategori', $kategori) - ->whereDate('sync_date', $syncDate) + FastestPermohonan::where('Kategori_ID', $kategori) + ->whereDate('SyncDate', $syncDate) ->delete(); foreach ($apiData['data'] as $index => $item) { FastestPermohonan::create([ - 'kategori' => $kategori, - 'nama' => $item['nama'], - 'total' => $item['total'], - 'durasi_pemohon' => $item['durasi_pemohon'], - 'durasi_petugas' => $item['durasi_petugas'], - 'api_last_updated' => $apiLastUpdated, - 'sync_date' => $syncDate + 'Kategori_ID' => $kategori, + 'Nama' => $item['nama'], + 'Total' => $item['total'], + 'DurasiPemohon' => $item['durasi_pemohon'], + 'DurasiPetugas' => $item['durasi_petugas'], + 'ApiLastUpdated' => $apiLastUpdated, + 'SyncDate' => $syncDate ]); $syncedCount++; @@ -303,18 +303,18 @@ class PerizinanApiService $syncedCount = 0; // Delete existing data for today to ensure we have fresh ranking - TerakhirTerbit::where('kategori', $kategori) - ->whereDate('sync_date', $syncDate) + TerakhirTerbit::where('Kategori_ID', $kategori) + ->whereDate('SyncDate', $syncDate) ->delete(); foreach ($apiData['data'] as $index => $item) { TerakhirTerbit::create([ - 'kategori' => $kategori, - 'nama_izin' => $item['nama_izin'], - 'pemohon' => $item['pemohon'], - 'tanggal_terbit' => Carbon::parse($item['tanggal_terbit']), - 'api_last_updated' => $apiLastUpdated, - 'sync_date' => $syncDate + 'Kategori_ID' => $kategori, + 'NamaIzin' => $item['nama_izin'], + 'Pemohon' => $item['pemohon'], + 'TanggalTerbit' => Carbon::parse($item['tanggal_terbit']), + 'ApiLastUpdated' => $apiLastUpdated, + 'SyncDate' => $syncDate ]); $syncedCount++; diff --git a/bootstrap/app.php b/bootstrap/app.php index 7b162da..91776f4 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -3,15 +3,24 @@ use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; +use Spatie\Permission\Middleware\PermissionMiddleware; +use Spatie\Permission\Middleware\RoleMiddleware; +use Spatie\Permission\Middleware\RoleOrPermissionMiddleware; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', + api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { - // + // Spatie Permission middleware aliases + $middleware->alias([ + 'role' => RoleMiddleware::class, + 'permission' => PermissionMiddleware::class, + 'role_or_permission' => RoleOrPermissionMiddleware::class, + ]); }) ->withExceptions(function (Exceptions $exceptions) { // diff --git a/composer.json b/composer.json index 8aa96f1..414936b 100644 --- a/composer.json +++ b/composer.json @@ -12,9 +12,11 @@ "php": "^8.2", "barryvdh/laravel-dompdf": "^3.1", "laravel/framework": "^12.0", + "laravel/sanctum": "^4.2", "laravel/tinker": "^2.10.1", "mallardduck/blade-lucide-icons": "^1.23", - "spatie/laravel-html": "^3.12" + "spatie/laravel-html": "^3.12", + "spatie/laravel-permission": "^6.21" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/composer.lock b/composer.lock index 2199d0f..a78ad1a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a630bee8cc849f41bc0261821020fefa", + "content-hash": "324973c896fb611b576d446434dcb170", "packages": [ { "name": "barryvdh/laravel-dompdf", @@ -1641,6 +1641,70 @@ }, "time": "2025-02-11T13:34:40+00:00" }, + { + "name": "laravel/sanctum", + "version": "v4.2.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/fd6df4f79f48a72992e8d29a9c0ee25422a0d677", + "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^11.0|^12.0", + "illuminate/contracts": "^11.0|^12.0", + "illuminate/database": "^11.0|^12.0", + "illuminate/support": "^11.0|^12.0", + "php": "^8.2", + "symfony/console": "^7.0" + }, + "require-dev": { + "mockery/mockery": "^1.6", + "orchestra/testbench": "^9.0|^10.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2025-07-09T19:45:24+00:00" + }, { "name": "laravel/serializable-closure", "version": "v2.0.3", @@ -3869,6 +3933,89 @@ ], "time": "2025-03-21T08:58:06+00:00" }, + { + "name": "spatie/laravel-permission", + "version": "6.21.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "6a118e8855dfffcd90403aab77bbf35a03db51b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/6a118e8855dfffcd90403aab77bbf35a03db51b3", + "reference": "6a118e8855dfffcd90403aab77bbf35a03db51b3", + "shasum": "" + }, + "require": { + "illuminate/auth": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/container": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/database": "^8.12|^9.0|^10.0|^11.0|^12.0", + "php": "^8.0" + }, + "require-dev": { + "laravel/passport": "^11.0|^12.0", + "laravel/pint": "^1.0", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0|^10.0", + "phpunit/phpunit": "^9.4|^10.1|^11.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "6.x-dev", + "dev-master": "6.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 8.0 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/6.21.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-07-23T16:08:05+00:00" + }, { "name": "symfony/clock", "version": "v7.2.0", diff --git a/config/permission.php b/config/permission.php new file mode 100644 index 0000000..f39f6b5 --- /dev/null +++ b/config/permission.php @@ -0,0 +1,202 @@ + [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + + 'permission' => Spatie\Permission\Models\Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + + 'role' => Spatie\Permission\Models\Role::class, + + ], + + 'table_names' => [ + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'roles' => 'roles', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your permissions. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'permissions' => 'permissions', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your models permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_permissions' => 'model_has_permissions', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your models roles. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_roles' => 'model_has_roles', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'role_has_permissions' => 'role_has_permissions', + ], + + 'column_names' => [ + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, // default 'role_id', + 'permission_pivot_key' => null, // default 'permission_id', + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + + 'model_morph_key' => 'model_id', + + /* + * Change this if you want to use the teams feature and your related model's + * foreign key is other than `team_id`. + */ + + 'team_foreign_key' => 'team_id', + ], + + /* + * When set to true, the method for checking permissions will be registered on the gate. + * Set this to false if you want to implement custom logic for checking permissions. + */ + + 'register_permission_check_method' => true, + + /* + * When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered + * this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated + * NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it. + */ + 'register_octane_reset_listener' => false, + + /* + * Events will fire when a role or permission is assigned/unassigned: + * \Spatie\Permission\Events\RoleAttached + * \Spatie\Permission\Events\RoleDetached + * \Spatie\Permission\Events\PermissionAttached + * \Spatie\Permission\Events\PermissionDetached + * + * To enable, set to true, and then create listeners to watch these events. + */ + 'events_enabled' => false, + + /* + * Teams Feature. + * When set to true the package implements teams using the 'team_foreign_key'. + * If you want the migrations to register the 'team_foreign_key', you must + * set this to true before doing the migration. + * If you already did the migration then you must make a new migration to also + * add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions' + * (view the latest version of this package's migration file) + */ + + 'teams' => false, + + /* + * The class to use to resolve the permissions team id + */ + 'team_resolver' => \Spatie\Permission\DefaultTeamResolver::class, + + /* + * Passport Client Credentials Grant + * When set to true the package will use Passports Client to check permissions + */ + + 'use_passport_client_credentials' => false, + + /* + * When set to true, the required permission names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => false, + + /* + * When set to true, the required role names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_role_in_exception' => false, + + /* + * By default wildcard permission lookups are disabled. + * See documentation to understand supported syntax. + */ + + 'enable_wildcard_permission' => false, + + /* + * The class to use for interpreting wildcard permissions. + * If you need to modify delimiters, override the class and specify its name here. + */ + // 'wildcard_permission' => Spatie\Permission\WildcardPermission::class, + + /* Cache-specific settings */ + + 'cache' => [ + + /* + * By default all permissions are cached for 24 hours to speed up performance. + * When permissions or roles are updated the cache is flushed automatically. + */ + + 'expiration_time' => \DateInterval::createFromDateString('24 hours'), + + /* + * The cache key used to store all permissions. + */ + + 'key' => 'spatie.permission.cache', + + /* + * You may optionally indicate a specific cache driver to use for permission and + * role caching using any of the `store` drivers listed in the cache.php config + * file. Using 'default' here means to use the `default` set in cache.php. + */ + + 'store' => 'default', + ], +]; diff --git a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php new file mode 100644 index 0000000..40ff706 --- /dev/null +++ b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php @@ -0,0 +1,33 @@ +id(); + $table->morphs('tokenable'); + $table->text('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamp('expires_at')->nullable()->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('personal_access_tokens'); + } +}; diff --git a/database/migrations/2025_07_14_015824_create_perizinan_status_table.php b/database/migrations/2025_07_14_015824_create_perizinan_status_table.php index 95a4c56..f6f7191 100644 --- a/database/migrations/2025_07_14_015824_create_perizinan_status_table.php +++ b/database/migrations/2025_07_14_015824_create_perizinan_status_table.php @@ -11,19 +11,19 @@ return new class extends Migration */ public function up(): void { - Schema::create('perizinan_status', function (Blueprint $table) { - $table->id(); - $table->string('kategori'); // pertek, amdal, etc - $table->string('status_id'); // ditolak, selesai, proses, total - $table->string('label'); // Izin Ditolak, Izin Selesai, etc - $table->integer('value'); // count value - $table->datetime('api_last_updated'); // from API last_updated field - $table->date('sync_date'); // date when this data was synced + Schema::create('PerizinanStatus', function (Blueprint $table) { + $table->id('PerizinanStatusID'); + $table->string('Kategori_ID'); // pertek, amdal, etc (identifier string) + $table->string('StatusID'); // ditolak, selesai, proses, total + $table->string('Label'); // Izin Ditolak, Izin Selesai, etc + $table->integer('Value'); // count value + $table->dateTime('ApiLastUpdated'); // from API last_updated field + $table->date('SyncDate'); // date when this data was synced $table->timestamps(); // Index untuk performa - $table->index(['kategori', 'status_id', 'sync_date']); - $table->unique(['kategori', 'status_id', 'sync_date']); // prevent duplicate data per day + $table->index(['Kategori_ID', 'StatusID', 'SyncDate']); + $table->unique(['Kategori_ID', 'StatusID', 'SyncDate']); // prevent duplicate data per day }); } @@ -32,6 +32,6 @@ return new class extends Migration */ public function down(): void { - Schema::dropIfExists('perizinan_status'); + Schema::dropIfExists('PerizinanStatus'); } }; diff --git a/database/migrations/2025_07_14_024445_create_fastest_permohonan_table.php b/database/migrations/2025_07_14_024445_create_fastest_permohonan_table.php index 8984c5f..fb755f9 100644 --- a/database/migrations/2025_07_14_024445_create_fastest_permohonan_table.php +++ b/database/migrations/2025_07_14_024445_create_fastest_permohonan_table.php @@ -11,20 +11,20 @@ return new class extends Migration */ public function up(): void { - Schema::create('fastest_permohonan', function (Blueprint $table) { - $table->id(); - $table->string('kategori'); // pertek, amdal, etc - $table->string('nama'); // nama izin from API - $table->integer('total'); // jumlah total izin from API - $table->string('durasi_pemohon')->nullable(); // durasi rata-rata pemohon from API - $table->string('durasi_petugas')->nullable(); // durasi rata-rata petugas from API - $table->datetime('api_last_updated'); // from API last_updated field - $table->date('sync_date'); // date when this data was synced + Schema::create('FastestPermohonan', function (Blueprint $table) { + $table->id('FastestPermohonanID'); + $table->string('Kategori_ID'); // pertek, amdal, etc (identifier string) + $table->string('Nama'); // nama izin from API + $table->integer('Total'); // jumlah total izin from API + $table->string('DurasiPemohon')->nullable(); // durasi rata-rata pemohon from API + $table->string('DurasiPetugas')->nullable(); // durasi rata-rata petugas from API + $table->dateTime('ApiLastUpdated'); // from API last_updated field + $table->date('SyncDate'); // date when this data was synced $table->timestamps(); // Index untuk performa - urutkan berdasarkan durasi dan total - $table->index(['kategori', 'sync_date']); - $table->index(['kategori', 'total']); // untuk sorting fastest + $table->index(['Kategori_ID', 'SyncDate']); + $table->index(['Kategori_ID', 'Total']); // untuk sorting fastest }); } @@ -33,6 +33,6 @@ return new class extends Migration */ public function down(): void { - Schema::dropIfExists('fastest_permohonan'); + Schema::dropIfExists('FastestPermohonan'); } }; diff --git a/database/migrations/2025_07_14_033547_create_terakhir_terbit_table.php b/database/migrations/2025_07_14_033547_create_terakhir_terbit_table.php index 3e9086e..b47fa1f 100644 --- a/database/migrations/2025_07_14_033547_create_terakhir_terbit_table.php +++ b/database/migrations/2025_07_14_033547_create_terakhir_terbit_table.php @@ -11,19 +11,19 @@ return new class extends Migration */ public function up(): void { - Schema::create('terakhir_terbit', function (Blueprint $table) { - $table->id(); - $table->string('kategori'); // pertek, amdal, etc - $table->string('nama_izin'); // nama izin dari API - $table->string('pemohon'); // nama pemohon dari API - $table->date('tanggal_terbit'); // tanggal terbit dari API - $table->datetime('api_last_updated'); // from API last_updated field - $table->date('sync_date'); // date when this data was synced + Schema::create('TerakhirTerbit', function (Blueprint $table) { + $table->id('TerakhirTerbitID'); + $table->string('Kategori_ID'); // pertek, amdal, dll (identifier string) + $table->string('NamaIzin'); // nama izin dari API + $table->string('Pemohon'); // nama pemohon dari API + $table->date('TanggalTerbit'); // tanggal terbit dari API + $table->dateTime('ApiLastUpdated'); // from API last_updated field + $table->date('SyncDate'); // date when this data was synced $table->timestamps(); // Index untuk performa - $table->index(['kategori', 'tanggal_terbit']); - $table->index(['kategori', 'sync_date']); + $table->index(['Kategori_ID', 'TanggalTerbit']); + $table->index(['Kategori_ID', 'SyncDate']); }); } @@ -32,6 +32,6 @@ return new class extends Migration */ public function down(): void { - Schema::dropIfExists('terakhir_terbit'); + Schema::dropIfExists('TerakhirTerbit'); } }; diff --git a/database/migrations/2025_09_10_000000_add_username_to_users_table.php b/database/migrations/2025_09_10_000000_add_username_to_users_table.php new file mode 100644 index 0000000..fa4bcbb --- /dev/null +++ b/database/migrations/2025_09_10_000000_add_username_to_users_table.php @@ -0,0 +1,24 @@ +string('username')->nullable()->unique()->after('email'); + }); + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropUnique(['username']); + $table->dropColumn('username'); + }); + } +}; + diff --git a/database/migrations/2025_09_10_152414_create_permission_tables.php b/database/migrations/2025_09_10_152414_create_permission_tables.php new file mode 100644 index 0000000..ce4d9d2 --- /dev/null +++ b/database/migrations/2025_09_10_152414_create_permission_tables.php @@ -0,0 +1,136 @@ +engine('InnoDB'); + $table->bigIncrements('id'); // permission id + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { + // $table->engine('InnoDB'); + $table->bigIncrements('id'); // role id + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + + Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { + $table->unsignedBigInteger($pivotPermission); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } else { + $table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + + }); + + Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { + $table->unsignedBigInteger($pivotRole); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } else { + $table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } + }); + + Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { + $table->unsignedBigInteger($pivotPermission); + $table->unsignedBigInteger($pivotRole); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + + $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $tableNames = config('permission.table_names'); + + if (empty($tableNames)) { + throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + } + + Schema::drop($tableNames['role_has_permissions']); + Schema::drop($tableNames['model_has_roles']); + Schema::drop($tableNames['model_has_permissions']); + Schema::drop($tableNames['roles']); + Schema::drop($tableNames['permissions']); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index d01a0ef..0ac5283 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -3,6 +3,8 @@ namespace Database\Seeders; use App\Models\User; +use Database\Seeders\UserSeeder; +use Database\Seeders\RolesAndPermissionsSeeder; // use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; @@ -13,11 +15,10 @@ class DatabaseSeeder extends Seeder */ public function run(): void { - // User::factory(10)->create(); - - User::factory()->create([ - 'name' => 'Test User', - 'email' => 'test@example.com', + // panggil seeder khusus + $this->call([ + RolesAndPermissionsSeeder::class, + UserSeeder::class, ]); } } diff --git a/database/seeders/RolesAndPermissionsSeeder.php b/database/seeders/RolesAndPermissionsSeeder.php new file mode 100644 index 0000000..d45240c --- /dev/null +++ b/database/seeders/RolesAndPermissionsSeeder.php @@ -0,0 +1,76 @@ +forgetCachedPermissions(); + + $permissions = [ + // Dashboard tabs + 'dashboard.view.pertek', + 'dashboard.view.rintek', + 'dashboard.view.amdal', + 'dashboard.view.izin_angkut', + 'dashboard.view.uji_emisi', + + // Menus + 'penjadwalan.access', + 'persetujuan_teknis.access', + 'rincian_teknis.access', + 'persetujuan_lingkungan.access', + 'izin_angkut_olah.access', + 'izin_tempat_uji_emisi.access', + + // Management + 'settings.manage', + 'content.manage', + 'master_data.manage', + ]; + + foreach ($permissions as $perm) { + Permission::firstOrCreate(['name' => $perm]); + } + + // Roles + $kadis = Role::firstOrCreate(['name' => 'Kadis']); + $ppkl = Role::firstOrCreate(['name' => 'PPKL']); + $dlh = Role::firstOrCreate(['name' => 'DLH']); + + // Kadis permissions (semua dashboard tabs + modules, tanpa pengaturan/konten/master data) + $kadisPerms = [ + 'dashboard.view.pertek', + 'dashboard.view.rintek', + 'dashboard.view.amdal', + 'dashboard.view.izin_angkut', + 'dashboard.view.uji_emisi', + 'penjadwalan.access', + 'persetujuan_teknis.access', + 'rincian_teknis.access', + 'persetujuan_lingkungan.access', + 'izin_angkut_olah.access', + 'izin_tempat_uji_emisi.access', + ]; + $kadis->syncPermissions($kadisPerms); + + // PPKL permissions (dashboard pertek + penjadwalan + persetujuan teknis) + $ppklPerms = [ + 'dashboard.view.pertek', + 'penjadwalan.access', + 'persetujuan_teknis.access', + ]; + $ppkl->syncPermissions($ppklPerms); + + // DLH (super admin) -> assign all permissions + $dlh->syncPermissions(Permission::all()); + + app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions(); + } +} + diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php new file mode 100644 index 0000000..6f1d44b --- /dev/null +++ b/database/seeders/UserSeeder.php @@ -0,0 +1,47 @@ + 'ammar@example.com'], + [ + 'name' => 'Ammar', + 'username' => 'ammar', + 'password' => Hash::make('Muammar123$'), + ] + ); + + // Assign DLH (super admin) role + $user->syncRoles(['DLH']); + + // Kadis user + $kadis = User::updateOrCreate( + ['email' => 'kadis@dinaslhdki.id'], + [ + 'name' => 'Kadis', + 'username' => 'kadis', + 'password' => Hash::make('Perling2025$'), + ] + ); + $kadis->syncRoles(['Kadis']); + + // PPKL user (note: domain as provided) + $ppkl = User::updateOrCreate( + ['email' => 'ppkl@dinaslhkdki.id'], + [ + 'name' => 'PPKL', + 'username' => 'ppkl', + 'password' => Hash::make('Perling2025$'), + ] + ); + $ppkl->syncRoles(['PPKL']); + } +} diff --git a/resources/views/admin/permissions/create.blade.php b/resources/views/admin/permissions/create.blade.php new file mode 100644 index 0000000..b2c5547 --- /dev/null +++ b/resources/views/admin/permissions/create.blade.php @@ -0,0 +1,24 @@ +@extends('layout.layout') + +@section('content') +
+
+
Tambah Permission
+ + @if ($errors->any()) +
    @foreach ($errors->all() as $error)
  • {{ $error }}
  • @endforeach
+ @endif + +
+ @csrf +
+ + +
+ + Batal +
+
+
+@endsection + diff --git a/resources/views/admin/permissions/edit.blade.php b/resources/views/admin/permissions/edit.blade.php new file mode 100644 index 0000000..793f2e6 --- /dev/null +++ b/resources/views/admin/permissions/edit.blade.php @@ -0,0 +1,25 @@ +@extends('layout.layout') + +@section('content') +
+
+
Edit Permission
+ + @if ($errors->any()) +
    @foreach ($errors->all() as $error)
  • {{ $error }}
  • @endforeach
+ @endif + +
+ @csrf + @method('PUT') +
+ + +
+ + Batal +
+
+
+@endsection + diff --git a/resources/views/admin/permissions/index.blade.php b/resources/views/admin/permissions/index.blade.php new file mode 100644 index 0000000..8ec64f6 --- /dev/null +++ b/resources/views/admin/permissions/index.blade.php @@ -0,0 +1,45 @@ +@extends('layout.layout') + +@section('content') +
+
+
+
Permissions
+ Tambah Permission +
+ + @if(session('success')) +
{{ session('success') }}
+ @endif + +
+ + + + + + + + + @foreach($permissions as $permission) + + + + + @endforeach + +
NamaAksi
{{ $permission->name }} + Edit +
+ @csrf + @method('DELETE') + +
+
+
+ + {{ $permissions->links() }} +
+
+@endsection + diff --git a/resources/views/admin/roles/create.blade.php b/resources/views/admin/roles/create.blade.php new file mode 100644 index 0000000..f90a3bd --- /dev/null +++ b/resources/views/admin/roles/create.blade.php @@ -0,0 +1,62 @@ +@extends('layout.layout') + +@php + $title = 'Tambah Role'; + $subTitle = 'Manajemen Roles Akses'; +@endphp + +@section('content') + +
+
+
+
+
Tambah Role
+
+ +
+ + @if ($errors->any()) +
    @foreach ($errors->all() as $error)
  • {{ $error }}
  • @endforeach
+ @endif + +
+ @csrf +
+
+ + +
+
+ +
+ +
+ @foreach($permissions as $perm) +
+
+ + +
+
+ @endforeach +
+
+ +
+ + Batal +
+
+
+
+ +@endsection diff --git a/resources/views/admin/roles/edit.blade.php b/resources/views/admin/roles/edit.blade.php new file mode 100644 index 0000000..395161c --- /dev/null +++ b/resources/views/admin/roles/edit.blade.php @@ -0,0 +1,63 @@ +@extends('layout.layout') + +@php + $title = 'Edit Role'; + $subTitle = 'Manajemen Roles Akses'; +@endphp + +@section('content') + +
+
+
+
+
Edit Role: {{ $role->name }}
+
+ +
+ + @if ($errors->any()) +
    @foreach ($errors->all() as $error)
  • {{ $error }}
  • @endforeach
+ @endif + +
+ @csrf + @method('PUT') +
+
+ + +
+
+ +
+ +
+ @foreach($permissions as $perm) +
+
+ name, $rolePermissions) ? 'checked' : '' }}> + +
+
+ @endforeach +
+
+ +
+ + Batal +
+
+
+
+ +@endsection diff --git a/resources/views/admin/roles/index.blade.php b/resources/views/admin/roles/index.blade.php new file mode 100644 index 0000000..fa52c99 --- /dev/null +++ b/resources/views/admin/roles/index.blade.php @@ -0,0 +1,112 @@ +@extends('layout.layout') + +@php + $title = 'Daftar Roles'; + $subTitle = 'Manajemen Roles Akses'; + $script = ' + '; +@endphp + +@section('content') + +
+
+
+
+
Daftar Roles
+
+ +
+ + @if(session('success')) +
{{ session('success') }}
+ @endif + +
+ + + + + + + + + + + @foreach($roles as $role) + + + + + + + @endforeach + +
NoNama RoleJumlah PermissionsAksi
{{ isset($roles->firstItem) ? ($roles->firstItem() + $loop->index) : $loop->iteration }}{{ $role->name }}{{ $role->permissions_count }} +
+ + + + +
+
+
+
+
+ + + + +@endsection diff --git a/resources/views/auth/signin.blade.php b/resources/views/auth/signin.blade.php index 504b74d..72ab58d 100644 --- a/resources/views/auth/signin.blade.php +++ b/resources/views/auth/signin.blade.php @@ -4,8 +4,6 @@ - - @@ -22,11 +20,11 @@

Login

-

Silakan masuk menggunakan akun DLH

-
+

Silakan masuk menggunakan email/username

+ @csrf
- +
@@ -38,19 +36,19 @@
-
+
+ +
+ + {{--
- {{-- Forgot Password? --}}
-
- - {{-- --}} +
--}} +
@@ -70,6 +68,81 @@ icon.className = 'bi bi-eye'; } }); + + const form = document.getElementById('login-form'); + const btn = document.getElementById('btn-login'); + const errorBox = document.getElementById('login-error'); + + form.addEventListener('submit', async function (e) { + e.preventDefault(); + errorBox.style.display = 'none'; + errorBox.textContent = ''; + + const identifier = document.getElementById('identifier').value.trim(); + const password = document.getElementById('password').value; + if (!identifier || !password) return; + + // Validasi kompleksitas password (min 8, upper, lower, digit, special) + const strongPwd = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/; + if (!strongPwd.test(password)) { + errorBox.textContent = 'Password minimal 8 karakter dan harus mengandung huruf besar, huruf kecil, angka, dan simbol khusus.'; + errorBox.style.display = 'inline'; + return; + } + + const originalText = btn.textContent; + btn.disabled = true; + btn.textContent = 'Memproses...'; + + try { + const resp = await fetch('{{ url('/api/auth/login') }}', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify({ + identifier: identifier, + password: password, + device_name: 'web-portal' + }) + }); + + const data = await resp.json().catch(() => ({})); + + if (!resp.ok || !data.success) { + throw new Error((data && (data.message || (data.errors && (data.errors.identifier?.[0] || data.errors.password?.[0])))) || 'Login gagal'); + } + + // Simpan token (Catatan: localStorage memiliki risiko XSS; gunakan hati-hati) + localStorage.setItem('auth_token', data.token); + localStorage.setItem('auth_user', JSON.stringify(data.user || {})); + + // Start web session agar middleware permission berfungsi + const sessionResp = await fetch('{{ route('login.session') }}', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'X-CSRF-TOKEN': document.querySelector('input[name=_token]').value, + }, + body: JSON.stringify({ identifier, password }) + }); + + if (!sessionResp.ok) { + throw new Error('Gagal membuat sesi login web'); + } + + // Redirect ke dashboard + window.location.href = '{{ route('dashboard.index') }}'; + } catch (err) { + errorBox.textContent = err.message || 'Login gagal'; + errorBox.style.display = 'inline'; + } finally { + btn.disabled = false; + btn.textContent = originalText; + } + }); diff --git a/resources/views/components/frontend/navbar.blade.php b/resources/views/components/frontend/navbar.blade.php index 6705e4c..2a22484 100644 --- a/resources/views/components/frontend/navbar.blade.php +++ b/resources/views/components/frontend/navbar.blade.php @@ -117,12 +117,18 @@ - - - MASUK - - + + @auth + + DASHBOARD + + + @else + + MASUK + + + @endauth @@ -180,9 +186,15 @@
- - MASUK - + @auth + + DASHBOARD + + @else + + MASUK + + @endauth
diff --git a/resources/views/components/head.blade.php b/resources/views/components/head.blade.php index 44cd343..fd66f8e 100644 --- a/resources/views/components/head.blade.php +++ b/resources/views/components/head.blade.php @@ -3,6 +3,7 @@ {{ $title ?? 'Sistem Perizinan Lingkungan' }} + diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index 8e1e71a..24f4dd2 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -13,7 +13,7 @@
- +{{--
-
+ --}} - - + --}} + diff --git a/resources/views/components/sidebar.blade.php b/resources/views/components/sidebar.blade.php index 9248f91..b29a2b6 100644 --- a/resources/views/components/sidebar.blade.php +++ b/resources/views/components/sidebar.blade.php @@ -4,7 +4,7 @@ diff --git a/resources/views/dashboard/index.blade.php b/resources/views/dashboard/index.blade.php index 90d9a7c..b02016f 100644 --- a/resources/views/dashboard/index.blade.php +++ b/resources/views/dashboard/index.blade.php @@ -226,10 +226,10 @@ @forelse($pertekFastestData as $fastest) {{ $fastest['rank_order'] }} - {{ $fastest['nama'] }} - {{ number_format($fastest['total']) }} - {{ $fastest['durasi_pemohon'] }} - {{ $fastest['durasi_petugas'] }} + {{ $fastest['Nama'] }} + {{ number_format($fastest['Total']) }} + {{ $fastest['DurasiPemohon'] }} + {{ $fastest['DurasiPetugas'] }} @empty @@ -297,9 +297,9 @@ @forelse($pertekTerakhirTerbitData as $terakhir) {{ $terakhir['rank_order'] }} - {{ $terakhir['nama_izin'] }} - {{ $terakhir['pemohon'] }} - {{ \App\Helpers\DashboardHelper::formatTanggalTerbit($terakhir['tanggal_terbit']) }} + {{ $terakhir['NamaIzin'] }} + {{ $terakhir['Pemohon'] }} + {{ \App\Helpers\DashboardHelper::formatTanggalTerbit($terakhir['TanggalTerbit']) }} @empty @@ -821,10 +821,10 @@ @forelse($amdalFastestData as $fastest) {{ $fastest['rank_order'] }} - {{ $fastest['nama'] }} - {{ number_format($fastest['total']) }} - {{ $fastest['durasi_pemohon'] }} - {{ $fastest['durasi_petugas'] }} + {{ $fastest['Nama'] }} + {{ number_format($fastest['Total']) }} + {{ $fastest['DurasiPemohon'] }} + {{ $fastest['DurasiPetugas'] }} @empty @@ -892,9 +892,9 @@ @forelse($amdalTerakhirTerbitData as $terakhir) {{ $terakhir['rank_order'] }} - {{ $terakhir['nama_izin'] }} - {{ $terakhir['pemohon'] }} - {{ \App\Helpers\DashboardHelper::formatTanggalTerbit($terakhir['tanggal_terbit']) }} + {{ $terakhir['NamaIzin'] }} + {{ $terakhir['Pemohon'] }} + {{ \App\Helpers\DashboardHelper::formatTanggalTerbit($terakhir['TanggalTerbit']) }} @empty diff --git a/resources/views/errors/403.blade.php b/resources/views/errors/403.blade.php new file mode 100644 index 0000000..9c070c4 --- /dev/null +++ b/resources/views/errors/403.blade.php @@ -0,0 +1,120 @@ +@extends('layout.layout') + +@php + $title = '403 Forbidden'; + $subTitle = 'Akses Ditolak'; +@endphp + +@section('content') +
+
+
+
+ +
+
+ +
+
+
+
+ + +
+

403

+

Akses Ditolak

+

Anda tidak memiliki izin untuk mengakses halaman ini.

+

Silakan hubungi administrator jika diperlukan.

+
+ + + +
+
+
+
+ +@push('css') + +@endpush +@endsection diff --git a/resources/views/pengguna/index_user.blade.php b/resources/views/pengguna/index_user.blade.php index 7843efd..67b3b3a 100644 --- a/resources/views/pengguna/index_user.blade.php +++ b/resources/views/pengguna/index_user.blade.php @@ -1,7 +1,7 @@ @extends('layout.layout') @php - $title='Data Pengguna'; - $subTitle = 'Data Pengguna'; + $title='Users Grid'; + $subTitle = 'Users Grid'; $script ='