-
-
-
+
+
+
+
+
+
+
+
+
+
+ {{ $t(scope.opt.label) }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t(scope.label) }}
+
+
+
+
+
+
+
+
+ {{ shift.is_remote ? $t('timesheet.shift.types.REMOTE') :
+ $t('timesheet.shift.types.OFFICE') }}
+
+
+
+
+
+ {{ shift.is_remote ? $t('timesheet.shift.types.REMOTE') :
+ $t('timesheet.shift.types.OFFICE') }}
+
+
+
+
{{ $t('shared.misc.in') }}
+ class="text-weight-medium text-uppercase q-px-sm no-pointer-events"
+ :class="$q.dark.isActive ? 'bg-secondary' : 'bg-blue-grey-7'"
+ >
+ {{ $t('timesheet.shift.types.label') }}
+
-
-
+
-
-
-
+
+
+
+ {{ getHoursMinutesStringFromHoursFloat(expectedDailyHours) }}
+
+
+
+
+
+
+
-
- {{ $t('shared.misc.out') }}
-
-
+ :readonly="isApproved"
+ :background-color="isApproved ? 'white' : undefined"
+ :input-text-color="isApproved ? 'accent' : ''"
+ :label="$t('timesheet.expense.employee_comment')"
+ :append-content="isApproved ? '' : `${commentLength ?? 0}/280`"
+ />
-
-
@@ -390,4 +337,13 @@
padding-top: 0;
align-items: center;
}
+
+:deep(.q-field--float .q-field__label) {
+ transform: translate(-17px, -60%) scale(0.75) !important;
+ border-radius: 10px 10px 10px 0px;
+}
+
+:deep(.q-field--auto-height.q-field--labeled .q-field__control-container) {
+ padding: 0;
+}
\ No newline at end of file
diff --git a/src/modules/timesheets/components/mobile/shift-list-mobile.vue b/src/modules/timesheets/components/mobile/shift-list-mobile.vue
index be8c29e..40e6ffe 100644
--- a/src/modules/timesheets/components/mobile/shift-list-mobile.vue
+++ b/src/modules/timesheets/components/mobile/shift-list-mobile.vue
@@ -2,17 +2,12 @@
setup
lang="ts"
>
- import ShiftListDay from 'src/modules/timesheets/components/shift-list-day.vue';
- import ShiftListDateWidget from 'src/modules/timesheets/components/shift-list-date-widget.vue';
+ import ShiftListDayMobile from 'src/modules/timesheets/components/mobile/shift-list-day-mobile.vue';
- import { date, useQuasar } from 'quasar';
- import { ref, computed, watch, onMounted, inject } from 'vue';
- import { useUiStore } from 'src/stores/ui-store';
+
+ import { useQuasar } from 'quasar';
+ import { ref, computed, watch, onMounted } from 'vue';
import { useTimesheetStore } from 'src/stores/timesheet-store';
- import { Shift } from 'src/modules/timesheets/models/shift.models';
- import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
- import type { TimesheetDay } from 'src/modules/timesheets/models/timesheet.models';
- import { useI18n } from 'vue-i18n';
// ========== constants ========================================
@@ -25,16 +20,11 @@
}>();
const q = useQuasar();
- const { extractDate } = date;
- const { locale } = useI18n();
- const uiStore = useUiStore();
- const timesheetApi = useTimesheetApi();
const timesheetStore = useTimesheetStore();
const mobileAnimationDirection = ref('fadeInLeft');
const currentDayComponent = ref
(null);
const currentDayComponentWatcher = ref(currentDayComponent);
- const employeeEmail = inject('employeeEmail');
// ========== computed ========================================
@@ -42,38 +32,10 @@
// ========== methods ========================================
- const addNewShift = (day_shifts: Shift[], date: string, timesheet_id: number) => {
- uiStore.focusNextComponent = true;
- const newShift = new Shift;
- newShift.date = date;
- newShift.timesheet_id = timesheet_id;
- day_shifts.push(newShift);
- };
-
- const getDayApproval = (day: TimesheetDay) => {
- if (day.shifts.length < 1) return false;
- return day.shifts.every(shift => shift.is_approved === true);
- };
-
const getMobileDayRef = (iso_date_string: string): string => {
return iso_date_string === CURRENT_DATE_STRING ? 'currentDayComponent' : '';
};
- const getHolidayName = (date: string) => {
- const holiday = timesheetStore.federal_holidays.find(holiday => holiday.date === date);
- if (!holiday) return;
-
- if (locale.value === 'fr-FR')
- return holiday.nameFr;
-
- else if (locale.value === 'en-CA')
- return holiday.nameEn;
- };
-
- const onClickApplyWeeklyPreset = async (timesheet_id: number) => {
- await timesheetApi.applyPreset(timesheet_id, undefined, undefined, employeeEmail);
- }
-
onMounted(async () => {
await timesheetStore.getCurrentFederalHolidays();
});
@@ -86,166 +48,28 @@
-
+
-
-
-
-
-
-
-
-
- {{ getHolidayName(day.date) }}
-
-
-
-
-
-
- {{ $d(extractDate(day.date, 'YYYY-MM-DD'), {
- weekday: 'long', day: 'numeric', month:
- 'long'
- }) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/src/modules/timesheets/components/shift-list-date-widget.vue b/src/modules/timesheets/components/shift-list-date-widget.vue
index f841d35..1d1405a 100644
--- a/src/modules/timesheets/components/shift-list-date-widget.vue
+++ b/src/modules/timesheets/components/shift-list-date-widget.vue
@@ -24,16 +24,17 @@
-
- {{ $t('shared.label.today') }}
-
+
+
+
+
{{ display_date.getDate() }}
@@ -67,7 +68,8 @@
lang="css"
>
.bordered-text {
- text-shadow: 2px 0 var(--q-dark), -2px 0 var(--q-dark), 0 2px var(--q-dark), 0 -2px var(--q-dark),
- 1px 1px var(--q-dark), -1px -1px var(--q-dark), 1px -1px var(--q-dark), -1px 1px var(--q-dark);
+ text-shadow: 2px 0 white, -2px 0 white, 0 2px white, 0 -2px white,
+ 1px 1px white, -1px -1px white, 1px -1px white, -1px 1px white;
+ font-size: 0.9em;
}
\ No newline at end of file
diff --git a/src/modules/timesheets/components/shift-list-day-row.vue b/src/modules/timesheets/components/shift-list-day-row.vue
index 3cbfa62..87cd998 100644
--- a/src/modules/timesheets/components/shift-list-day-row.vue
+++ b/src/modules/timesheets/components/shift-list-day-row.vue
@@ -3,10 +3,10 @@
lang="ts"
>
import DetailsDialogShiftMenu from 'src/modules/timesheet-approval/components/details-dialog-shift-menu.vue';
+ import TargoInput from 'src/modules/shared/components/targo-input.vue';
- import { useI18n } from 'vue-i18n';
import { computed, inject, onMounted, ref } from 'vue';
- import { QSelect, QInput, useQuasar, type QSelectProps } from 'quasar';
+ import { QSelect, useQuasar, colors, getCssVar } from 'quasar';
import { useUiStore } from 'src/stores/ui-store';
import { useAuthStore } from 'src/stores/auth-store';
import { getCurrentDailyMinutesWorked, getShiftOptions, getTimeStringFromMinutes, SHIFT_OPTIONS } from 'src/modules/timesheets/utils/shift.util';
@@ -23,7 +23,6 @@
const shift = defineModel('shift', { required: true });
const {
- errorMessage = undefined,
isTimesheetApproved = false,
currentShifts,
holiday = false,
@@ -32,7 +31,7 @@
currentShifts: Shift[];
expectedDailyHours?: number;
isTimesheetApproved?: boolean;
- errorMessage?: string | undefined;
+ errorTimesheet?: boolean | undefined;
holiday?: boolean | undefined;
}>();
@@ -42,7 +41,6 @@
}>();
const q = useQuasar();
- const { t } = useI18n();
const uiStore = useUiStore();
const authStore = useAuthStore();
@@ -59,41 +57,7 @@
// ================== Computed ==================
const hasPTO = computed(() => currentShifts.some(shift => SHIFT_TYPES_WITH_PREDEFINED_TIMES.includes(shift.type)));
-
- const timeInputProps = computed(() => ({
- dense: true,
- borderless: shift.value.is_approved && isTimesheetApproved,
- readonly: shift.value.is_approved && isTimesheetApproved,
- standout: q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9',
- labelSlot: true,
- lazyRules: true,
- noErrorIcon: true,
- hideBottomSpace: true,
- error: shift.value.has_error,
- errorMessage: errorMessage ? t(errorMessage) : (shiftErrorMessage.value ? t(shiftErrorMessage.value) : undefined),
- labelColor: shift.value.is_approved ? 'white' : (holiday ? 'purple-5' : 'accent'),
- class: `col rounded-5 bg-dark q-mx-xs ${shift.value.id === -2 ? 'bg-negative' : ''} ${shift.value.is_approved || isTimesheetApproved ? 'cursor-not-allowed inset-shadow' : ''}`,
- inputClass: `text-weight-medium ${shift.value.id === -2 ? 'text-white ' : ' '} ${shift.value.is_approved ? 'text-white cursor-not-allowed q-px-sm' : ''}`,
- style: shift.value.is_approved ? (holiday ? 'background-color: #7b1fa2 !important' : 'background-color: #0a7d32 !important;') : '',
- inputStyle: "font-size: 1.2em;"
- }));
-
- const shiftTypeSelectProps = computed>(() => ({
- standout: q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9',
- dense: true,
- borderless: shift.value.is_approved && isTimesheetApproved,
- readonly: shift.value.is_approved && isTimesheetApproved,
- optionsDense: !q.platform.is.mobile,
- hideDropdownIcon: true,
- menuOffset: [0, 10],
- menuAnchor: "bottom middle",
- menuSelf: "top middle",
- options: getShiftOptions(hasPTO.value, currentShifts.length > 1),
- class: `col rounded-5 q-mx-xs bg-dark ${!shift.value.is_approved && !isTimesheetApproved ? '' : 'inset-shadow'}`,
- popupContentClass: "text-uppercase text-weight-bold text-center rounded-5",
- style: shift.value.is_approved ? (holiday ? 'background-color: #7b1fa2 !important' : 'background-color: #0a7d32 !important;') : '',
- popupContentStyle: "border: 2px solid var(--q-accent)",
- }));
+ const isApproved = computed(() => isTimesheetApproved || shift.value.is_approved);
// ================== Methods ==================
@@ -170,7 +134,7 @@
-
+
+
{{ $t(scope.opt.label) }}
@@ -278,7 +261,7 @@
:disable="shift.is_approved"
dense
keep-color
- size="3em"
+ size="2.75em"
:color="holiday ? 'purple-5' : 'accent'"
icon="las la-building"
checked-icon="las la-laptop"
@@ -294,6 +277,15 @@
+
+
+
+ {{ $t('timesheet.shift.types.label') }}
+
+
@@ -319,38 +311,32 @@
v-else
class="col row items-start text-uppercase rounded-5 q-pa-xs"
>
-
-
- {{ $t('shared.misc.in') }}
-
-
+ />
-
-
- {{ $t('shared.misc.out') }}
-
-
+ />
padding-top: 0;
align-items: center;
}
+
+:deep(.q-field--dense.q-field--float .q-field__label) {
+ transform: translate(-17px, -60%) scale(0.75) !important;
+ border-radius: 10px 10px 10px 0px;
+}
\ No newline at end of file
diff --git a/src/modules/timesheets/components/shift-list-day.vue b/src/modules/timesheets/components/shift-list-day.vue
index 9c8f891..70aed74 100644
--- a/src/modules/timesheets/components/shift-list-day.vue
+++ b/src/modules/timesheets/components/shift-list-day.vue
@@ -95,24 +95,25 @@
-
+
{{ getHolidayName(day.date) }}
@@ -134,7 +135,7 @@
leave-active-class="animated zoomOut fast"
>
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/src/modules/timesheets/components/shift-list.vue b/src/modules/timesheets/components/shift-list.vue
index aa142b1..d5ea723 100644
--- a/src/modules/timesheets/components/shift-list.vue
+++ b/src/modules/timesheets/components/shift-list.vue
@@ -10,10 +10,6 @@
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
import { TimesheetDayDisplay } from 'src/modules/timesheets/models/timesheet.models';
- // ========== constants ========================================
-
- const CURRENT_DATE_STRING = new Date().toISOString().slice(0, 10);
-
// ========== state ========================================
const emit = defineEmits<{
@@ -96,15 +92,14 @@
>
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 weeklyHours = computed(() => timesheetStore.timesheets.map(timesheet =>
+ timesheet.days.reduce((daySum: number, day: TimesheetDay) => {
+ return daySum + day.shifts.reduce((shiftSum: number, shift: Shift) => {
+ if (!shift.end_time || !shift.start_time || shift.type === 'SICK') return shiftSum;
+
+ const time = getHoursMinutesBetweenTwoHHmm(shift.start_time, shift.end_time);
+ return shiftSum + time.hours + Number(time.minutes / 60);
+ }, 0)
+ }, 0)
));
- 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,
+
+ const totalHours = computed(() => weeklyHours.value.reduce((sum, week) =>
+ sum += week,
0 //initial value
));
@@ -82,7 +89,7 @@ import { RouteNames } from 'src/router/router-constants';
timesheetStore.isShowingUnsavedWarning = false;
timesheetStore.timesheets = [];
timesheetStore.initial_timesheets = [];
- await router.push({ name: timesheetStore.nextPageNameAfterUnsaveWarning ?? RouteNames.DASHBOARD});
+ await router.push({ name: timesheetStore.nextPageNameAfterUnsaveWarning ?? RouteNames.DASHBOARD });
}
const onClickSaveBeforeLeaving = async () => {
@@ -232,7 +239,7 @@ import { RouteNames } from 'src/router/router-constants';
style="min-height: 20vh;"
>
{{ $t('shared.error.no_data_found')
- }}
+ }}
{
const success = await expenses_store.upsertExpense(expense, employee_email);
if (success) {
- expenses_store.current_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
await timesheet_store.getTimesheetsByOptionalEmployeeEmail(employee_email);
return true;
diff --git a/src/modules/timesheets/models/expense.models.ts b/src/modules/timesheets/models/expense.models.ts
index 67a7b4b..e60a452 100644
--- a/src/modules/timesheets/models/expense.models.ts
+++ b/src/modules/timesheets/models/expense.models.ts
@@ -1,4 +1,4 @@
-export type ExpenseType = 'PER_DIEM' | 'MILEAGE' | 'EXPENSES' | 'ON_CALL';
+export type ExpenseType = 'PER_DIEM' | 'MILEAGE' | 'EXPENSES' | 'ON_CALL' | undefined;
export const EXPENSE_TYPE: ExpenseType[] = ['PER_DIEM', 'MILEAGE', 'EXPENSES', 'ON_CALL',];
export const TYPES_WITH_MILEAGE_ONLY: Readonly = ['MILEAGE'];
@@ -9,8 +9,8 @@ export class Expense {
timesheet_id: number;
date: string; //YYYY-MM-DD
type: ExpenseType;
- amount: number;
- mileage?: number;
+ amount?: number | null;
+ mileage?: number | null;
attachment_name?: string;
attachment_key?: string;
comment: string;
@@ -21,7 +21,7 @@ export class Expense {
this.id = -1;
this.timesheet_id = -1;
this.date = date;
- this.type = 'EXPENSES';
+ this.type = undefined;
this.amount = 0;
this.comment = '';
this.is_approved = false;
diff --git a/src/modules/timesheets/utils/expense.util.ts b/src/modules/timesheets/utils/expense.util.ts
index 5b97777..e6a202b 100644
--- a/src/modules/timesheets/utils/expense.util.ts
+++ b/src/modules/timesheets/utils/expense.util.ts
@@ -10,12 +10,12 @@ export const getExpenseIcon = (type: ExpenseType) => {
}
};
-export const useExpenseRules = (t: (_key: string) => string) => {
+export const useExpenseRules = () => {
const isPresent = (val: unknown) => val !== undefined && val !== null && val !== '';
- const typeRequired = (val: unknown) => (!!val) || t('timesheet.expense.errors.type_required');
- const amountRequired = (val: unknown) => (isPresent(val)) || t('timesheet.expense.errors.amount_required_for_type');
- const mileageRequired = (val: unknown) => (isPresent(val)) || t('timesheet.expense.errors.mileage_required_for_type');
- const commentRequired = (val: unknown) => (typeof val === 'string' ? val.trim().length > 0 : false) || t('timesheet.expense.hints.comment_required');
+ const typeRequired = (val: unknown) => isPresent(val);
+ const amountRequired = (val: number | null | undefined) => isPresent(val) && !!val && val > 0;
+ const mileageRequired = (val: number | null | undefined) => isPresent(val) && !!val && val > 0;
+ const commentRequired = (val: string | null | undefined) => typeof val === 'string' ? val.trim().length > 0 : false;
return {
typeRequired,