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
lang="ts"
>
const { imageSource = "", title = "", description = "", route = "" } = defineProps<{
imageSource?: string,
title?: string,
description?: string,
const { route = "" } = defineProps<{
iconImageSource: string,
bgImageSource: string,
name: string,
route?: string,
}>();
@ -15,28 +15,50 @@
</script>
<template>
<q-card
class="shortcut-card cursor-pointer shadow-12"
<div
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"
>
<q-img
:src="imageSource"
fit="contain"
<span
v-if="$q.platform.is.mobile"
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>
</q-img>
{{ name }}
</span>
<q-card-section v-if="description">
<span>{{ description }}</span>
</q-card-section>
</q-card>
<div
class="row items-center q-px-lg q-py-sm link-card rounded-10 inset-shadow"
:style="`background-image: url(${bgImageSource}); background-size: ${$q.platform.is.mobile ? 'cover' : 'contain'};`"
>
<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>
<style
lang="sass"
scoped
lang="css"
>
.shortcut-card
width: 100%
max-width: 250px
.link-card {
background-blend-mode: multiply;
background-position: bottom right;
background-repeat: no-repeat;
background-color: var(--q-dark);
background-size: contain;
}
</style>

View File

@ -2,10 +2,18 @@
setup
lang="ts"
>
import { ref } from 'vue';
import { RouteNames } from 'src/router/router-constants';
import { ref } from 'vue';
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>
<template>
@ -17,10 +25,12 @@ import { ref } from 'vue';
animated
infinite
arrows
:autoplay="9001"
:autoplay="autoplayTimer"
control-color="accent"
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 -->
<q-carousel-slide
@ -39,7 +49,7 @@ import { ref } from 'vue';
</div>
</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>
</div>
</div>
@ -63,7 +73,7 @@ import { ref } from 'vue';
</div>
</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>
</div>
</div>

View File

@ -10,6 +10,7 @@
const { mode = 'totals', timesheetMode = 'normal', totalHours = 0, totalExpenses = 0 } = defineProps<{
mode: 'total-hours' | 'off-hours';
timesheetMode: 'approval' | 'normal';
weeklyHours?: number[];
totalHours?: number;
totalExpenses?: number;
}>();
@ -37,6 +38,18 @@
v-if="mode === 'total-hours'"
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">
<span class="col-auto text-uppercase text-caption text-bold text-accent">
{{ $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 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) =>
sum += timesheet.weekly_hours.regular
+ timesheet.weekly_hours.evening
+ timesheet.weekly_hours.emergency
+ timesheet.weekly_hours.vacation
+ timesheet.weekly_hours.holiday
+ timesheet.weekly_hours.overtime,
0 //initial value
));
@ -124,6 +129,7 @@ import { RouteNames } from 'src/router/router-constants';
<ShiftListWeeklyOverview
mode="total-hours"
:timesheet-mode="mode"
:weekly-hours="weeklyHours"
:total-hours="totalHours"
:total-expenses="totalExpenses"
/>

View File

@ -12,68 +12,84 @@
class="q-pa-md justify-center items-stretch bg-secondary"
:class="$q.platform.is.mobile ? 'column' : 'row'"
>
<!-- left column -->
<div class="column col flex-center q-pa-md">
</div>
<!-- center column -->
<div class="column col-xs-12 col-md-8 col-xl-6 items-center q-pa-md">
<div class="col-auto full-width q-py-md">
<div class="column col items-center q-pa-md">
<div class="col-8 fit q-py-md">
<MainCarousel />
</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>
<!-- 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
class="col-auto row full-width within-iframe"
:class="$q.platform.is.mobile ? 'justify-center' : 'justify-end q-pl-md'"
style="height: 50vh;"
:class="$q.platform.is.mobile ? 'justify-center q-pt-lg' : 'justify-end'"
>
<iframe
title="Environment Canada Weather"
height="400px"
height="200px"
src="https://weather.gc.ca/wxlink/wxlink.html?coords=45.159%2C-73.676&lang=f"
allowtransparency="true"
style="border: 0;"
class="col-auto"
></iframe>
</div>
</div>