Merge pull request 'dev/nicolas/timesheet-validation' (#1) from dev/nicolas/timesheet-validation into main
Reviewed-on: Targo/targo_frontend#1
This commit is contained in:
commit
cf6f411ac6
BIN
src/assets/ChromeSetup.exe
Normal file
BIN
src/assets/ChromeSetup.exe
Normal file
Binary file not shown.
BIN
src/assets/approvals-banner.png
Normal file
BIN
src/assets/approvals-banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 159 KiB |
BIN
src/assets/event-banner.png
Normal file
BIN
src/assets/event-banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 241 KiB |
BIN
src/assets/targo-default-avatar.png
Normal file
BIN
src/assets/targo-default-avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -13,15 +13,18 @@
|
||||||
// Tip: Use the "Theme Builder" on Quasar's documentation website.
|
// Tip: Use the "Theme Builder" on Quasar's documentation website.
|
||||||
|
|
||||||
$primary: #019547;
|
$primary: #019547;
|
||||||
$secondary: #EFFFEF;
|
$secondary: #DAE0E7;
|
||||||
$accent: #4ada86;
|
$accent: #AAD5C4;
|
||||||
|
|
||||||
$dark-font: #305530;
|
$verdigris: #6EBAB0;
|
||||||
$dark: #323232;
|
$mint: #56B586;
|
||||||
|
|
||||||
|
$dark-font: #1f3a1f;
|
||||||
|
$dark: #000;
|
||||||
$dark-page: #323232;
|
$dark-page: #323232;
|
||||||
|
|
||||||
$positive: #21ba45;
|
$positive: #21ba45;
|
||||||
$negative: #c10015;
|
$negative: #ff586c71;
|
||||||
$info: #31ccec;
|
$info: #31ccec;
|
||||||
$warning: #eeb10a;
|
$warning: #ffde82c2;
|
||||||
$white: white;
|
$white: white;
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export default {
|
||||||
clearFilter: 'Clear filter',
|
clearFilter: 'Clear filter',
|
||||||
},
|
},
|
||||||
navBar: {
|
navBar: {
|
||||||
|
userMenuHome: 'Homepage',
|
||||||
userMenuEmployeeList: 'Employee list',
|
userMenuEmployeeList: 'Employee list',
|
||||||
userMenuShiftValidation: 'Timesheet Approval',
|
userMenuShiftValidation: 'Timesheet Approval',
|
||||||
userMenuProfile: 'Profile',
|
userMenuProfile: 'Profile',
|
||||||
|
|
@ -32,8 +33,8 @@ export default {
|
||||||
userMenuCalendar: 'Calendar',
|
userMenuCalendar: 'Calendar',
|
||||||
},
|
},
|
||||||
notFoundPage: {
|
notFoundPage: {
|
||||||
pageTitle: 'Oops. Nothing here...',
|
pageText: 'We cannot seem to find the page you are looking for, sorry!',
|
||||||
backButton: 'Go to the home page',
|
backButton: 'Take me back!',
|
||||||
},
|
},
|
||||||
loginPage: {
|
loginPage: {
|
||||||
title: 'Log in to Targo',
|
title: 'Log in to Targo',
|
||||||
|
|
@ -292,23 +293,21 @@ export default {
|
||||||
},
|
},
|
||||||
timeSheetValidations: {
|
timeSheetValidations: {
|
||||||
tableHeader: 'List of employees',
|
tableHeader: 'List of employees',
|
||||||
tableCol_1: 'Full name',
|
tableColumnLabelFullname: 'Full name',
|
||||||
tableCol_2: 'Regular hours',
|
tableColumnLabelRegularHours: 'regular hours',
|
||||||
tableCol_3: 'Evening hours',
|
tableColumnLabelEveningHours: 'evening hours',
|
||||||
tableCol_4: 'Emergency hours',
|
tableColumnLabelEmergencyHours: 'emergency hours',
|
||||||
tableCol_5: 'Overtime hours',
|
tableColumnLabelOvertime: 'overtime hours',
|
||||||
tableCol_6: 'Expenses',
|
tableColumnLabelExpenses: 'of expenses',
|
||||||
tableCol_7: 'Mileage',
|
tableColumnLabelMileage: 'of mileage',
|
||||||
tableCol_8: 'Status',
|
|
||||||
tableCol_9: 'Supervisor',
|
|
||||||
actionTitle: 'Please save the changes made.',
|
actionTitle: 'Please save the changes made.',
|
||||||
actionButton: 'Save',
|
actionButton: 'Save',
|
||||||
timeSheetStatus_verified: 'Verified',
|
timeSheetStatusVerified: 'approved',
|
||||||
timeSheetStatus_unverified: 'Unverified',
|
timeSheetStatusUnverified: 'pending',
|
||||||
timeSheetStatus_partial: 'Partial',
|
timeSheetStatusPartial: 'partially approved',
|
||||||
timeSheetStatus_complete: 'Complete',
|
timeSheetStatusComplete: 'complete',
|
||||||
timeSheetStatus_empty: 'Empty',
|
timeSheetStatusEmpty: 'empty',
|
||||||
timeSheetStatus_blocked: 'Blocked',
|
timeSheetStatusBlocked: 'blocked',
|
||||||
showAllCheckbox: 'Show all',
|
showAllCheckbox: 'Show all',
|
||||||
accumulatedSicknessTotal: 'Accumulated illnesses',
|
accumulatedSicknessTotal: 'Accumulated illnesses',
|
||||||
consumedSicknessTotal: 'Consumed with illnesses',
|
consumedSicknessTotal: 'Consumed with illnesses',
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,7 @@ export default {
|
||||||
clearFilter: 'Effacer le filtre',
|
clearFilter: 'Effacer le filtre',
|
||||||
},
|
},
|
||||||
navBar: {
|
navBar: {
|
||||||
|
userMenuHome: 'Accueil',
|
||||||
userMenuEmployeeList: 'Liste employés',
|
userMenuEmployeeList: 'Liste employés',
|
||||||
userMenuShiftValidation: 'Valider les heures',
|
userMenuShiftValidation: 'Valider les heures',
|
||||||
userMenuProfile: 'Profil',
|
userMenuProfile: 'Profil',
|
||||||
|
|
@ -171,8 +172,8 @@ export default {
|
||||||
userMenuCalendar: 'Calendrier annuel',
|
userMenuCalendar: 'Calendrier annuel',
|
||||||
},
|
},
|
||||||
notFoundPage: {
|
notFoundPage: {
|
||||||
pageTitle: 'Oops. Rien ici...',
|
pageText: 'On ne semble pas trouver la page que vous cherchez, désolé!',
|
||||||
backButton: 'Aller à la page d’accueil',
|
backButton: 'Je veux retourner en arrière!',
|
||||||
},
|
},
|
||||||
notificationDialog: {
|
notificationDialog: {
|
||||||
notice: 'Notification',
|
notice: 'Notification',
|
||||||
|
|
@ -338,23 +339,21 @@ export default {
|
||||||
},
|
},
|
||||||
timeSheetValidations: {
|
timeSheetValidations: {
|
||||||
tableHeader: 'Liste des employés',
|
tableHeader: 'Liste des employés',
|
||||||
tableCol_1: 'Nom et prénom',
|
tableColumnLabelFullname: 'nom complet',
|
||||||
tableCol_2: 'Heures régulières',
|
tableColumnLabelRegularHours: 'heures régulières',
|
||||||
tableCol_3: 'Heures de soir',
|
tableColumnLabelEveningHours: 'heures de soir',
|
||||||
tableCol_4: 'Heures d’urgence',
|
tableColumnLabelEmergencyHours: 'heures d’urgence',
|
||||||
tableCol_5: 'Heures supplémentaires',
|
tableColumnLabelOvertime: 'heures supplémentaires',
|
||||||
tableCol_6: 'Dépenses ',
|
tableColumnLabelExpenses: 'de dépenses',
|
||||||
tableCol_7: 'Kilométrage ',
|
tableColumnLabelMileage: 'de kilométrage',
|
||||||
tableCol_8: 'État',
|
|
||||||
tableCol_9: 'Superviseur',
|
|
||||||
actionTitle: 'Veuillez enregistrer les changements effectués.',
|
actionTitle: 'Veuillez enregistrer les changements effectués.',
|
||||||
actionButton: 'Enregistrer',
|
actionButton: 'Enregistrer',
|
||||||
timeSheetStatus_verified: 'Vérifié',
|
timeSheetStatusVerified: 'validé',
|
||||||
timeSheetStatus_unverified: 'Invérifié',
|
timeSheetStatusUnverified: 'à valider',
|
||||||
timeSheetStatus_partial: 'Partiel',
|
timeSheetStatusPartial: 'partiellement validé',
|
||||||
timeSheetStatus_complete: 'Complet',
|
timeSheetStatusComplete: 'complet',
|
||||||
timeSheetStatus_empty: 'Vide',
|
timeSheetStatusEmpty: 'vide',
|
||||||
timeSheetStatus_blocked: 'Bloqué',
|
timeSheetStatusBlocked: 'bloqué',
|
||||||
showAllCheckbox: 'Afficher tous',
|
showAllCheckbox: 'Afficher tous',
|
||||||
accumulatedSicknessTotal: 'Accumulées de maladies',
|
accumulatedSicknessTotal: 'Accumulées de maladies',
|
||||||
consumedSicknessTotal: 'Consommées de maladies',
|
consumedSicknessTotal: 'Consommées de maladies',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useAuthStore } from "../../../stores/auth-store";
|
import { useAuthStore } from "../../../stores/auth-store";
|
||||||
import type { User } from "src/modules/users/types/user-interface";
|
import type { User } from "src/modules/shared/types/user-interface";
|
||||||
|
|
||||||
export const useAuthApi = () => {
|
export const useAuthApi = () => {
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { useAuthApi } from '../composables/use-auth-api';
|
import { useAuthApi } from '../composables/use-auth-api';
|
||||||
import type { User } from 'src/modules/users/types/user-interface';
|
import type { User } from 'src/modules/shared/types/user-interface';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
|
||||||
const authApi = useAuthApi();
|
const authApi = useAuthApi();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { User } from "src/modules/users/types/user-interface";
|
import type { User } from "src/modules/shared/types/user-interface";
|
||||||
|
|
||||||
export interface AuthState {
|
export interface AuthState {
|
||||||
token: string;
|
token: string;
|
||||||
|
|
|
||||||
0
src/modules/employee-list/composables/use-users.ts
Normal file
0
src/modules/employee-list/composables/use-users.ts
Normal file
0
src/modules/employee-list/employee-constants.ts
Normal file
0
src/modules/employee-list/employee-constants.ts
Normal file
0
src/modules/employee-list/employee-store.ts
Normal file
0
src/modules/employee-list/employee-store.ts
Normal file
0
src/modules/employee-list/pages/user-add-page.vue
Normal file
0
src/modules/employee-list/pages/user-add-page.vue
Normal file
0
src/modules/employee-list/services/user-service.ts
Normal file
0
src/modules/employee-list/services/user-service.ts
Normal file
12
src/modules/shared/components/navigation/footer-bar.vue
Normal file
12
src/modules/shared/components/navigation/footer-bar.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import LanguageSwitch from '../language-switch.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-footer elevated class="bg-primary text-white">
|
||||||
|
<q-toolbar>
|
||||||
|
<q-toolbar-title>© 2025 Targo Communications inc.</q-toolbar-title>
|
||||||
|
<LanguageSwitch class="q-mr-xs text-white" />
|
||||||
|
</q-toolbar>
|
||||||
|
</q-footer>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAuthStore } from 'src/stores/auth-store';
|
||||||
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
|
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
|
||||||
|
const notifAmount = ref(7);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-item clickable v-ripple dark @click="uiStore.toggleRightDrawer">
|
||||||
|
<q-item-section side>
|
||||||
|
<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-label>{{ currentUser.firstName }} {{ currentUser.lastName }}</q-item-label>
|
||||||
|
<q-item-label caption>{{ notifAmount }} new messages</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
22
src/modules/shared/components/navigation/header-bar.vue
Normal file
22
src/modules/shared/components/navigation/header-bar.vue
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
|
import HeaderBarAvatar from './header-bar-avatar.vue';
|
||||||
|
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-header elevated>
|
||||||
|
<q-toolbar>
|
||||||
|
<q-toolbar-title>
|
||||||
|
<q-btn flat dense color="white" icon="menu" @click="uiStore.toggleRightDrawer">
|
||||||
|
<q-img src="src/assets/logo-targo-white.svg" fit="contain" width="150px" height="30px"/>
|
||||||
|
</q-btn>
|
||||||
|
</q-toolbar-title>
|
||||||
|
<q-item class="q-pa-none">
|
||||||
|
<HeaderBarAvatar />
|
||||||
|
</q-item>
|
||||||
|
</q-toolbar>
|
||||||
|
</q-header>
|
||||||
|
</template>
|
||||||
|
|
||||||
98
src/modules/shared/components/navigation/right-drawer.vue
Normal file
98
src/modules/shared/components/navigation/right-drawer.vue
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
<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';
|
||||||
|
|
||||||
|
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 handleLogout = () => {
|
||||||
|
authStore.logout();
|
||||||
|
|
||||||
|
router.push({ name: 'login' }).catch(err => {
|
||||||
|
console.log('could not log you out: ', err);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-drawer overlay elevated side="left" :mini="miniState" @mouseenter="miniState = false"
|
||||||
|
@mouseleave="miniState = true" v-model="uiStore.isRightDrawerOpen">
|
||||||
|
<q-scroll-area class="fit">
|
||||||
|
<q-list>
|
||||||
|
<!-- Home -->
|
||||||
|
<q-item v-ripple clickable side @click="goToPageName(RouteNames.DASHBOARD)">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="home" color="primary" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ $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')">
|
||||||
|
<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-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')">
|
||||||
|
<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-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<!-- Profile -->
|
||||||
|
<q-item v-ripple clickable side @click="goToPageName(RouteNames.PROFILE)">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="account_box" color="primary" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ $t('navBar.userMenuProfile') }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<!-- Help -->
|
||||||
|
<q-item v-ripple clickable @click="goToPageName('help')">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="contact_support" color="primary" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ $t('navBar.userMenuHelp') }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
|
||||||
|
<!-- Logout -->
|
||||||
|
<q-item v-ripple clickable @click="handleLogout" class="absolute-bottom">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<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-section>
|
||||||
|
</q-item>
|
||||||
|
</q-scroll-area>
|
||||||
|
</q-drawer>
|
||||||
|
</template>
|
||||||
7
src/modules/shared/types/pay-period-interface.ts
Normal file
7
src/modules/shared/types/pay-period-interface.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface PayPeriod {
|
||||||
|
period_number: number;
|
||||||
|
start_date: string;
|
||||||
|
end_date: string;
|
||||||
|
year: number;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
6
src/modules/shared/types/user-interface.ts
Normal file
6
src/modules/shared/types/user-interface.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface User {
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
email: string;
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
/* eslint-disable */
|
||||||
|
import { computed, 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';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
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 },
|
||||||
|
{ name: 'evening_hours', label: t('timeSheetValidations.tableColumnLabelEveningHours'), field: 'evening_hours' },
|
||||||
|
{ name: 'emergency_hours', label: t('timeSheetValidations.tableColumnLabelEmergencyHours'), field: 'emergency_hours' },
|
||||||
|
{ name: 'overtime_hours', label: t('timeSheetValidations.tableColumnLabelOvertime'), field: 'overtime_hours' },
|
||||||
|
{ name: 'expenses', label: t('timeSheetValidations.tableColumnLabelExpenses'), field: 'expenses', sortable: true },
|
||||||
|
{ name: 'mileage', label: t('timeSheetValidations.tableColumnLabelMileage'), field: 'mileage', sortable: true }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const filter = ref('');
|
||||||
|
const selectedRows = ref<PayPeriodEmployeeOverview[]>();
|
||||||
|
const rows: PayPeriodEmployeeOverview[] = mock_pay_period_employee_overviews;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="q-pa-md">
|
||||||
|
<q-table
|
||||||
|
:rows="rows"
|
||||||
|
:columns="columns"
|
||||||
|
row-key="employee_id"
|
||||||
|
selection="multiple"
|
||||||
|
v-model:selected="selectedRows"
|
||||||
|
:filter="filter"
|
||||||
|
grid
|
||||||
|
dense
|
||||||
|
:rows-per-page-options="[0]"
|
||||||
|
card-container-class="justify-center q-gutter-md"
|
||||||
|
>
|
||||||
|
<!-- 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-space />
|
||||||
|
|
||||||
|
<!-- Filters toggle -->
|
||||||
|
<q-btn flat dense class="text-white" label="filters" icon-right="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>
|
||||||
|
</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" 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>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||||
|
|
||||||
|
const timesheetStore = useTimesheetStore();
|
||||||
|
|
||||||
|
const updateCurrentPayPeriod = () => {
|
||||||
|
timesheetStore.getCurrentPayPeriod();
|
||||||
|
};
|
||||||
|
|
||||||
|
</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>
|
||||||
|
</template>
|
||||||
12
src/modules/timesheet-approval/pages/timesheet-approval.vue
Normal file
12
src/modules/timesheet-approval/pages/timesheet-approval.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<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';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-page padding class="q-pa-md bg-secondary">
|
||||||
|
<TimesheetApprovalPeriodPicker />
|
||||||
|
<TimesheetApprovalEmployeeOverviewList />
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
|
||||||
|
getAllPayPeriods: async () => {
|
||||||
|
// TODO: REMOVE MOCK DATA PEFORE PUSHING TO PROD
|
||||||
|
return await api.get(`/pay-periods/`) || mock_pay_periods;
|
||||||
|
},
|
||||||
|
|
||||||
|
getPayPeriodEmployeeOverviews: async (period_number: number) => {
|
||||||
|
// TODO: REMOVE MOCK DATA PEFORE PUSHING TO PROD
|
||||||
|
return await api.get(`/pay-periods/${period_number}/overview`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,287 @@
|
||||||
|
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_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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
export interface PayPeriodEmployeeOverview {
|
||||||
|
employee_id: string;
|
||||||
|
employee_name: string;
|
||||||
|
regular_hours: number;
|
||||||
|
evening_hours: number;
|
||||||
|
emergency_hours: number;
|
||||||
|
overtime_hours: number;
|
||||||
|
expenses: number;
|
||||||
|
mileage: number;
|
||||||
|
is_approved: boolean;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface PayPeriodOverview {
|
||||||
|
period_number: number;
|
||||||
|
start_date: string;
|
||||||
|
end_date: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
@ -3,25 +3,21 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="fullscreen bg-blue text-white text-center q-pa-md flex flex-center">
|
<q-layout view="hHh lpR fFf">
|
||||||
<div>
|
<q-page-container>
|
||||||
<div style="font-size: 30vh">
|
<q-page padding class="column justify-center items-center bg-secondary">
|
||||||
404
|
<q-card class="col-shrink rounded-20">
|
||||||
</div>
|
<q-img src="src/assets/line-truck-1.jpg" height="20vh">
|
||||||
|
<div class="absolute-bottom text-h4 text-center text-weight-bolder justify-center items-center row">
|
||||||
<div class="text-h2" style="opacity:.4">
|
<div class="q-pr-md text-primary text-h3 text-weight-bolder">404</div>
|
||||||
Oops. Nothing here...
|
PAGE NOT FOUND
|
||||||
</div>
|
</div>
|
||||||
|
</q-img>
|
||||||
<q-btn
|
<q-card-section class="text-center text-h5 text-primary">
|
||||||
class="q-mt-xl"
|
{{$t('notFoundPage.pageText')}}
|
||||||
color="white"
|
</q-card-section>
|
||||||
text-color="blue"
|
</q-card>
|
||||||
unelevated
|
</q-page>
|
||||||
to="/"
|
</q-page-container>
|
||||||
label="Go Home"
|
</q-layout>
|
||||||
no-caps
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
9
src/router/router-constants.ts
Normal file
9
src/router/router-constants.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
export enum RouteNames {
|
||||||
|
/* eslint-disable */
|
||||||
|
LOGIN = 'login',
|
||||||
|
LOGIN_SUCCESS = 'login-success',
|
||||||
|
DASHBOARD = 'dashboard',
|
||||||
|
TIMESHEET_APPROVALS = 'timesheet-approvals',
|
||||||
|
EMPLOYEE_LIST = 'employee-list',
|
||||||
|
PROFILE = 'user/profile',
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import type { RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
import { RouteNames } from './router-constants';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -8,22 +9,27 @@ const routes: RouteRecordRaw[] = [
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
name: 'dashboard',
|
name: RouteNames.DASHBOARD,
|
||||||
component: () => import('src/pages/test-page.vue'),
|
component: () => import('src/pages/test-page.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'timesheet-approvals',
|
||||||
|
name: RouteNames.TIMESHEET_APPROVALS,
|
||||||
|
component: () => import('src/modules/timesheet-approval/pages/timesheet-approval.vue'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/v1/login',
|
path: '/v1/login',
|
||||||
name: 'login',
|
name: RouteNames.LOGIN,
|
||||||
component: () => import('src/modules/auth/pages/auth-login.vue'),
|
component: () => import('src/modules/auth/pages/auth-login.vue'),
|
||||||
meta: { requiresAuth: false },
|
meta: { requiresAuth: false },
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/login-success',
|
path: '/login-success',
|
||||||
name: 'login-success',
|
name: RouteNames.LOGIN_SUCCESS,
|
||||||
component: () => import('src/modules/auth/pages/auth-login-popup-success.vue'),
|
component: () => import('src/modules/auth/pages/auth-login-popup-success.vue'),
|
||||||
meta: { requiresAuth: false },
|
meta: { requiresAuth: false },
|
||||||
},
|
},
|
||||||
|
|
|
||||||
39
src/stores/auth-store.ts
Normal file
39
src/stores/auth-store.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
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 const useAuthStore = defineStore('auth', () => {
|
||||||
|
const user = ref (defaultUser);
|
||||||
|
const authError = ref("");
|
||||||
|
const isAuthorizedUser = computed(() => user.value.role !== 'guest');
|
||||||
|
|
||||||
|
const login = () => {
|
||||||
|
//TODO: manage customer login process
|
||||||
|
};
|
||||||
|
|
||||||
|
const oidcLogin = () => {
|
||||||
|
const oidcPopup = AuthService.oidcLogin();
|
||||||
|
if (!oidcPopup) {
|
||||||
|
authError.value = "You have popups blocked on this website!";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
user.value = defaultUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setUser = (currentUser: User) => {
|
||||||
|
user.value = currentUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { user, authError, isAuthorizedUser, login, oidcLogin, logout, setUser };
|
||||||
|
});
|
||||||
|
|
||||||
232
src/stores/timesheet-store.ts
Normal file
232
src/stores/timesheet-store.ts
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { timesheetApprovalService } from 'src/modules/timesheet-approval/services/services-timesheet-approval';
|
||||||
|
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
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
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 getCurrentPayPeriod = () => {
|
||||||
|
currentPayPeriod.value = timesheetApprovalService.getCurrentPayPeriod();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { payPeriods, currentPayPeriod, payPeriodEmployeeOverviews, getCurrentPayPeriod};
|
||||||
|
});
|
||||||
11
src/stores/ui-store.ts
Normal file
11
src/stores/ui-store.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
export const useUiStore = defineStore('ui', () => {
|
||||||
|
const isRightDrawerOpen = ref(true);
|
||||||
|
const toggleRightDrawer = () => {
|
||||||
|
isRightDrawerOpen.value = !isRightDrawerOpen.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isRightDrawerOpen, toggleRightDrawer };
|
||||||
|
});
|
||||||
7
src/utils/has-required-role.ts
Normal file
7
src/utils/has-required-role.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { useAuthStore } from "src/stores/auth-store";
|
||||||
|
|
||||||
|
export const hasRequiredRole = (...requiredRoles: string[] ) => {
|
||||||
|
const currentUserRole = useAuthStore().user.role;
|
||||||
|
|
||||||
|
return requiredRoles.includes(currentUserRole);
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user