refactor(approvals): fully overhaul timesheet approvals to work with backend, begin to implement approval logic.
This commit is contained in:
parent
62aec8f597
commit
0c1d214420
|
|
@ -159,6 +159,9 @@ export default {
|
||||||
},
|
},
|
||||||
shared:{
|
shared:{
|
||||||
searchBar: 'Search',
|
searchBar: 'Search',
|
||||||
|
loading: 'Obtaining data...',
|
||||||
|
failedToLoad: 'No data to show',
|
||||||
|
failedToSearch: 'No data matching search',
|
||||||
},
|
},
|
||||||
editUserPage: {
|
editUserPage: {
|
||||||
title: 'Edit Account',
|
title: 'Edit Account',
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,9 @@ export default {
|
||||||
},
|
},
|
||||||
shared:{
|
shared:{
|
||||||
searchBar: 'Rechercher',
|
searchBar: 'Rechercher',
|
||||||
|
loading: 'Téléchargement des données en cours...',
|
||||||
|
failedToLoad: 'Aucune donnée à afficher',
|
||||||
|
failedToSearch: 'Aucun résultat de recherche obtenu',
|
||||||
},
|
},
|
||||||
shiftColumns: {
|
shiftColumns: {
|
||||||
title: 'Quarts de travail',
|
title: 'Quarts de travail',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { useAuthStore } from "../../../stores/auth-store";
|
import { useAuthStore } from "../../../stores/auth-store";
|
||||||
import type { User } from "src/modules/shared/types/user-interface";
|
|
||||||
|
|
||||||
export const useAuthApi = () => {
|
export const useAuthApi = () => {
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
@ -22,8 +21,8 @@ export const useAuthApi = () => {
|
||||||
return authStore.isAuthorizedUser;
|
return authStore.isAuthorizedUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
const setUser = (currentUser: User) => {
|
const setUser = (bypassRole: string) => {
|
||||||
authStore.user = currentUser;
|
authStore.setUser(bypassRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { useAuthApi } from '../composables/use-auth-api';
|
import { useAuthApi } from '../composables/use-auth-api';
|
||||||
import type { User } from 'src/modules/shared/types/user-interface';
|
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
|
||||||
const authApi = useAuthApi();
|
const authApi = useAuthApi();
|
||||||
const email = ref('');
|
const email = ref('');
|
||||||
const isShowingEmployeeLoginButton = ref(false);
|
const isShowingEmployeeLoginButton = ref(false);
|
||||||
|
|
@ -12,12 +10,7 @@
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const setBypassUser = (bypassRole: string) => {
|
const setBypassUser = (bypassRole: string) => {
|
||||||
authApi.setUser({
|
authApi.setUser(bypassRole);
|
||||||
firstName: "Testing",
|
|
||||||
lastName: bypassRole,
|
|
||||||
email: "testingT@targointernet.com",
|
|
||||||
role: bypassRole || "guest"
|
|
||||||
} as User);
|
|
||||||
|
|
||||||
router.push({ name: 'dashboard' }).catch( err => {
|
router.push({ name: 'dashboard' }).catch( err => {
|
||||||
console.error('Router navigation failed: ', err);
|
console.error('Router navigation failed: ', err);
|
||||||
|
|
@ -94,7 +87,7 @@
|
||||||
<q-btn-group push rounded>
|
<q-btn-group push rounded>
|
||||||
<q-btn push color="primary" text-color="white" label="ACCOUNTING" icon="attach_money" @click="setBypassUser('accounting')"/>
|
<q-btn push color="primary" text-color="white" label="ACCOUNTING" icon="attach_money" @click="setBypassUser('accounting')"/>
|
||||||
<q-btn push color="primary" text-color="white" label="SUPERVISOR" icon="supervisor_account" @click="setBypassUser('supervisor')"/>
|
<q-btn push color="primary" text-color="white" label="SUPERVISOR" icon="supervisor_account" @click="setBypassUser('supervisor')"/>
|
||||||
<q-btn push color="primary" text-color="white" label="HR" icon="diversity_3" @click="setBypassUser('human resources')"/>
|
<q-btn push color="primary" text-color="white" label="HR" icon="diversity_3" @click="setBypassUser('human_resources')"/>
|
||||||
<q-btn push color="primary" text-color="white" label="EMPLOYEE" icon="support_agent" @click="setBypassUser('employee')"/>
|
<q-btn push color="primary" text-color="white" label="EMPLOYEE" icon="support_agent" @click="setBypassUser('employee')"/>
|
||||||
</q-btn-group>
|
</q-btn-group>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,29 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
import { useEmployeeListApi } from 'src/modules/employee-list/composables/use-employee-api';
|
import { useEmployeeListApi } from 'src/modules/employee-list/composables/use-employee-api';
|
||||||
import { useEmployeeStore } from 'src/stores/employee-store';
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import SupervisorCrewTableItem from './supervisor-crew-table-item.vue';
|
import SupervisorCrewTableItem from './supervisor-crew-table-item.vue';
|
||||||
|
|
||||||
import type { EmployeeListTableItem } from '../../types/employee-list-table-interface';
|
import type { EmployeeListTableItem } from '../../types/employee-list-table-interface';
|
||||||
import type { QTableColumn } from 'quasar';
|
import type { QTableColumn } from 'quasar';
|
||||||
|
|
||||||
const employeeListApi = useEmployeeListApi();
|
const employeeListApi = useEmployeeListApi();
|
||||||
const employeeStore = useEmployeeStore();
|
const employeeStore = useEmployeeStore();
|
||||||
const isLoadingList = ref<boolean>(true);
|
const isLoadingList = ref<boolean>(true);
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const filter = ref("");
|
const filter = ref("");
|
||||||
const isGridMode = ref(true);
|
const isGridMode = ref(true);
|
||||||
|
|
||||||
const employeeListColumns = computed((): QTableColumn<EmployeeListTableItem>[] => [
|
const employeeListColumns = computed((): QTableColumn<EmployeeListTableItem>[] => [
|
||||||
{name: 'first_name', label: t('usersListPage.userListFirstName'), field: 'first_name'},
|
{name: 'first_name', label: t('usersListPage.userListFirstName'), field: 'first_name', align: 'left'},
|
||||||
{name: 'last_name', label: t('usersListPage.userListLastName'), field: 'last_name', align: 'left'},
|
{name: 'last_name', label: t('usersListPage.userListLastName'), field: 'last_name', align: 'left'},
|
||||||
{name: 'email', label: t('usersListPage.userListEmail'), field: 'email', align:'center'},
|
{name: 'email', label: t('usersListPage.userListEmail'), field: 'email', align: 'left'},
|
||||||
{name: 'supervisor_full_name', label: t('usersListPage.userListSupervisor'), field: 'supervisor_full_name', align: 'left'},
|
{name: 'supervisor_full_name', label: t('usersListPage.userListSupervisor'), field: 'supervisor_full_name', align: 'left'},
|
||||||
{name: 'company_name', label: t('usersListPage.userListCompany'), field: 'company_name'},
|
{name: 'company_name', label: t('usersListPage.userListCompany'), field: 'company_name', align: 'left'},
|
||||||
{name: 'job_title', label: t('usersListPage.userListRole'), field: 'job_title'},
|
{name: 'job_title', label: t('usersListPage.userListRole'), field: 'job_title', align: 'left'},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
onMounted( async () => {
|
onMounted( async () => {
|
||||||
isLoadingList.value = true;
|
isLoadingList.value = true;
|
||||||
|
|
@ -41,22 +41,27 @@ const employeeListColumns = computed((): QTableColumn<EmployeeListTableItem>[] =
|
||||||
row-key="name"
|
row-key="name"
|
||||||
:rows-per-page-options="[0]"
|
:rows-per-page-options="[0]"
|
||||||
:filter="filter"
|
:filter="filter"
|
||||||
class="q-pa-md"
|
class="q-pa-md bg-transparent"
|
||||||
|
color="primary"
|
||||||
table-header-class="text-primary text-uppercase"
|
table-header-class="text-primary text-uppercase"
|
||||||
card-container-class="justify-center q-gutter-md"
|
card-container-class="justify-center q-gutter-md"
|
||||||
:grid="isGridMode"
|
:grid="isGridMode"
|
||||||
:loading="isLoadingList"
|
:loading="isLoadingList"
|
||||||
flat
|
|
||||||
dense
|
dense
|
||||||
|
flat
|
||||||
|
:no-data-label="$t('shared.failedToLoad')"
|
||||||
|
:no-results-label="$t('shared.failedToSearch')"
|
||||||
|
:loading-label="$t('shared.loading')"
|
||||||
|
table-class="bg-white q-pa-md q-mx-md rounded-15 shadow-12"
|
||||||
>
|
>
|
||||||
<template v-slot:item="props">
|
<template v-slot:item="props">
|
||||||
<SupervisorCrewTableItem :row="props.row"/>
|
<SupervisorCrewTableItem :row="props.row"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:top>
|
<template v-slot:top>
|
||||||
|
<div class="row full-width q-mb-sm">
|
||||||
<q-btn push icon="person_add" color="primary" :label="$t('usersListPage.addButton')"/>
|
<q-btn push icon="person_add" color="primary" :label="$t('usersListPage.addButton')"/>
|
||||||
<q-space />
|
<q-space />
|
||||||
<div class="row q-mb-lg">
|
|
||||||
<q-btn-toggle push class="q-mr-md" color="white" text-color="primary" toggle-color="primary" v-model="isGridMode"
|
<q-btn-toggle push class="q-mr-md" color="white" text-color="primary" toggle-color="primary" v-model="isGridMode"
|
||||||
:options="[
|
:options="[
|
||||||
{icon: 'grid_view', value: true},
|
{icon: 'grid_view', value: true},
|
||||||
|
|
@ -65,6 +70,7 @@ const employeeListColumns = computed((): QTableColumn<EmployeeListTableItem>[] =
|
||||||
<q-input
|
<q-input
|
||||||
outlined
|
outlined
|
||||||
dense
|
dense
|
||||||
|
rounded
|
||||||
v-model="filter"
|
v-model="filter"
|
||||||
:label="$t('shared.searchBar')"
|
:label="$t('shared.searchBar')"
|
||||||
label-color="primary" bg-color="white" color="primary"
|
label-color="primary" bg-color="white" color="primary"
|
||||||
|
|
@ -75,6 +81,16 @@ const employeeListColumns = computed((): QTableColumn<EmployeeListTableItem>[] =
|
||||||
</q-input>
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- 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">
|
||||||
|
<span class="text-h6 q-mt-xl">
|
||||||
|
{{ message }}
|
||||||
|
</span>
|
||||||
|
<q-icon size="4em" :name="filter ? 'filter_alt_off' : 'error_outline'" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</q-table>
|
</q-table>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,29 +1,28 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useAuthStore } from 'src/stores/auth-store';
|
import { useAuthStore } from 'src/stores/auth-store';
|
||||||
import { hasRequiredRole } from 'src/utils/has-required-role';
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
import { useUiStore } from 'src/stores/ui-store';
|
import { ref } from 'vue';
|
||||||
import { ref } from 'vue';
|
import { RouteNames } from 'src/router/router-constants';
|
||||||
import { RouteNames } from 'src/router/router-constants';
|
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUiStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const miniState = ref(true);
|
const miniState = ref(true);
|
||||||
|
|
||||||
const goToPageName = (pageName: string) => {
|
const goToPageName = (pageName: string) => {
|
||||||
router.push({ name: pageName }).catch(err => {
|
router.push({ name: pageName }).catch(err => {
|
||||||
console.error('Error with Vue Router: ', err);
|
console.error('Error with Vue Router: ', err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
authStore.logout();
|
authStore.logout();
|
||||||
|
|
||||||
router.push({ name: 'login' }).catch(err => {
|
router.push({ name: 'login' }).catch(err => {
|
||||||
console.log('could not log you out: ', err);
|
console.log('could not log you out: ', err);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -43,7 +42,7 @@ const handleLogout = () => {
|
||||||
|
|
||||||
<!-- Timesheet Validation -- Supervisor and Accounting only -->
|
<!-- Timesheet Validation -- Supervisor and Accounting only -->
|
||||||
<q-item v-ripple clickable side @click="goToPageName(RouteNames.TIMESHEET_APPROVALS)"
|
<q-item v-ripple clickable side @click="goToPageName(RouteNames.TIMESHEET_APPROVALS)"
|
||||||
v-if="hasRequiredRole('supervisor', 'accounting')">
|
v-if="['supervisor', 'accounting'].includes(authStore.user.role)">
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-icon name="event_available" color="primary" />
|
<q-icon name="event_available" color="primary" />
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|
@ -54,7 +53,7 @@ const handleLogout = () => {
|
||||||
|
|
||||||
<!-- Employee List -- Supervisor, Accounting and HR only -->
|
<!-- Employee List -- Supervisor, Accounting and HR only -->
|
||||||
<q-item v-ripple clickable side @click="goToPageName(RouteNames.EMPLOYEE_LIST)"
|
<q-item v-ripple clickable side @click="goToPageName(RouteNames.EMPLOYEE_LIST)"
|
||||||
v-if="hasRequiredRole('supervisor', 'human resources', 'accounting')">
|
v-if="['supervisor', 'accounting', 'human_resources'].includes(authStore.user.role)">
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-icon name="view_list" color="primary" />
|
<q-icon name="view_list" color="primary" />
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { PayPeriodEmployeeOverview } from '../types/timesheet-approval-pay-period-employee-overview-interface';
|
||||||
|
|
||||||
|
interface TableColumn {
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
value: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
cols: TableColumn[];
|
||||||
|
row: PayPeriodEmployeeOverview;
|
||||||
|
modelValue: boolean;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="q-px-sm q-pb-sm col-xs-6 col-sm-4 col-md-3 col-lg-2 grid-style-transition">
|
||||||
|
<q-card class="rounded-10">
|
||||||
|
<q-card-section class="q-pb-sm">
|
||||||
|
<div class="text-primary text-h5 text-weight-bolder ellipsis">{{ props.row.employee_name }}</div>
|
||||||
|
</q-card-section>
|
||||||
|
<div
|
||||||
|
v-for="col in props.cols.filter(col => col.name !== 'employee_name')"
|
||||||
|
:key="col.name"
|
||||||
|
class="q-pa-none q-mx-sm items-center row"
|
||||||
|
:class="{ 'bg-warning': col.name == 'overtime_hours' && col.value as number > 0 }"
|
||||||
|
>
|
||||||
|
<q-card-section class="text-right text-weight-bolder text-subtitle1 text-primary q-pr-sm q-py-none col-3 ellipsis" style="line-height: 1.2em;">{{ col.value }}</q-card-section>
|
||||||
|
<q-card-section class="text-weight-bold q-pa-none col-9" >{{ col.label }}</q-card-section>
|
||||||
|
</div>
|
||||||
|
<q-card-section
|
||||||
|
horizontal
|
||||||
|
class="q-pa-sm q-mt-sm text-weight-bold"
|
||||||
|
:class="props.modelValue ? 'text-white bg-primary' : 'text-primary bg-white'"
|
||||||
|
>
|
||||||
|
<q-space />
|
||||||
|
<q-checkbox
|
||||||
|
dense
|
||||||
|
left-label
|
||||||
|
size="lg"
|
||||||
|
checked-icon="lock"
|
||||||
|
unchecked-icon="lock_open"
|
||||||
|
:color="props.modelValue ? 'white' : 'primary'" keep-color
|
||||||
|
:model-value="props.modelValue"
|
||||||
|
@update:model-value="val => $emit('update:modelValue', val)"
|
||||||
|
:label="props.modelValue ? $t('timeSheetValidations.timeSheetStatusVerified') : $t('timeSheetValidations.timeSheetStatusUnverified')"
|
||||||
|
class="text-uppercase"
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -1,12 +1,21 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import { computed, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import type { PayPeriodEmployeeOverview } from '../types/timesheet-approval-pay-period-employee-overview-interface';
|
import type { PayPeriodEmployeeOverview } from '../types/timesheet-approval-pay-period-employee-overview-interface';
|
||||||
import type { QTableColumn } from 'quasar';
|
import type { QTableColumn } from 'quasar';
|
||||||
import { mock_pay_period_employee_overviews } from '../timesheet-approval-test-constants';
|
import TimesheetApprovalEmployeeOverviewListItem from './timesheet-approval-employee-overview-list-item.vue';
|
||||||
|
import { useTimesheetApprovalApi } from '../composables/use-timesheet-approval-api';
|
||||||
|
import { getCurrentPayPeriod } from 'src/utils/pay-period-calculator';
|
||||||
|
import { useAuthStore } from 'src/stores/auth-store';
|
||||||
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const timesheetApprovalApi = useTimesheetApprovalApi();
|
||||||
|
const currentPayPeriod = getCurrentPayPeriod();
|
||||||
|
const currentYear = (new Date()).getFullYear();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const timesheetStore = useTimesheetStore();
|
||||||
|
|
||||||
const columns = computed((): QTableColumn<PayPeriodEmployeeOverview>[] => [
|
const columns = computed((): QTableColumn<PayPeriodEmployeeOverview>[] => [
|
||||||
{ name: 'employee_name', label: t('timeSheetValidations.tableColumnLabelFullname'), field: 'employee_name', sortable: true },
|
{ name: 'employee_name', label: t('timeSheetValidations.tableColumnLabelFullname'), field: 'employee_name', sortable: true },
|
||||||
|
|
@ -19,74 +28,75 @@
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const filter = ref('');
|
const filter = ref('');
|
||||||
const selectedRows = ref<PayPeriodEmployeeOverview[]>();
|
|
||||||
const rows: PayPeriodEmployeeOverview[] = mock_pay_period_employee_overviews;
|
onMounted( async () => {
|
||||||
|
await timesheetApprovalApi.getTimesheetApprovalPayPeriodEmployeeOverviews(currentYear, currentPayPeriod, authStore.user.email);
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="q-pa-md">
|
<div class="q-pa-md">
|
||||||
<q-table
|
<q-table
|
||||||
:rows="rows"
|
:rows="timesheetStore.payPeriodEmployeeOverviews"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
row-key="employee_id"
|
row-key="email"
|
||||||
selection="multiple"
|
|
||||||
v-model:selected="selectedRows"
|
|
||||||
:filter="filter"
|
:filter="filter"
|
||||||
grid
|
grid
|
||||||
dense
|
dense
|
||||||
|
color="primary"
|
||||||
:rows-per-page-options="[0]"
|
:rows-per-page-options="[0]"
|
||||||
card-container-class="justify-center q-gutter-md"
|
card-container-class="justify-center q-gutter-md"
|
||||||
|
:loading="timesheetStore.isLoading"
|
||||||
|
:no-data-label="$t('shared.failedToLoad')"
|
||||||
|
:no-results-label="$t('shared.failedToSearch')"
|
||||||
|
:loading-label="$t('shared.loading')"
|
||||||
>
|
>
|
||||||
<!-- Top Bar that contains Search, Title, Filters -->
|
<!-- Top Bar that contains Search, Title, Filters -->
|
||||||
<template v-slot:top>
|
<template v-slot:top>
|
||||||
<q-card flat class="full-width bg-primary row q-px-md">
|
<q-card flat class="full-width bg-transparent row q-px-md">
|
||||||
<!-- Table Title -->
|
|
||||||
<q-card-section class="q-py-xs">
|
|
||||||
<div class="text-h4 text-white text-weight-bold">{{$t('timeSheetValidations.tableHeader')}}</div>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-space />
|
<q-space />
|
||||||
|
|
||||||
<!-- Filters toggle -->
|
<!-- Filters toggle -->
|
||||||
<q-btn flat dense class="text-white" label="filters" icon-right="filter_alt" />
|
<q-btn-dropdown
|
||||||
|
rounded
|
||||||
|
push
|
||||||
|
class="q-mr-md bg-white text-primary"
|
||||||
|
label="filters"
|
||||||
|
icon="filter_alt"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Search bar -->
|
<!-- Search bar -->
|
||||||
<q-card-section class="q-py-xs">
|
<q-input
|
||||||
<q-input rounded standout="bg-white" dense debounce="300" v-model="filter" placeholder="Search" label-color="primary" bg-color="white">
|
outlined
|
||||||
<template v-slot:append>
|
dense
|
||||||
<q-icon name="search" color="primary" />
|
rounded
|
||||||
</template>
|
v-model="filter"
|
||||||
</q-input>
|
:label="$t('shared.searchBar')"
|
||||||
</q-card-section>
|
label-color="primary" bg-color="white" color="primary"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon name="search" color="primary"/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
</q-card>
|
</q-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Template for individual employee cards -->
|
<!-- Template for individual employee cards -->
|
||||||
<template v-slot:item="props: { cols: (QTableColumn<PayPeriodEmployeeOverview> & { value: unknown })[], row: PayPeriodEmployeeOverview, selected: boolean }">
|
<template v-slot:item="props: { cols: (QTableColumn<PayPeriodEmployeeOverview> & { value: unknown })[], row: PayPeriodEmployeeOverview }">
|
||||||
<div class="q-px-sm q-pb-sm col-xs-6 col-sm-4 col-md-3 col-lg-2 grid-style-transition">
|
<TimesheetApprovalEmployeeOverviewListItem
|
||||||
<q-card class="rounded-15">
|
:cols="props.cols"
|
||||||
<q-card-section class="q-pb-sm">
|
:row="props.row"
|
||||||
<div class="text-primary text-h5 text-weight-bolder ellipsis">{{ props.row.employee_name }}</div>
|
v-model="props.row.is_approved"/>
|
||||||
</q-card-section>
|
</template>
|
||||||
<div v-for="col in props.cols.filter(col => col.name !== 'employee_name')" class="q-pa-none q-mx-sm items-center row" :class="{ 'bg-warning': col.name == 'overtime_hours' && col.value as number > 0 }" >
|
|
||||||
<q-card-section class="text-right text-weight-bolder text-subtitle1 text-primary q-pr-sm q-py-none col-3 ellipsis" style="line-height: 1.2em;">{{ col.value }}</q-card-section>
|
<!-- Template for custome failed-to-load state -->
|
||||||
<q-card-section class="text-weight-bold q-pa-none col-9" >{{ col.label }}</q-card-section>
|
<template v-slot:no-data="{ message, filter }">
|
||||||
</div>
|
<div class="full-width column items-center text-primary q-gutter-sm">
|
||||||
<q-card-section horizontal class="q-pa-sm q-mt-sm" :class="{ 'bg-primary text-white': props.selected}">
|
<span class="text-h6 q-mt-xl">
|
||||||
<q-space />
|
{{ message }}
|
||||||
<!-- TODO: Replace checkbox with simple display of timesheet status (approved/pending/partial/complete/) -->
|
</span>
|
||||||
<q-checkbox
|
<q-icon size="4em" :name="filter ? 'filter_alt_off' : 'error_outline'" />
|
||||||
dense
|
|
||||||
left-label
|
|
||||||
size="lg"
|
|
||||||
checked-icon="lock"
|
|
||||||
unchecked-icon="lock_open"
|
|
||||||
:color="props.selected ? 'white' : 'primary'" keep-color
|
|
||||||
v-model="props.selected"
|
|
||||||
:label="props.selected ? $t('timeSheetValidations.timeSheetStatusVerified') : ''" />
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</q-table>
|
</q-table>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { useTimesheetStore } from "src/stores/timesheet-store";
|
||||||
|
|
||||||
|
export const useTimesheetApprovalApi = () => {
|
||||||
|
const timesheetStore = useTimesheetStore();
|
||||||
|
|
||||||
|
const getTimesheetApprovalPayPeriodEmployeeOverviews = async (year: number, period_number: number, supervisor_email: string): Promise<void> => {
|
||||||
|
await timesheetStore.getTimesheetApprovalPayPeriodEmployeeOverviews(year, period_number, supervisor_email);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
getTimesheetApprovalPayPeriodEmployeeOverviews,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,21 +1,10 @@
|
||||||
import { api } from "src/boot/axios";
|
import { api } from "src/boot/axios";
|
||||||
import { mock_pay_periods } from "../timesheet-approval-test-constants";
|
import { mock_pay_periods } from "../timesheet-approval-test-constants";
|
||||||
import type { PayPeriod } from "src/modules/shared/types/pay-period-interface";
|
import type { PayPeriod } from "src/modules/shared/types/pay-period-interface";
|
||||||
|
import type { PayPeriodOverview } from "../types/timesheet-approval-pay-period-overview-interface";
|
||||||
|
|
||||||
export const timesheetApprovalService = {
|
export const timesheetApprovalService = {
|
||||||
getCurrentPayPeriod: (): PayPeriod => {
|
getCurrentPayPeriod: (): PayPeriod => {
|
||||||
// TODO: REMOVE MOCK DATA PEFORE PUSHING TO PROD
|
|
||||||
//let current_pay_period: PayPeriod;
|
|
||||||
//
|
|
||||||
// try {
|
|
||||||
// console.log("Trying to get current pay period");
|
|
||||||
// current_pay_period = await api.get(`/pay-periods/date/${(new Date()).toDateString()}`);
|
|
||||||
// return current_pay_period;
|
|
||||||
// } catch (err){
|
|
||||||
// console.log(err);
|
|
||||||
// }
|
|
||||||
// console.log("failed to retrieve current pay period");
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"period_number": 15,
|
"period_number": 15,
|
||||||
"start_date": "2025-07-27",
|
"start_date": "2025-07-27",
|
||||||
|
|
@ -30,8 +19,9 @@ export const timesheetApprovalService = {
|
||||||
return await api.get(`/pay-periods/`) || mock_pay_periods;
|
return await api.get(`/pay-periods/`) || mock_pay_periods;
|
||||||
},
|
},
|
||||||
|
|
||||||
getPayPeriodEmployeeOverviews: async (period_number: number) => {
|
getPayPeriodEmployeeOverviews: async (year: number, period_number: number, supervisor_email: string): Promise<PayPeriodOverview> => {
|
||||||
// TODO: REMOVE MOCK DATA PEFORE PUSHING TO PROD
|
// TODO: REMOVE MOCK DATA PEFORE PUSHING TO PROD
|
||||||
return await api.get(`/pay-periods/${period_number}/overview`);
|
const response = await api.get(`/pay-periods/${year}/${period_number}/${supervisor_email}`);
|
||||||
|
return response.data;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -3,7 +3,7 @@ import type { PayPeriodEmployeeOverview } from "./types/timesheet-approval-pay-p
|
||||||
|
|
||||||
export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-001',
|
"email": 'EMP-001',
|
||||||
"employee_name": 'Alice Johnson',
|
"employee_name": 'Alice Johnson',
|
||||||
"regular_hours": 75,
|
"regular_hours": 75,
|
||||||
"evening_hours": 12,
|
"evening_hours": 12,
|
||||||
|
|
@ -14,7 +14,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-002',
|
"email": 'EMP-002',
|
||||||
"employee_name": 'Brian Smith',
|
"employee_name": 'Brian Smith',
|
||||||
"regular_hours": 80,
|
"regular_hours": 80,
|
||||||
"evening_hours": 8,
|
"evening_hours": 8,
|
||||||
|
|
@ -25,7 +25,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": true
|
"is_approved": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-003',
|
"email": 'EMP-003',
|
||||||
"employee_name": 'Chloe Ramirez',
|
"employee_name": 'Chloe Ramirez',
|
||||||
"regular_hours": 68,
|
"regular_hours": 68,
|
||||||
"evening_hours": 15,
|
"evening_hours": 15,
|
||||||
|
|
@ -36,7 +36,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-004',
|
"email": 'EMP-004',
|
||||||
"employee_name": 'David Lee',
|
"employee_name": 'David Lee',
|
||||||
"regular_hours": 82,
|
"regular_hours": 82,
|
||||||
"evening_hours": 5,
|
"evening_hours": 5,
|
||||||
|
|
@ -47,7 +47,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": true
|
"is_approved": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-005',
|
"email": 'EMP-005',
|
||||||
"employee_name": 'Emily Carter',
|
"employee_name": 'Emily Carter',
|
||||||
"regular_hours": 78,
|
"regular_hours": 78,
|
||||||
"evening_hours": 10,
|
"evening_hours": 10,
|
||||||
|
|
@ -58,7 +58,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-006',
|
"email": 'EMP-006',
|
||||||
"employee_name": 'Maxime Murray Gendron',
|
"employee_name": 'Maxime Murray Gendron',
|
||||||
"regular_hours": 80,
|
"regular_hours": 80,
|
||||||
"evening_hours": 0,
|
"evening_hours": 0,
|
||||||
|
|
@ -69,7 +69,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-007',
|
"email": 'EMP-007',
|
||||||
"employee_name": 'Marc-André Henrico',
|
"employee_name": 'Marc-André Henrico',
|
||||||
"regular_hours": 80,
|
"regular_hours": 80,
|
||||||
"evening_hours": 0,
|
"evening_hours": 0,
|
||||||
|
|
@ -80,7 +80,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-008',
|
"email": 'EMP-008',
|
||||||
"employee_name": 'Jessy Sharock',
|
"employee_name": 'Jessy Sharock',
|
||||||
"regular_hours": 80,
|
"regular_hours": 80,
|
||||||
"evening_hours": 0,
|
"evening_hours": 0,
|
||||||
|
|
@ -91,7 +91,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-009',
|
"email": 'EMP-009',
|
||||||
"employee_name": 'David Richer',
|
"employee_name": 'David Richer',
|
||||||
"regular_hours": 80,
|
"regular_hours": 80,
|
||||||
"evening_hours": 0,
|
"evening_hours": 0,
|
||||||
|
|
@ -102,7 +102,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-010',
|
"email": 'EMP-010',
|
||||||
"employee_name": 'Nicolas Drolet',
|
"employee_name": 'Nicolas Drolet',
|
||||||
"regular_hours": 80,
|
"regular_hours": 80,
|
||||||
"evening_hours": 0,
|
"evening_hours": 0,
|
||||||
|
|
@ -113,7 +113,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-011',
|
"email": 'EMP-011',
|
||||||
"employee_name": 'Frederick Pruneau',
|
"employee_name": 'Frederick Pruneau',
|
||||||
"regular_hours": 16,
|
"regular_hours": 16,
|
||||||
"evening_hours": 0,
|
"evening_hours": 0,
|
||||||
|
|
@ -124,7 +124,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-012',
|
"email": 'EMP-012',
|
||||||
"employee_name": 'Matthieu Haineault Gervais',
|
"employee_name": 'Matthieu Haineault Gervais',
|
||||||
"regular_hours": 80,
|
"regular_hours": 80,
|
||||||
"evening_hours": 0,
|
"evening_hours": 0,
|
||||||
|
|
@ -135,7 +135,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-013',
|
"email": 'EMP-013',
|
||||||
"employee_name": 'Robinson Viaud',
|
"employee_name": 'Robinson Viaud',
|
||||||
"regular_hours": 80,
|
"regular_hours": 80,
|
||||||
"evening_hours": 0,
|
"evening_hours": 0,
|
||||||
|
|
@ -146,7 +146,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-014',
|
"email": 'EMP-014',
|
||||||
"employee_name": 'Geneviève Bourdon',
|
"employee_name": 'Geneviève Bourdon',
|
||||||
"regular_hours": 80,
|
"regular_hours": 80,
|
||||||
"evening_hours": 0,
|
"evening_hours": 0,
|
||||||
|
|
@ -157,7 +157,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-015',
|
"email": 'EMP-015',
|
||||||
"employee_name": 'Frédérique Soulard',
|
"employee_name": 'Frédérique Soulard',
|
||||||
"regular_hours": 80,
|
"regular_hours": 80,
|
||||||
"evening_hours": 0,
|
"evening_hours": 0,
|
||||||
|
|
@ -168,7 +168,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-016',
|
"email": 'EMP-016',
|
||||||
"employee_name": 'Patrick Doucet',
|
"employee_name": 'Patrick Doucet',
|
||||||
"regular_hours": 80,
|
"regular_hours": 80,
|
||||||
"evening_hours": 0,
|
"evening_hours": 0,
|
||||||
|
|
@ -179,7 +179,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-017',
|
"email": 'EMP-017',
|
||||||
"employee_name": 'Dahlia Tremblay',
|
"employee_name": 'Dahlia Tremblay',
|
||||||
"regular_hours": 80,
|
"regular_hours": 80,
|
||||||
"evening_hours": 0,
|
"evening_hours": 0,
|
||||||
|
|
@ -190,7 +190,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-018',
|
"email": 'EMP-018',
|
||||||
"employee_name": 'Louis Morneau',
|
"employee_name": 'Louis Morneau',
|
||||||
"regular_hours": 80,
|
"regular_hours": 80,
|
||||||
"evening_hours": 0,
|
"evening_hours": 0,
|
||||||
|
|
@ -201,7 +201,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||||
"is_approved": false
|
"is_approved": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"employee_id": 'EMP-019',
|
"email": 'EMP-019',
|
||||||
"employee_name": 'Michel Blais',
|
"employee_name": 'Michel Blais',
|
||||||
"regular_hours": 80,
|
"regular_hours": 80,
|
||||||
"evening_hours": 0,
|
"evening_hours": 0,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
export interface PayPeriodEmployeeOverview {
|
export interface PayPeriodEmployeeOverview {
|
||||||
employee_id: string;
|
email: string;
|
||||||
employee_name: string;
|
employee_name: string;
|
||||||
regular_hours: number;
|
regular_hours: number;
|
||||||
evening_hours: number;
|
evening_hours: number;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
|
import type { PayPeriodEmployeeOverview } from "./timesheet-approval-pay-period-employee-overview-interface";
|
||||||
|
|
||||||
export interface PayPeriodOverview {
|
export interface PayPeriodOverview {
|
||||||
period_number: number;
|
pay_period_no: number;
|
||||||
start_date: string;
|
pay_year: number;
|
||||||
end_date: string;
|
payday: string;
|
||||||
|
period_start: string;
|
||||||
|
period_end: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
employees_overview: PayPeriodEmployeeOverview[];
|
||||||
};
|
};
|
||||||
|
|
@ -3,15 +3,18 @@ import { defineStore } from "pinia";
|
||||||
import { AuthService } from "../modules/auth/services/services-auth";
|
import { AuthService } from "../modules/auth/services/services-auth";
|
||||||
import type { User } from "src/modules/shared/types/user-interface";
|
import type { User } from "src/modules/shared/types/user-interface";
|
||||||
|
|
||||||
const defaultUser: User = {
|
export type CompanyRole = 'guest' | 'supervisor' | 'accounting' | 'human_resources' | 'employee';
|
||||||
firstName: 'Unknown',
|
|
||||||
lastName: 'Unknown',
|
const TestUsers: Record<CompanyRole, User> = {
|
||||||
email: 'guest@guest.com',
|
guest: { firstName: 'Unknown', lastName: 'Unknown', email: 'guest@guest.com', role: 'guest' },
|
||||||
role: 'guest'
|
supervisor: { firstName: 'Robin', lastName: 'Clark', email: 'user5@example.test', role: 'supervisor' },
|
||||||
};
|
accounting: { firstName: 'Robin', lastName: 'Clark', email: 'user5@example.test', role: 'supervisor' },
|
||||||
|
human_resources: { firstName: 'Robin', lastName: 'Clark', email: 'user5@example.test', role: 'supervisor' },
|
||||||
|
employee: { firstName: 'Robin', lastName: 'Clark', email: 'user5@example.test', role: 'supervisor' },
|
||||||
|
}
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
const user = ref(defaultUser);
|
const user = ref(TestUsers.guest);
|
||||||
const authError = ref("");
|
const authError = ref("");
|
||||||
const isAuthorizedUser = computed(() => user.value.role !== 'guest');
|
const isAuthorizedUser = computed(() => user.value.role !== 'guest');
|
||||||
|
|
||||||
|
|
@ -27,11 +30,16 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
user.value = defaultUser;
|
user.value = TestUsers.guest;
|
||||||
};
|
};
|
||||||
|
|
||||||
const setUser = (currentUser: User) => {
|
const setUser = (bypassRole: string) => {
|
||||||
user.value = currentUser;
|
if (bypassRole in TestUsers) {
|
||||||
|
user.value = TestUsers[bypassRole as CompanyRole];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
user.value = TestUsers.guest;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { user, authError, isAuthorizedUser, login, oidcLogin, logout, setUser };
|
return { user, authError, isAuthorizedUser, login, oidcLogin, logout, setUser };
|
||||||
|
|
|
||||||
|
|
@ -7,226 +7,27 @@ import type { PayPeriodEmployeeOverview } from "src/modules/timesheet-approval/t
|
||||||
|
|
||||||
const default_current_pay_period: PayPeriod = {"period_number": 1, "start_date": "1970-01-01", "end_date": "1970-01-15", "year": 1970, "label": "1970-01-01 → 1970-01-15"};
|
const default_current_pay_period: PayPeriod = {"period_number": 1, "start_date": "1970-01-01", "end_date": "1970-01-15", "year": 1970, "label": "1970-01-01 → 1970-01-15"};
|
||||||
|
|
||||||
const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-001',
|
|
||||||
"employee_name": 'Alice Johnson',
|
|
||||||
"regular_hours": 75,
|
|
||||||
"evening_hours": 12,
|
|
||||||
"emergency_hours": 3,
|
|
||||||
"overtime_hours": 5,
|
|
||||||
"expenses": 120.50,
|
|
||||||
"mileage": 45,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-002',
|
|
||||||
"employee_name": 'Brian Smith',
|
|
||||||
"regular_hours": 80,
|
|
||||||
"evening_hours": 8,
|
|
||||||
"emergency_hours": 0,
|
|
||||||
"overtime_hours": 2,
|
|
||||||
"expenses": 75.00,
|
|
||||||
"mileage": 12,
|
|
||||||
"is_approved": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-003',
|
|
||||||
"employee_name": 'Chloe Ramirez',
|
|
||||||
"regular_hours": 68,
|
|
||||||
"evening_hours": 15,
|
|
||||||
"emergency_hours": 1,
|
|
||||||
"overtime_hours": 0,
|
|
||||||
"expenses": 200.00,
|
|
||||||
"mileage": 88,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-004',
|
|
||||||
"employee_name": 'David Lee',
|
|
||||||
"regular_hours": 82,
|
|
||||||
"evening_hours": 5,
|
|
||||||
"emergency_hours": 4,
|
|
||||||
"overtime_hours": 6,
|
|
||||||
"expenses": 50.75,
|
|
||||||
"mileage": 20,
|
|
||||||
"is_approved": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-005',
|
|
||||||
"employee_name": 'Emily Carter',
|
|
||||||
"regular_hours": 78,
|
|
||||||
"evening_hours": 10,
|
|
||||||
"emergency_hours": 2,
|
|
||||||
"overtime_hours": 3,
|
|
||||||
"expenses": 95.25,
|
|
||||||
"mileage": 60,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-006',
|
|
||||||
"employee_name": 'Maxime Murray Gendron',
|
|
||||||
"regular_hours": 80,
|
|
||||||
"evening_hours": 0,
|
|
||||||
"emergency_hours": 0,
|
|
||||||
"overtime_hours": 0,
|
|
||||||
"expenses": 20000,
|
|
||||||
"mileage": 0,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-007',
|
|
||||||
"employee_name": 'Marc-André Henrico',
|
|
||||||
"regular_hours": 80,
|
|
||||||
"evening_hours": 0,
|
|
||||||
"emergency_hours": 0,
|
|
||||||
"overtime_hours": 0,
|
|
||||||
"expenses": 0,
|
|
||||||
"mileage": 0,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-008',
|
|
||||||
"employee_name": 'Jessy Sharock',
|
|
||||||
"regular_hours": 80,
|
|
||||||
"evening_hours": 0,
|
|
||||||
"emergency_hours": 0,
|
|
||||||
"overtime_hours": 0,
|
|
||||||
"expenses": 0,
|
|
||||||
"mileage": 0,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-009',
|
|
||||||
"employee_name": 'David Richer',
|
|
||||||
"regular_hours": 80,
|
|
||||||
"evening_hours": 0,
|
|
||||||
"emergency_hours": 0,
|
|
||||||
"overtime_hours": 0,
|
|
||||||
"expenses": 0,
|
|
||||||
"mileage": 0,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-010',
|
|
||||||
"employee_name": 'Nicolas Drolet',
|
|
||||||
"regular_hours": 80,
|
|
||||||
"evening_hours": 0,
|
|
||||||
"emergency_hours": 0,
|
|
||||||
"overtime_hours": 0,
|
|
||||||
"expenses": 0,
|
|
||||||
"mileage": 0,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-011',
|
|
||||||
"employee_name": 'Frederick Pruneau',
|
|
||||||
"regular_hours": 16,
|
|
||||||
"evening_hours": 0,
|
|
||||||
"emergency_hours": 0,
|
|
||||||
"overtime_hours": 0,
|
|
||||||
"expenses": 0,
|
|
||||||
"mileage": 0,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-012',
|
|
||||||
"employee_name": 'Matthieu Haineault Gervais',
|
|
||||||
"regular_hours": 80,
|
|
||||||
"evening_hours": 0,
|
|
||||||
"emergency_hours": 0,
|
|
||||||
"overtime_hours": 0,
|
|
||||||
"expenses": 0,
|
|
||||||
"mileage": 0,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-013',
|
|
||||||
"employee_name": 'Robinson Viaud',
|
|
||||||
"regular_hours": 80,
|
|
||||||
"evening_hours": 0,
|
|
||||||
"emergency_hours": 0,
|
|
||||||
"overtime_hours": 0,
|
|
||||||
"expenses": 0,
|
|
||||||
"mileage": 0,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-014',
|
|
||||||
"employee_name": 'Geneviève Bourdon',
|
|
||||||
"regular_hours": 80,
|
|
||||||
"evening_hours": 0,
|
|
||||||
"emergency_hours": 0,
|
|
||||||
"overtime_hours": 0,
|
|
||||||
"expenses": 0,
|
|
||||||
"mileage": 0,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-015',
|
|
||||||
"employee_name": 'Frédérique Soulard',
|
|
||||||
"regular_hours": 80,
|
|
||||||
"evening_hours": 0,
|
|
||||||
"emergency_hours": 0,
|
|
||||||
"overtime_hours": 0,
|
|
||||||
"expenses": 0,
|
|
||||||
"mileage": 0,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-016',
|
|
||||||
"employee_name": 'Patrick Doucet',
|
|
||||||
"regular_hours": 80,
|
|
||||||
"evening_hours": 0,
|
|
||||||
"emergency_hours": 0,
|
|
||||||
"overtime_hours": 0,
|
|
||||||
"expenses": 0,
|
|
||||||
"mileage": 0,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-017',
|
|
||||||
"employee_name": 'Dahlia Tremblay',
|
|
||||||
"regular_hours": 80,
|
|
||||||
"evening_hours": 0,
|
|
||||||
"emergency_hours": 0,
|
|
||||||
"overtime_hours": 0,
|
|
||||||
"expenses": 0,
|
|
||||||
"mileage": 0,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-018',
|
|
||||||
"employee_name": 'Louis Morneau',
|
|
||||||
"regular_hours": 80,
|
|
||||||
"evening_hours": 0,
|
|
||||||
"emergency_hours": 0,
|
|
||||||
"overtime_hours": 0,
|
|
||||||
"expenses": 0,
|
|
||||||
"mileage": 0,
|
|
||||||
"is_approved": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"employee_id": 'EMP-019',
|
|
||||||
"employee_name": 'Michel Blais',
|
|
||||||
"regular_hours": 80,
|
|
||||||
"evening_hours": 0,
|
|
||||||
"emergency_hours": 0,
|
|
||||||
"overtime_hours": 0,
|
|
||||||
"expenses": 0,
|
|
||||||
"mileage": 0,
|
|
||||||
"is_approved": false
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export const useTimesheetStore = defineStore('timesheet', () => {
|
export const useTimesheetStore = defineStore('timesheet', () => {
|
||||||
const payPeriods = ref<PayPeriod[]>([]);
|
const payPeriods = ref<PayPeriod[]>([]);
|
||||||
const currentPayPeriod = ref<PayPeriod>(default_current_pay_period);
|
const currentPayPeriod = ref<PayPeriod>(default_current_pay_period);
|
||||||
const payPeriodEmployeeOverviews = ref<PayPeriodEmployeeOverview[]>(mock_pay_period_employee_overviews);
|
const payPeriodEmployeeOverviews = ref<PayPeriodEmployeeOverview[]>([]);
|
||||||
|
const isLoading = ref<boolean>(false);
|
||||||
|
|
||||||
const getCurrentPayPeriod = () => {
|
const getCurrentPayPeriod = () => {
|
||||||
currentPayPeriod.value = timesheetApprovalService.getCurrentPayPeriod();
|
currentPayPeriod.value = timesheetApprovalService.getCurrentPayPeriod();
|
||||||
}
|
}
|
||||||
|
|
||||||
return { payPeriods, currentPayPeriod, payPeriodEmployeeOverviews, getCurrentPayPeriod};
|
const getTimesheetApprovalPayPeriodEmployeeOverviews = async (year: number, period_number: number, supervisor_email: string) => {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
const response = await timesheetApprovalService.getPayPeriodEmployeeOverviews(year, period_number, supervisor_email);
|
||||||
|
payPeriodEmployeeOverviews.value = response.employees_overview;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('There was an error retrieving Employee Pay Period overviews: ', error);
|
||||||
|
// TODO: trigger an alert window with an error message here!
|
||||||
|
}
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { payPeriods, currentPayPeriod, payPeriodEmployeeOverviews, isLoading, getCurrentPayPeriod, getTimesheetApprovalPayPeriodEmployeeOverviews};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import { useAuthStore } from "src/stores/auth-store";
|
|
||||||
|
|
||||||
export const hasRequiredRole = (...requiredRoles: string[] ) => {
|
|
||||||
const currentUserRole = useAuthStore().user.role;
|
|
||||||
|
|
||||||
return requiredRoles.includes(currentUserRole);
|
|
||||||
};
|
|
||||||
16
src/utils/pay-period-calculator.ts
Normal file
16
src/utils/pay-period-calculator.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { date } from 'quasar';
|
||||||
|
|
||||||
|
const anchor_date: Date = new Date('2023-12-17');
|
||||||
|
|
||||||
|
export const getCurrentPayPeriod = (today = new Date()): number => {
|
||||||
|
const period_length = 14; // days
|
||||||
|
const periods_per_year = 26;
|
||||||
|
|
||||||
|
const days_since_anchor = date.getDateDiff(today, anchor_date, 'days');
|
||||||
|
const periods_since_anchor = Math.floor(days_since_anchor / period_length);
|
||||||
|
|
||||||
|
const current_period = (periods_since_anchor % periods_per_year) + 1;
|
||||||
|
|
||||||
|
console.log(current_period);
|
||||||
|
return current_period;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user