diff --git a/.env.production b/.env.production index 61b1320..2ff661e 100644 --- a/.env.production +++ b/.env.production @@ -1 +1 @@ -VITE_TARGO_BACKEND_URL=PREFIX_BACKEND_URL \ No newline at end of file +VITE_TARGO_BACKEND_URL=http://localhost:3000/ diff --git a/public/img/circle.png b/public/img/circle.png new file mode 100644 index 0000000..373aeb7 Binary files /dev/null and b/public/img/circle.png differ diff --git a/public/img/links/facturation-transparent.png b/public/img/links/facturation-transparent.png new file mode 100644 index 0000000..2db5394 Binary files /dev/null and b/public/img/links/facturation-transparent.png differ diff --git a/public/img/links/hydroQC_icon.png b/public/img/links/hydroQC_icon.png new file mode 100644 index 0000000..2dbf4b3 Binary files /dev/null and b/public/img/links/hydroQC_icon.png differ diff --git a/public/img/links/info-pannes.png b/public/img/links/info-pannes.png new file mode 100644 index 0000000..fcfe362 Binary files /dev/null and b/public/img/links/info-pannes.png differ diff --git a/public/img/links/intranet_logo.png b/public/img/links/intranet_logo.png new file mode 100644 index 0000000..bc9089b Binary files /dev/null and b/public/img/links/intranet_logo.png differ diff --git a/public/img/links/logo_gmail.png b/public/img/links/logo_gmail.png new file mode 100644 index 0000000..957abed Binary files /dev/null and b/public/img/links/logo_gmail.png differ diff --git a/public/img/logo-targo-green.svg b/public/img/logo-targo-green.svg new file mode 100644 index 0000000..a3e9f51 --- /dev/null +++ b/public/img/logo-targo-green.svg @@ -0,0 +1 @@ + diff --git a/public/img/logo-targo-white.svg b/public/img/logo-targo-white.svg new file mode 100644 index 0000000..6f748fe --- /dev/null +++ b/public/img/logo-targo-white.svg @@ -0,0 +1 @@ + diff --git a/public/img/targo-default-avatar.png b/public/img/targo-default-avatar.png new file mode 100644 index 0000000..a6751b2 Binary files /dev/null and b/public/img/targo-default-avatar.png differ diff --git a/public/img/targo_building.png b/public/img/targo_building.png new file mode 100644 index 0000000..8f4fafc Binary files /dev/null and b/public/img/targo_building.png differ diff --git a/public/img/targo_help_banner.png b/public/img/targo_help_banner.png new file mode 100644 index 0000000..be0c2b5 Binary files /dev/null and b/public/img/targo_help_banner.png differ diff --git a/quasar.config.ts b/quasar.config.ts index 7a69943..81de3c6 100644 --- a/quasar.config.ts +++ b/quasar.config.ts @@ -57,7 +57,7 @@ export default defineConfig((ctx) => { // rebuildCache: true, // rebuilds Vite/linter/etc cache on startup - // publicPath: '/', + publicPath: '/assets/targo-app/', // analyze: true, // env: {}, // rawDefine: {} @@ -117,7 +117,7 @@ export default defineConfig((ctx) => { dark: 'auto', }, - // iconSet: 'material-icons', // Quasar icon set + iconSet: 'material-icons-outlined', // Modern outlined icons // lang: 'en-US', // Quasar language pack // For special cases outside of where the auto-import strategy can have an impact diff --git a/src/css/app.scss b/src/css/app.scss index ae674d8..adc0218 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -2,7 +2,7 @@ @each $size in (1, 2, 3, 4, 5, 10, 15, 20, 25, 50, 75, 100, 200) { .rounded-#{$size} { border-radius: #{$size}px !important; - } + } } .text-fb-blue { @@ -50,12 +50,13 @@ body.body--dark { background-color: #95f0a1B0; } +/* ── Modern button styling ── */ .q-btn--push::before { - border-bottom: 4px solid rgba(0,0,0, 0.25); + border-bottom: 3px solid rgba(0,0,0, 0.18); } .q-btn--push:active { - transform: translateY(3px); + transform: translateY(2px); } .q-btn--push:active::before { @@ -75,11 +76,11 @@ input[type=number] { } .q-field--dark .q-field__control::before { - border-color: #fff3; + border-color: #fff2; } .q-field--dark .q-field__control:hover::before, .q-field--outlined .q-field__control:hover::before { - border-color: var(--q-accent2); + border-color: var(--q-accent); border-width: 2px; } @@ -91,4 +92,126 @@ input[type=number] { .text-border-dark { text-shadow: 2px 0 var(--q-primary), -2px 0 var(--q-primary), 0 2px var(--q-primary), 0 -2px var(--q-primary), 1px 1px var(--q-primary), -1px -1px var(--q-primary), 1px -1px var(--q-primary), -1px 1px var(--q-primary); -} \ No newline at end of file +} + +/* ── Modern UI enhancements ── */ + +/* Smoother font rendering */ +body { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Today's row: prominent indicator */ +.shift-today-glow { + box-shadow: 0 0 0 2.5px var(--q-accent), 0 4px 20px rgba(14, 165, 80, 0.2) !important; + position: relative; + z-index: 2; +} + +/* Day rows: subtle hover lift */ +.shift-day-row { + transition: transform 0.15s ease, box-shadow 0.15s ease; + &:hover { + transform: translateY(-1px); + box-shadow: 0 4px 16px rgba(0,0,0,0.10) !important; + } +} + +/* Empty day card: dashed border invite */ +.shift-day-empty { + cursor: pointer; + .bg-dark { + border: 1.5px dashed rgba(255,255,255,0.12) !important; + transition: border-color 0.2s; + } + &:hover .bg-dark { + border-color: var(--q-accent) !important; + } +} + +/* Cleaner card borders */ +.q-card { + border: 1px solid rgba(0,0,0,0.06); +} + +body.body--dark .q-card { + border-color: rgba(255,255,255,0.06); +} + +/* Outlined icon style consistency */ +.q-icon { + font-weight: 300; +} + +/* Modern header bar */ +.q-header { + backdrop-filter: blur(12px); + .q-toolbar { + min-height: 56px; + } +} + +/* Shift type selector: prevent label truncation */ +@media (min-width: 1024px) { + .q-select .q-field__native { min-width: 120px; } +} + +/* Cleaner input fields */ +.q-field--outlined .q-field__control { + border-radius: 8px; +} + +/* Sidebar: cleaner look */ +.q-drawer { + .q-item { + border-radius: 8px; + margin: 2px 8px; + &.q-router-link--active { + font-weight: 700; + } + } +} + +/* Modern scrollbar */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: rgba(0,0,0,0.15); + border-radius: 3px; +} +body.body--dark ::-webkit-scrollbar-thumb { + background: rgba(255,255,255,0.12); +} + +/* Weekly overview bars */ +.weekly-bar-fill { + transition: width 0.4s ease, background-color 0.3s; +} + +/* Approved badge: outlined style */ +.q-badge { + font-weight: 600; + letter-spacing: 0.02em; +} + +/* Hover lift utility */ +.hover-lift { + transition: background 0.15s, transform 0.15s; + &:hover { + background: rgba(14, 165, 80, 0.08); + } +} + +/* Tooltip: modern look */ +.q-tooltip { + font-size: 0.75rem; + font-weight: 500; + border-radius: 6px; + padding: 4px 10px; +} diff --git a/src/css/quasar.variables.scss b/src/css/quasar.variables.scss index b210fed..4c53757 100644 --- a/src/css/quasar.variables.scss +++ b/src/css/quasar.variables.scss @@ -1,41 +1,31 @@ // Quasar SCSS (& Sass) Variables // -------------------------------------------------- -// To customize the look and feel of this app, you can override -// the Sass/SCSS variables found in Quasar's source Sass/SCSS files. +// Modernized theme — cleaner, lighter feel with outline aesthetic -// Check documentation for full list of Quasar variables - -// Your own variables (that are declared here) and Quasar's own -// ones will be available out of the box in your .vue/.scss/.sass files - -// It's highly recommended to change the default colors -// to match your app's branding. -// Tip: Use the "Theme Builder" on Quasar's documentation website. - -$primary : #30303A; -$secondary : #DAE0E7; -$accent : #0c9a3b; +$primary : #1e1e2a; +$secondary : #eef1f5; +$accent : #0ea550; $dark-shadow-color : #000; -$elevation-dark-umbra : rgba($dark-shadow-color, .2); -$elevation-dark-penumbra : rgba($dark-shadow-color, .14); -$elevation-dark-ambient : rgba($dark-shadow-color, .12); +$elevation-dark-umbra : rgba($dark-shadow-color, .15); +$elevation-dark-penumbra : rgba($dark-shadow-color, .10); +$elevation-dark-ambient : rgba($dark-shadow-color, .08); -$layout-shadow-dark : 0 0 5px 5px rgba($dark-shadow-color, 0.5); +$layout-shadow-dark : 0 1px 8px rgba($dark-shadow-color, 0.25); -$input-text-color : #455A64; -$input-autofill-color : #AAD5C4; +$input-text-color : #37474F; +$input-autofill-color : #c8e6d5; $field-dense-label-top : 5px !default; -$field-dense-label-font-size : 16px !default; +$field-dense-label-font-size : 14px !default; $button-shadow : 0 0 0 transparent; -$dark : #40404C; -$dark-page : #343444; +$dark : #2a2a3a; +$dark-page : #222233; $positive : #21ba45; $negative : #e6364b; -$info : #6bb9e7; +$info : #5ba8d9; $warning : #e4a944; $white : white; diff --git a/src/layouts/components/main-layout-header-bar.vue b/src/layouts/components/main-layout-header-bar.vue index 71c6eee..838dd71 100644 --- a/src/layouts/components/main-layout-header-bar.vue +++ b/src/layouts/components/main-layout-header-bar.vue @@ -3,56 +3,95 @@ setup > import { useAuthStore } from 'src/stores/auth-store'; -import { useUiStore } from 'src/stores/ui-store'; + import { useUiStore } from 'src/stores/ui-store'; const uiStore = useUiStore(); const authStore = useAuthStore(); + + diff --git a/src/layouts/components/main-layout-left-drawer.vue b/src/layouts/components/main-layout-left-drawer.vue index e480402..8831f01 100644 --- a/src/layouts/components/main-layout-left-drawer.vue +++ b/src/layouts/components/main-layout-left-drawer.vue @@ -12,12 +12,12 @@ import { useAuthApi } from 'src/modules/auth/composables/use-auth-api'; const DRAWER_BUTTONS: { i18n_key: string, icon: string, route: RouteNames, required_module?: UserModuleAccess }[] = [ - { i18n_key: 'nav_bar.home', icon: "home", route: RouteNames.DASHBOARD, required_module: ModuleNames.DASHBOARD }, - { i18n_key: 'nav_bar.timesheet_approvals', icon: "event_available", route: RouteNames.TIMESHEET_APPROVALS, required_module: ModuleNames.TIMESHEETS_APPROVAL }, - { i18n_key: 'nav_bar.employee_list', icon: "groups", route: RouteNames.EMPLOYEE_LIST, required_module: ModuleNames.EMPLOYEE_LIST }, - { i18n_key: 'nav_bar.timesheet', icon: "punch_clock", route: RouteNames.TIMESHEET, required_module: ModuleNames.TIMESHEETS }, - { i18n_key: 'nav_bar.profile', icon: "account_box", route: RouteNames.PROFILE, required_module: ModuleNames.PERSONAL_PROFILE }, - { i18n_key: 'nav_bar.help', icon: "contact_support", route: RouteNames.HELP }, + { i18n_key: 'nav_bar.home', icon: "o_home", route: RouteNames.DASHBOARD, required_module: ModuleNames.DASHBOARD }, + { i18n_key: 'nav_bar.timesheet_approvals', icon: "o_event_available", route: RouteNames.TIMESHEET_APPROVALS, required_module: ModuleNames.TIMESHEETS_APPROVAL }, + { i18n_key: 'nav_bar.employee_list', icon: "o_groups", route: RouteNames.EMPLOYEE_LIST, required_module: ModuleNames.EMPLOYEE_LIST }, + { i18n_key: 'nav_bar.timesheet', icon: "o_schedule", route: RouteNames.TIMESHEET, required_module: ModuleNames.TIMESHEETS }, + { i18n_key: 'nav_bar.profile', icon: "o_person", route: RouteNames.PROFILE, required_module: ModuleNames.PERSONAL_PROFILE }, + { i18n_key: 'nav_bar.help', icon: "o_help_outline", route: RouteNames.HELP }, ] const q = useQuasar(); @@ -67,18 +67,21 @@ import { useAuthApi } from 'src/modules/auth/composables/use-auth-api'; >
{{ $t(button.i18n_key) }} @@ -93,7 +96,7 @@ import { useAuthApi } from 'src/modules/auth/composables/use-auth-api'; @click="handleLogout" > { - auth_api.oidcLogin(); + const onClickEmployeeConnect = async () => { + await auth_api.oidcLogin(); } diff --git a/src/modules/auth/composables/use-auth-api.ts b/src/modules/auth/composables/use-auth-api.ts index 1cea1e2..05dd35c 100644 --- a/src/modules/auth/composables/use-auth-api.ts +++ b/src/modules/auth/composables/use-auth-api.ts @@ -7,8 +7,8 @@ export const useAuthApi = () => { authStore.login(); }; - const oidcLogin = () => { - authStore.oidcLogin(); + const oidcLogin = async () => { + await authStore.oidcLogin(); }; const logout = async () => { diff --git a/src/modules/dashboard/components/main-carousel.vue b/src/modules/dashboard/components/main-carousel.vue index 5d8e9fc..3e5c133 100644 --- a/src/modules/dashboard/components/main-carousel.vue +++ b/src/modules/dashboard/components/main-carousel.vue @@ -42,7 +42,7 @@ :class="$q.platform.is.mobile ? 'no-wrap' : ''" > - /* eslint-disable */ - import { date, useQuasar } from 'quasar'; - import { computed, onMounted, onUpdated, ref } from 'vue'; - - const { title, startDate = "", endDate = "" } = defineProps<{ - title: string; - startDate?: string; - endDate?: string; - }>(); - - const q = useQuasar(); - - const emit = defineEmits<{ 'onGetComponentHeight': [value: number] }>(); - - const selfRef = ref(null); - - const date_format_options = computed(() => q.platform.is.mobile ? { day: 'numeric', month: 'short', year: 'numeric' } : { day: 'numeric', month: 'long', year: 'numeric', }); - - onUpdated(() => { - if (selfRef.value) { - emit('onGetComponentHeight', selfRef.value.offsetHeight); - } - }); - - onMounted(() => { - if (selfRef.value) { - emit('onGetComponentHeight', selfRef.value.offsetHeight); - } - }) - - - \ No newline at end of file + + + + + diff --git a/src/modules/shared/components/pay-period-navigator.vue b/src/modules/shared/components/pay-period-navigator.vue index de20dfc..3639bd9 100644 --- a/src/modules/shared/components/pay-period-navigator.vue +++ b/src/modules/shared/components/pay-period-navigator.vue @@ -45,52 +45,45 @@ \ No newline at end of file + + + diff --git a/src/modules/timesheets/components/mobile/shift-list-day-mobile.vue b/src/modules/timesheets/components/mobile/shift-list-day-mobile.vue index 346c0cf..cdbab0a 100644 --- a/src/modules/timesheets/components/mobile/shift-list-day-mobile.vue +++ b/src/modules/timesheets/components/mobile/shift-list-day-mobile.vue @@ -12,7 +12,9 @@ import { Shift } from 'src/modules/timesheets/models/shift.models'; import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api'; import type { TimesheetDay } from 'src/modules/timesheets/models/timesheet.models'; -import { isShiftOverlap } from 'src/modules/timesheets/utils/shift.util'; + import { isShiftOverlap } from 'src/modules/timesheets/utils/shift.util'; + + const CURRENT_DATE_STRING = new Date().toISOString().slice(0, 10); const { locale } = useI18n(); const uiStore = useUiStore(); @@ -21,30 +23,32 @@ import { isShiftOverlap } from 'src/modules/timesheets/utils/shift.util'; const day = defineModel({ required: true }); - const { isTimesheetApproved = false } = defineProps<{ + const { timesheetId, isTimesheetApproved = false } = defineProps<{ timesheetId: number; isTimesheetApproved?: boolean; }>(); - const isDayApproved = computed(() => day.value.shifts.every(shift => shift.is_approved) && day.value.shifts.length > 1); + const isDayApproved = computed(() => day.value.shifts.every(shift => shift.is_approved) && day.value.shifts.length > 0); + const isToday = computed(() => CURRENT_DATE_STRING === day.value.date); + const isWeekend = computed(() => { + const d = date.extractDate(day.value.date, 'YYYY-MM-DD'); + return d.getDay() === 0 || d.getDay() === 6; + }); + const isHoliday = computed(() => timesheetStore.federal_holidays.some(h => h.date === day.value.date)); + const canEdit = computed(() => !isDayApproved.value && !isTimesheetApproved); - const addNewShift = (day_shifts: Shift[], date: string, timesheet_id: number) => { + const addNewShift = () => { + if (!canEdit.value) return; uiStore.focusNextComponent = true; - const newShift = new Shift; - newShift.date = date; - newShift.timesheet_id = timesheet_id; - day_shifts.push(newShift); + const newShift = new Shift(day.value.date); + newShift.timesheet_id = timesheetId; + day.value.shifts.push(newShift); }; - const getHolidayName = (date: string) => { - const holiday = timesheetStore.federal_holidays.find(holiday => holiday.date === date); + const getHolidayName = (_date: string) => { + const holiday = timesheetStore.federal_holidays.find(h => h.date === _date); if (!holiday) return; - - if (locale.value === 'fr-FR') - return holiday.nameFr; - - else if (locale.value === 'en-CA') - return holiday.nameEn; + return locale.value === 'fr-FR' ? holiday.nameFr : holiday.nameEn; }; const onTimeFieldBlur = () => { @@ -58,85 +62,186 @@ import { isShiftOverlap } from 'src/modules/timesheets/utils/shift.util'; else await shiftApi.deleteShiftById(shiftId); - if (day.value.shifts.length < 2) { - onTimeFieldBlur(); - } + if (day.value.shifts.length < 2) onTimeFieldBlur(); }; \ No newline at end of file + + + diff --git a/src/modules/timesheets/components/mobile/shift-list-day-row-mobile.vue b/src/modules/timesheets/components/mobile/shift-list-day-row-mobile.vue index a036f85..cd277fe 100644 --- a/src/modules/timesheets/components/mobile/shift-list-day-row-mobile.vue +++ b/src/modules/timesheets/components/mobile/shift-list-day-row-mobile.vue @@ -112,7 +112,7 @@ diff --git a/src/stores/auth-store.ts b/src/stores/auth-store.ts index db573a2..268e567 100644 --- a/src/stores/auth-store.ts +++ b/src/stores/auth-store.ts @@ -14,10 +14,18 @@ export const useAuthStore = defineStore('auth', () => { //TODO: manage customer login process }; - const oidcLogin = () => { - window.addEventListener('message', (event) => { - void handleAuthMessage(event); - }); + const oidcLogin = async () => { + // DEV: try direct profile fetch first (works when backend has DEV_BYPASS_AUTH) + try { + const result = await getProfile(); + if (result.status === 200 && user.value) { + void router.push('/'); + return; + } + } catch (e) { console.warn('DEV bypass login failed, falling back to OIDC:', e); } + + // Normal OIDC popup flow + window.addEventListener('message', (event) => void handleAuthMessage(event)); const oidc_popup = window.open(`${import.meta.env.VITE_TARGO_BACKEND_URL}auth/v1/login`, 'authPopup', 'width=600,height=800');