refactor: restrict role-based permissions and update employee management flow accordingly
This commit is contained in:
parent
57c311eaa1
commit
cffea55157
@ -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(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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']);
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>(() => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user