From 0c134b9038cd9663e3ed7966d1b5ce089cff943a Mon Sep 17 00:00:00 2001 From: Saufi Date: Sat, 4 Jul 2026 14:45:16 +0800 Subject: [PATCH] taklimat ulangan+profil --- app/Http/Controllers/CheckinController.php | 17 + app/Http/Controllers/KaunterController.php | 2 +- .../Controllers/PasswordResetController.php | 67 + app/Http/Controllers/PenggunaController.php | 94 + app/Http/Controllers/PengurusController.php | 26 +- app/Http/Controllers/ProfilController.php | 37 + .../Controllers/SesiTaklimatController.php | 6 +- app/Models/User.php | 21 + .../ResetKataLaluanNotification.php | 38 + ...050801_add_kaunter_role_to_users_table.php | 28 + package-lock.json | 1656 +++++++++++++++++ public/css/app.css | 7 + resources/views/auth/login.blade.php | 2 + .../views/auth/lupa-katalaluan.blade.php | 25 + .../views/auth/set-kata-laluan.blade.php | 30 + .../views/checkin/pernah-hadir.blade.php | 30 + resources/views/layouts/app.blade.php | 14 +- resources/views/pengguna/create.blade.php | 82 + resources/views/pengguna/index.blade.php | 48 + resources/views/pengurus/senarai.blade.php | 55 +- resources/views/profil/edit.blade.php | 52 + resources/views/sesi/index.blade.php | 10 +- resources/views/sesi/show.blade.php | 14 +- routes/web.php | 48 +- 24 files changed, 2375 insertions(+), 34 deletions(-) create mode 100644 app/Http/Controllers/PasswordResetController.php create mode 100644 app/Http/Controllers/PenggunaController.php create mode 100644 app/Http/Controllers/ProfilController.php create mode 100644 app/Notifications/ResetKataLaluanNotification.php create mode 100644 database/migrations/2026_07_04_050801_add_kaunter_role_to_users_table.php create mode 100644 package-lock.json create mode 100644 resources/views/auth/lupa-katalaluan.blade.php create mode 100644 resources/views/auth/set-kata-laluan.blade.php create mode 100644 resources/views/checkin/pernah-hadir.blade.php create mode 100644 resources/views/pengguna/create.blade.php create mode 100644 resources/views/pengguna/index.blade.php create mode 100644 resources/views/profil/edit.blade.php diff --git a/app/Http/Controllers/CheckinController.php b/app/Http/Controllers/CheckinController.php index e56db11..9687ed7 100644 --- a/app/Http/Controllers/CheckinController.php +++ b/app/Http/Controllers/CheckinController.php @@ -59,6 +59,23 @@ class CheckinController extends Controller ])->onlyInput('no_kp'); } + // Pernah hadir di sesi taklimat LAIN? Taklimat ulangan hanya untuk + // petugas yang belum pernah hadir langsung — tak perlu rekod semula. + $rekodSesiLain = KehadiranTaklimat::where('permohonan_id', $petugas->id) + ->where('sesi_taklimat_id', '!=', $sesi->id) + ->with('sesi.dun') + ->orderBy('hadir_at') + ->first(); + + if ($rekodSesiLain) { + return view('checkin.pernah-hadir', [ + 'sesi' => $sesi, + 'petugas' => $petugas, + 'kehadiranSebelumIni' => $rekodSesiLain, + 'sesiSebelumIni' => $rekodSesiLain->sesi, + ]); + } + // Jika sudah daftar dalam sesi ini, papar semula slip sedia ada $kehadiran = KehadiranTaklimat::where('sesi_taklimat_id', $sesi->id) ->where('permohonan_id', $petugas->id) diff --git a/app/Http/Controllers/KaunterController.php b/app/Http/Controllers/KaunterController.php index e8c35d0..ab31753 100644 --- a/app/Http/Controllers/KaunterController.php +++ b/app/Http/Controllers/KaunterController.php @@ -66,7 +66,7 @@ class KaunterController extends Controller { $user = $request->user(); - if ($user->isPengurus() && (int) $sesi->dun_id !== (int) $user->dun_id) { + if (! $user->isAdmin() && (int) $sesi->dun_id !== (int) $user->dun_id) { abort(403, 'Sesi ini bukan untuk DUN anda.'); } } diff --git a/app/Http/Controllers/PasswordResetController.php b/app/Http/Controllers/PasswordResetController.php new file mode 100644 index 0000000..e9d2039 --- /dev/null +++ b/app/Http/Controllers/PasswordResetController.php @@ -0,0 +1,67 @@ +validate(['email' => ['required', 'email']]); + + $status = Password::sendResetLink($request->only('email')); + + return $status === Password::RESET_LINK_SENT + ? back()->with('status', 'Pautan set semula kata laluan telah dihantar ke e-mel anda.') + : back()->withErrors(['email' => $this->mesejRalat($status)]); + } + + public function showResetForm(Request $request, string $token) + { + return view('auth.set-kata-laluan', [ + 'token' => $token, + 'email' => $request->query('email', ''), + ]); + } + + public function reset(Request $request) + { + $data = $request->validate([ + 'token' => ['required', 'string'], + 'email' => ['required', 'email'], + 'password' => ['required', 'string', 'min:8', 'confirmed'], + ], [ + 'password.min' => 'Kata laluan perlu sekurang-kurangnya 8 aksara.', + 'password.confirmed' => 'Pengesahan kata laluan tidak sepadan.', + ]); + + $status = Password::reset( + $data, + function ($user, $password) { + $user->forceFill(['password' => Hash::make($password)])->save(); + } + ); + + return $status === Password::PASSWORD_RESET + ? redirect()->route('login')->with('status', 'Kata laluan berjaya ditetapkan semula. Sila log masuk.') + : back()->withErrors(['email' => $this->mesejRalat($status)])->onlyInput('email'); + } + + private function mesejRalat(string $status): string + { + return match ($status) { + Password::INVALID_USER => 'Akaun dengan e-mel ini tidak ditemui.', + Password::RESET_THROTTLED => 'Sila tunggu sebentar sebelum cuba lagi.', + Password::INVALID_TOKEN => 'Pautan set semula tidak sah atau telah tamat tempoh.', + default => 'Gagal memproses permintaan. Sila cuba lagi.', + }; + } +} diff --git a/app/Http/Controllers/PenggunaController.php b/app/Http/Controllers/PenggunaController.php new file mode 100644 index 0000000..7cb7b5e --- /dev/null +++ b/app/Http/Controllers/PenggunaController.php @@ -0,0 +1,94 @@ +user(); + + $pengguna = User::query() + ->when( + $user->isPengurus(), + fn ($q) => $q->where('dun_id', $user->dun_id)->whereIn('role', self::PERANAN_PENGURUS) + ) + ->with('dun') + ->orderBy('role') + ->orderBy('name') + ->get(); + + return view('pengguna.index', compact('pengguna')); + } + + public function create(Request $request) + { + $user = $request->user(); + + $senaraiDun = Dun::where('is_active', true) + ->when($user->isPengurus(), fn ($q) => $q->where('id', $user->dun_id)) + ->orderBy('code') + ->get(); + + $peranan = $user->isAdmin() ? self::PERANAN_ADMIN : self::PERANAN_PENGURUS; + + return view('pengguna.create', compact('senaraiDun', 'peranan')); + } + + public function store(Request $request) + { + $user = $request->user(); + $perananDibenarkan = $user->isAdmin() ? self::PERANAN_ADMIN : self::PERANAN_PENGURUS; + + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'email', 'max:255', 'unique:users,email'], + 'role' => ['required', 'string', Rule::in($perananDibenarkan)], + 'dun_id' => ['nullable', 'integer'], + 'password' => ['required', 'string', 'min:8', 'confirmed'], + ], [ + 'email.unique' => 'E-mel ini sudah didaftarkan.', + 'password.min' => 'Kata laluan perlu sekurang-kurangnya 8 aksara.', + 'password.confirmed' => 'Pengesahan kata laluan tidak sepadan.', + ]); + + if ($user->isPengurus()) { + // Pengurus hanya boleh daftar untuk DUN sendiri, tidak kira input. + $data['dun_id'] = $user->dun_id; + } elseif ($data['role'] === 'admin') { + $data['dun_id'] = null; + } elseif (empty($data['dun_id'])) { + return back()->withErrors(['dun_id' => 'Sila pilih DUN untuk peranan ini.'])->withInput(); + } + + if (! empty($data['dun_id']) && ! Dun::where('id', $data['dun_id'])->where('is_active', true)->exists()) { + return back()->withErrors(['dun_id' => 'DUN tidak sah.'])->withInput(); + } + + User::create([ + 'name' => $data['name'], + 'email' => $data['email'], + 'role' => $data['role'], + 'dun_id' => $data['dun_id'] ?? null, + 'password' => Hash::make($data['password']), + ]); + + return redirect()->route('pengguna.index')->with('status', 'Pengguna baharu berjaya didaftarkan.'); + } +} diff --git a/app/Http/Controllers/PengurusController.php b/app/Http/Controllers/PengurusController.php index 610b96c..fa50708 100644 --- a/app/Http/Controllers/PengurusController.php +++ b/app/Http/Controllers/PengurusController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Models\KehadiranTaklimat; use App\Models\SesiTaklimat; use App\Models\Sppm\PetugasPermohonan; use Illuminate\Http\Request; @@ -9,13 +10,15 @@ use Illuminate\Http\Request; class PengurusController extends Controller { /** - * Senarai petugas hadir dan tidak/belum hadir bagi satu sesi taklimat. + * Senarai petugas bagi satu sesi taklimat, dipecahkan kepada 3 kumpulan: + * hadir (sesi ini), pernah hadir (sesi taklimat lain), dan + * tidak/belum hadir langsung (tidak pernah hadir mana-mana sesi). */ public function senarai(Request $request, SesiTaklimat $sesi) { $user = $request->user(); - if ($user->isPengurus() && (int) $sesi->dun_id !== (int) $user->dun_id) { + if (! $user->isAdmin() && (int) $sesi->dun_id !== (int) $user->dun_id) { abort(403, 'Sesi ini bukan untuk DUN anda.'); } @@ -29,15 +32,28 @@ class PengurusController extends Controller $kehadiran = $sesi->kehadiran()->get()->keyBy('permohonan_id'); - [$hadir, $tidakHadir] = $semuaPetugas->partition( - fn (PetugasPermohonan $p) => $kehadiran->has($p->id) + $belumHadirSesiIni = $semuaPetugas->reject(fn (PetugasPermohonan $p) => $kehadiran->has($p->id)); + + // Antara yang belum hadir sesi ini, semak siapa pernah hadir di sesi LAIN. + $kehadiranSesiLain = KehadiranTaklimat::whereIn('permohonan_id', $belumHadirSesiIni->pluck('id')) + ->where('sesi_taklimat_id', '!=', $sesi->id) + ->with('sesi.dun') + ->orderBy('hadir_at') + ->get() + ->unique('permohonan_id') + ->keyBy('permohonan_id'); + + [$hadirSesiLain, $tidakHadir] = $belumHadirSesiIni->partition( + fn (PetugasPermohonan $p) => $kehadiranSesiLain->has($p->id) ); return view('pengurus.senarai', [ 'sesi' => $sesi, - 'hadir' => $hadir->values(), + 'hadir' => $semuaPetugas->filter(fn (PetugasPermohonan $p) => $kehadiran->has($p->id))->values(), + 'hadirSesiLain' => $hadirSesiLain->values(), 'tidakHadir' => $tidakHadir->values(), 'kehadiran' => $kehadiran, + 'kehadiranSesiLain' => $kehadiranSesiLain, ]); } } diff --git a/app/Http/Controllers/ProfilController.php b/app/Http/Controllers/ProfilController.php new file mode 100644 index 0000000..e93d269 --- /dev/null +++ b/app/Http/Controllers/ProfilController.php @@ -0,0 +1,37 @@ + $request->user()]); + } + + public function updatePassword(Request $request) + { + $data = $request->validate([ + 'kata_laluan_semasa' => ['required', 'string'], + 'kata_laluan_baharu' => ['required', 'string', 'min:8', 'confirmed'], + ], [ + 'kata_laluan_semasa.required' => 'Sila masukkan kata laluan semasa.', + 'kata_laluan_baharu.required' => 'Sila masukkan kata laluan baharu.', + 'kata_laluan_baharu.min' => 'Kata laluan baharu perlu sekurang-kurangnya 8 aksara.', + 'kata_laluan_baharu.confirmed' => 'Pengesahan kata laluan baharu tidak sepadan.', + ]); + + $user = $request->user(); + + if (! Hash::check($data['kata_laluan_semasa'], $user->password)) { + return back()->withErrors(['kata_laluan_semasa' => 'Kata laluan semasa tidak tepat.']); + } + + $user->update(['password' => Hash::make($data['kata_laluan_baharu'])]); + + return back()->with('status', 'Kata laluan berjaya dikemaskini.'); + } +} diff --git a/app/Http/Controllers/SesiTaklimatController.php b/app/Http/Controllers/SesiTaklimatController.php index 53137b6..7bd4c6e 100644 --- a/app/Http/Controllers/SesiTaklimatController.php +++ b/app/Http/Controllers/SesiTaklimatController.php @@ -9,14 +9,14 @@ use Illuminate\Http\Request; class SesiTaklimatController extends Controller { /** - * Senarai sesi taklimat. Pengurus hanya nampak DUN sendiri. + * Senarai sesi taklimat. Pengurus & kaunter hanya nampak DUN sendiri. */ public function index(Request $request) { $user = $request->user(); $sesi = SesiTaklimat::withCount('kehadiran') - ->when($user->isPengurus(), fn ($q) => $q->where('dun_id', $user->dun_id)) + ->when(! $user->isAdmin(), fn ($q) => $q->where('dun_id', $user->dun_id)) ->orderByDesc('tarikh') ->get(); @@ -106,7 +106,7 @@ class SesiTaklimatController extends Controller { $user = $request->user(); - if ($user->isPengurus() && (int) $sesi->dun_id !== (int) $user->dun_id) { + if (! $user->isAdmin() && (int) $sesi->dun_id !== (int) $user->dun_id) { abort(403, 'Sesi ini bukan untuk DUN anda.'); } } diff --git a/app/Models/User.php b/app/Models/User.php index 5de3152..3caa54d 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,10 +3,13 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; +use App\Models\Sppm\Dun; +use App\Notifications\ResetKataLaluanNotification; use Database\Factories\UserFactory; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Hidden; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; @@ -27,6 +30,24 @@ class User extends Authenticatable return $this->role === 'pengurus'; } + public function isKaunter(): bool + { + return $this->role === 'kaunter'; + } + + public function dun(): BelongsTo + { + return $this->belongsTo(Dun::class, 'dun_id'); + } + + /** + * Guna e-mel dalam Bahasa Melayu untuk pautan set semula kata laluan. + */ + public function sendPasswordResetNotification(#[\SensitiveParameter] $token): void + { + $this->notify(new ResetKataLaluanNotification($token)); + } + /** * Get the attributes that should be cast. * diff --git a/app/Notifications/ResetKataLaluanNotification.php b/app/Notifications/ResetKataLaluanNotification.php new file mode 100644 index 0000000..decb240 --- /dev/null +++ b/app/Notifications/ResetKataLaluanNotification.php @@ -0,0 +1,38 @@ + + */ + public function via(mixed $notifiable): array + { + return ['mail']; + } + + public function toMail(mixed $notifiable): MailMessage + { + $url = url(route('password.reset', [ + 'token' => $this->token, + 'email' => $notifiable->getEmailForPasswordReset(), + ], false)); + + $minit = config('auth.passwords.users.expire'); + + return (new MailMessage) + ->subject('Set Semula Kata Laluan - Kehadiran Taklimat SPR') + ->line('Kami menerima permintaan untuk set semula kata laluan akaun anda.') + ->action('Set Semula Kata Laluan', $url) + ->line("Pautan ini akan tamat tempoh dalam {$minit} minit.") + ->line('Jika anda tidak membuat permintaan ini, sila abaikan e-mel ini.'); + } +} diff --git a/database/migrations/2026_07_04_050801_add_kaunter_role_to_users_table.php b/database/migrations/2026_07_04_050801_add_kaunter_role_to_users_table.php new file mode 100644 index 0000000..28c13cb --- /dev/null +++ b/database/migrations/2026_07_04_050801_add_kaunter_role_to_users_table.php @@ -0,0 +1,28 @@ +enum('role', ['admin', 'pengurus', 'kaunter'])->default('admin')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->enum('role', ['admin', 'pengurus'])->default('admin')->change(); + }); + } +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8590dc2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1656 @@ +{ + "name": "spr2026_taklimat", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "concurrently": "^9.0.1", + "laravel-vite-plugin": "^3.1", + "tailwindcss": "^4.0.0", + "vite": "^8.0.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.138.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz", + "integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz", + "integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz", + "integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz", + "integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz", + "integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz", + "integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz", + "integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz", + "integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz", + "integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz", + "integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz", + "integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz", + "integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz", + "integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz", + "integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz", + "integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz", + "integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", + "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz", + "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-x64": "4.3.2", + "@tailwindcss/oxide-freebsd-x64": "4.3.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-x64-musl": "4.3.2", + "@tailwindcss/oxide-wasm32-wasi": "4.3.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", + "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", + "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", + "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", + "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", + "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", + "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", + "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", + "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", + "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", + "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", + "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz", + "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.2.tgz", + "integrity": "sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.2", + "@tailwindcss/oxide": "4.3.2", + "tailwindcss": "4.3.2" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.3.tgz", + "integrity": "sha512-ihjs0E2SxvDgq/MK418hX6YycQgKhsqxpbZuZbHo0yKfqDWdymWMjWYIpCIzqDDLLKClHlXev8whW/8WXmJ0BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.4", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/laravel-vite-plugin": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-3.1.0.tgz", + "integrity": "sha512-Fzocl+X4eQ9jOi0RwdphYRGkUbPJ3ky1pTAST5Ot18cS2gw6d2vldK2eCrlKDVjtibCjCx5qptYDlA0373n7qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "tinyglobby": "^0.2.12", + "vite-plugin-full-reload": "^1.1.0" + }, + "bin": { + "clean-orphaned-assets": "bin/clean.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "fontaine": "^0.5.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "fontaine": { + "optional": true + } + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz", + "integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.138.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.4", + "@rolldown/binding-darwin-arm64": "1.1.4", + "@rolldown/binding-darwin-x64": "1.1.4", + "@rolldown/binding-freebsd-x64": "1.1.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.4", + "@rolldown/binding-linux-arm64-gnu": "1.1.4", + "@rolldown/binding-linux-arm64-musl": "1.1.4", + "@rolldown/binding-linux-ppc64-gnu": "1.1.4", + "@rolldown/binding-linux-s390x-gnu": "1.1.4", + "@rolldown/binding-linux-x64-gnu": "1.1.4", + "@rolldown/binding-linux-x64-musl": "1.1.4", + "@rolldown/binding-openharmony-arm64": "1.1.4", + "@rolldown/binding-wasm32-wasi": "1.1.4", + "@rolldown/binding-win32-arm64-msvc": "1.1.4", + "@rolldown/binding-win32-x64-msvc": "1.1.4" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/shell-quote": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", + "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", + "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/vite": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz", + "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.16", + "rolldown": "~1.1.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-full-reload": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.2.0.tgz", + "integrity": "sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "picomatch": "^2.3.1" + } + }, + "node_modules/vite-plugin-full-reload/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/public/css/app.css b/public/css/app.css index 3ca8833..fa90a64 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -46,6 +46,11 @@ a { color: var(--biru); } font-size: 0.9rem; } +.topbar .pengguna a { + color: #fff; + text-decoration: underline; +} + .container { max-width: 960px; margin: 1.25rem auto; @@ -199,6 +204,7 @@ table.jadual tbody tr:nth-child(even) { background: #f8f9fb; } .lencana-merah { background: #fdecea; color: var(--merah); } .lencana-kelabu { background: #ececec; color: #555; } .lencana-biru { background: #e8f0fe; color: var(--biru); } +.lencana-kuning { background: #fbf0d9; color: var(--kuning); } .baris-tindakan { display: flex; @@ -287,6 +293,7 @@ table.jadual tbody tr:nth-child(even) { background: #f8f9fb; } } .tajuk-jadual.hijau { border-color: var(--hijau); color: var(--hijau); } .tajuk-jadual.merah { border-color: var(--merah); color: var(--merah); } +.tajuk-jadual.kuning { border-color: var(--kuning); color: var(--kuning); } .jadual-kepala .cari-input { width: auto; diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 444cdd6..5b92fd7 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -22,5 +22,7 @@ + +

Lupa kata laluan?

@endsection diff --git a/resources/views/auth/lupa-katalaluan.blade.php b/resources/views/auth/lupa-katalaluan.blade.php new file mode 100644 index 0000000..7b3c74c --- /dev/null +++ b/resources/views/auth/lupa-katalaluan.blade.php @@ -0,0 +1,25 @@ +@extends('layouts.app') + +@section('tajuk', 'Lupa Kata Laluan') +@section('saiz-container', 'container-kecil') + +@section('kandungan') +
+

Lupa Kata Laluan

+

Masukkan e-mel akaun anda. Pautan set semula kata laluan akan dihantar ke e-mel tersebut.

+ + @if ($errors->any()) +
{{ $errors->first() }}
+ @endif + +
+ @csrf + + + + +
+ +

Kembali ke Log Masuk

+
+@endsection diff --git a/resources/views/auth/set-kata-laluan.blade.php b/resources/views/auth/set-kata-laluan.blade.php new file mode 100644 index 0000000..0359a21 --- /dev/null +++ b/resources/views/auth/set-kata-laluan.blade.php @@ -0,0 +1,30 @@ +@extends('layouts.app') + +@section('tajuk', 'Set Semula Kata Laluan') +@section('saiz-container', 'container-kecil') + +@section('kandungan') +
+

Set Semula Kata Laluan

+ + @if ($errors->any()) +
{{ $errors->first() }}
+ @endif + +
+ @csrf + + + + + + + + + + + + +
+
+@endsection diff --git a/resources/views/checkin/pernah-hadir.blade.php b/resources/views/checkin/pernah-hadir.blade.php new file mode 100644 index 0000000..f0649fd --- /dev/null +++ b/resources/views/checkin/pernah-hadir.blade.php @@ -0,0 +1,30 @@ +@extends('layouts.app') + +@section('tajuk', 'Pernah Hadir') +@section('saiz-container', 'container-kecil') + +@section('kandungan') +
+
+ Anda telah pun hadir taklimat sebelum ini. Tiada keperluan untuk daftar semula. +
+

Status

+
Pernah Hadir
+
+ +
+ + + +
Nama{{ $petugas->nama_ic }}
No. KP{{ $petugas->no_kp }}
+ +

Sesi Taklimat Sebelum Ini

+ + + + + + +
DUN{{ $sesiSebelumIni->dun->code ?? '' }} — {{ $sesiSebelumIni->dun->nama ?? '-' }}
Sesi{{ $sesiSebelumIni->nama }} ({{ $sesiSebelumIni->tarikh->format('d/m/Y') }})
Lokasi{{ $sesiSebelumIni->lokasi }}
Nombor Siri{{ $kehadiranSebelumIni->nombor_siri_papar }}
Masa Daftar{{ $kehadiranSebelumIni->hadir_at->format('d/m/Y h:i A') }}
+
+@endsection diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index d0e526d..d8501ae 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -13,8 +13,20 @@ Kehadiran Taklimat SPR 2026 @auth + @php + $labelPeranan = match (auth()->user()->role) { + 'admin' => 'Admin', + 'pengurus' => 'Pengurus DUN', + 'kaunter' => 'Kaunter', + default => auth()->user()->role, + }; + @endphp
- {{ auth()->user()->name }} ({{ auth()->user()->role === 'admin' ? 'Admin' : 'Pengurus DUN' }}) + @if (auth()->user()->isAdmin() || auth()->user()->isPengurus()) + Pengguna + @endif + Profil + {{ auth()->user()->name }} ({{ $labelPeranan }})
@csrf diff --git a/resources/views/pengguna/create.blade.php b/resources/views/pengguna/create.blade.php new file mode 100644 index 0000000..e1cb922 --- /dev/null +++ b/resources/views/pengguna/create.blade.php @@ -0,0 +1,82 @@ +@extends('layouts.app') + +@section('tajuk', 'Daftar Pengguna Baharu') +@section('saiz-container', 'container-kecil') + +@section('kandungan') +
+

Daftar Pengguna Baharu

+ @unless (auth()->user()->isAdmin()) +

Pengguna yang didaftarkan akan ditetapkan untuk DUN anda sendiri.

+ @endunless + + @if ($errors->any()) +
{{ $errors->first() }}
+ @endif + + + @csrf + + + + + + + + + + @if (auth()->user()->isAdmin()) +
+ + +
+ @endif + + + + + + + + + +
+ + @if (auth()->user()->isAdmin()) + + @endif +@endsection diff --git a/resources/views/pengguna/index.blade.php b/resources/views/pengguna/index.blade.php new file mode 100644 index 0000000..e988909 --- /dev/null +++ b/resources/views/pengguna/index.blade.php @@ -0,0 +1,48 @@ +@extends('layouts.app') + +@section('tajuk', 'Senarai Pengguna') + +@section('kandungan') +
+
+

Pengguna

+ + Daftar Pengguna +
+
+ + @if ($pengguna->isEmpty()) +
+

Tiada pengguna lagi.

+
+ @else +
+ + + + + + + + + + + @foreach ($pengguna as $p) + + + + + + + @endforeach + +
NamaE-melPerananDUN
{{ $p->name }}{{ $p->email }} + {{ match ($p->role) { + 'admin' => 'Admin', + 'pengurus' => 'Pengurus DUN', + 'kaunter' => 'Kaunter', + default => $p->role, + } }} + {{ $p->dun ? $p->dun->code . ' — ' . $p->dun->nama : '-' }}
+
+ @endif +@endsection diff --git a/resources/views/pengurus/senarai.blade.php b/resources/views/pengurus/senarai.blade.php index 2b6638b..736b41c 100644 --- a/resources/views/pengurus/senarai.blade.php +++ b/resources/views/pengurus/senarai.blade.php @@ -13,13 +13,17 @@
-
{{ $hadir->count() + $tidakHadir->count() }}
+
{{ $hadir->count() + $hadirSesiLain->count() + $tidakHadir->count() }}
Jumlah Petugas
{{ $hadir->count() }}
Hadir
+
+
{{ $hadirSesiLain->count() }}
+
Pernah Hadir (Sesi Lain)
+
{{ $tidakHadir->count() }}
Tidak / Belum Hadir
@@ -81,6 +85,55 @@
+ {{-- ============= PERNAH HADIR (SESI LAIN) ============= --}} +
+
+

PERNAH HADIR - SESI LAIN ({{ $hadirSesiLain->count() }})

+ +
+
+ + + + + + + + + + + + + + + @forelse ($hadirSesiLain as $p) + @php + $penempatan = $p->butiranPenempatan(); + $rekod = $kehadiranSesiLain->get($p->id); + @endphp + + + + + + + + + + + @empty + + @endforelse + +
NamaNo. TelefonEmelPusat MengundiSaluranJawatanSesi DihadiriTarikh Hadir
{{ $p->nama_ic }}{{ $p->no_telefon ?? '-' }}{{ $p->emel ?? '-' }}{{ $penempatan['pusat_mengundi'] }}{{ $penempatan['saluran'] }}{{ $penempatan['jawatan'] }}{{ $rekod?->sesi?->nama }}{{ $rekod?->hadir_at?->format('d/m/Y') }}
Tiada petugas dalam kumpulan ini.
+
+
+ +
+
+
+ {{-- ================ TIDAK / BELUM HADIR ================ --}}
diff --git a/resources/views/profil/edit.blade.php b/resources/views/profil/edit.blade.php new file mode 100644 index 0000000..6851f75 --- /dev/null +++ b/resources/views/profil/edit.blade.php @@ -0,0 +1,52 @@ +@extends('layouts.app') + +@section('tajuk', 'Profil Saya') +@section('saiz-container', 'container-kecil') + +@section('kandungan') +
+

Profil Saya

+ + + + + + + +
Nama{{ $pengguna->name }}
E-mel{{ $pengguna->email }}
Peranan + {{ match ($pengguna->role) { + 'admin' => 'Admin', + 'pengurus' => 'Pengurus DUN', + 'kaunter' => 'Kaunter', + default => $pengguna->role, + } }} + @if ($pengguna->dun) + — {{ $pengguna->dun->code }} {{ $pengguna->dun->nama }} + @endif +
+
+ +
+

Tukar Kata Laluan

+ + @if ($errors->any()) +
{{ $errors->first() }}
+ @endif + +
+ @csrf + @method('PUT') + + + + + + + + + + + +
+
+@endsection diff --git a/resources/views/sesi/index.blade.php b/resources/views/sesi/index.blade.php index 690748b..1e4c2ab 100644 --- a/resources/views/sesi/index.blade.php +++ b/resources/views/sesi/index.blade.php @@ -6,10 +6,12 @@

Sesi Taklimat

- + @unless (auth()->user()->isKaunter()) + + @endunless
diff --git a/resources/views/sesi/show.blade.php b/resources/views/sesi/show.blade.php index 9840d05..38c4b09 100644 --- a/resources/views/sesi/show.blade.php +++ b/resources/views/sesi/show.blade.php @@ -32,12 +32,14 @@ Papar / Cetak QR Code Kaunter Borang Senarai Kehadiran -
- @csrf - -
+ @unless (auth()->user()->isKaunter()) +
+ @csrf + +
+ @endunless
diff --git a/routes/web.php b/routes/web.php index a43e44a..b716c86 100644 --- a/routes/web.php +++ b/routes/web.php @@ -4,7 +4,10 @@ use App\Http\Controllers\AuthController; use App\Http\Controllers\CheckinController; use App\Http\Controllers\EksportKehadiranController; use App\Http\Controllers\KaunterController; +use App\Http\Controllers\PasswordResetController; use App\Http\Controllers\PengurusController; +use App\Http\Controllers\PenggunaController; +use App\Http\Controllers\ProfilController; use App\Http\Controllers\SesiTaklimatController; use Illuminate\Support\Facades\Route; @@ -19,20 +22,39 @@ Route::get('/login', [AuthController::class, 'showLogin'])->name('login'); Route::post('/login', [AuthController::class, 'login'])->name('login.attempt'); Route::post('/logout', [AuthController::class, 'logout'])->name('logout'); +// Lupa kata laluan +Route::get('/lupa-katalaluan', [PasswordResetController::class, 'showLinkRequestForm'])->name('password.request'); +Route::post('/lupa-katalaluan', [PasswordResetController::class, 'sendResetLink'])->name('password.email'); +Route::get('/set-kata-laluan/{token}', [PasswordResetController::class, 'showResetForm'])->name('password.reset'); +Route::post('/set-kata-laluan', [PasswordResetController::class, 'reset'])->name('password.update'); + Route::middleware(['auth'])->group(function () { - // Pengurusan sesi taklimat (admin & pengurus dun) - Route::get('/sesi', [SesiTaklimatController::class, 'index'])->name('sesi.index'); - Route::get('/sesi/eksport-kehadiran', EksportKehadiranController::class)->name('sesi.eksport'); - Route::get('/sesi/cipta', [SesiTaklimatController::class, 'create'])->name('sesi.create'); - Route::post('/sesi', [SesiTaklimatController::class, 'store'])->name('sesi.store'); - Route::get('/sesi/{sesi}', [SesiTaklimatController::class, 'show'])->name('sesi.show'); - Route::get('/sesi/{sesi}/qr', [SesiTaklimatController::class, 'qr'])->name('sesi.qr'); - Route::post('/sesi/{sesi}/toggle', [SesiTaklimatController::class, 'toggle'])->name('sesi.toggle'); + // Profil sendiri: tukar kata laluan (semua peranan) + Route::get('/profil', [ProfilController::class, 'edit'])->name('profil.edit'); + Route::put('/profil/kata-laluan', [ProfilController::class, 'updatePassword'])->name('profil.kata-laluan'); - // Kaunter: rekod nombor siri & serah borang - Route::get('/sesi/{sesi}/kaunter', [KaunterController::class, 'index'])->name('kaunter.index'); - Route::post('/sesi/{sesi}/kaunter/{kehadiran}/serah', [KaunterController::class, 'serahBorang'])->name('kaunter.serah'); + Route::middleware(['role:admin,pengurus'])->group(function () { + // Pengurusan sesi taklimat (cipta/tutup/eksport) — bukan kaunter + Route::get('/sesi/eksport-kehadiran', EksportKehadiranController::class)->name('sesi.eksport'); + Route::get('/sesi/cipta', [SesiTaklimatController::class, 'create'])->name('sesi.create'); + Route::post('/sesi', [SesiTaklimatController::class, 'store'])->name('sesi.store'); + Route::post('/sesi/{sesi}/toggle', [SesiTaklimatController::class, 'toggle'])->name('sesi.toggle'); - // Pengurus dun: senarai hadir / tidak hadir - Route::get('/sesi/{sesi}/senarai', [PengurusController::class, 'senarai'])->name('pengurus.senarai'); + // Pengurusan pengguna: admin daftar sesiapa, pengurus daftar kaunter/pengurus DUN sendiri + Route::get('/pengguna', [PenggunaController::class, 'index'])->name('pengguna.index'); + Route::get('/pengguna/cipta', [PenggunaController::class, 'create'])->name('pengguna.create'); + Route::post('/pengguna', [PenggunaController::class, 'store'])->name('pengguna.store'); + }); + + Route::middleware(['role:admin,pengurus,kaunter'])->group(function () { + // Lihat sesi & kehadiran (admin, pengurus dun, kaunter — dibatasi DUN sendiri kecuali admin) + Route::get('/sesi', [SesiTaklimatController::class, 'index'])->name('sesi.index'); + Route::get('/sesi/{sesi}', [SesiTaklimatController::class, 'show'])->name('sesi.show'); + Route::get('/sesi/{sesi}/qr', [SesiTaklimatController::class, 'qr'])->name('sesi.qr'); + Route::get('/sesi/{sesi}/senarai', [PengurusController::class, 'senarai'])->name('pengurus.senarai'); + + // Kaunter: rekod nombor siri & serah borang + Route::get('/sesi/{sesi}/kaunter', [KaunterController::class, 'index'])->name('kaunter.index'); + Route::post('/sesi/{sesi}/kaunter/{kehadiran}/serah', [KaunterController::class, 'serahBorang'])->name('kaunter.serah'); + }); });