refactor(approvals): remake entire card appearance, hook up to pay period picker to backend, make the period picker actually functional.

This commit is contained in:
Nicolas Drolet 2025-08-20 17:03:32 -04:00
parent 0c1d214420
commit 21b98ec825
14 changed files with 434 additions and 361 deletions

View File

@ -1,7 +1,7 @@
// app global css in SCSS form // app global css in SCSS form
@each $size in (5, 10, 15, 20, 25) { @each $size in (5, 10, 15, 20, 25) {
.rounded-#{$size} { .rounded-#{$size} {
border-radius: #{$size}px; border-radius: #{$size}px !important;
} }
} }

View File

@ -297,11 +297,11 @@ export default {
tableHeader: 'List of employees', tableHeader: 'List of employees',
tableColumnLabelFullname: 'Full name', tableColumnLabelFullname: 'Full name',
tableColumnLabelRegularHours: 'regular hours', tableColumnLabelRegularHours: 'regular hours',
tableColumnLabelEveningHours: 'evening hours', tableColumnLabelEveningHours: 'evening',
tableColumnLabelEmergencyHours: 'emergency hours', tableColumnLabelEmergencyHours: 'emergency',
tableColumnLabelOvertime: 'overtime hours', tableColumnLabelOvertime: 'overtime',
tableColumnLabelExpenses: 'of expenses', tableColumnLabelExpenses: 'expenses',
tableColumnLabelMileage: 'of mileage', tableColumnLabelMileage: 'mileage',
actionTitle: 'Please save the changes made.', actionTitle: 'Please save the changes made.',
actionButton: 'Save', actionButton: 'Save',
timeSheetStatusVerified: 'approved', timeSheetStatusVerified: 'approved',

View File

@ -347,11 +347,11 @@ export default {
tableHeader: 'Liste des employés', tableHeader: 'Liste des employés',
tableColumnLabelFullname: 'nom complet', tableColumnLabelFullname: 'nom complet',
tableColumnLabelRegularHours: 'heures régulières', tableColumnLabelRegularHours: 'heures régulières',
tableColumnLabelEveningHours: 'heures de soir', tableColumnLabelEveningHours: 'soir',
tableColumnLabelEmergencyHours: 'heures durgence', tableColumnLabelEmergencyHours: 'urgence',
tableColumnLabelOvertime: 'heures supplémentaires', tableColumnLabelOvertime: 'supplémentaires',
tableColumnLabelExpenses: 'de dépenses', tableColumnLabelExpenses: 'dépenses',
tableColumnLabelMileage: 'de kilométrage', tableColumnLabelMileage: 'kilométrage',
actionTitle: 'Veuillez enregistrer les changements effectués.', actionTitle: 'Veuillez enregistrer les changements effectués.',
actionButton: 'Enregistrer', actionButton: 'Enregistrer',
timeSheetStatusVerified: 'validé', timeSheetStatusVerified: 'validé',

View File

@ -25,7 +25,7 @@ import { useEmployeeListApi } from 'src/modules/employee-list/composables/use-em
<template> <template>
<q-card <q-card
v-ripple 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;" style="max-width: 230px;"
@click="onProfileCardClick(props.row.email)" @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;"> <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"> <div class="ellipsis">
{{ props.row.first_name }} {{ props.row.last_name }} {{ props.row.first_name }} {{ props.row.last_name }}
<q-separator color="primary" />
</div> </div>
</q-card-section> </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;"> <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"> <div class=" ellipsis-2-lines">
{{ props.row.job_title }} {{ props.row.job_title }}

View File

@ -15,6 +15,7 @@
const { t } = useI18n(); const { t } = useI18n();
const filter = ref(""); const filter = ref("");
const isGridMode = ref(true); const isGridMode = ref(true);
const pagination = ref({ rowsPerPage: 0 });
const employeeListColumns = computed((): QTableColumn<EmployeeListTableItem>[] => [ const employeeListColumns = computed((): QTableColumn<EmployeeListTableItem>[] => [
{name: 'first_name', label: t('usersListPage.userListFirstName'), field: 'first_name', align: 'left'}, {name: 'first_name', label: t('usersListPage.userListFirstName'), field: 'first_name', align: 'left'},
@ -35,24 +36,30 @@
<template> <template>
<div class="q-pa-lg col"> <div class="q-pa-lg col">
<q-table <q-table
title=" " dense
flat
hide-pagination
virtual-scroll
title=" "
card-style="max-height: 70vh;"
:rows="employeeStore.employeeList" :rows="employeeStore.employeeList"
:columns="employeeListColumns" :columns="employeeListColumns"
row-key="name" row-key="name"
v-model:pagination="pagination"
:rows-per-page-options="[0]" :rows-per-page-options="[0]"
:filter="filter" :filter="filter"
class="q-pa-md bg-transparent" class="q-pa-md bg-transparent"
color="primary" color="primary"
table-header-class="text-primary text-uppercase" table-header-class="text-primary text-uppercase"
card-container-class="justify-center q-gutter-md" card-container-class="justify-center"
:grid="isGridMode" :grid="isGridMode"
:loading="isLoadingList" :loading="isLoadingList"
dense
flat
:no-data-label="$t('shared.failedToLoad')" :no-data-label="$t('shared.failedToLoad')"
:no-results-label="$t('shared.failedToSearch')" :no-results-label="$t('shared.failedToSearch')"
:loading-label="$t('shared.loading')" :loading-label="$t('shared.loading')"
table-class="bg-white q-pa-md q-mx-md rounded-15 shadow-12" 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"> <template v-slot:item="props">
<SupervisorCrewTableItem :row="props.row"/> <SupervisorCrewTableItem :row="props.row"/>

View File

@ -0,0 +1,6 @@
import type { PayPeriod } from "./pay-period-interface";
export interface PayPeriodBundle {
current: PayPeriod;
periods: PayPeriod[];
}

View File

@ -1,7 +1,8 @@
export interface PayPeriod { export interface PayPeriod {
period_number: number; pay_period_no: number;
start_date: string; period_start: string;
end_date: string; period_end: string;
year: number; payday: string;
pay_year: number;
label: string; label: string;
}; }

View File

@ -15,24 +15,68 @@
</script> </script>
<template> <template>
<div class="q-px-sm q-pb-sm col-xs-6 col-sm-4 col-md-3 col-lg-2 grid-style-transition"> <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"> <q-card class="rounded-10">
<q-card-section class="q-pb-sm"> <!-- Card header with employee name -->
<div class="text-primary text-h5 text-weight-bolder ellipsis">{{ props.row.employee_name }}</div> <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-card-section>
<div
v-for="col in props.cols.filter(col => col.name !== 'employee_name')" <q-separator color="accent" style="height: 2px;"/>
:key="col.name"
class="q-pa-none q-mx-sm items-center row" <!-- Main body of pay period card -->
:class="{ 'bg-warning': col.name == 'overtime_hours' && col.value as number > 0 }" <q-card-section class="q-pa-none q-mt-xs q-mb-sm">
> <div class="row no-wrap">
<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> <!-- left portion of pay period card -->
<q-card-section class="text-weight-bold q-pa-none col-9" >{{ col.label }}</q-card-section> <div class="column no-wrap" :class="$q.screen.lt.md ? 'col' : 'col-8'">
</div> <!-- 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 <q-card-section
horizontal horizontal
class="q-pa-sm q-mt-sm text-weight-bold" class="q-pa-sm text-weight-bold"
:class="props.modelValue ? 'text-white bg-primary' : 'text-primary bg-white'" :class="props.modelValue ? 'text-white bg-primary' : 'text-primary bg-white'"
> >
<q-space /> <q-space />
<q-checkbox <q-checkbox

View File

@ -11,11 +11,20 @@
import { useTimesheetStore } from 'src/stores/timesheet-store'; import { useTimesheetStore } from 'src/stores/timesheet-store';
const { t } = useI18n(); const { t } = useI18n();
const timesheetApprovalApi = useTimesheetApprovalApi();
const currentPayPeriod = getCurrentPayPeriod(); const currentPayPeriod = getCurrentPayPeriod();
const currentYear = (new Date()).getFullYear(); 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 authStore = useAuthStore();
const timesheetStore = useTimesheetStore(); const timesheetStore = useTimesheetStore();
const timesheetApprovalApi = useTimesheetApprovalApi();
const columns = computed((): QTableColumn<PayPeriodEmployeeOverview>[] => [ const columns = computed((): QTableColumn<PayPeriodEmployeeOverview>[] => [
{ name: 'employee_name', label: t('timeSheetValidations.tableColumnLabelFullname'), field: 'employee_name', sortable: true }, { name: 'employee_name', label: t('timeSheetValidations.tableColumnLabelFullname'), field: 'employee_name', sortable: true },
@ -30,7 +39,9 @@
const filter = ref(''); const filter = ref('');
onMounted( async () => { onMounted( async () => {
await timesheetApprovalApi.getCurrentAndAllPayPeriods();
await timesheetApprovalApi.getTimesheetApprovalPayPeriodEmployeeOverviews(currentYear, currentPayPeriod, authStore.user.email); await timesheetApprovalApi.getTimesheetApprovalPayPeriodEmployeeOverviews(currentYear, currentPayPeriod, authStore.user.email);
originalApprovals.value = Object.fromEntries( timesheetStore.payPeriodEmployeeOverviews.map(emp => [emp.email, emp.is_approved]));
}) })
</script> </script>
@ -44,9 +55,10 @@
:filter="filter" :filter="filter"
grid grid
dense dense
hide-pagination
color="primary" color="primary"
:rows-per-page-options="[0]" :rows-per-page-options="[0]"
card-container-class="justify-center q-gutter-md" card-container-class="justify-center"
:loading="timesheetStore.isLoading" :loading="timesheetStore.isLoading"
:no-data-label="$t('shared.failedToLoad')" :no-data-label="$t('shared.failedToLoad')"
:no-results-label="$t('shared.failedToSearch')" :no-results-label="$t('shared.failedToSearch')"
@ -54,7 +66,7 @@
> >
<!-- Top Bar that contains Search, Title, Filters --> <!-- Top Bar that contains Search, Title, Filters -->
<template v-slot:top> <template v-slot:top>
<q-card flat class="full-width bg-transparent row q-px-md"> <div class="full-width row">
<q-space /> <q-space />
<!-- Filters toggle --> <!-- Filters toggle -->
@ -79,7 +91,7 @@
<q-icon name="search" color="primary"/> <q-icon name="search" color="primary"/>
</template> </template>
</q-input> </q-input>
</q-card> </div>
</template> </template>
<!-- Template for individual employee cards --> <!-- Template for individual employee cards -->

View File

@ -2,20 +2,15 @@
import { useTimesheetStore } from 'src/stores/timesheet-store'; import { useTimesheetStore } from 'src/stores/timesheet-store';
const timesheetStore = useTimesheetStore(); const timesheetStore = useTimesheetStore();
const updateCurrentPayPeriod = () => {
timesheetStore.getCurrentPayPeriod();
};
</script> </script>
<template> <template>
<div class="column items-center"> <div class="column items-center">
<div class="text-primary text-h5">{{ timesheetStore.currentPayPeriod?.label }}</div> <div class="text-primary text-h5">{{ timesheetStore.currentPayPeriod?.label || '' }}</div>
<q-btn-group push rounded> <q-btn-group push rounded>
<q-btn push icon="keyboard_arrow_left" color="primary" class="q-px-xl" /> <q-btn push icon="keyboard_arrow_left" color="primary" class="q-px-xl" @click="timesheetStore.getPreviousOrNextPayPeriod(-1)"/>
<q-btn push icon="date_range" color="primary" class="q-px-xl" @click="updateCurrentPayPeriod" /> <q-btn push icon="date_range" color="primary" class="q-px-xl" />
<q-btn push icon="keyboard_arrow_right" color="primary" class="q-px-xl" /> <q-btn push icon="keyboard_arrow_right" color="primary" class="q-px-xl" @click="timesheetStore.getPreviousOrNextPayPeriod(1)"/>
</q-btn-group> </q-btn-group>
</div> </div>
</template> </template>

View File

@ -1,13 +1,18 @@
import { useTimesheetStore } from "src/stores/timesheet-store"; import { useTimesheetStore } from "src/stores/timesheet-store";
export const useTimesheetApprovalApi = () => { export const useTimesheetApprovalApi = () => {
const timesheetStore = useTimesheetStore(); const timesheet_store = useTimesheetStore();
const getCurrentAndAllPayPeriods = async () => {
await timesheet_store.getCurrentAndAllPayPeriods();
}
const getTimesheetApprovalPayPeriodEmployeeOverviews = async (year: number, period_number: number, supervisor_email: string): Promise<void> => { const getTimesheetApprovalPayPeriodEmployeeOverviews = async (year: number, period_number: number, supervisor_email: string): Promise<void> => {
await timesheetStore.getTimesheetApprovalPayPeriodEmployeeOverviews(year, period_number, supervisor_email); await timesheet_store.getTimesheetApprovalPayPeriodEmployeeOverviews(year, period_number, supervisor_email);
} }
return { return {
getTimesheetApprovalPayPeriodEmployeeOverviews, getTimesheetApprovalPayPeriodEmployeeOverviews,
getCurrentAndAllPayPeriods,
} }
}; };

View File

@ -1,22 +1,11 @@
import { api } from "src/boot/axios"; import { api } from "src/boot/axios";
import { mock_pay_periods } from "../timesheet-approval-test-constants";
import type { PayPeriod } from "src/modules/shared/types/pay-period-interface";
import type { PayPeriodOverview } from "../types/timesheet-approval-pay-period-overview-interface"; import type { PayPeriodOverview } from "../types/timesheet-approval-pay-period-overview-interface";
import type { PayPeriodBundle } from "src/modules/shared/types/pay-period-bundle-interface";
export const timesheetApprovalService = { export const timesheetApprovalService = {
getCurrentPayPeriod: (): PayPeriod => { getCurrentAndAllPayPeriod: async (): Promise<PayPeriodBundle> => {
return { const response = await api.get('pay-periods/bundle/current-and-all');
"period_number": 15, return response.data;
"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 (year: number, period_number: number, supervisor_email: string): Promise<PayPeriodOverview> => { getPayPeriodEmployeeOverviews: async (year: number, period_number: number, supervisor_email: string): Promise<PayPeriodOverview> => {

View File

@ -1,287 +1,287 @@
import type { PayPeriod } from "../shared/types/pay-period-interface"; // import type { PayPeriod } from "../shared/types/pay-period-interface";
import type { PayPeriodEmployeeOverview } from "./types/timesheet-approval-pay-period-employee-overview-interface" // import type { PayPeriodEmployeeOverview } from "./types/timesheet-approval-pay-period-employee-overview-interface"
export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [ // export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
{ // {
"email": 'EMP-001', // "email": 'EMP-001',
"employee_name": 'Alice Johnson', // "employee_name": 'Alice Johnson',
"regular_hours": 75, // "regular_hours": 75,
"evening_hours": 12, // "evening_hours": 12,
"emergency_hours": 3, // "emergency_hours": 3,
"overtime_hours": 5, // "overtime_hours": 5,
"expenses": 120.50, // "expenses": 120.50,
"mileage": 45, // "mileage": 45,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-002', // "email": 'EMP-002',
"employee_name": 'Brian Smith', // "employee_name": 'Brian Smith',
"regular_hours": 80, // "regular_hours": 80,
"evening_hours": 8, // "evening_hours": 8,
"emergency_hours": 0, // "emergency_hours": 0,
"overtime_hours": 2, // "overtime_hours": 2,
"expenses": 75.00, // "expenses": 75.00,
"mileage": 12, // "mileage": 12,
"is_approved": true // "is_approved": true
}, // },
{ // {
"email": 'EMP-003', // "email": 'EMP-003',
"employee_name": 'Chloe Ramirez', // "employee_name": 'Chloe Ramirez',
"regular_hours": 68, // "regular_hours": 68,
"evening_hours": 15, // "evening_hours": 15,
"emergency_hours": 1, // "emergency_hours": 1,
"overtime_hours": 0, // "overtime_hours": 0,
"expenses": 200.00, // "expenses": 200.00,
"mileage": 88, // "mileage": 88,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-004', // "email": 'EMP-004',
"employee_name": 'David Lee', // "employee_name": 'David Lee',
"regular_hours": 82, // "regular_hours": 82,
"evening_hours": 5, // "evening_hours": 5,
"emergency_hours": 4, // "emergency_hours": 4,
"overtime_hours": 6, // "overtime_hours": 6,
"expenses": 50.75, // "expenses": 50.75,
"mileage": 20, // "mileage": 20,
"is_approved": true // "is_approved": true
}, // },
{ // {
"email": 'EMP-005', // "email": 'EMP-005',
"employee_name": 'Emily Carter', // "employee_name": 'Emily Carter',
"regular_hours": 78, // "regular_hours": 78,
"evening_hours": 10, // "evening_hours": 10,
"emergency_hours": 2, // "emergency_hours": 2,
"overtime_hours": 3, // "overtime_hours": 3,
"expenses": 95.25, // "expenses": 95.25,
"mileage": 60, // "mileage": 60,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-006', // "email": 'EMP-006',
"employee_name": 'Maxime Murray Gendron', // "employee_name": 'Maxime Murray Gendron',
"regular_hours": 80, // "regular_hours": 80,
"evening_hours": 0, // "evening_hours": 0,
"emergency_hours": 0, // "emergency_hours": 0,
"overtime_hours": 0, // "overtime_hours": 0,
"expenses": 0, // "expenses": 0,
"mileage": 0, // "mileage": 0,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-007', // "email": 'EMP-007',
"employee_name": 'Marc-André Henrico', // "employee_name": 'Marc-André Henrico',
"regular_hours": 80, // "regular_hours": 80,
"evening_hours": 0, // "evening_hours": 0,
"emergency_hours": 0, // "emergency_hours": 0,
"overtime_hours": 0, // "overtime_hours": 0,
"expenses": 0, // "expenses": 0,
"mileage": 0, // "mileage": 0,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-008', // "email": 'EMP-008',
"employee_name": 'Jessy Sharock', // "employee_name": 'Jessy Sharock',
"regular_hours": 80, // "regular_hours": 80,
"evening_hours": 0, // "evening_hours": 0,
"emergency_hours": 0, // "emergency_hours": 0,
"overtime_hours": 0, // "overtime_hours": 0,
"expenses": 0, // "expenses": 0,
"mileage": 0, // "mileage": 0,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-009', // "email": 'EMP-009',
"employee_name": 'David Richer', // "employee_name": 'David Richer',
"regular_hours": 80, // "regular_hours": 80,
"evening_hours": 0, // "evening_hours": 0,
"emergency_hours": 0, // "emergency_hours": 0,
"overtime_hours": 0, // "overtime_hours": 0,
"expenses": 0, // "expenses": 0,
"mileage": 0, // "mileage": 0,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-010', // "email": 'EMP-010',
"employee_name": 'Nicolas Drolet', // "employee_name": 'Nicolas Drolet',
"regular_hours": 80, // "regular_hours": 80,
"evening_hours": 0, // "evening_hours": 0,
"emergency_hours": 0, // "emergency_hours": 0,
"overtime_hours": 0, // "overtime_hours": 0,
"expenses": 0, // "expenses": 0,
"mileage": 0, // "mileage": 0,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-011', // "email": 'EMP-011',
"employee_name": 'Frederick Pruneau', // "employee_name": 'Frederick Pruneau',
"regular_hours": 16, // "regular_hours": 16,
"evening_hours": 0, // "evening_hours": 0,
"emergency_hours": 0, // "emergency_hours": 0,
"overtime_hours": 0, // "overtime_hours": 0,
"expenses": 0, // "expenses": 0,
"mileage": 0, // "mileage": 0,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-012', // "email": 'EMP-012',
"employee_name": 'Matthieu Haineault Gervais', // "employee_name": 'Matthieu Haineault Gervais',
"regular_hours": 80, // "regular_hours": 80,
"evening_hours": 0, // "evening_hours": 0,
"emergency_hours": 0, // "emergency_hours": 0,
"overtime_hours": 0, // "overtime_hours": 0,
"expenses": 0, // "expenses": 0,
"mileage": 0, // "mileage": 0,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-013', // "email": 'EMP-013',
"employee_name": 'Robinson Viaud', // "employee_name": 'Robinson Viaud',
"regular_hours": 80, // "regular_hours": 80,
"evening_hours": 0, // "evening_hours": 0,
"emergency_hours": 0, // "emergency_hours": 0,
"overtime_hours": 0, // "overtime_hours": 0,
"expenses": 0, // "expenses": 0,
"mileage": 0, // "mileage": 0,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-014', // "email": 'EMP-014',
"employee_name": 'Geneviève Bourdon', // "employee_name": 'Geneviève Bourdon',
"regular_hours": 80, // "regular_hours": 80,
"evening_hours": 0, // "evening_hours": 0,
"emergency_hours": 0, // "emergency_hours": 0,
"overtime_hours": 0, // "overtime_hours": 0,
"expenses": 0, // "expenses": 0,
"mileage": 0, // "mileage": 0,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-015', // "email": 'EMP-015',
"employee_name": 'Frédérique Soulard', // "employee_name": 'Frédérique Soulard',
"regular_hours": 80, // "regular_hours": 80,
"evening_hours": 0, // "evening_hours": 0,
"emergency_hours": 0, // "emergency_hours": 0,
"overtime_hours": 0, // "overtime_hours": 0,
"expenses": 0, // "expenses": 0,
"mileage": 0, // "mileage": 0,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-016', // "email": 'EMP-016',
"employee_name": 'Patrick Doucet', // "employee_name": 'Patrick Doucet',
"regular_hours": 80, // "regular_hours": 80,
"evening_hours": 0, // "evening_hours": 0,
"emergency_hours": 0, // "emergency_hours": 0,
"overtime_hours": 0, // "overtime_hours": 0,
"expenses": 0, // "expenses": 0,
"mileage": 0, // "mileage": 0,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-017', // "email": 'EMP-017',
"employee_name": 'Dahlia Tremblay', // "employee_name": 'Dahlia Tremblay',
"regular_hours": 80, // "regular_hours": 80,
"evening_hours": 0, // "evening_hours": 0,
"emergency_hours": 0, // "emergency_hours": 0,
"overtime_hours": 0, // "overtime_hours": 0,
"expenses": 0, // "expenses": 0,
"mileage": 0, // "mileage": 0,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-018', // "email": 'EMP-018',
"employee_name": 'Louis Morneau', // "employee_name": 'Louis Morneau',
"regular_hours": 80, // "regular_hours": 80,
"evening_hours": 0, // "evening_hours": 0,
"emergency_hours": 0, // "emergency_hours": 0,
"overtime_hours": 0, // "overtime_hours": 0,
"expenses": 0, // "expenses": 0,
"mileage": 0, // "mileage": 0,
"is_approved": false // "is_approved": false
}, // },
{ // {
"email": 'EMP-019', // "email": 'EMP-019',
"employee_name": 'Michel Blais', // "employee_name": 'Michel Blais',
"regular_hours": 80, // "regular_hours": 80,
"evening_hours": 0, // "evening_hours": 0,
"emergency_hours": 0, // "emergency_hours": 0,
"overtime_hours": 0, // "overtime_hours": 0,
"expenses": 0, // "expenses": 0,
"mileage": 0, // "mileage": 0,
"is_approved": false // "is_approved": false
} // }
]; // ];
export const mock_pay_periods: PayPeriod[] = [ // export const mock_pay_periods: PayPeriod[] = [
{ // {
"period_number": 15, // "period_number": 15,
"start_date": "2025-07-27", // "start_date": "2025-07-27",
"end_date": "2025-08-09", // "end_date": "2025-08-09",
"year": 2025, // "year": 2025,
"label": "2025-07-27 → 2025-08-09" // "label": "2025-07-27 → 2025-08-09"
}, // },
{ // {
"period_number": 14, // "period_number": 14,
"start_date": "2025-07-13", // "start_date": "2025-07-13",
"end_date": "2025-07-26", // "end_date": "2025-07-26",
"year": 2025, // "year": 2025,
"label": "2025-07-13 → 2025-07-26" // "label": "2025-07-13 → 2025-07-26"
}, // },
{ // {
"period_number": 13, // "period_number": 13,
"start_date": "2025-06-29", // "start_date": "2025-06-29",
"end_date": "2025-07-12", // "end_date": "2025-07-12",
"year": 2025, // "year": 2025,
"label": "2025-06-29 → 2025-07-12" // "label": "2025-06-29 → 2025-07-12"
}, // },
{ // {
"period_number": 12, // "period_number": 12,
"start_date": "2025-06-15", // "start_date": "2025-06-15",
"end_date": "2025-06-28", // "end_date": "2025-06-28",
"year": 2025, // "year": 2025,
"label": "2025-06-15 → 2025-06-28" // "label": "2025-06-15 → 2025-06-28"
}, // },
{ // {
"period_number": 11, // "period_number": 11,
"start_date": "2025-06-01", // "start_date": "2025-06-01",
"end_date": "2025-06-14", // "end_date": "2025-06-14",
"year": 2025, // "year": 2025,
"label": "2025-06-01 → 2025-06-14" // "label": "2025-06-01 → 2025-06-14"
}, // },
{ // {
"period_number": 10, // "period_number": 10,
"start_date": "2025-05-18", // "start_date": "2025-05-18",
"end_date": "2025-05-31", // "end_date": "2025-05-31",
"year": 2025, // "year": 2025,
"label": "2025-05-18 → 2025-05-31" // "label": "2025-05-18 → 2025-05-31"
}, // },
{ // {
"period_number": 9, // "period_number": 9,
"start_date": "2025-05-04", // "start_date": "2025-05-04",
"end_date": "2025-05-17", // "end_date": "2025-05-17",
"year": 2025, // "year": 2025,
"label": "2025-05-04 → 2025-05-17" // "label": "2025-05-04 → 2025-05-17"
}, // },
{ // {
"period_number": 8, // "period_number": 8,
"start_date": "2025-04-20", // "start_date": "2025-04-20",
"end_date": "2025-05-03", // "end_date": "2025-05-03",
"year": 2025, // "year": 2025,
"label": "2025-04-20 → 2025-05-03" // "label": "2025-04-20 → 2025-05-03"
}, // },
{ // {
"period_number": 7, // "period_number": 7,
"start_date": "2025-04-06", // "start_date": "2025-04-06",
"end_date": "2025-04-19", // "end_date": "2025-04-19",
"year": 2025, // "year": 2025,
"label": "2025-04-06 → 2025-04-19" // "label": "2025-04-06 → 2025-04-19"
}, // },
{ // {
"period_number": 6, // "period_number": 6,
"start_date": "2025-03-23", // "start_date": "2025-03-23",
"end_date": "2025-04-05", // "end_date": "2025-04-05",
"year": 2025, // "year": 2025,
"label": "2025-03-23 → 2025-04-05" // "label": "2025-03-23 → 2025-04-05"
} // }
] // ]

View File

@ -4,17 +4,31 @@ import { timesheetApprovalService } from 'src/modules/timesheet-approval/service
import type { PayPeriod } from 'src/modules/shared/types/pay-period-interface'; 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"; 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"};
export const useTimesheetStore = defineStore('timesheet', () => { export const useTimesheetStore = defineStore('timesheet', () => {
const payPeriods = ref<PayPeriod[]>([]); const payPeriods = ref<PayPeriod[]>([]);
const currentPayPeriod = ref<PayPeriod>(default_current_pay_period); const currentPayPeriod = ref<PayPeriod>();
const currentPayPeriodIndex = ref<number>(-1);
const payPeriodEmployeeOverviews = ref<PayPeriodEmployeeOverview[]>([]); const payPeriodEmployeeOverviews = ref<PayPeriodEmployeeOverview[]>([]);
const isLoading = ref<boolean>(false); const isLoading = ref<boolean>(false);
const getCurrentPayPeriod = () => { const getCurrentAndAllPayPeriods = async () => {
currentPayPeriod.value = timesheetApprovalService.getCurrentPayPeriod(); isLoading.value = true;
try {
const response = await timesheetApprovalService.getCurrentAndAllPayPeriod();
currentPayPeriod.value = response.current;
payPeriods.value = response.periods;
currentPayPeriodIndex.value = payPeriods.value.findIndex( pay_period => pay_period.pay_period_no === currentPayPeriod.value?.pay_period_no);
} catch( error ){
console.error('Could not get current pay period: ', error );
}
isLoading.value = false;
};
const getPreviousOrNextPayPeriod = (direction: number) => {
if ( currentPayPeriodIndex.value + direction >= 0 && currentPayPeriodIndex.value + direction < payPeriods.value.length ) {
currentPayPeriodIndex.value += direction;
currentPayPeriod.value = payPeriods.value.at(currentPayPeriodIndex.value);
}
} }
const getTimesheetApprovalPayPeriodEmployeeOverviews = async (year: number, period_number: number, supervisor_email: string) => { const getTimesheetApprovalPayPeriodEmployeeOverviews = async (year: number, period_number: number, supervisor_email: string) => {
@ -27,7 +41,7 @@ export const useTimesheetStore = defineStore('timesheet', () => {
// TODO: trigger an alert window with an error message here! // TODO: trigger an alert window with an error message here!
} }
isLoading.value = false; isLoading.value = false;
} };
return { payPeriods, currentPayPeriod, payPeriodEmployeeOverviews, isLoading, getCurrentPayPeriod, getTimesheetApprovalPayPeriodEmployeeOverviews}; return { payPeriods, currentPayPeriod, payPeriodEmployeeOverviews, isLoading, getCurrentAndAllPayPeriods, getTimesheetApprovalPayPeriodEmployeeOverviews, getPreviousOrNextPayPeriod};
}); });