feat(dashboard, timesheet): Add weekly hours to timesheet overview, overhaul dashboard links
BIN
src/assets/links/facturation-transparent.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/assets/links/facturation_bg.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
src/assets/links/google_bg.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
BIN
src/assets/links/hydroQC_bg.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
src/assets/links/hydroQC_icon.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
BIN
src/assets/links/intranet_logo.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
src/assets/links/intranet_targo_bg.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
src/assets/links/logo_gmail.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/assets/links/logo_gmail_lockup_default_1x_r5.png
Normal file
|
After Width: | Height: | Size: 1006 B |
BIN
src/assets/links/map-icon.png
Normal file
|
After Width: | Height: | Size: 504 KiB |
BIN
src/assets/links/map_targo_banner.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 9.3 KiB |
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}>();
|
}>();
|
||||||
|
|
@ -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') }}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||