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)
This commit is contained in:
@@ -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'],
|
||||
|
||||
+1
-1
@@ -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'];
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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::table('users', function (Blueprint $table) {
|
||||
$table->string('phone', 20)->nullable()->after('email');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('phone');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -77,6 +77,9 @@
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900">{{ $user->name }}</p>
|
||||
<p class="text-xs text-gray-400">{{ $user->email }}</p>
|
||||
@if ($user->phone)
|
||||
<p class="text-xs text-gray-400 mt-0.5"><i class="fa-solid fa-phone text-[10px]"></i> {{ $user->phone }}</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -119,7 +122,7 @@
|
||||
</td>
|
||||
<td class="px-5 py-4 text-right">
|
||||
<div class="flex items-center justify-end space-x-1">
|
||||
<button @click="openEdit({{ $user->id }}, '{{ addslashes($user->name) }}', '{{ $user->username }}', '{{ $user->email }}', '{{ implode(',', $user->roleList()) }}', {{ $user->is_active ? 'true' : 'false' }})"
|
||||
<button @click="openEdit({{ $user->id }}, '{{ addslashes($user->name) }}', '{{ $user->username }}', '{{ $user->email }}', '{{ addslashes($user->phone ?? '') }}', '{{ implode(',', $user->roleList()) }}', {{ $user->is_active ? 'true' : 'false' }})"
|
||||
class="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition" title="Edit">
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
</button>
|
||||
@@ -234,6 +237,16 @@
|
||||
placeholder="email@sekolah.sch.id">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-2">Nomor HP</label>
|
||||
<div class="relative">
|
||||
<i class="fa-solid fa-phone absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 text-sm pointer-events-none"></i>
|
||||
<input type="tel" x-model="formPhone" name="phone"
|
||||
class="w-full pl-11 pr-4 py-3 border-2 border-gray-200 rounded-2xl text-sm font-semibold bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-4 focus:ring-blue-500/15 focus:border-blue-500 transition"
|
||||
placeholder="08xxxxxxxxxx">
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1.5">Nomor untuk kontak & notifikasi (opsional)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 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;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class UserManagementTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->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']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user