feat(approvals): add page for timesheet approvals, complete date picker component, begin work for approvals table.

This commit is contained in:
Nicolas Drolet 2025-08-11 17:04:21 -04:00
parent 1f94d6a900
commit 2fb7b4a4c1
10 changed files with 207 additions and 48 deletions

View File

@ -14,10 +14,13 @@
$primary: #019547; $primary: #019547;
$secondary: #DAE0E7; $secondary: #DAE0E7;
$accent: #81AFE3; $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;

View File

@ -292,19 +292,17 @@ 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: 'Expenses',
tableCol_7: 'Mileage', tableColumnLabelMileage: '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: 'Verified',
timeSheetStatus_unverified: 'Unverified', timeSheetStatusUnverified: 'Unverified',
timeSheetStatus_partial: 'Partial', timeSheetStatus_partial: 'Partial',
timeSheetStatus_complete: 'Complete', timeSheetStatus_complete: 'Complete',
timeSheetStatus_empty: 'Empty', timeSheetStatus_empty: 'Empty',

View File

@ -338,19 +338,17 @@ 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 durgence', tableColumnLabelEmergencyHours: 'Heures durgence',
tableCol_5: 'Heures supplémentaires', tableColumnLabelOvertime: 'Heures supplémentaires',
tableCol_6: 'Dépenses ', tableColumnLabelExpenses: 'Dépenses',
tableCol_7: 'Kilométrage ', tableColumnLabelMileage: '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: 'Vérifié',
timeSheetStatus_unverified: 'Invérifié', timeSheetStatusUnverified: 'Invérifié',
timeSheetStatus_partial: 'Partiel', timeSheetStatus_partial: 'Partiel',
timeSheetStatus_complete: 'Complet', timeSheetStatus_complete: 'Complet',
timeSheetStatus_empty: 'Vide', timeSheetStatus_empty: 'Vide',

View File

@ -0,0 +1,76 @@
<script setup lang="ts">
/* eslint-disable */
import { 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';
const { t } = useI18n();
const columns: QTableColumn<PayPeriodEmployeeOverview>[] = [
{ name: 'employee_name', label: t('tableColumnLabelFullname'), field: 'employee_name', sortable: true },
{ name: 'regular_hours', label: t('tableColumnLabelRegularHours'), field: 'regular_hours', sortable: true },
{ name: 'evening_hours', label: t('tableColumnLabelEveningHours'), field: 'evening_hours' },
{ name: 'emergency_hours', label: t('tableColumnLabelEmergencyHours'), field: 'emergency_hours' },
{ name: 'overtime_hours', label: t('tableColumnLabelOvertimeHours'), field: 'overtime_hours' },
{ name: 'expenses', label: t('tableColumnLabelExpenses'), field: 'expenses', sortable: true },
{ name: 'mileage', label: t('tableColumnLabelMileage'), field: 'mileage', sortable: true }
]
const filter = ref('');
const selected = ref([]);
const props = defineProps<{
rows: PayPeriodEmployeeOverview[];
}>();
</script>
<template>
<div class="q-pa-md">
<q-table
:title="$t('timeSheetValidations.tableHeader')"
:rows="rows"
:columns="columns"
row-key="name"
selection="multiple"
v-model:selected="selected"
:filter="filter"
grid
hide-header
>
<template v-slot:top-right>
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
</template>
<template v-slot:item="props: { cols: (QTableColumn<PayPeriodEmployeeOverview> & { value: unknown })[], row: PayPeriodEmployeeOverview, selected: boolean }">
<div
class="q-pa-xs col-xs-12 col-sm-6 col-md-4 col-lg-3 grid-style-transition"
:style="props.selected ? 'transform: scale(0.95);' : ''"
>
<q-card>
<q-card-section>
<q-checkbox dense v-model="props.selected" :label="props.row.is_approved ? $t('timeSheetValidations.timeSheetStatusVerified') : $t('timeSheetValidations.timeSheetStatusUnverified')" />
</q-card-section>
<q-separator />
<q-list dense>
<q-item v-for="col in props.cols.filter(col => col.name !== 'desc')" :key="col.name">
<q-item-section>
<q-item-label>{{ col.label }}</q-item-label>
</q-item-section>
<q-item-section side>
<q-item-label caption>{{ col.value }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-card>
</div>
</template>
</q-table>
</div>
</template>

View File

@ -2,15 +2,20 @@
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="row"> <div class="column items-center">
<q-btn-group outline rounded> <div class="text-primary text-h5">{{ timesheetStore.currentPayPeriod?.label }}</div>
<q-btn outline icon="arrow_circle_left"/> <q-btn-group push rounded>
<q-btn outline icon="calendar_month" :label="timesheetStore.currentPayPeriod?.label"/> <q-btn push icon="keyboard_arrow_left" color="primary" class="q-px-xl" />
<q-btn outline icon="arrow_circle_right"/> <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> </q-btn-group>
<div class="text-primary text-h4">{{ timesheetStore.currentPayPeriod?.label }}</div>
</div> </div>
</template> </template>

View File

@ -1,9 +1,11 @@
<script setup lang="ts"> <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'] // 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-item.vue';
</script> </script>
<template> <template>
<q-page padding class="q-pa-md bg-secondary"> <q-page padding class="q-pa-md bg-secondary">
<TimesheetApprovalPeriodPicker />
</q-page> </q-page>
</template> </template>

View File

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

View File

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

View File

@ -1,18 +1,78 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { ref } from 'vue'; 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'; 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
}
];
export const useTimesheetStore = defineStore('timesheet', () => { export const useTimesheetStore = defineStore('timesheet', () => {
const payPeriods = ref<PayPeriod[]>([]); const payPeriods = ref<PayPeriod[]>([]);
const currentPayPeriod = ref<PayPeriod>(); const currentPayPeriod = ref<PayPeriod>(default_current_pay_period);
const payPeriodEmployeeOverviews = ref<PayPeriodEmployeeOverview[]>(mock_pay_period_employee_overviews);
const getCurrentPayPeriod = async () => { const getCurrentPayPeriod = () => {
if (!currentPayPeriod.value) { currentPayPeriod.value = timesheetApprovalService.getCurrentPayPeriod();
currentPayPeriod.value = await timesheetApprovalService.getCurrentPayPeriod();
}
return currentPayPeriod.value;
} }
return { payPeriods, currentPayPeriod, getCurrentPayPeriod}; return { payPeriods, currentPayPeriod, payPeriodEmployeeOverviews, getCurrentPayPeriod};
}); });