Merge branch 'dev/nicolas/staging-prep' of https://git.targo.ca/Targo/targo_frontend into dev/nicolas/staging-prep

This commit is contained in:
Nic D 2026-01-27 14:53:34 -05:00
commit e7b154e041
15 changed files with 133 additions and 103 deletions

View File

@ -224,6 +224,7 @@ export default {
error: { error: {
no_data_found: "no data found", no_data_found: "no data found",
no_search_results: "no results matching search", no_search_results: "no results matching search",
generic_error: "An error occured",
}, },
label: { label: {
search: "search", search: "search",
@ -284,6 +285,7 @@ export default {
apply_preset: "auto-fill", apply_preset: "auto-fill",
apply_preset_day: "Apply schedule to day", apply_preset_day: "Apply schedule to day",
apply_preset_week: "Apply schedule to week", apply_preset_week: "Apply schedule to week",
save_successful: "timesheets saved",
nav_button: { nav_button: {
calendar_date_picker: "Calendar", calendar_date_picker: "Calendar",
current_week: "This week", current_week: "This week",
@ -359,6 +361,7 @@ export default {
SHIFT_TYPE_REQUIRED: "Shift type required", SHIFT_TYPE_REQUIRED: "Shift type required",
TIMESHEET_NOT_FOUND: "No timesheet found with provided data", TIMESHEET_NOT_FOUND: "No timesheet found with provided data",
UPDATE_ERROR: "Error while updating data", UPDATE_ERROR: "Error while updating data",
ERROR_SAVING_SHIFTS: "Timesheet changes were not saved",
}, },
}, },

View File

@ -224,6 +224,7 @@ export default {
error: { error: {
no_data_found: 'aucune donnée à afficher', no_data_found: 'aucune donnée à afficher',
no_search_results: 'aucun résultat ne correspond à la recherche', no_search_results: 'aucun résultat ne correspond à la recherche',
generic_error: "Une erreur est survenue",
}, },
label: { label: {
search: 'recherche', search: 'recherche',
@ -284,6 +285,7 @@ export default {
apply_preset: "auto-remplir", apply_preset: "auto-remplir",
apply_preset_day: "Appliquer horaire pour la journée", apply_preset_day: "Appliquer horaire pour la journée",
apply_preset_week: "Appliquer horaire pour la semaine", apply_preset_week: "Appliquer horaire pour la semaine",
save_successful: "feuilles de temps enregistrées",
nav_button: { nav_button: {
calendar_date_picker: "Calendrier", calendar_date_picker: "Calendrier",
current_week: "Semaine actuelle", current_week: "Semaine actuelle",
@ -359,6 +361,7 @@ export default {
SHIFT_TYPE_REQUIRED: "Type requis", SHIFT_TYPE_REQUIRED: "Type requis",
TIMESHEET_NOT_FOUND: "Aucune feuille de temps ne correspond au détails fournis", TIMESHEET_NOT_FOUND: "Aucune feuille de temps ne correspond au détails fournis",
UPDATE_ERROR: "Une erreur est survenu lors de la mise à jour", UPDATE_ERROR: "Une erreur est survenu lors de la mise à jour",
ERROR_SAVING_SHIFTS: "les changements aux feuilles de temps n'ont pas été enregistrés",
}, },
}, },

View File

@ -2,12 +2,12 @@
setup setup
lang="ts" lang="ts"
> >
import { onMounted, ref } from 'vue'; import { computed, ref } from 'vue';
import { Bar } from 'vue-chartjs'; import { Bar } from 'vue-chartjs';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar, colors } from 'quasar'; import { useQuasar, colors } from 'quasar';
import { useTimesheetStore } from 'src/stores/timesheet-store'; import { useTimesheetStore } from 'src/stores/timesheet-store';
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, TimeScale, type ChartDataset } from 'chart.js'; import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, TimeScale } from 'chart.js';
const { t } = useI18n(); const { t } = useI18n();
const $q = useQuasar(); const $q = useQuasar();
@ -19,27 +19,21 @@
const timesheet_store = useTimesheetStore(); const timesheet_store = useTimesheetStore();
const all_days = timesheet_store.timesheets.flatMap(week => week.days.flatMap(day => day.daily_expenses)); const all_days = computed(() => timesheet_store.timesheets.flatMap(week => week.days.flatMap(day => day.daily_expenses)));
const expenses_labels = ref<string[]>(timesheet_store.timesheets.flatMap(week => week.days.map(day => day.date.slice(-5,)))); const expenses_labels = ref<string[]>(timesheet_store.timesheets.flatMap(week => week.days.map(day => day.date.slice(-5,))));
const expenses_dataset = ref<ChartDataset<'bar'>[]>([]); const expenses_dataset = computed(() => [
{
onMounted(() => { label: t('timesheet_approvals.table.expenses'),
setTimeout(() => { data: all_days.value.map(day => (day.expenses + day.on_call + day.per_diem)),
expenses_dataset.value = [ backgroundColor: colors.getPaletteColor('accent'),
{ },
label: t('timesheet_approvals.table.expenses'), {
data: all_days.map(day => (day.expenses + day.on_call + day.per_diem)), label: t('timesheet_approvals.table.mileage'),
backgroundColor: colors.getPaletteColor('accent'), data: all_days.value.map(day => day.mileage),
}, backgroundColor: colors.getPaletteColor('info'),
{ }
label: t('timesheet_approvals.table.mileage'), ]);
data: all_days.map(day => day.mileage),
backgroundColor: colors.getPaletteColor('info'),
}
]
}, 100)
});
</script> </script>
<template> <template>

View File

@ -2,17 +2,17 @@
setup setup
lang="ts" lang="ts"
> >
import { ref, onMounted } from 'vue'; import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { colors, date, useQuasar } from 'quasar'; import { colors, date, useQuasar } from 'quasar';
import { Bar } from 'vue-chartjs'; import { Bar } from 'vue-chartjs';
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, TimeScale, type ChartDataset } from 'chart.js'; import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, TimeScale } from 'chart.js';
import { useTimesheetStore } from 'src/stores/timesheet-store'; import { useTimesheetStore } from 'src/stores/timesheet-store';
import type { TotalHours } from 'src/modules/timesheets/models/timesheet.models'; import type { TotalHours } from 'src/modules/timesheets/models/timesheet.models';
import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-utils'; import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-utils';
interface ChartConfigHoursWorked { interface ChartConfigHoursWorked {
key: keyof Pick<TotalHours, 'regular' | 'evening' | 'emergency' | 'overtime'| 'vacation' | 'holiday'>; key: keyof Pick<TotalHours, 'regular' | 'evening' | 'emergency' | 'overtime' | 'vacation' | 'holiday'>;
label: string; label: string;
color: string; color: string;
} }
@ -27,7 +27,7 @@ import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-uti
const timesheet_store = useTimesheetStore(); const timesheet_store = useTimesheetStore();
const all_days = timesheet_store.timesheets.flatMap(week => week.days); const all_days = computed(() => timesheet_store.timesheets.flatMap(week => week.days));
const datasetConfig: ChartConfigHoursWorked[] = [ const datasetConfig: ChartConfigHoursWorked[] = [
{ {
@ -62,18 +62,12 @@ import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-uti
}, },
]; ];
const hours_worked_labels = ref<string[]>(all_days.map(day => day.date.slice(-5,))); const hours_worked_labels = ref<string[]>(all_days.value.map(day => day.date.slice(-5,)));
const hours_worked_dataset = ref<ChartDataset<'bar'>[]>([]); const hours_worked_datasets = computed(() => datasetConfig.map(cfg => ({
label: cfg.label,
onMounted(() => { data: all_days.value.map(day => day.daily_hours[cfg.key]),
setTimeout(() => { backgroundColor: cfg.color,
hours_worked_dataset.value = datasetConfig.map(cfg => ({ })))
label: cfg.label,
data: all_days.map(day => day.daily_hours[cfg.key]),
backgroundColor: cfg.color,
}));
}, 100);
});
</script> </script>
<template> <template>
@ -83,7 +77,7 @@ import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-uti
> >
<Bar <Bar
:data="{ :data="{
datasets: hours_worked_dataset, datasets: hours_worked_datasets,
labels: hours_worked_labels, labels: hours_worked_labels,
}" }"
:options="({ :options="({
@ -92,7 +86,7 @@ import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-uti
tooltip: { tooltip: {
callbacks: { callbacks: {
title: function (context) { title: function (context) {
return $d(date.extractDate(`2025-${context[0]!.label}`, 'YYYY-MM-DD'), {month: 'long', day: 'numeric'}); return $d(date.extractDate(`2025-${context[0]!.label}`, 'YYYY-MM-DD'), { month: 'long', day: 'numeric' });
}, },
label: function (context) { label: function (context) {
return getHoursMinutesStringFromHoursFloat(context.parsed.y); return getHoursMinutesStringFromHoursFloat(context.parsed.y);

View File

@ -2,15 +2,15 @@
setup setup
lang="ts" lang="ts"
> >
/* eslint-disable */ import { computed } from 'vue';
import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { colors } from 'quasar'; import { colors } from 'quasar';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { Doughnut } from 'vue-chartjs'; import { Doughnut } from 'vue-chartjs';
import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale, LinearScale, type ChartDataset } from 'chart.js'; import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale, LinearScale } from 'chart.js';
import { useTimesheetStore } from 'src/stores/timesheet-store'; import { useTimesheetStore } from 'src/stores/timesheet-store';
import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-utils'; import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-utils';
import type { Timesheet, TotalHours } from 'src/modules/timesheets/models/timesheet.models';
const $q = useQuasar(); const $q = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
@ -22,33 +22,27 @@
const timesheet_store = useTimesheetStore(); const timesheet_store = useTimesheetStore();
const shift_type_labels = ref<string[]>([ const TRACKABLE_SHIFT_TYPES: (keyof TotalHours)[] = ['regular', 'evening', 'emergency', 'overtime', 'holiday', 'vacation']
const SHIFT_TYPE_LABELS = [
t('shared.shift_type.regular'), t('shared.shift_type.regular'),
t('shared.shift_type.evening'), t('shared.shift_type.evening'),
t('shared.shift_type.emergency'), t('shared.shift_type.emergency'),
t('shared.shift_type.overtime'), t('shared.shift_type.overtime'),
]); t('shared.shift_type.holiday'),
t('shared.shift_type.vacation'),
];
const shift_type_totals = ref<ChartDataset<'doughnut'>[]>([]); const shift_type_data = computed(() => {
const initial_totals = new Array(TRACKABLE_SHIFT_TYPES.length).fill(0);
onMounted(() => { return timesheet_store.timesheets.reduce((accumulator: number[], timesheet: Timesheet) => {
setTimeout(() => { TRACKABLE_SHIFT_TYPES.forEach( (trackable_shift_type, index) => {
shift_type_totals.value = [{ accumulator[index]! += timesheet.weekly_hours[trackable_shift_type] ?? 0;
data: [ });
timesheet_store.current_pay_period_overview!.regular_hours,
timesheet_store.current_pay_period_overview!.other_hours.evening_hours,
timesheet_store.current_pay_period_overview!.other_hours.emergency_hours,
timesheet_store.current_pay_period_overview!.other_hours.overtime_hours,
],
backgroundColor: [
colors.getPaletteColor('accent'), // Regular
colors.getPaletteColor('green-10'), // Evening
colors.getPaletteColor('warning'), // Emergency
colors.getPaletteColor('negative'), // Overtime
]
}]
}, 100); return accumulator;
}, initial_totals);
}); });
</script> </script>
@ -59,8 +53,16 @@
> >
<Doughnut <Doughnut
:data="{ :data="{
labels: shift_type_labels, labels: SHIFT_TYPE_LABELS,
datasets: shift_type_totals, datasets: [{
data: shift_type_data,
backgroundColor: [
colors.getPaletteColor('accent'), // Regular
colors.getPaletteColor('green-10'), // Evening
colors.getPaletteColor('warning'), // Emergency
colors.getPaletteColor('negative'), // Overtime
]
}],
}" }"
:options="({ :options="({
plugins: { plugins: {

View File

@ -25,6 +25,7 @@
backdrop-filter="blur(6px)" backdrop-filter="blur(6px)"
@show="is_dialog_open = true" @show="is_dialog_open = true"
@hide="is_dialog_open = false" @hide="is_dialog_open = false"
@before-hide="timesheet_store.getTimesheetOverviews"
> >
<div <div
class="column bg-secondary hide-scrollbar shadow-12 rounded-15 q-pa-sm no-wrap" class="column bg-secondary hide-scrollbar shadow-12 rounded-15 q-pa-sm no-wrap"

View File

@ -19,6 +19,7 @@
value: ExpenseType; value: ExpenseType;
icon: string; icon: string;
} }
const expense = defineModel<Expense>({ default: new Expense(new Date().toISOString().slice(0, 10)) })
const COMMENT_MAX_LENGTH = 280; const COMMENT_MAX_LENGTH = 280;
@ -30,6 +31,8 @@
const files = defineModel<File[] | null>('files'); const files = defineModel<File[] | null>('files');
const is_navigator_open = ref(false); const is_navigator_open = ref(false);
const rules = useExpenseRules(t); const rules = useExpenseRules(t);
const expense_options: ExpenseOption[] = [ const expense_options: ExpenseOption[] = [
{ label: t('timesheet.expense.types.PER_DIEM'), value: 'PER_DIEM', icon: getExpenseIcon('PER_DIEM') }, { label: t('timesheet.expense.types.PER_DIEM'), value: 'PER_DIEM', icon: getExpenseIcon('PER_DIEM') },
{ label: t('timesheet.expense.types.EXPENSES'), value: 'EXPENSES', icon: getExpenseIcon('EXPENSES') }, { label: t('timesheet.expense.types.EXPENSES'), value: 'EXPENSES', icon: getExpenseIcon('EXPENSES') },
@ -67,13 +70,16 @@
}; };
onMounted(() => { onMounted(() => {
expense_selected.value = expense_options.find(expense => expense.value === expenses_store.current_expense.type); if (expense.value)
expense_selected.value = expense_options.find(expense_option => expense_option.value === expense.value.type);
else
expense_selected.value = expense_options[1];
}) })
</script> </script>
<template> <template>
<q-form <q-form
v-if="!timesheet_store.timesheets?.every(timesheet => timesheet.is_approved)" v-if="!expenses_store.current_expense.is_approved"
flat flat
@submit.prevent="requestExpenseCreationOrUpdate" @submit.prevent="requestExpenseCreationOrUpdate"
class="full-width q-mt-md q-px-md" class="full-width q-mt-md q-px-md"
@ -292,7 +298,7 @@
</template> </template>
<style scoped> <style scoped>
:deep(.q-field--standout.q-field--readonly .q-field__control::before) { :deep(.q-field--standout.q-field--readonly .q-field__control::before) {
border: transparent; border: transparent;
} }
</style> </style>

View File

@ -212,6 +212,6 @@
</div> </div>
</template> </template>
<ExpenseDialogForm /> <ExpenseDialogForm v-model="expense" />
</q-expansion-item> </q-expansion-item>
</template> </template>

View File

@ -2,7 +2,6 @@
setup setup
lang="ts" lang="ts"
> >
/* eslint-disable */
import ShiftList from 'src/modules/timesheets/components/shift-list.vue'; import ShiftList from 'src/modules/timesheets/components/shift-list.vue';
import ShiftListScrollable from 'src/modules/timesheets/components/shift-list-scrollable.vue'; import ShiftListScrollable from 'src/modules/timesheets/components/shift-list-scrollable.vue';
import LoadingOverlay from 'src/modules/shared/components/loading-overlay.vue'; import LoadingOverlay from 'src/modules/shared/components/loading-overlay.vue';
@ -13,7 +12,8 @@
import ShiftListWeeklyOverview from 'src/modules/timesheets/components/shift-list-weekly-overview.vue'; import ShiftListWeeklyOverview from 'src/modules/timesheets/components/shift-list-weekly-overview.vue';
import ShiftListWeeklyOverviewMobile from 'src/modules/timesheets/components/mobile/shift-list-weekly-overview-mobile.vue'; import ShiftListWeeklyOverviewMobile from 'src/modules/timesheets/components/mobile/shift-list-weekly-overview-mobile.vue';
import { date } from 'quasar'; import { date, Notify } from 'quasar';
import { useI18n } from 'vue-i18n';
import { computed, onMounted, provide } from 'vue'; import { computed, onMounted, provide } from 'vue';
import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api'; import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api'; import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
@ -27,6 +27,7 @@
employeeEmail?: string | undefined; employeeEmail?: string | undefined;
}>(); }>();
const { t } = useI18n();
const expenses_store = useExpensesStore(); const expenses_store = useExpensesStore();
const timesheet_store = useTimesheetStore(); const timesheet_store = useTimesheetStore();
const timesheet_api = useTimesheetApi(); const timesheet_api = useTimesheetApi();
@ -50,13 +51,25 @@
sum + timesheet.weekly_expenses.expenses sum + timesheet.weekly_expenses.expenses
+ timesheet.weekly_expenses.on_call + timesheet.weekly_expenses.on_call
+ timesheet.weekly_expenses.per_diem, + timesheet.weekly_expenses.per_diem,
0) //initial value 0 //initial value
); ));
// =================== methods ========================== // =================== methods ==========================
provide('employeeEmail', employeeEmail); provide('employeeEmail', employeeEmail);
const onClickSaveTimesheets = async () => {
if (mode === 'normal') {
await shift_api.saveShiftChanges();
Notify.create({
message: t('timesheet.save_successful'),
color: 'accent',
});
} else {
await shift_api.saveShiftChanges(timesheet_store.current_pay_period_overview?.email);
}
}
onMounted(async () => { onMounted(async () => {
if (mode === 'normal') if (mode === 'normal')
await timesheet_api.getTimesheetsByDate(date.formatDate(new Date(), 'YYYY-MM-DD')); await timesheet_api.getTimesheetsByDate(date.formatDate(new Date(), 'YYYY-MM-DD'));
@ -106,7 +119,10 @@
v-if="!$q.platform.is.mobile" v-if="!$q.platform.is.mobile"
class="col-xs-6 col-md-4 col-xl-3 q-pa-md" class="col-xs-6 col-md-4 col-xl-3 q-pa-md"
> >
<ShiftListWeeklyOverview mode="off-hours" :timesheet-mode="mode" /> <ShiftListWeeklyOverview
mode="off-hours"
:timesheet-mode="mode"
/>
</div> </div>
</div> </div>
@ -125,7 +141,10 @@
/> />
<!-- mobile expenses button --> <!-- mobile expenses button -->
<div v-if="($q.platform.is.mobile && ($q.screen.width < $q.screen.height))" class="col q-pl-lg"> <div
v-if="($q.platform.is.mobile && ($q.screen.width < $q.screen.height))"
class="col q-pl-lg"
>
<q-btn <q-btn
push push
rounded rounded
@ -159,7 +178,7 @@
icon="upload" icon="upload"
:label="$t('shared.label.save')" :label="$t('shared.label.save')"
:class="$q.platform.is.mobile && ($q.screen.width < $q.screen.height) ? 'full-width' : 'q-ml-md'" :class="$q.platform.is.mobile && ($q.screen.width < $q.screen.height) ? 'full-width' : 'q-ml-md'"
@click="shift_api.saveShiftChanges" @click="onClickSaveTimesheets"
/> />
</div> </div>
@ -213,7 +232,7 @@
:label="$t('shared.label.save')" :label="$t('shared.label.save')"
class="col-auto absolute-bottom shadow-up-10" class="col-auto absolute-bottom shadow-up-10"
style="height: 50px;" style="height: 50px;"
@click="shift_api.saveShiftChanges" @click="onClickSaveTimesheets"
/> />
<ExpenseDialog <ExpenseDialog

View File

@ -10,16 +10,17 @@ export const useExpensesApi = () => {
const upsertExpense = async (expense: Expense, employee_email?: string): Promise<void> => { const upsertExpense = async (expense: Expense, employee_email?: string): Promise<void> => {
const success = await expenses_store.upsertExpense(expense, employee_email); const success = await expenses_store.upsertExpense(expense, employee_email);
if (success) { if (success) {
expenses_store.current_expense = new Expense(date.formatDate( new Date(), 'YYYY-MM-DD')); expenses_store.current_expense = new Expense(date.formatDate( new Date(), 'YYYY-MM-DD'));
timesheet_store.getTimesheetsByOptionalEmployeeEmail(); timesheet_store.getTimesheetsByOptionalEmployeeEmail(employee_email);
} }
}; };
const deleteExpenseById = async (expense_id: number): Promise<void> => { const deleteExpenseById = async (expense_id: number, employee_email?: string): Promise<void> => {
const success = await expenses_store.deleteExpenseById(expense_id); const success = await expenses_store.deleteExpenseById(expense_id);
if (success) { if (success) {
timesheet_store.getTimesheetsByOptionalEmployeeEmail(); timesheet_store.getTimesheetsByOptionalEmployeeEmail(employee_email);
} }
}; };

View File

@ -1,31 +1,29 @@
import { useAuthStore } from "src/stores/auth-store";
import { useShiftStore } from "src/stores/shift-store"; import { useShiftStore } from "src/stores/shift-store";
import { useTimesheetStore } from "src/stores/timesheet-store"; import { useTimesheetStore } from "src/stores/timesheet-store";
export const useShiftApi = () => { export const useShiftApi = () => {
const timesheet_store = useTimesheetStore(); const timesheet_store = useTimesheetStore();
const shift_store = useShiftStore(); const shift_store = useShiftStore();
const auth_store = useAuthStore();
const deleteShiftById = async (shift_id: number, employee_email?: string) => { const deleteShiftById = async (shift_id: number, employee_email?: string) => {
timesheet_store.is_loading = true; timesheet_store.is_loading = true;
const success = await shift_store.deleteShiftById(shift_id, employee_email); const success = await shift_store.deleteShiftById(shift_id, employee_email);
if (success) { if (success) {
await timesheet_store.getTimesheetsByOptionalEmployeeEmail(auth_store.user?.email ?? ''); await timesheet_store.getTimesheetsByOptionalEmployeeEmail(employee_email);
} }
timesheet_store.is_loading = false; timesheet_store.is_loading = false;
}; };
const saveShiftChanges = async () => { const saveShiftChanges = async (employee_email?: string) => {
timesheet_store.is_loading = true; timesheet_store.is_loading = true;
const update_success = await shift_store.updateShifts(); const update_success = await shift_store.updateShifts(employee_email);
const create_success = await shift_store.createNewShifts(); const create_success = await shift_store.createNewShifts(employee_email);
if (create_success || update_success){ if (create_success || update_success){
await timesheet_store.getTimesheetsByOptionalEmployeeEmail(auth_store.user?.email ?? ''); await timesheet_store.getTimesheetsByOptionalEmployeeEmail(employee_email);
} }
timesheet_store.is_loading = false; timesheet_store.is_loading = false;

View File

@ -15,7 +15,7 @@ export const ShiftService = {
createNewShifts: async (new_shifts: Shift[], employee_email?: string):Promise<BackendResponse<Shift>> => { createNewShifts: async (new_shifts: Shift[], employee_email?: string):Promise<BackendResponse<Shift>> => {
if (employee_email) { if (employee_email) {
const response = await api.post(`/shift/create/${employee_email}`); const response = await api.post(`/shift/create/${employee_email}`, new_shifts);
return response.data; return response.data;
} }
@ -25,7 +25,7 @@ export const ShiftService = {
updateShifts: async (existing_shifts: Shift[], employee_email?: string):Promise<BackendResponse<Shift>> => { updateShifts: async (existing_shifts: Shift[], employee_email?: string):Promise<BackendResponse<Shift>> => {
if (employee_email) { if (employee_email) {
const response = await api.patch(`/shift/update/${employee_email}`); const response = await api.patch(`/shift/update/${employee_email}`, existing_shifts);
return response.data; return response.data;
} }

View File

@ -31,6 +31,10 @@ export const useAuthStore = defineStore('auth', () => {
const logout = () => { const logout = () => {
user.value = undefined; user.value = undefined;
AuthService.logout();
const logout_popup = window.open('https://auth.targo.ca/application/o/montargo/end-session/', 'logoutPopup', 'width=200,height=200');
setInterval(() => logout_popup?.close(), 2000);
}; };
const handleAuthMessage = async (event: MessageEvent) => { const handleAuthMessage = async (event: MessageEvent) => {

View File

@ -54,7 +54,6 @@ export const useShiftStore = defineStore('shift_store', () => {
} }
} }
Notify.create('No shifts to update')
return false; return false;
} catch (error) { } catch (error) {
Notify.create('Error updating shifts'); Notify.create('Error updating shifts');

View File

@ -21,7 +21,13 @@ export const getMinutes = (hours: number) => {
} }
export const getHoursMinutesStringFromHoursFloat = (hours: number): string => { export const getHoursMinutesStringFromHoursFloat = (hours: number): string => {
const flat_hours = Math.floor(hours); let flat_hours = Math.floor(hours);
const minutes = Math.round((hours - flat_hours) * 60); let minutes = Math.round((hours - flat_hours) * 60);
if (minutes === 60) {
flat_hours += 1;
minutes = 0;
}
return `${flat_hours}h${minutes > 1 ? ' ' + minutes : ''}` return `${flat_hours}h${minutes > 1 ? ' ' + minutes : ''}`
} }