Merge pull request 'dev/nicolas/timesheet-gui-refactor' (#27) from dev/nicolas/timesheet-gui-refactor into main
Reviewed-on: Targo/targo_frontend#27
This commit is contained in:
commit
3669f65fe4
|
|
@ -1,4 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
<script
|
||||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import type { EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models';
|
||||
|
||||
// const getEmployeeAvatar = (first_name: string, last_name: string) => {
|
||||
|
|
@ -6,8 +9,9 @@
|
|||
// return first_name.charAt(0) + last_name.charAt(0);
|
||||
// };
|
||||
|
||||
const { row } = defineProps<{
|
||||
const { row, index = 0 } = defineProps<{
|
||||
row: EmployeeProfile
|
||||
index?: number
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
onProfileClick: [email: string]
|
||||
|
|
@ -15,37 +19,47 @@
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<q-card
|
||||
v-ripple
|
||||
class="column col-xs-6 col-sm-4 col-md-3 col-lg-2 no-wrap rounded-15 cursor-pointer q-ma-sm"
|
||||
style="max-width: 230px;"
|
||||
@click="emit('onProfileClick', row.email)"
|
||||
<transition
|
||||
appear
|
||||
enter-active-class="animated fadeInUp slow"
|
||||
mode="out-in"
|
||||
>
|
||||
<q-card-section class="col-6 text-center">
|
||||
<q-avatar
|
||||
color="primary"
|
||||
size="8em"
|
||||
class="shadow-3 q-mb-md"
|
||||
>
|
||||
<img
|
||||
src="src/assets/targo-default-avatar.png"
|
||||
alt="employee avatar"
|
||||
class="q-pa-xs"
|
||||
>
|
||||
</q-avatar>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section
|
||||
class="col-grow text-center text-h6 text-weight-medium text-uppercase q-pb-none"
|
||||
style="line-height: 0.8em;"
|
||||
<q-card
|
||||
v-ripple
|
||||
class="column col-xs-6 col-sm-4 col-md-3 col-lg-2 no-wrap rounded-15 cursor-pointer q-ma-sm"
|
||||
style="max-width: 230px;"
|
||||
:style="`animation-delay: ${index / 25}s;`"
|
||||
@click="emit('onProfileClick', row.email)"
|
||||
>
|
||||
<div class="ellipsis text-primary"> {{ row.first_name }} {{ row.last_name }} </div>
|
||||
<q-separator color="primary" class="q-mx-sm q-mt-xs" />
|
||||
<div class=" ellipsis-2-lines text-caption"> {{ row.job_title }} </div>
|
||||
</q-card-section>
|
||||
<q-card-section class="col-6 text-center">
|
||||
<q-avatar
|
||||
:color="row.last_work_day === undefined ? 'accent' : 'negative'"
|
||||
size="8em"
|
||||
class="shadow-3 q-mb-md"
|
||||
>
|
||||
<img
|
||||
src="src/assets/targo-default-avatar.png"
|
||||
alt="employee avatar"
|
||||
class="q-pa-xs"
|
||||
>
|
||||
</q-avatar>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section class="bg-primary text-white text-caption text-center q-py-none col-2 content-center ">
|
||||
<div> {{ row.email }} </div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card-section
|
||||
class="col-grow text-center text-h6 text-weight-medium text-uppercase q-pb-none"
|
||||
style="line-height: 0.8em;"
|
||||
>
|
||||
<div class="ellipsis" :class="row.last_work_day === undefined ? 'text-accent' : 'text-negative'"> {{ row.first_name }} {{ row.last_name }} </div>
|
||||
<q-separator
|
||||
color="accent"
|
||||
class="q-mx-sm q-mt-xs"
|
||||
/>
|
||||
<div class=" ellipsis-2-lines text-caption"> {{ row.job_title }} </div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section class="bg-primary text-white text-caption text-center q-py-none col-2 content-center">
|
||||
<div> {{ row.email }} </div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</transition>
|
||||
</template>
|
||||
|
|
@ -1,31 +1,21 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
<script
|
||||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useEmployeeListApi } from 'src/modules/employee-list/composables/use-employee-api';
|
||||
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import EmployeeListTableItem from 'src/modules/employee-list/components/employee-list-table-item.vue';
|
||||
import type { EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models';
|
||||
import type { QTableColumn } from 'quasar';
|
||||
import { employee_list_columns } from 'src/modules/employee-list/models/employee-profile.models';
|
||||
|
||||
const employee_list_api = useEmployeeListApi();
|
||||
const employee_list_api = useEmployeeListApi();
|
||||
const employee_store = useEmployeeStore();
|
||||
const is_loading_list = ref<boolean>(true);
|
||||
|
||||
const { t } = useI18n();
|
||||
const filter = ref("");
|
||||
const is_grid_mode = ref(true);
|
||||
const pagination = ref({ rowsPerPage: 0 });
|
||||
|
||||
const employee_list_columns = computed((): QTableColumn<EmployeeProfile>[] => [
|
||||
{name: 'first_name', label: t('employee_list.table.first_name'), field: 'first_name', align: 'left'},
|
||||
{name: 'last_name', label: t('employee_list.table.last_name'), field: 'last_name', align: 'left'},
|
||||
{name: 'email', label: t('employee_list.table.email'), field: 'email', align: 'left'},
|
||||
{name: 'supervisor_full_name', label: t('employee_list.table.supervisor'), field: 'supervisor_full_name', align: 'left'},
|
||||
{name: 'company_name', label: t('employee_list.table.company'), field: 'company_name', align: 'left'},
|
||||
{name: 'job_title', label: t('employee_list.table.role'), field: 'job_title', align: 'left'},
|
||||
]);
|
||||
|
||||
onMounted( async () => {
|
||||
onMounted(async () => {
|
||||
is_loading_list.value = true;
|
||||
await employee_list_api.getEmployeeList();
|
||||
is_loading_list.value = false;
|
||||
|
|
@ -41,18 +31,17 @@
|
|||
virtual-scroll
|
||||
title=" "
|
||||
card-style="max-height: 70vh;"
|
||||
:rows="employee_store.employeeList"
|
||||
:columns="employee_list_columns"
|
||||
:rows="employee_store.employee_list"
|
||||
:columns="employee_list_columns"
|
||||
row-key="name"
|
||||
v-model:pagination="pagination"
|
||||
:rows-per-page-options="[0]"
|
||||
:filter="filter"
|
||||
class="q-pa-md bg-transparent"
|
||||
:class="is_grid_mode ? '': 'sticky-header-table'"
|
||||
:class="is_grid_mode ? '' : 'sticky-header-table'"
|
||||
:style="$q.screen.lt.md ? '' : 'width: 80vw;'"
|
||||
:table-class="$q.dark.isActive ? 'q-px-md q-py-none q-mx-md rounded-10 bg-dark' : 'q-px-md q-py-none q-mx-md rounded-10 bg-white'"
|
||||
color="primary"
|
||||
table-header-class="text-primary text-uppercase"
|
||||
color="accent"
|
||||
table-header-class="text-accent text-uppercase"
|
||||
card-container-class="justify-center"
|
||||
:grid="is_grid_mode"
|
||||
:loading="is_loading_list"
|
||||
|
|
@ -61,48 +50,69 @@
|
|||
:loading-label="$t('shared.label.loading')"
|
||||
@row-click="() => console.log('click!')"
|
||||
>
|
||||
<template #header="props">
|
||||
<q-tr
|
||||
:props="props"
|
||||
class="bg-accent"
|
||||
>
|
||||
<q-th
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
>
|
||||
<span class="text-uppercase text-weight-bolder text-white">
|
||||
{{ $t(col.label) }}
|
||||
</span>
|
||||
</q-th>
|
||||
</q-tr>
|
||||
</template>
|
||||
|
||||
<template v-slot:item="props">
|
||||
<EmployeeListTableItem :row="props.row"/>
|
||||
<EmployeeListTableItem
|
||||
:row="props.row"
|
||||
:index="props.rowIndex"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-slot:top>
|
||||
<div class="row full-width q-mb-sm">
|
||||
<q-btn
|
||||
push
|
||||
color="primary"
|
||||
icon="person_add"
|
||||
<q-btn
|
||||
push
|
||||
color="accent"
|
||||
icon="person_add"
|
||||
:label="$t('shared.label.add')"
|
||||
class="text-uppercase"
|
||||
/>
|
||||
|
||||
|
||||
<q-space />
|
||||
|
||||
<q-btn-toggle
|
||||
|
||||
<q-btn-toggle
|
||||
v-model="is_grid_mode"
|
||||
push
|
||||
color="white"
|
||||
text-color="primary"
|
||||
toggle-color="primary"
|
||||
class="q-mr-md"
|
||||
push
|
||||
rounded
|
||||
color="white"
|
||||
text-color="accent"
|
||||
toggle-color="accent"
|
||||
class="q-mr-md"
|
||||
:options="[
|
||||
{icon: 'grid_view', value: true},
|
||||
{icon: 'view_list', value: false},
|
||||
{ icon: 'grid_view', value: true },
|
||||
{ icon: 'view_list', value: false },
|
||||
]"
|
||||
/>
|
||||
<q-input
|
||||
<q-input
|
||||
v-model="filter"
|
||||
outlined
|
||||
dense
|
||||
outlined
|
||||
dense
|
||||
rounded
|
||||
color="primary"
|
||||
bg-color="white"
|
||||
label-color="primary"
|
||||
:label="$t('shared.label.search')"
|
||||
color="accent"
|
||||
bg-color="white"
|
||||
label-color="accent"
|
||||
:label="$t('shared.label.search')"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon
|
||||
name="search"
|
||||
color="primary"
|
||||
<q-icon
|
||||
name="search"
|
||||
color="accent"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
|
|
@ -111,13 +121,13 @@
|
|||
|
||||
<!-- Template for custome failed-to-load state -->
|
||||
<template v-slot:no-data="{ message, filter }">
|
||||
<div class="full-width column items-center text-primary q-gutter-sm">
|
||||
<div class="full-width column items-center text-accent q-gutter-sm">
|
||||
<span class="text-h6 q-mt-xl">
|
||||
{{ message }}
|
||||
</span>
|
||||
<q-icon
|
||||
size="4em"
|
||||
:name="filter ? 'filter_alt_off' : 'error_outline'"
|
||||
<q-icon
|
||||
size="4em"
|
||||
:name="filter ? 'filter_alt_off' : 'error_outline'"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import type { QTableColumn } from "quasar";
|
||||
|
||||
export interface EmployeeProfile {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
|
|
@ -24,4 +26,43 @@ export const default_employee_profile: EmployeeProfile = {
|
|||
last_work_day: '',
|
||||
residence: '',
|
||||
birth_date: '',
|
||||
}
|
||||
}
|
||||
|
||||
export const employee_list_columns: QTableColumn<EmployeeProfile>[] = [
|
||||
{
|
||||
name: 'first_name',
|
||||
label: 'employee_list.table.first_name',
|
||||
field: 'first_name',
|
||||
align: 'left'
|
||||
},
|
||||
{
|
||||
name: 'last_name',
|
||||
label: 'employee_list.table.last_name',
|
||||
field: 'last_name',
|
||||
align: 'left'
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
label: 'employee_list.table.email',
|
||||
field: 'email',
|
||||
align: 'left'
|
||||
},
|
||||
{
|
||||
name: 'supervisor_full_name',
|
||||
label: 'employee_list.table.supervisor',
|
||||
field: 'supervisor_full_name',
|
||||
align: 'left'
|
||||
},
|
||||
{
|
||||
name: 'company_name',
|
||||
label: 'employee_list.table.company',
|
||||
field: 'company_name',
|
||||
align: 'left'
|
||||
},
|
||||
{
|
||||
name: 'job_title',
|
||||
label: 'employee_list.table.role',
|
||||
field: 'job_title',
|
||||
align: 'left'
|
||||
},
|
||||
];
|
||||
|
|
@ -3,8 +3,8 @@ import type { EmployeeProfile } from 'src/modules/employee-list/models/employee-
|
|||
|
||||
export const EmployeeListService = {
|
||||
getEmployeeList: async (): Promise<EmployeeProfile[]> => {
|
||||
const response = await api.get<EmployeeProfile[]>('/employees/employee-list')
|
||||
return response.data;
|
||||
const response = await api.get<{success: boolean, data: EmployeeProfile[], error?: string }>('/employees/employee-list')
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
getEmployeeDetails: async (email: string): Promise<EmployeeProfile> => {
|
||||
|
|
|
|||
|
|
@ -2,14 +2,12 @@
|
|||
setup
|
||||
lang="ts"
|
||||
>
|
||||
/* eslint-disable */
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { Bar } from 'vue-chartjs';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar, colors } from 'quasar';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, TimeScale, type ChartData, type ChartDataset } from 'chart.js';
|
||||
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, TimeScale, type ChartDataset } from 'chart.js';
|
||||
|
||||
const { t } = useI18n();
|
||||
const $q = useQuasar();
|
||||
|
|
@ -27,18 +25,20 @@
|
|||
const expenses_dataset = ref<ChartDataset<'bar'>[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
expenses_dataset.value = [
|
||||
{
|
||||
label: t('timesheet_approvals.table.expenses'),
|
||||
data: all_days.map(day => (day.expenses + day.on_call + day.per_diem)),
|
||||
backgroundColor: colors.getPaletteColor('accent'),
|
||||
},
|
||||
{
|
||||
label: t('timesheet_approvals.table.mileage'),
|
||||
data: all_days.map(day => day.mileage),
|
||||
backgroundColor: colors.getPaletteColor('info'),
|
||||
}
|
||||
]
|
||||
setTimeout(() => {
|
||||
expenses_dataset.value = [
|
||||
{
|
||||
label: t('timesheet_approvals.table.expenses'),
|
||||
data: all_days.map(day => (day.expenses + day.on_call + day.per_diem)),
|
||||
backgroundColor: colors.getPaletteColor('accent'),
|
||||
},
|
||||
{
|
||||
label: t('timesheet_approvals.table.mileage'),
|
||||
data: all_days.map(day => day.mileage),
|
||||
backgroundColor: colors.getPaletteColor('info'),
|
||||
}
|
||||
]
|
||||
}, 100)
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -55,7 +55,6 @@
|
|||
:options="({
|
||||
indexAxis: $q.screen.lt.md ? 'y' : 'x',
|
||||
plugins: {
|
||||
|
||||
title: {
|
||||
display: true,
|
||||
text: t('timesheet_approvals.chart.expenses_title'),
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { colors, useQuasar } from 'quasar';
|
||||
import { Bar } from 'vue-chartjs';
|
||||
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, type ChartData, type ChartDataset } from 'chart.js';
|
||||
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, TimeScale, type ChartDataset } from 'chart.js';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import type { TotalHours } from 'src/modules/timesheets/models/timesheet.models';
|
||||
|
||||
|
|
@ -19,14 +19,14 @@
|
|||
const { t } = useI18n();
|
||||
const $q = useQuasar();
|
||||
|
||||
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale);
|
||||
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, TimeScale);
|
||||
ChartJS.defaults.font.family = '"Roboto", sans-serif';
|
||||
// ChartJS.defaults.maintainAspectRatio = false;
|
||||
ChartJS.defaults.maintainAspectRatio = false;
|
||||
ChartJS.defaults.color = $q.dark.isActive ? '#F5F5F5' : '#616161';
|
||||
|
||||
const timesheet_store = useTimesheetStore();
|
||||
|
||||
const all_days = computed(() => timesheet_store.timesheets.flatMap(week => week.days));
|
||||
const all_days = timesheet_store.timesheets.flatMap(week => week.days);
|
||||
|
||||
const datasetConfig: ChartConfigHoursWorked[] = [
|
||||
{
|
||||
|
|
@ -42,36 +42,38 @@
|
|||
{
|
||||
key: 'emergency',
|
||||
label: t('shared.shift_type.emergency'),
|
||||
color: getComputedStyle(document.body).getPropertyValue('--q-warning').trim(),
|
||||
color: colors.getPaletteColor('warning'),
|
||||
},
|
||||
{
|
||||
key: 'overtime',
|
||||
label: t('shared.shift_type.overtime'),
|
||||
color: getComputedStyle(document.body).getPropertyValue('--q-negative').trim(),
|
||||
color: colors.getPaletteColor('negative'),
|
||||
},
|
||||
];
|
||||
|
||||
const hours_worked_labels = ref<string[]>(all_days.value.map(day => day.date.slice(-5,)));
|
||||
const hours_worked_labels = ref<string[]>(all_days.map(day => day.date.slice(-5,)));
|
||||
const hours_worked_dataset = ref<ChartDataset<'bar'>[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
hours_worked_dataset.value = datasetConfig.map(cfg => ({
|
||||
label: cfg.label,
|
||||
data: all_days.value.map(day => day.daily_hours[cfg.key]),
|
||||
backgroundColor: cfg.color,
|
||||
}));
|
||||
setTimeout(() => {
|
||||
hours_worked_dataset.value = datasetConfig.map(cfg => ({
|
||||
label: cfg.label,
|
||||
data: all_days.map(day => day.daily_hours[cfg.key]),
|
||||
backgroundColor: cfg.color,
|
||||
}));
|
||||
}, 100);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="bg-dark rounded-10 q-pa-sm"
|
||||
:style="`min-height: ${$q.screen.lt.md ? '450px;' : '200px'}`"
|
||||
:style="`min-height: ${$q.screen.lt.md ? '350px;' : '200px'}`"
|
||||
>
|
||||
<Bar
|
||||
:data="{
|
||||
labels: hours_worked_labels,
|
||||
datasets: hours_worked_dataset,
|
||||
labels: hours_worked_labels,
|
||||
}"
|
||||
:options="({
|
||||
indexAxis: $q.screen.lt.md ? 'y' : 'x',
|
||||
|
|
|
|||
|
|
@ -29,20 +29,23 @@
|
|||
const shift_type_totals = ref<ChartDataset<'doughnut'>[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
shift_type_totals.value = [{
|
||||
data: [
|
||||
timesheet_store.current_pay_period_overview!.regular_hours,
|
||||
timesheet_store.current_pay_period_overview!.other_hours.evening_hours,
|
||||
timesheet_store.current_pay_period_overview!.other_hours.emergency_hours,
|
||||
timesheet_store.current_pay_period_overview!.other_hours.overtime_hours,
|
||||
],
|
||||
backgroundColor: [
|
||||
colors.getPaletteColor('accent'), // Regular
|
||||
colors.getPaletteColor('green-10'), // Evening
|
||||
colors.getPaletteColor('warning'), // Emergency
|
||||
colors.getPaletteColor('negative'), // Overtime
|
||||
]
|
||||
}]
|
||||
setTimeout(() => {
|
||||
shift_type_totals.value = [{
|
||||
data: [
|
||||
timesheet_store.current_pay_period_overview!.regular_hours,
|
||||
timesheet_store.current_pay_period_overview!.other_hours.evening_hours,
|
||||
timesheet_store.current_pay_period_overview!.other_hours.emergency_hours,
|
||||
timesheet_store.current_pay_period_overview!.other_hours.overtime_hours,
|
||||
],
|
||||
backgroundColor: [
|
||||
colors.getPaletteColor('accent'), // Regular
|
||||
colors.getPaletteColor('green-10'), // Evening
|
||||
colors.getPaletteColor('warning'), // Emergency
|
||||
colors.getPaletteColor('negative'), // Overtime
|
||||
]
|
||||
}]
|
||||
|
||||
}, 100);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type { PayPeriodOverviewResponse } from "src/modules/timesheet-approval/m
|
|||
|
||||
export const timesheetApprovalService = {
|
||||
getPayPeriodOverviews: async (year: number, period_number: number): Promise<PayPeriodOverviewResponse> => {
|
||||
const response = await api.get(`pay-periods/crew/${year}/${period_number}`);
|
||||
const response = await api.get(`pay-periods/overview/${year}/${period_number}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { default_employee_profile, type EmployeeProfile } from "src/modules/empl
|
|||
|
||||
export const useEmployeeStore = defineStore('employee', () => {
|
||||
const employee = ref<EmployeeProfile>( default_employee_profile );
|
||||
const employeeList = ref<EmployeeProfile[]>([]);
|
||||
const employee_list = ref<EmployeeProfile[]>([]);
|
||||
const isShowingEmployeeAddModifyWindow = ref<boolean>(false);
|
||||
const isLoadingEmployeeProfile = ref(false);
|
||||
const isLoadingEmployeeList = ref(false);
|
||||
|
|
@ -14,7 +14,7 @@ export const useEmployeeStore = defineStore('employee', () => {
|
|||
isLoadingEmployeeList.value = true;
|
||||
try {
|
||||
const response = await EmployeeListService.getEmployeeList();
|
||||
employeeList.value = response;
|
||||
employee_list.value = response;
|
||||
} catch (error) {
|
||||
console.error("Ran into an error fetching employee list: ", error);
|
||||
//TODO: trigger an alert window with an error message here!
|
||||
|
|
@ -35,6 +35,6 @@ export const useEmployeeStore = defineStore('employee', () => {
|
|||
isLoadingEmployeeProfile.value = false;
|
||||
};
|
||||
|
||||
return { employee, employeeList, isShowingEmployeeAddModifyWindow, isLoadingEmployeeList, isLoadingEmployeeProfile, getEmployeeList, getEmployeeDetails };
|
||||
return { employee, employee_list, isShowingEmployeeAddModifyWindow, isLoadingEmployeeList, isLoadingEmployeeProfile, getEmployeeList, getEmployeeDetails };
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user