diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php new file mode 100644 index 0000000..1bcb4fa --- /dev/null +++ b/app/Http/Controllers/Admin/UserController.php @@ -0,0 +1,115 @@ +orderBy('name')->paginate(25); + $roles = User::ROLE_LABELS; + + return view('admin.users.index', compact('users', 'roles')); + } + + public function create(): View + { + $roles = User::ROLE_LABELS; + + return view('admin.users.create', compact('roles')); + } + + public function store(Request $request): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'email', 'max:255', 'unique:users,email'], + 'role' => ['required', Rule::in(array_keys(User::ROLE_LABELS))], + 'password' => ['required', 'confirmed', Password::min(8)], + ]); + + $user = User::create([ + 'name' => $data['name'], + 'email' => $data['email'], + 'password' => Hash::make($data['password']), + ]); + $user->syncRoles([$data['role']]); + + AuditLog::record('user.create', "Cipta pengguna: {$user->name} ({$user->email})", $user, ['role' => $data['role']]); + + return redirect()->route('admin.users.index')->with('success', "Pengguna '{$user->name}' dicipta."); + } + + public function edit(User $user): View + { + $roles = User::ROLE_LABELS; + + return view('admin.users.edit', compact('user', 'roles')); + } + + public function update(Request $request, User $user): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'email', 'max:255', Rule::unique('users', 'email')->ignore($user->id)], + 'role' => ['required', Rule::in(array_keys(User::ROLE_LABELS))], + 'password' => ['nullable', 'confirmed', Password::min(8)], + ]); + + // Jangan biar admin terakhir diturunkan peranannya (boleh kunci sistem) + if ($this->isLastAdmin($user) && $data['role'] !== User::ROLE_ADMIN) { + return back()->withInput()->with('error', 'Tidak boleh menukar peranan admin terakhir.'); + } + + $user->update([ + 'name' => $data['name'], + 'email' => $data['email'], + ]); + + if (! empty($data['password'])) { + $user->update(['password' => Hash::make($data['password'])]); + } + + $user->syncRoles([$data['role']]); + + AuditLog::record('user.update', "Kemaskini pengguna: {$user->name}", $user, [ + 'role' => $data['role'], + 'password_changed' => ! empty($data['password']), + ]); + + return redirect()->route('admin.users.index')->with('success', 'Maklumat pengguna dikemaskini.'); + } + + public function destroy(Request $request, User $user): RedirectResponse + { + if ($user->id === $request->user()->id) { + return back()->with('error', 'Anda tidak boleh memadam akaun sendiri.'); + } + + if ($this->isLastAdmin($user)) { + return back()->with('error', 'Tidak boleh memadam admin terakhir.'); + } + + $nama = $user->name; + $user->delete(); + AuditLog::record('user.delete', "Padam pengguna: {$nama}"); + + return redirect()->route('admin.users.index')->with('success', "Pengguna '{$nama}' dipadam."); + } + + private function isLastAdmin(User $user): bool + { + return $user->hasRole(User::ROLE_ADMIN) + && User::role(User::ROLE_ADMIN)->count() <= 1; + } +} diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php new file mode 100644 index 0000000..c4589f9 --- /dev/null +++ b/app/Http/Controllers/ProfileController.php @@ -0,0 +1,49 @@ + auth()->user()]); + } + + public function update(Request $request): RedirectResponse + { + $user = $request->user(); + + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'email', 'max:255', Rule::unique('users', 'email')->ignore($user->id)], + ]); + + $user->update($data); + AuditLog::record('profile.update', 'Kemaskini profil sendiri', $user); + + return redirect()->route('profile.edit')->with('success', 'Profil dikemaskini.'); + } + + public function updatePassword(Request $request): RedirectResponse + { + $request->validate([ + 'current_password' => ['required', 'current_password'], + 'password' => ['required', 'confirmed', Password::min(8)], + ]); + + $request->user()->update([ + 'password' => Hash::make($request->input('password')), + ]); + AuditLog::record('profile.password', 'Tukar kata laluan sendiri', $request->user()); + + return redirect()->route('profile.edit')->with('success', 'Kata laluan ditukar.'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 87969d4..0079981 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -17,6 +17,21 @@ class User extends Authenticatable public const ROLE_KAUNTER = 'petugas_kaunter'; public const ROLE_CABUTAN = 'petugas_cabutan'; + /** Peranan yang boleh ditugaskan kepada pengguna + label paparan ringkas. */ + public const ROLE_LABELS = [ + self::ROLE_ADMIN => 'Admin', + self::ROLE_KAUNTER => 'Kaunter', + self::ROLE_CABUTAN => 'Cabutan', + ]; + + /** Label ringkas peranan utama pengguna ini (cth "Admin"). */ + public function roleLabel(): string + { + $role = $this->getRoleNames()->first(); + + return self::ROLE_LABELS[$role] ?? ($role ? \Illuminate\Support\Str::headline($role) : '-'); + } + protected $fillable = [ 'name', 'email', diff --git a/resources/views/admin/users/create.blade.php b/resources/views/admin/users/create.blade.php new file mode 100644 index 0000000..99c3713 --- /dev/null +++ b/resources/views/admin/users/create.blade.php @@ -0,0 +1,37 @@ +@extends('layouts.app') +@section('title', 'Pengguna Baru') + +@section('content') +