+
{{ $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;
}
-
+
{{ $t('timesheet.expense.add_expense') }}
-
-
+
-
+
-
+
+
@@ -82,19 +100,25 @@
-
+
-
+ >
+
+
+ {{ $t('timesheet.expense.amount') }}
+
+
+
+
-
+
-
+ >
+
+
+ {{ $t('timesheet.expense.mileage') }}
+
+
+
+
-
- {{ $t('timesheet.expense.comment') }}
+
+ {{ $t('timesheet.expense.employee_comment') }}
@@ -156,14 +191,14 @@
+
+
\ No newline at end of file
diff --git a/src/modules/timesheets/components/expense-dialog-header.vue b/src/modules/timesheets/components/expense-dialog-header.vue
index 02e3753..a34a182 100644
--- a/src/modules/timesheets/components/expense-dialog-header.vue
+++ b/src/modules/timesheets/components/expense-dialog-header.vue
@@ -2,43 +2,91 @@
setup
lang="ts"
>
- /* eslint-disable */
+ import { computed } from 'vue';
import { useExpensesStore } from 'src/stores/expense-store';
+ import { useTimesheetStore } from 'src/stores/timesheet-store';
+ const timesheet_store = useTimesheetStore();
const expense_store = useExpensesStore();
+ const weekly_totals = computed(() => {
+ let expenses = 0;
+ let mileage = 0;
+ timesheet_store.timesheets.forEach(timesheet => {
+ expenses += timesheet.weekly_expenses.expenses ?? 0;
+ mileage += timesheet.weekly_expenses.mileage ?? 0;
+ });
+
+ return { expenses, mileage };
+ });
-
-
- {{ $t('timesheet.expense.title') }}
-
+
+
+
+ {{ $t('timesheet.expense.title') }}
+
-
-
+
+
+
+
+
+ {{ $t('timesheet.expense.total_amount') }} :
+
+
+
+
+
+ {{ weekly_totals.expenses.toFixed(2) }}
+
+
+
+
+
+ {{ $t('timesheet.expense.total_mileage') }} :
+
+
+
+
+
+ {{ weekly_totals.mileage.toFixed(1) }}
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/timesheets/components/expense-dialog-list-item.vue b/src/modules/timesheets/components/expense-dialog-list-item.vue
index 2537aaa..83f85c8 100644
--- a/src/modules/timesheets/components/expense-dialog-list-item.vue
+++ b/src/modules/timesheets/components/expense-dialog-list-item.vue
@@ -2,60 +2,56 @@
setup
lang="ts"
>
- /* eslint-disable */
- import { computed, inject, ref } from 'vue';
+ import { date } from 'quasar';
+ import { computed, ref } from 'vue';
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
+ import { deepEqual } from 'src/utils/deep-equal';
import { useExpensesApi } from 'src/modules/timesheets/composables/use-expense-api';
import { useExpensesStore } from 'src/stores/expense-store';
- import { useTimesheetStore } from 'src/stores/timesheet-store';
import { getExpenseIcon } from 'src/modules/timesheets/utils/expense.util';
import { useAuthStore } from 'src/stores/auth-store';
import { CAN_APPROVE_PAY_PERIODS } from 'src/modules/shared/models/user.models';
- import { empty_expense, type Expense } from 'src/modules/timesheets/models/expense.models';
-
+ import { Expense } from 'src/modules/timesheets/models/expense.models';
+
const { expense, horizontal = false } = defineProps<{
expense: Expense;
index: number;
horizontal?: boolean;
}>();
+ const is_approved = defineModel
({ required: true });
- const timesheet_store = useTimesheetStore();
const expenses_store = useExpensesStore();
const auth_store = useAuthStore();
const expenses_api = useExpensesApi();
- const is_approved = defineModel({ required: true });
- const is_selected = ref(false);
const refresh_key = ref(1);
+ const background_class = computed(() => deepEqual(expense, expenses_store.current_expense) ? '' : '');
+ const background_style = computed(() => deepEqual(expense, expenses_store.current_expense) ? 'border: 3px solid var(--q-accent);' : '');
+ const approved_class = computed(() => expense.is_approved ? ' bg-accent text-white' : '')
const is_authorized_to_approve = computed(() => CAN_APPROVE_PAY_PERIODS.includes(auth_store.user?.role ?? 'GUEST'))
- const expenseItemStyle = computed(() => is_approved.value ? 'border: solid 2px var(--q-primary);' : 'border: solid 2px grey;');
- // const highlightClass = computed(() => (expenses_store.mode === 'update' && is_selected) ? 'bg-accent' : '');
- const approvedClass = computed(() => horizontal ? ' q-mx-xs q-pa-xs cursor-pointer' : '')
-
-
- const employeeEmail = inject('employeeEmail') ?? '';
-
-
- const setExpenseToModify = () => {
- // expenses_store.mode = 'update';
- expenses_store.current_expense = expense;
- expenses_store.initial_expense = unwrapAndClone(expense);
- };
-
const requestExpenseDeletion = async () => {
- // expenses_store.mode = 'delete';
- expenses_store.initial_expense = expense;
- expenses_store.current_expense = empty_expense;
- await expenses_api.deleteExpenseByEmployeeEmail(employeeEmail, expenses_store.initial_expense.date);
+ await expenses_api.deleteExpenseById(expense.id);
}
- function onExpenseClicked() {
+ const onExpenseClicked = () => {
if (is_authorized_to_approve.value) {
is_approved.value = !is_approved.value;
refresh_key.value += 1;
}
}
+
+ const onUpdateClicked = () => {
+ if (deepEqual(expense, expenses_store.current_expense)) {
+ expenses_store.mode = 'create';
+ expenses_store.current_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
+ return;
+ }
+
+ expenses_store.mode = 'update';
+ expenses_store.current_expense = expense;
+ expenses_store.initial_expense = unwrapAndClone(expense);
+ }
@@ -66,27 +62,16 @@
-
-
-
-
-
+
{{ expense.mileage?.toFixed(1) }} km
@@ -121,37 +106,45 @@
-
- {{ expense.date }}
+ {{ $d(new Date(expense.date), { month: 'short', day: 'numeric', weekday: 'long' }) }}
-
+
+
+ attachment_goes_here.jpg
+
+
-
+
{{ $t('timesheet.expense.employee_comment') }}
{{ expense.comment }}
@@ -159,10 +152,13 @@
-
+
{{ $t('timesheet.expense.supervisor_comment') }}
-
+
+
+
diff --git a/src/modules/timesheets/components/expense-dialog-list.vue b/src/modules/timesheets/components/expense-dialog-list.vue
index e0f31b7..34c7047 100644
--- a/src/modules/timesheets/components/expense-dialog-list.vue
+++ b/src/modules/timesheets/components/expense-dialog-list.vue
@@ -2,32 +2,41 @@
setup
lang="ts"
>
- import { useExpensesStore } from 'src/stores/expense-store';
+ import { computed } from 'vue';
+ import { useTimesheetStore } from 'src/stores/timesheet-store';
import ExpenseDialogListItem from 'src/modules/timesheets/components/expense-dialog-list-item.vue';
- const expenses_store = useExpensesStore();
+ const timesheet_store = useTimesheetStore();
const { horizontal = false } = defineProps<{
horizontal?: boolean;
}>();
+
+ const expenses_list = computed(() => {
+ if (timesheet_store.timesheets !== undefined) {
+ return timesheet_store.timesheets.flatMap(week => week.days).flatMap(day => day.expenses);
+ }
+
+ return [];
+ })
{{ $t('timesheet.expense.empty_list') }}
-
+
-
-
-
\ No newline at end of file
diff --git a/src/modules/timesheets/components/shift-list-date-widget.vue b/src/modules/timesheets/components/shift-list-date-widget.vue
new file mode 100644
index 0000000..5555892
--- /dev/null
+++ b/src/modules/timesheets/components/shift-list-date-widget.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+ {{ $d(display_date, { weekday: $q.screen.lt.md ? 'short' : 'long'}) }}
+
+
+ {{ display_date.getDate() }}
+
+
+ {{ $d(display_date, { month: $q.screen.lt.md ? 'short' : 'long' }) }}
+
+
+
\ 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
new file mode 100644
index 0000000..a9c8ccb
--- /dev/null
+++ b/src/modules/timesheets/components/shift-list-day-row.vue
@@ -0,0 +1,265 @@
+
+
+
+ slideDeleteShift(details.reset)"
+ >
+
+
+
+
+
+
+
shift.type = option.value"
+ >
+
+
+
+ {{ scope.opt.label }}
+
+
+
+
+
+
+
+ {{ $t('shared.misc.in') }}
+
+
+
+
+
+
+ {{ $t('shared.misc.out') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 280 - (scope.value?.length ?? 0) }}
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 0000000..2d926eb
--- /dev/null
+++ b/src/modules/timesheets/components/shift-list-day.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/timesheets/components/shift-list-row.vue b/src/modules/timesheets/components/shift-list-row.vue
deleted file mode 100644
index d732de0..0000000
--- a/src/modules/timesheets/components/shift-list-row.vue
+++ /dev/null
@@ -1,197 +0,0 @@
-
-
-
-
-
-
-
-
-
- {{ scope.opt.label }}
-
-
-
-
-
-
-
- {{ $t('shared.misc.in') }}
-
-
-
-
-
-
-
-
-
-
- {{ $t('shared.misc.out') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ 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 0b5855b..60c520d 100644
--- a/src/modules/timesheets/components/shift-list.vue
+++ b/src/modules/timesheets/components/shift-list.vue
@@ -3,27 +3,17 @@
lang="ts"
>
import { date } from 'quasar';
- import { computed } from 'vue';
- import { useQuasar } from 'quasar';
- import ShiftListRow from 'src/modules/timesheets/components/shift-list-row.vue';
- import { useTimesheetStore } from 'src/stores/timesheet-store';
- import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
- import { Shift } from 'src/modules/timesheets/models/shift.models';
import { useUiStore } from 'src/stores/ui-store';
+ import { useTimesheetStore } from 'src/stores/timesheet-store';
+ import { Shift } from 'src/modules/timesheets/models/shift.models';
+ import ShiftListDay from 'src/modules/timesheets/components/shift-list-day.vue';
+ import ShiftListDateWidget from 'src/modules/timesheets/components/shift-list-date-widget.vue';
+ import type { TimesheetDay } from 'src/modules/timesheets/models/timesheet.models';
+
+ const { extractDate } = date;
- const q = useQuasar();
const ui_store = useUiStore();
const timesheet_store = useTimesheetStore();
- const shift_api = useShiftApi();
-
- const { dense = false } = defineProps<{
- dense?: boolean;
- }>();
-
- const is_mobile = computed(() => q.screen.lt.md);
- const date_font_size = computed(() => dense ? '1.5em' : '2.5em');
- const weekday_font_size = computed(() => dense ? '0.55em;' : '0.7em;');
- const date_box_size = computed(() => dense || is_mobile.value ? 'width: 40px; height: 75px;' : 'width: 75px; height: 75px;');
const addNewShift = (day_shifts: Shift[], date: string, timesheet_id: number) => {
ui_store.focus_next_component = true;
@@ -33,90 +23,139 @@
day_shifts.push(new_shift);
};
- const deleteCurrentShift = async (shift: Shift) => {
- console.log('shift to delete: ', shift);
- if (shift.shift_id < 0) {
- shift.shift_id = 0;
- return;
+ const deleteUnsavedShift = (timesheet_index: number, day_index: number) => {
+ if (timesheet_store.timesheets !== undefined) {
+ const day = timesheet_store.timesheets[timesheet_index]!.days[day_index]!;
+ const shifts_without_deleted_shift = day.shifts.filter(shift => shift.id !== 0);
+ day.shifts = shifts_without_deleted_shift;
+ console.log("day's shifts after cleanup: ", day.shifts);
}
- await shift_api.deleteShiftById(shift.shift_id);
+ }
+
+ const getDayApproval = (day: TimesheetDay) => {
+ if (day.shifts.length < 1) return false;
+ return day.shifts.every(shift => shift.is_approved === true);
}
-
+
-
-
-
+
+
- {{ $d(date.extractDate(day.date, 'YYYY-MM-DD'), {
- weekday: $q.screen.lt.md ? 'short' :
- 'long'
- })
- }}
-
-
- {{ date.extractDate(day.date, 'YYYY-MM-DD').getDate() }}
-
-
- {{ $d(date.extractDate(day.date, 'YYYY-MM-DD'), {
- month: $q.screen.lt.md ? 'short' : 'long'
- })
- }}
-
-
+
{{ $d(extractDate(day.date, 'YYYY-MM-DD'), {
+ weekday: 'long', day: 'numeric', month:
+ 'long'
+ }) }}
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/src/modules/timesheets/components/timesheet-error-widget.vue b/src/modules/timesheets/components/timesheet-error-widget.vue
new file mode 100644
index 0000000..a667a26
--- /dev/null
+++ b/src/modules/timesheets/components/timesheet-error-widget.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+ {{ error.conflicts.date }}
+
+
+
+
+ {{ error.conflicts.start_time }} - {{ error.conflicts.end_time }}
+
+
+
+
+ {{ $t('timesheet.shift.errors.' + error.error_code) }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/timesheets/components/timesheet-wrapper.vue b/src/modules/timesheets/components/timesheet-wrapper.vue
index 627e133..3c0a970 100644
--- a/src/modules/timesheets/components/timesheet-wrapper.vue
+++ b/src/modules/timesheets/components/timesheet-wrapper.vue
@@ -5,12 +5,12 @@
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 ShiftListLegend from 'src/modules/timesheets/components/shift-list-legend.vue';
+ import TimesheetErrorWidget from 'src/modules/timesheets/components/timesheet-error-widget.vue';
import { useTimesheetStore } from 'src/stores/timesheet-store';
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
import { useExpensesStore } from 'src/stores/expense-store';
import { provide } from 'vue';
-import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
+ import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
const { open } = useExpensesStore();
const shift_api = useShiftApi();
@@ -77,12 +77,12 @@ import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
-
+
+
+
+
diff --git a/src/modules/timesheets/composables/use-expense-api.ts b/src/modules/timesheets/composables/use-expense-api.ts
index a3b24e7..13b98b8 100644
--- a/src/modules/timesheets/composables/use-expense-api.ts
+++ b/src/modules/timesheets/composables/use-expense-api.ts
@@ -7,7 +7,7 @@ import type { Expense } from "src/modules/timesheets/models/expense.models";
export const useExpensesApi = () => {
const expenses_store = useExpensesStore();
-
+
const createExpenseByEmployeeEmail = async (employee_email: string, date: string): Promise => {
// await expenses_store.upsertOrDeleteExpensesByEmployeeEmail(employee_email, date, upsert_expense);
};
@@ -16,13 +16,13 @@ export const useExpensesApi = () => {
// await expenses_store.upsertOrDeleteExpensesByEmployeeEmail(employee_email, date, upsert_expense);
};
- const deleteExpenseByEmployeeEmail = async (employee_email: string, date: string): Promise => {
- // await expenses_store.upsertOrDeleteExpensesByEmployeeEmail(employee_email, date, upsert_expense);
+ const deleteExpenseById = async (expense_id: number): Promise => {
+ await expenses_store.deleteExpenseById(expense_id);
};
return {
createExpenseByEmployeeEmail,
updateExpenseByEmployeeEmail,
- deleteExpenseByEmployeeEmail,
+ deleteExpenseById,
};
};
\ No newline at end of file
diff --git a/src/modules/timesheets/composables/use-shift-api.ts b/src/modules/timesheets/composables/use-shift-api.ts
index ccbbcc4..bb41d22 100644
--- a/src/modules/timesheets/composables/use-shift-api.ts
+++ b/src/modules/timesheets/composables/use-shift-api.ts
@@ -20,15 +20,14 @@ export const useShiftApi = () => {
const saveShiftChanges = async () => {
timesheet_store.is_loading = true;
+
const create_success = await shift_store.createNewShifts();
+ const update_success = await shift_store.updateShifts();
- if (create_success) {
- const update_success = await shift_store.updateShifts();
-
- if (update_success) {
- await timesheet_store.getTimesheetsByEmployeeEmail(auth_store.user?.email ?? '')
- }
+ if (create_success || update_success){
+ await timesheet_store.getTimesheetsByEmployeeEmail(auth_store.user?.email ?? '');
}
+
timesheet_store.is_loading = false;
}
diff --git a/src/modules/timesheets/models/expense.models.ts b/src/modules/timesheets/models/expense.models.ts
index 231a39c..84ecc45 100644
--- a/src/modules/timesheets/models/expense.models.ts
+++ b/src/modules/timesheets/models/expense.models.ts
@@ -4,44 +4,22 @@ export const EXPENSE_TYPE: ExpenseType[] = ['PER_DIEM', 'MILEAGE', 'EXPENSES', '
export const TYPES_WITH_MILEAGE_ONLY: Readonly = ['MILEAGE'];
export const TYPES_WITH_AMOUNT_ONLY: Readonly = ['PER_DIEM', 'EXPENSES', 'ON_CALL',];
-export interface Expense {
- id: number;
- date: string; //YYYY-MM-DD
- type: ExpenseType;
- amount: number;
- mileage?: number;
- comment: string;
+export class Expense {
+ id: number;
+ date: string; //YYYY-MM-DD
+ type: ExpenseType;
+ amount: number;
+ mileage?: number;
+ comment: string;
supervisor_comment?: string;
is_approved: boolean;
-};
-
-export const empty_expense: Expense = {
- id: -1,
- date: '',
- type: 'EXPENSES',
- amount: 0,
- comment: '',
- is_approved: false,
-};
-
-export const test_expenses: Expense[] = [
- {
- id: 201,
- date: '2025-01-06',
- type: 'EXPENSES',
- amount: 15.5,
- comment: 'Lunch receipt',
- is_approved: false,
- },
- {
- id: 202,
- date: '2025-01-07',
- type: 'MILEAGE',
- amount: 0,
- mileage: 32.4,
- comment: 'Travel to client site',
- is_approved: true,
- },
-];
-
+ constructor(date: string) {
+ this.id = -1;
+ this.date = date;
+ this.type = 'EXPENSES';
+ this.amount = 0;
+ this.comment = '';
+ this.is_approved = false;
+ };
+};
\ No newline at end of file
diff --git a/src/modules/timesheets/models/shift.models.ts b/src/modules/timesheets/models/shift.models.ts
index f312091..2881a61 100644
--- a/src/modules/timesheets/models/shift.models.ts
+++ b/src/modules/timesheets/models/shift.models.ts
@@ -9,6 +9,8 @@ export const SHIFT_TYPES: ShiftType[] = [
export type ShiftType = 'REGULAR' | 'EVENING' | 'EMERGENCY' | 'HOLIDAY' | 'VACATION' | 'SICK';
+export type ShiftErrorCode = 'SHIFT_OVERLAP' | 'MISSING_START_TIME' | 'MISSING_END_TIME' | 'COMMENT_LENGTH_EXCEEDED' | 'APPROVAL_LOCK' | 'INVALID_DATE' | 'INVALID TYPE' | 'INVALID_TIMESHEET';
+
export type ShiftLegendItem = {
type: ShiftType;
color: string;
@@ -17,7 +19,7 @@ export type ShiftLegendItem = {
};
export class Shift {
- shift_id: number;
+ id: number;
timesheet_id: number;
date: string; //YYYY-MM-DD
type: ShiftType;
@@ -28,7 +30,7 @@ export class Shift {
is_remote: boolean;
constructor() {
- this.shift_id = -1;
+ this.id = -1;
this.timesheet_id = -1;
this.date = '';
this.type = 'REGULAR';
@@ -40,7 +42,21 @@ export class Shift {
}
}
-export interface NewShift {
- timesheet_id: number;
- shifts: Shift[];
+export interface ShiftAPIResponse {
+ ok: boolean;
+ data?: {
+ shift: Shift;
+ overtime: unknown;
+ }
+ error?: ShiftAPIError;
+}
+
+export interface ShiftAPIError {
+ error_code: ShiftErrorCode;
+ conflicts:
+ {
+ date: string;
+ start_time: string;
+ end_time: string;
+ }
}
\ No newline at end of file
diff --git a/src/modules/timesheets/services/expense-service.ts b/src/modules/timesheets/services/expense-service.ts
index 9d621c6..d022549 100644
--- a/src/modules/timesheets/services/expense-service.ts
+++ b/src/modules/timesheets/services/expense-service.ts
@@ -1,13 +1,19 @@
import { api } from "src/boot/axios";
+import type { Expense } from "src/modules/timesheets/models/expense.models";
export const ExpenseService = {
- getExpensesByTimesheetId: async (timesheet_id: number) => {
- const response = await api.get(`timesheet/${timesheet_id}`);
+ createExpense: async (expense: Expense) => {
+ const response = await api.post('expense/create', expense);
return response.data;
},
- upsertOrDeleteExpenseById: async (expense_id: number) => {
- const response = await api.post(`epxense/${expense_id}`);
+ updateExpenseById: async (expense: Expense) => {
+ const response = await api.patch(`expense/update`, expense);
return response.data;
},
+
+ deleteExpenseById: async (expense_id: number): Promise<{ok: boolean, id: number, error?: unknown}> => {
+ const response = await api.delete(`expense/delete/${expense_id}`);
+ return response.data;
+ }
};
\ No newline at end of file
diff --git a/src/modules/timesheets/services/shift-service.ts b/src/modules/timesheets/services/shift-service.ts
index 0880c56..f7eac5b 100644
--- a/src/modules/timesheets/services/shift-service.ts
+++ b/src/modules/timesheets/services/shift-service.ts
@@ -1,6 +1,5 @@
-/* eslint-disable */
import { api } from "src/boot/axios";
-import type { Shift } from "src/modules/timesheets/models/shift.models";
+import type { Shift, ShiftAPIResponse } from "src/modules/timesheets/models/shift.models";
export const ShiftService = {
deleteShiftById: async (shift_id: number) => {
@@ -8,17 +7,15 @@ export const ShiftService = {
return response.data;
},
- createNewShifts: async (new_shifts: Shift[]) => {
- // const response = await api.post(`/shift/`, { dtos: new_shifts });
- // return response;
- console.log('create shift payload: ', new_shifts);
- return {status: 200};
+ createNewShifts: async (new_shifts: Shift[]):Promise => {
+ const response = await api.post(`/shift/create`, new_shifts);
+ return response.data;
},
updateShifts: async (existing_shifts: Shift[]) => {
- // const response = await api.patch(`/shift/`, { dtos: existing_shifts });
- // return response;
- console.log('update shift payload: ', existing_shifts);
- return {status: 200};
+ console.log('sent shifts: ', existing_shifts)
+ const response = await api.patch(`/shift/update`, existing_shifts);
+ console.log('API response to existing shifts: ', response.data);
+ return response;
}
};
\ No newline at end of file
diff --git a/src/modules/timesheets/services/timesheet-service.ts b/src/modules/timesheets/services/timesheet-service.ts
index c12caf0..422c726 100644
--- a/src/modules/timesheets/services/timesheet-service.ts
+++ b/src/modules/timesheets/services/timesheet-service.ts
@@ -19,8 +19,8 @@ export const timesheetService = {
return response.data;
},
- getTimesheetsByPayPeriodAndEmployeeEmail: async (employee_email: string, year: number, period_number: number): Promise => {
- const response = await api.get('timesheets', { params: { employee_email, year, period_number } });
+ getTimesheetsByPayPeriod: async (year: number, period_number: number): Promise => {
+ const response = await api.get('timesheets', { params: { year, period_number } });
return response.data;
},
};
\ No newline at end of file
diff --git a/src/pages/dashboard-page.vue b/src/pages/dashboard-page.vue
index b707787..0c502fe 100644
--- a/src/pages/dashboard-page.vue
+++ b/src/pages/dashboard-page.vue
@@ -2,7 +2,16 @@
setup
lang="ts"
>
-import { Notify } from 'quasar';
+ import { ref } from 'vue';
+ import { Notify } from 'quasar';
+
+ const LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et \
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip \
+ ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu \
+ fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia \
+ deserunt mollit anim id est laborum."
+
+ const slide = ref('welcome');
const clickNotify = () => {
Notify.create({
@@ -10,38 +19,84 @@ import { Notify } from 'quasar';
color: 'info'
})
}
-
-
-
-
- Welcome to App Targo, !
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
- dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
- fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
- deserunt mollit anim id est laborum.
-
-
-
-
-
+
+
+
+
+
+
+ Welcome to App Targo!
+
+
+
+ {{ LOREM_IPSUM }}
+
+
+
+
+
+ {{ LOREM_IPSUM }}
+
+
+
+
+
+ {{ LOREM_IPSUM }}
+
+
+
+
+
+ {{ LOREM_IPSUM }}
+
+
+
+
-
+
\ No newline at end of file
diff --git a/src/stores/auth-store.ts b/src/stores/auth-store.ts
index b08a124..17b54e7 100644
--- a/src/stores/auth-store.ts
+++ b/src/stores/auth-store.ts
@@ -20,7 +20,7 @@ export const useAuthStore = defineStore('auth', () => {
void handleAuthMessage(event);
});
- const oidc_popup = window.open(`${import.meta.env.VITE_TARGO_BACKEND_AUTH_URL}auth/v1/login`, 'authPopup', 'width=600,height=800');
+ const oidc_popup = window.open(`${import.meta.env.VITE_TARGO_BACKEND_URL}auth/v1/login`, 'authPopup', 'width=600,height=800');
if (!oidc_popup)
Notify.create({
diff --git a/src/stores/expense-store.ts b/src/stores/expense-store.ts
index ea5aefe..af8ec5f 100644
--- a/src/stores/expense-store.ts
+++ b/src/stores/expense-store.ts
@@ -1,92 +1,56 @@
import { ref } from "vue";
import { defineStore } from "pinia";
-import { ExpensesApiError, type GenericApiError } from "src/modules/timesheets/models/expense-validation.models";
-import { empty_expense, test_expenses, type Expense } from "src/modules/timesheets/models/expense.models";
+import { Expense } from "src/modules/timesheets/models/expense.models";
import { ExpenseService } from "src/modules/timesheets/services/expense-service";
+import { date } from "quasar";
export const useExpensesStore = defineStore('expenses', () => {
const is_open = ref(false);
const is_loading = ref(false);
- const pay_period_expenses = ref(test_expenses);
- const current_expense = ref(empty_expense);
- const initial_expense = ref(empty_expense);
- const error = ref(null);
-
- // const setErrorFrom = (err: unknown) => {
- // const e = err as any;
- // error.value = e?.message || 'Unknown error';
- // };
+ const mode = ref<'create' | 'update' | 'delete'>('create');
+ const current_expense = ref(new Expense(date.formatDate(new Date(), 'YYYY-MM-DD')));
+ const initial_expense = ref(new Expense(date.formatDate(new Date(), 'YYYY-MM-DD')));
const open = (): void => {
is_open.value = true;
- is_loading.value = true;
- error.value = null;
- current_expense.value = empty_expense;
- initial_expense.value = empty_expense;
-
- // await getPayPeriodExpensesByTimesheetId(timesheet_id);
- is_loading.value = false;
+ current_expense.value = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
+ initial_expense.value = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
+ mode.value = 'create';
}
const close = () => {
- error.value = null;
is_open.value = false;
};
- const getPayPeriodExpensesByTimesheetId = async (timesheet_id: number): Promise => {
- is_loading.value = true;
- error.value = null;
-
+ const upsertExpensesById = async (expense_id: number, expense: Expense): Promise => {
try {
- const expenses = await ExpenseService.getExpensesByTimesheetId(timesheet_id);
- pay_period_expenses.value = expenses;
- } catch (err: unknown) {
- if (typeof err === 'object') {
- const error = err as GenericApiError;
- const status_code: number = error.status_code ?? 500;
- // const data = error.context ?? '';
- // error.value = data.message || data.error || err.message;
-
- throw new ExpensesApiError({
- status_code,
- // error_code: data.error_code,
- // message: data.message || data.error || err.message,
- // context: data.context,
- });
+ if (expense_id < 0) {
+ const data = await ExpenseService.createExpense(expense);
+ return data;
}
-
- } finally {
- is_loading.value = false;
- }
- };
-
- const upsertOrDeleteExpensesById = async (expense_id: number): Promise => {
- is_loading.value = true;
- error.value = null;
-
- try {
- await ExpenseService.upsertOrDeleteExpenseById(expense_id);
// TODO: Save response data into proper ref
} catch (err) {
// setErrorFrom(err);
console.error(err);
- } finally {
- is_loading.value = false;
}
};
+ const deleteExpenseById = async (expense_id: number): Promise => {
+ const data = await ExpenseService.deleteExpenseById(expense_id);
+ return data.ok;
+ }
+
return {
is_open,
is_loading,
- pay_period_expenses,
+ mode,
current_expense,
initial_expense,
- error,
open,
- getPayPeriodExpensesByTimesheetId,
- upsertOrDeleteExpensesById,
+ upsertExpensesById,
+ deleteExpenseById,
close,
};
});
\ No newline at end of file
diff --git a/src/stores/schedule-presets.store.ts b/src/stores/schedule-presets.store.ts
new file mode 100644
index 0000000..ede62f9
--- /dev/null
+++ b/src/stores/schedule-presets.store.ts
@@ -0,0 +1,68 @@
+/* eslint-disable */
+import { ref } from "vue";
+import { defineStore } from "pinia";
+import { SchedulePresetsService } from "src/modules/profile/services/schedule-presets-service";
+import type { SchedulePreset } from "src/modules/profile/models/schedule-presets.models";
+
+
+export const useSchedulePresetsStore = defineStore('schedule_presets_store', () => {
+ const schedule_presets = ref();
+
+ const createSchedulePreset = async (): Promise => {
+ try {
+ // const new_preset: SchedulePreset = ??
+ // await SchedulePresetsService.createSchedulePresets(new_preset);
+ return true;
+ } catch (error) {
+ console.error('DEV ERROR || error while creating schedule preset: ', error);
+ return false;
+ }
+ }
+
+ const updateSchedulePreset = async (): Promise => {
+ try {
+ return true;
+ } catch (error) {
+ console.error('DEV ERROR || error while updating schedule preset: ', error);
+ return false;
+ }
+
+ }
+
+ const deleteSchedulePreset = async (preset_id: number): Promise => {
+ try {
+ await SchedulePresetsService.deleteSchedulePresets(preset_id);
+ return true;
+ } catch (error) {
+ console.error('DEV ERROR || error while deleting schedule preset: ', error);
+ return false
+ }
+ }
+
+ const findSchedulePresetList = async (): Promise => {
+ try {
+ return true;
+ } catch (error) {
+ console.error('DEV ERROR || error while searching for schedule presets: ', error);
+ return false
+ }
+ }
+
+ const applySchedulePreset = async (): Promise => {
+ try {
+ return true;
+ } catch (error) {
+ console.error('DEV ERROR || error while building schedule: ', error);
+ return false
+ }
+ }
+
+ return {
+ schedule_presets,
+ createSchedulePreset,
+ updateSchedulePreset,
+ deleteSchedulePreset,
+ findSchedulePresetList,
+ applySchedulePreset,
+ }
+})
\ No newline at end of file
diff --git a/src/stores/shift-store.ts b/src/stores/shift-store.ts
index 8f2950c..1b2e699 100644
--- a/src/stores/shift-store.ts
+++ b/src/stores/shift-store.ts
@@ -1,12 +1,13 @@
import { ref } from "vue";
+import { Notify } from "quasar";
import { defineStore } from "pinia";
import { ShiftService } from "src/modules/timesheets/services/shift-service";
import { useTimesheetStore } from "src/stores/timesheet-store";
-import { Notify } from "quasar";
+import type { ShiftAPIError } from "src/modules/timesheets/models/shift.models";
export const useShiftStore = defineStore('shift_store', () => {
const timesheet_store = useTimesheetStore();
- const shift_error = ref();
+ const shift_errors = ref([]);
const deleteShiftById = async (shift_id: number): Promise => {
try {
@@ -19,20 +20,25 @@ export const useShiftStore = defineStore('shift_store', () => {
};
const createNewShifts = async (): Promise => {
- if (timesheet_store.timesheets === undefined) return false;
+ if (timesheet_store.timesheets === undefined) {
+ console.log('no changes in existing shifts detected');
+ return false;
+ }
try {
- const new_shifts = timesheet_store.timesheets.flatMap(week => week.days).flatMap(day => day.shifts).filter(shift => shift.shift_id < 0);
+ const new_shifts = timesheet_store.timesheets.flatMap(week => week.days).flatMap(day => day.shifts).filter(shift => shift.id < 0);
if (new_shifts?.length > 0) {
const response = await ShiftService.createNewShifts(new_shifts);
- if (response.status <= 200) {
+ if (response.every(res => res.ok)) {
return true;
}
+ else {
+ response.forEach(res => {
+ shift_errors.value.push(res.error!);
+ });
+ }
}
-
- console.log('No new shifts to save');
- Notify.create('no new shifts to save')
return false;
} catch (error) {
console.error('Error creating new shifts: ', error);
@@ -44,11 +50,12 @@ export const useShiftStore = defineStore('shift_store', () => {
if (timesheet_store.timesheets === undefined) return false;
try {
- const existing_shifts = timesheet_store.timesheets.flatMap(week => week.days).flatMap(day => day.shifts).filter(shift => shift.shift_id > 0);
+ const existing_shifts = timesheet_store.timesheets.flatMap(week => week.days).flatMap(day => day.shifts).filter(shift => shift.id > 0);
if (existing_shifts?.length > 0) {
const response = await ShiftService.updateShifts(existing_shifts);
- if (response.status <= 200) {
+
+ if (response.status < 400) {
return true;
}
}
@@ -63,7 +70,7 @@ export const useShiftStore = defineStore('shift_store', () => {
}
return {
- shift_error,
+ shift_errors,
deleteShiftById,
createNewShifts,
updateShifts,
diff --git a/src/stores/timesheet-store.ts b/src/stores/timesheet-store.ts
index 73c5b82..07a746d 100644
--- a/src/stores/timesheet-store.ts
+++ b/src/stores/timesheet-store.ts
@@ -1,6 +1,7 @@
import { ref } from 'vue';
import { defineStore } from 'pinia';
import { useAuthStore } from 'src/stores/auth-store';
+import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
import { timesheetApprovalService } from 'src/modules/timesheet-approval/services/timesheet-approval-service';
import { timesheetService } from 'src/modules/timesheets/services/timesheet-service';
import type { TimesheetOverview } from "src/modules/timesheet-approval/models/timesheet-overview.models";
@@ -15,7 +16,8 @@ export const useTimesheetStore = defineStore('timesheet', () => {
const pay_period = ref();
const pay_period_overviews = ref([]);
const current_pay_period_overview = ref();
- const timesheets = ref();
+ const timesheets = ref([]);
+ const initial_timesheets = ref([]);
const pay_period_report = ref();
const getPayPeriodByDateOrYearAndNumber = async (date_or_year: string | number, period_number?: number): Promise => {
@@ -58,13 +60,17 @@ export const useTimesheetStore = defineStore('timesheet', () => {
}
};
- const getTimesheetsByEmployeeEmail = async (employee_email: string) => {
+ const getTimesheetsByEmployeeEmail = async (employee_email?: string) => {
is_loading.value = true;
if (pay_period.value === undefined) return;
try {
- const response = await timesheetService.getTimesheetsByPayPeriodAndEmployeeEmail(employee_email, pay_period.value.pay_year, pay_period.value.pay_period_no);
+ if (employee_email) {
+ console.log('email: ', employee_email);
+ }
+ const response = await timesheetService.getTimesheetsByPayPeriod(pay_period.value.pay_year, pay_period.value.pay_period_no);
timesheets.value = response.timesheets;
+ initial_timesheets.value = unwrapAndClone(timesheets.value);
is_loading.value = false;
} catch (error) {
console.error('There was an error retrieving timesheet details for this employee: ', error);
@@ -97,6 +103,7 @@ export const useTimesheetStore = defineStore('timesheet', () => {
pay_period_overviews,
current_pay_period_overview,
timesheets,
+ initial_timesheets,
getPayPeriodByDateOrYearAndNumber,
getTimesheetOverviewsByPayPeriod,
getTimesheetsByEmployeeEmail,