feat(approvals): add SSE to timesheet-approval to notify of timesheet changes

This commit is contained in:
Nic D 2026-01-21 10:21:01 -05:00
parent 2e2f2cd802
commit dccc2f4a10
7 changed files with 47 additions and 13 deletions

View File

@ -373,6 +373,7 @@ export default {
delete: "has deleted",
expense: "an expense",
shift: "a shift",
preset: "many shifts",
},
print_report: {
title: "Download options",

View File

@ -373,6 +373,7 @@ export default {
delete: "a supprimé",
expense: "une dépense",
shift: " un quart de travail",
preset: "plusieurs quarts de travail",
},
print_report: {
title: "options de téléchargement",

View File

@ -0,0 +1,5 @@
export interface PayPeriodEvent {
employee_email: string;
event_type: 'shift' | 'expense' | 'preset';
action: 'create' | 'update' | 'delete';
}

View File

@ -20,6 +20,6 @@ export const timesheetApprovalService = {
},
subscribeToPayPeriodObservable: (): EventSource => {
return new EventSource('pay-periods/subscribe')
return new EventSource('http://localhost:3000/pay-periods/subscribe');
},
};

View File

@ -9,7 +9,7 @@
import OverviewReport from 'src/modules/timesheet-approval/components/overview-report.vue';
import { date } from 'quasar';
import { computed, onMounted, ref } from 'vue';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { useTimesheetApprovalApi } from 'src/modules/timesheet-approval/composables/use-timesheet-approval-api';
import { useTimesheetStore } from 'src/stores/timesheet-store';
@ -28,7 +28,12 @@
onMounted(async () => {
await timesheet_approval_api.getTimesheetOverviewsByDate(date.formatDate(new Date(), 'YYYY-MM-DD'));
timesheet_store.subscribeToPayPeriodObservable();
});
onUnmounted(() => {
timesheet_store.unsubscribeToPayPeriodObservable();
})
</script>
<template>

View File

@ -2,23 +2,24 @@
setup
lang="ts"
>
import TimesheetWrapper from 'src/modules/timesheets/components/timesheet-wrapper.vue';
import { useAuthStore } from 'src/stores/auth-store';
const { user } = useAuthStore();
const auth_store = useAuthStore();
</script>
<template>
<q-page
class="column bg-secondary items-center"
>
<q-page class="column bg-secondary items-center">
<div
class="col column fit"
:style="$q.platform.is.mobile && ($q.screen.width < $q.screen.height) ? '' : 'width: 90vw'"
>
<TimesheetWrapper :employee-email="user?.email ?? ''" class="col"/>
<TimesheetWrapper
:employee-email="auth_store.user?.email ?? ''"
class="col"
/>
</div>
</q-page>
</template>

View File

@ -6,11 +6,15 @@ import { timesheetService } from 'src/modules/timesheets/services/timesheet-serv
import type { PayPeriodOverviewResponse, TimesheetApprovalOverview } from "src/modules/timesheet-approval/models/timesheet-overview.models";
import type { PayPeriod } from 'src/modules/shared/models/pay-period.models';
import type { Timesheet } from 'src/modules/timesheets/models/timesheet.models';
import type { PayPeriodEvent } from 'src/modules/timesheet-approval/models/pay-period-event.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';
import { Notify } from 'quasar';
import { useI18n } from 'vue-i18n';
export const useTimesheetStore = defineStore('timesheet', () => {
const { t } = useI18n();
const is_loading = ref<boolean>(false);
const pay_period = ref<PayPeriod>();
const timesheets = ref<Timesheet[]>([]);
@ -30,7 +34,7 @@ export const useTimesheetStore = defineStore('timesheet', () => {
const federal_holidays = ref<FederalHoliday[]>([]);
const getCurrentFederalHolidays = async(): Promise<boolean> => {
const getCurrentFederalHolidays = async (): Promise<boolean> => {
const all_federal_holidays = await timesheetService.getAllFederalHolidays();
const all_federal_holidays_2025 = await timesheetService.getAllFederalHolidays(2025);
if (!all_federal_holidays || !all_federal_holidays_2025) return false;
@ -38,7 +42,7 @@ export const useTimesheetStore = defineStore('timesheet', () => {
federal_holidays.value = all_federal_holidays.filter(holiday => TARGO_HOLIDAY_NAMES_FR.includes(holiday.nameFr));
const targo_fed_holidays_2025 = all_federal_holidays_2025.filter(holiday => TARGO_HOLIDAY_NAMES_FR.includes(holiday.nameFr));
targo_fed_holidays_2025.forEach(holiday => federal_holidays.value.push(holiday));
return true;
};
@ -138,7 +142,7 @@ export const useTimesheetStore = defineStore('timesheet', () => {
const toggleTimesheetsApprovalByEmployeeEmail = async (email: string, approval_status: boolean): Promise<boolean> => {
try {
const timesheet_ids = timesheets.value.map(timesheet => timesheet.timesheet_id);
// Backend returns the amount of shifts and expenses successfully updated, could be useful for error handling???
// const shift_expense_count = timesheets.value.reduce((timesheets_sum, timesheet) => {
// return timesheets_sum + timesheet.days.reduce((day_sum, day) => {
@ -183,13 +187,29 @@ export const useTimesheetStore = defineStore('timesheet', () => {
const subscribeToPayPeriodObservable = () => {
if (pay_period_observer.value === undefined) {
console.log('subscribing to observable');
pay_period_observer.value = timesheetApprovalService.subscribeToPayPeriodObservable();
pay_period_observer.value.onmessage = () => {
console.log('subscription success: ', pay_period_observer.value);
pay_period_observer.value.onmessage = (event: MessageEvent<string>) => {
console.log('event received: ');
const pay_period_event: PayPeriodEvent = JSON.parse(event.data);
const overview = pay_period_overviews.value.find(overview => overview.email === pay_period_event.employee_email);
const employee_name = overview?.employee_first_name + ' ' + overview?.employee_last_name;
Notify.create({
message: `${employee_name} ${t('timesheet_approvals.event.' + pay_period_event.action)} ${t('timesheet_approvals.event.' + pay_period_event.event_type)}`
})
}
}
}
const unsubscribeToPayPeriodObservable = () => {
if (pay_period_observer.value) {
pay_period_observer.value.close();
pay_period_observer.value = undefined;
}
}
return {
is_loading,
is_report_dialog_open,
@ -214,5 +234,6 @@ export const useTimesheetStore = defineStore('timesheet', () => {
openReportDialog,
closeReportDialog,
subscribeToPayPeriodObservable,
unsubscribeToPayPeriodObservable,
};
});