From 58d45f83635c00950192a448d386a6583225470f Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 6 Aug 2026 13:41:26 +0700 Subject: [PATCH] feat: add role-based visibility for employee management features --- .../Admin/HR/EmployeeController.php | 8 +- app/Services/Admin/HR/EmployeeService.php | 15 +++ .../js/pages/admin/hr/employee/columns.tsx | 33 +++--- .../js/pages/admin/hr/employee/create.tsx | 100 +++++++++++------- resources/js/pages/admin/hr/employee/edit.tsx | 3 +- .../js/pages/admin/hr/employee/index.tsx | 4 +- 6 files changed, 106 insertions(+), 57 deletions(-) diff --git a/app/Http/Controllers/Admin/HR/EmployeeController.php b/app/Http/Controllers/Admin/HR/EmployeeController.php index baf90af..46ae2fe 100644 --- a/app/Http/Controllers/Admin/HR/EmployeeController.php +++ b/app/Http/Controllers/Admin/HR/EmployeeController.php @@ -26,13 +26,19 @@ public function index(PaginatedRequest $request): Response filters: $request->only(['employment_status', 'is_active', 'gender']), ), 'filters' => $request->only(['employment_status', 'is_active', 'gender']), + 'canViewAll' => $this->service->canViewAll(), ]); } public function create(): Response { + $user = auth()->user(); + return Inertia::render('admin/hr/employee/create', [ - 'roles' => Role::where('name', '!=', 'Developer')->get(['id', 'name']), + 'roles' => $this->service->canViewAll() + ? Role::where('name', '!=', 'Developer')->get(['id', 'name']) + : Role::where('name', '=', $user->roles->first()?->name)->get(['id', 'name']), + 'canViewAll' => $this->service->canViewAll(), ]); } diff --git a/app/Services/Admin/HR/EmployeeService.php b/app/Services/Admin/HR/EmployeeService.php index 4659ef4..d280a44 100644 --- a/app/Services/Admin/HR/EmployeeService.php +++ b/app/Services/Admin/HR/EmployeeService.php @@ -9,6 +9,13 @@ class EmployeeService { + private const ADMIN_ROLES = ['developer', 'owner', 'direktur', 'admin-toko']; + + public function canViewAll(): bool + { + return auth()->user()->hasAnyRole(self::ADMIN_ROLES); + } + public function getAll(array $filters = []): Collection { return User::select(['id', 'email', 'username', 'is_active']) @@ -18,6 +25,10 @@ public function getAll(array $filters = []): Collection 'employee' => fn ($q) => $q->select(['id', 'user_id', 'join_date', 'employment_status', 'base_salary']), 'roles' => fn ($q) => $q->select(['id', 'name']), ]) + ->when(! $this->canViewAll(), function ($q) { + $userRoles = auth()->user()->roles->pluck('name'); + $q->whereHas('roles', fn ($rq) => $rq->whereIn('name', $userRoles)); + }) ->when($filters['employment_status'] ?? null, fn ($q, $status) => $q->whereHas('employee', fn ($eq) => $eq->where('employment_status', $status))) ->when(isset($filters['is_active']) && $filters['is_active'] !== '', fn ($q) => $q->where('is_active', filter_var($filters['is_active'], FILTER_VALIDATE_BOOLEAN))) ->when($filters['gender'] ?? null, fn ($q, $gender) => $q->whereHas('userProfile', fn ($uq) => $uq->where('gender', $gender))) @@ -35,6 +46,10 @@ public function paginated(int $perPage = 25, string $search = '', string $sort = 'employee' => fn ($q) => $q->select(['id', 'user_id', 'join_date', 'employment_status', 'base_salary']), 'roles' => fn ($q) => $q->select(['id', 'name']), ]) + ->when(! $this->canViewAll(), function ($q) { + $userRoles = auth()->user()->roles->pluck('name'); + $q->whereHas('roles', fn ($rq) => $rq->whereIn('name', $userRoles)); + }) ->when($search, fn ($q) => $q->whereHas('userProfile', fn ($uq) => $uq->where('full_name', 'like', "%{$search}%"))) ->when($filters['employment_status'] ?? null, fn ($q, $status) => $q->whereHas('employee', fn ($eq) => $eq->where('employment_status', $status))) ->when(isset($filters['is_active']) && $filters['is_active'] !== '', fn ($q) => $q->where('is_active', filter_var($filters['is_active'], FILTER_VALIDATE_BOOLEAN))) diff --git a/resources/js/pages/admin/hr/employee/columns.tsx b/resources/js/pages/admin/hr/employee/columns.tsx index 4e6aebf..959a5ec 100644 --- a/resources/js/pages/admin/hr/employee/columns.tsx +++ b/resources/js/pages/admin/hr/employee/columns.tsx @@ -39,6 +39,7 @@ type CreateColumnsParams = { handleResetPassword: (employee: Employee) => void; toggleActiveUrl: (id: number) => string; can: (permission: string) => boolean; + canViewAll: boolean; }; export function createEmployeeColumns( @@ -50,6 +51,7 @@ export function createEmployeeColumns( handleResetPassword, toggleActiveUrl, can, + canViewAll, } = params; return [ @@ -93,20 +95,25 @@ export function createEmployeeColumns( ); }, }, - { - id: 'role', - header: () => Role, - cell: ({ row }) => { - const employee = row.original; - const roleName = employee.roles?.[0]?.name ?? '-'; + ...(canViewAll + ? [ + { + id: 'role', + header: () => Role, + cell: ({ row }) => { + const employee = row.original; + const roleName = + employee.roles?.[0]?.name ?? '-'; - return ( - - {roleName} - - ); - }, - }, + return ( + + {roleName} + + ); + }, + }, + ] + : []), { accessorKey: 'employee.employment_status', id: 'employment_status', diff --git a/resources/js/pages/admin/hr/employee/create.tsx b/resources/js/pages/admin/hr/employee/create.tsx index de816eb..2b50eb4 100644 --- a/resources/js/pages/admin/hr/employee/create.tsx +++ b/resources/js/pages/admin/hr/employee/create.tsx @@ -36,12 +36,15 @@ type Role = { type Props = { roles: Role[]; + canViewAll: boolean; }; -export default function EmployeeCreate({ roles }: Props) { +export default function EmployeeCreate({ roles, canViewAll }: Props) { const [joinDate, setJoinDate] = useState(undefined); const [resignDate, setResignDate] = useState(undefined); - const [selectedRole, setSelectedRole] = useState(null); + const [selectedRole, setSelectedRole] = useState( + !canViewAll && roles.length === 1 ? roles[0] : null, + ); return ( <> @@ -117,46 +120,61 @@ export default function EmployeeCreate({ roles }: Props) { message={errors.username} /> -
- - r.name} - value={selectedRole} - onValueChange={(value) => - setSelectedRole(value) - } - > - + + + r.name + } + value={selectedRole} + onValueChange={(value) => + setSelectedRole(value) + } + > + + + + Tidak ada role + ditemukan. + + + {(role) => ( + + {role.name} + + )} + + + + - - - Tidak ada role - ditemukan. - - - {(role) => ( - - {role.name} - - )} - - - - -
+ + ) : ( +
+ +
+ {selectedRole?.name ?? '-'} +
+
+ )} diff --git a/resources/js/pages/admin/hr/employee/edit.tsx b/resources/js/pages/admin/hr/employee/edit.tsx index 0ae7f94..a926396 100644 --- a/resources/js/pages/admin/hr/employee/edit.tsx +++ b/resources/js/pages/admin/hr/employee/edit.tsx @@ -56,9 +56,10 @@ type EmployeeData = { type Props = { employee: EmployeeData; roles: Role[]; + canViewAll: boolean; }; -export default function EmployeeEdit({ employee, roles }: Props) { +export default function EmployeeEdit({ employee, roles, canViewAll }: Props) { const currentRole = useMemo( () => roles.find((r) => r.name === employee.roles?.[0]?.name) ?? null, diff --git a/resources/js/pages/admin/hr/employee/index.tsx b/resources/js/pages/admin/hr/employee/index.tsx index f79989d..f8a04da 100644 --- a/resources/js/pages/admin/hr/employee/index.tsx +++ b/resources/js/pages/admin/hr/employee/index.tsx @@ -40,9 +40,10 @@ type Props = { is_active?: string; gender?: string; }; + canViewAll: boolean; }; -export default function EmployeeIndex({ employees, filters }: Props) { +export default function EmployeeIndex({ employees, filters, canViewAll }: Props) { const { can } = useCan(); const [deleting, setDeleting] = useState(null); const [resetPasswordTarget, setResetPasswordTarget] = @@ -102,6 +103,7 @@ export default function EmployeeIndex({ employees, filters }: Props) { handleResetPassword: (employee) => setResetPasswordTarget(employee), toggleActiveUrl: (id) => toggleActive.url(id), can, + canViewAll, }); const filterToolbar = (