refactor(timesheet): update appearance, work on expense dialog, plugging to backend.
This commit is contained in:
parent
62385461d5
commit
1274a1b65b
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
const NEXT = 1;
|
const NEXT = 1;
|
||||||
const PREVIOUS = -1;
|
const PREVIOUS = -1;
|
||||||
|
const PAY_PERIOD_DATE_LIMIT = '2023/12/17';
|
||||||
|
|
||||||
const timesheet_store = useTimesheetStore();
|
const timesheet_store = useTimesheetStore();
|
||||||
|
|
||||||
|
|
@ -125,7 +126,7 @@
|
||||||
class="q-mt-xl"
|
class="q-mt-xl"
|
||||||
today-btn
|
today-btn
|
||||||
mask="YYYY-MM-DD"
|
mask="YYYY-MM-DD"
|
||||||
:options="date => date > '2023/12/16'"
|
:options="date => date >= PAY_PERIOD_DATE_LIMIT"
|
||||||
@update:model-value="onDateSelected"
|
@update:model-value="onDateSelected"
|
||||||
/>
|
/>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelUpdateMode = () => {
|
const cancelUpdateMode = () => {
|
||||||
expenses_store.current_expense = new Expense;
|
expenses_store.current_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
||||||
expenses_store.initial_expense = new Expense;
|
expenses_store.initial_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
||||||
expenses_store.mode = 'create';
|
expenses_store.mode = 'create';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -40,6 +40,12 @@
|
||||||
if (expenses_store.mode === '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 ?? '');
|
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;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -83,6 +89,7 @@
|
||||||
v-model="expenses_store.current_expense.date"
|
v-model="expenses_store.current_expense.date"
|
||||||
mask="YYYY-MM-DD"
|
mask="YYYY-MM-DD"
|
||||||
event-color="accent"
|
event-color="accent"
|
||||||
|
:options="getExpenseCalendarRange"
|
||||||
@update:model-value="is_navigator_open = false"
|
@update:model-value="is_navigator_open = false"
|
||||||
/>
|
/>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
setup
|
setup
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
import { computed, inject, ref } from 'vue';
|
import { date } from 'quasar';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
||||||
import { deepEqual } from 'src/utils/deep-equal';
|
import { deepEqual } from 'src/utils/deep-equal';
|
||||||
import { useExpensesApi } from 'src/modules/timesheets/composables/use-expense-api';
|
import { useExpensesApi } from 'src/modules/timesheets/composables/use-expense-api';
|
||||||
|
|
@ -22,7 +23,6 @@
|
||||||
const expenses_store = useExpensesStore();
|
const expenses_store = useExpensesStore();
|
||||||
const auth_store = useAuthStore();
|
const auth_store = useAuthStore();
|
||||||
const expenses_api = useExpensesApi();
|
const expenses_api = useExpensesApi();
|
||||||
const employee_email = inject<string>('employeeEmail') ?? '';
|
|
||||||
|
|
||||||
const refresh_key = ref(1);
|
const refresh_key = ref(1);
|
||||||
const background_class = computed(() => deepEqual(expense, expenses_store.current_expense) ? '' : '');
|
const background_class = computed(() => deepEqual(expense, expenses_store.current_expense) ? '' : '');
|
||||||
|
|
@ -31,10 +31,7 @@
|
||||||
const is_authorized_to_approve = computed(() => CAN_APPROVE_PAY_PERIODS.includes(auth_store.user?.role ?? 'GUEST'))
|
const is_authorized_to_approve = computed(() => CAN_APPROVE_PAY_PERIODS.includes(auth_store.user?.role ?? 'GUEST'))
|
||||||
|
|
||||||
const requestExpenseDeletion = async () => {
|
const requestExpenseDeletion = async () => {
|
||||||
// expenses_store.mode = 'delete';
|
await expenses_api.deleteExpenseById(expense.id);
|
||||||
expenses_store.initial_expense = expense;
|
|
||||||
expenses_store.current_expense = new Expense;
|
|
||||||
await expenses_api.deleteExpenseByEmployeeEmail(employee_email, expenses_store.initial_expense.date);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onExpenseClicked = () => {
|
const onExpenseClicked = () => {
|
||||||
|
|
@ -45,9 +42,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const onUpdateClicked = () => {
|
const onUpdateClicked = () => {
|
||||||
if (deepEqual(expense, expenses_store.current_expense)){
|
if (deepEqual(expense, expenses_store.current_expense)) {
|
||||||
expenses_store.mode = 'create';
|
expenses_store.mode = 'create';
|
||||||
expenses_store.current_expense = new Expense;
|
expenses_store.current_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,7 +135,10 @@
|
||||||
v-if="!horizontal"
|
v-if="!horizontal"
|
||||||
top
|
top
|
||||||
>
|
>
|
||||||
<q-item-label lines="1" class="text-weight-medium text-uppercase">
|
<q-item-label
|
||||||
|
lines="1"
|
||||||
|
class="text-weight-medium text-uppercase"
|
||||||
|
>
|
||||||
{{ $t('timesheet.expense.employee_comment') }}
|
{{ $t('timesheet.expense.employee_comment') }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label
|
<q-item-label
|
||||||
|
|
@ -155,7 +155,10 @@
|
||||||
v-if="is_authorized_to_approve"
|
v-if="is_authorized_to_approve"
|
||||||
top
|
top
|
||||||
>
|
>
|
||||||
<q-item-label lines="1" class="text-weight-medium text-uppercase">
|
<q-item-label
|
||||||
|
lines="1"
|
||||||
|
class="text-weight-medium text-uppercase"
|
||||||
|
>
|
||||||
{{ $t('timesheet.expense.supervisor_comment') }}
|
{{ $t('timesheet.expense.supervisor_comment') }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label
|
<q-item-label
|
||||||
|
|
@ -170,9 +173,10 @@
|
||||||
<q-item-section :side="$q.screen.gt.sm">
|
<q-item-section :side="$q.screen.gt.sm">
|
||||||
<q-btn
|
<q-btn
|
||||||
flat
|
flat
|
||||||
color="accent"
|
|
||||||
icon="edit"
|
|
||||||
size="lg"
|
size="lg"
|
||||||
|
icon="edit"
|
||||||
|
color="accent"
|
||||||
|
:disable="expense.is_approved"
|
||||||
class="q-pa-none z-top"
|
class="q-pa-none z-top"
|
||||||
:class="expense.is_approved ? 'invisible no-pointer' : ''"
|
:class="expense.is_approved ? 'invisible no-pointer' : ''"
|
||||||
@click.stop="onUpdateClicked"
|
@click.stop="onUpdateClicked"
|
||||||
|
|
@ -182,10 +186,11 @@
|
||||||
<q-item-section :side="$q.screen.gt.sm">
|
<q-item-section :side="$q.screen.gt.sm">
|
||||||
<q-btn
|
<q-btn
|
||||||
flat
|
flat
|
||||||
:color="expense.is_approved ? 'white' : 'negative'"
|
|
||||||
:icon="expense.is_approved ? 'verified' : 'close'"
|
|
||||||
size="lg"
|
size="lg"
|
||||||
|
:icon="expense.is_approved ? 'verified' : 'close'"
|
||||||
|
:color="expense.is_approved ? 'white' : 'negative'"
|
||||||
class="q-pa-none z-top"
|
class="q-pa-none z-top"
|
||||||
|
:class="expense.is_approved ? 'no-pointer' : ''"
|
||||||
@click.stop="requestExpenseDeletion"
|
@click.stop="requestExpenseDeletion"
|
||||||
/>
|
/>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,23 @@
|
||||||
setup
|
setup
|
||||||
lang="ts"
|
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';
|
import ExpenseDialogListItem from 'src/modules/timesheets/components/expense-dialog-list-item.vue';
|
||||||
|
|
||||||
const expenses_store = useExpensesStore();
|
const timesheet_store = useTimesheetStore();
|
||||||
|
|
||||||
const { horizontal = false } = defineProps<{
|
const { horizontal = false } = defineProps<{
|
||||||
horizontal?: boolean;
|
horizontal?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const expenses_list = computed(() => {
|
||||||
|
if (timesheet_store.timesheets !== undefined) {
|
||||||
|
return timesheet_store.timesheets.flatMap(week => week.days).flatMap(day => day.expenses);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -20,14 +29,14 @@
|
||||||
:class="horizontal ? 'row flex-center' : ''"
|
:class="horizontal ? 'row flex-center' : ''"
|
||||||
>
|
>
|
||||||
<q-item-label
|
<q-item-label
|
||||||
v-if="expenses_store.pay_period_expenses?.length === 0"
|
v-if="expenses_list.length > 0"
|
||||||
class="text-italic q-px-sm"
|
class="text-italic q-px-sm"
|
||||||
>
|
>
|
||||||
{{ $t('timesheet.expense.empty_list') }}
|
{{ $t('timesheet.expense.empty_list') }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
|
|
||||||
<ExpenseDialogListItem
|
<ExpenseDialogListItem
|
||||||
v-for="(expense, index) in expenses_store.pay_period_expenses"
|
v-for="(expense, index) in expenses_list"
|
||||||
:key="index"
|
:key="index"
|
||||||
v-model="expense.is_approved"
|
v-model="expense.is_approved"
|
||||||
:index="index"
|
:index="index"
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,6 @@
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-slide-item
|
<q-slide-item
|
||||||
|
|
||||||
right-color="negative"
|
right-color="negative"
|
||||||
class="q-my-xs rounded-5 bg-transparent"
|
class="q-my-xs rounded-5 bg-transparent"
|
||||||
@right="details => slideDeleteShift(details.reset)"
|
@right="details => slideDeleteShift(details.reset)"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import type { Expense } from "src/modules/timesheets/models/expense.models";
|
||||||
|
|
||||||
export const useExpensesApi = () => {
|
export const useExpensesApi = () => {
|
||||||
const expenses_store = useExpensesStore();
|
const expenses_store = useExpensesStore();
|
||||||
|
|
||||||
const createExpenseByEmployeeEmail = async (employee_email: string, date: string): Promise<void> => {
|
const createExpenseByEmployeeEmail = async (employee_email: string, date: string): Promise<void> => {
|
||||||
// await expenses_store.upsertOrDeleteExpensesByEmployeeEmail(employee_email, date, upsert_expense);
|
// 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);
|
// await expenses_store.upsertOrDeleteExpensesByEmployeeEmail(employee_email, date, upsert_expense);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteExpenseByEmployeeEmail = async (employee_email: string, date: string): Promise<void> => {
|
const deleteExpenseById = async (expense_id: number): Promise<void> => {
|
||||||
// await expenses_store.upsertOrDeleteExpensesByEmployeeEmail(employee_email, date, upsert_expense);
|
await expenses_store.deleteExpenseById(expense_id);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createExpenseByEmployeeEmail,
|
createExpenseByEmployeeEmail,
|
||||||
updateExpenseByEmployeeEmail,
|
updateExpenseByEmployeeEmail,
|
||||||
deleteExpenseByEmployeeEmail,
|
deleteExpenseById,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -14,35 +14,12 @@ export class Expense {
|
||||||
supervisor_comment?: string;
|
supervisor_comment?: string;
|
||||||
is_approved: boolean;
|
is_approved: boolean;
|
||||||
|
|
||||||
constructor() {
|
constructor(date: string) {
|
||||||
this.id = -1;
|
this.id = -1;
|
||||||
this.date = '';
|
this.date = date;
|
||||||
this.type = 'EXPENSES';
|
this.type = 'EXPENSES';
|
||||||
this.amount = 0;
|
this.amount = 0;
|
||||||
this.comment = '';
|
this.comment = '';
|
||||||
this.is_approved = false;
|
this.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,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,13 +1,19 @@
|
||||||
import { api } from "src/boot/axios";
|
import { api } from "src/boot/axios";
|
||||||
|
import type { Expense } from "src/modules/timesheets/models/expense.models";
|
||||||
|
|
||||||
export const ExpenseService = {
|
export const ExpenseService = {
|
||||||
getExpensesByTimesheetId: async (timesheet_id: number) => {
|
createExpense: async (expense: Expense) => {
|
||||||
const response = await api.get(`timesheet/${timesheet_id}`);
|
const response = await api.post('expense/create', expense);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
upsertOrDeleteExpenseById: async (expense_id: number) => {
|
updateExpenseById: async (expense: Expense) => {
|
||||||
const response = await api.post(`epxense/${expense_id}`);
|
const response = await api.patch(`expense/update`, expense);
|
||||||
return response.data;
|
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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { ExpensesApiError, type GenericApiError } from "src/modules/timesheets/models/expense-validation.models";
|
import { Expense } from "src/modules/timesheets/models/expense.models";
|
||||||
import { test_expenses, Expense } from "src/modules/timesheets/models/expense.models";
|
|
||||||
import { ExpenseService } from "src/modules/timesheets/services/expense-service";
|
import { ExpenseService } from "src/modules/timesheets/services/expense-service";
|
||||||
|
import { date } from "quasar";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -10,82 +10,47 @@ export const useExpensesStore = defineStore('expenses', () => {
|
||||||
const is_open = ref(false);
|
const is_open = ref(false);
|
||||||
const is_loading = ref(false);
|
const is_loading = ref(false);
|
||||||
const mode = ref<'create' | 'update' | 'delete'>('create');
|
const mode = ref<'create' | 'update' | 'delete'>('create');
|
||||||
const pay_period_expenses = ref<Expense[]>(test_expenses);
|
const current_expense = ref<Expense>(new Expense(date.formatDate(new Date(), 'YYYY-MM-DD')));
|
||||||
const current_expense = ref<Expense>(new Expense);
|
const initial_expense = ref<Expense>(new Expense(date.formatDate(new Date(), 'YYYY-MM-DD')));
|
||||||
const initial_expense = ref<Expense>(new Expense);
|
|
||||||
const error = ref<string | null>(null);
|
|
||||||
|
|
||||||
// const setErrorFrom = (err: unknown) => {
|
|
||||||
// const e = err as any;
|
|
||||||
// error.value = e?.message || 'Unknown error';
|
|
||||||
// };
|
|
||||||
|
|
||||||
const open = (): void => {
|
const open = (): void => {
|
||||||
is_open.value = true;
|
is_open.value = true;
|
||||||
error.value = null;
|
current_expense.value = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
||||||
current_expense.value = new Expense;
|
initial_expense.value = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
||||||
initial_expense.value = new Expense;
|
|
||||||
mode.value = 'create';
|
mode.value = 'create';
|
||||||
}
|
}
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
error.value = null;
|
|
||||||
is_open.value = false;
|
is_open.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPayPeriodExpensesByTimesheetId = async (timesheet_id: number): Promise<void> => {
|
const upsertExpensesById = async (expense_id: number, expense: Expense): Promise<void> => {
|
||||||
is_loading.value = true;
|
|
||||||
error.value = null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const expenses = await ExpenseService.getExpensesByTimesheetId(timesheet_id);
|
if (expense_id < 0) {
|
||||||
pay_period_expenses.value = expenses;
|
const data = await ExpenseService.createExpense(expense);
|
||||||
} catch (err: unknown) {
|
return data;
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} finally {
|
|
||||||
is_loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const upsertOrDeleteExpensesById = async (expense_id: number): Promise<void> => {
|
|
||||||
is_loading.value = true;
|
|
||||||
error.value = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await ExpenseService.upsertOrDeleteExpenseById(expense_id);
|
|
||||||
// TODO: Save response data into proper ref
|
// TODO: Save response data into proper ref
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// setErrorFrom(err);
|
// setErrorFrom(err);
|
||||||
console.error(err);
|
console.error(err);
|
||||||
} finally {
|
|
||||||
is_loading.value = false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteExpenseById = async (expense_id: number): Promise<boolean> => {
|
||||||
|
const data = await ExpenseService.deleteExpenseById(expense_id);
|
||||||
|
return data.ok;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
is_open,
|
is_open,
|
||||||
is_loading,
|
is_loading,
|
||||||
mode,
|
mode,
|
||||||
pay_period_expenses,
|
|
||||||
current_expense,
|
current_expense,
|
||||||
initial_expense,
|
initial_expense,
|
||||||
error,
|
|
||||||
open,
|
open,
|
||||||
getPayPeriodExpensesByTimesheetId,
|
upsertExpensesById,
|
||||||
upsertOrDeleteExpensesById,
|
deleteExpenseById,
|
||||||
close,
|
close,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useAuthStore } from 'src/stores/auth-store';
|
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 { timesheetApprovalService } from 'src/modules/timesheet-approval/services/timesheet-approval-service';
|
||||||
import { timesheetService } from 'src/modules/timesheets/services/timesheet-service';
|
import { timesheetService } from 'src/modules/timesheets/services/timesheet-service';
|
||||||
import type { TimesheetOverview } from "src/modules/timesheet-approval/models/timesheet-overview.models";
|
import type { TimesheetOverview } from "src/modules/timesheet-approval/models/timesheet-overview.models";
|
||||||
import type { PayPeriod } from 'src/modules/shared/models/pay-period.models';
|
import type { PayPeriod } from 'src/modules/shared/models/pay-period.models';
|
||||||
import type { Timesheet } from 'src/modules/timesheets/models/timesheet.models';
|
import type { Timesheet } from 'src/modules/timesheets/models/timesheet.models';
|
||||||
import type { TimesheetApprovalCSVReportFilters } from 'src/modules/timesheet-approval/models/timesheet-approval-csv-report.models';
|
import type { TimesheetApprovalCSVReportFilters } from 'src/modules/timesheet-approval/models/timesheet-approval-csv-report.models';
|
||||||
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
|
||||||
|
|
||||||
|
|
||||||
export const useTimesheetStore = defineStore('timesheet', () => {
|
export const useTimesheetStore = defineStore('timesheet', () => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user