refactor: restrict role-based permissions and update employee management flow accordingly

This commit is contained in:
Yoga Pangestu 2026-06-14 02:30:34 +07:00
parent 57c311eaa1
commit cffea55157
4 changed files with 80 additions and 6 deletions

View File

@ -28,13 +28,27 @@ public function __construct(
public function index(Request $request): Response public function index(Request $request): Response
{ {
$tableQuery = $this->parseDataTableQuery($request); $tableQuery = $this->parseDataTableQuery($request);
$role = $request->string('role')->toString();
$gender = $request->string('gender')->toString();
$employmentStatus = $request->string('employment_status')->toString(); $employmentStatus = $request->string('employment_status')->toString();
$isActive = $request->string('is_active')->toString();
return Inertia::render('admin/hr/employees/Index', [ return Inertia::render('admin/hr/employees/Index', [
'employees' => $this->employeeService->paginateForIndex($tableQuery, $employmentStatus), 'employees' => $this->employeeService->paginateForIndex(
$tableQuery,
$role,
$gender,
$employmentStatus,
$isActive
),
'filters' => $this->dataTableFilters($tableQuery, [ 'filters' => $this->dataTableFilters($tableQuery, [
'role' => $role,
'gender' => $gender,
'employment_status' => $employmentStatus, 'employment_status' => $employmentStatus,
'is_active' => $isActive,
]), ]),
'roles' => Role::assignableSelectOptions(),
'genders' => Gender::selectOptions(),
'employmentStatuses' => EmploymentStatus::selectOptions(), 'employmentStatuses' => EmploymentStatus::selectOptions(),
]); ]);
} }

View File

@ -15,8 +15,13 @@ class EmployeeService
/** /**
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery * @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
*/ */
public function paginateForIndex(array $tableQuery, string $employmentStatus): LengthAwarePaginator public function paginateForIndex(
{ array $tableQuery,
string $role = '',
string $gender = '',
string $employmentStatus = '',
string $isActive = '',
): LengthAwarePaginator {
$query = User::query() $query = User::query()
->with(['profile', 'employee', 'roles']) ->with(['profile', 'employee', 'roles'])
->whereHas('employee') ->whereHas('employee')
@ -31,9 +36,21 @@ public function paginateForIndex(array $tableQuery, string $employmentStatus): L
}); });
}); });
}) })
->when(
$role !== '',
fn ($query) => $query->whereHas('roles', fn ($query) => $query->where('name', $role))
)
->when(
$gender !== '',
fn ($query) => $query->whereHas('profile', fn ($query) => $query->where('gender', $gender))
)
->when( ->when(
$employmentStatus !== '', $employmentStatus !== '',
fn ($query) => $query->whereHas('employee', fn ($query) => $query->where('employment_status', $employmentStatus)) fn ($query) => $query->whereHas('employee', fn ($query) => $query->where('employment_status', $employmentStatus))
)
->when(
$isActive !== '',
fn ($query) => $query->where('is_active', $isActive === '1')
); );
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);

View File

@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { useMediaQuery } from '@vueuse/core';
import { ListFilter, Search } from '@lucide/vue'; import { ListFilter, Search } from '@lucide/vue';
import { computed } from 'vue'; import { computed } from 'vue';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
@ -55,6 +56,10 @@ function onFilterChange(key: string, value: string): void {
function resetFilters(): void { function resetFilters(): void {
emit('filters-reset'); emit('filters-reset');
} }
const isMobile = useMediaQuery('(max-width: 640px)');
const selectSide = computed(() => (isMobile.value ? 'bottom' : 'left'));
const selectSideOffset = computed(() => (isMobile.value ? 4 : 12));
</script> </script>
<template> <template>
@ -83,7 +88,16 @@ function resetFilters(): void {
</Badge> </Badge>
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent align="end" class="w-80 space-y-4 p-4"> <PopoverContent
align="end"
class="w-80 space-y-4 p-4"
@pointer-down-outside="(event) => {
const target = event.target as HTMLElement;
if (target.closest('[data-slot=select-content]') || target.closest('[data-slot=select-trigger]')) {
event.preventDefault();
}
}"
>
<div class="space-y-1"> <div class="space-y-1">
<h4 class="text-sm font-medium"> <h4 class="text-sm font-medium">
Filter Filter
@ -116,7 +130,7 @@ function resetFilters(): void {
<SelectTrigger :id="`filter-${filter.key}`" class="w-full"> <SelectTrigger :id="`filter-${filter.key}`" class="w-full">
<SelectValue :placeholder="filter.placeholder ?? 'Semua'" /> <SelectValue :placeholder="filter.placeholder ?? 'Semua'" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent class="z-[300]" :side="selectSide" :side-offset="selectSideOffset">
<SelectItem value="all"> <SelectItem value="all">
Semua Semua
</SelectItem> </SelectItem>

View File

@ -18,8 +18,13 @@ const props = defineProps<{
search: string; search: string;
sort?: string; sort?: string;
direction?: 'asc' | 'desc'; direction?: 'asc' | 'desc';
role?: string;
gender?: string;
employment_status?: string; employment_status?: string;
is_active?: string;
}; };
roles: EnumOption[];
genders: EnumOption[];
employmentStatuses: EnumOption[]; employmentStatuses: EnumOption[];
}>(); }>();
@ -29,22 +34,46 @@ const search = ref(props.filters.search ?? '');
const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = useDataTableQuery({ const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/hr/employees', url: '/admin/hr/employees',
initial: { ...props.filters }, initial: { ...props.filters },
filterKeys: ['employment_status'], filterKeys: ['role', 'gender', 'employment_status', 'is_active'],
}); });
useDataTableQuerySync(() => props.filters, syncFromServer); useDataTableQuerySync(() => props.filters, syncFromServer);
const filterDefs = computed<DataTableFilterDef[]>(() => [ const filterDefs = computed<DataTableFilterDef[]>(() => [
{
key: 'role',
label: 'Role',
type: 'select',
options: props.roles,
},
{
key: 'gender',
label: 'Jenis Kelamin',
type: 'select',
options: props.genders,
},
{ {
key: 'employment_status', key: 'employment_status',
label: 'Status Kepegawaian', label: 'Status Kepegawaian',
type: 'select', type: 'select',
options: props.employmentStatuses, options: props.employmentStatuses,
}, },
{
key: 'is_active',
label: 'Status Akun',
type: 'select',
options: [
{ value: '1', label: 'Aktif' },
{ value: '0', label: 'Nonaktif' },
],
},
]); ]);
const filterValues = computed(() => ({ const filterValues = computed(() => ({
role: query.value.role ?? '',
gender: query.value.gender ?? '',
employment_status: query.value.employment_status ?? '', employment_status: query.value.employment_status ?? '',
is_active: query.value.is_active ?? '',
})); }));
const currentSort = computed<DataTableSort | null>(() => { const currentSort = computed<DataTableSort | null>(() => {