diff --git a/Dockerfile b/Dockerfile index 63f43f8..9c8c234 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,15 +7,12 @@ WORKDIR /app # Set environment variables ENV VITE_TARGO_BACKEND_URL="http://targo-backend:3000" -# Copy package.json & package-lock.json first (for caching) -COPY package*.json ./ +# Copy the code +COPY . . # Install dependencies RUN npm install -# Copy the rest of the code -COPY . . - # Expose Quasar dev port EXPOSE 9000 diff --git a/quasar.config.ts b/quasar.config.ts index 13781d0..30f789f 100644 --- a/quasar.config.ts +++ b/quasar.config.ts @@ -104,7 +104,6 @@ export default defineConfig((ctx) => { config: { notify: { color: 'primary', - avatar: 'https://cdn.quasar.dev/img/boy-avatar.png', }, dark: false, }, diff --git a/src/css/app.scss b/src/css/app.scss index 45df8bc..c79a67f 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -25,7 +25,7 @@ } body.body--dark { - --q-secondary: #2b2f34; + --q-secondary: #151520; color: $grey-2; } @@ -41,4 +41,16 @@ body.body--dark { .frosted-glass { background-color: #FFFA !important; backdrop-filter: blur(5px); +} + +.q-btn--push::before { + border-bottom: 4px solid rgba(0,0,0, 0.25); +} + +.q-btn--push:active { + transform: translateY(3px); +} + +.q-btn--push:active::before { + border-bottom-width: 1px; } \ No newline at end of file diff --git a/src/css/quasar.variables.scss b/src/css/quasar.variables.scss index f4316d0..b7391b6 100644 --- a/src/css/quasar.variables.scss +++ b/src/css/quasar.variables.scss @@ -12,9 +12,9 @@ // to match your app's branding. // Tip: Use the "Theme Builder" on Quasar's documentation website. -$primary : #019547; +$primary : #30303A; $secondary : #DAE0E7; -$accent : #AAD5C4; +$accent : #0c9a3b; $dark-shadow-color : #00220f; @@ -30,9 +30,10 @@ $input-autofill-color : #AAD5C4; $field-dense-label-top : 5px !default; $field-dense-label-font-size : 16px !default; +$button-shadow : 0 0 0 transparent; -$dark : #42444b; -$dark-page : #343434; +$dark : #40404C; +$dark-page : #343444; $positive : #21ba45; $negative : #e6364b; diff --git a/src/i18n/en-ca/index.ts b/src/i18n/en-ca/index.ts index ae81e67..498fbb6 100644 --- a/src/i18n/en-ca/index.ts +++ b/src/i18n/en-ca/index.ts @@ -66,6 +66,11 @@ export default { dark_mode: "dark", light_mode: "light", }, + schedule_presets: { + tab_title: "Schedule", + selected_schedule: "Selected Schedule Preset", + new_preset: "Build a new preset", + }, errors: { must_enter_birthdate: "You must enter a valid birthdate", } @@ -151,12 +156,12 @@ export default { REMOTE: "Remote work", }, errors: { - not_found:"Shift not found", - overlap:"An overlaps occured between 2 or more shifts", - invalid:"Invalid shift`s entry", - unknown:"Unknown error", - comment_required:"A comment is required", - comment_too_long:"Your comment is too long", + not_found: "Shift not found", + SHIFT_OVERLAP: "An overlaps occured between 2 or more shifts", + invalid: "Invalid shift`s entry", + unknown: "Unknown error", + comment_required: "A comment is required", + comment_too_long: "Your comment is too long", }, fields: { start:"Start (HH:mm)", @@ -197,7 +202,7 @@ export default { PER_DIEM:"Per Diem", EXPENSES:"expense", MILEAGE:"mileage", - PRIME_GARDE:"on-call allowance", + ON_CALL:"on-call allowance", }, }, }, diff --git a/src/i18n/fr-ca/index.ts b/src/i18n/fr-ca/index.ts index c4ba08a..d6a3a62 100644 --- a/src/i18n/fr-ca/index.ts +++ b/src/i18n/fr-ca/index.ts @@ -66,6 +66,12 @@ export default { dark_mode: "sombre", light_mode: "clair", }, + schedule_presets: { + tab_title: "horaire", + selected_schedule: "Horaire Sélectionné", + new_preset: "Construire un nouvel horaire", + + }, errors: { must_enter_birthdate: "Vous devez entrer une date de naissance valide", } @@ -151,12 +157,12 @@ export default { REMOTE: "Télétravail", }, errors: { - not_found:"Aucun quart trouvé", - overlap:"Il y a un chevauchement entre deux ou plusieurs quarts", - invalid:"Entrée du quart invalide", - unknown:"Erreur inconnue", - comment_required:"un commentaire est requis", - comment_too_long:"votre commentaire est trop long", + not_found: "Aucun quart trouvé", + SHIFT_OVERLAP: "Il y a un chevauchement entre deux ou plusieurs quarts", + invalid: "Entrée du quart invalide", + unknown: "Erreur inconnue", + comment_required: "un commentaire est requis", + comment_too_long: "votre commentaire est trop long", }, fields: { start:"Début (HH:mm)", @@ -197,7 +203,7 @@ export default { PER_DIEM:"Per diem", EXPENSES:"dépense", MILEAGE:"kilométrage", - PRIME_GARDE:"Prime de garde", + ON_CALL:"Prime de garde", }, }, }, diff --git a/src/layouts/components/main-layout-header-bar.vue b/src/layouts/components/main-layout-header-bar.vue index 8120ef7..0d4beb8 100644 --- a/src/layouts/components/main-layout-header-bar.vue +++ b/src/layouts/components/main-layout-header-bar.vue @@ -1,22 +1,36 @@ - - diff --git a/src/layouts/components/main-layout-left-drawer.vue b/src/layouts/components/main-layout-left-drawer.vue index 4223f04..2d0a146 100644 --- a/src/layouts/components/main-layout-left-drawer.vue +++ b/src/layouts/components/main-layout-left-drawer.vue @@ -32,14 +32,14 @@ - + + + + {{ $t('login.button.remember_me') }} + @@ -58,7 +74,7 @@ rounded disabled type="submit" - color="primary" + color="accent" :label="$t('login.button.connect')" class="full-width" /> @@ -72,12 +88,14 @@ {{ $t('shared.misc.or') }} @@ -108,7 +126,7 @@ ({ default: default_employee_profile }); @@ -24,8 +29,8 @@ class="rounded-5 bg-transparent q-pa-none" > diff --git a/src/modules/profile/components/shared/menu-header.vue b/src/modules/profile/components/shared/menu-header.vue index b63319e..b98ec9d 100644 --- a/src/modules/profile/components/shared/menu-header.vue +++ b/src/modules/profile/components/shared/menu-header.vue @@ -1,18 +1,24 @@ - \ No newline at end of file diff --git a/src/modules/profile/components/shared/menu-panel-input-field.vue b/src/modules/profile/components/shared/menu-panel-input-field.vue index b69e4d6..ab00772 100644 --- a/src/modules/profile/components/shared/menu-panel-input-field.vue +++ b/src/modules/profile/components/shared/menu-panel-input-field.vue @@ -22,7 +22,7 @@ autogrow filled debounce="500" - label-color="primary" + label-color="accent" class="q-ma-xs text-uppercase" input-class="text-weight-medium text-h6" :hide-hint="hint === ''" diff --git a/src/modules/profile/components/shared/menu-panel-schedule-presets.vue b/src/modules/profile/components/shared/menu-panel-schedule-presets.vue new file mode 100644 index 0000000..3f2d9cd --- /dev/null +++ b/src/modules/profile/components/shared/menu-panel-schedule-presets.vue @@ -0,0 +1,45 @@ + + + \ No newline at end of file diff --git a/src/modules/profile/components/shared/menu-panel-select-field.vue b/src/modules/profile/components/shared/menu-panel-select-field.vue index d4f98e7..3a5a202 100644 --- a/src/modules/profile/components/shared/menu-panel-select-field.vue +++ b/src/modules/profile/components/shared/menu-panel-select-field.vue @@ -17,7 +17,7 @@ dense :stack-label="!isEditing" filled - label-color="primary" + label-color="accent" class="q-ma-xs text-h6 text-uppercase" popup-content-class="text-weight-medium text-h6" input-class="text-weight-medium" diff --git a/src/modules/profile/components/shared/menu-template.vue b/src/modules/profile/components/shared/menu-template.vue index ff42bb0..bafe5e7 100644 --- a/src/modules/profile/components/shared/menu-template.vue +++ b/src/modules/profile/components/shared/menu-template.vue @@ -2,7 +2,7 @@ import { ref } from 'vue'; import MenuHeader from 'src/modules/profile/components/shared/menu-header.vue'; - const { firstName, lastName, initialMenu } = defineProps<{ + const { initialMenu } = defineProps<{ firstName: string; lastName: string; initialMenu: string; @@ -33,8 +33,8 @@ v-model="current_menu" :vertical="$q.screen.gt.sm" dense - active-color="primary" - indicator-color="primary" + active-color="accent" + indicator-color="accent" > diff --git a/src/modules/profile/models/schedule-presets.models.ts b/src/modules/profile/models/schedule-presets.models.ts new file mode 100644 index 0000000..03ed36d --- /dev/null +++ b/src/modules/profile/models/schedule-presets.models.ts @@ -0,0 +1,27 @@ +export interface SchedulePreset { + name: string; + is_default: boolean; + presets_shifts: ShiftPreset[]; +} + +class ShiftPreset { + week_day: Weekday; + preset_id: number; + sort_order: number; + type: string; + start_time: string; + end_time: string; + is_remote: boolean; + + constructor() { + this.week_day = ''; + this.preset_id = -1; + this.sort_order = -1; + this.type = ''; + this.start_time = ''; + this.end_time = ''; + this.is_remote = false; + } +} + +export type Weekday = 'SUN' | 'MON' | 'TUE' | 'WED' | 'THU' | 'FRI' | 'SAT' | ''; \ No newline at end of file diff --git a/src/modules/profile/services/schedule-presets-service.ts b/src/modules/profile/services/schedule-presets-service.ts new file mode 100644 index 0000000..86c6a47 --- /dev/null +++ b/src/modules/profile/services/schedule-presets-service.ts @@ -0,0 +1,29 @@ +import { api } from "src/boot/axios"; +import type { SchedulePreset } from "src/modules/profile/models/schedule-presets.models"; + +export const SchedulePresetsService = { + createSchedulePresets: async (new_schedule: SchedulePreset) => { + const response = await api.post(`/schedule-presets/create/`, new_schedule); + return response.data; + }, + + updateSchedulePresets: async (preset_id: number, dto: Partial) => { + const response = await api.patch(`/schedule-presets/update/${preset_id}`, dto); + return response.data; + }, + + deleteSchedulePresets: async (preset_id: number) => { + const response = await api.delete(`/schedule-presets/delete/${preset_id}`); + return response.data; + }, + + findListOfSchedulePresets: async () => { + const response = await api.get(`/schedule-presets/find-list`); + return response.data; + }, + + applyPresets: async (preset_name: string, start_date: string) => { + const response = await api.post(`/schedule-presets/apply-presets/`, { preset: preset_name, start: start_date }); + return response.data; + }, +}; \ No newline at end of file diff --git a/src/modules/shared/components/page-header-template.vue b/src/modules/shared/components/page-header-template.vue index 28045a3..88859e6 100644 --- a/src/modules/shared/components/page-header-template.vue +++ b/src/modules/shared/components/page-header-template.vue @@ -21,13 +21,13 @@ v-if="startDate.length > 0" class="col row flex-center full-width q-py-none q-my-none" > -
+
{{ $d(date.extractDate(startDate, 'YYYY-MM-DD'), date_format_options) }}
{{ $t('shared.misc.to') }}
-
+
{{ $d(date.extractDate(endDate, 'YYYY-MM-DD'), date_format_options) }}
diff --git a/src/modules/shared/components/pay-period-navigator.vue b/src/modules/shared/components/pay-period-navigator.vue index cd24802..86c4268 100644 --- a/src/modules/shared/components/pay-period-navigator.vue +++ b/src/modules/shared/components/pay-period-navigator.vue @@ -5,6 +5,7 @@ const NEXT = 1; const PREVIOUS = -1; + const PAY_PERIOD_DATE_LIMIT = '2023/12/17'; const timesheet_store = useTimesheetStore(); @@ -63,7 +64,7 @@ diff --git a/src/modules/timesheets/components/expense-dialog-form.vue b/src/modules/timesheets/components/expense-dialog-form.vue index 82066ca..5574c57 100644 --- a/src/modules/timesheets/components/expense-dialog-form.vue +++ b/src/modules/timesheets/components/expense-dialog-form.vue @@ -2,14 +2,14 @@ setup lang="ts" > - /* eslint-disable */ import { inject, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import { useExpensesStore } from 'src/stores/expense-store'; - import { empty_expense, EXPENSE_TYPE, TYPES_WITH_AMOUNT_ONLY } from 'src/modules/timesheets/models/expense.models'; + import { Expense, EXPENSE_TYPE, TYPES_WITH_AMOUNT_ONLY } from 'src/modules/timesheets/models/expense.models'; import { useExpenseRules } from 'src/modules/timesheets/utils/expense.util'; import { useExpensesApi } from 'src/modules/timesheets/composables/use-expense-api'; import { useTimesheetStore } from 'src/stores/timesheet-store'; + import { date } from 'quasar'; const { t } = useI18n(); @@ -18,61 +18,79 @@ const expenses_api = useExpensesApi(); const files = defineModel('files'); const is_navigator_open = ref(false); - const mode = ref<'create' | 'update' | 'delete'>('create'); const COMMENT_MAX_LENGTH = 280; const employee_email = inject('employeeEmail'); const rules = useExpenseRules(t); + const openDatePicker = () => { + is_navigator_open.value = true; + if (expenses_store.current_expense.date === '') { + expenses_store.current_expense.date = date.formatDate(new Date(), 'YYYY-MM-DD'); + } + }; + const cancelUpdateMode = () => { - expenses_store.current_expense = empty_expense; - expenses_store.initial_expense = empty_expense; - } + expenses_store.current_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD')); + expenses_store.initial_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD')); + expenses_store.mode = 'create'; + }; const requestExpenseCreationOrUpdate = async () => { - if (mode.value === 'create') await expenses_api.createExpenseByEmployeeEmail(employee_email ?? '', expenses_store.current_expense?.date ?? ''); + if (expenses_store.mode === 'create') await expenses_api.createExpenseByEmployeeEmail(employee_email ?? '', expenses_store.current_expense?.date ?? ''); else await expenses_api.updateExpenseByEmployeeEmail(employee_email ?? '', expenses_store.current_expense?.date ?? ''); + }; + + const getExpenseCalendarRange = (current_date: string) => { + const period = timesheet_store.pay_period; + if (period !== undefined) return current_date >= period.period_start && current_date <= period.period_end; + return false; }