feat(timesheet mobile): add interfaces for expense dialog in mobile format
Added mobile versions for expense form as well as expense items.
This commit is contained in:
parent
b307f33ab0
commit
88cdb9e5ff
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 MiB After Width: | Height: | Size: 242 KiB |
|
|
@ -232,7 +232,6 @@
|
||||||
color="primary"
|
color="primary"
|
||||||
type="text"
|
type="text"
|
||||||
class="col q-px-sm"
|
class="col q-px-sm"
|
||||||
:counter="true"
|
|
||||||
:maxlength="COMMENT_MAX_LENGTH"
|
:maxlength="COMMENT_MAX_LENGTH"
|
||||||
lazy-rules="ondemand"
|
lazy-rules="ondemand"
|
||||||
:rules="[rules.commentRequired]"
|
:rules="[rules.commentRequired]"
|
||||||
|
|
@ -289,7 +288,7 @@
|
||||||
push
|
push
|
||||||
color="accent"
|
color="accent"
|
||||||
:icon="expenses_store.mode === 'update' ? 'save' : 'upload'"
|
:icon="expenses_store.mode === 'update' ? 'save' : 'upload'"
|
||||||
:label="$q.screen.gt.sm ? (expenses_store.mode === 'update' ? $t('shared.label.update') : $t('shared.label.add')) : ''"
|
:label="expenses_store.mode === 'update' ? $t('shared.label.update') : $t('shared.label.add')"
|
||||||
class="q-px-sm "
|
class="q-px-sm "
|
||||||
:class="expenses_store.mode === 'create' ? 'q-mr-lg q-mb-md' : 'q-mb-sm q-ml-lg'"
|
:class="expenses_store.mode === 'create' ? 'q-mr-lg q-mb-md' : 'q-mb-sm q-ml-lg'"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|
|
||||||
|
|
@ -48,20 +48,20 @@
|
||||||
{{ $t('timesheet.expense.total_amount') }} :
|
{{ $t('timesheet.expense.total_amount') }} :
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<q-icon
|
|
||||||
v-else
|
|
||||||
name="payments"
|
|
||||||
size="sm"
|
|
||||||
color="accent"
|
|
||||||
class="col"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="col-auto text-weight-light"
|
class="col-auto text-weight-light"
|
||||||
style="font-size: 2.5em; line-height: 1em;"
|
style="font-size: 2.5em; line-height: 1em;"
|
||||||
>
|
>
|
||||||
{{ weekly_totals.expenses.toFixed(2) }}
|
{{ weekly_totals.expenses.toFixed(2) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<q-icon
|
||||||
|
v-if="$q.screen.lt.md"
|
||||||
|
name="attach_money"
|
||||||
|
size="md"
|
||||||
|
color="accent"
|
||||||
|
class="col q-ml-sm"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-auto row items-center q-px-sm">
|
<div class="col-auto row items-center q-px-sm">
|
||||||
|
|
@ -72,20 +72,20 @@
|
||||||
{{ $t('timesheet.expense.total_mileage') }} :
|
{{ $t('timesheet.expense.total_mileage') }} :
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<q-icon
|
|
||||||
v-else
|
|
||||||
name="drive_eta"
|
|
||||||
size="sm"
|
|
||||||
color="accent"
|
|
||||||
class="col"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="col-auto text-weight-light"
|
class="col-auto text-weight-light"
|
||||||
style="font-size: 2.5em; line-height: 1em;"
|
style="font-size: 2.5em; line-height: 1em;"
|
||||||
>
|
>
|
||||||
{{ weekly_totals.mileage.toFixed(1) }}
|
{{ weekly_totals.mileage.toFixed(1) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<q-icon
|
||||||
|
v-if="$q.screen.lt.md"
|
||||||
|
name="commute"
|
||||||
|
size="md"
|
||||||
|
color="accent"
|
||||||
|
class="col q-ml-sm"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,9 @@
|
||||||
class="text-uppercase text-weight-light"
|
class="text-uppercase text-weight-light"
|
||||||
:class="approved_class"
|
:class="approved_class"
|
||||||
>
|
>
|
||||||
{{ $d(date.extractDate(expense.date, 'YYYY-MM-DD'), { month: 'short', day: 'numeric', weekday: 'long' }) }}
|
{{ $d(date.extractDate(expense.date, 'YYYY-MM-DD'), {
|
||||||
|
month: 'short', day: 'numeric', weekday:
|
||||||
|
'long' }) }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|
||||||
|
|
@ -194,7 +196,10 @@
|
||||||
@hide="expenses_store.is_hiding_create_form = false"
|
@hide="expenses_store.is_hiding_create_form = false"
|
||||||
:duration="200"
|
:duration="200"
|
||||||
>
|
>
|
||||||
<ExpenseDialogForm v-if="is_showing_update_form && expenses_store.is_hiding_create_form" @on-click-update-cancel="onUpdateClicked"/>
|
<ExpenseDialogForm
|
||||||
|
v-if="is_showing_update_form && expenses_store.is_hiding_create_form"
|
||||||
|
@on-click-update-cancel="onUpdateClicked"
|
||||||
|
/>
|
||||||
</q-slide-transition>
|
</q-slide-transition>
|
||||||
</q-item>
|
</q-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
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';
|
||||||
|
import ExpenseDialogListItemMobile from 'src/modules/timesheets/components/mobile/expense-dialog-list-item-mobile.vue';
|
||||||
|
|
||||||
const timesheet_store = useTimesheetStore();
|
const timesheet_store = useTimesheetStore();
|
||||||
|
|
||||||
|
|
@ -38,14 +39,25 @@
|
||||||
<q-separator spaced />
|
<q-separator spaced />
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
|
|
||||||
<ExpenseDialogListItem
|
<div
|
||||||
v-for="(expense, index) in expenses_list"
|
v-for="(expense, index) in expenses_list"
|
||||||
:key="index"
|
:key="index"
|
||||||
v-model="expense.is_approved"
|
>
|
||||||
:index="index"
|
<ExpenseDialogListItemMobile
|
||||||
:expense="expense"
|
v-if="$q.screen.lt.md"
|
||||||
:horizontal="horizontal"
|
v-model="expense.is_approved"
|
||||||
/>
|
:index="index"
|
||||||
</q-list>
|
:expense="expense"
|
||||||
|
:horizontal="horizontal"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ExpenseDialogListItem
|
||||||
|
v-else
|
||||||
|
v-model="expense.is_approved"
|
||||||
|
:index="index"
|
||||||
|
:expense="expense"
|
||||||
|
:horizontal="horizontal"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-list>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
import { useExpensesStore } from 'src/stores/expense-store';
|
import { useExpensesStore } from 'src/stores/expense-store';
|
||||||
import ExpenseDialogList from 'src/modules/timesheets/components/expense-dialog-list.vue';
|
import ExpenseDialogList from 'src/modules/timesheets/components/expense-dialog-list.vue';
|
||||||
import ExpenseDialogForm from 'src/modules/timesheets/components/expense-dialog-form.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 ExpenseDialogHeader from 'src/modules/timesheets/components/expense-dialog-header.vue';
|
||||||
|
|
||||||
const expense_store = useExpensesStore();
|
const expense_store = useExpensesStore();
|
||||||
|
|
@ -18,8 +19,10 @@
|
||||||
transition-hide="jump-down"
|
transition-hide="jump-down"
|
||||||
>
|
>
|
||||||
<q-card
|
<q-card
|
||||||
class="q-pa-none rounded-10 shadow-10 bg-secondary"
|
class="q-pa-none rounded-10 shadow-10"
|
||||||
|
:class="$q.screen.lt.md ? ' bg-primary' : 'bg-secondary'"
|
||||||
style=" min-width: 70vw;"
|
style=" min-width: 70vw;"
|
||||||
|
:style="$q.screen.lt.md ? 'border: solid 2px var(--q-accent);' : ''"
|
||||||
>
|
>
|
||||||
<q-inner-loading :showing="expense_store.is_loading">
|
<q-inner-loading :showing="expense_store.is_loading">
|
||||||
<q-spinner size="32px" />
|
<q-spinner size="32px" />
|
||||||
|
|
@ -38,8 +41,13 @@
|
||||||
|
|
||||||
<ExpenseDialogList />
|
<ExpenseDialogList />
|
||||||
|
|
||||||
|
<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">
|
<q-slide-transition @hide="expense_store.is_hiding_create_form = true" :duration="200">
|
||||||
<ExpenseDialogForm v-if="!expense_store.current_expense.is_approved && expense_store.mode !== 'update' && expense_store.is_hiding_create_form === false" />
|
<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/>
|
||||||
|
</div>
|
||||||
</q-slide-transition>
|
</q-slide-transition>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,317 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
import { computed, inject, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
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';
|
||||||
|
|
||||||
|
interface ExpenseOption {
|
||||||
|
label: string;
|
||||||
|
value: ExpenseType;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const ui_store = useUiStore();
|
||||||
|
const timesheet_store = useTimesheetStore();
|
||||||
|
const expenses_store = useExpensesStore();
|
||||||
|
const expenses_api = useExpensesApi();
|
||||||
|
const files = defineModel<File[] | null>('files');
|
||||||
|
const is_navigator_open = ref(false);
|
||||||
|
const is_showing_comment_dialog_mobile = ref(false);
|
||||||
|
|
||||||
|
const COMMENT_MAX_LENGTH = 280;
|
||||||
|
const employee_email = inject<string>('employeeEmail');
|
||||||
|
const rules = useExpenseRules(t);
|
||||||
|
|
||||||
|
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 expense_options: ExpenseOption[] = [
|
||||||
|
{ label: t('timesheet.expense.types.PER_DIEM'), value: 'PER_DIEM', icon: getExpenseIcon('PER_DIEM') },
|
||||||
|
{ label: t('timesheet.expense.types.EXPENSES'), value: 'EXPENSES', icon: getExpenseIcon('EXPENSES') },
|
||||||
|
{ 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 openDatePicker = () => {
|
||||||
|
is_navigator_open.value = true;
|
||||||
|
if (timesheet_store.pay_period !== undefined) {
|
||||||
|
expenses_store.current_expense.date = timesheet_store.pay_period.period_start;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const requestExpenseCreationOrUpdate = async () => {
|
||||||
|
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 ?? '');
|
||||||
|
};
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
'onClickUpdateCancel': [void];
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-form
|
||||||
|
v-if="!timesheet_store.timesheets?.every(timesheet => timesheet.is_approved)"
|
||||||
|
:key="expenses_store.current_expense.id"
|
||||||
|
flat
|
||||||
|
@submit.prevent="requestExpenseCreationOrUpdate"
|
||||||
|
class="column full-width"
|
||||||
|
>
|
||||||
|
<!-- header -->
|
||||||
|
<div
|
||||||
|
class="col text-uppercase text-weight-medium text-h6 q-ma-xs"
|
||||||
|
:class="expenses_store.mode === 'create' ? 'q-px-md' : 'invisible'"
|
||||||
|
>
|
||||||
|
{{ $t('timesheet.expense.add_expense') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="col column items-start rounded-5 q-pb-sm"
|
||||||
|
:class="expenses_store.mode === 'create' ? 'q-px-md' : ''"
|
||||||
|
>
|
||||||
|
<!-- date and type row -->
|
||||||
|
<div class="col row q-my-xs full-width">
|
||||||
|
<!-- date selection input -->
|
||||||
|
<q-input
|
||||||
|
v-model="expenses_store.current_expense.date"
|
||||||
|
dense
|
||||||
|
type="date"
|
||||||
|
outlined
|
||||||
|
readonly
|
||||||
|
stack-label
|
||||||
|
hide-bottom-space
|
||||||
|
color="primary"
|
||||||
|
class="col-auto q-mr-sm"
|
||||||
|
input-class="text-weight-medium"
|
||||||
|
input-style="font-size: 1em;"
|
||||||
|
:label="$t('timesheet.expense.date')"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<q-btn
|
||||||
|
push
|
||||||
|
dense
|
||||||
|
icon="event"
|
||||||
|
color="accent"
|
||||||
|
class="q-mr-sm"
|
||||||
|
@click="openDatePicker"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-dialog
|
||||||
|
v-model="is_navigator_open"
|
||||||
|
transition-show="jump-right"
|
||||||
|
transition-hide="jump-right"
|
||||||
|
>
|
||||||
|
<q-date
|
||||||
|
v-model="expenses_store.current_expense.date"
|
||||||
|
mask="YYYY-MM-DD"
|
||||||
|
event-color="accent"
|
||||||
|
:options="date => date >= period_start_date && date <= period_end_date"
|
||||||
|
@update:model-value="is_navigator_open = false"
|
||||||
|
/>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #label>
|
||||||
|
<span class="text-weight-bold text-accent text-uppercase text-caption">
|
||||||
|
{{ $t('timesheet.expense.date') }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<!-- expenses type selection -->
|
||||||
|
<q-select
|
||||||
|
v-model="expense_selected"
|
||||||
|
standout="bg-blue-grey-9 text-white"
|
||||||
|
dense
|
||||||
|
:options="expense_options"
|
||||||
|
hide-dropdown-icon
|
||||||
|
stack-label
|
||||||
|
label-slot
|
||||||
|
hide-bottom-space
|
||||||
|
class="col"
|
||||||
|
color="primary"
|
||||||
|
:label="$t('timesheet.expense.type')"
|
||||||
|
:menu-offset="[0, 10]"
|
||||||
|
menu-anchor="bottom middle"
|
||||||
|
menu-self="top middle"
|
||||||
|
popup-content-class="text-uppercase text-weight-bold text-center rounded-5"
|
||||||
|
popup-content-style="border: 2px solid var(--q-accent)"
|
||||||
|
:rules="[rules.typeRequired]"
|
||||||
|
@update:model-value="option => expenses_store.current_expense.type = option.value"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<span class="text-weight-bold text-accent text-uppercase text-caption">
|
||||||
|
{{ $t('timesheet.expense.type') }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #selected-item="scope">
|
||||||
|
<div
|
||||||
|
class="row items-center text-weight-bold q-ma-none q-pa-none full-width"
|
||||||
|
:class="ui_store.is_mobile_mode ? 'full-height' : ''"
|
||||||
|
:tabindex="scope.tabindex"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
:name="scope.opt.icon"
|
||||||
|
size="sm"
|
||||||
|
class="col-auto q-mx-xs"
|
||||||
|
/>
|
||||||
|
<span class="col text-uppercase ellipsis">{{ scope.opt.label }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- amount and comment row -->
|
||||||
|
<div class="col row q-my-xs full-width">
|
||||||
|
<!-- amount input -->
|
||||||
|
<div class="col q-mr-sm">
|
||||||
|
<q-input
|
||||||
|
v-if="TYPES_WITH_AMOUNT_ONLY.includes(expenses_store.current_expense?.type ?? 'EXPENSES')"
|
||||||
|
v-model.number="expenses_store.current_expense.amount"
|
||||||
|
key="amount"
|
||||||
|
standout="bg-blue-grey-9"
|
||||||
|
dense
|
||||||
|
label-slot
|
||||||
|
stack-label
|
||||||
|
hide-bottom-space
|
||||||
|
suffix="$"
|
||||||
|
color="primary"
|
||||||
|
input-class="text-right text-weight-bold"
|
||||||
|
: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">
|
||||||
|
{{ $t('timesheet.expense.amount') }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<!-- mileage input -->
|
||||||
|
<q-input
|
||||||
|
v-else
|
||||||
|
v-model.number="expenses_store.current_expense.mileage"
|
||||||
|
key="mileage"
|
||||||
|
standout="bg-blue-grey-9"
|
||||||
|
input-class="text-right"
|
||||||
|
dense
|
||||||
|
stack-label
|
||||||
|
clearable
|
||||||
|
hide-bottom-space
|
||||||
|
color="primary"
|
||||||
|
label-slot
|
||||||
|
suffix="km"
|
||||||
|
lazy-rules="ondemand"
|
||||||
|
:rules="[rules.mileageRequired]"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<span class="text-weight-bold text-accent text-uppercase text-caption">
|
||||||
|
{{ $t('timesheet.expense.mileage') }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- employee comment input -->
|
||||||
|
<q-btn
|
||||||
|
push
|
||||||
|
color="accent"
|
||||||
|
:icon="expenses_store.current_expense.comment ? 'chat' : 'chat_bubble_outline'"
|
||||||
|
@click="is_showing_comment_dialog_mobile = true"
|
||||||
|
class="col-auto"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-dialog v-model="is_showing_comment_dialog_mobile">
|
||||||
|
<q-card class="full-width bg-secondary rounded-10">
|
||||||
|
<q-card-section class="q-pa-none">
|
||||||
|
<span
|
||||||
|
class="text-weight-bold text-accent text-uppercase text-caption"
|
||||||
|
style="font-size: 1.5em;"
|
||||||
|
>
|
||||||
|
{{ $t('timesheet.expense.employee_comment') }}
|
||||||
|
</span>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-section class="q-pa-none bg-primary rounded-10">
|
||||||
|
<q-input
|
||||||
|
v-model="expenses_store.current_expense.comment"
|
||||||
|
standout="bg-blue-grey-9"
|
||||||
|
dense
|
||||||
|
hide-bottom-space
|
||||||
|
color="primary"
|
||||||
|
type="textarea"
|
||||||
|
:maxlength="COMMENT_MAX_LENGTH"
|
||||||
|
lazy-rules="ondemand"
|
||||||
|
:rules="[rules.commentRequired]"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- import attach file section -->
|
||||||
|
<q-file
|
||||||
|
v-model="files"
|
||||||
|
standout="bg-blue-grey-9"
|
||||||
|
dense
|
||||||
|
use-chips
|
||||||
|
multiple
|
||||||
|
stack-label
|
||||||
|
:label="$t('timesheet.expense.hints.attach_file')"
|
||||||
|
class="col full-width q-my-xs"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<q-icon
|
||||||
|
name="attach_file"
|
||||||
|
size="sm"
|
||||||
|
color="accent"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #label>
|
||||||
|
<span class="text-weight-bold text-accent text-uppercase text-caption">
|
||||||
|
{{ $t('timesheet.expense.hints.attach_file') }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</q-file>
|
||||||
|
</div>
|
||||||
|
<div class="col row full-width items-center">
|
||||||
|
<q-space />
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
v-if="expenses_store.mode === 'update'"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
class="col-auto"
|
||||||
|
icon="clear"
|
||||||
|
color="negative"
|
||||||
|
:label="$q.screen.gt.sm ? $t('shared.label.cancel') : ''"
|
||||||
|
@click="$emit('onClickUpdateCancel')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
push
|
||||||
|
color="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"
|
||||||
|
:class="expenses_store.mode === 'create' ? 'q-mr-md q-mb-md' : 'q-mb-sm q-ml-lg'"
|
||||||
|
type="submit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-form>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
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';
|
||||||
|
import { Expense } from 'src/modules/timesheets/models/expense.models';
|
||||||
|
import ExpenseDialogFormMobile from 'src/modules/timesheets/components/mobile/expense-dialog-form-mobile.vue';
|
||||||
|
|
||||||
|
const { expense, horizontal = false } = defineProps<{
|
||||||
|
expense: Expense;
|
||||||
|
index: number;
|
||||||
|
horizontal?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const expenses_store = useExpensesStore();
|
||||||
|
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);
|
||||||
|
|
||||||
|
const requestExpenseDeletion = async () => {
|
||||||
|
await expenses_api.deleteExpenseById(expense.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onUpdateClicked = () => {
|
||||||
|
if (expense.is_approved) return;
|
||||||
|
|
||||||
|
if (deepEqual(expense, 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;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
expenses_store.mode = 'update';
|
||||||
|
expenses_store.current_expense = expense;
|
||||||
|
expenses_store.initial_expense = unwrapAndClone(expense);
|
||||||
|
is_showing_update_form.value = true;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="column bg-dark rounded-5 q-my-sm full-width">
|
||||||
|
<q-slide-item
|
||||||
|
right-color="negative"
|
||||||
|
class="rounded-5 bg-dark full-width"
|
||||||
|
@right="requestExpenseDeletion"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
#right
|
||||||
|
v-if="$q.screen.lt.md && !expenses_store.is_hiding_create_form && !expense.is_approved"
|
||||||
|
>
|
||||||
|
<q-icon name="delete" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
:key="refresh_key"
|
||||||
|
clickable
|
||||||
|
class="row q-py-none q-pa-xs rounded-5 full-width"
|
||||||
|
:class="background_class + approved_class"
|
||||||
|
@click="onUpdateClicked"
|
||||||
|
>
|
||||||
|
<div class="column col">
|
||||||
|
<!-- date label -->
|
||||||
|
<div class="col-auto row items-center q-pl-xs">
|
||||||
|
<q-icon
|
||||||
|
name="calendar_month"
|
||||||
|
size="sm"
|
||||||
|
class="col-auto"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="col text-uppercase text-weight-light full-width q-pl-sm text-h6"
|
||||||
|
:class="approved_class"
|
||||||
|
>
|
||||||
|
{{ $d(
|
||||||
|
date.extractDate(expense.date, 'YYYY-MM-DD'),
|
||||||
|
{ month: 'long', day: 'numeric' }
|
||||||
|
) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col row full-width items-center">
|
||||||
|
<!-- avatar type icon section -->
|
||||||
|
<q-icon
|
||||||
|
:name="getExpenseIcon(expense.type)"
|
||||||
|
:color="expense.is_approved ? 'white' : ($q.dark.isActive ? 'white' : 'primary')"
|
||||||
|
size="lg"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- amount or mileage section -->
|
||||||
|
<q-item-section class="col text-weight-bold text-h6">
|
||||||
|
<q-item-label v-if="expense.type === 'MILEAGE'">
|
||||||
|
{{ expense.mileage?.toFixed(1) }} km
|
||||||
|
</q-item-label>
|
||||||
|
<q-item-label v-else>
|
||||||
|
$ {{ expense.amount.toFixed(2) }}
|
||||||
|
</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-space v-if="horizontal" />
|
||||||
|
|
||||||
|
<!-- attachment file icon -->
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-btn
|
||||||
|
push
|
||||||
|
:color="expense.is_approved ? 'white' : 'accent'"
|
||||||
|
:text-color="expense.is_approved ? 'accent' : 'white'"
|
||||||
|
class="col-auto q-mx-sm q-px-sm q-pb-sm"
|
||||||
|
icon="attach_file"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="col-auto q-px-sm"
|
||||||
|
:class="expense.is_approved ? '' : 'invisible'"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
v-if="expense.is_approved"
|
||||||
|
name="verified"
|
||||||
|
color="white"
|
||||||
|
size="lg"
|
||||||
|
class="full-height"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-item>
|
||||||
|
</q-slide-item>
|
||||||
|
|
||||||
|
<q-slide-transition
|
||||||
|
@hide="expenses_store.is_hiding_create_form = false"
|
||||||
|
:duration="200"
|
||||||
|
>
|
||||||
|
<ExpenseDialogFormMobile
|
||||||
|
v-if="is_showing_update_form && expenses_store.is_hiding_create_form"
|
||||||
|
class="q-mt-sm q-pa-sm"
|
||||||
|
@on-click-update-cancel="onUpdateClicked"
|
||||||
|
/>
|
||||||
|
</q-slide-transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -4,10 +4,10 @@
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-layout view="hHh lpR fFf">
|
<q-layout view="hHh lpR fFf">
|
||||||
<q-page-container class="bg-dark">
|
<q-page-container class="bg-secondary">
|
||||||
<q-img src="src/assets/village.png" fit="cover" position="50% 100%" class="absolute-full" />
|
<q-page class="column">
|
||||||
<q-page class="flex flex-center">
|
<q-img src="src/assets/village.png" fit="contain" class="col absolute-bottom-right" style="opacity: 50%;" />
|
||||||
<transition appear slow enter-active-class="animated zoomIn" leave-active-class="animated zoomOut">
|
<transition appear slow enter-active-class="animated zoomIn" leave-active-class="animated zoomOut" class="col absolute-center">
|
||||||
<LoginConnectionPanel />
|
<LoginConnectionPanel />
|
||||||
</transition>
|
</transition>
|
||||||
</q-page>
|
</q-page>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user