From f5a80fd4e25a00400927222889906279b195aabc Mon Sep 17 00:00:00 2001 From: Sari Date: Mon, 3 Aug 2026 09:30:14 +0700 Subject: [PATCH] feat: kolom phone di manajemen user (tampil di tabel, input modal, validasi regex) + FIX bug: catch-all exception handler mengubah ValidationException jadi 500 (kini redirect+error normal) --- app/Http/Controllers/Admin/UserController.php | 2 + app/Models/User.php | 2 +- bootstrap/app.php | 4 ++ ..._08_03_092708_add_phone_to_users_table.php | 28 ++++++++ resources/views/users/index.blade.php | 23 ++++-- tests/Feature/UserManagementTest.php | 70 +++++++++++++++++++ 6 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 database/migrations/2026_08_03_092708_add_phone_to_users_table.php create mode 100644 tests/Feature/UserManagementTest.php diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index c157693..52b9ce9 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -43,6 +43,7 @@ class UserController extends Controller 'name' => ['required', 'string', 'max:255'], 'username' => ['required', 'string', 'max:255', 'unique:users,username'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'], + 'phone' => ['nullable', 'string', 'max:20', 'regex:/^[0-9+\-\s()]*$/'], 'password' => ['required', 'string', 'min:6'], 'roles' => ['required', 'array', 'min:1'], 'roles.*' => ['in:admin,bk,wali_kelas,staff,other,kepala_sekolah,waka_kesiswaan,ketua_tim'], @@ -67,6 +68,7 @@ class UserController extends Controller 'name' => ['required', 'string', 'max:255'], 'username' => ['required', 'string', 'max:255', 'unique:users,username,' . $user->id], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email,' . $user->id], + 'phone' => ['nullable', 'string', 'max:20', 'regex:/^[0-9+\-\s()]*$/'], 'password' => ['nullable', 'string', 'min:6'], 'roles' => ['required', 'array', 'min:1'], 'roles.*' => ['in:admin,bk,wali_kelas,staff,other,kepala_sekolah,waka_kesiswaan,ketua_tim'], diff --git a/app/Models/User.php b/app/Models/User.php index 31cc434..3054c00 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -13,7 +13,7 @@ class User extends Authenticatable public const ROLES = ['admin', 'bk', 'wali_kelas', 'staff', 'other', 'kepala_sekolah', 'waka_kesiswaan', 'ketua_tim']; protected $fillable = [ - 'name', 'username', 'email', 'password', 'role', 'roles', 'is_active', + 'name', 'username', 'email', 'phone', 'password', 'role', 'roles', 'is_active', ]; protected $hidden = ['password', 'remember_token']; diff --git a/bootstrap/app.php b/bootstrap/app.php index 09f2091..cc4d5f0 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -60,6 +60,10 @@ return Application::configure(basePath: dirname(__DIR__)) if ($e instanceof \Illuminate\Auth\AuthenticationException) { return null; } + // Biarkan default menangani: redirect balik + pesan error validasi + if ($e instanceof \Illuminate\Validation\ValidationException) { + return null; + } \Illuminate\Support\Facades\Log::error('Unhandled exception: ' . get_class($e) . ' - ' . $e->getMessage() . ' @ ' . $request->path()); return $htmlView('errors.500', 500, $request); }); diff --git a/database/migrations/2026_08_03_092708_add_phone_to_users_table.php b/database/migrations/2026_08_03_092708_add_phone_to_users_table.php new file mode 100644 index 0000000..3c93f47 --- /dev/null +++ b/database/migrations/2026_08_03_092708_add_phone_to_users_table.php @@ -0,0 +1,28 @@ +string('phone', 20)->nullable()->after('email'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('phone'); + }); + } +}; diff --git a/resources/views/users/index.blade.php b/resources/views/users/index.blade.php index 422db37..41aeea8 100644 --- a/resources/views/users/index.blade.php +++ b/resources/views/users/index.blade.php @@ -77,6 +77,9 @@

{{ $user->name }}

{{ $user->email }}

+ @if ($user->phone) +

{{ $user->phone }}

+ @endif
@@ -119,7 +122,7 @@
- @@ -234,6 +237,16 @@ placeholder="email@sekolah.sch.id">
+
+ +
+ + +
+

Nomor untuk kontak & notifikasi (opsional)

+
{{-- Password --}} @@ -331,7 +344,7 @@ function userManager() { return { modalOpen: false, isEditing: false, editId: null, - formName: '', formUsername: '', formEmail: '', formPassword: '', formRoles: ['bk'], formActive: true, + formName: '', formUsername: '', formEmail: '', formPhone: '', formPassword: '', formRoles: ['bk'], formActive: true, showPassword: false, get roleInfo() { const map = { @@ -349,12 +362,12 @@ function userManager() { }, openCreate() { this.isEditing = false; this.editId = null; - this.formName = ''; this.formUsername = ''; this.formEmail = ''; this.formPassword = ''; + this.formName = ''; this.formUsername = ''; this.formEmail = ''; this.formPhone = ''; this.formPassword = ''; this.formRoles = ['bk']; this.formActive = true; this.showPassword = false; this.modalOpen = true; }, - openEdit(id, name, username, email, rolesCsv, active) { + openEdit(id, name, username, email, phone, rolesCsv, active) { this.isEditing = true; this.editId = id; - this.formName = name; this.formUsername = username; this.formEmail = email; + this.formName = name; this.formUsername = username; this.formEmail = email; this.formPhone = phone; this.formPassword = ''; this.formRoles = rolesCsv ? rolesCsv.split(',') : ['bk']; this.formActive = active; this.showPassword = false; this.modalOpen = true; diff --git a/tests/Feature/UserManagementTest.php b/tests/Feature/UserManagementTest.php new file mode 100644 index 0000000..fb8e86c --- /dev/null +++ b/tests/Feature/UserManagementTest.php @@ -0,0 +1,70 @@ +admin = User::factory()->create(['roles' => ['admin']]); + } + + public function test_admin_menambah_user_dengan_nomor_hp(): void + { + $this->actingAs($this->admin) + ->post('/users', [ + 'name' => 'Guru Baru', + 'username' => 'gurubaru', + 'email' => 'gurubaru@sekolah.sch.id', + 'phone' => '081234567890', + 'password' => 'rahasia123', + 'roles' => ['bk'], + 'is_active' => 1, + ]) + ->assertRedirect(); + + $user = User::where('email', 'gurubaru@sekolah.sch.id')->firstOrFail(); + $this->assertSame('081234567890', $user->phone); + } + + public function test_admin_mengupdate_nomor_hp_user(): void + { + $user = User::factory()->create(['roles' => ['bk'], 'phone' => '081111111111']); + + $this->actingAs($this->admin) + ->put("/users/{$user->id}", [ + 'name' => $user->name, + 'username' => $user->username, + 'email' => $user->email, + 'phone' => '082222222222', + 'roles' => ['bk'], + 'is_active' => 1, + ]) + ->assertRedirect(); + + $this->assertSame('082222222222', $user->refresh()->phone); + } + + public function test_nomor_hp_tidak_boleh_karakter_aneh(): void + { + $this->actingAs($this->admin) + ->post('/users', [ + 'name' => 'Guru Aneh', + 'username' => 'guruaneh', + 'email' => 'guruaneh@sekolah.sch.id', + 'phone' => 'abc; drop table', + 'password' => 'rahasia123', + 'roles' => ['bk'], + ]) + ->assertSessionHasErrors('phone'); + + $this->assertDatabaseMissing('users', ['username' => 'guruaneh']); + } +}