fix(all): Many fixes and adjustements, see full commit comment:

Dashboard: Reworked carousel and added useful links. Help page: made title sections more obvious, minor UI adjustments to spacing, appearance. Timesheets: Make mobile timesheet automaticall scroll to today's date when loading. Layout: Fix UI bugs where menu labels would not appear in mobile and tray would load automatically on mobile.
This commit is contained in:
Nicolas Drolet 2026-01-02 12:38:35 -05:00
parent 8989a7d9c0
commit 20fcc0206c
26 changed files with 289 additions and 137 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 150 KiB

View File

@ -4,8 +4,9 @@ export default {
welcome_title: "Welcome to the new Targo Application!",
welcome_message: "Development is complete and the application is live! Things have remained mostly the same, but with a new coat of paint, a more streamlined user experience, and most importantly, drastically improved security and optimization!",
help_title: "We have a help page!",
help_message: "We did our best to keep the app intuitive with as few clicks and changes as possible, but it's not always perfect! We made this page to explain every part of the app if you any of it ever feels confusing.",
help_message: "We've modernized the app while trying to make as few functional changes as possible, but if there's ever any part of the site that leaves you scratching your head, feel free to check out the help page.",
},
useful_links: "useful links",
},
help: {
label: "Centre d'aide",
@ -207,6 +208,7 @@ export default {
modify: "modify",
close: "close",
download: "download",
open: "open",
},
misc: {
or: "or",

View File

@ -2,10 +2,11 @@ export default {
dashboard: {
carousel: {
welcome_title: "Bienvenue dans la nouvelle application Targo!",
welcome_message: "Le développement est terminé et l'application est officiellement en ligne! Les fonctionnalités demeurent grandement intactes comparé à l'ancienne version, mise à part une nouvelle couche de peinture, une expérience utilisateur plus intuitive et surtout une sécurité et optimization drastiquement amélioriés!",
welcome_message: "La nouvelle application est officiellement en ligne ! Plus performante et plus sécuritaire, elle conserve lessentiel avec un design rafraîchie.",
help_title: "Nous avons une page d'aide!",
help_message: "Nous avons fait notre possible pour rendre l'application plus intuitive et facile d'accès en suivant les tendances modernes, mais il y a toujours place à l'amélioration! La page d'aide est là pour vous si jamais nous avons raté la cible et qu'une partie du site semble nébuleux.",
help_message: "Lapplication a été pensée pour être plus intuitive et moderne. En cas de doute, la page daide est à votre disposition.",
},
useful_links: "liens utiles",
},
help: {
label: "Centre d'aide",
@ -208,6 +209,7 @@ export default {
modify: "modifier",
close: "fermer",
download: "télécharger",
open: "ouvrir",
},
misc: {
or: "ou",

View File

@ -2,7 +2,8 @@
setup
lang="ts"
>
import { ref } from 'vue';
import { onMounted, ref } from 'vue';
import { useQuasar } from 'quasar';
import { useRouter } from 'vue-router';
import { useAuthStore } from 'src/stores/auth-store';
import { useUiStore } from 'src/stores/ui-store';
@ -18,6 +19,7 @@
{ i18n_key: 'nav_bar.help', icon: "contact_support", route: RouteNames.HELP },
]
const q = useQuasar();
const auth_store = useAuthStore();
const ui_store = useUiStore();
const router = useRouter();
@ -27,7 +29,7 @@
ui_store.current_page = page_name;
is_mini.value = true;
router.push({ name: page_name }).catch( error => {
router.push({ name: page_name }).catch(error => {
console.error('failed to reach page: ', error);
});
};
@ -39,6 +41,12 @@
console.error('could not log you out: ', err);
})
};
onMounted(() => {
if(q.platform.is.mobile) {
ui_store.is_left_drawer_open = false;
}
})
</script>
<template>
@ -69,7 +77,10 @@
class="col-auto q-pl-sm"
/>
<div class="col text-uppercase text-weight-bold text-h6 q-mini-drawer-hide q-pl-sm">
<div
class="col text-uppercase text-weight-bold text-h6 q-pl-sm"
:class="$q.platform.is.mobile ? '' : 'q-mini-drawer-hide'"
>
{{ $t(button.i18n_key) }}
</div>
</div>
@ -87,7 +98,10 @@
class="col-auto q-pl-sm"
/>
<div class="col text-uppercase text-weight-bold text-h6 q-mini-drawer-hide q-pl-sm">
<div
class="col text-uppercase text-weight-bold text-h6 q-pl-sm"
:class="$q.platform.is.mobile ? '' : 'q-mini-drawer-hide'"
>
{{ $t('nav_bar.logout') }}
</div>
</div>

View File

@ -0,0 +1,42 @@
<script
setup
lang="ts"
>
const { imageSource = "", title = "", description = "", route = "" } = defineProps<{
imageSource?: string,
title?: string,
description?: string,
route?: string,
}>();
const onClickExternalShortcut = () => {
window.open(route, '_blank')?.focus();
}
</script>
<template>
<q-card
class="shortcut-card cursor-pointer"
@click="onClickExternalShortcut"
>
<q-img
:src="imageSource"
fit="contain"
>
<div class="absolute-bottom text-uppercase text-weight-bolder text-center">{{ title }}</div>
</q-img>
<q-card-section v-if="description">
<span>{{ description }}</span>
</q-card-section>
</q-card>
</template>
<style
lang="sass"
scoped
>
.shortcut-card
width: 100%
max-width: 250px
</style>

View File

@ -0,0 +1,71 @@
<script
setup
lang="ts"
>
import { RouteNames } from 'src/router/router-constants';
import { ref } from 'vue';
const slide = ref<string>('welcome');
</script>
<template>
<q-carousel
v-model="slide"
transition-prev="jump-right"
transition-next="jump-left"
swipeable
animated
infinite
arrows
:autoplay="9001"
control-color="accent"
class="bg-dark full-width rounded-15 shadow-18"
>
<!-- welcome slide -->
<q-carousel-slide
name="welcome"
class="q-pa-none fit"
>
<div class="column fit">
<q-img
src="src/assets/targo_building.png"
position="50% 25%"
fit="cover"
class="col-9"
>
<div class="absolute-bottom text-h6 text-uppercase text-weight-light">
{{ $t('dashboard.carousel.welcome_title') }}
</div>
</q-img>
<div class="col column flex-center q-px-md">
<span class="col-auto text-center">{{ $t('dashboard.carousel.welcome_message') }}</span>
</div>
</div>
</q-carousel-slide>
<!-- help page slide -->
<q-carousel-slide
name="tv"
class="q-pa-none cursor-pointer"
@click="$router.push(RouteNames.HELP)"
>
<div class="column fit">
<q-img
src="src/assets/targo_help_banner.png"
position="50% 25%"
fit="none"
class="col-9"
>
<div class="absolute-bottom text-h6 text-uppercase text-weight-light">
{{ $t('dashboard.carousel.help_title') }}
</div>
</q-img>
<div class="col column flex-center q-px-md">
<span class="col-auto text-center">{{ $t('dashboard.carousel.help_message') }}</span>
</div>
</div>
</q-carousel-slide>
</q-carousel>
</template>

View File

@ -1,40 +1,43 @@
<script setup lang="ts">
//default images
// import default_dashboard from 'src/assets/help-ss/default-dashboard.png';
import default_personal_profile from 'src/assets/help-ss/default-personnal_profile.png';
import default_timesheet from 'src/assets/help-ss/default-timesheet.png';
import default_employee_list from 'src/assets/help-ss/default-employee-list.png';
import default_employee_management from 'src/assets/help-ss/default-employee-management.png';
import default_validation_page from 'src/assets/help-ss/default-validation-page.png';
<script
setup
lang="ts"
>
//default images
// import default_dashboard from 'src/assets/help-ss/default-dashboard.png';
import default_personal_profile from 'src/assets/help-ss/default-personnal_profile.png';
import default_timesheet from 'src/assets/help-ss/default-timesheet.png';
import default_employee_list from 'src/assets/help-ss/default-employee-list.png';
import default_employee_management from 'src/assets/help-ss/default-employee-management.png';
import default_validation_page from 'src/assets/help-ss/default-validation-page.png';
const default_images: Record<UserModuleAccess, string> = {
dashboard: '',
personal_profile: default_personal_profile,
timesheets: default_timesheet,
employee_list: default_employee_list,
employee_management: default_employee_management,
timesheets_approval: default_validation_page,
};
const default_images: Record<UserModuleAccess, string> = {
dashboard: '',
personal_profile: default_personal_profile,
timesheets: default_timesheet,
employee_list: default_employee_list,
employee_management: default_employee_management,
timesheets_approval: default_validation_page,
};
import type { HelpModuleOptions } from 'src/modules/help/models/help-module.model';
import type { UserModuleAccess } from 'src/modules/shared/models/user.models';
import { ref } from 'vue';
import type { HelpModuleOptions } from 'src/modules/help/models/help-module.model';
import type { UserModuleAccess } from 'src/modules/shared/models/user.models';
import { ref } from 'vue';
const props = defineProps<{
help_module: UserModuleAccess;
options: HelpModuleOptions[];
moduleIndex: number;
}>();
const props = defineProps<{
help_module: UserModuleAccess;
options: HelpModuleOptions[];
moduleIndex: number;
}>();
const help_module = props.help_module;
const current_path = ref<string>(default_images[help_module]);
const help_module = props.help_module;
const current_path = ref<string>(default_images[help_module]);
const switchSide = (index: number) => {
if (index % 2 !== 0) {
return false
const switchSide = (index: number) => {
if (index % 2 !== 0) {
return false
}
return true
}
return true
}
</script>
@ -45,14 +48,15 @@ const switchSide = (index: number) => {
>
<!-- Card Header -->
<div
class="row col-auto text-h5 q-pa-md text-primary bg-secondary"
:class="switchSide(moduleIndex) ? 'justify-start' : 'justify-end'"
class="row col-auto text-h4 text-weight-thin q-py-md q-px-xl bg-secondary"
:class="switchSide(moduleIndex) ? 'justify-end' : 'justify-start'"
>
{{ ($t(`help.tutorial.${help_module}.title`)).toUpperCase() }}
</div>
<!-- Card Body -->
<!-- Card Body. The visual support image will alternate left and right -->
<div class="row col full-width q-px-none">
<!-- Object and descriptions zone -->
<!-- Left-sided visual support -->
<div
class="col flex-center row"
v-if="moduleIndex % 2 !== 0"
@ -63,16 +67,17 @@ const switchSide = (index: number) => {
leave-active-class="animated fade-out"
>
<q-img
class="image-wrapper"
:src="current_path"
loading="lazy"
fit="contain"
class="rounded-10 image-wrapper"
>
</q-img>
</transition>
</div>
<div class="col column">
<!-- Area with expandable help items -->
<div class="col column q-px-md">
<q-expansion-item
v-for="option, index in options"
:key="index"
@ -112,7 +117,8 @@ const switchSide = (index: number) => {
</div>
</q-expansion-item>
</div>
<!-- images of the related selected option -->
<!-- right-sided visual support -->
<div
class="col flex-center row"
v-if="moduleIndex % 2 === 0"
@ -123,11 +129,10 @@ const switchSide = (index: number) => {
leave-active-class="animated fade-out"
>
<q-img
class="image-wrapper"
:src="current_path"
loading="lazy"
fit="contain"
style="width: 150%;"
class="rounded-10 image-wrapper"
>
</q-img>
</transition>

View File

@ -17,11 +17,13 @@
</script>
<template>
<div class="column text-uppercase text-center text-weight-bolder text-h4 q-pt-lg">
<div class="column text-uppercase text-center text-weight-bolder text-h4">
<span
v-if="!$q.platform.is.mobile"
class="col"
>{{ $t(title) }}</span>
class="col q-pt-lg"
>
{{ $t(title) }}
</span>
<transition
enter-active-class="animated fadeInDown"

View File

@ -96,6 +96,7 @@
v-model="calendar_date"
color="primary"
today-btn
no-unset
mask="YYYY-MM-DD"
:options="date => date >= PAY_PERIOD_DATE_LIMIT"
@update:model-value="onDateSelected"

View File

@ -209,6 +209,18 @@
</q-tooltip>
</q-toggle>
</template>
<template #option="scope">
<q-item>
<q-item-section avatar>
<q-icon :name="scope.opt.icon" />
</q-item-section>
<q-item-section class="text-left">
{{ $t(scope.label) }}
</q-item-section>
</q-item>
</template>
</q-select>
</div>

View File

@ -60,7 +60,7 @@
class="col row flex-center"
>
<q-badge
:color="day.shifts.length > 0 ? (day.shifts.every(shift => shift.is_approved) ? 'accent shadow-2' : 'dark shadow-2') : 'blue-grey-5'"
:color="day.shifts.length > 0 ? (day.shifts.every(shift => shift.is_approved) ? 'accent shadow-2' : 'white shadow-2') : 'blue-grey-5'"
:class="day.shifts.length > 0 ? (day.shifts.every(shift => shift.is_approved) ? 'q-px-xs' : 'q-pa-sm') : ''"
:style="day.shifts.length > 0 ? '' : 'opacity: 0.5'"
>

View File

@ -149,6 +149,20 @@
</q-tooltip>
</q-toggle>
</template>
<template #option="scope">
<q-item>
<q-item-section avatar>
<q-icon
:name="scope.opt.icon"
/>
</q-item-section>
<q-item-section class="text-left">
{{ $t(scope.label) }}
</q-item-section>
</q-item>
</template>
</q-select>
</div>

View File

@ -6,7 +6,7 @@
import ShiftListDateWidget from 'src/modules/timesheets/components/shift-list-date-widget.vue';
import { date } from 'quasar';
import { computed, ref } from 'vue';
import { computed, ref, watch } from 'vue';
import { useUiStore } from 'src/stores/ui-store';
import { useTimesheetStore } from 'src/stores/timesheet-store';
import { Shift } from 'src/modules/timesheets/models/shift.models';
@ -14,13 +14,15 @@
import type { QScrollArea } from 'quasar';
import type { TimesheetDay } from 'src/modules/timesheets/models/timesheet.models';
const CURRENT_DATE_STRING = new Date().toISOString().slice(0, 10);
const { extractDate } = date;
const ui_store = useUiStore();
const timesheet_store = useTimesheetStore();
const timesheet_api = useTimesheetApi();
const { mode = 'normal'} = defineProps<{
const { mode = 'normal' } = defineProps<{
mode: 'normal' | 'approval';
}>();
@ -28,6 +30,9 @@
const animation_style = computed(() => ui_store.is_mobile_mode ? mobile_animation_direction.value : 'fadeInDown');
const timesheet_page = ref<QScrollArea | null>(null);
const currentDayComponent = ref<HTMLElement[] | null>(null);
const currentDayComponentWatcher = ref(currentDayComponent);
const scroll_y = computed(() => timesheet_page.value?.getScrollPosition().top ?? 0)
const addNewShift = (day_shifts: Shift[], date: string, timesheet_id: number) => {
@ -44,25 +49,37 @@
const shifts_without_deleted_shift = day.shifts.filter(shift => shift.id !== 0);
day.shifts = shifts_without_deleted_shift;
}
}
};
const getDayApproval = (day: TimesheetDay) => {
if (day.shifts.length < 1) return false;
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 getMobileDayRef = (iso_date_string: string): string => {
return iso_date_string === CURRENT_DATE_STRING ? 'currentDayComponent' : '';
};
watch(currentDayComponentWatcher, () => {
if (currentDayComponent.value && timesheet_page.value) {
console.log('setting scroll position to offsetTop of currentDayComponent: ', currentDayComponent.value[0]!.offsetTop);
timesheet_page.value.setScrollPosition('vertical', currentDayComponent.value[0]!.offsetTop, 800);
return;
}
})
</script>
<template>
<div
class="col column fit relative-position"
:style="$q.platform.is.mobile ? 'margin-bottom: 40px' : ''"
: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 })"
>
<q-scroll-area
@ -79,7 +96,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"
@ -129,9 +146,11 @@
<div
v-for="day, day_index in timesheet.days"
:key="day.date"
:ref="getMobileDayRef(day.date)"
class="col-auto row q-pa-sm fit"
:style="`animation-delay: ${day_index / 15}s;`"
>
<!-- mobile version in portrait mode -->
<div
v-if="$q.platform.is.mobile && ($q.screen.width < $q.screen.height)"
class="col column full-width q-px-md q-py-sm"
@ -192,6 +211,7 @@
</q-card>
</div>
<!-- desktop version -->
<div
v-else
class="col row full-width rounded-10 ellipsis shadow-10"
@ -274,19 +294,21 @@
</template>
<style scoped lang="scss">
<style
scoped
lang="scss"
>
@each $size in (1, 2, 3, 4, 5, 10, 15, 20, 25, 50, 75, 100, 200) {
.mobile-rounded-#{$size} {
border-radius: #{$size}px !important;
}
.mobile-rounded-#{$size} {
border-radius: #{$size}px !important;
}
.mobile-rounded-#{$size} > div:first-child {
border-radius: #{$size}px #{$size}px 0 0 !important;
}
.mobile-rounded-#{$size}>div:first-child {
border-radius: #{$size}px #{$size}px 0 0 !important;
}
.mobile-rounded-#{$size} > div:last-child {
border-radius: 0 0 #{$size}px #{$size}px !important;
}
.mobile-rounded-#{$size}>div:last-child {
border-radius: 0 0 #{$size}px #{$size}px !important;
}
}
</style>

View File

@ -49,7 +49,7 @@
<PayPeriodNavigator
v-if="mode === 'normal'"
class="col-auto"
@date-selected="date_value => timesheet_api.getTimesheetsByDate(date_value)"
@date-selected="timesheet_api.getTimesheetsByDate"
@pressed-previous-button="timesheet_api.getTimesheetsByCurrentPayPeriod"
@pressed-next-button="timesheet_api.getTimesheetsByCurrentPayPeriod"
/>
@ -112,7 +112,7 @@
color="accent"
icon="upload"
:label="$t('shared.label.save')"
class="col-auto absolute-bottom shadow-up-10 z-top"
class="col-auto absolute-bottom shadow-up-10"
style="height: 50px;"
@click="shift_api.saveShiftChanges"
/>

View File

@ -2,15 +2,14 @@
setup
lang="ts"
>
import { ref } from 'vue';
const slide = ref<string>('welcome');
import MainCarousel from 'src/modules/dashboard/components/main-carousel.vue';
import ShortcutCard from 'src/modules/dashboard/components/employee/shortcut-card.vue';
</script>
<template>
<q-page
padding
class="q-pa-md justify-center items-stretch"
class="q-pa-md justify-center items-stretch bg-secondary"
:class="$q.platform.is.mobile ? 'column' : 'row'"
>
<!-- left column -->
@ -19,71 +18,37 @@
</div>
<!-- center column -->
<div class="column col-xs-12 col-md-8 col-xl-6 flex-center q-pa-md">
<q-carousel
v-model="slide"
transition-prev="jump-right"
transition-next="jump-left"
swipeable
animated
navigation
infinite
:autoplay="9001"
control-color="accent"
class="col-auto bg-dark rounded-15 shadow-18"
:style="$q.platform.is.mobile ? 'height: 60vh;' : 'height: 50vh;'"
>
<!-- welcome slide -->
<q-carousel-slide
name="welcome"
class="q-pa-none q-pb-xl fit"
>
<div class="column fit">
<q-img
src="src/assets/targo_building.png"
height="30vh"
position="50% 25%"
fit="cover"
class="col-auto"
>
<div class="absolute-bottom text-h6 text-uppercase text-weight-light">
{{ $t('dashboard.carousel.welcome_title') }}
</div>
</q-img>
<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">
<MainCarousel />
</div>
<div class="column col q-mt-md q-px-md flex-center">
<span class="col-auto">{{ $t('dashboard.carousel.welcome_message') }}</span>
</div>
</div>
</q-carousel-slide>
<span class="col-auto text-uppercase text-weight-bold self-start q-pt-md">{{ $t('dashboard.useful_links') }}</span>
<!-- help page slide -->
<q-carousel-slide
name="tv"
class="q-pa-none q-pb-xl"
>
<div class="column fit">
<q-img
src="src/assets/targo_help_banner.png"
height="30vh"
position="50% 25%"
fit="none"
class="col-auto"
>
<div class="absolute-bottom text-h6 text-uppercase text-weight-light">
{{ $t('dashboard.carousel.help_title') }}
</div>
</q-img>
<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 column justify-center q-mt-md q-px-md">
<span class="col-auto">{{ $t('dashboard.carousel.help_message') }}</span>
</div>
</div>
</q-carousel-slide>
</q-carousel>
<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 column">
<span class="col-auto text-h6 text-uppercase"> </span>
<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>
</div>

View File

@ -10,7 +10,7 @@ import { RouteNames } from 'src/router/router-constants';
export const useUiStore = defineStore('ui', () => {
const q = useQuasar();
const { locale } = useI18n();
const is_left_drawer_open = ref(false);
const is_left_drawer_open = ref(true);
const focus_next_component = ref(false);
const is_mobile_mode = computed(() => q.screen.lt.md);
const user_preferences = ref<Preferences>(new Preferences);