eat(approvals): add and define components and other files related to timesheet approval page

This commit is contained in:
Nicolas Drolet 2025-08-08 17:04:54 -04:00
parent c1ce7e36cb
commit 1f94d6a900
34 changed files with 442 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View 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>

View File

@ -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>

View 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>

View File

@ -0,0 +1,88 @@
<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>
<!-- 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>

View File

@ -0,0 +1,7 @@
export interface PayPeriod {
period_number: number;
start_date: string;
end_date: string;
year: number;
label: string;
};

View File

@ -0,0 +1,6 @@
export interface User {
firstName: string;
lastName: string;
email: string;
role: string;
}

View File

@ -0,0 +1,16 @@
<script setup lang="ts">
import { useTimesheetStore } from 'src/stores/timesheet-store';
const timesheetStore = useTimesheetStore();
</script>
<template>
<div class="row">
<q-btn-group outline rounded>
<q-btn outline icon="arrow_circle_left"/>
<q-btn outline icon="calendar_month" :label="timesheetStore.currentPayPeriod?.label"/>
<q-btn outline icon="arrow_circle_right"/>
</q-btn-group>
<div class="text-primary text-h4">{{ timesheetStore.currentPayPeriod?.label }}</div>
</div>
</template>

View File

@ -0,0 +1,9 @@
<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']
</script>
<template>
<q-page padding class="q-pa-md bg-secondary">
</q-page>
</template>

View File

@ -0,0 +1,20 @@
import { api } from "src/boot/axios";
import { mock_pay_period_employee_overviews, mock_pay_periods } from "../timesheet-approval-test-constants";
import { PayPeriod } from "src/modules/shared/types/pay-period-interface";
export const timesheetApprovalService = {
getCurrentPayPeriod: async () :Promise<PayPeriod> => {
// TODO: REMOVE MOCK DATA PEFORE PUSHING TO PROD
return await api.get(`/pay-periods/date/${new Date()}`) || mock_pay_periods[0];
},
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`) || mock_pay_period_employee_overviews;
},
};

View File

@ -0,0 +1,133 @@
import { PayPeriod } from "../shared/types/pay-period-interface";
import { 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
}
];
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"
}
]

View File

@ -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;
};

View File

@ -0,0 +1,6 @@
export interface PayPeriodOverview {
period_number: number;
start_date: string;
end_date: string;
label: string;
};

View 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',
}

39
src/stores/auth-store.ts Normal file
View 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 };
});

View File

@ -0,0 +1,18 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
import type { PayPeriod } from 'src/modules/shared/types/pay-period-interface';
import { timesheetApprovalService } from 'src/modules/timesheet-approval/services/services-timesheet-approval';
export const useTimesheetStore = defineStore('timesheet', () => {
const payPeriods = ref<PayPeriod[]>([]);
const currentPayPeriod = ref<PayPeriod>();
const getCurrentPayPeriod = async () => {
if (!currentPayPeriod.value) {
currentPayPeriod.value = await timesheetApprovalService.getCurrentPayPeriod();
}
return currentPayPeriod.value;
}
return { payPeriods, currentPayPeriod, getCurrentPayPeriod};
});

11
src/stores/ui-store.ts Normal file
View 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 };
});

View 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);
};