refactor(timesheet): redo expense dialog to work with Expansion Items, improve UI/UX
This commit is contained in:
parent
1b4e59b292
commit
faa239784b
|
|
@ -53,3 +53,15 @@ body.body--dark {
|
|||
.q-btn--push:active::before {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
auth_store.logout();
|
||||
|
||||
router.push({ name: 'login' }).catch(err => {
|
||||
console.log('could not log you out: ', err);
|
||||
console.error('could not log you out: ', err);
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -38,10 +38,7 @@ export const useEmployeeListApi = () => {
|
|||
weekday.is_error = isShiftOverlap(weekday.shifts);
|
||||
}
|
||||
|
||||
console.log('current preset: ', preset);
|
||||
|
||||
if (preset.weekdays.some(weekday => weekday.is_error)) {
|
||||
console.log('overlap!');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
const setDisplayLanguage = (locale: MessageLanguages) => {
|
||||
if (ui_store.user_preferences !== undefined) {
|
||||
ui_store.user_preferences.display_language = locale;
|
||||
console.log('triggered language change: ', ui_store.user_preferences.display_language);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@
|
|||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import { date } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { deepEqual } from 'src/utils/deep-equal';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import { useExpensesStore } from 'src/stores/expense-store';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { useExpensesApi } from 'src/modules/timesheets/composables/use-expense-api';
|
||||
import { convertToMonetaryAmount, getExpenseIcon, useExpenseRules } from 'src/modules/timesheets/utils/expense.util';
|
||||
import { type ExpenseType, TYPES_WITH_AMOUNT_ONLY } from 'src/modules/timesheets/models/expense.models';
|
||||
import { getExpenseIcon, useExpenseRules } from 'src/modules/timesheets/utils/expense.util';
|
||||
import { Expense, type ExpenseType, TYPES_WITH_AMOUNT_ONLY } from 'src/modules/timesheets/models/expense.models';
|
||||
|
||||
interface ExpenseOption {
|
||||
label: string;
|
||||
|
|
@ -26,7 +26,6 @@
|
|||
const files = defineModel<File[] | null>('files');
|
||||
|
||||
const is_navigator_open = ref(false);
|
||||
const is_initial_expense = ref(true);
|
||||
|
||||
const COMMENT_MAX_LENGTH = 280;
|
||||
const rules = useExpenseRules(t);
|
||||
|
|
@ -40,15 +39,9 @@
|
|||
{ label: t('timesheet.expense.types.MILEAGE'), value: 'MILEAGE', icon: getExpenseIcon('MILEAGE') },
|
||||
{ label: t('timesheet.expense.types.ON_CALL'), value: 'ON_CALL', icon: getExpenseIcon('ON_CALL') },
|
||||
]
|
||||
|
||||
const expense_selected = ref(expense_options.find(expense => expense.value == expenses_store.current_expense.type));
|
||||
|
||||
const expense_monetary_string = ref(expenses_store.current_expense.amount.toString());
|
||||
|
||||
const emit = defineEmits<{
|
||||
'onClickUpdateCancel': [void];
|
||||
'onClickSaveUpdates': [void];
|
||||
}>();
|
||||
|
||||
const openDatePicker = () => {
|
||||
is_navigator_open.value = true;
|
||||
if (expenses_store.current_expense.date === undefined) {
|
||||
|
|
@ -64,21 +57,10 @@
|
|||
const requestExpenseCreationOrUpdate = async () => {
|
||||
await expenses_api.upsertExpense(expenses_store.current_expense);
|
||||
|
||||
if (expenses_store.current_expense.id) {
|
||||
emit('onClickSaveUpdates');
|
||||
}
|
||||
|
||||
expenses_store.is_showing_create_form = true;
|
||||
expenses_store.mode = 'create';
|
||||
expenses_store.current_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
||||
};
|
||||
|
||||
const saveAndConvert = () => {
|
||||
expenses_store.current_expense.amount = convertToMonetaryAmount(expense_monetary_string.value);
|
||||
expense_monetary_string.value = expenses_store.current_expense.amount.toString();
|
||||
console.log('current expense amount: ', expenses_store.current_expense.amount);
|
||||
}
|
||||
|
||||
watch(expenses_store.current_expense, () => {
|
||||
is_initial_expense.value = deepEqual(expenses_store.current_expense, expenses_store.initial_expense);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -87,14 +69,8 @@
|
|||
:key="expenses_store.current_expense.id"
|
||||
flat
|
||||
@submit.prevent="requestExpenseCreationOrUpdate"
|
||||
class="full-width"
|
||||
class="full-width q-mt-md q-px-md"
|
||||
>
|
||||
<div
|
||||
class="text-uppercase text-weight-medium q-pt-sm q-ma-sm"
|
||||
:class="expenses_store.mode === 'create' ? 'q-px-lg' : 'invisible'"
|
||||
>
|
||||
{{ $t('timesheet.expense.add_expense') }}
|
||||
</div>
|
||||
<div
|
||||
class="row justify-between items-start rounded-5 q-pb-sm"
|
||||
:class="expenses_store.mode === 'create' ? 'q-px-lg' : ''"
|
||||
|
|
@ -195,18 +171,18 @@
|
|||
<div class="col q-px-xs">
|
||||
<q-input
|
||||
v-if="TYPES_WITH_AMOUNT_ONLY.includes(expenses_store.current_expense?.type ?? 'EXPENSES')"
|
||||
v-model="expense_monetary_string"
|
||||
v-model="expenses_store.current_expense.amount"
|
||||
standout
|
||||
dense
|
||||
label-slot
|
||||
stack-label
|
||||
suffix="$"
|
||||
type="number"
|
||||
color="primary"
|
||||
input-class="text-right text-weight-bold"
|
||||
:input-style="'font-size: 1.2em;'"
|
||||
input-class="text-right text-weight-medium"
|
||||
input-style="font-size: 1.3em;"
|
||||
lazy-rules="ondemand"
|
||||
:rules="[rules.amountRequired]"
|
||||
@blur="saveAndConvert()"
|
||||
>
|
||||
<template #label>
|
||||
<span class="text-weight-bold text-accent text-uppercase text-caption">
|
||||
|
|
@ -217,19 +193,18 @@
|
|||
|
||||
<q-input
|
||||
v-else
|
||||
key="mileage"
|
||||
v-model="expenses_store.current_expense.mileage"
|
||||
standout
|
||||
dense
|
||||
stack-label
|
||||
clearable
|
||||
label-slot
|
||||
input-class="text-right"
|
||||
color="primary"
|
||||
stack-label
|
||||
suffix="km"
|
||||
type="number"
|
||||
input-class="text-right text-weight-medium"
|
||||
input-style="font-size: 1.3em;"
|
||||
color="primary"
|
||||
lazy-rules="ondemand"
|
||||
:rules="[rules.mileageRequired]"
|
||||
@blur="expenses_store.current_expense.amount = convertToMonetaryAmount(expense_monetary_string)"
|
||||
>
|
||||
<template #label>
|
||||
<span class="text-weight-bold text-accent text-uppercase text-caption">
|
||||
|
|
@ -248,7 +223,8 @@
|
|||
stack-label
|
||||
label-slot
|
||||
color="primary"
|
||||
type="text"
|
||||
input-class="text-weight-medium"
|
||||
input-style="font-size: 1.3em;"
|
||||
:maxlength="COMMENT_MAX_LENGTH"
|
||||
lazy-rules="ondemand"
|
||||
:rules="[rules.commentRequired]"
|
||||
|
|
@ -292,21 +268,10 @@
|
|||
<div class="col row full-width items-center">
|
||||
<q-space />
|
||||
|
||||
<q-btn
|
||||
v-if="expenses_store.mode === 'update'"
|
||||
flat
|
||||
dense
|
||||
class="col-auto q-ml-sm"
|
||||
icon="clear"
|
||||
color="negative"
|
||||
:label="$q.screen.gt.sm ? $t('shared.label.cancel') : ''"
|
||||
@click="$emit('onClickUpdateCancel')"
|
||||
/>
|
||||
|
||||
<q-btn
|
||||
push
|
||||
:disable="is_initial_expense"
|
||||
:color="is_initial_expense ? 'grey-5' : 'accent'"
|
||||
:disable="expenses_store.is_save_disabled"
|
||||
:color="expenses_store.is_save_disabled ? 'grey-5' : 'accent'"
|
||||
:icon="expenses_store.mode === 'update' ? 'save' : 'upload'"
|
||||
:label="expenses_store.mode === 'update' ? $t('shared.label.update') : $t('shared.label.add')"
|
||||
class="q-px-sm "
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@
|
|||
|
||||
<template>
|
||||
<div class="column items-center q-pa-none">
|
||||
<div class="col row full-width">
|
||||
<q-item-label class="col text-h6 text-weight-bolder text-uppercase q-py-sm q-px-md">
|
||||
<div class="col row full-width bg-primary">
|
||||
<q-item-label class="col text-h6 text-weight-bolder text-uppercase text-white q-py-sm q-px-md">
|
||||
{{ $t('timesheet.expense.title') }}
|
||||
</q-item-label>
|
||||
|
||||
|
|
@ -39,8 +39,8 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div class="col column items-end full-width q-pt-sm q-px-md">
|
||||
<div class="col-auto row items-center q-px-sm">
|
||||
<div class="col row flex-center full-width q-pt-sm q-px-md">
|
||||
<div class="col-auto row items-center q-px-md">
|
||||
<span
|
||||
v-if="$q.screen.gt.sm"
|
||||
class="col-auto text-uppercase text-weight-light text-accent q-mr-xs"
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
|
||||
<span
|
||||
class="col-auto text-weight-light"
|
||||
style="font-size: 2.5em; line-height: 1em;"
|
||||
style="font-size: 2em; line-height: 1em;"
|
||||
>
|
||||
{{ weekly_totals.expenses.toFixed(2) }}
|
||||
</span>
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-auto row items-center q-px-sm">
|
||||
<div class="col-auto row items-center q-px-md">
|
||||
<span
|
||||
v-if="$q.screen.gt.sm"
|
||||
class="col text-uppercase text-weight-light text-accent q-mr-xs"
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
|
||||
<span
|
||||
class="col-auto text-weight-light"
|
||||
style="font-size: 2.5em; line-height: 1em;"
|
||||
style="font-size: 2em; line-height: 1em;"
|
||||
>
|
||||
{{ weekly_totals.mileage.toFixed(1) }}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@
|
|||
import ExpenseDialogForm from 'src/modules/timesheets/components/expense-dialog-form.vue';
|
||||
|
||||
import { date } from 'quasar';
|
||||
import { computed, ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
||||
import { useExpensesStore } from 'src/stores/expense-store';
|
||||
import { useExpensesApi } from 'src/modules/timesheets/composables/use-expense-api';
|
||||
import { getExpenseIcon } from 'src/modules/timesheets/utils/expense.util';
|
||||
import { Expense } from 'src/modules/timesheets/models/expense.models';
|
||||
import type { Expense } from 'src/modules/timesheets/models/expense.models';
|
||||
|
||||
const expense = defineModel<Expense>({ required: true });
|
||||
|
||||
|
|
@ -17,16 +18,15 @@
|
|||
const expenses_api = useExpensesApi();
|
||||
|
||||
const is_showing_update_form = ref(false);
|
||||
const is_current_expense = computed(() => expense.value.id === expenses_store.current_expense.id);
|
||||
|
||||
const requestExpenseDeletion = async () => {
|
||||
await expenses_api.deleteExpenseById(expense.value.id);
|
||||
}
|
||||
|
||||
const onSaveUpdatesClicked = () => {
|
||||
is_showing_update_form.value = false;
|
||||
expenses_store.mode = 'create';
|
||||
expenses_store.current_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
||||
const onClickExpenseUpdate = () => {
|
||||
expenses_store.mode = 'update';
|
||||
expenses_store.current_expense = expense.value;
|
||||
expenses_store.initial_expense = unwrapAndClone(expense.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -36,8 +36,9 @@
|
|||
hide-expand-icon
|
||||
dense
|
||||
group="expenses"
|
||||
class="shadow-3 rounded-5 bg-dark"
|
||||
class="shadow-3 rounded-5 bg-dark q-my-sm"
|
||||
:class="expense.is_approved ? ' bg-accent text-white' : ''"
|
||||
@before-show="onClickExpenseUpdate()"
|
||||
>
|
||||
<template #header>
|
||||
<div class="col row items-center full-width">
|
||||
|
|
@ -47,7 +48,7 @@
|
|||
:name="getExpenseIcon(expense.type)"
|
||||
:color="expense.is_approved ? 'white' : ($q.dark.isActive ? 'white' : 'primary')"
|
||||
size="lg"
|
||||
class="q-px-sm"
|
||||
class="q-pr-md"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -58,8 +59,8 @@
|
|||
:class="expense.is_approved ? ' bg-accent text-white' : ''"
|
||||
style="font-size: 1.3em;"
|
||||
>
|
||||
{{ expense.type === 'MILEAGE' ? `${expense.mileage?.toFixed(1)} km` : `$
|
||||
${expense.amount.toFixed(2)}` }}
|
||||
{{ expense.type === 'MILEAGE' ? `${Number(expense.mileage).toFixed(1)} km` : `$
|
||||
${Number(expense.amount).toFixed(2)}` }}
|
||||
</span>
|
||||
|
||||
<!-- date label -->
|
||||
|
|
@ -123,10 +124,7 @@
|
|||
</div>
|
||||
|
||||
|
||||
<div
|
||||
class="col-auto row"
|
||||
:class="is_current_expense ? 'invisible' : ''"
|
||||
>
|
||||
<div class="col-auto">
|
||||
<q-icon
|
||||
v-if="expense.is_approved"
|
||||
name="verified"
|
||||
|
|
@ -141,16 +139,13 @@
|
|||
size="lg"
|
||||
icon="close"
|
||||
color="negative"
|
||||
class="q-py-none z-top q-my-xs"
|
||||
class="q-py-none q-my-xs"
|
||||
@click.stop="requestExpenseDeletion"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ExpenseDialogForm
|
||||
@on-click-update-cancel="is_showing_update_form = false"
|
||||
@on-click-save-updates="onSaveUpdatesClicked"
|
||||
/>
|
||||
<ExpenseDialogForm />
|
||||
</q-expansion-item>
|
||||
</template>
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
>
|
||||
<ExpenseDialogListItemMobile
|
||||
v-if="$q.screen.lt.md"
|
||||
v-model="expense.is_approved"
|
||||
v-model="expenses_list[index]!"
|
||||
:index="index"
|
||||
:expense="expense"
|
||||
:horizontal="horizontal"
|
||||
|
|
|
|||
|
|
@ -2,13 +2,21 @@
|
|||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import { useExpensesStore } from 'src/stores/expense-store';
|
||||
import ExpenseDialogList from 'src/modules/timesheets/components/expense-dialog-list.vue';
|
||||
import ExpenseDialogForm from 'src/modules/timesheets/components/expense-dialog-form.vue';
|
||||
import ExpenseDialogFormMobile from 'src/modules/timesheets/components/mobile/expense-dialog-form-mobile.vue';
|
||||
import ExpenseDialogHeader from 'src/modules/timesheets/components/expense-dialog-header.vue';
|
||||
import ExpenseDialogFormMobile from 'src/modules/timesheets/components/mobile/expense-dialog-form-mobile.vue';
|
||||
|
||||
import { date } from 'quasar';
|
||||
import { useExpensesStore } from 'src/stores/expense-store';
|
||||
import { Expense } from 'src/modules/timesheets/models/expense.models';
|
||||
|
||||
const expense_store = useExpensesStore();
|
||||
|
||||
const onClickExpenseCreate = () => {
|
||||
expense_store.mode = 'create';
|
||||
expense_store.current_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -29,26 +37,45 @@
|
|||
</q-inner-loading>
|
||||
|
||||
<q-card-section class="q-pa-none">
|
||||
<!-- <q-banner
|
||||
v-if="expenses_error"
|
||||
dense
|
||||
class="bg-red-2 col-auto text-negative q-mt-sm"
|
||||
>
|
||||
{{ expenses_error }}
|
||||
</q-banner> -->
|
||||
|
||||
<ExpenseDialogHeader />
|
||||
|
||||
<ExpenseDialogList />
|
||||
|
||||
<q-separator v-if="$q.screen.lt.md" spaced color="accent" size="2px" class="q-mx-md" />
|
||||
<q-separator
|
||||
v-if="$q.screen.lt.md"
|
||||
spaced
|
||||
color="accent"
|
||||
size="2px"
|
||||
class="q-mx-md"
|
||||
/>
|
||||
|
||||
<q-slide-transition @hide="expense_store.is_hiding_create_form = true" :duration="200">
|
||||
<div v-if="!expense_store.current_expense.is_approved && expense_store.mode !== 'update' && expense_store.is_hiding_create_form === false">
|
||||
<ExpenseDialogFormMobile v-if="$q.screen.lt.md" />
|
||||
<ExpenseDialogForm v-else/>
|
||||
<q-expansion-item
|
||||
v-model="expense_store.is_showing_create_form"
|
||||
hide-expand-icon
|
||||
dense
|
||||
group="expenses"
|
||||
@before-show="onClickExpenseCreate()"
|
||||
@hide="expense_store.mode = 'update'"
|
||||
header-class="bg-accent text-white"
|
||||
>
|
||||
<template #header>
|
||||
<div class="row items-center">
|
||||
<span class="col-auto text-uppercase text-weight-bold text-h6 q-ml-lg q-mr-sm">
|
||||
{{ $t('timesheet.expense.add_expense') }}
|
||||
</span>
|
||||
<q-icon
|
||||
v-if="expense_store.mode !== 'create'"
|
||||
name="las la-plus-square"
|
||||
size="md"
|
||||
class="col-auto"
|
||||
/>
|
||||
</div>
|
||||
</q-slide-transition>
|
||||
</template>
|
||||
|
||||
<ExpenseDialogFormMobile v-if="$q.screen.lt.md" />
|
||||
|
||||
<ExpenseDialogForm v-else />
|
||||
</q-expansion-item>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import { useExpensesStore } from 'src/stores/expense-store';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { useExpensesApi } from 'src/modules/timesheets/composables/use-expense-api';
|
||||
import { convertToMonetaryAmount, getExpenseIcon, useExpenseRules } from 'src/modules/timesheets/utils/expense.util';
|
||||
import { getExpenseIcon, useExpenseRules } from 'src/modules/timesheets/utils/expense.util';
|
||||
import { type ExpenseType, TYPES_WITH_AMOUNT_ONLY } from 'src/modules/timesheets/models/expense.models';
|
||||
|
||||
interface ExpenseOption {
|
||||
|
|
@ -189,7 +189,6 @@
|
|||
:input-style="'font-size: 1.2em;'"
|
||||
lazy-rules="ondemand"
|
||||
:rules="[rules.amountRequired]"
|
||||
@blur="expenses_store.current_expense.amount = convertToMonetaryAmount(expenses_store.current_expense.amount)"
|
||||
>
|
||||
<template #label>
|
||||
<span class="text-weight-bold text-accent text-uppercase text-caption">
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
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 { getExpenseIcon } from 'src/modules/timesheets/utils/expense.util';
|
||||
|
|
@ -22,7 +21,6 @@
|
|||
const expenses_api = useExpensesApi();
|
||||
|
||||
const refresh_key = ref(1);
|
||||
const background_class = computed(() => deepEqual(expense, expenses_store.current_expense) ? '' : '');
|
||||
const approved_class = computed(() => expense.is_approved ? ' bg-accent text-white' : '')
|
||||
const is_showing_update_form = ref(false);
|
||||
|
||||
|
|
@ -33,7 +31,7 @@
|
|||
const onUpdateClicked = () => {
|
||||
if (expense.is_approved) return;
|
||||
|
||||
if (deepEqual(expense, expenses_store.current_expense)) {
|
||||
if (JSON.stringify(expense) === JSON.stringify(expenses_store.current_expense)) {
|
||||
expenses_store.mode = 'create';
|
||||
expenses_store.current_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
||||
is_showing_update_form.value = false;
|
||||
|
|
@ -56,7 +54,7 @@
|
|||
>
|
||||
<template
|
||||
#right
|
||||
v-if="$q.screen.lt.md && !expenses_store.is_hiding_create_form && !expense.is_approved"
|
||||
v-if="$q.screen.lt.md && expenses_store.is_showing_create_form && !expense.is_approved"
|
||||
>
|
||||
<q-icon name="delete" />
|
||||
</template>
|
||||
|
|
@ -65,7 +63,7 @@
|
|||
:key="refresh_key"
|
||||
clickable
|
||||
class="row q-py-none q-pa-xs rounded-5 full-width"
|
||||
:class="background_class + approved_class"
|
||||
:class="approved_class"
|
||||
@click="onUpdateClicked"
|
||||
>
|
||||
<div class="column col">
|
||||
|
|
@ -137,11 +135,11 @@
|
|||
</q-slide-item>
|
||||
|
||||
<q-slide-transition
|
||||
@hide="expenses_store.is_hiding_create_form = false"
|
||||
@hide="expenses_store.is_showing_create_form = true"
|
||||
:duration="200"
|
||||
>
|
||||
<ExpenseDialogFormMobile
|
||||
v-if="is_showing_update_form && expenses_store.is_hiding_create_form"
|
||||
v-if="is_showing_update_form && !expenses_store.is_showing_create_form"
|
||||
class="q-mt-sm q-pa-sm"
|
||||
@on-click-update-cancel="onUpdateClicked"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ export const ShiftService = {
|
|||
},
|
||||
|
||||
updateShifts: async (existing_shifts: Shift[]):Promise<BackendResponse<Shift>> => {
|
||||
console.log('sent shifts: ', existing_shifts)
|
||||
const response = await api.patch(`/shift/update`, existing_shifts);
|
||||
return response.data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,28 +24,3 @@ export const useExpenseRules = (t: (_key: string) => string) => {
|
|||
commentRequired,
|
||||
};
|
||||
};
|
||||
|
||||
export const convertToMonetaryAmount = (amount: number | string): number => {
|
||||
if (typeof amount === 'number') return Number(amount.toFixed(2));
|
||||
|
||||
if (typeof amount === 'string') {
|
||||
try {
|
||||
let cleaned_amount = amount.replace(/[^\d.]/g, '');
|
||||
const first_dot = cleaned_amount.indexOf('.');
|
||||
|
||||
if (first_dot !== -1) {
|
||||
cleaned_amount =
|
||||
cleaned_amount.slice(0, first_dot + 1) +
|
||||
cleaned_amount
|
||||
.slice(first_dot + 1, first_dot + 3)
|
||||
.replace(/\./g, '');
|
||||
}
|
||||
|
||||
return Number(cleaned_amount);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
|
@ -10,16 +10,12 @@ export const isShiftOverlap = (shifts: Shift[] | SchedulePresetShift[]): boolean
|
|||
end: date.extractDate(`2000-01-01 ${shift.end_time}`, 'YYYY-MM-DD HH:mm').getTime(),
|
||||
}));
|
||||
|
||||
console.log('parsed_shifts: ', parsed_shifts);
|
||||
|
||||
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;
|
||||
console.log('times(a start, b start, a end, b end): ', parsed_shift_a.start, parsed_shift_b.start, parsed_shift_a.end, parsed_shift_b.end);
|
||||
console.log('result: ', Math.max(parsed_shift_a.start, parsed_shift_b.start) < Math.min(parsed_shift_a.end, parsed_shift_b.end))
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export default defineRouter(function (/* { store, ssrContext } */) {
|
|||
const result = await authStore.getProfile() ?? { status: 400, message: 'unknown error occured' };
|
||||
|
||||
if ((destinationPage.meta.requiresAuth && !authStore.isAuthorizedUser) || (result.status >= 400 && destinationPage.name !== RouteNames.LOGIN)) {
|
||||
console.log('no user account found');
|
||||
console.error('no user account found');
|
||||
return { name: 'login' };
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
import { ref } from "vue";
|
||||
import { date } from "quasar";
|
||||
import { computed, ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { useTimesheetStore } from "src/stores/timesheet-store";
|
||||
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 timesheet_store = useTimesheetStore();
|
||||
const is_open = ref(false);
|
||||
const is_loading = ref(false);
|
||||
const is_hiding_create_form = ref(false);
|
||||
const is_showing_create_form = ref(true);
|
||||
const mode = ref<'create' | 'update' | 'delete'>('create');
|
||||
const current_expense = ref<Expense>(new Expense(date.formatDate(new Date(), 'YYYY-MM-DD')));
|
||||
const initial_expense = ref<Expense>(new Expense(date.formatDate(new Date(), 'YYYY-MM-DD')));
|
||||
const is_save_disabled = computed(() => JSON.stringify(current_expense.value) === JSON.stringify(initial_expense.value))
|
||||
|
||||
const open = (): void => {
|
||||
is_open.value = true;
|
||||
|
|
@ -25,7 +26,7 @@ export const useExpensesStore = defineStore('expenses', () => {
|
|||
|
||||
const close = () => {
|
||||
is_open.value = false;
|
||||
is_hiding_create_form.value = false;
|
||||
is_showing_create_form.value = true;
|
||||
};
|
||||
|
||||
const upsertExpense = async (expense: Expense): Promise<boolean> => {
|
||||
|
|
@ -51,10 +52,11 @@ export const useExpensesStore = defineStore('expenses', () => {
|
|||
return {
|
||||
is_open,
|
||||
is_loading,
|
||||
is_hiding_create_form,
|
||||
is_showing_create_form,
|
||||
mode,
|
||||
current_expense,
|
||||
initial_expense,
|
||||
is_save_disabled,
|
||||
open,
|
||||
upsertExpense,
|
||||
deleteExpenseById,
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ 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;
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ export const useUiStore = defineStore('ui', () => {
|
|||
Dark.set(user_preferences.value.is_dark_mode ?? "auto");
|
||||
locale.value = user_preferences.value.display_language;
|
||||
}
|
||||
console.log('quasar dark mode: ', q.dark.mode, 'preferences: ', user_preferences.value.is_dark_mode);
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,5 @@ export const getCurrentPayPeriod = (today = new Date()): number => {
|
|||
|
||||
const current_period = (periods_since_anchor % periods_per_year) + 1;
|
||||
|
||||
console.log(current_period);
|
||||
return current_period;
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
import { unwrapAndClone } from "src/utils/unwrap-and-clone";
|
||||
|
||||
/**
|
||||
* Internal recursive function comparing two plain values.
|
||||
*/
|
||||
const _deepEqualRecursive = (a: unknown, b: unknown): boolean => {
|
||||
if (a === b) return true;
|
||||
|
||||
if (a == null || b == null || typeof a !== "object" || typeof b !== "object") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle arrays
|
||||
if (Array.isArray(a) && Array.isArray(b)) {
|
||||
if (a.length !== b.length) return false;
|
||||
return a.every((val, i) => _deepEqualRecursive(val, b[i]));
|
||||
} else if (Array.isArray(a) || Array.isArray(b)) {
|
||||
return false; // one is array, other is not
|
||||
}
|
||||
|
||||
const aKeys = Object.keys(a as Record<string, unknown>);
|
||||
const bKeys = Object.keys(b as Record<string, unknown>);
|
||||
|
||||
if (aKeys.length !== bKeys.length) return false;
|
||||
|
||||
return aKeys.every((key) =>
|
||||
_deepEqualRecursive(
|
||||
(a as Record<string, unknown>)[key],
|
||||
(b as Record<string, unknown>)[key]
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deep equality check that normalizes reactive objects first.
|
||||
*/
|
||||
export const deepEqual = (given: unknown, expected: unknown): boolean => {
|
||||
const a = unwrapAndClone(given as object);
|
||||
const b = unwrapAndClone(expected as object);
|
||||
return _deepEqualRecursive(a, b);
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user