fix(user): memperbaiki attach role dan mengubha tipe data prop gender ke int

- kasih otorisasi saat delete dan reset password
- memperbaiki testnya
- kasih kondisi untuk data kosong jangan di tampilkan di view
This commit is contained in:
Yoga Pangestu 2025-12-10 08:36:29 +07:00
parent d365c6c8f8
commit 2b5983fb2a
7 changed files with 57 additions and 34 deletions

View File

@ -14,6 +14,7 @@
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Livewire\Form; use Livewire\Form;
use Spatie\Permission\Models\Role;
class UserForm extends Form class UserForm extends Form
{ {
@ -35,7 +36,7 @@ class UserForm extends Form
public ?string $address = null; public ?string $address = null;
public string $gender = ''; public int $gender;
public int $status = UserStatus::ACTIVE->value; public int $status = UserStatus::ACTIVE->value;
@ -183,7 +184,9 @@ private function createReferralCode(User $user, array $data): void
private function assignOutletsAndRoles(User $user): void private function assignOutletsAndRoles(User $user): void
{ {
$user->outlets()->attach($this->outlet_ids); $user->outlets()->attach($this->outlet_ids);
$user->assignRole(array_map('intval', $this->role_ids));
$roles = Role::whereIn('id', $this->role_ids)->get();
$user->assignRole($roles);
} }
public function update(): void public function update(): void
@ -225,6 +228,8 @@ private function updateEmployee(array $data): void
private function syncOutletsAndRoles(): void private function syncOutletsAndRoles(): void
{ {
$this->user->outlets()->sync($this->outlet_ids); $this->user->outlets()->sync($this->outlet_ids);
$this->user->syncRoles(array_map('intval', $this->role_ids));
$roles = Role::whereIn('id', $this->role_ids)->get();
$this->user->syncRoles($roles);
} }
} }

View File

@ -5,6 +5,7 @@
use App\Models\Employee; use App\Models\Employee;
use App\Models\User; use App\Models\User;
use App\Traits\Notification\WithSubscribeNotification; use App\Traits\Notification\WithSubscribeNotification;
use App\Traits\WithAuthorization;
use App\Traits\WithConfirmation; use App\Traits\WithConfirmation;
use App\Traits\WithToast; use App\Traits\WithToast;
use Flux\Flux; use Flux\Flux;
@ -17,7 +18,7 @@
#[Title('Pegawai')] #[Title('Pegawai')]
class Index extends Component class Index extends Component
{ {
use WithConfirmation, WithSubscribeNotification, WithToast; use WithAuthorization, WithConfirmation, WithSubscribeNotification, WithToast;
/** @var Collection<int, \App\Models\Employee> */ /** @var Collection<int, \App\Models\Employee> */
public Collection $employees; public Collection $employees;
@ -60,6 +61,8 @@ public function updatedStatus(): void
public function delete(User $user): void public function delete(User $user): void
{ {
$this->canOrAbort('delete user');
$user->delete(); $user->delete();
$this->loadEmployees(); $this->loadEmployees();
@ -71,6 +74,8 @@ public function delete(User $user): void
public function resetPassword(User $user): void public function resetPassword(User $user): void
{ {
$this->canOrAbort('reset password user');
$user->update(['password' => Hash::make(config('myconfig.password_default'))]); $user->update(['password' => Hash::make(config('myconfig.password_default'))]);
$this->toast('Kata sandi pegawai berhasil direset.'); $this->toast('Kata sandi pegawai berhasil direset.');

View File

@ -114,29 +114,37 @@ class="w-4 h-4 text-gray-500 dark:text-white flex-shrink-0 mt-0.5" />
</div> </div>
@endif @endif
<div> @if ($employee->user?->outlets?->isNotEmpty())
<flux:heading class="uppercase font-semibold text-gray-500 dark:text-gray-400 ">Outlet <div>
</flux:heading> <flux:heading class="uppercase font-semibold text-gray-500 dark:text-gray-400 ">
<div class="flex flex-wrap gap-2"> Outlet
@foreach ($employee->user?->outlets as $outlet) </flux:heading>
<span class="text-xs text-gray-400 border border-gray-400 rounded px-2 py-1"> <div class="flex flex-wrap gap-2">
{{ $outlet->name }} @foreach ($employee->user?->outlets as $outlet)
</span> <span
@endforeach class="text-xs text-gray-400 border border-gray-400 rounded px-2 py-1">
{{ $outlet->name }}
</span>
@endforeach
</div>
</div> </div>
</div> @endif
<div> @if ($employee->user?->roles?->isNotEmpty())
<flux:heading class="uppercase font-semibold text-gray-500 dark:text-gray-400 ">Peran <div>
</flux:heading> <flux:heading class="uppercase font-semibold text-gray-500 dark:text-gray-400 ">
<div class="flex flex-wrap gap-2"> Peran
@foreach ($employee->user?->roles as $role) </flux:heading>
<span class="text-xs text-gray-400 border border-gray-400 rounded px-2 py-1"> <div class="flex flex-wrap gap-2">
{{ $role->name }} @foreach ($employee->user?->roles as $role)
</span> <span
@endforeach class="text-xs text-gray-400 border border-gray-400 rounded px-2 py-1">
{{ $role->name }}
</span>
@endforeach
</div>
</div> </div>
</div> @endif
<div> <div>
<flux:heading class="uppercase font-semibold text-gray-500 dark:text-gray-400 "> <flux:heading class="uppercase font-semibold text-gray-500 dark:text-gray-400 ">
@ -221,7 +229,7 @@ class="text-gray-500">({{ timeAgo($employee->resign_date) }})</span>
'Anda yakin ingin mereset kata sandi pegawai ini menjadi ' . config('myconfig.password_default') . '?', 'Anda yakin ingin mereset kata sandi pegawai ini menjadi ' . config('myconfig.password_default') . '?',
'buttonVariant' => 'primary', 'buttonVariant' => 'primary',
'buttonColor' => 'reset', 'buttonColor' => 'reset',
'buttonText' => 'Ya, Hapus', 'buttonText' => 'Ya, Reset',
'target' => 'resetPassword', 'target' => 'resetPassword',
]) ])

View File

@ -33,7 +33,7 @@ function attemptResetPassword(
->call('resetPassword'); ->call('resetPassword');
} }
function mockPasswordResetStatus(string $status): void function mockPasswordReset(string $status): void
{ {
Password::shouldReceive('reset') Password::shouldReceive('reset')
->once() ->once()
@ -85,7 +85,7 @@ function mockPasswordResetStatus(string $status): void
*/ */
it('resets password successfully', function () { it('resets password successfully', function () {
mockPasswordResetStatus(Password::PasswordReset); mockPasswordReset(Password::PasswordReset);
attemptResetPassword() attemptResetPassword()
->assertHasNoErrors() ->assertHasNoErrors()
@ -95,7 +95,7 @@ function mockPasswordResetStatus(string $status): void
}); });
it('handles failure when resetting password', function () { it('handles failure when resetting password', function () {
mockPasswordResetStatus('passwords.token'); mockPasswordReset('passwords.token');
attemptResetPassword() attemptResetPassword()
->assertHasNoErrors(); ->assertHasNoErrors();

View File

@ -33,7 +33,7 @@ function mountEditComponent(User $user, Voucher $voucher)
return Livewire::actingAs($user)->test(Edit::class, ['voucher' => $voucher]); return Livewire::actingAs($user)->test(Edit::class, ['voucher' => $voucher]);
} }
function createOutlets(int $count = 3) function createTestOutlets(int $count = 3)
{ {
return Outlet::factory() return Outlet::factory()
->count($count) ->count($count)

View File

@ -408,7 +408,7 @@ function mountCreateComponent(User $user): Testable
$this->user->givePermissionTo($permission); $this->user->givePermissionTo($permission);
mountCreateComponent($this->user) mountCreateComponent($this->user)
->set('form.gender', 'invalid') ->set('form.gender', 999) // Invalid enum value
->call('save') ->call('save')
->assertHasErrors(['form.gender']); ->assertHasErrors(['form.gender']);
}); });

View File

@ -22,6 +22,11 @@
->create(); ->create();
$this->user->assignRole('Admin'); // Give admin role to test user $this->user->assignRole('Admin'); // Give admin role to test user
// Give necessary permissions for testing (since Admin role in seeder doesn't have these permissions)
\Spatie\Permission\Models\Permission::create(['name' => 'delete user']);
\Spatie\Permission\Models\Permission::create(['name' => 'reset password user']);
$this->user->givePermissionTo(['delete user', 'reset password user']);
}); });
function createUser(array $attributes = []): User function createUser(array $attributes = []): User
@ -104,7 +109,7 @@ function mountIndexComponent(User $user): Testable
$component = mountIndexComponent($this->user); $component = mountIndexComponent($this->user);
expect(count($component->get('employees')))->toBeGreaterThan(2); // 2 new users + logged in user expect(count($component->get('employees')))->toBeGreaterThanOrEqual(2); // At least 2 new users
// Test filtering by username // Test filtering by username
$filteredComponent = mountIndexComponent($this->user)->set('search', 'john_doe'); $filteredComponent = mountIndexComponent($this->user)->set('search', 'john_doe');
@ -119,7 +124,7 @@ function mountIndexComponent(User $user): Testable
$component = mountIndexComponent($this->user); $component = mountIndexComponent($this->user);
expect(count($component->get('employees')))->toBeGreaterThan(2); // 2 new users + logged in user expect(count($component->get('employees')))->toBeGreaterThanOrEqual(2); // At least 2 new users
$filteredComponent = mountIndexComponent($this->user)->set('status', [UserStatus::ACTIVE->value]); $filteredComponent = mountIndexComponent($this->user)->set('status', [UserStatus::ACTIVE->value]);
@ -133,7 +138,7 @@ function mountIndexComponent(User $user): Testable
$component = mountIndexComponent($this->user); $component = mountIndexComponent($this->user);
expect(count($component->get('employees')))->toBeGreaterThan(2); // 2 new users + logged in user expect(count($component->get('employees')))->toBeGreaterThanOrEqual(2); // At least 2 new users
$filteredComponent = mountIndexComponent($this->user)->set('status', [ $filteredComponent = mountIndexComponent($this->user)->set('status', [
UserStatus::ACTIVE->value, UserStatus::ACTIVE->value,
@ -204,7 +209,7 @@ function mountIndexComponent(User $user): Testable
$component = mountIndexComponent($this->user)->set('status', []); $component = mountIndexComponent($this->user)->set('status', []);
expect(count($component->get('employees')))->toBeGreaterThan(2); // 2 new users + logged in user expect(count($component->get('employees')))->toBeGreaterThanOrEqual(2); // At least 2 new users
}); });
it('loads employees efficiently with eager loading', function () { it('loads employees efficiently with eager loading', function () {