fix(all): many changes, see commit details. Add weekly overview data to timesheets

This commit is contained in:
Nicolas Drolet 2026-01-06 09:12:49 -05:00
parent f738a5872a
commit 1e16c8334b
16 changed files with 155 additions and 42 deletions

BIN
src/assets/info-pannes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -245,6 +245,9 @@ export default {
page_header: "Timesheet",
week: "week",
total_hours: "total hours: ",
total_expenses: "total expenses: ",
vacation_available: "vacation time available: ",
sick_available: "sick time available: ",
current_shifts: "shifts worked",
apply_preset: "auto-fill",
apply_preset_day: "Apply schedule to day",

View File

@ -246,6 +246,9 @@ export default {
page_header: "Carte de temps",
week: "semaine",
total_hours: "heures totales: ",
total_expenses: "dépenses totales: ",
vacation_available: "vacances disponibles: ",
sick_available: "congés maladie disponible: ",
current_shifts: "quarts entrées",
apply_preset: "auto-remplir",
apply_preset_day: "Appliquer horaire pour la journée",

View File

@ -16,7 +16,7 @@
<template>
<q-card
class="shortcut-card cursor-pointer"
class="shortcut-card cursor-pointer shadow-12"
@click="onClickExternalShortcut"
>
<q-img

View File

@ -22,7 +22,7 @@
const getPresetOptions = (): { label: string, value: number }[] => {
const options = schedule_preset_store.schedule_presets.map(preset => { return { label: preset.name, value: preset.id } });
options.push({ label: '', value: -1 });
options.push({ label: 'Aucun', value: -1 });
return options;
};

View File

@ -58,21 +58,21 @@
name="form"
icon="las la-id-card"
:label="$q.screen.lt.sm ? '' : $t('employee_management.details_label')"
class="rounded-25 q-ma-xs"
class="rounded-25 q-ma-xs bg-dark"
style="border: 2px solid var(--q-accent);"
/>
<q-tab
name="access"
icon="las la-key"
:label="$q.screen.lt.sm ? '' : $t('employee_management.access_label')"
class="rounded-25 q-ma-xs"
class="rounded-25 q-ma-xs bg-dark"
style="border: 2px solid var(--q-accent);"
/>
<q-tab
name="schedule"
icon="calendar_month"
:label="$q.screen.lt.sm ? '' : $t('employee_management.schedule_label')"
class="rounded-25 q-ma-xs"
class="rounded-25 q-ma-xs bg-dark"
style="border: 2px solid var(--q-accent);"
/>
</q-tabs>

View File

@ -184,7 +184,7 @@
:row="props.row"
:index="props.rowIndex"
:is-management="is_management"
@on-profile-click="is_management ? employee_store.openAddModifyDialog : ''"
@on-profile-click="email => is_management ? employee_store.openAddModifyDialog(email) : ''"
/>
</transition>
</template>

View File

@ -72,7 +72,7 @@
v-model="shift.is_remote"
dense
keep-color
size="3em"
size="2.5em"
color="accent"
icon="las la-building"
checked-icon="las la-laptop"

View File

@ -37,7 +37,7 @@
<!-- employee pay period details using chart -->
<div
v-if="is_dialog_open && !$q.platform.is.mobile"
class="col-4 q-px-md no-wrap"
class="col-auto q-px-md no-wrap"
:class="$q.platform.is.mobile ? 'column' : 'row'"
>
<DetailsDialogChartHoursWorked class="col" />
@ -50,8 +50,8 @@
</div>
<!-- list of shifts -->
<div class="col column no-wrap">
<TimesheetWrapper mode="approval" class="col"/>
<div class="col-auto column no-wrap">
<TimesheetWrapper mode="approval" class="col-auto"/>
</div>
</div>
</q-dialog>

View File

@ -151,7 +151,7 @@
</template>
<template #option="scope">
<q-item>
<q-item clickable v-bind="scope.itemProps">
<q-item-section avatar>
<q-icon
:name="scope.opt.icon"

View File

@ -0,0 +1,71 @@
<script
setup
lang="ts"
>
import { useAuthStore } from 'src/stores/auth-store';
import { getHoursMinutesStringFromHoursFloat } from 'src/utils/date-and-time-utils';
const { mode = 'totals', totalHours = 0, vacationHours = 0, sickHours = 0, totalExpenses = 0 } = defineProps<{
mode: 'total-hours' | 'off-hours';
totalHours?: number;
vacationHours?: number;
sickHours?: number;
totalExpenses?: number;
}>();
const auth_store = useAuthStore();
const is_management = auth_store.user?.user_module_access.includes('timesheets_approval');
</script>
<template>
<div
class="column full-width shadow-4 rounded-5 q-pa-sm"
style="border: 1px solid var(--q-accent);"
>
<div
v-if="mode === 'total-hours'"
class="col column full-width"
>
<div class="col row full-width">
<span class="col-auto text-uppercase text-caption text-bold text-accent">
{{ $t('timesheet.total_hours') }}
</span>
<span class="col text-right">{{ getHoursMinutesStringFromHoursFloat(totalHours) }}</span>
</div>
<div class="col row full-width">
<span class="col-auto text-uppercase text-caption text-bold text-accent">
{{ $t('timesheet.total_expenses') }}
</span>
<span class="col text-right">{{ totalExpenses }}$</span>
</div>
</div>
<div
v-else
class="col column full-width"
>
<div class="col row full-width">
<span class="col-auto text-uppercase text-caption text-bold text-accent">
{{ $t('timesheet.vacation_available') }}
</span>
<span class="col text-right">{{ Math.floor(vacationHours / 8) }}</span>
</div>
<div
v-if="is_management"
class="col row full-width"
>
<span class="col-auto text-uppercase text-caption text-bold text-accent">
{{ $t('timesheet.sick_available') }}
</span>
<span class="col text-right">{{ Math.floor(sickHours / 8) }}</span>
</div>
</div>
</div>
</template>

View File

@ -11,7 +11,7 @@
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 { QScrollArea } from 'quasar';
import type { QScrollArea, TouchSwipeValue } from 'quasar';
import type { TimesheetDay } from 'src/modules/timesheets/models/timesheet.models';
const CURRENT_DATE_STRING = new Date().toISOString().slice(0, 10);
@ -34,7 +34,9 @@
const currentDayComponent = ref<HTMLElement[] | null>(null);
const currentDayComponentWatcher = ref(currentDayComponent);
const scroll_y = computed(() => timesheet_page.value?.getScrollPosition().top ?? 0)
const scroll_y = computed(() => timesheet_page.value?.getScrollPosition().top ?? 0);
const timesheet_container = ref<HTMLElement | null>(null);
const scroll_area_height = ref(0);
const addNewShift = (day_shifts: Shift[], date: string, timesheet_id: number) => {
ui_store.focus_next_component = true;
@ -57,10 +59,10 @@
return day.shifts.every(shift => shift.is_approved === true);
};
const handleSwipe = async (direction: 'left' | 'up' | 'down' | 'right' | undefined, distance: { x?: number, y?: number }) => {
mobile_animation_direction.value = direction === 'left' ? 'fadeInRight' : 'fadeInLeft';
if (distance.x && Math.abs(distance.x) > 10) {
await timesheet_api.getTimesheetsBySwiping(direction === 'left' ? 1 : -1)
const handleSwipe: TouchSwipeValue = (details) => {
mobile_animation_direction.value = details.direction === 'left' ? 'fadeInRight' : 'fadeInLeft';
if (details.distance && details.distance.x && Math.abs(details.distance.x) > 10) {
timesheet_api.getTimesheetsBySwiping(details.direction === 'left' ? 1 : -1).catch(error => console.error(error));
}
};
@ -71,8 +73,12 @@
watch(currentDayComponentWatcher, () => {
if (currentDayComponent.value && timesheet_page.value && q.platform.is.mobile) {
console.log('setting scroll position to offsetTop of currentDayComponent: ', currentDayComponent.value[0]!.offsetTop);
timesheet_page.value.setScrollPosition('vertical', currentDayComponent.value[0]!.offsetTop, 800);
return;
timesheet_page.value.setScrollPosition('vertical', currentDayComponent.value[0]!.offsetTop, 800);
return;
}
if (timesheet_container.value !== null && mode === 'approval') {
scroll_area_height.value = timesheet_container.value.offsetHeight
}
})
</script>
@ -81,12 +87,13 @@
<div
class="column fit relative-position"
:style="$q.platform.is.mobile && $q.screen.width < $q.screen.height ? 'margin-bottom: 40px' : ''"
v-touch-swipe="value => handleSwipe(value.direction, value.distance ?? { x: 0, y: 0 })"
v-touch-swipe="handleSwipe"
>
<q-scroll-area
ref="timesheet_page"
:horizontal-offset="[0, 3]"
class="absolute-full hide-scrollbar"
class="col absolute-full hide-scrollbar"
:style="mode === 'approval' ? `height: ${scroll_area_height}px;` : ''"
:thumb-style="{ opacity: '0' }"
:bar-style="{ opacity: '0' }"
>
@ -97,7 +104,7 @@
style="min-height: 20vh;"
>
<span class="text-uppercase text-weight-bolder text-center">{{ $t('shared.error.no_data_found')
}}</span>
}}</span>
<q-icon
name="las la-calendar"
color="accent"
@ -110,6 +117,7 @@
<!-- Else show timesheets if found -->
<div
v-else
ref="timesheet_container"
class="col fit"
:class="$q.platform.is.mobile ? 'column' : 'row'"
>

View File

@ -4,10 +4,12 @@
>
/* eslint-disable */
import ShiftList from 'src/modules/timesheets/components/shift-list.vue';
import LoadingOverlay from 'src/modules/shared/components/loading-overlay.vue';
import ExpenseDialog from 'src/modules/timesheets/components/expense-dialog.vue';
import PageHeaderTemplate from 'src/modules/shared/components/page-header-template.vue';
import PayPeriodNavigator from 'src/modules/shared/components/pay-period-navigator.vue';
import TimesheetErrorWidget from 'src/modules/timesheets/components/timesheet-error-widget.vue';
import LoadingOverlay from 'src/modules/shared/components/loading-overlay.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 { computed, onMounted } from 'vue';
@ -37,9 +39,41 @@
</script>
<template>
<div class="column items-center full-height">
<div class="column items-center full-height relative-position no-wrap">
<LoadingOverlay v-model="timesheet_store.is_loading" />
<!-- label for approval mode to delimit that this is the timesheet -->
<span
v-if="mode === 'approval'"
class="col-auto text-uppercase text-bold text-h5"
>
{{ $t('timesheet.page_header') }}
</span>
<!-- weekly overview -->
<div class="col-auto row q-px-lg full-width">
<!-- supervisor weekly overview -->
<div class="col-xs-6 col-md-4 col-xl-3 q-pa-md">
<ShiftListWeeklyOverview mode="total-hours" />
</div>
<PageHeaderTemplate
v-if="mode === 'normal'"
:title="'timesheet.page_header'"
:start-date="timesheet_store.pay_period?.period_start ?? ''"
:end-date="timesheet_store.pay_period?.period_end ?? ''"
class="col"
/>
<q-space v-else />
<!-- employee weekly overview -->
<div class="col-xs-6 col-md-4 col-xl-3 q-pa-md">
<ShiftListWeeklyOverview mode="off-hours" />
</div>
</div>
<!-- top menu -->
<div
class="col-auto row items-center full-width"
@ -66,12 +100,6 @@
@click="expenses_store.open"
/>
<!-- label for approval mode to delimit that this is the timesheet -->
<span
v-if="mode === 'approval'"
class="col-auto text-uppercase text-bold text-h5"
> {{ $t('timesheet.page_header') }}</span>
<q-space v-if="$q.screen.width > $q.screen.height" />
<!-- desktop expenses button -->
@ -107,7 +135,8 @@
<ShiftList
:mode="mode"
class="col"
:class="mode === 'normal' ? 'col' : 'col-auto'"
:style="mode === 'normal' ? '' : 'min-height: 100vh'"
/>
<q-btn

View File

@ -49,6 +49,14 @@
route="https://map.targointernet.com/infrastructure/map.php"
/>
</div>
<div class="col-3 q-pa-sm">
<ShortcutCard
image-source="src/assets/info-pannes.png"
title="Info Pannes"
route="https://infopannes.solutions.hydroquebec.com/info-pannes/pannes/pannes-en-cours"
/>
</div>
</div>
</div>

View File

@ -20,7 +20,7 @@
const headerComponent = ref<HTMLElement | null>(null);
const table_max_height = computed(() => {
const height = page_height.value - (headerComponent.value?.offsetHeight ?? 0);
const height = page_height.value - (headerComponent.value?.clientHeight ?? 0);
return height;
});

View File

@ -2,14 +2,12 @@
setup
lang="ts"
>
import PageHeaderTemplate from 'src/modules/shared/components/page-header-template.vue';
import TimesheetWrapper from 'src/modules/timesheets/components/timesheet-wrapper.vue';
import { useAuthStore } from 'src/stores/auth-store';
import { useTimesheetStore } from 'src/stores/timesheet-store';
const { user } = useAuthStore();
const timesheet_store = useTimesheetStore();
</script>
@ -17,13 +15,6 @@
<q-page
class="column bg-secondary items-center"
>
<PageHeaderTemplate
:title="'timesheet.page_header'"
:start-date="timesheet_store.pay_period?.period_start ?? ''"
:end-date="timesheet_store.pay_period?.period_end ?? ''"
class="col-auto"
/>
<div
class="col column fit"
:style="$q.platform.is.mobile && ($q.screen.width < $q.screen.height) ? '' : 'width: 90vw'"