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:{
|
||||
searchBar: 'Search',
|
||||
loading: 'Obtaining data...',
|
||||
failedToLoad: 'No data to show',
|
||||
failedToSearch: 'No data matching search',
|
||||
},
|
||||
editUserPage: {
|
||||
title: 'Edit Account',
|
||||
|
|
|
|||
|
|
@ -228,6 +228,9 @@ export default {
|
|||
},
|
||||
shared:{
|
||||
searchBar: 'Rechercher',
|
||||
loading: 'Téléchargement des données en cours...',
|
||||
failedToLoad: 'Aucune donnée à afficher',
|
||||
failedToSearch: 'Aucun résultat de recherche obtenu',
|
||||
},
|
||||
shiftColumns: {
|
||||
title: 'Quarts de travail',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useAuthStore } from "../../../stores/auth-store";
|
||||
import type { User } from "src/modules/shared/types/user-interface";
|
||||
|
||||
export const useAuthApi = () => {
|
||||
const authStore = useAuthStore();
|
||||
|
|
@ -22,8 +21,8 @@ export const useAuthApi = () => {
|
|||
return authStore.isAuthorizedUser;
|
||||
};
|
||||
|
||||
const setUser = (currentUser: User) => {
|
||||
authStore.user = currentUser;
|
||||
const setUser = (bypassRole: string) => {
|
||||
authStore.setUser(bypassRole);
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { useAuthApi } from '../composables/use-auth-api';
|
||||
import type { User } from 'src/modules/shared/types/user-interface';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
|
||||
const authApi = useAuthApi();
|
||||
const email = ref('');
|
||||
const isShowingEmployeeLoginButton = ref(false);
|
||||
|
|
@ -12,12 +10,7 @@
|
|||
const router = useRouter();
|
||||
|
||||
const setBypassUser = (bypassRole: string) => {
|
||||
authApi.setUser({
|
||||
firstName: "Testing",
|
||||
lastName: bypassRole,
|
||||
email: "testingT@targointernet.com",
|
||||
role: bypassRole || "guest"
|
||||
} as User);
|
||||
authApi.setUser(bypassRole);
|
||||
|
||||
router.push({ name: 'dashboard' }).catch( err => {
|
||||
console.error('Router navigation failed: ', err);
|
||||
|
|
@ -94,7 +87,7 @@
|
|||
<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="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-group>
|
||||
</q-card-section>
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, 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 SupervisorCrewTableItem from './supervisor-crew-table-item.vue';
|
||||
import { computed, 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 SupervisorCrewTableItem from './supervisor-crew-table-item.vue';
|
||||
|
||||
import type { EmployeeListTableItem } from '../../types/employee-list-table-interface';
|
||||
import type { QTableColumn } from 'quasar';
|
||||
import type { EmployeeListTableItem } from '../../types/employee-list-table-interface';
|
||||
import type { QTableColumn } from 'quasar';
|
||||
|
||||
const employeeListApi = useEmployeeListApi();
|
||||
const employeeStore = useEmployeeStore();
|
||||
const isLoadingList = ref<boolean>(true);
|
||||
const employeeListApi = useEmployeeListApi();
|
||||
const employeeStore = useEmployeeStore();
|
||||
const isLoadingList = ref<boolean>(true);
|
||||
|
||||
const { t } = useI18n();
|
||||
const filter = ref("");
|
||||
const isGridMode = ref(true);
|
||||
const { t } = useI18n();
|
||||
const filter = ref("");
|
||||
const isGridMode = ref(true);
|
||||
|
||||
const employeeListColumns = computed((): QTableColumn<EmployeeListTableItem>[] => [
|
||||
{name: 'first_name', label: t('usersListPage.userListFirstName'), field: 'first_name'},
|
||||
const employeeListColumns = computed((): QTableColumn<EmployeeListTableItem>[] => [
|
||||
{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: '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: 'company_name', label: t('usersListPage.userListCompany'), field: 'company_name'},
|
||||
{name: 'job_title', label: t('usersListPage.userListRole'), field: 'job_title'},
|
||||
]);
|
||||
{name: 'company_name', label: t('usersListPage.userListCompany'), field: 'company_name', align: 'left'},
|
||||
{name: 'job_title', label: t('usersListPage.userListRole'), field: 'job_title', align: 'left'},
|
||||
]);
|
||||
|
||||
onMounted( async () => {
|
||||
isLoadingList.value = true;
|
||||
|
|
@ -41,22 +41,27 @@ const employeeListColumns = computed((): QTableColumn<EmployeeListTableItem>[] =
|
|||
row-key="name"
|
||||
:rows-per-page-options="[0]"
|
||||
:filter="filter"
|
||||
class="q-pa-md"
|
||||
class="q-pa-md bg-transparent"
|
||||
color="primary"
|
||||
table-header-class="text-primary text-uppercase"
|
||||
card-container-class="justify-center q-gutter-md"
|
||||
:grid="isGridMode"
|
||||
:loading="isLoadingList"
|
||||
flat
|
||||
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">
|
||||
<SupervisorCrewTableItem :row="props.row"/>
|
||||
</template>
|
||||
|
||||
<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-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"
|
||||
:options="[
|
||||
{icon: 'grid_view', value: true},
|
||||
|
|
@ -65,6 +70,7 @@ const employeeListColumns = computed((): QTableColumn<EmployeeListTableItem>[] =
|
|||
<q-input
|
||||
outlined
|
||||
dense
|
||||
rounded
|
||||
v-model="filter"
|
||||
:label="$t('shared.searchBar')"
|
||||
label-color="primary" bg-color="white" color="primary"
|
||||
|
|
@ -75,6 +81,16 @@ const employeeListColumns = computed((): QTableColumn<EmployeeListTableItem>[] =
|
|||
</q-input>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,29 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useAuthStore } from 'src/stores/auth-store';
|
||||
import { hasRequiredRole } from 'src/utils/has-required-role';
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import { ref } from 'vue';
|
||||
import { RouteNames } from 'src/router/router-constants';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useAuthStore } from 'src/stores/auth-store';
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import { ref } from 'vue';
|
||||
import { RouteNames } from 'src/router/router-constants';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const uiStore = useUiStore();
|
||||
const router = useRouter();
|
||||
const miniState = ref(true);
|
||||
const authStore = useAuthStore();
|
||||
const uiStore = useUiStore();
|
||||
const router = useRouter();
|
||||
const miniState = ref(true);
|
||||
|
||||
const goToPageName = (pageName: string) => {
|
||||
const goToPageName = (pageName: string) => {
|
||||
router.push({ name: pageName }).catch(err => {
|
||||
console.error('Error with Vue Router: ', err);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
const handleLogout = () => {
|
||||
authStore.logout();
|
||||
|
||||
router.push({ name: 'login' }).catch(err => {
|
||||
console.log('could not log you out: ', err);
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -43,7 +42,7 @@ const handleLogout = () => {
|
|||
|
||||
<!-- Timesheet Validation -- Supervisor and Accounting only -->
|
||||
<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-icon name="event_available" color="primary" />
|
||||
</q-item-section>
|
||||
|
|
@ -54,7 +53,7 @@ const handleLogout = () => {
|
|||
|
||||
<!-- Employee List -- Supervisor, Accounting and HR only -->
|
||||
<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-icon name="view_list" color="primary" />
|
||||
</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">
|
||||
/* eslint-disable */
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { PayPeriodEmployeeOverview } from '../types/timesheet-approval-pay-period-employee-overview-interface';
|
||||
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 timesheetApprovalApi = useTimesheetApprovalApi();
|
||||
const currentPayPeriod = getCurrentPayPeriod();
|
||||
const currentYear = (new Date()).getFullYear();
|
||||
const authStore = useAuthStore();
|
||||
const timesheetStore = useTimesheetStore();
|
||||
|
||||
const columns = computed((): QTableColumn<PayPeriodEmployeeOverview>[] => [
|
||||
{ name: 'employee_name', label: t('timeSheetValidations.tableColumnLabelFullname'), field: 'employee_name', sortable: true },
|
||||
|
|
@ -19,74 +28,75 @@
|
|||
]);
|
||||
|
||||
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>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-table
|
||||
:rows="rows"
|
||||
:rows="timesheetStore.payPeriodEmployeeOverviews"
|
||||
:columns="columns"
|
||||
row-key="employee_id"
|
||||
selection="multiple"
|
||||
v-model:selected="selectedRows"
|
||||
row-key="email"
|
||||
:filter="filter"
|
||||
grid
|
||||
dense
|
||||
color="primary"
|
||||
:rows-per-page-options="[0]"
|
||||
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 -->
|
||||
<template v-slot:top>
|
||||
<q-card flat class="full-width bg-primary 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-card flat class="full-width bg-transparent row q-px-md">
|
||||
<q-space />
|
||||
|
||||
<!-- 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 -->
|
||||
<q-card-section class="q-py-xs">
|
||||
<q-input rounded standout="bg-white" dense debounce="300" v-model="filter" placeholder="Search" label-color="primary" bg-color="white">
|
||||
<q-input
|
||||
outlined
|
||||
dense
|
||||
rounded
|
||||
v-model="filter"
|
||||
:label="$t('shared.searchBar')"
|
||||
label-color="primary" bg-color="white" color="primary"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" color="primary" />
|
||||
<q-icon name="search" color="primary"/>
|
||||
</template>
|
||||
</q-input>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
<!-- Template for individual employee cards -->
|
||||
<template v-slot:item="props: { cols: (QTableColumn<PayPeriodEmployeeOverview> & { value: unknown })[], row: PayPeriodEmployeeOverview, selected: boolean }">
|
||||
<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-15">
|
||||
<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')" 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" :class="{ 'bg-primary text-white': props.selected}">
|
||||
<q-space />
|
||||
<!-- TODO: Replace checkbox with simple display of timesheet status (approved/pending/partial/complete/) -->
|
||||
<q-checkbox
|
||||
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>
|
||||
<template v-slot:item="props: { cols: (QTableColumn<PayPeriodEmployeeOverview> & { value: unknown })[], row: PayPeriodEmployeeOverview }">
|
||||
<TimesheetApprovalEmployeeOverviewListItem
|
||||
:cols="props.cols"
|
||||
:row="props.row"
|
||||
v-model="props.row.is_approved"/>
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -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 { mock_pay_periods } from "../timesheet-approval-test-constants";
|
||||
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 = {
|
||||
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 {
|
||||
"period_number": 15,
|
||||
"start_date": "2025-07-27",
|
||||
|
|
@ -30,8 +19,9 @@ export const timesheetApprovalService = {
|
|||
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
|
||||
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[] = [
|
||||
{
|
||||
"employee_id": 'EMP-001',
|
||||
"email": 'EMP-001',
|
||||
"employee_name": 'Alice Johnson',
|
||||
"regular_hours": 75,
|
||||
"evening_hours": 12,
|
||||
|
|
@ -14,7 +14,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-002',
|
||||
"email": 'EMP-002',
|
||||
"employee_name": 'Brian Smith',
|
||||
"regular_hours": 80,
|
||||
"evening_hours": 8,
|
||||
|
|
@ -25,7 +25,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": true
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-003',
|
||||
"email": 'EMP-003',
|
||||
"employee_name": 'Chloe Ramirez',
|
||||
"regular_hours": 68,
|
||||
"evening_hours": 15,
|
||||
|
|
@ -36,7 +36,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-004',
|
||||
"email": 'EMP-004',
|
||||
"employee_name": 'David Lee',
|
||||
"regular_hours": 82,
|
||||
"evening_hours": 5,
|
||||
|
|
@ -47,7 +47,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": true
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-005',
|
||||
"email": 'EMP-005',
|
||||
"employee_name": 'Emily Carter',
|
||||
"regular_hours": 78,
|
||||
"evening_hours": 10,
|
||||
|
|
@ -58,7 +58,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-006',
|
||||
"email": 'EMP-006',
|
||||
"employee_name": 'Maxime Murray Gendron',
|
||||
"regular_hours": 80,
|
||||
"evening_hours": 0,
|
||||
|
|
@ -69,7 +69,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-007',
|
||||
"email": 'EMP-007',
|
||||
"employee_name": 'Marc-André Henrico',
|
||||
"regular_hours": 80,
|
||||
"evening_hours": 0,
|
||||
|
|
@ -80,7 +80,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-008',
|
||||
"email": 'EMP-008',
|
||||
"employee_name": 'Jessy Sharock',
|
||||
"regular_hours": 80,
|
||||
"evening_hours": 0,
|
||||
|
|
@ -91,7 +91,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-009',
|
||||
"email": 'EMP-009',
|
||||
"employee_name": 'David Richer',
|
||||
"regular_hours": 80,
|
||||
"evening_hours": 0,
|
||||
|
|
@ -102,7 +102,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-010',
|
||||
"email": 'EMP-010',
|
||||
"employee_name": 'Nicolas Drolet',
|
||||
"regular_hours": 80,
|
||||
"evening_hours": 0,
|
||||
|
|
@ -113,7 +113,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-011',
|
||||
"email": 'EMP-011',
|
||||
"employee_name": 'Frederick Pruneau',
|
||||
"regular_hours": 16,
|
||||
"evening_hours": 0,
|
||||
|
|
@ -124,7 +124,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-012',
|
||||
"email": 'EMP-012',
|
||||
"employee_name": 'Matthieu Haineault Gervais',
|
||||
"regular_hours": 80,
|
||||
"evening_hours": 0,
|
||||
|
|
@ -135,7 +135,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-013',
|
||||
"email": 'EMP-013',
|
||||
"employee_name": 'Robinson Viaud',
|
||||
"regular_hours": 80,
|
||||
"evening_hours": 0,
|
||||
|
|
@ -146,7 +146,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-014',
|
||||
"email": 'EMP-014',
|
||||
"employee_name": 'Geneviève Bourdon',
|
||||
"regular_hours": 80,
|
||||
"evening_hours": 0,
|
||||
|
|
@ -157,7 +157,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-015',
|
||||
"email": 'EMP-015',
|
||||
"employee_name": 'Frédérique Soulard',
|
||||
"regular_hours": 80,
|
||||
"evening_hours": 0,
|
||||
|
|
@ -168,7 +168,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-016',
|
||||
"email": 'EMP-016',
|
||||
"employee_name": 'Patrick Doucet',
|
||||
"regular_hours": 80,
|
||||
"evening_hours": 0,
|
||||
|
|
@ -179,7 +179,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-017',
|
||||
"email": 'EMP-017',
|
||||
"employee_name": 'Dahlia Tremblay',
|
||||
"regular_hours": 80,
|
||||
"evening_hours": 0,
|
||||
|
|
@ -190,7 +190,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-018',
|
||||
"email": 'EMP-018',
|
||||
"employee_name": 'Louis Morneau',
|
||||
"regular_hours": 80,
|
||||
"evening_hours": 0,
|
||||
|
|
@ -201,7 +201,7 @@ export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
|||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-019',
|
||||
"email": 'EMP-019',
|
||||
"employee_name": 'Michel Blais',
|
||||
"regular_hours": 80,
|
||||
"evening_hours": 0,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
export interface PayPeriodEmployeeOverview {
|
||||
employee_id: string;
|
||||
email: string;
|
||||
employee_name: string;
|
||||
regular_hours: number;
|
||||
evening_hours: number;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
import type { PayPeriodEmployeeOverview } from "./timesheet-approval-pay-period-employee-overview-interface";
|
||||
|
||||
export interface PayPeriodOverview {
|
||||
period_number: number;
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
pay_period_no: number;
|
||||
pay_year: number;
|
||||
payday: string;
|
||||
period_start: string;
|
||||
period_end: string;
|
||||
label: string;
|
||||
employees_overview: PayPeriodEmployeeOverview[];
|
||||
};
|
||||
|
|
@ -3,15 +3,18 @@ import { defineStore } from "pinia";
|
|||
import { AuthService } from "../modules/auth/services/services-auth";
|
||||
import type { User } from "src/modules/shared/types/user-interface";
|
||||
|
||||
const defaultUser: User = {
|
||||
firstName: 'Unknown',
|
||||
lastName: 'Unknown',
|
||||
email: 'guest@guest.com',
|
||||
role: 'guest'
|
||||
};
|
||||
export type CompanyRole = 'guest' | 'supervisor' | 'accounting' | 'human_resources' | 'employee';
|
||||
|
||||
const TestUsers: Record<CompanyRole, User> = {
|
||||
guest: { firstName: 'Unknown', lastName: 'Unknown', email: 'guest@guest.com', 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', () => {
|
||||
const user = ref(defaultUser);
|
||||
const user = ref(TestUsers.guest);
|
||||
const authError = ref("");
|
||||
const isAuthorizedUser = computed(() => user.value.role !== 'guest');
|
||||
|
||||
|
|
@ -27,11 +30,16 @@ export const useAuthStore = defineStore('auth', () => {
|
|||
};
|
||||
|
||||
const logout = () => {
|
||||
user.value = defaultUser;
|
||||
user.value = TestUsers.guest;
|
||||
};
|
||||
|
||||
const setUser = (currentUser: User) => {
|
||||
user.value = currentUser;
|
||||
const setUser = (bypassRole: string) => {
|
||||
if (bypassRole in TestUsers) {
|
||||
user.value = TestUsers[bypassRole as CompanyRole];
|
||||
}
|
||||
else {
|
||||
user.value = TestUsers.guest;
|
||||
}
|
||||
};
|
||||
|
||||
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 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', () => {
|
||||
const payPeriods = ref<PayPeriod[]>([]);
|
||||
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 = () => {
|
||||
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