update: roles permissions

main
marszayn 2025-09-10 23:50:13 +07:00
parent e0fb72af83
commit 722115674f
43 changed files with 1938 additions and 288 deletions

View File

@ -14,21 +14,21 @@ class CheckPerizinanData extends Command
{ {
$this->info('📊 Checking synced perizinan data...'); $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()) { if ($data->isEmpty()) {
$this->warn('No data found in database'); $this->warn('No data found in database');
return; return;
} }
$groupedData = $data->groupBy('kategori'); $groupedData = $data->groupBy('Kategori_ID');
foreach ($groupedData as $kategori => $items) { foreach ($groupedData as $kategori => $items) {
$this->newLine(); $this->newLine();
$this->info("=== {$kategori} ==="); $this->info("=== {$kategori} ===");
foreach ($items as $item) { foreach ($items as $item) {
$this->line(" {$item->status_id}: {$item->label} = {$item->value}"); $this->line(" {$item->StatusID}: {$item->Label} = {$item->Value}");
} }
} }

View File

@ -123,7 +123,7 @@ class SyncPerizinanData extends Command
} }
$endTime = Carbon::now(); $endTime = Carbon::now();
$duration = $endTime->diffInSeconds($startTime); $duration = $endTime->diffInSeconds($startTime, true);
$this->info("🎉 Data synchronization completed in {$duration} seconds"); $this->info("🎉 Data synchronization completed in {$duration} seconds");

View File

@ -35,7 +35,7 @@ class TestProductionSetup extends Command
$this->info('2. Testing Database Table...'); $this->info('2. Testing Database Table...');
try { try {
$count = PerizinanStatus::count(); $count = PerizinanStatus::count();
$this->line("perizinan_status table exists with {$count} records"); $this->line("PerizinanStatus table exists with {$count} records");
} catch (\Exception $e) { } catch (\Exception $e) {
$this->error(' ❌ Database table issue: ' . $e->getMessage()); $this->error(' ❌ Database table issue: ' . $e->getMessage());
$allPassed = false; $allPassed = false;

View File

@ -64,10 +64,10 @@ class DashboardHelper
foreach ($dbData as $item) { foreach ($dbData as $item) {
$data[] = [ $data[] = [
'id' => $item->status_id, 'id' => $item->StatusID,
'label' => $item->label, 'label' => $item->Label,
'value' => $item->value, 'value' => $item->Value,
'color' => $colorMap[$item->status_id] ?? 'secondary' 'color' => $colorMap[$item->StatusID] ?? 'secondary'
]; ];
} }
@ -281,10 +281,10 @@ class DashboardHelper
$index = 1; $index = 1;
foreach ($dbData as $item) { foreach ($dbData as $item) {
$data[] = [ $data[] = [
'nama' => $item->nama, 'Nama' => $item->Nama,
'total' => $item->total, 'Total' => $item->Total,
'durasi_pemohon' => $item->durasi_pemohon, 'DurasiPemohon' => $item->DurasiPemohon,
'durasi_petugas' => $item->durasi_petugas, 'DurasiPetugas' => $item->DurasiPetugas,
'rank_order' => $index++ // Generate rank_order dinamis untuk display 'rank_order' => $index++ // Generate rank_order dinamis untuk display
]; ];
} }
@ -302,18 +302,18 @@ class DashboardHelper
{ {
$fallbackData = [ $fallbackData = [
'pertek' => [ '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 Pengelolaan Limbah B3', 'Total' => 45, 'DurasiPemohon' => '2 Hari 3 Jam', 'DurasiPetugas' => '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' => 'Izin Emisi Kendaraan', 'Total' => 67, 'DurasiPemohon' => '3 Hari 1 Jam', 'DurasiPetugas' => '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' => 'SPPL Industri', 'Total' => 23, 'DurasiPemohon' => '4 Hari 2 Jam', 'DurasiPetugas' => '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 Penyimpanan B3', 'Total' => 34, 'DurasiPemohon' => '5 Hari', 'DurasiPetugas' => '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 Pengolahan Limbah', 'Total' => 19, 'DurasiPemohon' => '6 Hari 4 Jam', 'DurasiPetugas' => '10 Hari 2 Jam', 'rank_order' => 5],
], ],
'amdal' => [ 'amdal' => [
['nama' => 'AMDAL Bangunan Tinggi', 'total' => 12, 'durasi_pemohon' => '15 Hari', 'durasi_petugas' => '30 Hari', 'rank_order' => 1], ['Nama' => 'AMDAL Bangunan Tinggi', 'Total' => 12, 'DurasiPemohon' => '15 Hari', 'DurasiPetugas' => '30 Hari', 'rank_order' => 1],
['nama' => 'AMDAL Infrastruktur', 'total' => 8, 'durasi_pemohon' => '18 Hari', 'durasi_petugas' => '35 Hari', 'rank_order' => 2], ['Nama' => 'AMDAL Infrastruktur', 'Total' => 8, 'DurasiPemohon' => '18 Hari', 'DurasiPetugas' => '35 Hari', 'rank_order' => 2],
['nama' => 'AMDAL Industri', 'total' => 15, 'durasi_pemohon' => '20 Hari', 'durasi_petugas' => '40 Hari', 'rank_order' => 3], ['Nama' => 'AMDAL Industri', 'Total' => 15, 'DurasiPemohon' => '20 Hari', 'DurasiPetugas' => '40 Hari', 'rank_order' => 3],
['nama' => 'AMDAL Perumahan', 'total' => 25, 'durasi_pemohon' => '22 Hari', 'durasi_petugas' => '42 Hari', 'rank_order' => 4], ['Nama' => 'AMDAL Perumahan', 'Total' => 25, 'DurasiPemohon' => '22 Hari', 'DurasiPetugas' => '42 Hari', 'rank_order' => 4],
['nama' => 'AMDAL Komersial', 'total' => 18, 'durasi_pemohon' => '25 Hari', 'durasi_petugas' => '45 Hari', 'rank_order' => 5], ['Nama' => 'AMDAL Komersial', 'Total' => 18, 'DurasiPemohon' => '25 Hari', 'DurasiPetugas' => '45 Hari', 'rank_order' => 5],
] ]
]; ];
@ -377,9 +377,9 @@ class DashboardHelper
$index = 1; $index = 1;
foreach ($dbData as $item) { foreach ($dbData as $item) {
$data[] = [ $data[] = [
'nama_izin' => $item->nama_izin, 'NamaIzin' => $item->NamaIzin,
'pemohon' => $item->pemohon, 'Pemohon' => $item->Pemohon,
'tanggal_terbit' => $item->tanggal_terbit, 'TanggalTerbit' => $item->TanggalTerbit,
'rank_order' => $index++ // Generate rank_order dinamis untuk display 'rank_order' => $index++ // Generate rank_order dinamis untuk display
]; ];
} }
@ -397,18 +397,18 @@ class DashboardHelper
{ {
$fallbackData = [ $fallbackData = [
'pertek' => [ 'pertek' => [
['nama_izin' => 'SERTIFIKAT LAIK OPERASI - PEMENUHAN BAKU MUTU EMISI', 'pemohon' => 'PT. MAJU BERSAMA', 'tanggal_terbit' => Carbon::parse('2025-07-15'), 'rank_order' => 1], ['NamaIzin' => 'SERTIFIKAT LAIK OPERASI - PEMENUHAN BAKU MUTU EMISI', 'Pemohon' => 'PT. MAJU BERSAMA', 'TanggalTerbit' => 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], ['NamaIzin' => 'PERSETUJUAN TEKNIS - PEMENUHAN BAKU MUTU AIR LIMBAH', 'Pemohon' => 'CV. KARYA MANDIRI', 'TanggalTerbit' => 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], ['NamaIzin' => 'SERTIFIKAT LAIK OPERASI - PEMENUHAN BAKU MUTU AIR LIMBAH', 'Pemohon' => 'PT. INDUSTRI SEJAHTERA', 'TanggalTerbit' => 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], ['NamaIzin' => 'PERSETUJUAN TEKNIS - PEMENUHAN BAKU MUTU AIR LIMBAH', 'Pemohon' => 'PT. TEKNOLOGI MODERN', 'TanggalTerbit' => 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' => 'CV. BERKAH JAYA', 'TanggalTerbit' => Carbon::parse('2025-07-11'), 'rank_order' => 5],
], ],
'amdal' => [ 'amdal' => [
['nama_izin' => 'AMDAL BANGUNAN TINGGI', 'pemohon' => 'PT. KONSTRUKSI PRIMA', 'tanggal_terbit' => Carbon::parse('2025-07-10'), 'rank_order' => 1], ['NamaIzin' => 'AMDAL BANGUNAN TINGGI', 'Pemohon' => 'PT. KONSTRUKSI PRIMA', 'TanggalTerbit' => 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], ['NamaIzin' => 'AMDAL INFRASTRUKTUR', 'Pemohon' => 'CV. INFRATEK', 'TanggalTerbit' => 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], ['NamaIzin' => 'AMDAL INDUSTRI', 'Pemohon' => 'PT. INDUSTRI MODERN', 'TanggalTerbit' => 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], ['NamaIzin' => 'AMDAL PERUMAHAN', 'Pemohon' => 'PT. PROPERTI SEJAHTERA', 'TanggalTerbit' => 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 KOMERSIAL', 'Pemohon' => 'CV. KOMERSIAL JAYA', 'TanggalTerbit' => Carbon::parse('2025-07-06'), 'rank_order' => 5],
] ]
]; ];

View File

@ -0,0 +1,55 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Spatie\Permission\Models\Permission;
class PermissionController extends Controller
{
public function index()
{
$permissions = Permission::orderBy('name')->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.');
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class RoleController extends Controller
{
public function index()
{
$roles = Role::withCount('permissions')->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.');
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
// =============== API (Sanctum Token) ===============
public function login(LoginRequest $request)
{
$data = $request->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');
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\Auth\LoginRequest;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
use Illuminate\Http\Request;
class WebAuthController extends Controller
{
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 logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
if ($request->expectsJson()) {
return response()->json(['success' => true]);
}
return redirect()->route('login.index');
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Foundation\Http\FormRequest;
class LoginRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'identifier' => ['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.',
];
}
}

View File

@ -7,22 +7,23 @@ use Carbon\Carbon;
class FastestPermohonan extends Model class FastestPermohonan extends Model
{ {
protected $table = 'fastest_permohonan'; protected $table = 'FastestPermohonan';
protected $primaryKey = 'FastestPermohonanID';
protected $fillable = [ protected $fillable = [
'kategori', 'Kategori_ID',
'nama', 'Nama',
'total', 'Total',
'durasi_pemohon', 'DurasiPemohon',
'durasi_petugas', 'DurasiPetugas',
'api_last_updated', 'ApiLastUpdated',
'sync_date' 'SyncDate'
]; ];
protected $casts = [ protected $casts = [
'api_last_updated' => 'datetime', 'ApiLastUpdated' => 'datetime',
'sync_date' => 'date', 'SyncDate' => 'date',
'total' => 'integer' 'Total' => 'integer'
]; ];
/** /**
@ -32,15 +33,15 @@ class FastestPermohonan extends Model
*/ */
public static function getLatestByKategori($kategori) public static function getLatestByKategori($kategori)
{ {
$latestSyncDate = self::where('kategori', $kategori)->max('sync_date'); $latestSyncDate = self::where('Kategori_ID', $kategori)->max('SyncDate');
if (!$latestSyncDate) { if (!$latestSyncDate) {
return collect(); return collect();
} }
return self::where('kategori', $kategori) return self::where('Kategori_ID', $kategori)
->whereDate('sync_date', $latestSyncDate) ->whereDate('SyncDate', $latestSyncDate)
->orderBy('id') // Keep API order by using insertion order ->orderBy('FastestPermohonanID') // Keep API order by using insertion order
->limit(5) ->limit(5)
->get(); ->get();
} }
@ -52,9 +53,9 @@ class FastestPermohonan extends Model
{ {
$date = $date ?: Carbon::today(); $date = $date ?: Carbon::today();
return self::where('kategori', $kategori) return self::where('Kategori_ID', $kategori)
->whereDate('sync_date', $date) ->whereDate('SyncDate', $date)
->orderBy('id') // Keep API order by using insertion order ->orderBy('FastestPermohonanID') // Keep API order by using insertion order
->limit(5) ->limit(5)
->get(); ->get();
} }

View File

@ -7,21 +7,22 @@ use Carbon\Carbon;
class PerizinanStatus extends Model class PerizinanStatus extends Model
{ {
protected $table = 'perizinan_status'; protected $table = 'PerizinanStatus';
protected $primaryKey = 'PerizinanStatusID';
protected $fillable = [ protected $fillable = [
'kategori', 'Kategori_ID',
'status_id', 'StatusID',
'label', 'Label',
'value', 'Value',
'api_last_updated', 'ApiLastUpdated',
'sync_date' 'SyncDate'
]; ];
protected $casts = [ protected $casts = [
'api_last_updated' => 'datetime', 'ApiLastUpdated' => 'datetime',
'sync_date' => 'date', 'SyncDate' => 'date',
'value' => 'integer' 'Value' => 'integer'
]; ];
/** /**
@ -30,18 +31,18 @@ class PerizinanStatus extends Model
*/ */
public static function getLatestByKategori($kategori) public static function getLatestByKategori($kategori)
{ {
// Find the most recent sync_date for the given kategori // Find the most recent SyncDate for the given kategori
$latestSyncDate = self::where('kategori', $kategori)->max('sync_date'); $latestSyncDate = self::where('Kategori_ID', $kategori)->max('SyncDate');
if (!$latestSyncDate) { if (!$latestSyncDate) {
// No data at all for this kategori // No data at all for this kategori
return collect(); return collect();
} }
return self::where('kategori', $kategori) return self::where('Kategori_ID', $kategori)
->whereDate('sync_date', $latestSyncDate) ->whereDate('SyncDate', $latestSyncDate)
->get() ->get()
->keyBy('status_id'); ->keyBy('StatusID');
} }
/** /**
@ -51,9 +52,9 @@ class PerizinanStatus extends Model
{ {
$date = $date ?: Carbon::today(); $date = $date ?: Carbon::today();
return self::where('kategori', $kategori) return self::where('Kategori_ID', $kategori)
->whereDate('sync_date', $date) ->whereDate('SyncDate', $date)
->get() ->get()
->keyBy('status_id'); ->keyBy('StatusID');
} }
} }

View File

@ -7,21 +7,22 @@ use Carbon\Carbon;
class TerakhirTerbit extends Model class TerakhirTerbit extends Model
{ {
protected $table = 'terakhir_terbit'; protected $table = 'TerakhirTerbit';
protected $primaryKey = 'TerakhirTerbitID';
protected $fillable = [ protected $fillable = [
'kategori', 'Kategori_ID',
'nama_izin', 'NamaIzin',
'pemohon', 'Pemohon',
'tanggal_terbit', 'TanggalTerbit',
'api_last_updated', 'ApiLastUpdated',
'sync_date' 'SyncDate'
]; ];
protected $casts = [ protected $casts = [
'tanggal_terbit' => 'date', 'TanggalTerbit' => 'date',
'api_last_updated' => 'datetime', 'ApiLastUpdated' => 'datetime',
'sync_date' => 'date' 'SyncDate' => 'date'
]; ];
/** /**
@ -30,15 +31,15 @@ class TerakhirTerbit extends Model
*/ */
public static function getLatestByKategori($kategori) public static function getLatestByKategori($kategori)
{ {
$latestSyncDate = self::where('kategori', $kategori)->max('sync_date'); $latestSyncDate = self::where('Kategori_ID', $kategori)->max('SyncDate');
if (!$latestSyncDate) { if (!$latestSyncDate) {
return collect(); return collect();
} }
return self::where('kategori', $kategori) return self::where('Kategori_ID', $kategori)
->whereDate('sync_date', $latestSyncDate) ->whereDate('SyncDate', $latestSyncDate)
->orderBy('tanggal_terbit', 'desc') ->orderBy('TanggalTerbit', 'desc')
->limit(5) ->limit(5)
->get(); ->get();
} }
@ -48,9 +49,9 @@ class TerakhirTerbit extends Model
*/ */
public static function getByKategoriAndDate($kategori, $date) public static function getByKategoriAndDate($kategori, $date)
{ {
return self::where('kategori', $kategori) return self::where('Kategori_ID', $kategori)
->whereDate('sync_date', $date) ->whereDate('SyncDate', $date)
->orderBy('tanggal_terbit', 'desc') ->orderBy('TanggalTerbit', 'desc')
->limit(5) ->limit(5)
->get(); ->get();
} }

View File

@ -4,13 +4,15 @@ namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail; // use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable class User extends Authenticatable
{ {
/** @use HasFactory<\Database\Factories\UserFactory> */ /** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable; use HasApiTokens, HasFactory, Notifiable, HasRoles;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
@ -20,6 +22,7 @@ class User extends Authenticatable
protected $fillable = [ protected $fillable = [
'name', 'name',
'email', 'email',
'username',
'password', 'password',
]; ];

View File

@ -94,19 +94,19 @@ class PerizinanApiService
foreach ($apiData['data'] as $item) { foreach ($apiData['data'] as $item) {
// Check if data already exists for today // Check if data already exists for today
$exists = PerizinanStatus::where('kategori', $kategori) $exists = PerizinanStatus::where('Kategori_ID', $kategori)
->where('status_id', $item['id']) ->where('StatusID', $item['id'])
->whereDate('sync_date', $syncDate) ->whereDate('SyncDate', $syncDate)
->exists(); ->exists();
if (!$exists) { if (!$exists) {
PerizinanStatus::create([ PerizinanStatus::create([
'kategori' => $kategori, 'Kategori_ID' => $kategori,
'status_id' => $item['id'], 'StatusID' => $item['id'],
'label' => $item['label'], 'Label' => $item['label'],
'value' => $item['value'], 'Value' => $item['value'],
'api_last_updated' => $apiLastUpdated, 'ApiLastUpdated' => $apiLastUpdated,
'sync_date' => $syncDate 'SyncDate' => $syncDate
]); ]);
$syncedCount++; $syncedCount++;
@ -199,19 +199,19 @@ class PerizinanApiService
$syncedCount = 0; $syncedCount = 0;
// Delete existing data for today to ensure we have fresh data // Delete existing data for today to ensure we have fresh data
FastestPermohonan::where('kategori', $kategori) FastestPermohonan::where('Kategori_ID', $kategori)
->whereDate('sync_date', $syncDate) ->whereDate('SyncDate', $syncDate)
->delete(); ->delete();
foreach ($apiData['data'] as $index => $item) { foreach ($apiData['data'] as $index => $item) {
FastestPermohonan::create([ FastestPermohonan::create([
'kategori' => $kategori, 'Kategori_ID' => $kategori,
'nama' => $item['nama'], 'Nama' => $item['nama'],
'total' => $item['total'], 'Total' => $item['total'],
'durasi_pemohon' => $item['durasi_pemohon'], 'DurasiPemohon' => $item['durasi_pemohon'],
'durasi_petugas' => $item['durasi_petugas'], 'DurasiPetugas' => $item['durasi_petugas'],
'api_last_updated' => $apiLastUpdated, 'ApiLastUpdated' => $apiLastUpdated,
'sync_date' => $syncDate 'SyncDate' => $syncDate
]); ]);
$syncedCount++; $syncedCount++;
@ -303,18 +303,18 @@ class PerizinanApiService
$syncedCount = 0; $syncedCount = 0;
// Delete existing data for today to ensure we have fresh ranking // Delete existing data for today to ensure we have fresh ranking
TerakhirTerbit::where('kategori', $kategori) TerakhirTerbit::where('Kategori_ID', $kategori)
->whereDate('sync_date', $syncDate) ->whereDate('SyncDate', $syncDate)
->delete(); ->delete();
foreach ($apiData['data'] as $index => $item) { foreach ($apiData['data'] as $index => $item) {
TerakhirTerbit::create([ TerakhirTerbit::create([
'kategori' => $kategori, 'Kategori_ID' => $kategori,
'nama_izin' => $item['nama_izin'], 'NamaIzin' => $item['nama_izin'],
'pemohon' => $item['pemohon'], 'Pemohon' => $item['pemohon'],
'tanggal_terbit' => Carbon::parse($item['tanggal_terbit']), 'TanggalTerbit' => Carbon::parse($item['tanggal_terbit']),
'api_last_updated' => $apiLastUpdated, 'ApiLastUpdated' => $apiLastUpdated,
'sync_date' => $syncDate 'SyncDate' => $syncDate
]); ]);
$syncedCount++; $syncedCount++;

View File

@ -3,15 +3,24 @@
use Illuminate\Foundation\Application; use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware; 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__)) return Application::configure(basePath: dirname(__DIR__))
->withRouting( ->withRouting(
web: __DIR__.'/../routes/web.php', web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php', commands: __DIR__.'/../routes/console.php',
health: '/up', health: '/up',
) )
->withMiddleware(function (Middleware $middleware) { ->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) { ->withExceptions(function (Exceptions $exceptions) {
// //

View File

@ -12,9 +12,11 @@
"php": "^8.2", "php": "^8.2",
"barryvdh/laravel-dompdf": "^3.1", "barryvdh/laravel-dompdf": "^3.1",
"laravel/framework": "^12.0", "laravel/framework": "^12.0",
"laravel/sanctum": "^4.2",
"laravel/tinker": "^2.10.1", "laravel/tinker": "^2.10.1",
"mallardduck/blade-lucide-icons": "^1.23", "mallardduck/blade-lucide-icons": "^1.23",
"spatie/laravel-html": "^3.12" "spatie/laravel-html": "^3.12",
"spatie/laravel-permission": "^6.21"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.23", "fakerphp/faker": "^1.23",

149
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "a630bee8cc849f41bc0261821020fefa", "content-hash": "324973c896fb611b576d446434dcb170",
"packages": [ "packages": [
{ {
"name": "barryvdh/laravel-dompdf", "name": "barryvdh/laravel-dompdf",
@ -1641,6 +1641,70 @@
}, },
"time": "2025-02-11T13:34:40+00:00" "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", "name": "laravel/serializable-closure",
"version": "v2.0.3", "version": "v2.0.3",
@ -3869,6 +3933,89 @@
], ],
"time": "2025-03-21T08:58:06+00:00" "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", "name": "symfony/clock",
"version": "v7.2.0", "version": "v7.2.0",

View File

@ -0,0 +1,202 @@
<?php
return [
'models' => [
/*
* 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',
],
];

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->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');
}
};

View File

@ -11,19 +11,19 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
Schema::create('perizinan_status', function (Blueprint $table) { Schema::create('PerizinanStatus', function (Blueprint $table) {
$table->id(); $table->id('PerizinanStatusID');
$table->string('kategori'); // pertek, amdal, etc $table->string('Kategori_ID'); // pertek, amdal, etc (identifier string)
$table->string('status_id'); // ditolak, selesai, proses, total $table->string('StatusID'); // ditolak, selesai, proses, total
$table->string('label'); // Izin Ditolak, Izin Selesai, etc $table->string('Label'); // Izin Ditolak, Izin Selesai, etc
$table->integer('value'); // count value $table->integer('Value'); // count value
$table->datetime('api_last_updated'); // from API last_updated field $table->dateTime('ApiLastUpdated'); // from API last_updated field
$table->date('sync_date'); // date when this data was synced $table->date('SyncDate'); // date when this data was synced
$table->timestamps(); $table->timestamps();
// Index untuk performa // Index untuk performa
$table->index(['kategori', 'status_id', 'sync_date']); $table->index(['Kategori_ID', 'StatusID', 'SyncDate']);
$table->unique(['kategori', 'status_id', 'sync_date']); // prevent duplicate data per day $table->unique(['Kategori_ID', 'StatusID', 'SyncDate']); // prevent duplicate data per day
}); });
} }
@ -32,6 +32,6 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('perizinan_status'); Schema::dropIfExists('PerizinanStatus');
} }
}; };

View File

@ -11,20 +11,20 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
Schema::create('fastest_permohonan', function (Blueprint $table) { Schema::create('FastestPermohonan', function (Blueprint $table) {
$table->id(); $table->id('FastestPermohonanID');
$table->string('kategori'); // pertek, amdal, etc $table->string('Kategori_ID'); // pertek, amdal, etc (identifier string)
$table->string('nama'); // nama izin from API $table->string('Nama'); // nama izin from API
$table->integer('total'); // jumlah total izin from API $table->integer('Total'); // jumlah total izin from API
$table->string('durasi_pemohon')->nullable(); // durasi rata-rata pemohon from API $table->string('DurasiPemohon')->nullable(); // durasi rata-rata pemohon from API
$table->string('durasi_petugas')->nullable(); // durasi rata-rata petugas from API $table->string('DurasiPetugas')->nullable(); // durasi rata-rata petugas from API
$table->datetime('api_last_updated'); // from API last_updated field $table->dateTime('ApiLastUpdated'); // from API last_updated field
$table->date('sync_date'); // date when this data was synced $table->date('SyncDate'); // date when this data was synced
$table->timestamps(); $table->timestamps();
// Index untuk performa - urutkan berdasarkan durasi dan total // Index untuk performa - urutkan berdasarkan durasi dan total
$table->index(['kategori', 'sync_date']); $table->index(['Kategori_ID', 'SyncDate']);
$table->index(['kategori', 'total']); // untuk sorting fastest $table->index(['Kategori_ID', 'Total']); // untuk sorting fastest
}); });
} }
@ -33,6 +33,6 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('fastest_permohonan'); Schema::dropIfExists('FastestPermohonan');
} }
}; };

View File

@ -11,19 +11,19 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
Schema::create('terakhir_terbit', function (Blueprint $table) { Schema::create('TerakhirTerbit', function (Blueprint $table) {
$table->id(); $table->id('TerakhirTerbitID');
$table->string('kategori'); // pertek, amdal, etc $table->string('Kategori_ID'); // pertek, amdal, dll (identifier string)
$table->string('nama_izin'); // nama izin dari API $table->string('NamaIzin'); // nama izin dari API
$table->string('pemohon'); // nama pemohon dari API $table->string('Pemohon'); // nama pemohon dari API
$table->date('tanggal_terbit'); // tanggal terbit dari API $table->date('TanggalTerbit'); // tanggal terbit dari API
$table->datetime('api_last_updated'); // from API last_updated field $table->dateTime('ApiLastUpdated'); // from API last_updated field
$table->date('sync_date'); // date when this data was synced $table->date('SyncDate'); // date when this data was synced
$table->timestamps(); $table->timestamps();
// Index untuk performa // Index untuk performa
$table->index(['kategori', 'tanggal_terbit']); $table->index(['Kategori_ID', 'TanggalTerbit']);
$table->index(['kategori', 'sync_date']); $table->index(['Kategori_ID', 'SyncDate']);
}); });
} }
@ -32,6 +32,6 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('terakhir_terbit'); Schema::dropIfExists('TerakhirTerbit');
} }
}; };

View File

@ -0,0 +1,24 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('username')->nullable()->unique()->after('email');
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropUnique(['username']);
$table->dropColumn('username');
});
}
};

View File

@ -0,0 +1,136 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$teams = config('permission.teams');
$tableNames = config('permission.table_names');
$columnNames = config('permission.column_names');
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
throw_if(empty($tableNames), new Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.'));
throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), new Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.'));
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
// $table->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']);
}
};

View File

@ -3,6 +3,8 @@
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\User; use App\Models\User;
use Database\Seeders\UserSeeder;
use Database\Seeders\RolesAndPermissionsSeeder;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents; // use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
@ -13,11 +15,10 @@ class DatabaseSeeder extends Seeder
*/ */
public function run(): void public function run(): void
{ {
// User::factory(10)->create(); // panggil seeder khusus
$this->call([
User::factory()->create([ RolesAndPermissionsSeeder::class,
'name' => 'Test User', UserSeeder::class,
'email' => 'test@example.com',
]); ]);
} }
} }

View File

@ -0,0 +1,76 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class RolesAndPermissionsSeeder extends Seeder
{
public function run(): void
{
app()[\Spatie\Permission\PermissionRegistrar::class]->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();
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
class UserSeeder extends Seeder
{
public function run(): void
{
$user = User::updateOrCreate(
['email' => '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']);
}
}

View File

@ -0,0 +1,24 @@
@extends('layout.layout')
@section('content')
<div class="card shadow-none border">
<div class="card-body p-24">
<h5 class="mb-3">Tambah Permission</h5>
@if ($errors->any())
<div class="alert alert-danger"><ul class="mb-0">@foreach ($errors->all() as $error)<li>{{ $error }}</li>@endforeach</ul></div>
@endif
<form method="POST" action="{{ route('admin.permissions.store') }}">
@csrf
<div class="mb-3">
<label class="form-label">Nama Permission</label>
<input type="text" name="name" class="form-control" required value="{{ old('name') }}">
</div>
<button class="btn btn-primary">Simpan</button>
<a href="{{ route('admin.permissions.index') }}" class="btn btn-secondary">Batal</a>
</form>
</div>
</div>
@endsection

View File

@ -0,0 +1,25 @@
@extends('layout.layout')
@section('content')
<div class="card shadow-none border">
<div class="card-body p-24">
<h5 class="mb-3">Edit Permission</h5>
@if ($errors->any())
<div class="alert alert-danger"><ul class="mb-0">@foreach ($errors->all() as $error)<li>{{ $error }}</li>@endforeach</ul></div>
@endif
<form method="POST" action="{{ route('admin.permissions.update', $permission) }}">
@csrf
@method('PUT')
<div class="mb-3">
<label class="form-label">Nama Permission</label>
<input type="text" name="name" class="form-control" required value="{{ old('name', $permission->name) }}">
</div>
<button class="btn btn-primary">Simpan</button>
<a href="{{ route('admin.permissions.index') }}" class="btn btn-secondary">Batal</a>
</form>
</div>
</div>
@endsection

View File

@ -0,0 +1,45 @@
@extends('layout.layout')
@section('content')
<div class="card shadow-none border">
<div class="card-body p-24">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">Permissions</h5>
<a href="{{ route('admin.permissions.create') }}" class="btn btn-primary btn-sm">Tambah Permission</a>
</div>
@if(session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Nama</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
@foreach($permissions as $permission)
<tr>
<td>{{ $permission->name }}</td>
<td>
<a href="{{ route('admin.permissions.edit', $permission) }}" class="btn btn-sm btn-outline-secondary">Edit</a>
<form action="{{ route('admin.permissions.destroy', $permission) }}" method="POST" class="d-inline" onsubmit="return confirm('Hapus permission ini?')">
@csrf
@method('DELETE')
<button class="btn btn-sm btn-outline-danger">Hapus</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
{{ $permissions->links() }}
</div>
</div>
@endsection

View File

@ -0,0 +1,62 @@
@extends('layout.layout')
@php
$title = 'Tambah Role';
$subTitle = 'Manajemen Roles Akses';
@endphp
@section('content')
<div class="card basic-data-table">
<div class="card-body">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-3 gap-3">
<div>
<h5 class="mb-0">Tambah Role</h5>
</div>
<div class="d-flex gap-2">
<a href="{{ route('admin.roles.index') }}" class="btn btn-secondary btn-sm d-flex align-items-center gap-2">
<iconify-icon icon="iconoir:arrow-left"></iconify-icon>
<span>Kembali</span>
</a>
</div>
</div>
@if ($errors->any())
<div class="alert alert-danger"><ul class="mb-0">@foreach ($errors->all() as $error)<li>{{ $error }}</li>@endforeach</ul></div>
@endif
<form method="POST" action="{{ route('admin.roles.store') }}">
@csrf
<div class="row g-3">
<div class="col-12 col-md-6">
<label class="form-label">Nama Role</label>
<input type="text" name="name" class="form-control" required value="{{ old('name') }}" placeholder="Contoh: Kadis, PPKL, DLH">
</div>
</div>
<div class="mt-4">
<label class="form-label">Permissions</label>
<div class="row g-2">
@foreach($permissions as $perm)
<div class="col-12 col-md-4">
<div class="form-check style-check d-flex align-items-center">
<input class="form-check-input border border-neutral-300 me-8" type="checkbox" name="permissions[]" value="{{ $perm->name }}" id="perm_{{ $perm->id }}">
<label class="form-check-label" for="perm_{{ $perm->id }}">{{ $perm->name }}</label>
</div>
</div>
@endforeach
</div>
</div>
<div class="mt-4 d-flex gap-2">
<button class="btn btn-primary d-flex align-items-center gap-2">
<iconify-icon icon="material-symbols:save"></iconify-icon>
<span>Simpan</span>
</button>
<a href="{{ route('admin.roles.index') }}" class="btn btn-light">Batal</a>
</div>
</form>
</div>
</div>
@endsection

View File

@ -0,0 +1,63 @@
@extends('layout.layout')
@php
$title = 'Edit Role';
$subTitle = 'Manajemen Roles Akses';
@endphp
@section('content')
<div class="card basic-data-table">
<div class="card-body">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-3 gap-3">
<div>
<h5 class="mb-0">Edit Role: {{ $role->name }}</h5>
</div>
<div class="d-flex gap-2">
<a href="{{ route('admin.roles.index') }}" class="btn btn-secondary btn-sm d-flex align-items-center gap-2">
<iconify-icon icon="iconoir:arrow-left"></iconify-icon>
<span>Kembali</span>
</a>
</div>
</div>
@if ($errors->any())
<div class="alert alert-danger"><ul class="mb-0">@foreach ($errors->all() as $error)<li>{{ $error }}</li>@endforeach</ul></div>
@endif
<form method="POST" action="{{ route('admin.roles.update', $role) }}">
@csrf
@method('PUT')
<div class="row g-3">
<div class="col-12 col-md-6">
<label class="form-label">Nama Role</label>
<input type="text" name="name" class="form-control" required value="{{ old('name', $role->name) }}" placeholder="Contoh: Kadis, PPKL, DLH">
</div>
</div>
<div class="mt-4">
<label class="form-label">Permissions</label>
<div class="row g-2">
@foreach($permissions as $perm)
<div class="col-12 col-md-4">
<div class="form-check style-check d-flex align-items-center">
<input class="form-check-input border border-neutral-300 me-8" type="checkbox" name="permissions[]" value="{{ $perm->name }}" id="perm_{{ $perm->id }}" {{ in_array($perm->name, $rolePermissions) ? 'checked' : '' }}>
<label class="form-check-label" for="perm_{{ $perm->id }}">{{ $perm->name }}</label>
</div>
</div>
@endforeach
</div>
</div>
<div class="mt-4 d-flex gap-2">
<button class="btn btn-primary d-flex align-items-center gap-2">
<iconify-icon icon="material-symbols:save"></iconify-icon>
<span>Simpan</span>
</button>
<a href="{{ route('admin.roles.index') }}" class="btn btn-light">Batal</a>
</div>
</form>
</div>
</div>
@endsection

View File

@ -0,0 +1,112 @@
@extends('layout.layout')
@php
$title = 'Daftar Roles';
$subTitle = 'Manajemen Roles Akses';
$script = '
<script>
let table = new DataTable("#dataTable");
// Handle delete modal
document.addEventListener("DOMContentLoaded", function() {
const deleteModal = document.getElementById("deleteModal");
const deleteForm = document.getElementById("deleteForm");
const roleNameText = document.getElementById("roleNameText");
deleteModal.addEventListener("show.bs.modal", function(event) {
const button = event.relatedTarget;
const roleName = button.getAttribute("data-role-name");
const roleId = button.getAttribute("data-role-id");
roleNameText.textContent = roleName;
deleteForm.action = "' . route('admin.roles.index') . '/" + roleId;
});
});
</script>';
@endphp
@section('content')
<div class="card basic-data-table">
<div class="card-body">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-3 gap-3">
<div>
<h5 class="mb-0">Daftar Roles</h5>
</div>
<div class="d-flex flex-column flex-sm-row align-items-stretch align-items-sm-center justify-content-end gap-2 w-md-auto">
<div class="flex-fill flex-sm-fill-0">
<a href="{{ route('admin.roles.create') }}" class="btn btn-primary btn-sm d-flex align-items-center justify-content-center gap-2 w-100">
<iconify-icon icon="material-symbols:add" class="text-lg"></iconify-icon>
<span>Tambah Role</span>
</a>
</div>
</div>
</div>
@if(session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
<div class="table-responsive">
<table class="table bordered-table mb-0" id="dataTable" data-page-length='10'>
<thead>
<tr>
<th scope="col" class="text-center">No</th>
<th scope="col" class="text-center">Nama Role</th>
<th scope="col" class="text-center">Jumlah Permissions</th>
<th scope="col" class="text-center">Aksi</th>
</tr>
</thead>
<tbody>
@foreach($roles as $role)
<tr>
<td class="text-center">{{ isset($roles->firstItem) ? ($roles->firstItem() + $loop->index) : $loop->iteration }}</td>
<td class="text-center">{{ $role->name }}</td>
<td class="text-center">{{ $role->permissions_count }}</td>
<td class="text-center">
<div class="d-flex align-items-center gap-10 justify-content-center">
<a href="{{ route('admin.roles.edit', $role) }}" class="bg-success-focus text-success-600 bg-hover-success-200 fw-medium w-40-px h-40-px d-flex justify-content-center align-items-center rounded-circle text-decoration-none" title="Edit Role">
<iconify-icon icon="lucide:edit" class="menu-icon"></iconify-icon>
</a>
<button type="button" class="remove-item-btn bg-danger-focus bg-hover-danger-200 text-danger-600 fw-medium w-40-px h-40-px d-flex justify-content-center align-items-center rounded-circle" title="Hapus Role" data-bs-toggle="modal" data-bs-target="#deleteModal" data-role-name="{{ $role->name }}" data-role-id="{{ $role->id }}">
<iconify-icon icon="fluent:delete-24-regular" class="menu-icon"></iconify-icon>
</button>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
<!-- Delete Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header border-0 pb-0">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center px-4 pb-4">
<div class="mb-3">
<div class="bg-danger-focus w-80-px h-80-px d-flex justify-content-center align-items-center rounded-circle mx-auto mb-3">
<iconify-icon icon="fluent:delete-24-regular" class="text-danger-600 text-4xl"></iconify-icon>
</div>
<h4 class="mb-2 text-dark">Hapus Role</h4>
<p class="text-secondary mb-0">Apakah Anda yakin ingin menghapus role <strong id="roleNameText"></strong>? Tindakan ini tidak dapat dibatalkan.</p>
</div>
<div class="d-flex gap-2 justify-content-center">
<button type="button" class="btn btn-light-secondary px-4" data-bs-dismiss="modal">Batal</button>
<form id="deleteForm" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger px-4">Ya, Hapus</button>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -4,8 +4,6 @@
<x-head/> <x-head/>
<link rel="stylesheet" href="{{ asset('assets/css/login/login.css') }}"> <link rel="stylesheet" href="{{ asset('assets/css/login/login.css') }}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css">
@ -22,11 +20,11 @@
<img src="{{ asset('assets/images/auth/logo-login.svg') }}" alt="Balai Sertifikasi Elektronik" class="logo"> <img src="{{ asset('assets/images/auth/logo-login.svg') }}" alt="Balai Sertifikasi Elektronik" class="logo">
<h1 class="text-login">Login</h1> <h1 class="text-login">Login</h1>
<p class="subtext">Silakan masuk menggunakan akun DLH</p> <p class="subtext">Silakan masuk menggunakan email/username</p>
<form action="#" method="POST" style="width: 100%;"> <form id="login-form" action="#" method="POST" style="width: 100%;">
@csrf @csrf
<div class="form-group"> <div class="form-group">
<input type="text" id="nik" name="nik" placeholder="NIK (No. KTP) / Email" required> <input type="text" id="identifier" name="identifier" placeholder="Email atau Username" required>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -38,19 +36,19 @@
</div> </div>
</div> </div>
<div class=""> <div class="mb-2">
<small id="login-error" class="text-danger" style="display:none;"></small>
</div>
{{-- <div class="">
<div class="d-flex justify-content-between gap-2"> <div class="d-flex justify-content-between gap-2">
<div class="form-check style-check d-flex align-items-center"> <div class="form-check style-check d-flex align-items-center">
<input class="form-check-input border border-neutral-300" type="checkbox" value="" id="remember"> <input class="form-check-input border border-neutral-300" type="checkbox" value="" id="remember">
<label class="form-check-label" for="remember">Ingat saya</label> <label class="form-check-label" for="remember">Ingat saya</label>
</div> </div>
{{-- <a href="javascript:void(0)" class="text-primary-600 fw-medium">Forgot Password?</a> --}}
</div> </div>
</div> </div> --}}
<a class="login-button" href="{{ route('dashboard.index') }}"> <button id="btn-login" type="submit" class="login-button">Login</button>
Login
</a>
{{-- <button type="submit" class="login-button">Log in</button> --}}
</form> </form>
</div> </div>
</div> </div>
@ -70,6 +68,81 @@
icon.className = 'bi bi-eye'; 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;
}
});
</script> </script>
</html> </html>

View File

@ -117,12 +117,18 @@
</a> </a>
</div> </div>
<!-- Login Button with enhanced style --> <!-- Login/Dashboard Button -->
<a href="/auth/login" class=" bg-green-500 ml-4 relative overflow-hidden group text-white px-6 py-2.5 rounded-full @auth
hover:shadow-lg"> <a href="{{ route('dashboard.index') }}" class=" bg-green-500 ml-4 relative overflow-hidden group text-white px-6 py-2.5 rounded-full hover:shadow-lg">
<span class="relative z-10">MASUK</span> <span class="relative z-10">DASHBOARD</span>
<span class="absolute left-0 bottom-0 w-0 h-full bg-blue-600 group-hover:w-full transition-all duration-300"></span> <span class="absolute left-0 bottom-0 w-0 h-full bg-blue-600 group-hover:w-full transition-all duration-300"></span>
</a> </a>
@else
<a href="{{ route('login.index') }}" class=" bg-green-500 ml-4 relative overflow-hidden group text-white px-6 py-2.5 rounded-full hover:shadow-lg">
<span class="relative z-10">MASUK</span>
<span class="absolute left-0 bottom-0 w-0 h-full bg-blue-600 group-hover:w-full transition-all duration-300"></span>
</a>
@endauth
</div> </div>
<!-- Mobile Menu Button --> <!-- Mobile Menu Button -->
@ -180,9 +186,15 @@
</div> </div>
<div class="mt-8"> <div class="mt-8">
<a href="#login" class="block text-center bg-blue-500 text-white px-6 py-3 rounded-full hover:bg-blue-600"> @auth
MASUK <a href="{{ route('dashboard.index') }}" class="block text-center bg-blue-500 text-white px-6 py-3 rounded-full hover:bg-blue-600">
</a> DASHBOARD
</a>
@else
<a href="{{ route('login.index') }}" class="block text-center bg-blue-500 text-white px-6 py-3 rounded-full hover:bg-blue-600">
MASUK
</a>
@endauth
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,6 +3,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ $title ?? 'Sistem Perizinan Lingkungan' }}</title> <title>{{ $title ?? 'Sistem Perizinan Lingkungan' }}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="csrf-token" content="{{ csrf_token() }}">
<!-- SEO Meta Tags --> <!-- SEO Meta Tags -->
<meta name="description" content="{{ $description ?? 'Sistem Perizinan Lingkungan Dinas Lingkungan Hidup Provinsi DKI Jakarta' }}" /> <meta name="description" content="{{ $description ?? 'Sistem Perizinan Lingkungan Dinas Lingkungan Hidup Provinsi DKI Jakarta' }}" />

View File

@ -13,7 +13,7 @@
</div> </div>
<div class="col-auto"> <div class="col-auto">
<div class="d-flex flex-wrap align-items-center gap-3"> <div class="d-flex flex-wrap align-items-center gap-3">
{{--
<div class="dropdown"> <div class="dropdown">
<button class="has-indicator w-40-px h-40-px bg-neutral-200 rounded-circle d-flex justify-content-center align-items-center" type="button" data-bs-toggle="dropdown"> <button class="has-indicator w-40-px h-40-px bg-neutral-200 rounded-circle d-flex justify-content-center align-items-center" type="button" data-bs-toggle="dropdown">
<iconify-icon icon="mage:email" class="text-primary-light text-xl"></iconify-icon> <iconify-icon icon="mage:email" class="text-primary-light text-xl"></iconify-icon>
@ -118,9 +118,9 @@
<a href="javascript:void(0)" class="text-primary-600 fw-semibold text-md">See All Message</a> <a href="javascript:void(0)" class="text-primary-600 fw-semibold text-md">See All Message</a>
</div> </div>
</div> </div>
</div><!-- Message dropdown end --> </div><!-- Message dropdown end --> --}}
<div class="dropdown"> {{-- <div class="dropdown">
<button class="has-indicator w-40-px h-40-px bg-neutral-200 rounded-circle d-flex justify-content-center align-items-center" type="button" data-bs-toggle="dropdown"> <button class="has-indicator w-40-px h-40-px bg-neutral-200 rounded-circle d-flex justify-content-center align-items-center" type="button" data-bs-toggle="dropdown">
<iconify-icon icon="iconoir:bell" class="text-primary-light text-xl"></iconify-icon> <iconify-icon icon="iconoir:bell" class="text-primary-light text-xl"></iconify-icon>
</button> </button>
@ -204,17 +204,17 @@
</div> </div>
</div> </div>
</div><!-- Notification dropdown end --> </div><!-- Notification dropdown end --> --}}
<div class="dropdown"> <div class="dropdown">
<button class="d-flex justify-content-center align-items-center rounded-circle" type="button" data-bs-toggle="dropdown"> <button class="d-flex justify-content-center align-items-center rounded-circle" type="button" data-bs-toggle="dropdown">
<img src="{{ asset('assets/images/user.png') }}" alt="image" class="w-40-px h-40-px object-fit-cover rounded-circle"> <iconify-icon icon="solar:user-linear" class="text-primary-light text-xl w-40-px h-40-px d-flex justify-content-center align-items-center bg-neutral-200 rounded-circle"></iconify-icon>
</button> </button>
<div class="dropdown-menu to-top dropdown-menu-sm"> <div class="dropdown-menu to-top dropdown-menu-sm">
<div class="py-12 px-16 radius-8 bg-primary-50 mb-16 d-flex align-items-center justify-content-between gap-2"> <div class="py-12 px-16 radius-8 bg-primary-50 mb-16 d-flex align-items-center justify-content-between gap-2">
<div> <div>
<h6 class="text-lg text-primary-light fw-semibold mb-2">Muammar</h6> <h6 class="text-lg text-primary-light fw-semibold mb-2">{{ auth()->user()->name ?? 'User' }}</h6>
<span class="text-secondary-light fw-medium text-sm">Admin</span> <span class="text-secondary-light fw-medium text-sm">{{ optional(auth()->user())->getRoleNames()->implode(', ') ?? '-' }}</span>
</div> </div>
</div> </div>
<ul class="to-top-list"> <ul class="to-top-list">
@ -224,7 +224,7 @@
</a> </a>
</li> </li>
<li> <li>
<a class="dropdown-item text-black px-0 py-8 hover-bg-transparent hover-text-danger d-flex align-items-center gap-3" href="javascript:void(0)"> <a id="btn-logout" class="dropdown-item text-black px-0 py-8 hover-bg-transparent hover-text-danger d-flex align-items-center gap-3" href="#">
<iconify-icon icon="lucide:power" class="icon text-xl"></iconify-icon> Log Out <iconify-icon icon="lucide:power" class="icon text-xl"></iconify-icon> Log Out
</a> </a>
</li> </li>
@ -236,3 +236,35 @@
</div> </div>
</div> </div>
</div> </div>
<script>
(function(){
const btn = document.getElementById('btn-logout');
if(!btn) return;
btn.addEventListener('click', async function(e){
e.preventDefault();
const token = localStorage.getItem('auth_token');
try {
if (token) {
await fetch('{{ url('/api/auth/logout') }}', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + token, 'Accept': 'application/json' }
}).catch(()=>{});
}
} catch (err) { /* ignore */ }
try {
await fetch('{{ route('logout.session') }}', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
'Accept': 'application/json'
}
}).catch(()=>{});
} catch (err) { /* ignore */ }
localStorage.removeItem('auth_token');
localStorage.removeItem('auth_user');
window.location.href = '{{ route('login.index') }}';
});
})();
</script>

View File

@ -4,7 +4,7 @@
</button> </button>
<div class="sidebar-logo-area"> <div class="sidebar-logo-area">
<a href="{{ route('dashboard.index') }}" class="sidebar-logo"> <a href="{{ route('dashboard.index') }}" class="sidebar-logo">
<img src="{{ asset('assets/images/asset/LOGO_PL.svg') }}" alt="site logo" class="light-logo"> <img src="{{ asset('assets/images/LOGO_PL.svg') }}" alt="site logo" class="light-logo">
<img src="{{ asset('assets/images/logo-light.png') }}" alt="site logo" class="dark-logo"> <img src="{{ asset('assets/images/logo-light.png') }}" alt="site logo" class="dark-logo">
<img src="{{ asset('assets/images/logo_only.svg') }}" alt="site logo" class="logo-icon"> <img src="{{ asset('assets/images/logo_only.svg') }}" alt="site logo" class="logo-icon">
</a> </a>
@ -13,19 +13,22 @@
<ul class="sidebar-menu" id="sidebar-menu"> <ul class="sidebar-menu" id="sidebar-menu">
<li> <li>
<a href="{{ route('dashboard.index') }}"> <a href="{{ route('dashboard.index') }}">
<i class="w-5 h-5" data-lucide="home"></i> <iconify-icon icon="solar:home-smile-angle-outline" class="menu-icon"></iconify-icon>
<span>DASHBOARD</span> <span>DASHBOARD</span>
</a> </a>
</li> </li>
@can('penjadwalan.access')
<li> <li>
<a href="{{ route('jadwal.index') }}"> <a href="{{ route('jadwal.index') }}">
<iconify-icon icon="bi:calendar-date" class="menu-icon"></iconify-icon> <iconify-icon icon="bi:calendar-date" class="menu-icon"></iconify-icon>
<span>PENJADWALAN</span> <span>PENJADWALAN</span>
</a> </a>
</li> </li>
@endcan
{{-- <li class="sidebar-menu-group-title">Persetujuan Teknis</li> --}} {{-- <li class="sidebar-menu-group-title">Persetujuan Teknis</li> --}}
@can('persetujuan_teknis.access')
<li class="dropdown"> <li class="dropdown">
<a href="javascript:void(0)"> <a href="javascript:void(0)">
<iconify-icon icon="bi:file-earmark-medical" class="menu-icon"></iconify-icon> <iconify-icon icon="bi:file-earmark-medical" class="menu-icon"></iconify-icon>
@ -65,9 +68,11 @@
</li> </li>
</ul> </ul>
</li> </li>
@endcan
{{-- <li class="sidebar-menu-group-title">Rincian Teknis</li> --}} {{-- <li class="sidebar-menu-group-title">Rincian Teknis</li> --}}
@can('rincian_teknis.access')
<li class="dropdown"> <li class="dropdown">
<a href="javascript:void(0)"> <a href="javascript:void(0)">
<iconify-icon icon="bi:card-text" class="menu-icon"></iconify-icon> <iconify-icon icon="bi:card-text" class="menu-icon"></iconify-icon>
@ -83,9 +88,11 @@
</li> </li>
</ul> </ul>
</li> </li>
@endcan
{{-- <li class="sidebar-menu-group-title">Persetujuan Lingkungan</li> --}} {{-- <li class="sidebar-menu-group-title">Persetujuan Lingkungan</li> --}}
@can('persetujuan_lingkungan.access')
<li class="dropdown"> <li class="dropdown">
<a href="javascript:void(0)"> <a href="javascript:void(0)">
<iconify-icon icon="bi:card-text" class="menu-icon"></iconify-icon> <iconify-icon icon="bi:card-text" class="menu-icon"></iconify-icon>
@ -143,7 +150,9 @@
</li> </li>
</ul> </ul>
</li> </li>
@endcan
@can('izin_angkut_olah.access')
<li class="dropdown"> <li class="dropdown">
<a href="javascript:void(0)"> <a href="javascript:void(0)">
<iconify-icon icon="bi:card-text" class="menu-icon"></iconify-icon> <iconify-icon icon="bi:card-text" class="menu-icon"></iconify-icon>
@ -159,7 +168,9 @@
</li> </li>
</ul> </ul>
</li> </li>
@endcan
@can('izin_tempat_uji_emisi.access')
<li class="dropdown"> <li class="dropdown">
<a href="javascript:void(0)"> <a href="javascript:void(0)">
<iconify-icon icon="bi:card-text" class="menu-icon"></iconify-icon> <iconify-icon icon="bi:card-text" class="menu-icon"></iconify-icon>
@ -175,8 +186,10 @@
</li> </li>
</ul> </ul>
</li> </li>
@endcan
@can('settings.manage')
<li class="sidebar-menu-group-title">Pengaturan</li> <li class="sidebar-menu-group-title">Pengaturan</li>
<li class="dropdown"> <li class="dropdown">
<a href="javascript:void(0)"> <a href="javascript:void(0)">
@ -192,15 +205,17 @@
</a> </a>
</li> </li>
<li> <li>
<a href="#"><i class="text-primary-600 w-auto"></i> <a href="{{ route('admin.roles.index') }}"><i class="text-primary-600 w-auto"></i>
<iconify-icon icon="bi:person-gear" class="menu-icon"></iconify-icon> <iconify-icon icon="bi:person-gear" class="menu-icon"></iconify-icon>
<span>Roles & Akses</span> <span>Roles & Akses</span>
</a> </a>
</li> </li>
</ul> </ul>
</li> </li>
@endcan
@can('content.manage')
<li class="sidebar-menu-group-title">Konten</li> <li class="sidebar-menu-group-title">Konten</li>
<li> <li>
<a href="{{ route('news.index_newsvideo') }}"> <a href="{{ route('news.index_newsvideo') }}">
@ -208,7 +223,9 @@
<span>News & Video</span> <span>News & Video</span>
</a> </a>
</li> </li>
@endcan
@can('master_data.manage')
<li class="sidebar-menu-group-title">Data Master</li> <li class="sidebar-menu-group-title">Data Master</li>
<li class="dropdown"> <li class="dropdown">
@ -250,6 +267,7 @@
</li> </li>
</ul> </ul>
</li> </li>
@endcan
</ul> </ul>
</div> </div>
</aside> </aside>

View File

@ -226,10 +226,10 @@
@forelse($pertekFastestData as $fastest) @forelse($pertekFastestData as $fastest)
<tr> <tr>
<td>{{ $fastest['rank_order'] }}</td> <td>{{ $fastest['rank_order'] }}</td>
<td class="text-sm">{{ $fastest['nama'] }}</td> <td class="text-sm">{{ $fastest['Nama'] }}</td>
<td class="text-sm d-none d-md-table-cell">{{ number_format($fastest['total']) }}</td> <td class="text-sm d-none d-md-table-cell">{{ number_format($fastest['Total']) }}</td>
<td class="text-sm d-none d-xl-table-cell">{{ $fastest['durasi_pemohon'] }}</td> <td class="text-sm d-none d-xl-table-cell">{{ $fastest['DurasiPemohon'] }}</td>
<td class="text-sm d-none d-xl-table-cell">{{ $fastest['durasi_petugas'] }}</td> <td class="text-sm d-none d-xl-table-cell">{{ $fastest['DurasiPetugas'] }}</td>
</tr> </tr>
@empty @empty
<tr> <tr>
@ -297,9 +297,9 @@
@forelse($pertekTerakhirTerbitData as $terakhir) @forelse($pertekTerakhirTerbitData as $terakhir)
<tr> <tr>
<td>{{ $terakhir['rank_order'] }}</td> <td>{{ $terakhir['rank_order'] }}</td>
<td class="text-sm">{{ $terakhir['nama_izin'] }}</td> <td class="text-sm">{{ $terakhir['NamaIzin'] }}</td>
<td class="text-sm d-none d-md-table-cell">{{ $terakhir['pemohon'] }}</td> <td class="text-sm d-none d-md-table-cell">{{ $terakhir['Pemohon'] }}</td>
<td class="text-sm d-none d-xl-table-cell">{{ \App\Helpers\DashboardHelper::formatTanggalTerbit($terakhir['tanggal_terbit']) }}</td> <td class="text-sm d-none d-xl-table-cell">{{ \App\Helpers\DashboardHelper::formatTanggalTerbit($terakhir['TanggalTerbit']) }}</td>
</tr> </tr>
@empty @empty
<tr> <tr>
@ -821,10 +821,10 @@
@forelse($amdalFastestData as $fastest) @forelse($amdalFastestData as $fastest)
<tr> <tr>
<td>{{ $fastest['rank_order'] }}</td> <td>{{ $fastest['rank_order'] }}</td>
<td class="text-sm">{{ $fastest['nama'] }}</td> <td class="text-sm">{{ $fastest['Nama'] }}</td>
<td class="text-sm">{{ number_format($fastest['total']) }}</td> <td class="text-sm">{{ number_format($fastest['Total']) }}</td>
<td class="text-sm d-none d-lg-table-cell">{{ $fastest['durasi_pemohon'] }}</td> <td class="text-sm d-none d-lg-table-cell">{{ $fastest['DurasiPemohon'] }}</td>
<td class="text-sm d-none d-lg-table-cell">{{ $fastest['durasi_petugas'] }}</td> <td class="text-sm d-none d-lg-table-cell">{{ $fastest['DurasiPetugas'] }}</td>
</tr> </tr>
@empty @empty
<tr> <tr>
@ -892,9 +892,9 @@
@forelse($amdalTerakhirTerbitData as $terakhir) @forelse($amdalTerakhirTerbitData as $terakhir)
<tr> <tr>
<td>{{ $terakhir['rank_order'] }}</td> <td>{{ $terakhir['rank_order'] }}</td>
<td class="text-sm">{{ $terakhir['nama_izin'] }}</td> <td class="text-sm">{{ $terakhir['NamaIzin'] }}</td>
<td class="text-sm">{{ $terakhir['pemohon'] }}</td> <td class="text-sm">{{ $terakhir['Pemohon'] }}</td>
<td class="text-sm d-none d-lg-table-cell">{{ \App\Helpers\DashboardHelper::formatTanggalTerbit($terakhir['tanggal_terbit']) }}</td> <td class="text-sm d-none d-lg-table-cell">{{ \App\Helpers\DashboardHelper::formatTanggalTerbit($terakhir['TanggalTerbit']) }}</td>
</tr> </tr>
@empty @empty
<tr> <tr>

View File

@ -0,0 +1,120 @@
@extends('layout.layout')
@php
$title = '403 Forbidden';
$subTitle = 'Akses Ditolak';
@endphp
@section('content')
<div class="row justify-content-center min-vh-100 align-items-center">
<div class="col-lg-6 col-md-8 col-12">
<div class="card border-0 shadow-sm">
<div class="card-body p-5 text-center">
<!-- Error Icon -->
<div class="mb-4">
<div class="error-icon-wrapper d-inline-block position-relative">
<iconify-icon icon="lucide:shield-x" class="error-icon text-danger"></iconify-icon>
<div class="pulse-ring"></div>
<div class="pulse-ring-2"></div>
</div>
</div>
<!-- Content -->
<div class="mb-4">
<h1 class="display-1 fw-bold text-danger mb-3">403</h1>
<h3 class="fw-semibold mb-3 text-dark">Akses Ditolak</h3>
<p class="text-muted mb-2">Anda tidak memiliki izin untuk mengakses halaman ini.</p>
<p class="text-muted small">Silakan hubungi administrator jika diperlukan.</p>
</div>
<!-- Action Button -->
<div class="d-flex flex-row justify-content-center">
<a href="{{ route('dashboard.index') }}" class="btn btn-primary px-4 py-2 d-flex align-items-center justify-content-center">
<iconify-icon icon="lucide:home" class="me-2"></iconify-icon>
<span>Kembali ke Dashboard</span>
</a>
</div>
</div>
</div>
</div>
</div>
@push('css')
<style>
.error-icon-wrapper {
position: relative;
display: inline-block;
}
.error-icon {
font-size: 64px;
animation: shake 2s ease-in-out infinite;
position: relative;
z-index: 2;
}
.pulse-ring, .pulse-ring-2 {
width: 100px;
height: 100px;
border: 2px solid #dc3545;
border-radius: 50%;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
animation: pulsate 2s ease-out infinite;
opacity: 0;
}
.pulse-ring-2 {
animation-delay: 1s;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-3px); }
20%, 40%, 60%, 80% { transform: translateX(3px); }
}
@keyframes pulsate {
0% {
transform: translate(-50%, -50%) scale(1);
opacity: 0.7;
}
100% {
transform: translate(-50%, -50%) scale(1.4);
opacity: 0;
}
}
.card {
border-radius: 12px;
}
.btn {
border-radius: 8px;
font-weight: 500;
transition: all 0.2s ease;
}
.btn:hover {
transform: translateY(-1px);
}
@media (max-width: 768px) {
.error-icon {
font-size: 48px;
}
.pulse-ring, .pulse-ring-2 {
width: 80px;
height: 80px;
}
.display-1 {
font-size: 4rem;
}
}
</style>
@endpush
@endsection

View File

@ -1,7 +1,7 @@
@extends('layout.layout') @extends('layout.layout')
@php @php
$title='Data Pengguna'; $title='Users Grid';
$subTitle = 'Data Pengguna'; $subTitle = 'Users Grid';
$script ='<script> $script ='<script>
$(".remove-item-btn").on("click", function() { $(".remove-item-btn").on("click", function() {
$(this).closest("tr").addClass("d-none") $(this).closest("tr").addClass("d-none")
@ -49,15 +49,17 @@
<tr> <tr>
<th scope="col"> <th scope="col">
<div class="d-flex align-items-center gap-10"> <div class="d-flex align-items-center gap-10">
{{-- <div class="form-check style-check d-flex align-items-center"> <div class="form-check style-check d-flex align-items-center">
<input class="form-check-input radius-4 border input-form-dark" type="checkbox" name="checkbox" id="selectAll"> <input class="form-check-input radius-4 border input-form-dark" type="checkbox" name="checkbox" id="selectAll">
</div> --}} </div>
No. S.L
</div> </div>
</th> </th>
<th scope="col">Join Date</th>
<th scope="col">Name</th> <th scope="col">Name</th>
<th scope="col">Email</th> <th scope="col">Email</th>
<th scope="col">Roles</th> <th scope="col">Department</th>
<th scope="col">Designation</th>
<th scope="col" class="text-center">Status</th> <th scope="col" class="text-center">Status</th>
<th scope="col" class="text-center">Action</th> <th scope="col" class="text-center">Action</th>
</tr> </tr>
@ -66,20 +68,24 @@
<tr> <tr>
<td> <td>
<div class="d-flex align-items-center gap-10"> <div class="d-flex align-items-center gap-10">
<div class="form-check style-check d-flex align-items-center">
<input class="form-check-input radius-4 border border-neutral-400" type="checkbox" name="checkbox">
</div>
01 01
</div> </div>
</td> </td>
<td>25 Jan 2024</td>
<td> <td>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<img src="https://img.freepik.com/premium-vector/user-profile-icon-flat-style-member-avatar-vector-illustration-isolated-background-human-permission-sign-business-concept_157943-15752.jpg?semt=ais_hybrid&w=740" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden"> <img src="{{ asset('assets/images/user-list/user-list1.png') }}" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden">
<div class="flex-grow-1"> <div class="flex-grow-1">
<span class="text-md mb-0 fw-normal text-secondary-light">Kathryn Murphy</span> <span class="text-md mb-0 fw-normal text-secondary-light">Kathryn Murphy</span>
</div> </div>
</div> </div>
</td> </td>
<td><span class="text-md mb-0 fw-normal text-secondary-light">osgoodwy@gmail.com</span></td> <td><span class="text-md mb-0 fw-normal text-secondary-light">osgoodwy@gmail.com</span></td>
<td>Sekretariat</td> <td>HR</td>
<td>Manager</td>
<td class="text-center"> <td class="text-center">
<span class="bg-success-focus text-success-600 border border-success-main px-24 py-4 radius-4 fw-medium text-sm">Active</span> <span class="bg-success-focus text-success-600 border border-success-main px-24 py-4 radius-4 fw-medium text-sm">Active</span>
</td> </td>
@ -100,21 +106,24 @@
<tr> <tr>
<td> <td>
<div class="d-flex align-items-center gap-10"> <div class="d-flex align-items-center gap-10">
<div class="form-check style-check d-flex align-items-center">
<input class="form-check-input radius-4 border border-neutral-400" type="checkbox" name="checkbox">
</div>
02 02
</div> </div>
</td> </td>
<td>25 Jan 2024</td>
<td> <td>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<img src="https://img.freepik.com/premium-vector/user-profile-icon-flat-style-member-avatar-vector-illustration-isolated-background-human-permission-sign-business-concept_157943-15752.jpg?semt=ais_hybrid&w=740" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden"> <img src="{{ asset('assets/images/user-list/user-list2.png') }}" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden">
<div class="flex-grow-1"> <div class="flex-grow-1">
<span class="text-md mb-0 fw-normal text-secondary-light">Annette Black</span> <span class="text-md mb-0 fw-normal text-secondary-light">Annette Black</span>
</div> </div>
</div> </div>
</td> </td>
<td><span class="text-md mb-0 fw-normal text-secondary-light">redaniel@gmail.com</span></td> <td><span class="text-md mb-0 fw-normal text-secondary-light">redaniel@gmail.com</span></td>
<td>Tim Teknis</td> <td>Design</td>
<td>UI UX Designer</td>
<td class="text-center"> <td class="text-center">
<span class="bg-neutral-200 text-neutral-600 border border-neutral-400 px-24 py-4 radius-4 fw-medium text-sm">Inactive</span> <span class="bg-neutral-200 text-neutral-600 border border-neutral-400 px-24 py-4 radius-4 fw-medium text-sm">Inactive</span>
</td> </td>
@ -135,21 +144,24 @@
<tr> <tr>
<td> <td>
<div class="d-flex align-items-center gap-10"> <div class="d-flex align-items-center gap-10">
<div class="form-check style-check d-flex align-items-center">
<input class="form-check-input radius-4 border border-neutral-400" type="checkbox" name="checkbox">
</div>
03 03
</div> </div>
</td> </td>
<td>10 Feb 2024</td>
<td> <td>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<img src="https://img.freepik.com/premium-vector/user-profile-icon-flat-style-member-avatar-vector-illustration-isolated-background-human-permission-sign-business-concept_157943-15752.jpg?semt=ais_hybrid&w=740" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden"> <img src="{{ asset('assets/images/user-list/user-list3.png') }}" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden">
<div class="flex-grow-1"> <div class="flex-grow-1">
<span class="text-md mb-0 fw-normal text-secondary-light">Ronald Richards</span> <span class="text-md mb-0 fw-normal text-secondary-light">Ronald Richards</span>
</div> </div>
</div> </div>
</td> </td>
<td><span class="text-md mb-0 fw-normal text-secondary-light">seannand@mail.ru</span></td> <td><span class="text-md mb-0 fw-normal text-secondary-light">seannand@mail.ru</span></td>
<td>Tim Teknis</td> <td>Design</td>
<td>UI UX Designer</td>
<td class="text-center"> <td class="text-center">
<span class="bg-success-focus text-success-600 border border-success-main px-24 py-4 radius-4 fw-medium text-sm">Active</span> <span class="bg-success-focus text-success-600 border border-success-main px-24 py-4 radius-4 fw-medium text-sm">Active</span>
</td> </td>
@ -171,21 +183,24 @@
<tr> <tr>
<td> <td>
<div class="d-flex align-items-center gap-10"> <div class="d-flex align-items-center gap-10">
<div class="form-check style-check d-flex align-items-center">
<input class="form-check-input radius-4 border border-neutral-400" type="checkbox" name="checkbox">
</div>
04 04
</div> </div>
</td> </td>
<td>10 Feb 2024</td>
<td> <td>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<img src="https://img.freepik.com/premium-vector/user-profile-icon-flat-style-member-avatar-vector-illustration-isolated-background-human-permission-sign-business-concept_157943-15752.jpg?semt=ais_hybrid&w=740" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden"> <img src="{{ asset('assets/images/user-list/user-list4.png') }}" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden">
<div class="flex-grow-1"> <div class="flex-grow-1">
<span class="text-md mb-0 fw-normal text-secondary-light">Eleanor Pena</span> <span class="text-md mb-0 fw-normal text-secondary-light">Eleanor Pena</span>
</div> </div>
</div> </div>
</td> </td>
<td><span class="text-md mb-0 fw-normal text-secondary-light">miyokoto@mail.ru</span></td> <td><span class="text-md mb-0 fw-normal text-secondary-light">miyokoto@mail.ru</span></td>
<td>Tim Teknis</td> <td>Design</td>
<td>UI UX Designer</td>
<td class="text-center"> <td class="text-center">
<span class="bg-success-focus text-success-600 border border-success-main px-24 py-4 radius-4 fw-medium text-sm">Active</span> <span class="bg-success-focus text-success-600 border border-success-main px-24 py-4 radius-4 fw-medium text-sm">Active</span>
</td> </td>
@ -207,21 +222,24 @@
<tr> <tr>
<td> <td>
<div class="d-flex align-items-center gap-10"> <div class="d-flex align-items-center gap-10">
<div class="form-check style-check d-flex align-items-center">
<input class="form-check-input radius-4 border border-neutral-400" type="checkbox" name="checkbox">
</div>
05 05
</div> </div>
</td> </td>
<td>15 March 2024</td>
<td> <td>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<img src="https://img.freepik.com/premium-vector/user-profile-icon-flat-style-member-avatar-vector-illustration-isolated-background-human-permission-sign-business-concept_157943-15752.jpg?semt=ais_hybrid&w=740" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden"> <img src="{{ asset('assets/images/user-list/user-list5.png') }}" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden">
<div class="flex-grow-1"> <div class="flex-grow-1">
<span class="text-md mb-0 fw-normal text-secondary-light">Leslie Alexander</span> <span class="text-md mb-0 fw-normal text-secondary-light">Leslie Alexander</span>
</div> </div>
</div> </div>
</td> </td>
<td><span class="text-md mb-0 fw-normal text-secondary-light">icadahli@gmail.com</span></td> <td><span class="text-md mb-0 fw-normal text-secondary-light">icadahli@gmail.com</span></td>
<td>Tim Teknis</td> <td>Design</td>
<td>UI UX Designer</td>
<td class="text-center"> <td class="text-center">
<span class="bg-neutral-200 text-neutral-600 border border-neutral-400 px-24 py-4 radius-4 fw-medium text-sm">Inactive</span> <span class="bg-neutral-200 text-neutral-600 border border-neutral-400 px-24 py-4 radius-4 fw-medium text-sm">Inactive</span>
</td> </td>
@ -243,21 +261,24 @@
<tr> <tr>
<td> <td>
<div class="d-flex align-items-center gap-10"> <div class="d-flex align-items-center gap-10">
<div class="form-check style-check d-flex align-items-center">
<input class="form-check-input radius-4 border border-neutral-400" type="checkbox" name="checkbox">
</div>
06 06
</div> </div>
</td> </td>
<td>15 March 2024</td>
<td> <td>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<img src="https://img.freepik.com/premium-vector/user-profile-icon-flat-style-member-avatar-vector-illustration-isolated-background-human-permission-sign-business-concept_157943-15752.jpg?semt=ais_hybrid&w=740" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden"> <img src="{{ asset('assets/images/user-list/user-list6.png') }}" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden">
<div class="flex-grow-1"> <div class="flex-grow-1">
<span class="text-md mb-0 fw-normal text-secondary-light">Albert Flores</span> <span class="text-md mb-0 fw-normal text-secondary-light">Albert Flores</span>
</div> </div>
</div> </div>
</td> </td>
<td><span class="text-md mb-0 fw-normal text-secondary-light">warn@mail.ru</span></td> <td><span class="text-md mb-0 fw-normal text-secondary-light">warn@mail.ru</span></td>
<td>Tim Teknis</td> <td>Design</td>
<td>UI UX Designer</td>
<td class="text-center"> <td class="text-center">
<span class="bg-success-focus text-success-600 border border-success-main px-24 py-4 radius-4 fw-medium text-sm">Active</span> <span class="bg-success-focus text-success-600 border border-success-main px-24 py-4 radius-4 fw-medium text-sm">Active</span>
</td> </td>
@ -279,21 +300,24 @@
<tr> <tr>
<td> <td>
<div class="d-flex align-items-center gap-10"> <div class="d-flex align-items-center gap-10">
<div class="form-check style-check d-flex align-items-center">
<input class="form-check-input radius-4 border border-neutral-400" type="checkbox" name="checkbox">
</div>
07 07
</div> </div>
</td> </td>
<td>27 April 2024</td>
<td> <td>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<img src="https://img.freepik.com/premium-vector/user-profile-icon-flat-style-member-avatar-vector-illustration-isolated-background-human-permission-sign-business-concept_157943-15752.jpg?semt=ais_hybrid&w=740" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden"> <img src="{{ asset('assets/images/user-list/user-list7.png') }}" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden">
<div class="flex-grow-1"> <div class="flex-grow-1">
<span class="text-md mb-0 fw-normal text-secondary-light">Jacob Jones</span> <span class="text-md mb-0 fw-normal text-secondary-light">Jacob Jones</span>
</div> </div>
</div> </div>
</td> </td>
<td><span class="text-md mb-0 fw-normal text-secondary-light">zitka@mail.ru</span></td> <td><span class="text-md mb-0 fw-normal text-secondary-light">zitka@mail.ru</span></td>
<td>Kepala Dinas</td> <td>Development</td>
<td>Frontend developer</td>
<td class="text-center"> <td class="text-center">
<span class="bg-success-focus text-success-600 border border-success-main px-24 py-4 radius-4 fw-medium text-sm">Active</span> <span class="bg-success-focus text-success-600 border border-success-main px-24 py-4 radius-4 fw-medium text-sm">Active</span>
</td> </td>
@ -315,21 +339,24 @@
<tr> <tr>
<td> <td>
<div class="d-flex align-items-center gap-10"> <div class="d-flex align-items-center gap-10">
<div class="form-check style-check d-flex align-items-center">
<input class="form-check-input radius-4 border border-neutral-400" type="checkbox" name="checkbox">
</div>
08 08
</div> </div>
</td> </td>
<td>25 Jan 2024</td>
<td> <td>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<img src="https://img.freepik.com/premium-vector/user-profile-icon-flat-style-member-avatar-vector-illustration-isolated-background-human-permission-sign-business-concept_157943-15752.jpg?semt=ais_hybrid&w=740" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden"> <img src="{{ asset('assets/images/user-list/user-list8.png') }}" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden">
<div class="flex-grow-1"> <div class="flex-grow-1">
<span class="text-md mb-0 fw-normal text-secondary-light">Jerome Bell</span> <span class="text-md mb-0 fw-normal text-secondary-light">Jerome Bell</span>
</div> </div>
</div> </div>
</td> </td>
<td><span class="text-md mb-0 fw-normal text-secondary-light">igerrin@gmail.com</span></td> <td><span class="text-md mb-0 fw-normal text-secondary-light">igerrin@gmail.com</span></td>
<td>Kepala Dinas</td> <td>Development</td>
<td>Frontend developer</td>
<td class="text-center"> <td class="text-center">
<span class="bg-neutral-200 text-neutral-600 border border-neutral-400 px-24 py-4 radius-4 fw-medium text-sm">Inactive</span> <span class="bg-neutral-200 text-neutral-600 border border-neutral-400 px-24 py-4 radius-4 fw-medium text-sm">Inactive</span>
</td> </td>
@ -351,21 +378,24 @@
<tr> <tr>
<td> <td>
<div class="d-flex align-items-center gap-10"> <div class="d-flex align-items-center gap-10">
<div class="form-check style-check d-flex align-items-center">
<input class="form-check-input radius-4 border border-neutral-400" type="checkbox" name="checkbox">
</div>
09 09
</div> </div>
</td> </td>
<td>30 April 2024</td>
<td> <td>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<img src="https://img.freepik.com/premium-vector/user-profile-icon-flat-style-member-avatar-vector-illustration-isolated-background-human-permission-sign-business-concept_157943-15752.jpg?semt=ais_hybrid&w=740" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden"> <img src="{{ asset('assets/images/user-list/user-list2.png') }}" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden">
<div class="flex-grow-1"> <div class="flex-grow-1">
<span class="text-md mb-0 fw-normal text-secondary-light">Marvin McKinney</span> <span class="text-md mb-0 fw-normal text-secondary-light">Marvin McKinney</span>
</div> </div>
</div> </div>
</td> </td>
<td><span class="text-md mb-0 fw-normal text-secondary-light">maka@yandex.ru</span></td> <td><span class="text-md mb-0 fw-normal text-secondary-light">maka@yandex.ru</span></td>
<td>Kepala Dinas</td> <td>Development</td>
<td>Frontend developer</td>
<td class="text-center"> <td class="text-center">
<span class="bg-success-focus text-success-600 border border-success-main px-24 py-4 radius-4 fw-medium text-sm">Active</span> <span class="bg-success-focus text-success-600 border border-success-main px-24 py-4 radius-4 fw-medium text-sm">Active</span>
</td> </td>
@ -387,21 +417,24 @@
<tr> <tr>
<td> <td>
<div class="d-flex align-items-center gap-10"> <div class="d-flex align-items-center gap-10">
<div class="form-check style-check d-flex align-items-center">
<input class="form-check-input radius-4 border border-neutral-400" type="checkbox" name="checkbox">
</div>
10 10
</div> </div>
</td> </td>
<td>30 April 2024</td>
<td> <td>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<img src="https://img.freepik.com/premium-vector/user-profile-icon-flat-style-member-avatar-vector-illustration-isolated-background-human-permission-sign-business-concept_157943-15752.jpg?semt=ais_hybrid&w=740" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden"> <img src="{{ asset('assets/images/user-list/user-list10.png') }}" alt="" class="w-40-px h-40-px rounded-circle flex-shrink-0 me-12 overflow-hidden">
<div class="flex-grow-1"> <div class="flex-grow-1">
<span class="text-md mb-0 fw-normal text-secondary-light">Cameron Williamson</span> <span class="text-md mb-0 fw-normal text-secondary-light">Cameron Williamson</span>
</div> </div>
</div> </div>
</td> </td>
<td><span class="text-md mb-0 fw-normal text-secondary-light">danten@mail.ru</span></td> <td><span class="text-md mb-0 fw-normal text-secondary-light">danten@mail.ru</span></td>
<td>Kepala Dinas</td> <td>Development</td>
<td>Frontend developer</td>
<td class="text-center"> <td class="text-center">
<span class="bg-success-focus text-success-600 border border-success-main px-24 py-4 radius-4 fw-medium text-sm">Active</span> <span class="bg-success-focus text-success-600 border border-success-main px-24 py-4 radius-4 fw-medium text-sm">Active</span>
</td> </td>

13
routes/api.php 100644
View File

@ -0,0 +1,13 @@
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AuthController;
Route::prefix('auth')->group(function () {
Route::post('/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () {
Route::post('/logout', [AuthController::class, 'logout']);
Route::post('/logout-all', [AuthController::class, 'logoutAll']);
Route::get('/me', [AuthController::class, 'me']);
});
});

View File

@ -6,9 +6,12 @@ use App\Http\Controllers\Izin\IzinAngkutController;
use App\Http\Controllers\Izin\IzinEmisiController; use App\Http\Controllers\Izin\IzinEmisiController;
use App\Http\Controllers\JadwalSidangController; use App\Http\Controllers\JadwalSidangController;
use App\Http\Controllers\LoginController; use App\Http\Controllers\LoginController;
use App\Http\Controllers\AuthController as WebAuthController;
use App\Http\Controllers\NewsController; use App\Http\Controllers\NewsController;
use App\Http\Controllers\PenugasanController; use App\Http\Controllers\PenugasanController;
use App\Http\Controllers\PerizinanLingkunganController; use App\Http\Controllers\PerizinanLingkunganController;
use App\Http\Controllers\Admin\RoleController;
use App\Http\Controllers\Admin\PermissionController;
use App\Http\Controllers\Persetujuan\AddendumController; use App\Http\Controllers\Persetujuan\AddendumController;
use App\Http\Controllers\Persetujuan\AmdalController; use App\Http\Controllers\Persetujuan\AmdalController;
use App\Http\Controllers\Persetujuan\DelhController; use App\Http\Controllers\Persetujuan\DelhController;
@ -60,45 +63,64 @@ Route::match(['get', 'post'], '/surat/exportPDF', [SuratArahanController::class,
// Dashboard // Dashboard
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard.index'); Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard.index');
Route::get('/dashboard-pertek', [DashboardController::class, 'pertek'])->name('dashboard.pertek'); Route::get('/dashboard-pertek', [DashboardController::class, 'pertek'])->middleware('permission:dashboard.view.pertek')->name('dashboard.pertek');
Route::get('/dashboard-rintek', [DashboardController::class, 'rintek'])->name('dashboard.rintek'); Route::get('/dashboard-rintek', [DashboardController::class, 'rintek'])->middleware('permission:dashboard.view.rintek')->name('dashboard.rintek');
Route::get('/dashboard-amdal', [DashboardController::class, 'amdal'])->name('dashboard.amdal'); Route::get('/dashboard-amdal', [DashboardController::class, 'amdal'])->middleware('permission:dashboard.view.amdal')->name('dashboard.amdal');
Route::get('/dashboard-bengkel', [DashboardController::class, 'bengkel'])->name('dashboard.bengkel'); Route::get('/dashboard-bengkel', [DashboardController::class, 'bengkel'])->middleware('permission:dashboard.view.uji_emisi')->name('dashboard.bengkel');
// login // login
Route::get('/auth/login', [LoginController::class, 'index'])->name('login.index'); Route::get('/auth/login', [LoginController::class, 'index'])->name('login.index');
Route::post('/auth/session-login', [WebAuthController::class, 'sessionLogin'])->name('login.session');
Route::post('/auth/logout', [WebAuthController::class, 'logout'])->name('logout.session');
// Roles & Permissions Management (restricted to settings.manage)
Route::prefix('admin')->middleware('permission:settings.manage')->group(function () {
Route::get('/roles', [RoleController::class, 'index'])->name('admin.roles.index');
Route::get('/roles/create', [RoleController::class, 'create'])->name('admin.roles.create');
Route::post('/roles', [RoleController::class, 'store'])->name('admin.roles.store');
Route::get('/roles/{role}/edit', [RoleController::class, 'edit'])->name('admin.roles.edit');
Route::put('/roles/{role}', [RoleController::class, 'update'])->name('admin.roles.update');
Route::delete('/roles/{role}', [RoleController::class, 'destroy'])->name('admin.roles.destroy');
Route::get('/permissions', [PermissionController::class, 'index'])->name('admin.permissions.index');
Route::get('/permissions/create', [PermissionController::class, 'create'])->name('admin.permissions.create');
Route::post('/permissions', [PermissionController::class, 'store'])->name('admin.permissions.store');
Route::get('/permissions/{permission}/edit', [PermissionController::class, 'edit'])->name('admin.permissions.edit');
Route::put('/permissions/{permission}', [PermissionController::class, 'update'])->name('admin.permissions.update');
Route::delete('/permissions/{permission}', [PermissionController::class, 'destroy'])->name('admin.permissions.destroy');
});
// Pertek // Pertek
Route::prefix('admin')->group(function () { Route::prefix('admin')->group(function () {
Route::get('/pertek/arahan', [PersetujuanTeknisController::class, 'index_arahan'])->name('pertek.index_arahan'); Route::get('/pertek/arahan', [PersetujuanTeknisController::class, 'index_arahan'])->middleware('permission:persetujuan_teknis.access')->name('pertek.index_arahan');
Route::get('/pertek/slo', [PersetujuanTeknisController::class, 'index_slo'])->name('pertek.index_slo'); Route::get('/pertek/slo', [PersetujuanTeknisController::class, 'index_slo'])->middleware('permission:persetujuan_teknis.access')->name('pertek.index_slo');
Route::get('/pertek/detail-slo', [PersetujuanTeknisController::class, 'detail_slo'])->name('pertek.detail_slo'); Route::get('/pertek/detail-slo', [PersetujuanTeknisController::class, 'detail_slo'])->middleware('permission:persetujuan_teknis.access')->name('pertek.detail_slo');
Route::get('/pertek/create-arahan', [PersetujuanTeknisController::class, 'create_arahan'])->name('pertek.create_arahan'); Route::get('/pertek/create-arahan', [PersetujuanTeknisController::class, 'create_arahan'])->middleware('permission:persetujuan_teknis.access')->name('pertek.create_arahan');
Route::get('/pertek/verifikator/arahan', [PersetujuanTeknisController::class, 'verifikator_arahan'])->name('pertek.verifikator_arahan'); Route::get('/pertek/verifikator/arahan', [PersetujuanTeknisController::class, 'verifikator_arahan'])->middleware('permission:persetujuan_teknis.access')->name('pertek.verifikator_arahan');
Route::get('/pertek/user/arahan', [PersetujuanTeknisController::class, 'user_arahan'])->name('pertek.user_arahan'); Route::get('/pertek/user/arahan', [PersetujuanTeknisController::class, 'user_arahan'])->middleware('permission:persetujuan_teknis.access')->name('pertek.user_arahan');
Route::get('/pertek/emisi', [PersetujuanTeknisController::class, 'index_emisi'])->name('pertek.index_emisi'); Route::get('/pertek/emisi', [PersetujuanTeknisController::class, 'index_emisi'])->middleware('permission:persetujuan_teknis.access')->name('pertek.index_emisi');
Route::get('/pertek/emisi/detail', [PersetujuanTeknisController::class, 'detail_emisi'])->name('pertek.detail_emisi'); Route::get('/pertek/emisi/detail', [PersetujuanTeknisController::class, 'detail_emisi'])->middleware('permission:persetujuan_teknis.access')->name('pertek.detail_emisi');
Route::get('/pertek/airlimbah', [PersetujuanTeknisController::class, 'index_airlimbah'])->name('pertek.index_airlimbah'); Route::get('/pertek/airlimbah', [PersetujuanTeknisController::class, 'index_airlimbah'])->middleware('permission:persetujuan_teknis.access')->name('pertek.index_airlimbah');
Route::get('/pertek/limbahb3', [PersetujuanTeknisController::class, 'index_limbahb3'])->name('pertek.index_limbahb3'); Route::get('/pertek/limbahb3', [PersetujuanTeknisController::class, 'index_limbahb3'])->middleware('permission:persetujuan_teknis.access')->name('pertek.index_limbahb3');
}); });
//rintek //rintek
Route::prefix('admin')->group(function () { Route::prefix('admin')->group(function () {
Route::get('/rintek/arahan', [RincianTeknisController::class, 'index_arahan'])->name('rintek.index_arahan'); Route::get('/rintek/arahan', [RincianTeknisController::class, 'index_arahan'])->middleware('permission:rincian_teknis.access')->name('rintek.index_arahan');
Route::get('/rintek/create-arahan', [RincianTeknisController::class, 'create_arahan'])->name('rintek.create_arahan'); Route::get('/rintek/create-arahan', [RincianTeknisController::class, 'create_arahan'])->middleware('permission:rincian_teknis.access')->name('rintek.create_arahan');
Route::get('/rintek/verifikator/arahan', [RincianTeknisController::class, 'verifikator_arahan'])->name('rintek.verifikator_arahan'); Route::get('/rintek/verifikator/arahan', [RincianTeknisController::class, 'verifikator_arahan'])->middleware('permission:rincian_teknis.access')->name('rintek.verifikator_arahan');
}); });
//izin angkut //izin angkut
Route::prefix('admin')->group(function () { Route::prefix('admin')->group(function () {
Route::get('/izinangkut', [IzinAngkutController::class, 'index_angkut'])->name('izinangkut.index_permohonan'); Route::get('/izinangkut', [IzinAngkutController::class, 'index_angkut'])->middleware('permission:izin_angkut_olah.access')->name('izinangkut.index_permohonan');
}); });
//izin angkut //izin angkut
Route::prefix('admin')->group(function () { Route::prefix('admin')->group(function () {
Route::get('/izinemisi', [IzinEmisiController::class, 'index_emisi'])->name('izinemisi.index_permohonan'); Route::get('/izinemisi', [IzinEmisiController::class, 'index_emisi'])->middleware('permission:izin_tempat_uji_emisi.access')->name('izinemisi.index_permohonan');
}); });
//rintek //rintek
@ -108,8 +130,8 @@ Route::prefix('admin')->group(function () {
// }); // });
Route::prefix('admin')->group(function () { Route::prefix('admin')->group(function () {
Route::get('/jadwal', [JadwalSidangController::class, 'index'])->name('jadwal.index'); Route::get('/jadwal', [JadwalSidangController::class, 'index'])->middleware('permission:penjadwalan.access')->name('jadwal.index');
Route::match(['get', 'post'], '/jadwal/create', [JadwalSidangController::class, 'create'])->name('jadwal.create'); Route::match(['get', 'post'], '/jadwal/create', [JadwalSidangController::class, 'create'])->middleware('permission:penjadwalan.access')->name('jadwal.create');
}); });
// Penugasan // Penugasan
@ -127,44 +149,42 @@ Route::prefix('admin')->group(function () {
}); });
Route::prefix('admin')->group(function () { Route::prefix('admin')->group(function () {
Route::get('/kerangka', [KerangkaController::class, 'index'])->name('persetujuan.kerangka.index'); Route::get('/kerangka', [KerangkaController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.kerangka.index');
Route::get('/kerangka/create', [KerangkaController::class, 'create'])->name('persetujuan.kerangka.create'); Route::get('/kerangka/create', [KerangkaController::class, 'create'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.kerangka.create');
}); });
Route::prefix('admin')->group(function () { Route::prefix('admin')->group(function () {
Route::get('/amdal', [AmdalController::class, 'index'])->name('persetujuan.amdal.index'); Route::get('/amdal', [AmdalController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.amdal.index');
Route::get('/amdal/detail', [AmdalController::class, 'detail'])->name('persetujuan.amdal.detail'); Route::get('/amdal/detail', [AmdalController::class, 'detail'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.amdal.detail');
}); });
Route::prefix('admin')->group(function () { Route::prefix('admin')->group(function () {
Route::get('/ukl', [UklController::class, 'index'])->name('persetujuan.ukl.index'); Route::get('/ukl', [UklController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.ukl.index');
Route::get('/ukl/detail', [UklController::class, 'detail'])->name('persetujuan.ukl.detail'); Route::get('/ukl/detail', [UklController::class, 'detail'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.ukl.detail');
}); });
Route::prefix('admin')->group(function () { Route::prefix('admin')->group(function () {
Route::get('/rkl', [RklController::class, 'index'])->name('persetujuan.rkl.index'); Route::get('/rkl', [RklController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.rkl.index');
Route::get('/rkl/detail', [RklController::class, 'detail'])->name('persetujuan.rkl.detail'); Route::get('/rkl/detail', [RklController::class, 'detail'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.rkl.detail');
}); });
Route::prefix('admin')->group(function () { Route::prefix('admin')->group(function () {
Route::get('/sppl', [SpplController::class, 'index'])->name('persetujuan.sppl.index'); Route::get('/sppl', [SpplController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.sppl.index');
Route::get('/sppl/detail', [SpplController::class, 'detail'])->name('persetujuan.sppl.detail'); Route::get('/sppl/detail', [SpplController::class, 'detail'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.sppl.detail');
}); });
Route::prefix('admin')->group(function () { Route::prefix('admin')->group(function () {
Route::get('/addendum', [AddendumController::class, 'index'])->name('persetujuan.addendum.index'); Route::get('/addendum', [AddendumController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.addendum.index');
Route::get('/addendum/detail', [AddendumController::class, 'detail'])->name('persetujuan.addendum.detail'); Route::get('/addendum/detail', [AddendumController::class, 'detail'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.addendum.detail');
}); });
Route::prefix('admin')->group(function () { Route::prefix('admin')->group(function () {
Route::get('/delh', [DelhController::class, 'index'])->name('persetujuan.delh.index'); Route::get('/delh', [DelhController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.delh.index');
Route::get('/delh/detail', [DelhController::class, 'detail'])->name('persetujuan.delh.detail'); Route::get('/delh/detail', [DelhController::class, 'detail'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.delh.detail');
}); });
Route::prefix('admin')->group(function () { Route::prefix('admin')->group(function () {
Route::get('/dplh', [DplhController::class, 'index'])->name('persetujuan.dplh.index'); Route::get('/dplh', [DplhController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.dplh.index');
Route::get('/dplh/detail', [DplhController::class, 'detail'])->name('persetujuan.dplh.detail'); Route::get('/dplh/detail', [DplhController::class, 'detail'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.dplh.detail');
}); });