feat: add role-based visibility for employee management features
This commit is contained in:
parent
8eb3e565ec
commit
58d45f8363
@ -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(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -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)))
|
||||
|
||||
@ -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: () => <span>Role</span>,
|
||||
cell: ({ row }) => {
|
||||
const employee = row.original;
|
||||
const roleName = employee.roles?.[0]?.name ?? '-';
|
||||
...(canViewAll
|
||||
? [
|
||||
{
|
||||
id: 'role',
|
||||
header: () => <span>Role</span>,
|
||||
cell: ({ row }) => {
|
||||
const employee = row.original;
|
||||
const roleName =
|
||||
employee.roles?.[0]?.name ?? '-';
|
||||
|
||||
return (
|
||||
<span className="inline-flex items-center rounded-md bg-muted px-2 py-1 text-xs font-medium">
|
||||
{roleName}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
return (
|
||||
<span className="inline-flex items-center rounded-md bg-muted px-2 py-1 text-xs font-medium">
|
||||
{roleName}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
accessorKey: 'employee.employment_status',
|
||||
id: 'employment_status',
|
||||
|
||||
@ -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<Date | undefined>(undefined);
|
||||
const [resignDate, setResignDate] = useState<Date | undefined>(undefined);
|
||||
const [selectedRole, setSelectedRole] = useState<Role | null>(null);
|
||||
const [selectedRole, setSelectedRole] = useState<Role | null>(
|
||||
!canViewAll && roles.length === 1 ? roles[0] : null,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -117,46 +120,61 @@ export default function EmployeeCreate({ roles }: Props) {
|
||||
message={errors.username}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>
|
||||
Role{' '}
|
||||
<span className="text-destructive">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<Combobox
|
||||
items={roles}
|
||||
itemToStringLabel={(r) => r.name}
|
||||
value={selectedRole}
|
||||
onValueChange={(value) =>
|
||||
setSelectedRole(value)
|
||||
}
|
||||
>
|
||||
<ComboboxInput
|
||||
placeholder="Cari role..."
|
||||
className="w-full"
|
||||
{canViewAll ? (
|
||||
<div className="grid gap-2">
|
||||
<Label>
|
||||
Role{' '}
|
||||
<span className="text-destructive">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<Combobox
|
||||
items={roles}
|
||||
itemToStringLabel={(r) =>
|
||||
r.name
|
||||
}
|
||||
value={selectedRole}
|
||||
onValueChange={(value) =>
|
||||
setSelectedRole(value)
|
||||
}
|
||||
>
|
||||
<ComboboxInput
|
||||
placeholder="Cari role..."
|
||||
className="w-full"
|
||||
/>
|
||||
<ComboboxContent>
|
||||
<ComboboxEmpty>
|
||||
Tidak ada role
|
||||
ditemukan.
|
||||
</ComboboxEmpty>
|
||||
<ComboboxList>
|
||||
{(role) => (
|
||||
<ComboboxItem
|
||||
key={
|
||||
role.id
|
||||
}
|
||||
value={
|
||||
role
|
||||
}
|
||||
>
|
||||
{role.name}
|
||||
</ComboboxItem>
|
||||
)}
|
||||
</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<InputError
|
||||
message={errors.role}
|
||||
/>
|
||||
<ComboboxContent>
|
||||
<ComboboxEmpty>
|
||||
Tidak ada role
|
||||
ditemukan.
|
||||
</ComboboxEmpty>
|
||||
<ComboboxList>
|
||||
{(role) => (
|
||||
<ComboboxItem
|
||||
key={role.id}
|
||||
value={role}
|
||||
>
|
||||
{role.name}
|
||||
</ComboboxItem>
|
||||
)}
|
||||
</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<InputError
|
||||
message={errors.role}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-2">
|
||||
<Label>Role</Label>
|
||||
<div className="flex h-10 w-full items-center rounded-md border border-input bg-muted px-3 py-2 text-sm">
|
||||
{selectedRole?.name ?? '-'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<Employee | null>(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 = (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user