Merge pull request 'dev/nicolas/timesheet-validation-implementation' (#28) from dev/nicolas/timesheet-validation-implementation into main
Reviewed-on: Targo/targo_frontend#28
This commit is contained in:
commit
4c79820128
|
|
@ -191,6 +191,8 @@ export default {
|
|||
INVALID_SHIFT_TIME: "In and Out shift times are reversed",
|
||||
SHIFT_OVERLAP: "An overlaps occured between 2 or more shifts",
|
||||
INVALID_SHIFT: "A shift contains missing or corrupted data",
|
||||
SHIFT_TIME_REQUIRED: "Valid time required",
|
||||
SHIFT_TYPE_REQUIRED: "Shift type required",
|
||||
SHIFT_NOT_FOUND: "Shift missing or deleted",
|
||||
PAY_PERIOD_NOT_FOUND: "No pay period matching given dates",
|
||||
EMPLOYEE_NOT_FOUND: "No employee matching current login details",
|
||||
|
|
|
|||
|
|
@ -192,6 +192,8 @@ export default {
|
|||
INVALID_SHIFT_TIME: "Les heures d'entrée et de sortie sont inversées",
|
||||
SHIFT_OVERLAP: "Il y a un chevauchement entre deux ou plusieurs quarts",
|
||||
INVALID_SHIFT: "Un quart de travail contient des données manquantes ou corrompues",
|
||||
SHIFT_TIME_REQUIRED: "Heure requise",
|
||||
SHIFT_TYPE_REQUIRED: "Type requis",
|
||||
SHIFT_NOT_FOUND: "Quart de travail manquant ou supprimé",
|
||||
PAY_PERIOD_NOT_FOUND: "Aucune période de paie ne correspond aux dates fournies",
|
||||
EMPLOYEE_NOT_FOUND: "Aucun employé ne correspond aux détails de votre connexion",
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@
|
|||
@hide="is_dialog_open = false"
|
||||
>
|
||||
<q-card
|
||||
class="shadow-12 rounded-15 column no-wrap relative bg-secondary hide-scrollbar"
|
||||
class="shadow-12 rounded-15 bg-secondary hide-scrollbar"
|
||||
:style="($q.screen.lt.md ? '' : 'width:80vw !important;') + ($q.dark.isActive ? ' border: 2px solid var(--q-accent)' : '')"
|
||||
>
|
||||
|
||||
<!-- employee name -->
|
||||
<q-card-section class="col-auto text-h4 text-weight-bolder text-center text-uppercase q-px-none q-py-sm">
|
||||
<q-card-section class="text-h4 text-weight-bolder text-center text-uppercase q-px-none q-py-sm">
|
||||
<span>{{ timesheet_store.selected_employee_name }}</span>
|
||||
</q-card-section>
|
||||
|
||||
|
|
@ -39,24 +39,22 @@
|
|||
<q-card-section
|
||||
v-if="is_dialog_open"
|
||||
:horizontal="!$q.screen.lt.md"
|
||||
class="col-auto q-px-md rounded-10 no-wrap"
|
||||
class="q-px-md rounded-10 no-wrap"
|
||||
>
|
||||
<DetailsDialogChartHoursWorked class="col" />
|
||||
<DetailsDialogChartShiftTypes class="col q-ma-lg" />
|
||||
<DetailsDialogChartExpenses class="col" />
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section class="col-auto">
|
||||
<q-card-section>
|
||||
<ExpenseDialogList />
|
||||
</q-card-section>
|
||||
|
||||
<!-- list of shifts -->
|
||||
<q-card-section
|
||||
:horizontal="$q.screen.gt.sm"
|
||||
class="col-auto q-px-sm rounded-5 no-wrap"
|
||||
>
|
||||
<q-card-section class="col-auto">
|
||||
<TimesheetWrapper mode="approval" />
|
||||
</q-card-section>
|
||||
<q-separator />
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
|
@ -4,8 +4,8 @@ import type { PayPeriodOverviewResponse } from "src/modules/timesheet-approval/m
|
|||
|
||||
export const timesheetApprovalService = {
|
||||
getPayPeriodOverviews: async (year: number, period_number: number): Promise<PayPeriodOverviewResponse> => {
|
||||
const response = await api.get(`pay-periods/overview/${year}/${period_number}`);
|
||||
return response.data;
|
||||
const response = await api.get<{success: boolean, data: PayPeriodOverviewResponse, error? : string}>(`pay-periods/overview/${year}/${period_number}`);
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
getPayPeriodReportByYearAndPeriodNumber: async (year: number, period_number: number, report_filters?: TimesheetApprovalCSVReportFilters) => {
|
||||
|
|
|
|||
|
|
@ -3,21 +3,17 @@
|
|||
lang="ts"
|
||||
>
|
||||
/* eslint-disable*/
|
||||
import { onBeforeUnmount, onMounted, ref, useTemplateRef } from 'vue';
|
||||
import { onBeforeUnmount, onMounted, ref, useTemplateRef, watch, nextTick } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { QSelect } from 'quasar';
|
||||
import { Shift, ShiftType } from 'src/modules/timesheets/models/shift.models';
|
||||
import { QSelect, QInput } from 'quasar';
|
||||
import { Shift, type ShiftOption } from 'src/modules/timesheets/models/shift.models';
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import { useShiftRules } from 'src/modules/timesheets/utils/shift.util';
|
||||
|
||||
let timer: NodeJS.Timeout;
|
||||
const { t } = useI18n();
|
||||
const ui_store = useUiStore();
|
||||
|
||||
interface ShiftOption {
|
||||
label: string;
|
||||
value: ShiftType;
|
||||
icon: string;
|
||||
icon_color: string;
|
||||
}
|
||||
const shift_rules = useShiftRules(t('timesheet.errors.SHIFT_TIME_REQUIRED'),);
|
||||
|
||||
const COMMENT_LENGTH_MAX = 280;
|
||||
const SHIFT_OPTIONS: ShiftOption[] = [
|
||||
|
|
@ -28,8 +24,12 @@
|
|||
{ label: t('timesheet.shift.types.HOLIDAY'), value: 'HOLIDAY', icon: 'forest', icon_color: 'green-8' },
|
||||
{ label: t('timesheet.shift.types.SICK'), value: 'SICK', icon: 'medication_liquid', icon_color: 'light-blue-6' },
|
||||
];
|
||||
|
||||
const shift = defineModel<Shift>('shift', { required: true });
|
||||
const shift_type_selected = ref(SHIFT_OPTIONS.find(option => option.value == shift.value.type));
|
||||
const select_ref = useTemplateRef<QSelect>('select');
|
||||
const start_time_ref = useTemplateRef<QInput>('start_time');
|
||||
const end_time_ref = useTemplateRef<QInput>('end_time');
|
||||
|
||||
const { dense = false, hasShiftAfter = false, isTimesheetApproved = false } = defineProps<{
|
||||
dense?: boolean;
|
||||
hasShiftAfter?: boolean;
|
||||
|
|
@ -41,12 +41,6 @@
|
|||
'requestDelete': [void];
|
||||
}>();
|
||||
|
||||
const select_ref = useTemplateRef<QSelect>('select');
|
||||
|
||||
let timer: NodeJS.Timeout;
|
||||
|
||||
const shift_type_selected = ref(SHIFT_OPTIONS.find(option => option.value == shift.value.type));
|
||||
|
||||
const onBlurShiftTypeSelect = () => {
|
||||
if (shift_type_selected.value === undefined) {
|
||||
shift.value.type = 'REGULAR';
|
||||
|
|
@ -80,6 +74,10 @@
|
|||
onBeforeUnmount(() => {
|
||||
clearTimeout(timer);
|
||||
});
|
||||
|
||||
watch(() => [start_time_ref.value?.hasError, end_time_ref.value?.hasError], ([start_error, end_error]) => {
|
||||
shift.value.has_error = (start_error || end_error) ?? false;
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -98,8 +96,8 @@
|
|||
|
||||
<div :class="ui_store.is_mobile_mode ? 'column' : 'row'">
|
||||
<div
|
||||
class="row items-center text-uppercase rounded-5"
|
||||
:class="ui_store.is_mobile_mode ? 'col q-mb-xs' : 'col-4'"
|
||||
class="row items-start text-uppercase rounded-5"
|
||||
:class="ui_store.is_mobile_mode ? 'col q-mb-xs q-px-xs' : 'col-4'"
|
||||
>
|
||||
<!-- mobile comment button -->
|
||||
<q-btn
|
||||
|
|
@ -168,10 +166,10 @@
|
|||
menu-anchor="bottom middle"
|
||||
menu-self="top middle"
|
||||
:options="SHIFT_OPTIONS"
|
||||
class="col rounded-5 q-mx-xs bg-dark"
|
||||
class="col rounded-5 q-mx-xs bg-dark q-pt-xs"
|
||||
:class="!shift.is_approved && !isTimesheetApproved ? '' : 'inset-shadow'"
|
||||
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
||||
popup-content-class="text-uppercase text-weight-bold text-center rounded-5"
|
||||
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
||||
popup-content-style="border: 2px solid var(--q-accent)"
|
||||
@blur="onBlurShiftTypeSelect"
|
||||
@update:model-value="option => shift.type = option.value"
|
||||
|
|
@ -198,9 +196,10 @@
|
|||
</q-select>
|
||||
</div>
|
||||
|
||||
<div class="col row flex-center text-uppercase rounded-5 q-pa-xs">
|
||||
<div class="col row items-start text-uppercase rounded-5 q-pa-xs">
|
||||
<!-- punch in field -->
|
||||
<q-input
|
||||
ref="start_time"
|
||||
v-model="shift.start_time"
|
||||
dense
|
||||
:borderless="(shift.is_approved && isTimesheetApproved)"
|
||||
|
|
@ -208,11 +207,15 @@
|
|||
type="time"
|
||||
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
||||
label-slot
|
||||
lazy-rules
|
||||
no-error-icon
|
||||
hide-bottom-space
|
||||
:rules="[shift_rules.isTimeRequired]"
|
||||
:label-color="!shift.is_approved ? 'accent' : 'white'"
|
||||
:input-class="'text-weight-medium ' + (shift.id === -2 ? 'text-white ' : ' ') + (shift.is_approved ? 'text-white cursor-not-allowed q-px-sm' : '')"
|
||||
input-style="font-size: 1.2em;"
|
||||
class="col rounded-5 bg-dark"
|
||||
:class="(shift.id === -2 ? 'bg-negative ' : ' ') + (ui_store.is_mobile_mode ? 'q-mr-xs ' : 'q-mx-xs ') + (!shift.is_approved && !isTimesheetApproved ? '' : 'cursor-not-allowed inset-shadow')"
|
||||
:input-class="'text-weight-medium ' + (shift.id === -2 ? 'text-white ' : ' ') + (shift.is_approved ? 'text-white cursor-not-allowed q-px-sm' : '')"
|
||||
input-style="font-size: 1.2em;"
|
||||
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
||||
>
|
||||
<template #label>
|
||||
|
|
@ -226,6 +229,7 @@
|
|||
|
||||
<!-- punch out field -->
|
||||
<q-input
|
||||
ref="end_time"
|
||||
v-model="shift.end_time"
|
||||
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
||||
dense
|
||||
|
|
@ -233,6 +237,10 @@
|
|||
:readonly="(shift.is_approved && isTimesheetApproved)"
|
||||
type="time"
|
||||
label-slot
|
||||
lazy-rules
|
||||
no-error-icon
|
||||
hide-bottom-space
|
||||
:rules="[shift_rules.isTimeRequired]"
|
||||
:label-color="!shift.is_approved ? 'accent' : 'white'"
|
||||
:input-class="'text-weight-medium ' + (shift.id === -2 ? 'text-white ' : ' ') + (shift.is_approved ? 'text-white cursor-not-allowed q-px-sm' : '')"
|
||||
input-style="font-size: 1.2em;"
|
||||
|
|
@ -250,7 +258,7 @@
|
|||
</q-input>
|
||||
|
||||
<!-- comment and delete buttons -->
|
||||
<div :class="ui_store.is_mobile_mode ? 'col-12 row' : 'col-auto'">
|
||||
<div :class="ui_store.is_mobile_mode ? 'col-12 row' : 'col-auto self-start'">
|
||||
<q-icon
|
||||
v-if="shift.type && dense"
|
||||
:name="shift.comment ? 'comment' : ''"
|
||||
|
|
@ -328,7 +336,7 @@
|
|||
:class="shift.is_approved ? 'invisible' : ''"
|
||||
@click="$emit('requestDelete')"
|
||||
>
|
||||
<q-badge
|
||||
<q-badge
|
||||
v-if="!shift.is_approved"
|
||||
color="white"
|
||||
class="absolute"
|
||||
|
|
@ -346,4 +354,18 @@
|
|||
color="accent"
|
||||
class="q-mx-md"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.q-field--error) {
|
||||
background-color: var(--q-negative) !important;
|
||||
}
|
||||
|
||||
:deep(.q-field--error .q-field__bottom) {
|
||||
color: white;
|
||||
font-weight: 900;
|
||||
border-radius: 0 0 5px 5px;
|
||||
padding-top: 0;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -2,31 +2,34 @@
|
|||
setup
|
||||
lang="ts"
|
||||
>
|
||||
/* eslint-disable */
|
||||
import ShiftList from 'src/modules/timesheets/components/shift-list.vue';
|
||||
import ExpenseDialog from 'src/modules/timesheets/components/expense-dialog.vue';
|
||||
import PayPeriodNavigator from 'src/modules/shared/components/pay-period-navigator.vue';
|
||||
import TimesheetErrorWidget from 'src/modules/timesheets/components/timesheet-error-widget.vue';
|
||||
import LoadingOverlay from 'src/modules/shared/components/loading-overlay.vue';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
|
||||
import { computed } from 'vue';
|
||||
import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
|
||||
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
|
||||
import { useExpensesStore } from 'src/stores/expense-store';
|
||||
import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
|
||||
|
||||
const expenses_store = useExpensesStore();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const timesheet_api = useTimesheetApi();
|
||||
const shift_api = useShiftApi();
|
||||
const has_shift_errors = computed(() => timesheet_store.all_current_shifts.filter(shift => shift.has_error === true).length > 0);
|
||||
|
||||
const { mode = 'normal' } = defineProps<{
|
||||
mode?: 'approval' | 'normal';
|
||||
}>();
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="column flex-center full-width"
|
||||
:class="mode === 'approval' ? 'bg-dark q-px-sm q-pb-sm q-mb-md rounded-10 shadow-10' : ''"
|
||||
>
|
||||
<div class="column flex-center full-width">
|
||||
<LoadingOverlay v-model="timesheet_store.is_loading" />
|
||||
|
||||
<q-card
|
||||
|
|
@ -65,8 +68,8 @@
|
|||
v-if="mode === 'normal'"
|
||||
push
|
||||
rounded
|
||||
:disable="timesheet_store.is_loading"
|
||||
color="accent"
|
||||
:disable="timesheet_store.is_loading || has_shift_errors"
|
||||
:color="timesheet_store.is_loading || has_shift_errors ? 'grey-5' : 'accent'"
|
||||
icon="upload"
|
||||
:label="$t('shared.label.save')"
|
||||
class="q-mr-md"
|
||||
|
|
@ -92,11 +95,7 @@
|
|||
|
||||
<ShiftList :mode="mode" />
|
||||
|
||||
<q-card-section
|
||||
horizontal
|
||||
class="q-my-md"
|
||||
>
|
||||
<q-space />
|
||||
<q-card-actions align="right">
|
||||
<q-btn
|
||||
v-if="mode === 'approval'"
|
||||
push
|
||||
|
|
@ -108,7 +107,7 @@
|
|||
class="q-mr-md"
|
||||
@click="shift_api.saveShiftChanges"
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
<ExpenseDialog />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,78 +0,0 @@
|
|||
// import { type Expense, EXPENSE_TYPE, type ExpenseType } from "src/modules/timesheets/models/expense.models";
|
||||
// import type { Normalizer } from "src/utils/normalize-object";
|
||||
|
||||
// export interface ApiErrorPayload {
|
||||
// status_code: number;
|
||||
// error_code?: string;
|
||||
// message?: string;
|
||||
// context?: Record<string, unknown>;
|
||||
// };
|
||||
|
||||
// export abstract class ApiError extends Error {
|
||||
// status_code: number;
|
||||
// error_code?: string;
|
||||
// context?: Record<string, unknown>;
|
||||
|
||||
// constructor(payload: ApiErrorPayload, defaultMessage: string) {
|
||||
// super(payload.message || defaultMessage);
|
||||
// this.status_code = payload.status_code;
|
||||
// this.error_code = payload.error_code ?? "unknown";
|
||||
// this.context = payload.context ?? {'unknown': 'unknown error has occured', };
|
||||
// }
|
||||
// };
|
||||
|
||||
// export class GenericApiError extends ApiError {
|
||||
// constructor(payload: ApiErrorPayload) {
|
||||
// super(payload, 'Encountered an error processing request');
|
||||
// this.name = 'GenericApiError';
|
||||
// }
|
||||
// };
|
||||
|
||||
// export class ExpensesValidationError extends ApiError {
|
||||
// constructor(payload: ApiErrorPayload) {
|
||||
// super(payload, 'Invalid expense payload');
|
||||
// this.name = 'ExpensesValidationError';
|
||||
// }
|
||||
// };
|
||||
|
||||
// export class ExpensesApiError extends ApiError {
|
||||
// constructor(payload: ApiErrorPayload) {
|
||||
// super(payload, 'Request failed');
|
||||
// this.name = 'ExpensesApiError';
|
||||
// }
|
||||
// };
|
||||
|
||||
// export const expense_validation_schema: Normalizer<Expense> = {
|
||||
// id: v => typeof v === 'number' ? v : -1,
|
||||
// date: v => typeof v === 'string' ? v.trim() : '1970-01-01',
|
||||
// type: v => EXPENSE_TYPE.includes(v as ExpenseType) ? v as ExpenseType : "EXPENSES",
|
||||
// amount: v => typeof v === "number" ? v : -1,
|
||||
// mileage: v => typeof v === "number" ? v : undefined,
|
||||
// comment: v => typeof v === 'string' ? v.trim() : '',
|
||||
// supervisor_comment: v => typeof v === 'string' ? v.trim() : '',
|
||||
// is_approved: v => !!v,
|
||||
// };
|
||||
|
||||
// export function toExpensesError(err: unknown): ExpensesValidationError | ExpensesApiError {
|
||||
// if (err instanceof ExpensesValidationError || err instanceof ExpensesApiError) {
|
||||
// return err;
|
||||
// }
|
||||
|
||||
// if (typeof err === 'object' && err !== null && 'status_code' in err) {
|
||||
// const payload = err as ApiErrorPayload;
|
||||
|
||||
// // Don't know how to differentiate both types of errors, can be updated here
|
||||
// if (payload.error_code?.startsWith('API_')) {
|
||||
// return new ExpensesApiError(payload);
|
||||
// }
|
||||
|
||||
// return new ExpensesValidationError(payload);
|
||||
// }
|
||||
|
||||
// // Fallback with ValidationError as default
|
||||
// return new ExpensesValidationError({
|
||||
// status_code: 500,
|
||||
// message: err instanceof Error ? err.message : 'Unknown error',
|
||||
// context: { original: err }
|
||||
// });
|
||||
// }
|
||||
|
|
@ -28,6 +28,7 @@ export class Shift {
|
|||
comment: string | undefined;
|
||||
is_approved: boolean;
|
||||
is_remote: boolean;
|
||||
has_error: boolean;
|
||||
|
||||
constructor() {
|
||||
this.id = -1;
|
||||
|
|
@ -39,9 +40,17 @@ export class Shift {
|
|||
this.comment = undefined;
|
||||
this.is_approved = false;
|
||||
this.is_remote = false;
|
||||
this.has_error = false;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ShiftOption {
|
||||
label: string;
|
||||
value: ShiftType;
|
||||
icon: string;
|
||||
icon_color: string;
|
||||
}
|
||||
|
||||
export interface ShiftAPIResponse {
|
||||
ok: boolean;
|
||||
data?: {
|
||||
|
|
|
|||
|
|
@ -5,18 +5,18 @@ import type { TimesheetOverview } from "src/modules/timesheet-approval/models/ti
|
|||
|
||||
export const timesheetService = {
|
||||
getPayPeriodByDate: async (date_string: string): Promise<PayPeriod> => {
|
||||
const response = await api.get(`pay-periods/date/${date_string}`);
|
||||
return response.data;
|
||||
const response = await api.get<{success: boolean, data: PayPeriod, error? : string}>(`pay-periods/date/${date_string}`);
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
getPayPeriodByYearAndPeriodNumber: async (year: number, period_number: number): Promise<PayPeriod> => {
|
||||
const response = await api.get(`pay-periods/${year}/${period_number}`);
|
||||
return response.data;
|
||||
const response = await api.get<{success: boolean, data: PayPeriod, error? : string}>(`pay-periods/${year}/${period_number}`);
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
getTimesheetOverviewsByPayPeriodAndSupervisorEmail: async (year: number, period_number: number, supervisor_email: string): Promise<TimesheetOverview[]> => {
|
||||
const response = await api.get(`pay-periods/${year}/${period_number}/${supervisor_email}`);
|
||||
return response.data;
|
||||
const response = await api.get<{success: boolean, data: TimesheetOverview[], error? : string}>(`pay-periods/${year}/${period_number}/${supervisor_email}`);
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
getTimesheetsByPayPeriodAndOptionalEmail: async (year: number, period_number: number, employee_email?: string): Promise<TimesheetResponse> => {
|
||||
|
|
|
|||
34
src/modules/timesheets/utils/shift.util.ts
Normal file
34
src/modules/timesheets/utils/shift.util.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { date, patterns, type ValidationRule } from "quasar";
|
||||
import type { Shift } from "src/modules/timesheets/models/shift.models";
|
||||
|
||||
export const isShiftOverlap = (shifts: Shift[]): boolean => {
|
||||
if (shifts.length < 2) return false;
|
||||
|
||||
const parsed_shifts = shifts.map(shift => ({
|
||||
start: date.extractDate(`${shift.date} ${shift.start_time}`, 'YYYY-MM-DD HH:mm').getTime(),
|
||||
end: date.extractDate(`${shift.date} ${shift.end_time}`, 'YYYY-MM-DD HH:mm').getTime(),
|
||||
}));
|
||||
|
||||
for (let i = 0; i < parsed_shifts.length; i++) {
|
||||
for (let j = i + 1; j < parsed_shifts.length; j++) {
|
||||
const parsed_shift_a = parsed_shifts[i];
|
||||
const parsed_shift_b = parsed_shifts[j];
|
||||
|
||||
if (parsed_shift_a === undefined || parsed_shift_b === undefined) continue;
|
||||
|
||||
if (Math.max(parsed_shift_a.start, parsed_shift_b.start) < Math.min(parsed_shift_a.end, parsed_shift_b.end)) {
|
||||
return true; // overlap found
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const useShiftRules = (time_required_error: string) => {
|
||||
const isTimeRequired: ValidationRule<string> = (time_string: string) => (!!time_string && patterns.testPattern.time(time_string)) || time_required_error;
|
||||
|
||||
return {
|
||||
isTimeRequired,
|
||||
};
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable */
|
||||
import { ref } from "vue";
|
||||
import { Notify } from "quasar";
|
||||
import { defineStore } from "pinia";
|
||||
|
|
@ -20,13 +21,12 @@ export const useShiftStore = defineStore('shift_store', () => {
|
|||
};
|
||||
|
||||
const createNewShifts = async (): Promise<boolean> => {
|
||||
if (timesheet_store.timesheets === undefined) {
|
||||
console.log('no changes in existing shifts detected');
|
||||
return false;
|
||||
}
|
||||
if (timesheet_store.timesheets === undefined) return false;
|
||||
const has_errors = false;
|
||||
|
||||
try {
|
||||
const new_shifts = timesheet_store.timesheets.flatMap(week => week.days).flatMap(day => day.shifts).filter(shift => shift.id < 0);
|
||||
const days = timesheet_store.timesheets.flatMap(week => week.days);
|
||||
const new_shifts = days.flatMap(day => day.shifts).filter(shift => shift.id < 0);
|
||||
|
||||
if (new_shifts?.length > 0) {
|
||||
const response = await ShiftService.createNewShifts(new_shifts);
|
||||
|
|
@ -41,7 +41,7 @@ export const useShiftStore = defineStore('shift_store', () => {
|
|||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Error creating new shifts: ', error);
|
||||
Notify.create('Error creating new shifts');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
@ -60,11 +60,10 @@ export const useShiftStore = defineStore('shift_store', () => {
|
|||
}
|
||||
}
|
||||
|
||||
console.log('No shifts to update');
|
||||
Notify.create('no shifts to update')
|
||||
Notify.create('No shifts to update')
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Error updating shifts: ', error);
|
||||
Notify.create('Error updating shifts');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
||||
import { timesheetApprovalService } from 'src/modules/timesheet-approval/services/timesheet-approval-service';
|
||||
|
|
@ -13,6 +13,7 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
|||
const is_loading = ref<boolean>(false);
|
||||
const pay_period = ref<PayPeriod>();
|
||||
const timesheets = ref<Timesheet[]>([]);
|
||||
const all_current_shifts = computed(() => timesheets.value.flatMap(week => week.days.flatMap(day => day.shifts)) ?? []);
|
||||
const initial_timesheets = ref<Timesheet[]>([]);
|
||||
|
||||
const pay_period_overviews = ref<TimesheetOverview[]>([]);
|
||||
|
|
@ -69,6 +70,7 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
|||
|
||||
const getTimesheetsByOptionalEmployeeEmail = async (employee_email?: string) => {
|
||||
if (pay_period.value === undefined) return;
|
||||
console.log('pay period: ', pay_period.value);
|
||||
is_loading.value = true;
|
||||
let response;
|
||||
|
||||
|
|
@ -118,6 +120,7 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
|||
current_pay_period_overview,
|
||||
selected_employee_name,
|
||||
timesheets,
|
||||
all_current_shifts,
|
||||
initial_timesheets,
|
||||
getPayPeriodByDateOrYearAndNumber,
|
||||
getTimesheetOverviews,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user