feat(timesheet): fully implement holiday tracker with web API. Only works for current year, however.

This commit is contained in:
Nicolas Drolet 2026-01-12 16:14:54 -05:00
parent 8b0e40dc2a
commit b8c112f149
5 changed files with 77 additions and 24 deletions

View File

@ -6,18 +6,19 @@
import ShiftListDateWidget from 'src/modules/timesheets/components/shift-list-date-widget.vue'; import ShiftListDateWidget from 'src/modules/timesheets/components/shift-list-date-widget.vue';
import { date, useQuasar } from 'quasar'; import { date, useQuasar } from 'quasar';
import { ref, computed, watch } from 'vue'; import { ref, computed, watch, onMounted } from 'vue';
import { useUiStore } from 'src/stores/ui-store'; import { useUiStore } from 'src/stores/ui-store';
import { useTimesheetStore } from 'src/stores/timesheet-store'; import { useTimesheetStore } from 'src/stores/timesheet-store';
import { Shift } from 'src/modules/timesheets/models/shift.models'; import { Shift } from 'src/modules/timesheets/models/shift.models';
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api'; import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
import type { TimesheetDay } from 'src/modules/timesheets/models/timesheet.models'; import type { TimesheetDay } from 'src/modules/timesheets/models/timesheet.models';
import { useI18n } from 'vue-i18n';
const CURRENT_DATE_STRING = new Date().toISOString().slice(0, 10); const CURRENT_DATE_STRING = new Date().toISOString().slice(0, 10);
const HOLIDAYS: string[] = ['2026-01-01', '2025-11-26']
const { extractDate } = date; const { extractDate } = date;
const { locale } = useI18n();
const q = useQuasar(); const q = useQuasar();
const ui_store = useUiStore(); const ui_store = useUiStore();
@ -59,11 +60,26 @@
return iso_date_string === CURRENT_DATE_STRING ? 'currentDayComponent' : ''; return iso_date_string === CURRENT_DATE_STRING ? 'currentDayComponent' : '';
}; };
const getHolidayName = (date: string) => {
const holiday = timesheet_store.federal_holidays.find(holiday => holiday.date === date);
if (!holiday) return;
if (locale.value === 'fr-FR')
return holiday.nameFr;
else if (locale.value === 'en-CA')
return holiday.nameEn;
};
onMounted(async () => {
await timesheet_store.getCurrentFederalHolidays();
});
watch(currentDayComponentWatcher, () => { watch(currentDayComponentWatcher, () => {
if (currentDayComponent.value && q.platform.is.mobile) { if (currentDayComponent.value && q.platform.is.mobile) {
emit('onCurrentDayComponentFound', currentDayComponent.value[0]) emit('onCurrentDayComponentFound', currentDayComponent.value[0])
} }
}) });
</script> </script>
<template> <template>
@ -111,11 +127,11 @@
> >
<!-- optional label indicating which holiday if today is a holiday --> <!-- optional label indicating which holiday if today is a holiday -->
<span <span
v-if="HOLIDAYS.includes(day.date)" v-if="timesheet_store.federal_holidays.some(holiday => holiday.date === day.date)"
class="absolute-top-left text-uppercase text-weight-bolder text-purple-5" class="absolute-top-left text-uppercase text-weight-bolder text-purple-5"
style="transform: translate(25px, -7px);" style="transform: translate(25px, -7px);"
> >
New Year's of the nouvelle annee {{ getHolidayName(day.date) }}
</span> </span>
<!-- mobile version in portrait mode --> <!-- mobile version in portrait mode -->
@ -183,11 +199,11 @@
<div <div
v-else v-else
class="col row full-width rounded-10 ellipsis shadow-10" class="col row full-width rounded-10 ellipsis shadow-10"
:style="HOLIDAYS.includes(day.date) ? 'border: 2px solid #ab47bc' : ''" :style="timesheet_store.federal_holidays.some(holiday => holiday.date === day.date) ? 'border: 2px solid #ab47bc' : ''"
> >
<div <div
class="col row" class="col row"
:class="(getDayApproval(day) || timesheet.is_approved) ? (HOLIDAYS.includes(day.date) ? 'bg-purple-5' : 'bg-accent') : 'bg-dark'" :class="(getDayApproval(day) || timesheet.is_approved) ? (timesheet_store.federal_holidays.some(holiday => holiday.date === day.date) ? 'bg-purple-5' : 'bg-accent') : 'bg-dark'"
> >
<!-- Date block --> <!-- Date block -->
<ShiftListDateWidget <ShiftListDateWidget
@ -200,7 +216,7 @@
:timesheet-id="timesheet.timesheet_id" :timesheet-id="timesheet.timesheet_id"
:week-day-index="day_index" :week-day-index="day_index"
:day="day" :day="day"
:holiday="HOLIDAYS.includes(day.date)" :holiday="timesheet_store.federal_holidays.some(holiday => holiday.date === day.date)"
:approved="getDayApproval(day) || timesheet.is_approved" :approved="getDayApproval(day) || timesheet.is_approved"
class="col" class="col"
@delete-unsaved-shift="deleteUnsavedShift(timesheet_index, day_index)" @delete-unsaved-shift="deleteUnsavedShift(timesheet_index, day_index)"
@ -215,7 +231,7 @@
color="white" color="white"
size="xl" size="xl"
class="full-height" class="full-height"
:class="(getDayApproval(day) || timesheet.is_approved) ? (HOLIDAYS.includes(day.date) ? 'bg-purple-5' : 'bg-accent') : ''" :class="(getDayApproval(day) || timesheet.is_approved) ? (timesheet_store.federal_holidays.some(holiday => holiday.date === day.date) ? 'bg-purple-5' : 'bg-accent') : ''"
/> />
<q-btn <q-btn
@ -224,7 +240,7 @@
square square
icon="more_time" icon="more_time"
size="lg" size="lg"
:color="HOLIDAYS.includes(day.date) ? 'purple-5' : 'accent'" :color="timesheet_store.federal_holidays.some(holiday => holiday.date === day.date) ? 'purple-5' : 'accent'"
text-color="white" text-color="white"
class="full-height" class="full-height"
:class="$q.platform.is.mobile ? 'q-px-xs' : ''" :class="$q.platform.is.mobile ? 'q-px-xs' : ''"

View File

@ -0,0 +1,31 @@
export interface FederalHoliday {
id: number;
date: string;
nameEn: string;
nameFr: string;
federal: number;
observedDate: string;
provinces: FederalHolidayProvince[];
}
export interface FederalHolidayProvince {
id: number;
nameEn: string;
nameFr: string;
sourceLink: string;
sourceEn: string;
}
export const TARGO_HOLIDAY_NAMES_FR: string[] = [
"Jour de lAn",
"Vendredi saint",
"Lundi de Pâques",
"Journée nationale des patriotes",
"Saint-Jean-Baptiste / Fête nationale du Québec",
"Fête du Canada",
"Fête du travail",
"Journée nationale de la vérité et de la réconciliation",
"Action de grâce",
"Noël",
"Lendemain de Noël",
]

View File

@ -3,8 +3,14 @@ import type { PayPeriod } from "src/modules/shared/models/pay-period.models";
import type { TimesheetResponse } from "src/modules/timesheets/models/timesheet.models"; import type { TimesheetResponse } from "src/modules/timesheets/models/timesheet.models";
import type { TimesheetApprovalOverview } from "src/modules/timesheet-approval/models/timesheet-overview.models"; import type { TimesheetApprovalOverview } from "src/modules/timesheet-approval/models/timesheet-overview.models";
import type { BackendResponse } from "src/modules/shared/models/backend-response.models"; import type { BackendResponse } from "src/modules/shared/models/backend-response.models";
import type { FederalHoliday } from "src/modules/timesheets/models/federal-holidays.models";
export const timesheetService = { export const timesheetService = {
getAllFederalHolidays: async (): Promise<FederalHoliday[]> => {
const response = await api.get<{ holidays: FederalHoliday[] }>('https://canada-holidays.ca/api/v1/holidays', { withCredentials: false });
return response.data.holidays;
},
getPayPeriodByDate: async (date_string: string): Promise<PayPeriod> => { getPayPeriodByDate: async (date_string: string): Promise<PayPeriod> => {
const response = await api.get<{ success: boolean, data: PayPeriod, error?: string }>(`pay-periods/date/${date_string}`); const response = await api.get<{ success: boolean, data: PayPeriod, error?: string }>(`pay-periods/date/${date_string}`);
return response.data.data; return response.data.data;

View File

@ -35,17 +35,4 @@ export const SHIFT_OPTIONS: ShiftOption[] = [
{ label: 'timesheet.shift.types.SICK', value: 'SICK', icon: 'medication_liquid', icon_color: 'light-blue-6' }, { label: 'timesheet.shift.types.SICK', value: 'SICK', icon: 'medication_liquid', icon_color: 'light-blue-6' },
{ label: 'timesheet.shift.types.BANKING', value: 'BANKING', icon: 'savings', icon_color: 'pink-3' }, { label: 'timesheet.shift.types.BANKING', value: 'BANKING', icon: 'savings', icon_color: 'pink-3' },
{ label: 'timesheet.shift.types.WITHDRAW_BANKED', value: 'WITHDRAW_BANKED', icon: 'attach_money', icon_color: 'yellow-4' }, { label: 'timesheet.shift.types.WITHDRAW_BANKED', value: 'WITHDRAW_BANKED', icon: 'attach_money', icon_color: 'yellow-4' },
]; ];
export const HOLIDAY_NAMES: string[] = [
"Jour de lAn",
"Vendredi saint",
"Lundi de Pâques",
"Journée nationale des patriotes",
"Saint-Jean-Baptiste / Fête nationale du Québec",
"Fête du Canada",
"Fête du travail",
"Journée nationale de la vérité et de la réconciliation",
"Action de grâce",
"Noël",
]

View File

@ -7,6 +7,7 @@ import type { PayPeriodOverviewResponse, TimesheetApprovalOverview } from "src/m
import type { PayPeriod } from 'src/modules/shared/models/pay-period.models'; import type { PayPeriod } from 'src/modules/shared/models/pay-period.models';
import type { Timesheet } from 'src/modules/timesheets/models/timesheet.models'; import type { Timesheet } from 'src/modules/timesheets/models/timesheet.models';
import type { TimesheetApprovalCSVReportFilters } from 'src/modules/timesheet-approval/models/timesheet-approval-csv-report.models'; import type { TimesheetApprovalCSVReportFilters } from 'src/modules/timesheet-approval/models/timesheet-approval-csv-report.models';
import { type FederalHoliday, TARGO_HOLIDAY_NAMES_FR } from 'src/modules/timesheets/models/federal-holidays.models';
export const useTimesheetStore = defineStore('timesheet', () => { export const useTimesheetStore = defineStore('timesheet', () => {
@ -26,6 +27,16 @@ export const useTimesheetStore = defineStore('timesheet', () => {
const current_pay_period_overview = ref<TimesheetApprovalOverview>(); const current_pay_period_overview = ref<TimesheetApprovalOverview>();
const pay_period_report = ref(); const pay_period_report = ref();
const federal_holidays = ref<FederalHoliday[]>([]);
const getCurrentFederalHolidays = async(): Promise<boolean> => {
const all_federal_holidays = await timesheetService.getAllFederalHolidays();
if (!all_federal_holidays) return false;
federal_holidays.value = all_federal_holidays.filter(holiday => TARGO_HOLIDAY_NAMES_FR.includes(holiday.nameFr));
return true;
};
const getNextOrPreviousPayPeriod = (direction: number) => { const getNextOrPreviousPayPeriod = (direction: number) => {
if (!pay_period.value) return; if (!pay_period.value) return;
@ -178,6 +189,8 @@ export const useTimesheetStore = defineStore('timesheet', () => {
timesheets, timesheets,
all_current_shifts, all_current_shifts,
initial_timesheets, initial_timesheets,
federal_holidays,
getCurrentFederalHolidays,
getNextOrPreviousPayPeriod, getNextOrPreviousPayPeriod,
getPayPeriodByDateOrYearAndNumber, getPayPeriodByDateOrYearAndNumber,
getTimesheetOverviews, getTimesheetOverviews,