feat(dashboard, timesheet): Add weekly hours to timesheet overview, overhaul dashboard links

This commit is contained in:
Nic D 2026-03-18 15:35:49 -04:00
parent 57946dbadd
commit 57334ed118
19 changed files with 141 additions and 74 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -2,10 +2,10 @@
setup setup
lang="ts" lang="ts"
> >
const { imageSource = "", title = "", description = "", route = "" } = defineProps<{ const { route = "" } = defineProps<{
imageSource?: string, iconImageSource: string,
title?: string, bgImageSource: string,
description?: string, name: string,
route?: string, route?: string,
}>(); }>();
@ -15,28 +15,50 @@
</script> </script>
<template> <template>
<q-card <div
class="shortcut-card cursor-pointer shadow-12" class="full-width cursor-pointer bg-dark shadow-2 rounded-15 q-pa-xs position-relative"
style="border: solid 1px var(--q-accent);"
@click="onClickExternalShortcut" @click="onClickExternalShortcut"
> >
<q-img <span
:src="imageSource" v-if="$q.platform.is.mobile"
fit="contain" class="col text-uppercase text-bold text-accent absolute"
style="transform: translate(20px, -20px);"
> >
<div class="absolute-bottom text-uppercase text-weight-bolder text-center">{{ title }}</div> {{ name }}
</q-img> </span>
<q-card-section v-if="description"> <div
<span>{{ description }}</span> class="row items-center q-px-lg q-py-sm link-card rounded-10 inset-shadow"
</q-card-section> :style="`background-image: url(${bgImageSource}); background-size: ${$q.platform.is.mobile ? 'cover' : 'contain'};`"
</q-card> >
<q-icon
round
color="dark"
size="md"
:name="`img:${iconImageSource}`"
class="col-auto q-pr-md"
/>
<span
v-if="!$q.platform.is.mobile"
class="col text-uppercase text-bold"
>
{{ name }}
</span>
</div>
</div>
</template> </template>
<style <style
lang="sass"
scoped scoped
lang="css"
> >
.shortcut-card .link-card {
width: 100% background-blend-mode: multiply;
max-width: 250px background-position: bottom right;
background-repeat: no-repeat;
background-color: var(--q-dark);
background-size: contain;
}
</style> </style>

View File

@ -2,10 +2,18 @@
setup setup
lang="ts" lang="ts"
> >
import { ref } from 'vue';
import { RouteNames } from 'src/router/router-constants'; import { RouteNames } from 'src/router/router-constants';
import { ref } from 'vue';
const slide = ref<string>('welcome'); const slide = ref<string>('welcome');
const autoplayTimer = ref(9001);
const onCarouselMouseEvent = (state: 'enter' | 'exit') => {
if (state === 'enter')
autoplayTimer.value = 0
else
autoplayTimer.value = 9001
}
</script> </script>
<template> <template>
@ -17,10 +25,12 @@ import { ref } from 'vue';
animated animated
infinite infinite
arrows arrows
:autoplay="9001" :autoplay="autoplayTimer"
control-color="accent" control-color="accent"
control-type="outline" control-type="outline"
class="bg-dark full-width rounded-15 shadow-18" class="bg-dark fit rounded-15 shadow-18"
@mouseenter="onCarouselMouseEvent('enter')"
@mouseleave="onCarouselMouseEvent('exit')"
> >
<!-- welcome slide --> <!-- welcome slide -->
<q-carousel-slide <q-carousel-slide
@ -39,7 +49,7 @@ import { ref } from 'vue';
</div> </div>
</q-img> </q-img>
<div class="col column flex-center q-px-md"> <div class="col column flex-center q-px-md text-h5 text-weight-light">
<span class="col-auto text-center">{{ $t('dashboard.carousel.welcome_message') }}</span> <span class="col-auto text-center">{{ $t('dashboard.carousel.welcome_message') }}</span>
</div> </div>
</div> </div>
@ -63,7 +73,7 @@ import { ref } from 'vue';
</div> </div>
</q-img> </q-img>
<div class="col column flex-center q-px-md"> <div class="col column flex-center q-px-md text-h5 text-weight-light">
<span class="col-auto text-center">{{ $t('dashboard.carousel.help_message') }}</span> <span class="col-auto text-center">{{ $t('dashboard.carousel.help_message') }}</span>
</div> </div>
</div> </div>

View File

@ -10,6 +10,7 @@
const { mode = 'totals', timesheetMode = 'normal', totalHours = 0, totalExpenses = 0 } = defineProps<{ const { mode = 'totals', timesheetMode = 'normal', totalHours = 0, totalExpenses = 0 } = defineProps<{
mode: 'total-hours' | 'off-hours'; mode: 'total-hours' | 'off-hours';
timesheetMode: 'approval' | 'normal'; timesheetMode: 'approval' | 'normal';
weeklyHours?: number[];
totalHours?: number; totalHours?: number;
totalExpenses?: number; totalExpenses?: number;
}>(); }>();
@ -18,8 +19,8 @@
const timesheetStore = useTimesheetStore(); const timesheetStore = useTimesheetStore();
const is_management = auth_store.user?.user_module_access.includes('timesheets_approval'); const is_management = auth_store.user?.user_module_access.includes('timesheets_approval');
const vacationHours = computed(() => timesheetStore.paid_time_off_totals.vacation_hours); const vacationHours = computed(() => timesheetStore.paid_time_off_totals.vacation_hours);
const sickHours = computed(() => timesheetStore.paid_time_off_totals.sick_hours); const sickHours = computed(() => timesheetStore.paid_time_off_totals.sick_hours);
const bankedHours = computed(() => timesheetStore.paid_time_off_totals.banked_hours); const bankedHours = computed(() => timesheetStore.paid_time_off_totals.banked_hours);
onMounted(async () => { onMounted(async () => {
@ -37,6 +38,18 @@
v-if="mode === 'total-hours'" v-if="mode === 'total-hours'"
class="col column full-width" class="col column full-width"
> >
<div
v-for="hours, index in weeklyHours"
:key="index"
class="col row full-width"
>
<span class="col-auto text-uppercase text-caption text-bold text-accent">
{{ $t(`timesheet_approvals.table.weekly_hours_${index + 1}`) }}
</span>
<span class="col text-right">{{ getHoursMinutesStringFromHoursFloat(hours) }}</span>
</div>
<div class="col row full-width"> <div class="col row full-width">
<span class="col-auto text-uppercase text-caption text-bold text-accent"> <span class="col-auto text-uppercase text-caption text-bold text-accent">
{{ $t('timesheet.total_hours') }} {{ $t('timesheet.total_hours') }}

View File

@ -41,10 +41,15 @@ import { RouteNames } from 'src/router/router-constants';
const hasShiftErrors = computed(() => timesheetStore.all_current_shifts.filter(shift => shift.has_error === true).length > 0); const hasShiftErrors = computed(() => timesheetStore.all_current_shifts.filter(shift => shift.has_error === true).length > 0);
const isTimesheetsApproved = computed(() => timesheetStore.timesheets.every(timesheet => timesheet.is_approved)); const isTimesheetsApproved = computed(() => timesheetStore.timesheets.every(timesheet => timesheet.is_approved));
const weeklyHours = computed(() => timesheetStore.timesheets.map(timesheet =>
Object.values(timesheet.weekly_hours).reduce((sum, hoursPerType) => sum += hoursPerType, 0) - timesheet.weekly_hours.sick
));
const totalHours = computed(() => timesheetStore.timesheets.reduce((sum, timesheet) => const totalHours = computed(() => timesheetStore.timesheets.reduce((sum, timesheet) =>
sum += timesheet.weekly_hours.regular sum += timesheet.weekly_hours.regular
+ timesheet.weekly_hours.evening + timesheet.weekly_hours.evening
+ timesheet.weekly_hours.emergency + timesheet.weekly_hours.emergency
+ timesheet.weekly_hours.vacation
+ timesheet.weekly_hours.holiday
+ timesheet.weekly_hours.overtime, + timesheet.weekly_hours.overtime,
0 //initial value 0 //initial value
)); ));
@ -124,6 +129,7 @@ import { RouteNames } from 'src/router/router-constants';
<ShiftListWeeklyOverview <ShiftListWeeklyOverview
mode="total-hours" mode="total-hours"
:timesheet-mode="mode" :timesheet-mode="mode"
:weekly-hours="weeklyHours"
:total-hours="totalHours" :total-hours="totalHours"
:total-expenses="totalExpenses" :total-expenses="totalExpenses"
/> />

View File

@ -12,68 +12,84 @@
class="q-pa-md justify-center items-stretch bg-secondary" class="q-pa-md justify-center items-stretch bg-secondary"
:class="$q.platform.is.mobile ? 'column' : 'row'" :class="$q.platform.is.mobile ? 'column' : 'row'"
> >
<!-- left column -->
<div class="column col flex-center q-pa-md">
</div>
<!-- center column --> <!-- center column -->
<div class="column col-xs-12 col-md-8 col-xl-6 items-center q-pa-md"> <div class="column col items-center q-pa-md">
<div class="col-auto full-width q-py-md"> <div class="col-8 fit q-py-md">
<MainCarousel /> <MainCarousel />
</div> </div>
<span class="col-auto text-uppercase text-weight-bold self-start q-pt-md">{{ $t('dashboard.useful_links') }}</span>
<div class="col row full-width justify-evenly items-start q-py-md">
<div class="col-3 q-pa-sm">
<ShortcutCard
image-source="src/assets/google_thumbnail.png"
title="Google Workspace"
route="https://mail.google.com/mail/u/0/#inbox"
/>
</div>
<div class="col-3 q-pa-sm">
<ShortcutCard
image-source="src/assets/facturation_thumbnail.png"
title="Facturation"
route="https://facturation.targo.ca/facturation/accueil.php?menu=ticket_open"
/>
</div>
<div class="col-3 q-pa-sm">
<ShortcutCard
image-source="src/assets/map_targo_banner.png"
title="Map Targo"
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> </div>
<!-- right column --> <!-- right column -->
<div class="column col items-center"> <div class="column col-lg-4 items-center" :class="$q.platform.is.mobile ? 'q-px-md' : 'q-px-xl'">
<span
v-if="!$q.platform.is.mobile"
class="col-auto text-uppercase text-weight-bold self-start q-px-md q-pt-lg"
>
{{ $t('dashboard.useful_links') }}
</span>
<div class="col-auto full-width"
:class="$q.platform.is.mobile ? 'q-py-md' : 'q-py-sm'">
<ShortcutCard
icon-image-source="src/assets/links/logo_gmail.png"
bg-image-source="src/assets/links/google_bg.png"
name="Messagerie"
route="https://mail.google.com/mail/u/0/#inbox"
/>
</div>
<div class="col-auto full-width"
:class="$q.platform.is.mobile ? 'q-py-md' : 'q-py-sm'">
<ShortcutCard
icon-image-source="src/assets/links/facturation-transparent.png"
bg-image-source="src/assets/links/facturation_bg.png"
name="Facturation"
route="https://facturation.targo.ca/facturation/accueil.php?menu=ticket_open"
/>
</div>
<div class="col-auto full-width"
:class="$q.platform.is.mobile ? 'q-py-md' : 'q-py-sm'">
<ShortcutCard
icon-image-source="src/assets/links/map-icon.png"
bg-image-source="src/assets/links/map_targo_banner.png"
name="Map Targo"
route="https://map.targointernet.com/infrastructure/map"
/>
</div>
<div class="col-auto full-width"
:class="$q.platform.is.mobile ? 'q-py-md' : 'q-py-sm'">
<ShortcutCard
icon-image-source="src/assets/links/hydroQC_icon.png"
bg-image-source="src/assets/links/hydroQC_bg.png"
name="Info Pannes"
route="https://infopannes.solutions.hydroquebec.com/info-pannes/pannes/pannes-en-cours"
/>
</div>
<div class="col-auto full-width"
:class="$q.platform.is.mobile ? 'q-py-md' : 'q-py-sm'">
<ShortcutCard
icon-image-source="src/assets/links/intranet_logo.png"
bg-image-source="src/assets/links/intranet_targo_bg.png"
name="Intranet"
route="https://intranet.facturation.targo.ca/"
/>
</div>
<div class="col"></div>
<div <div
class="col-auto row full-width within-iframe" class="col-auto row full-width within-iframe"
:class="$q.platform.is.mobile ? 'justify-center' : 'justify-end q-pl-md'" :class="$q.platform.is.mobile ? 'justify-center q-pt-lg' : 'justify-end'"
style="height: 50vh;"
> >
<iframe <iframe
title="Environment Canada Weather" title="Environment Canada Weather"
height="400px" height="200px"
src="https://weather.gc.ca/wxlink/wxlink.html?coords=45.159%2C-73.676&lang=f" src="https://weather.gc.ca/wxlink/wxlink.html?coords=45.159%2C-73.676&lang=f"
allowtransparency="true" allowtransparency="true"
style="border: 0;" style="border: 0;"
class="col-auto"
></iframe> ></iframe>
</div> </div>
</div> </div>