From 81f81352734f5653b74c861663abc557fa1e1ba6 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Tue, 4 Aug 2026 22:51:56 +0700 Subject: [PATCH] feat: add role selection to employee create and edit forms with conditional fields --- .../Admin/HR/EmployeeController.php | 16 +- .../Requests/Admin/HR/EmployeeRequest.php | 25 +- .../js/pages/admin/hr/employee/create.tsx | 249 +++++++++++------ resources/js/pages/admin/hr/employee/edit.tsx | 263 +++++++++++------- 4 files changed, 358 insertions(+), 195 deletions(-) diff --git a/app/Http/Controllers/Admin/HR/EmployeeController.php b/app/Http/Controllers/Admin/HR/EmployeeController.php index ecec02a..45027bf 100644 --- a/app/Http/Controllers/Admin/HR/EmployeeController.php +++ b/app/Http/Controllers/Admin/HR/EmployeeController.php @@ -10,6 +10,7 @@ use Illuminate\Http\RedirectResponse; use Inertia\Inertia; use Inertia\Response; +use Spatie\Permission\Models\Role; class EmployeeController extends Controller { @@ -30,13 +31,15 @@ public function index(PaginatedRequest $request): Response public function create(): Response { - return Inertia::render('admin/hr/employee/create'); + return Inertia::render('admin/hr/employee/create', [ + 'roles' => Role::where('name', '!=', 'Developer')->get(['id', 'name']), + ]); } public function store(EmployeeRequest $request): RedirectResponse { return $this->handleAction( - fn () => $this->service->create($request->validated()), + fn() => $this->service->create($request->validated()), 'Pegawai berhasil ditambahkan.', 'admin.hr.employees.index' ); @@ -44,17 +47,18 @@ public function store(EmployeeRequest $request): RedirectResponse public function edit(User $user): Response { - $user->load(['userProfile', 'employee']); + $user->load(['userProfile', 'employee', 'roles']); return Inertia::render('admin/hr/employee/edit', [ 'employee' => $user, + 'roles' => Role::where('name', '!=', 'Developer')->get(['id', 'name']), ]); } public function update(EmployeeRequest $request, User $user): RedirectResponse { return $this->handleAction( - fn () => $this->service->update($user, $request->validated()), + fn() => $this->service->update($user, $request->validated()), 'Pegawai berhasil diperbarui.', 'admin.hr.employees.index' ); @@ -63,7 +67,7 @@ public function update(EmployeeRequest $request, User $user): RedirectResponse public function destroy(User $user): RedirectResponse { return $this->handleAction( - fn () => $this->service->delete($user), + fn() => $this->service->delete($user), 'Pegawai berhasil dihapus.', 'admin.hr.employees.index' ); @@ -82,7 +86,7 @@ public function toggleActive(User $user): RedirectResponse public function resetPassword(User $user): RedirectResponse { return $this->handleAction( - fn () => $this->service->resetPassword($user), + fn() => $this->service->resetPassword($user), 'Kata sandi pegawai berhasil direset ke kata sandi default.', 'admin.hr.employees.index' ); diff --git a/app/Http/Requests/Admin/HR/EmployeeRequest.php b/app/Http/Requests/Admin/HR/EmployeeRequest.php index 31c0d72..5da6a6e 100644 --- a/app/Http/Requests/Admin/HR/EmployeeRequest.php +++ b/app/Http/Requests/Admin/HR/EmployeeRequest.php @@ -15,6 +15,7 @@ public function authorize(): bool public function rules(): array { $userId = $this->route('user')?->id; + $isOwner = $this->input('role') === 'owner'; return [ 'email' => [ @@ -30,15 +31,30 @@ public function rules(): array 'alpha_dash', Rule::unique('users', 'username')->ignore($userId, 'id'), ], + 'role' => ['required', 'string', Rule::exists('roles', 'name')], 'full_name' => ['required', 'string', 'max:200'], 'phone_number' => ['nullable', 'string', 'max:20'], 'gender' => ['nullable', 'in:male,female'], 'birth_date' => ['nullable', 'date'], 'address' => ['nullable', 'string'], - 'join_date' => ['required', 'date'], - 'resign_date' => ['nullable', 'date', 'after_or_equal:join_date'], - 'employment_status' => ['required', Rule::in(['full_time', 'part_time', 'contract', 'internship', 'resigned'])], - 'base_salary' => ['required', 'integer', 'min:0'], + 'join_date' => [ + 'date', + Rule::requiredIf(! $isOwner), + ], + 'resign_date' => [ + 'nullable', + 'date', + 'after_or_equal:join_date', + ], + 'employment_status' => [ + Rule::requiredIf(! $isOwner), + Rule::in(['full_time', 'part_time', 'contract', 'internship', 'resigned']), + ], + 'base_salary' => [ + Rule::requiredIf(! $isOwner), + 'integer', + 'min:0', + ], ]; } @@ -47,6 +63,7 @@ public function attributes(): array return [ 'email' => 'Email', 'username' => 'Username', + 'role' => 'Role', 'full_name' => 'Nama Lengkap', 'phone_number' => 'No. Telepon', 'gender' => 'Jenis Kelamin', diff --git a/resources/js/pages/admin/hr/employee/create.tsx b/resources/js/pages/admin/hr/employee/create.tsx index fd4a3da..ff001b8 100644 --- a/resources/js/pages/admin/hr/employee/create.tsx +++ b/resources/js/pages/admin/hr/employee/create.tsx @@ -8,6 +8,14 @@ import { RupiahInput } from '@/components/rupiah-input'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { + Combobox, + ComboboxContent, + ComboboxEmpty, + ComboboxInput, + ComboboxItem, + ComboboxList, +} from '@/components/ui/combobox'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; @@ -21,9 +29,19 @@ import { import { Textarea } from '@/components/ui/textarea'; import { index as employeeIndex, store } from '@/routes/admin/hr/employees'; -export default function EmployeeCreate() { +type Role = { + id: number; + name: string; +}; + +type Props = { + roles: Role[]; +}; + +export default function EmployeeCreate({ roles }: Props) { const [joinDate, setJoinDate] = useState(undefined); const [resignDate, setResignDate] = useState(undefined); + const [selectedRole, setSelectedRole] = useState(null); return ( <> @@ -55,12 +73,17 @@ export default function EmployeeCreate() {
{({ errors, processing }) => ( <> +
Akun - +
+
+ + r.name} + value={selectedRole} + onValueChange={(value) => + setSelectedRole(value) + } + > + + + + Tidak ada role + ditemukan. + + + {(role) => ( + + {role.name} + + )} + + + + +
@@ -191,94 +254,100 @@ export default function EmployeeCreate() { - - - Pegawai - - -
- - - -
-
- - - -
-
- - - -
-
- - - -
-
-
+ {selectedRole?.name !== 'owner' && ( + + + Pegawai + + +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+ )}
diff --git a/resources/js/pages/admin/hr/employee/edit.tsx b/resources/js/pages/admin/hr/employee/edit.tsx index 0926480..26f2a1f 100644 --- a/resources/js/pages/admin/hr/employee/edit.tsx +++ b/resources/js/pages/admin/hr/employee/edit.tsx @@ -1,12 +1,20 @@ import { Form, Head } from '@inertiajs/react'; import { ArrowLeft } from 'lucide-react'; -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import { DatePicker } from '@/components/date-picker'; import InputError from '@/components/input-error'; import { PhoneNumberInput } from '@/components/phone-number-input'; import { RupiahInput } from '@/components/rupiah-input'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { + Combobox, + ComboboxContent, + ComboboxEmpty, + ComboboxInput, + ComboboxItem, + ComboboxList, +} from '@/components/ui/combobox'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; @@ -20,10 +28,16 @@ import { import { Textarea } from '@/components/ui/textarea'; import { index as employeeIndex, update } from '@/routes/admin/hr/employees'; +type Role = { + id: number; + name: string; +}; + type EmployeeData = { id: number; email: string; username: string; + roles?: { name: string }[]; user_profile: { full_name: string; phone_number: string | null; @@ -41,9 +55,16 @@ type EmployeeData = { type Props = { employee: EmployeeData; + roles: Role[]; }; -export default function EmployeeEdit({ employee }: Props) { +export default function EmployeeEdit({ employee, roles }: Props) { + const currentRole = useMemo( + () => + roles.find((r) => r.name === employee.roles?.[0]?.name) ?? null, + [roles, employee.roles], + ); + const [joinDate, setJoinDate] = useState( employee.employee?.join_date ? new Date(employee.employee.join_date) @@ -59,6 +80,7 @@ export default function EmployeeEdit({ employee }: Props) { ? new Date(employee.user_profile.birth_date) : undefined, ); + const [selectedRole, setSelectedRole] = useState(currentRole); return ( <> @@ -85,12 +107,17 @@ export default function EmployeeEdit({ employee }: Props) { > {({ errors, processing }) => ( <> +
Akun - +
+
+ + r.name} + value={selectedRole} + onValueChange={(value) => + setSelectedRole(value) + } + > + + + + Tidak ada role + ditemukan. + + + {(role) => ( + + {role.name} + + )} + + + + +
@@ -235,98 +302,104 @@ export default function EmployeeEdit({ employee }: Props) { - - - Pegawai - - -
- - - -
-
- - - -
-
- - - -
-
- - - -
-
-
+ {selectedRole?.name !== 'owner' && ( + + + Pegawai + + +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+ )}