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 { 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 { useTimesheetStore } from 'src/stores/timesheet-store';
import { Shift } from 'src/modules/timesheets/models/shift.models';
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
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 HOLIDAYS: string[] = ['2026-01-01', '2025-11-26']
const { extractDate } = date;
const { locale } = useI18n();
const q = useQuasar();
const ui_store = useUiStore();
@ -59,11 +60,26 @@
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, () => {
if (currentDayComponent.value && q.platform.is.mobile) {
emit('onCurrentDayComponentFound', currentDayComponent.value[0])
}
})
});
</script>
<template>
@ -111,11 +127,11 @@
>
<!-- optional label indicating which holiday if today is a holiday -->
<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"
style="transform: translate(25px, -7px);"
>
New Year's of the nouvelle annee
{{ getHolidayName(day.date) }}
</span>
<!-- mobile version in portrait mode -->
@ -183,11 +199,11 @@
<div
v-else
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
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 -->
<ShiftListDateWidget
@ -200,7 +216,7 @@
:timesheet-id="timesheet.timesheet_id"
:week-day-index="day_index"
:day="day"
:holiday="HOLIDAYS.includes(day.date)"
:holiday="timesheet_store.federal_holidays.some(holiday => holiday.date === day.date)"
:approved="getDayApproval(day) || timesheet.is_approved"
class="col"
@delete-unsaved-shift="deleteUnsavedShift(timesheet_index, day_index)"
@ -215,7 +231,7 @@
color="white"
size="xl"
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
@ -224,7 +240,7 @@
square
icon="more_time"
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"
class="full-height"
: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 { TimesheetApprovalOverview } from "src/modules/timesheet-approval/models/timesheet-overview.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 = {
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> => {
const response = await api.get<{ success: boolean, data: PayPeriod, error?: string }>(`pay-periods/date/${date_string}`);
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.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' },
];
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 { Timesheet } from 'src/modules/timesheets/models/timesheet.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', () => {
@ -26,6 +27,16 @@ export const useTimesheetStore = defineStore('timesheet', () => {
const current_pay_period_overview = ref<TimesheetApprovalOverview>();
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) => {
if (!pay_period.value) return;
@ -178,6 +189,8 @@ export const useTimesheetStore = defineStore('timesheet', () => {
timesheets,
all_current_shifts,
initial_timesheets,
federal_holidays,
getCurrentFederalHolidays,
getNextOrPreviousPayPeriod,
getPayPeriodByDateOrYearAndNumber,
getTimesheetOverviews,