fix(timesheet): fix issue with expense not updating properly in approval module
Also rework expense item appearance in list to better divide space between components for visual clarity.
This commit is contained in:
parent
c6187305d9
commit
1271d1eb61
|
|
@ -66,10 +66,13 @@ export default defineConfig((ctx) => {
|
|||
// polyfillModulePreload: true,
|
||||
// distDir
|
||||
|
||||
extendViteConf: (_config) => ({
|
||||
extendViteConf: (config) => ({
|
||||
optimizeDeps: {
|
||||
exclude: ['tesseract.js']
|
||||
}
|
||||
},
|
||||
define: {
|
||||
__VUE_PROD_DEVTOOLS__: config.mode !== 'production'
|
||||
},
|
||||
}),
|
||||
// viteVuePluginOptions: {},
|
||||
|
||||
|
|
|
|||
|
|
@ -2,20 +2,21 @@
|
|||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import DetailsDialogChartHoursWorked from 'src/modules/timesheet-approval/components/details-dialog-chart-hours-worked.vue';
|
||||
import DetailsDialogChartShiftTypes from 'src/modules/timesheet-approval/components/details-dialog-chart-shift-types.vue';
|
||||
import DetailsDialogChartExpenses from 'src/modules/timesheet-approval/components/details-dialog-chart-expenses.vue';
|
||||
import TimesheetWrapper from 'src/modules/timesheets/components/timesheet-wrapper.vue';
|
||||
import ExpenseDialogList from 'src/modules/timesheets/components/expense-dialog-list.vue';
|
||||
import ExpenseDialogForm from 'src/modules/timesheets/components/expense-dialog-form.vue';
|
||||
|
||||
import { date } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { useTimesheetApprovalApi } from '../composables/use-timesheet-approval-api';
|
||||
import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
|
||||
import { useExpensesStore } from 'src/stores/expense-store';
|
||||
import { Expense } from 'src/modules/timesheets/models/expense.models';
|
||||
import { date } from 'quasar';
|
||||
|
||||
// ========== state ========================================
|
||||
|
||||
|
|
@ -25,6 +26,7 @@
|
|||
const timesheetApprovalApi = useTimesheetApprovalApi();
|
||||
const shiftApi = useShiftApi();
|
||||
const isDialogOpen = ref(false);
|
||||
const refreshKey = ref(0);
|
||||
|
||||
// ========== computed ========================================
|
||||
|
||||
|
|
@ -60,13 +62,22 @@
|
|||
expenseStore.is_showing_create_form = false;
|
||||
}
|
||||
|
||||
const onClickExpenseCreate = () => {
|
||||
const onClickNewExpense = () => {
|
||||
expenseStore.mode = 'create';
|
||||
if (timesheetStore.pay_period)
|
||||
expenseStore.current_expense = new Expense(timesheetStore.pay_period.period_start);
|
||||
else
|
||||
expenseStore.current_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
||||
}
|
||||
|
||||
const onClickSaveNewExpense = () => {
|
||||
expenseStore.is_showing_create_form = false;
|
||||
}
|
||||
|
||||
const onShowDetailsDialog = () => {
|
||||
isDialogOpen.value = true;
|
||||
expenseStore.is_showing_create_form = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -77,7 +88,7 @@
|
|||
transition-show="jump-down"
|
||||
transition-hide="jump-down"
|
||||
backdrop-filter="blur(6px)"
|
||||
@show="isDialogOpen = true"
|
||||
@show="onShowDetailsDialog"
|
||||
@hide="isDialogOpen = false"
|
||||
@before-hide="timesheetStore.getTimesheetOverviews"
|
||||
>
|
||||
|
|
@ -154,7 +165,10 @@
|
|||
class="q-mx-md"
|
||||
/>
|
||||
|
||||
<ExpenseDialogList mode="approval" />
|
||||
<ExpenseDialogList
|
||||
mode="approval"
|
||||
:key="refreshKey + 1"
|
||||
/>
|
||||
|
||||
<q-expansion-item
|
||||
v-if="!isApproved"
|
||||
|
|
@ -162,7 +176,7 @@
|
|||
hide-expand-icon
|
||||
:dense="!$q.platform.is.mobile"
|
||||
group="expenses"
|
||||
@show="onClickExpenseCreate()"
|
||||
@show="onClickNewExpense()"
|
||||
header-class="bg-accent text-white q-mx-md rounded-5"
|
||||
>
|
||||
<template #header>
|
||||
|
|
@ -180,7 +194,11 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<ExpenseDialogForm :email="timesheetStore.current_pay_period_overview?.email"/>
|
||||
<ExpenseDialogForm
|
||||
:email="timesheetStore.current_pay_period_overview?.email"
|
||||
:key="refreshKey"
|
||||
@click-save="onClickSaveNewExpense"
|
||||
/>
|
||||
</q-expansion-item>
|
||||
|
||||
<q-separator
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ watch(selected_company, (company) => {
|
|||
left-label
|
||||
color="white"
|
||||
dense
|
||||
:label="$t(company.label)"
|
||||
:label="company.label"
|
||||
:val="company.value"
|
||||
checked-icon="radio_button_checked"
|
||||
unchecked-icon="radio_button_unchecked"
|
||||
|
|
|
|||
|
|
@ -2,16 +2,14 @@
|
|||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import { date } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, inject, onMounted, ref } from 'vue';
|
||||
import { computed, onMounted, 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 { getExpenseIcon, useExpenseRules } from 'src/modules/timesheets/utils/expense.util';
|
||||
import { Expense, type ExpenseOption, TYPES_WITH_AMOUNT_ONLY } from 'src/modules/timesheets/models/expense.models';
|
||||
import { useAuthStore } from 'src/stores/auth-store';
|
||||
|
||||
// ================= state ======================
|
||||
|
||||
|
|
@ -19,90 +17,86 @@
|
|||
|
||||
const expense = defineModel<Expense>({ default: new Expense(new Date().toISOString().slice(0, 10)) })
|
||||
const file = defineModel<File>('file');
|
||||
|
||||
const {email} = defineProps<{
|
||||
const { email } = defineProps<{
|
||||
email?: string | undefined;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
'clickSave': [void];
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const ui_store = useUiStore();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const expenses_store = useExpensesStore();
|
||||
const auth_store = useAuthStore();
|
||||
const expenses_api = useExpensesApi();
|
||||
const is_navigator_open = ref(false);
|
||||
const timesheetStore = useTimesheetStore();
|
||||
const expenseStore = useExpensesStore();
|
||||
const expensesApi = useExpensesApi();
|
||||
const isNavigatorOpen = ref(false);
|
||||
const rules = useExpenseRules(t);
|
||||
|
||||
|
||||
const expense_options: ExpenseOption[] = [
|
||||
{ label: t('timesheet.expense.types.PER_DIEM'), value: 'PER_DIEM', icon: getExpenseIcon('PER_DIEM') },
|
||||
const expenseOptions: ExpenseOption[] = [
|
||||
{ label: t('timesheet.expense.types.EXPENSES'), value: 'EXPENSES', icon: getExpenseIcon('EXPENSES') },
|
||||
{ label: t('timesheet.expense.types.PER_DIEM'), value: 'PER_DIEM', icon: getExpenseIcon('PER_DIEM') },
|
||||
{ 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<ExpenseOption | undefined>();
|
||||
const employeeEmail = inject<string>('employeeEmail');
|
||||
const expenseSelected = ref<ExpenseOption | undefined>();
|
||||
|
||||
// ================== computed ===================
|
||||
|
||||
const period_start_date = computed(() => timesheet_store.pay_period?.period_start.replaceAll('-', '/') ?? '');
|
||||
const period_end_date = computed(() => timesheet_store.pay_period?.period_end.replaceAll('-', '/') ?? '');
|
||||
const period_start_date = computed(() => timesheetStore.pay_period?.period_start.replaceAll('-', '/') ?? '');
|
||||
const period_end_date = computed(() => timesheetStore.pay_period?.period_end.replaceAll('-', '/') ?? '');
|
||||
const isSaveDisabled = computed(() =>
|
||||
JSON.stringify(expenseStore.current_expense) === JSON.stringify(expenseStore.initial_expense)
|
||||
);
|
||||
|
||||
// ==================== method =======================
|
||||
|
||||
const openDatePicker = () => {
|
||||
is_navigator_open.value = true;
|
||||
if (expenses_store.current_expense.date === undefined) {
|
||||
expenses_store.current_expense.date = timesheet_store.pay_period?.period_start ?? '';
|
||||
isNavigatorOpen.value = true;
|
||||
if (expenseStore.current_expense.date === undefined) {
|
||||
expenseStore.current_expense.date = timesheetStore.pay_period?.period_start ?? '';
|
||||
}
|
||||
};
|
||||
|
||||
const closeDatePicker = (date: string) => {
|
||||
is_navigator_open.value = false;
|
||||
expenses_store.current_expense.date = date;
|
||||
isNavigatorOpen.value = false;
|
||||
expenseStore.current_expense.date = date;
|
||||
}
|
||||
|
||||
const requestExpenseCreationOrUpdate = async () => {
|
||||
if (file.value)
|
||||
await expenses_api.upsertExpense(
|
||||
expenses_store.current_expense,
|
||||
email ?? employeeEmail ?? auth_store.user?.email ?? 'MISSING_EMAIL',
|
||||
file.value
|
||||
);
|
||||
else
|
||||
await expenses_api.upsertExpense(
|
||||
expenses_store.current_expense,
|
||||
email ?? employeeEmail ?? auth_store.user?.email ?? 'MISSING_EMAIL'
|
||||
);
|
||||
const success = await expensesApi.upsertExpense(expenseStore.current_expense, email, file.value);
|
||||
|
||||
expenses_store.is_showing_create_form = true;
|
||||
expenses_store.mode = 'create';
|
||||
expenses_store.current_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
||||
if (success) {
|
||||
expenseStore.is_showing_create_form = false;
|
||||
emit('clickSave');
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (expense.value)
|
||||
expense_selected.value = expense_options.find(expense_option => expense_option.value === expense.value.type);
|
||||
expenseSelected.value = expenseOptions.find(expense_option => expense_option.value === expense.value.type);
|
||||
else
|
||||
expense_selected.value = expense_options[1];
|
||||
expenseSelected.value = expenseOptions[0];
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="!expenseStore.current_expense.is_approved"
|
||||
class="full-width q-mt-md q-px-md"
|
||||
>
|
||||
<q-form
|
||||
v-if="!expenses_store.current_expense.is_approved"
|
||||
flat
|
||||
@submit.prevent="requestExpenseCreationOrUpdate"
|
||||
class="full-width q-mt-md q-px-md"
|
||||
>
|
||||
<div
|
||||
class="row justify-between items-start rounded-5 q-pb-sm"
|
||||
:class="expenses_store.mode === 'create' ? 'q-px-lg' : ''"
|
||||
:class="expenseStore.mode === 'create' ? 'q-px-lg' : ''"
|
||||
>
|
||||
<!-- date selection input -->
|
||||
<div class="col q-px-xs">
|
||||
<q-input
|
||||
v-model="expenses_store.current_expense.date"
|
||||
v-model="expenseStore.current_expense.date"
|
||||
dense
|
||||
standout
|
||||
readonly
|
||||
|
|
@ -123,13 +117,13 @@
|
|||
/>
|
||||
|
||||
<q-dialog
|
||||
v-model="is_navigator_open"
|
||||
v-model="isNavigatorOpen"
|
||||
transition-show="jump-right"
|
||||
transition-hide="jump-right"
|
||||
class="z-top"
|
||||
>
|
||||
<q-date
|
||||
v-model="expenses_store.current_expense.date"
|
||||
v-model="expenseStore.current_expense.date"
|
||||
mask="YYYY-MM-DD"
|
||||
event-color="accent"
|
||||
:options="date => date >= period_start_date && date <= period_end_date"
|
||||
|
|
@ -149,10 +143,10 @@
|
|||
<!-- expenses type selection -->
|
||||
<div class="col q-px-xs">
|
||||
<q-select
|
||||
v-model="expense_selected"
|
||||
v-model="expenseSelected"
|
||||
standout
|
||||
dense
|
||||
:options="expense_options"
|
||||
:options="expenseOptions"
|
||||
hide-dropdown-icon
|
||||
stack-label
|
||||
label-slot
|
||||
|
|
@ -165,7 +159,7 @@
|
|||
options-selected-class="text-weight-bolder text-white bg-accent"
|
||||
popup-content-style="border: 2px solid var(--q-accent)"
|
||||
:rules="[rules.typeRequired]"
|
||||
@update:model-value="option => expenses_store.current_expense.type = option.value"
|
||||
@update:model-value="option => expenseStore.current_expense.type = option.value"
|
||||
>
|
||||
<template #label>
|
||||
<span class="text-weight-bold text-accent text-uppercase text-caption">
|
||||
|
|
@ -196,8 +190,8 @@
|
|||
<!-- amount input -->
|
||||
<div class="col q-px-xs">
|
||||
<q-input
|
||||
v-if="TYPES_WITH_AMOUNT_ONLY.includes(expenses_store.current_expense?.type ?? 'EXPENSES')"
|
||||
v-model="expenses_store.current_expense.amount"
|
||||
v-if="TYPES_WITH_AMOUNT_ONLY.includes(expenseStore.current_expense?.type ?? 'EXPENSES')"
|
||||
v-model.number="expenseStore.current_expense.amount"
|
||||
standout
|
||||
dense
|
||||
label-slot
|
||||
|
|
@ -219,7 +213,7 @@
|
|||
|
||||
<q-input
|
||||
v-else
|
||||
v-model="expenses_store.current_expense.mileage"
|
||||
v-model="expenseStore.current_expense.mileage"
|
||||
standout
|
||||
dense
|
||||
label-slot
|
||||
|
|
@ -243,7 +237,7 @@
|
|||
<!-- employee comment input -->
|
||||
<div class="col q-px-xs">
|
||||
<q-input
|
||||
v-model="expenses_store.current_expense.comment"
|
||||
v-model="expenseStore.current_expense.comment"
|
||||
standout
|
||||
dense
|
||||
stack-label
|
||||
|
|
@ -296,16 +290,17 @@
|
|||
|
||||
<q-btn
|
||||
push
|
||||
: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')"
|
||||
:disable="isSaveDisabled"
|
||||
:color="isSaveDisabled ? 'grey-5' : 'accent'"
|
||||
:icon="expenseStore.mode === 'update' ? 'save' : 'upload'"
|
||||
:label="expenseStore.mode === 'update' ? $t('shared.label.update') : $t('shared.label.add')"
|
||||
class="q-px-sm "
|
||||
:class="expenses_store.mode === 'create' ? 'q-mr-lg q-mb-md' : 'q-mb-sm q-ml-lg'"
|
||||
:class="expenseStore.mode === 'create' ? 'q-mr-lg q-mb-md' : 'q-mb-sm q-ml-lg'"
|
||||
type="submit"
|
||||
/>
|
||||
</div>
|
||||
</q-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@
|
|||
>
|
||||
import ExpenseDialogForm from 'src/modules/timesheets/components/expense-dialog-form.vue';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, ref, toRaw } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { date, Notify } from 'quasar';
|
||||
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
||||
import { useExpensesStore } from 'src/stores/expense-store';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { useExpensesApi } from 'src/modules/timesheets/composables/use-expense-api';
|
||||
|
|
@ -23,10 +22,10 @@
|
|||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const expenses_api = useExpensesApi();
|
||||
const expenses_store = useExpensesStore();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const is_showing_update_form = ref(false);
|
||||
const expensesApi = useExpensesApi();
|
||||
const expenseStore = useExpensesStore();
|
||||
const timesheetStore = useTimesheetStore();
|
||||
const isShowingUpdateForm = ref(false);
|
||||
|
||||
// ========== computed ===================================
|
||||
|
||||
|
|
@ -37,30 +36,30 @@
|
|||
// ===================== methods =========================
|
||||
|
||||
const requestExpenseDeletion = async () => {
|
||||
await expenses_api.deleteExpenseById(expense.value.id);
|
||||
await expensesApi.deleteExpenseById(expense.value.id);
|
||||
}
|
||||
|
||||
const onClickExpenseUpdate = () => {
|
||||
if (expense.value.is_approved) return;
|
||||
|
||||
expenses_store.mode = 'update';
|
||||
expenses_store.current_expense = expense.value;
|
||||
expenses_store.initial_expense = unwrapAndClone(expense.value);
|
||||
expenseStore.mode = 'update';
|
||||
expenseStore.current_expense = structuredClone(toRaw(expense.value));
|
||||
expenseStore.initial_expense = structuredClone(toRaw(expense.value));
|
||||
}
|
||||
|
||||
const onClickApproval = async () => {
|
||||
expenses_store.current_expense = unwrapAndClone(expense.value);
|
||||
expenses_store.current_expense.is_approved = !expenses_store.current_expense.is_approved;
|
||||
expenseStore.current_expense = structuredClone(toRaw(expense.value));
|
||||
expenseStore.current_expense.is_approved = !expenseStore.current_expense.is_approved;
|
||||
|
||||
const success = await expenses_store.upsertExpense(
|
||||
expenses_store.current_expense,
|
||||
timesheet_store.current_pay_period_overview?.email
|
||||
const success = await expenseStore.upsertExpense(
|
||||
expenseStore.current_expense,
|
||||
timesheetStore.current_pay_period_overview?.email
|
||||
);
|
||||
|
||||
if (success) {
|
||||
expense.value.is_approved = !expense.value.is_approved;
|
||||
} else {
|
||||
expenses_store.current_expense.is_approved = !expenses_store.current_expense.is_approved;
|
||||
expenseStore.current_expense.is_approved = !expenseStore.current_expense.is_approved;
|
||||
Notify.create({
|
||||
message: t('timesheet.errors.UPDATE_ERROR'),
|
||||
color: "negative"
|
||||
|
|
@ -68,16 +67,25 @@
|
|||
}
|
||||
}
|
||||
|
||||
const getEmployeeEmail = () => {
|
||||
if (mode === 'approval')
|
||||
return timesheetStore.current_pay_period_overview?.email;
|
||||
}
|
||||
|
||||
const onClickAttachment = async () => {
|
||||
expenses_store.isShowingAttachmentDialog = true;
|
||||
await expenses_store.getAttachmentURL(expense.value.attachment_key);
|
||||
console.log('image url: ', expenses_store.attachmentURL);
|
||||
expenseStore.isShowingAttachmentDialog = true;
|
||||
await expenseStore.getAttachmentURL(expense.value.attachment_key);
|
||||
console.log('image url: ', expenseStore.attachmentURL);
|
||||
}
|
||||
|
||||
const hideUpdateForm = () => {
|
||||
isShowingUpdateForm.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-expansion-item
|
||||
v-model="is_showing_update_form"
|
||||
v-model="isShowingUpdateForm"
|
||||
hide-expand-icon
|
||||
dense
|
||||
group="expenses"
|
||||
|
|
@ -106,7 +114,7 @@
|
|||
</div>
|
||||
|
||||
<!-- amount or mileage section -->
|
||||
<div class="col column">
|
||||
<div class="col-auto column q-pr-md">
|
||||
<span
|
||||
class="text-weight-bolder"
|
||||
:class="expense.is_approved ? ' bg-accent text-white' : ''"
|
||||
|
|
@ -122,81 +130,74 @@
|
|||
:class="expense.is_approved ? ' bg-accent text-white' : ''"
|
||||
>
|
||||
{{ $d(date.extractDate(expense.date, 'YYYY-MM-DD'), {
|
||||
month: 'short', day: 'numeric', weekday:
|
||||
'long'
|
||||
month: 'long', day: 'numeric', weekday: 'long'
|
||||
}) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- attachment file icon -->
|
||||
<div class="col row items-center justify-start">
|
||||
<q-btn
|
||||
push
|
||||
:disable="expense.attachment_name === undefined"
|
||||
:color="attachmentButtonColor"
|
||||
:text-color="expense.is_approved ? 'accent' : 'white'"
|
||||
class="col-auto q-px-sm q-mr-sm"
|
||||
icon="attach_file"
|
||||
@click.stop="onClickAttachment"
|
||||
/>
|
||||
|
||||
<q-item-label class="col">
|
||||
<span v-if="expense.attachment_name">
|
||||
{{ expense.attachment_name }}
|
||||
</span>
|
||||
<q-separator vertical spaced class="q-my-xs"/>
|
||||
|
||||
<!-- comments section -->
|
||||
<div class="col column">
|
||||
<div class="col row items-center">
|
||||
<span
|
||||
v-else
|
||||
class="text-italic text-blue-grey-5 text-uppercase"
|
||||
class="col-auto text-weight-medium text-accent text-uppercase q-pr-md"
|
||||
style="font-size: 1.2em;"
|
||||
>
|
||||
{{ $t('timesheet.expense.no_attachment') }}
|
||||
</span>
|
||||
</q-item-label>
|
||||
</div>
|
||||
|
||||
<!-- comment section -->
|
||||
<div class="col column no-wrap">
|
||||
<span class="col-auto text-weight-bold text-accent text-uppercase text-caption">
|
||||
{{ $t('timesheet.expense.employee_comment') }}
|
||||
{{ $t('timesheet.expense.employee_comment') }} :
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="col ellipsis"
|
||||
class="col"
|
||||
:class="expense.is_approved ? ' bg-accent text-white' : ''"
|
||||
style="font-size: 1em;"
|
||||
>
|
||||
{{ expense.comment }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<q-separator class="q-mr-md"/>
|
||||
|
||||
<div class="col row items-center">
|
||||
<span
|
||||
class="col-auto text-weight-medium text-accent text-uppercase q-pr-md"
|
||||
style="font-size: 1.2em; "
|
||||
:style="expense.supervisor_comment ? '' : 'filter: grayscale(1);'"
|
||||
>
|
||||
{{ $t('timesheet.expense.supervisor_comment') }} :
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="col"
|
||||
:class="expense.is_approved ? ' bg-accent text-white' : ''"
|
||||
>
|
||||
{{ expense.supervisor_comment }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- attachment -->
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
flat
|
||||
size="lg"
|
||||
:disable="expense.attachment_name === undefined"
|
||||
:color="attachmentButtonColor"
|
||||
class="col-auto q-px-sm q-mr-sm"
|
||||
:icon="expense.attachment_key ? 'image' : 'hide_image'"
|
||||
@click.stop="onClickAttachment"
|
||||
>
|
||||
<q-tooltip
|
||||
anchor="top middle"
|
||||
self="center middle"
|
||||
:offset="[0, 20]"
|
||||
class="bg-accent text-uppercase text-weight-bold"
|
||||
>
|
||||
{{ expense.comment }}
|
||||
{{ expense.attachment_name ?? $t('timesheet.expense.no_attachment') }}
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
|
||||
<!-- supervisor comment section -->
|
||||
<div
|
||||
v-if="expense.supervisor_comment"
|
||||
class="col column"
|
||||
>
|
||||
<span class="col-auto text-weight-bold text-accent text-uppercase text-caption">
|
||||
{{ $t('timesheet.expense.supervisor_comment') }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="col"
|
||||
:class="expense.is_approved ? ' bg-accent text-white' : ''"
|
||||
style="font-size: 1.3em;"
|
||||
>
|
||||
{{ expense.supervisor_comment }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- buttons -->
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
v-if="mode === 'approval'"
|
||||
|
|
@ -240,6 +241,10 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<ExpenseDialogForm v-model="expense" />
|
||||
<ExpenseDialogForm
|
||||
v-model="expense"
|
||||
:email="getEmployeeEmail()"
|
||||
@click-save="hideUpdateForm"
|
||||
/>
|
||||
</q-expansion-item>
|
||||
</template>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import { computed, inject } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import ExpenseDialogListItem from 'src/modules/timesheets/components/expense-dialog-list-item.vue';
|
||||
import ExpenseDialogListItemMobile from 'src/modules/timesheets/components/mobile/expense-dialog-list-item-mobile.vue';
|
||||
|
|
@ -23,10 +23,6 @@
|
|||
}
|
||||
return [];
|
||||
})
|
||||
|
||||
// ==================== methods ========================
|
||||
|
||||
inject( 'employeeEmail', mode === 'approval' ? timesheet_store.current_pay_period_overview?.email : undefined);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -45,7 +41,7 @@
|
|||
</q-item-label>
|
||||
|
||||
<div
|
||||
v-for="(expense, index) in expenses_list"
|
||||
v-for="(_expense, index) in expenses_list"
|
||||
:key="index"
|
||||
>
|
||||
<ExpenseDialogListItemMobile
|
||||
|
|
|
|||
|
|
@ -11,10 +11,12 @@
|
|||
import { date } from 'quasar';
|
||||
import { useExpensesStore } from 'src/stores/expense-store';
|
||||
import { Expense } from 'src/modules/timesheets/models/expense.models';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const expense_store = useExpensesStore();
|
||||
const refreshKey = ref(0);
|
||||
|
||||
const { isApproved = false} = defineProps<{
|
||||
const { isApproved = false } = defineProps<{
|
||||
isApproved?: boolean;
|
||||
}>();
|
||||
|
||||
|
|
@ -47,7 +49,7 @@
|
|||
<q-card-section class="q-pa-none">
|
||||
<ExpenseDialogHeader />
|
||||
|
||||
<ExpenseDialogList />
|
||||
<ExpenseDialogList :key="refreshKey + 1" />
|
||||
|
||||
<q-expansion-item
|
||||
v-if="!isApproved"
|
||||
|
|
@ -56,6 +58,7 @@
|
|||
:dense="!$q.platform.is.mobile"
|
||||
group="expenses"
|
||||
@show="onClickExpenseCreate()"
|
||||
@after-hide="refreshKey += 1"
|
||||
header-class="bg-accent text-white"
|
||||
>
|
||||
<template #header>
|
||||
|
|
@ -74,8 +77,10 @@
|
|||
</template>
|
||||
|
||||
<ExpenseDialogFormMobile v-if="$q.platform.is.mobile" />
|
||||
|
||||
<ExpenseDialogForm v-else />
|
||||
<ExpenseDialogForm
|
||||
v-else
|
||||
:key="refreshKey"
|
||||
/>
|
||||
</q-expansion-item>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
import { useExpensesStore } from "src/stores/expense-store";
|
||||
import { useTimesheetStore } from "src/stores/timesheet-store";
|
||||
import { Expense } from "src/modules/timesheets/models/expense.models";
|
||||
import { date } from "quasar";
|
||||
import { date, Notify } from "quasar";
|
||||
|
||||
export const useExpensesApi = () => {
|
||||
const expenses_store = useExpensesStore();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
|
||||
const upsertExpense = async (expense: Expense, employee_email: string, file?: File): Promise<string> => {
|
||||
const upsertExpense = async (expense: Expense, employee_email?: string, file?: File): Promise<boolean> => {
|
||||
if (file) {
|
||||
const attachmentKey = await expenses_store.uploadAttachment(file);
|
||||
|
||||
|
|
@ -19,23 +19,24 @@ export const useExpensesApi = () => {
|
|||
expense.attachment_name = file.name;
|
||||
}
|
||||
}
|
||||
console.log('employee email provided for expense: ', employee_email)
|
||||
|
||||
const success = await expenses_store.upsertExpense(expense, employee_email);
|
||||
|
||||
if (success) {
|
||||
expenses_store.current_expense = new Expense(date.formatDate( new Date(), 'YYYY-MM-DD'));
|
||||
timesheet_store.getTimesheetsByOptionalEmployeeEmail(employee_email);
|
||||
return 'SUCCESS';
|
||||
expenses_store.current_expense = new Expense(date.formatDate(new Date(), 'YYYY-MM-DD'));
|
||||
await timesheet_store.getTimesheetsByOptionalEmployeeEmail(employee_email);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'INVALID_EXPENSE';
|
||||
return false;
|
||||
};
|
||||
|
||||
const deleteExpenseById = async (expense_id: number, employee_email?: string): Promise<void> => {
|
||||
const success = await expenses_store.deleteExpenseById(expense_id);
|
||||
if (success) {
|
||||
timesheet_store.getTimesheetsByOptionalEmployeeEmail(employee_email);
|
||||
}
|
||||
|
||||
if (success)
|
||||
await timesheet_store.getTimesheetsByOptionalEmployeeEmail(employee_email);
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { date } from "quasar";
|
||||
import { computed, ref } from "vue";
|
||||
import { ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { useTimesheetStore } from "src/stores/timesheet-store";
|
||||
import { Expense } from "src/modules/timesheets/models/expense.models";
|
||||
|
|
@ -16,7 +16,6 @@ export const useExpensesStore = defineStore('expenses', () => {
|
|||
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 isShowingAttachmentDialog = ref(false);
|
||||
const is_save_disabled = computed(() => JSON.stringify(current_expense.value) === JSON.stringify(initial_expense.value))
|
||||
|
||||
const open = (): void => {
|
||||
is_open.value = true;
|
||||
|
|
@ -103,7 +102,6 @@ export const useExpensesStore = defineStore('expenses', () => {
|
|||
current_expense,
|
||||
initial_expense,
|
||||
isShowingAttachmentDialog,
|
||||
is_save_disabled,
|
||||
attachmentURL,
|
||||
open,
|
||||
upsertExpense,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Notify, date } from 'quasar';
|
||||
import { Notify } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, ref } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
|
|
@ -14,6 +14,7 @@ import type { TimesheetApprovalCSVReportFilters } from 'src/modules/timesheet-ap
|
|||
import { type FederalHoliday, TARGO_HOLIDAY_NAMES_FR } from 'src/modules/timesheets/models/federal-holidays.models';
|
||||
import type { RouteNames } from 'src/router/router-constants';
|
||||
import type { RouteRecordNameGeneric } from 'vue-router';
|
||||
import { isBetweenDateStrings } from 'src/utils/date-and-time-utils';
|
||||
|
||||
export const useTimesheetStore = defineStore('timesheet', () => {
|
||||
const { t } = useI18n();
|
||||
|
|
@ -223,11 +224,7 @@ export const useTimesheetStore = defineStore('timesheet', () => {
|
|||
const pay_period_event: PayPeriodEvent = JSON.parse(event.data);
|
||||
|
||||
// abort notification if event date is not within pay period being currently viewed
|
||||
const eventDate = date.extractDate(pay_period_event.date, 'YYYY-MM-DD');
|
||||
const startDate = date.extractDate(pay_period.value!.period_start, 'YYYY-MM-DD');
|
||||
const endDate = date.extractDate(pay_period.value!.period_end, 'YYYY-MM-DD');
|
||||
|
||||
if (!date.isBetweenDates(eventDate, startDate, endDate, { inclusiveFrom: true, inclusiveTo: true, onlyDate: true }))
|
||||
if (!isBetweenDateStrings(pay_period_event.date, pay_period.value!.period_start, pay_period.value!.period_end))
|
||||
return;
|
||||
|
||||
const overview = pay_period_overviews.value.find(overview => overview.email === pay_period_event.employee_email);
|
||||
|
|
|
|||
|
|
@ -44,3 +44,19 @@ export const getHoursMinutesBetweenTwoHHmm = (startTime: string, endTime: string
|
|||
minutes: Number(endMinutes) - Number(startMinutes),
|
||||
}
|
||||
}
|
||||
|
||||
export const isBetweenDateStrings = (evDate: string, start: string, end: string, inclusive: boolean = true) => {
|
||||
const eventDate = date.extractDate(evDate, 'YYYY-MM-DD');
|
||||
const startDate = date.extractDate(start, 'YYYY-MM-DD');
|
||||
const endDate = date.extractDate(end, 'YYYY-MM-DD');
|
||||
|
||||
return date.isBetweenDates(
|
||||
eventDate,
|
||||
startDate,
|
||||
endDate,
|
||||
{
|
||||
inclusiveFrom: inclusive,
|
||||
inclusiveTo: inclusive,
|
||||
onlyDate: true
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user