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']),
|
||||||
),
|
),
|
||||||
'filters' => $request->only(['employment_status', 'is_active', 'gender']),
|
'filters' => $request->only(['employment_status', 'is_active', 'gender']),
|
||||||
|
'canViewAll' => $this->service->canViewAll(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create(): Response
|
public function create(): Response
|
||||||
{
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
return Inertia::render('admin/hr/employee/create', [
|
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
|
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
|
public function getAll(array $filters = []): Collection
|
||||||
{
|
{
|
||||||
return User::select(['id', 'email', 'username', 'is_active'])
|
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']),
|
'employee' => fn ($q) => $q->select(['id', 'user_id', 'join_date', 'employment_status', 'base_salary']),
|
||||||
'roles' => fn ($q) => $q->select(['id', 'name']),
|
'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($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(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)))
|
->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']),
|
'employee' => fn ($q) => $q->select(['id', 'user_id', 'join_date', 'employment_status', 'base_salary']),
|
||||||
'roles' => fn ($q) => $q->select(['id', 'name']),
|
'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($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($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(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;
|
handleResetPassword: (employee: Employee) => void;
|
||||||
toggleActiveUrl: (id: number) => string;
|
toggleActiveUrl: (id: number) => string;
|
||||||
can: (permission: string) => boolean;
|
can: (permission: string) => boolean;
|
||||||
|
canViewAll: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createEmployeeColumns(
|
export function createEmployeeColumns(
|
||||||
@ -50,6 +51,7 @@ export function createEmployeeColumns(
|
|||||||
handleResetPassword,
|
handleResetPassword,
|
||||||
toggleActiveUrl,
|
toggleActiveUrl,
|
||||||
can,
|
can,
|
||||||
|
canViewAll,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -93,20 +95,25 @@ export function createEmployeeColumns(
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
...(canViewAll
|
||||||
id: 'role',
|
? [
|
||||||
header: () => <span>Role</span>,
|
{
|
||||||
cell: ({ row }) => {
|
id: 'role',
|
||||||
const employee = row.original;
|
header: () => <span>Role</span>,
|
||||||
const roleName = employee.roles?.[0]?.name ?? '-';
|
cell: ({ row }) => {
|
||||||
|
const employee = row.original;
|
||||||
|
const roleName =
|
||||||
|
employee.roles?.[0]?.name ?? '-';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center rounded-md bg-muted px-2 py-1 text-xs font-medium">
|
<span className="inline-flex items-center rounded-md bg-muted px-2 py-1 text-xs font-medium">
|
||||||
{roleName}
|
{roleName}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
accessorKey: 'employee.employment_status',
|
accessorKey: 'employee.employment_status',
|
||||||
id: 'employment_status',
|
id: 'employment_status',
|
||||||
|
|||||||
@ -36,12 +36,15 @@ type Role = {
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
roles: Role[];
|
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 [joinDate, setJoinDate] = useState<Date | undefined>(undefined);
|
||||||
const [resignDate, setResignDate] = 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -117,46 +120,61 @@ export default function EmployeeCreate({ roles }: Props) {
|
|||||||
message={errors.username}
|
message={errors.username}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
{canViewAll ? (
|
||||||
<Label>
|
<div className="grid gap-2">
|
||||||
Role{' '}
|
<Label>
|
||||||
<span className="text-destructive">
|
Role{' '}
|
||||||
*
|
<span className="text-destructive">
|
||||||
</span>
|
*
|
||||||
</Label>
|
</span>
|
||||||
<Combobox
|
</Label>
|
||||||
items={roles}
|
<Combobox
|
||||||
itemToStringLabel={(r) => r.name}
|
items={roles}
|
||||||
value={selectedRole}
|
itemToStringLabel={(r) =>
|
||||||
onValueChange={(value) =>
|
r.name
|
||||||
setSelectedRole(value)
|
}
|
||||||
}
|
value={selectedRole}
|
||||||
>
|
onValueChange={(value) =>
|
||||||
<ComboboxInput
|
setSelectedRole(value)
|
||||||
placeholder="Cari role..."
|
}
|
||||||
className="w-full"
|
>
|
||||||
|
<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>
|
</div>
|
||||||
<ComboboxEmpty>
|
) : (
|
||||||
Tidak ada role
|
<div className="grid gap-2">
|
||||||
ditemukan.
|
<Label>Role</Label>
|
||||||
</ComboboxEmpty>
|
<div className="flex h-10 w-full items-center rounded-md border border-input bg-muted px-3 py-2 text-sm">
|
||||||
<ComboboxList>
|
{selectedRole?.name ?? '-'}
|
||||||
{(role) => (
|
</div>
|
||||||
<ComboboxItem
|
</div>
|
||||||
key={role.id}
|
)}
|
||||||
value={role}
|
|
||||||
>
|
|
||||||
{role.name}
|
|
||||||
</ComboboxItem>
|
|
||||||
)}
|
|
||||||
</ComboboxList>
|
|
||||||
</ComboboxContent>
|
|
||||||
</Combobox>
|
|
||||||
<InputError
|
|
||||||
message={errors.role}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
@ -56,9 +56,10 @@ type EmployeeData = {
|
|||||||
type Props = {
|
type Props = {
|
||||||
employee: EmployeeData;
|
employee: EmployeeData;
|
||||||
roles: Role[];
|
roles: Role[];
|
||||||
|
canViewAll: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function EmployeeEdit({ employee, roles }: Props) {
|
export default function EmployeeEdit({ employee, roles, canViewAll }: Props) {
|
||||||
const currentRole = useMemo(
|
const currentRole = useMemo(
|
||||||
() =>
|
() =>
|
||||||
roles.find((r) => r.name === employee.roles?.[0]?.name) ?? null,
|
roles.find((r) => r.name === employee.roles?.[0]?.name) ?? null,
|
||||||
|
|||||||
@ -40,9 +40,10 @@ type Props = {
|
|||||||
is_active?: string;
|
is_active?: string;
|
||||||
gender?: string;
|
gender?: string;
|
||||||
};
|
};
|
||||||
|
canViewAll: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function EmployeeIndex({ employees, filters }: Props) {
|
export default function EmployeeIndex({ employees, filters, canViewAll }: Props) {
|
||||||
const { can } = useCan();
|
const { can } = useCan();
|
||||||
const [deleting, setDeleting] = useState<Employee | null>(null);
|
const [deleting, setDeleting] = useState<Employee | null>(null);
|
||||||
const [resetPasswordTarget, setResetPasswordTarget] =
|
const [resetPasswordTarget, setResetPasswordTarget] =
|
||||||
@ -102,6 +103,7 @@ export default function EmployeeIndex({ employees, filters }: Props) {
|
|||||||
handleResetPassword: (employee) => setResetPasswordTarget(employee),
|
handleResetPassword: (employee) => setResetPasswordTarget(employee),
|
||||||
toggleActiveUrl: (id) => toggleActive.url(id),
|
toggleActiveUrl: (id) => toggleActive.url(id),
|
||||||
can,
|
can,
|
||||||
|
canViewAll,
|
||||||
});
|
});
|
||||||
|
|
||||||
const filterToolbar = (
|
const filterToolbar = (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user