Merge pull request 'dev/nicolas/approvals-refactor' (#4) from dev/nicolas/approvals-refactor into main
Reviewed-on: Targo/targo_frontend#4
This commit is contained in:
commit
b0dcfdc73c
|
|
@ -4,26 +4,19 @@ import { createI18n } from 'vue-i18n';
|
|||
import messages from 'src/i18n';
|
||||
|
||||
export type MessageLanguages = keyof typeof messages;
|
||||
// Type-define 'en-US' as the master schema for the resource
|
||||
export type MessageSchema = typeof messages['en'];
|
||||
export type MessageSchema = typeof messages['en-CA'];
|
||||
|
||||
// See https://vue-i18n.intlify.dev/guide/advanced/typescript.html#global-resource-schema-type-definition
|
||||
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
||||
declare module 'vue-i18n' {
|
||||
// define the locale messages schema
|
||||
export interface DefineLocaleMessage extends MessageSchema {}
|
||||
|
||||
// define the datetime format schema
|
||||
export interface DefineDateTimeFormat {}
|
||||
|
||||
// define the number format schema
|
||||
export interface DefineNumberFormat {}
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-empty-object-type */
|
||||
|
||||
/* eslint-enable @typescript-eslint/no-empty-object-type */
|
||||
export default defineBoot(({ app }) => {
|
||||
const i18n = createI18n<{ message: MessageSchema }, MessageLanguages>({
|
||||
locale: 'fr',
|
||||
locale: 'fr-FR',
|
||||
legacy: false,
|
||||
messages,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// app global css in SCSS form
|
||||
@each $size in (5, 10, 15, 20, 25) {
|
||||
.rounded-#{$size} {
|
||||
border-radius: #{$size}px;
|
||||
border-radius: #{$size}px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export default {
|
|||
},
|
||||
navBar: {
|
||||
userMenuHome: 'Homepage',
|
||||
userMenuEmployeeList: 'Employee list',
|
||||
userMenuEmployeeList: 'Employee Directory',
|
||||
userMenuShiftValidation: 'Timesheet Approval',
|
||||
userMenuProfile: 'Profile',
|
||||
userMenuHelp: 'Help',
|
||||
|
|
@ -140,7 +140,7 @@ export default {
|
|||
card_4: 'Customers',
|
||||
},
|
||||
usersListPage: {
|
||||
tableHeader: 'Employee list',
|
||||
tableHeader: 'Employee Directory',
|
||||
searchInput: 'Search',
|
||||
userListFirstName: 'First name',
|
||||
userListLastName: 'Last name',
|
||||
|
|
@ -159,6 +159,10 @@ export default {
|
|||
},
|
||||
shared:{
|
||||
searchBar: 'Search',
|
||||
loading: 'Obtaining data...',
|
||||
failedToLoad: 'No data to show',
|
||||
failedToSearch: 'No data matching search',
|
||||
languageLabel: 'Language',
|
||||
},
|
||||
editUserPage: {
|
||||
title: 'Edit Account',
|
||||
|
|
@ -237,11 +241,12 @@ export default {
|
|||
passwordValidation: 'Password must meet all criteria.',
|
||||
confirmPasswordValidation: 'Password must match new Password.',
|
||||
},
|
||||
pagesTitles: {
|
||||
newUserPageTitle: 'New user',
|
||||
updateUserPageTitle: 'Update user',
|
||||
timeSheetPageTitle: 'Time sheet',
|
||||
timeSheetValidationsIdPageTitle: 'Time sheet',
|
||||
pageTitles: {
|
||||
employeeDirectory: 'Employee Directory',
|
||||
newUsers: 'New user',
|
||||
updateUsers: 'Update user',
|
||||
timeSheets: 'Time sheet',
|
||||
timeSheetValidations: 'Time sheet',
|
||||
},
|
||||
timeSheet: {
|
||||
timeSheetTab_1: 'Shifts',
|
||||
|
|
@ -262,7 +267,7 @@ export default {
|
|||
sickDay: 'Sick working day',
|
||||
vacancyDay: 'vacation',
|
||||
holiday: 'Holiday',
|
||||
dateRangesWeek: 'Week from',
|
||||
dateRangesFrom: 'from',
|
||||
dateRangesTo: 'to',
|
||||
shiftBankedHours: 'Total hours to bank',
|
||||
bankedHoursHint_1: ' on',
|
||||
|
|
@ -291,14 +296,13 @@ export default {
|
|||
mileage: 'Mileage',
|
||||
},
|
||||
timeSheetValidations: {
|
||||
tableHeader: 'List of employees',
|
||||
tableColumnLabelFullname: 'Full name',
|
||||
tableColumnLabelRegularHours: 'regular hours',
|
||||
tableColumnLabelEveningHours: 'evening hours',
|
||||
tableColumnLabelEmergencyHours: 'emergency hours',
|
||||
tableColumnLabelOvertime: 'overtime hours',
|
||||
tableColumnLabelExpenses: 'of expenses',
|
||||
tableColumnLabelMileage: 'of mileage',
|
||||
tableColumnLabelEveningHours: 'evening',
|
||||
tableColumnLabelEmergencyHours: 'emergency',
|
||||
tableColumnLabelOvertime: 'overtime',
|
||||
tableColumnLabelExpenses: 'expenses',
|
||||
tableColumnLabelMileage: 'mileage',
|
||||
actionTitle: 'Please save the changes made.',
|
||||
actionButton: 'Save',
|
||||
timeSheetStatusVerified: 'approved',
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ export default {
|
|||
},
|
||||
navBar: {
|
||||
userMenuHome: 'Accueil',
|
||||
userMenuEmployeeList: 'Liste employés',
|
||||
userMenuEmployeeList: 'Répertoire employés',
|
||||
userMenuShiftValidation: 'Valider les heures',
|
||||
userMenuProfile: 'Profil',
|
||||
userMenuHelp: 'Aide',
|
||||
|
|
@ -181,11 +181,12 @@ export default {
|
|||
deleteAll: 'Supprimer tout',
|
||||
close: 'Fermer',
|
||||
},
|
||||
pagesTitles: {
|
||||
newUserPageTitle: 'Nouvel utilisateur',
|
||||
updateUserPageTitle: 'Mettre à jour l’utilisateur',
|
||||
timeSheetPageTitle: 'Carte de temps',
|
||||
timeSheetValidationsIdPageTitle: 'Carte de temps',
|
||||
pageTitles: {
|
||||
employeeDirectory: 'Répertoire des Employés',
|
||||
newUsers: 'Nouvel utilisateur',
|
||||
updateUsers: 'Mettre à jour l’utilisateur',
|
||||
timeSheets: 'Carte de temps',
|
||||
timeSheetValidations: 'Validation cartes de temps',
|
||||
},
|
||||
profilePage: {
|
||||
title: 'Profil',
|
||||
|
|
@ -228,6 +229,10 @@ 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',
|
||||
languageLabel: 'Langue',
|
||||
},
|
||||
shiftColumns: {
|
||||
title: 'Quarts de travail',
|
||||
|
|
@ -312,7 +317,7 @@ export default {
|
|||
sickDay: 'Maladie',
|
||||
vacancyDay: 'Vacances',
|
||||
holiday: 'Férié',
|
||||
dateRangesWeek: 'Semaine du',
|
||||
dateRangesFrom: 'du',
|
||||
dateRangesTo: 'au',
|
||||
shiftBankedHours: 'Totale d’heures à banquer',
|
||||
bankedHoursHint_1: ' sur',
|
||||
|
|
@ -341,14 +346,13 @@ export default {
|
|||
mileage: 'Kilometrage',
|
||||
},
|
||||
timeSheetValidations: {
|
||||
tableHeader: 'Liste des employés',
|
||||
tableColumnLabelFullname: 'nom complet',
|
||||
tableColumnLabelRegularHours: 'heures régulières',
|
||||
tableColumnLabelEveningHours: 'heures de soir',
|
||||
tableColumnLabelEmergencyHours: 'heures d’urgence',
|
||||
tableColumnLabelOvertime: 'heures supplémentaires',
|
||||
tableColumnLabelExpenses: 'de dépenses',
|
||||
tableColumnLabelMileage: 'de kilométrage',
|
||||
tableColumnLabelEveningHours: 'soir',
|
||||
tableColumnLabelEmergencyHours: 'urgence',
|
||||
tableColumnLabelOvertime: 'supplémentaires',
|
||||
tableColumnLabelExpenses: 'dépenses',
|
||||
tableColumnLabelMileage: 'kilométrage',
|
||||
actionTitle: 'Veuillez enregistrer les changements effectués.',
|
||||
actionButton: 'Enregistrer',
|
||||
timeSheetStatusVerified: 'validé',
|
||||
|
|
@ -375,7 +379,7 @@ export default {
|
|||
unlockToolTip: 'Déverrouiller la semaine',
|
||||
},
|
||||
usersListPage: {
|
||||
tableHeader: 'Liste d’employées',
|
||||
tableHeader: 'Répertoire du personnel',
|
||||
searchInput: 'rechercher',
|
||||
userListFirstName: 'prénom',
|
||||
userListLastName: 'nom de famille',
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@ import enCA from './en-ca';
|
|||
import frCA from './fr-ca';
|
||||
|
||||
export default {
|
||||
'en': enCA,
|
||||
'fr': frCA,
|
||||
'en-CA': enCA,
|
||||
'fr-FR': frCA,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import { useEmployeeListApi } from 'src/modules/employee-list/composables/use-em
|
|||
<template>
|
||||
<q-card
|
||||
v-ripple
|
||||
class="rounded-15 bg-white col-xs-6 col-sm-4 col-md-3 col-lg-2 column no-wrap cursor-pointer"
|
||||
class="rounded-15 bg-white col-xs-6 col-sm-4 col-md-3 col-lg-2 column no-wrap cursor-pointer q-ma-sm"
|
||||
style="max-width: 230px;"
|
||||
@click="onProfileCardClick(props.row.email)"
|
||||
>
|
||||
|
|
@ -37,9 +37,9 @@ import { useEmployeeListApi } from 'src/modules/employee-list/composables/use-em
|
|||
<q-card-section class="text-center text-h6 text-primary text-weight-medium text-uppercase q-pb-none col-2 content-end" style="line-height: 0.7em;">
|
||||
<div class="ellipsis">
|
||||
{{ props.row.first_name }} {{ props.row.last_name }}
|
||||
<q-separator color="primary" />
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-separator color="primary" class="q-mx-sm q-mt-xs" />
|
||||
<q-card-section class="text-caption text-grey-8 text-body2 text-uppercase q-pt-none text-center col content-start" style="min-height: 5em;">
|
||||
<div class=" ellipsis-2-lines">
|
||||
{{ props.row.job_title }}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,30 @@
|
|||
<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 pagination = ref({ rowsPerPage: 0 });
|
||||
|
||||
const employeeListColumns = computed((): QTableColumn<EmployeeListTableItem>[] => [
|
||||
{name: 'first_name', label: t('usersListPage.userListFirstName'), field: 'first_name'},
|
||||
{name: 'last_name', label: t('usersListPage.userListLastName'), field: 'last_name', align: 'left'},
|
||||
{name: 'email', label: t('usersListPage.userListEmail'), field: 'email', align:'center'},
|
||||
{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'},
|
||||
]);
|
||||
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: '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', align: 'left'},
|
||||
{name: 'job_title', label: t('usersListPage.userListRole'), field: 'job_title', align: 'left'},
|
||||
]);
|
||||
|
||||
onMounted( async () => {
|
||||
isLoadingList.value = true;
|
||||
|
|
@ -35,28 +36,39 @@ const employeeListColumns = computed((): QTableColumn<EmployeeListTableItem>[] =
|
|||
<template>
|
||||
<div class="q-pa-lg col">
|
||||
<q-table
|
||||
dense
|
||||
flat
|
||||
hide-pagination
|
||||
virtual-scroll
|
||||
title=" "
|
||||
card-style="max-height: 70vh;"
|
||||
:rows="employeeStore.employeeList"
|
||||
:columns="employeeListColumns"
|
||||
row-key="name"
|
||||
v-model:pagination="pagination"
|
||||
: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"
|
||||
card-container-class="justify-center"
|
||||
:grid="isGridMode"
|
||||
:loading="isLoadingList"
|
||||
flat
|
||||
dense
|
||||
: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-10 shadow-12"
|
||||
table-style=""
|
||||
@row-click="() => console.log('click!')"
|
||||
>
|
||||
<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 +77,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 +88,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>
|
||||
|
|
@ -6,8 +6,8 @@
|
|||
<template>
|
||||
<q-page>
|
||||
<EmployeeListAddModifyDialog />
|
||||
<div class="text-h4 row justify-center q-py-sm q-mt-lg text-uppercase text-weight-bolder text-primary">
|
||||
{{ $t('usersListPage.tableHeader') }}
|
||||
<div class="text-h4 row justify-center q-py-sm q-mt-lg text-uppercase text-weight-bolder text-grey-8">
|
||||
{{ $t('pageTitles.employeeDirectory') }}
|
||||
</div>
|
||||
<SupervisorCrewTable />
|
||||
</q-page>
|
||||
|
|
|
|||
|
|
@ -2,19 +2,33 @@
|
|||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { locale } = useI18n();
|
||||
const setLocale = (newLocale: string) => { locale.value = newLocale; };
|
||||
const localeOptions = [
|
||||
{ value: 'en', label: 'English' },
|
||||
{ value: 'fr', label: 'Francais' },
|
||||
{ value: 'en-CA', label: 'English' },
|
||||
{ value: 'fr-FR', label: 'Francais' },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-btn-dropdown flat :label=locale class="rounded-borders" icon="language">
|
||||
<q-btn-dropdown flat :label="$t('shared.languageLabel')" class="rounded-borders" icon="language">
|
||||
<q-list>
|
||||
<q-item clickable v-close-popup v-for="option in localeOptions" :key="option.value" @click="setLocale(option.value)">
|
||||
<q-item clickable v-close-popup v-for="option in localeOptions" :key="option.value" @click="locale = option.value">
|
||||
<q-item-section>{{ option.label }}</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
|
||||
<!-- <q-select
|
||||
v-model="locale"
|
||||
:options="localeOptions"
|
||||
dense
|
||||
borderless
|
||||
emit-value
|
||||
map-options
|
||||
hide-dropdown-icon
|
||||
class="text-white"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="language" color="white"/>
|
||||
</template>
|
||||
</q-select> -->
|
||||
</template>
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import { useAuthStore } from 'src/stores/auth-store';
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import { ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const uiStore = useUiStore();
|
||||
const currentUser = authStore.user;
|
||||
|
||||
// Will need to implement this eventually, just testing the look for now
|
||||
|
|
@ -12,15 +10,15 @@ import { ref } from 'vue';
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<q-item clickable v-ripple dark @click="uiStore.toggleRightDrawer">
|
||||
<q-item-section side>
|
||||
<q-item clickable v-ripple dark class="q-pa-none">
|
||||
<q-item-section :side="$q.screen.gt.sm">
|
||||
<q-avatar rounded >
|
||||
<q-img src="src/assets/targo-default-avatar.png" />
|
||||
<q-badge floating color="red" v-if="notifAmount > 0" >{{ notifAmount }}</q-badge>
|
||||
</q-avatar>
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section>
|
||||
<q-item-section v-if="$q.screen.gt.sm">
|
||||
<q-item-label>{{ currentUser.firstName }} {{ currentUser.lastName }}</q-item-label>
|
||||
<q-item-label caption>{{ notifAmount }} new messages</q-item-label>
|
||||
</q-item-section>
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
router.push({ name: pageName }).catch(err => {
|
||||
console.error('Error with Vue Router: ', err);
|
||||
});
|
||||
};
|
||||
const goToPageName = (pageName: string) => {
|
||||
router.push({ name: pageName }).catch(err => {
|
||||
console.error('Error with Vue Router: ', err);
|
||||
});
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
authStore.logout();
|
||||
const handleLogout = () => {
|
||||
authStore.logout();
|
||||
|
||||
router.push({ name: 'login' }).catch(err => {
|
||||
console.log('could not log you out: ', err);
|
||||
})
|
||||
}
|
||||
router.push({ name: 'login' }).catch(err => {
|
||||
console.log('could not log you out: ', err);
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -37,29 +36,29 @@ const handleLogout = () => {
|
|||
<q-icon name="home" color="primary" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ $t('navBar.userMenuHome') }}</q-item-label>
|
||||
<q-item-label class="text-uppercase text-grey-8 text-weight-bold">{{ $t('navBar.userMenuHome') }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<!-- 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>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ $t('navBar.userMenuShiftValidation') }}</q-item-label>
|
||||
<q-item-label class="text-uppercase text-grey-8 text-weight-bold">{{ $t('navBar.userMenuShiftValidation') }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<!-- 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>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ $t('navBar.userMenuEmployeeList') }}</q-item-label>
|
||||
<q-item-label class="text-uppercase text-grey-8 text-weight-bold">{{ $t('navBar.userMenuEmployeeList') }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
|
|
@ -69,7 +68,7 @@ const handleLogout = () => {
|
|||
<q-icon name="account_box" color="primary" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ $t('navBar.userMenuProfile') }}</q-item-label>
|
||||
<q-item-label class="text-uppercase text-grey-8 text-weight-bold">{{ $t('navBar.userMenuProfile') }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
|
|
@ -79,7 +78,7 @@ const handleLogout = () => {
|
|||
<q-icon name="contact_support" color="primary" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ $t('navBar.userMenuHelp') }}</q-item-label>
|
||||
<q-item-label class="text-uppercase text-grey-8 text-weight-bold">{{ $t('navBar.userMenuHelp') }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
|
@ -90,7 +89,7 @@ const handleLogout = () => {
|
|||
<q-icon name="exit_to_app" color="primary" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ $t('navBar.userMenuLogout') }}</q-item-label>
|
||||
<q-item-label class="text-uppercase text-grey-8 text-weight-bold">{{ $t('navBar.userMenuLogout') }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-scroll-area>
|
||||
|
|
|
|||
6
src/modules/shared/types/pay-period-bundle-interface.ts
Normal file
6
src/modules/shared/types/pay-period-bundle-interface.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import type { PayPeriod } from "./pay-period-interface";
|
||||
|
||||
export interface PayPeriodBundle {
|
||||
current: PayPeriod;
|
||||
periods: PayPeriod[];
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
export interface PayPeriod {
|
||||
period_number: number;
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
year: number;
|
||||
pay_period_no: number;
|
||||
period_start: string;
|
||||
period_end: string;
|
||||
payday: string;
|
||||
pay_year: number;
|
||||
label: string;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
<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-12 col-sm-6 col-md-4 col-lg-4 col-xl-3 grid-style-transition">
|
||||
<q-card class="rounded-10">
|
||||
<!-- Card header with employee name -->
|
||||
<q-card-section horizontal class="q-py-none q-px-md">
|
||||
<div class="text-primary text-h5 text-weight-bolder q-pt-xs overflow-hidden">{{ props.row.employee_name }}</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-separator color="accent" style="height: 2px;"/>
|
||||
|
||||
<!-- Main body of pay period card -->
|
||||
<q-card-section class="q-pa-none q-mt-xs q-mb-sm">
|
||||
<div class="row no-wrap">
|
||||
<!-- left portion of pay period card -->
|
||||
<div class="column no-wrap" :class="$q.screen.lt.md ? 'col' : 'col-8'">
|
||||
<!-- Regular hours segment -->
|
||||
<q-item dense class="column" :class="$q.screen.lt.md ? 'col' : 'col-8'">
|
||||
<q-item-label class="text-weight-bold text-primary q-pa-none text-uppercase text-caption">
|
||||
{{ props.cols.find(c => c.name === 'regular_hours')?.label }}
|
||||
</q-item-label>
|
||||
<q-item-label class="text-weight-bolder text-h3 text-grey-8 q-py-none">
|
||||
{{ props.cols.find(c => c.name === 'regular_hours')?.value }}
|
||||
</q-item-label>
|
||||
</q-item>
|
||||
|
||||
<q-separator color="accent" class="q-mx-sm"/>
|
||||
|
||||
<!-- Other hour types segment -->
|
||||
<div :class="$q.screen.lt.md ? 'column' : 'row no-wrap'">
|
||||
<q-item dense class="column ellipsis " v-for="col in props.cols.slice(2, 5)" :key="col.label">
|
||||
<q-item-label class="text-weight-bold text-primary q-pa-none text-uppercase text-caption" style="font-size: 0.65em;">
|
||||
{{ col.label }}
|
||||
</q-item-label>
|
||||
<q-item-label class="text-weight-bolder q-pa-none text-h6 text-grey-8">
|
||||
{{ col.value }}
|
||||
</q-item-label>
|
||||
</q-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-separator vertical color="accent" class="q-mt-xs q-mb-none"/>
|
||||
|
||||
<!-- Right portion of pay period card -->
|
||||
<div class="no-wrap ellipsis col">
|
||||
<q-item dense class="column" v-for="col in props.cols.slice(5, )" :key="col.label">
|
||||
<q-item-label class="text-weight-bold text-primary q-pa-none text-uppercase text-caption ellipsis" style="font-size: 0.8em;">
|
||||
{{ col.label }}
|
||||
</q-item-label>
|
||||
<q-item-label class="text-weight-bolder q-pa-none text-h6 text-grey-8">
|
||||
{{ col.value }}
|
||||
</q-item-label>
|
||||
</q-item>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-separator color="primary" style="height: 2px;" />
|
||||
|
||||
<!-- Validate entire Pay Period section -->
|
||||
<q-card-section
|
||||
horizontal
|
||||
class="q-pa-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,13 +1,32 @@
|
|||
<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';
|
||||
import TimesheetApprovalPeriodPicker from '../components/timesheet-approval-period-picker.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const currentPayPeriod = getCurrentPayPeriod();
|
||||
const currentYear = (new Date()).getFullYear();
|
||||
|
||||
const originalApprovals = ref<Record<string, boolean>>({});
|
||||
const hasChanges = computed(() => {
|
||||
return timesheetStore.payPeriodEmployeeOverviews.some(emp => {
|
||||
return emp.is_approved !== originalApprovals.value[emp.email];
|
||||
});
|
||||
});
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const timesheetStore = useTimesheetStore();
|
||||
const timesheetApprovalApi = useTimesheetApprovalApi();
|
||||
|
||||
const columns = computed((): QTableColumn<PayPeriodEmployeeOverview>[] => [
|
||||
{ name: 'employee_name', label: t('timeSheetValidations.tableColumnLabelFullname'), field: 'employee_name', sortable: true },
|
||||
{ name: 'regular_hours', label: t('timeSheetValidations.tableColumnLabelRegularHours'), field: 'regular_hours', sortable: true },
|
||||
|
|
@ -19,74 +38,80 @@
|
|||
]);
|
||||
|
||||
const filter = ref('');
|
||||
const selectedRows = ref<PayPeriodEmployeeOverview[]>();
|
||||
const rows: PayPeriodEmployeeOverview[] = mock_pay_period_employee_overviews;
|
||||
|
||||
onMounted( async () => {
|
||||
await timesheetApprovalApi.getPayPeriodOverviewByDate(new Date());
|
||||
originalApprovals.value = Object.fromEntries( timesheetStore.payPeriodEmployeeOverviews.map(emp => [emp.email, emp.is_approved]));
|
||||
})
|
||||
</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
|
||||
hide-pagination
|
||||
color="primary"
|
||||
:rows-per-page-options="[0]"
|
||||
card-container-class="justify-center q-gutter-md"
|
||||
card-container-class="justify-center"
|
||||
: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>
|
||||
<div :class="$q.screen.lt.md ? 'column justify-center items-center' : 'full-width row'">
|
||||
<!-- Date Picker -->
|
||||
<TimesheetApprovalPeriodPicker />
|
||||
|
||||
<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">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" color="primary" />
|
||||
</template>
|
||||
</q-input>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<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"/>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,55 @@
|
|||
<script setup lang="ts">
|
||||
/* eslint-disable */
|
||||
import { ref, computed } from 'vue';
|
||||
import { useTimesheetApprovalApi } from '../composables/use-timesheet-approval-api';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { date } from 'quasar';
|
||||
|
||||
const timesheetStore = useTimesheetStore();
|
||||
|
||||
const updateCurrentPayPeriod = () => {
|
||||
timesheetStore.getCurrentPayPeriod();
|
||||
};
|
||||
const timesheet_approval_api = useTimesheetApprovalApi();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const is_showing_calendar_picker = ref(false);
|
||||
const calendar_date = ref(date.formatDate( Date.now(), 'YYYY/MM/DD' ));
|
||||
const is_calendar_limit = computed( () => {
|
||||
return timesheet_store.currentPayPeriod.pay_year === 2024 && timesheet_store.currentPayPeriod.pay_period_no <= 1;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="column items-center">
|
||||
<div class="text-primary text-h5">{{ timesheetStore.currentPayPeriod?.label }}</div>
|
||||
<q-btn-group push rounded>
|
||||
<q-btn push icon="keyboard_arrow_left" color="primary" class="q-px-xl" />
|
||||
<q-btn push icon="date_range" color="primary" class="q-px-xl" @click="updateCurrentPayPeriod" />
|
||||
<q-btn push icon="keyboard_arrow_right" color="primary" class="q-px-xl" />
|
||||
</q-btn-group>
|
||||
<div class="row">
|
||||
<q-btn
|
||||
push rounded
|
||||
icon="keyboard_arrow_left"
|
||||
color="primary"
|
||||
@click="timesheet_approval_api.getNextPayPeriodOverview(-1)"
|
||||
:disable="is_calendar_limit || timesheet_store.isLoading"
|
||||
class="q-mr-sm q-px-sm"
|
||||
/>
|
||||
<q-btn
|
||||
push rounded
|
||||
icon="date_range"
|
||||
color="primary"
|
||||
@click="is_showing_calendar_picker = true"
|
||||
:disable="timesheet_store.isLoading"
|
||||
class="q-px-lg"
|
||||
/>
|
||||
<q-btn
|
||||
push rounded
|
||||
icon="keyboard_arrow_right"
|
||||
color="primary"
|
||||
@click="timesheet_approval_api.getNextPayPeriodOverview(1)"
|
||||
:disable="timesheet_store.isLoading"
|
||||
class="q-ml-sm q-px-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<q-dialog v-model="is_showing_calendar_picker" transition-show="slide-down" transition-hide="slide-up" position="top">
|
||||
<q-date
|
||||
v-model="calendar_date"
|
||||
color="primary"
|
||||
class="q-mt-xl"
|
||||
today-btn
|
||||
:options="date => date > '2023/12/16'"
|
||||
/>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { useTimesheetStore } from "src/stores/timesheet-store";
|
||||
import { useAuthStore } from "src/stores/auth-store";
|
||||
|
||||
export const useTimesheetApprovalApi = () => {
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const auth_store = useAuthStore();
|
||||
|
||||
const getPayPeriodOverviewByDate = async (date: Date) => {
|
||||
const success = await timesheet_store.getPayPeriodByDate(date);
|
||||
|
||||
if (success) {
|
||||
const current_pay_period = timesheet_store.currentPayPeriod;
|
||||
await timesheet_store.getTimesheetApprovalPayPeriodEmployeeOverviews(current_pay_period.pay_year, current_pay_period.pay_period_no, auth_store.user.email);
|
||||
}
|
||||
}
|
||||
|
||||
/* This method attempts to get the next or previous pay period.
|
||||
It checks if pay period number is within a certain range, adjusts pay period and year accordingly.
|
||||
It then requests the matching pay period object to set as current pay period from server.
|
||||
If successful, it then requests pay period overviews from that new pay period. */
|
||||
const getNextPayPeriodOverview = async (direction: number) => {
|
||||
const current_pay_period = timesheet_store.currentPayPeriod;
|
||||
let new_pay_period_no = current_pay_period.pay_period_no + direction;
|
||||
let new_pay_year = current_pay_period.pay_year;
|
||||
|
||||
if (new_pay_period_no > 26) {
|
||||
new_pay_period_no = 1;
|
||||
new_pay_year += 1;
|
||||
}
|
||||
|
||||
if (new_pay_period_no < 1) {
|
||||
new_pay_period_no = 26;
|
||||
new_pay_year -= 1;
|
||||
}
|
||||
|
||||
const success = await timesheet_store.getPayPeriodByYearAndPeriodNumber(new_pay_year, new_pay_period_no);
|
||||
|
||||
if (success) {
|
||||
await timesheet_store.getTimesheetApprovalPayPeriodEmployeeOverviews(new_pay_year, new_pay_period_no, auth_store.user.email);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getPayPeriodOverviewByDate,
|
||||
getNextPayPeriodOverview,
|
||||
}
|
||||
};
|
||||
|
|
@ -1,12 +1,39 @@
|
|||
<script setup lang="ts">
|
||||
// const testDates: string[] = ['25 Juin 2025', '12 Juillet 2025', '13 Juillet 2025', '26 Juillet 2025', '27 Juillet 2025', '9 Aout 2025']
|
||||
import TimesheetApprovalPeriodPicker from '../components/timesheet-approval-period-picker.vue';
|
||||
import TimesheetApprovalEmployeeOverviewList from '../components/timesheet-approval-employee-overview-list.vue';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { date } from 'quasar';
|
||||
|
||||
const { locale } = useI18n();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const date_options: Intl.DateTimeFormatOptions = {
|
||||
day: 'numeric',
|
||||
month: "long",
|
||||
year: 'numeric',
|
||||
};
|
||||
|
||||
|
||||
const pay_period_label = computed(() => {
|
||||
const dates = timesheet_store.currentPayPeriod.label.split('.');
|
||||
const start_date = new Intl.DateTimeFormat(locale.value, date_options).format(date.extractDate(dates[0] as string, 'YYYY-MM-DD'));
|
||||
const end_date = new Intl.DateTimeFormat(locale.value, date_options).format(date.extractDate(dates[1] as string, 'YYYY-MM-DD'));
|
||||
|
||||
return {
|
||||
start_date: start_date,
|
||||
end_date: end_date,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-page padding class="q-pa-md bg-secondary">
|
||||
<TimesheetApprovalPeriodPicker />
|
||||
<div class="text-h4 row justify-center q-mt-lg text-uppercase text-weight-bolder text-grey-8">{{ $t('pageTitles.timeSheetValidations') }}</div>
|
||||
<div class="row items-center justify-center q-py-none q-my-none">
|
||||
<div class="text-primary text-h6 text-uppercase">{{ pay_period_label.start_date }}</div>
|
||||
<div class="text-grey-8 text-weight-bold text-uppercase q-mx-md">{{ $t('timeSheet.dateRangesTo') }}</div>
|
||||
<div class="text-primary text-h6 text-uppercase">{{ pay_period_label.end_date }}</div>
|
||||
</div>
|
||||
<TimesheetApprovalEmployeeOverviewList />
|
||||
</q-page>
|
||||
</template>
|
||||
|
|
@ -1,37 +1,22 @@
|
|||
import { api } from "src/boot/axios";
|
||||
import { mock_pay_periods } from "../timesheet-approval-test-constants";
|
||||
import type { PayPeriodOverview } from "../types/timesheet-approval-pay-period-overview-interface";
|
||||
import type { PayPeriod } from "src/modules/shared/types/pay-period-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",
|
||||
"end_date": "2025-08-09",
|
||||
"year": 2025,
|
||||
"label": "2025-07-27 → 2025-08-09"
|
||||
} as PayPeriod;
|
||||
getPayPeriodByDate: async (date: Date): Promise<PayPeriod> => {
|
||||
const response = await api.get(`pay-periods/date/${date.toISOString()}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getAllPayPeriods: async () => {
|
||||
// TODO: REMOVE MOCK DATA PEFORE PUSHING TO PROD
|
||||
return await api.get(`/pay-periods/`) || mock_pay_periods;
|
||||
getPayPeriodByYearAndPeriodNumber: async (year: number, period_number: number): Promise<PayPeriod> => {
|
||||
const response = await api.get(`pay-periods/${year}/${period_number}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
|
@ -1,287 +1,287 @@
|
|||
import type { PayPeriod } from "../shared/types/pay-period-interface";
|
||||
import type { PayPeriodEmployeeOverview } from "./types/timesheet-approval-pay-period-employee-overview-interface"
|
||||
// import type { PayPeriod } from "../shared/types/pay-period-interface";
|
||||
// import type { PayPeriodEmployeeOverview } from "./types/timesheet-approval-pay-period-employee-overview-interface"
|
||||
|
||||
export 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": 0,
|
||||
"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 mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": 'EMP-006',
|
||||
// "employee_name": 'Maxime Murray Gendron',
|
||||
// "regular_hours": 80,
|
||||
// "evening_hours": 0,
|
||||
// "emergency_hours": 0,
|
||||
// "overtime_hours": 0,
|
||||
// "expenses": 0,
|
||||
// "mileage": 0,
|
||||
// "is_approved": false
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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
|
||||
// },
|
||||
// {
|
||||
// "email": '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 mock_pay_periods: PayPeriod[] = [
|
||||
{
|
||||
"period_number": 15,
|
||||
"start_date": "2025-07-27",
|
||||
"end_date": "2025-08-09",
|
||||
"year": 2025,
|
||||
"label": "2025-07-27 → 2025-08-09"
|
||||
},
|
||||
{
|
||||
"period_number": 14,
|
||||
"start_date": "2025-07-13",
|
||||
"end_date": "2025-07-26",
|
||||
"year": 2025,
|
||||
"label": "2025-07-13 → 2025-07-26"
|
||||
},
|
||||
{
|
||||
"period_number": 13,
|
||||
"start_date": "2025-06-29",
|
||||
"end_date": "2025-07-12",
|
||||
"year": 2025,
|
||||
"label": "2025-06-29 → 2025-07-12"
|
||||
},
|
||||
{
|
||||
"period_number": 12,
|
||||
"start_date": "2025-06-15",
|
||||
"end_date": "2025-06-28",
|
||||
"year": 2025,
|
||||
"label": "2025-06-15 → 2025-06-28"
|
||||
},
|
||||
{
|
||||
"period_number": 11,
|
||||
"start_date": "2025-06-01",
|
||||
"end_date": "2025-06-14",
|
||||
"year": 2025,
|
||||
"label": "2025-06-01 → 2025-06-14"
|
||||
},
|
||||
{
|
||||
"period_number": 10,
|
||||
"start_date": "2025-05-18",
|
||||
"end_date": "2025-05-31",
|
||||
"year": 2025,
|
||||
"label": "2025-05-18 → 2025-05-31"
|
||||
},
|
||||
{
|
||||
"period_number": 9,
|
||||
"start_date": "2025-05-04",
|
||||
"end_date": "2025-05-17",
|
||||
"year": 2025,
|
||||
"label": "2025-05-04 → 2025-05-17"
|
||||
},
|
||||
{
|
||||
"period_number": 8,
|
||||
"start_date": "2025-04-20",
|
||||
"end_date": "2025-05-03",
|
||||
"year": 2025,
|
||||
"label": "2025-04-20 → 2025-05-03"
|
||||
},
|
||||
{
|
||||
"period_number": 7,
|
||||
"start_date": "2025-04-06",
|
||||
"end_date": "2025-04-19",
|
||||
"year": 2025,
|
||||
"label": "2025-04-06 → 2025-04-19"
|
||||
},
|
||||
{
|
||||
"period_number": 6,
|
||||
"start_date": "2025-03-23",
|
||||
"end_date": "2025-04-05",
|
||||
"year": 2025,
|
||||
"label": "2025-03-23 → 2025-04-05"
|
||||
}
|
||||
]
|
||||
// export const mock_pay_periods: PayPeriod[] = [
|
||||
// {
|
||||
// "period_number": 15,
|
||||
// "start_date": "2025-07-27",
|
||||
// "end_date": "2025-08-09",
|
||||
// "year": 2025,
|
||||
// "label": "2025-07-27 → 2025-08-09"
|
||||
// },
|
||||
// {
|
||||
// "period_number": 14,
|
||||
// "start_date": "2025-07-13",
|
||||
// "end_date": "2025-07-26",
|
||||
// "year": 2025,
|
||||
// "label": "2025-07-13 → 2025-07-26"
|
||||
// },
|
||||
// {
|
||||
// "period_number": 13,
|
||||
// "start_date": "2025-06-29",
|
||||
// "end_date": "2025-07-12",
|
||||
// "year": 2025,
|
||||
// "label": "2025-06-29 → 2025-07-12"
|
||||
// },
|
||||
// {
|
||||
// "period_number": 12,
|
||||
// "start_date": "2025-06-15",
|
||||
// "end_date": "2025-06-28",
|
||||
// "year": 2025,
|
||||
// "label": "2025-06-15 → 2025-06-28"
|
||||
// },
|
||||
// {
|
||||
// "period_number": 11,
|
||||
// "start_date": "2025-06-01",
|
||||
// "end_date": "2025-06-14",
|
||||
// "year": 2025,
|
||||
// "label": "2025-06-01 → 2025-06-14"
|
||||
// },
|
||||
// {
|
||||
// "period_number": 10,
|
||||
// "start_date": "2025-05-18",
|
||||
// "end_date": "2025-05-31",
|
||||
// "year": 2025,
|
||||
// "label": "2025-05-18 → 2025-05-31"
|
||||
// },
|
||||
// {
|
||||
// "period_number": 9,
|
||||
// "start_date": "2025-05-04",
|
||||
// "end_date": "2025-05-17",
|
||||
// "year": 2025,
|
||||
// "label": "2025-05-04 → 2025-05-17"
|
||||
// },
|
||||
// {
|
||||
// "period_number": 8,
|
||||
// "start_date": "2025-04-20",
|
||||
// "end_date": "2025-05-03",
|
||||
// "year": 2025,
|
||||
// "label": "2025-04-20 → 2025-05-03"
|
||||
// },
|
||||
// {
|
||||
// "period_number": 7,
|
||||
// "start_date": "2025-04-06",
|
||||
// "end_date": "2025-04-19",
|
||||
// "year": 2025,
|
||||
// "label": "2025-04-06 → 2025-04-19"
|
||||
// },
|
||||
// {
|
||||
// "period_number": 6,
|
||||
// "start_date": "2025-03-23",
|
||||
// "end_date": "2025-04-05",
|
||||
// "year": 2025,
|
||||
// "label": "2025-03-23 → 2025-04-05"
|
||||
// }
|
||||
// ]
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -4,229 +4,79 @@ import { timesheetApprovalService } from 'src/modules/timesheet-approval/service
|
|||
import type { PayPeriod } from 'src/modules/shared/types/pay-period-interface';
|
||||
import type { PayPeriodEmployeeOverview } from "src/modules/timesheet-approval/types/timesheet-approval-pay-period-employee-overview-interface";
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
];
|
||||
const default_pay_period: PayPeriod = {
|
||||
pay_period_no: -1,
|
||||
period_start: '',
|
||||
period_end: '',
|
||||
payday: '',
|
||||
pay_year: -1,
|
||||
label: ''
|
||||
};
|
||||
|
||||
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 currentPayPeriod = ref<PayPeriod>(default_pay_period);
|
||||
const payPeriodEmployeeOverviews = ref<PayPeriodEmployeeOverview[]>([]);
|
||||
const isLoading = ref<boolean>(false);
|
||||
|
||||
const getCurrentPayPeriod = () => {
|
||||
currentPayPeriod.value = timesheetApprovalService.getCurrentPayPeriod();
|
||||
}
|
||||
const getPayPeriodByDate = async (date: Date): Promise<boolean> => {
|
||||
isLoading.value = true;
|
||||
|
||||
return { payPeriods, currentPayPeriod, payPeriodEmployeeOverviews, getCurrentPayPeriod};
|
||||
try {
|
||||
const response = await timesheetApprovalService.getPayPeriodByDate(date);
|
||||
currentPayPeriod.value = response;
|
||||
isLoading.value = false;
|
||||
|
||||
return true;
|
||||
} catch(error){
|
||||
console.error('Could not get current pay period: ', error );
|
||||
//TODO: More in-depth error-handling here
|
||||
}
|
||||
|
||||
isLoading.value = false;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const getPayPeriodByYearAndPeriodNumber = async (year: number, period_number: number): Promise<boolean> => {
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
const response = await timesheetApprovalService.getPayPeriodByYearAndPeriodNumber(year, period_number);
|
||||
currentPayPeriod.value = response;
|
||||
isLoading.value = false;
|
||||
|
||||
return true;
|
||||
} catch(error){
|
||||
console.error('Could not get current pay period: ', error );
|
||||
//TODO: More in-depth error-handling here
|
||||
}
|
||||
|
||||
isLoading.value = false;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const getTimesheetApprovalPayPeriodEmployeeOverviews = async (pay_year: number, period_number: number, supervisor_email: string) => {
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
const response = await timesheetApprovalService.getPayPeriodEmployeeOverviews(pay_year, period_number, supervisor_email);
|
||||
payPeriodEmployeeOverviews.value = response.employees_overview;
|
||||
} catch (error) {
|
||||
console.error('There was an error retrieving Employee Pay Period overviews: ', error);
|
||||
payPeriodEmployeeOverviews.value = [];
|
||||
// TODO: trigger an alert window with an error message here!
|
||||
}
|
||||
|
||||
isLoading.value = false;
|
||||
};
|
||||
|
||||
return {
|
||||
currentPayPeriod,
|
||||
payPeriodEmployeeOverviews,
|
||||
isLoading,
|
||||
getPayPeriodByDate,
|
||||
getPayPeriodByYearAndPeriodNumber,
|
||||
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