c57c1e8490
实际无可见效果——用户点的是下方 #title 里的日期/时间文本(上游 commit 368322f9
已实现按点击内容路由),#header label 行很少被点。改回上游行为。
FORK.md item #8 标记为已回滚。
1537 lines
74 KiB
Vue
1537 lines
74 KiB
Vue
<template>
|
|
<f7-page @page:afterin="onPageAfterIn" @page:beforeout="onPageBeforeOut">
|
|
<f7-navbar>
|
|
<f7-nav-left :class="{ 'disabled': loading }" :back-link="tt('Back')"></f7-nav-left>
|
|
<f7-nav-title :title="tt(title)"></f7-nav-title>
|
|
<f7-nav-right :class="{ 'navbar-compact-icons': true, 'disabled': loading }" v-if="mode !== TransactionEditPageMode.View || transaction.type !== TransactionType.ModifyBalance">
|
|
<f7-link icon-f7="ellipsis" @click="showMoreActionSheet = true"></f7-link>
|
|
<f7-link icon-f7="checkmark_alt" :class="{ 'disabled': inputIsEmpty || submitting }" @click="save(AfterSaveAction.GoBack)" v-if="mode !== TransactionEditPageMode.View"></f7-link>
|
|
</f7-nav-right>
|
|
</f7-navbar>
|
|
|
|
<f7-block :class="{ 'no-margin-top margin-bottom': true, 'disabled': loading }">
|
|
<f7-segmented strong round :class="{ 'readonly': pageTypeAndMode?.type === TransactionEditPageType.Transaction && mode !== TransactionEditPageMode.Add }">
|
|
<f7-button round :text="tt('Expense')" :active="transaction.type === TransactionType.Expense"
|
|
:disabled="pageTypeAndMode?.type === TransactionEditPageType.Transaction && mode !== TransactionEditPageMode.Add && transaction.type !== TransactionType.Expense"
|
|
v-if="transaction.type !== TransactionType.ModifyBalance"
|
|
@click="transaction.type = TransactionType.Expense"></f7-button>
|
|
<f7-button round :text="tt('Income')" :active="transaction.type === TransactionType.Income"
|
|
:disabled="pageTypeAndMode?.type === TransactionEditPageType.Transaction && mode !== TransactionEditPageMode.Add && transaction.type !== TransactionType.Income"
|
|
v-if="transaction.type !== TransactionType.ModifyBalance"
|
|
@click="transaction.type = TransactionType.Income"></f7-button>
|
|
<f7-button round :text="tt('Transfer')" :active="transaction.type === TransactionType.Transfer"
|
|
:disabled="pageTypeAndMode?.type === TransactionEditPageType.Transaction && mode !== TransactionEditPageMode.Add && transaction.type !== TransactionType.Transfer"
|
|
v-if="transaction.type !== TransactionType.ModifyBalance"
|
|
@click="transaction.type = TransactionType.Transfer"></f7-button>
|
|
<f7-button round :text="tt('Modify Balance')" :active="transaction.type === TransactionType.ModifyBalance"
|
|
v-if="pageTypeAndMode?.type === TransactionEditPageType.Transaction && transaction.type === TransactionType.ModifyBalance"></f7-button>
|
|
</f7-segmented>
|
|
</f7-block>
|
|
|
|
<f7-list strong inset dividers class="margin-vertical skeleton-text" v-if="loading">
|
|
<f7-list-input label="Template Name" placeholder="Template Name" v-if="pageTypeAndMode?.type === TransactionEditPageType.Template"></f7-list-input>
|
|
<f7-list-item
|
|
class="transaction-edit-amount ebk-large-amount"
|
|
header="Expense Amount" title="0.00">
|
|
</f7-list-item>
|
|
<f7-list-item class="list-item-with-header-and-title list-item-title-hide-overflow" header="Category" title="Category Names" v-if="transaction.type !== TransactionType.ModifyBalance"></f7-list-item>
|
|
<f7-list-item class="list-item-with-header-and-title" header="Account" title="Account Name"></f7-list-item>
|
|
<f7-list-item class="list-item-with-header-and-title" header="Transaction Time" title="YYYY/MM/DD HH:mm:ss" v-if="pageTypeAndMode?.type === TransactionEditPageType.Transaction"></f7-list-item>
|
|
<f7-list-item class="list-item-with-header-and-title" header="Scheduled Transaction Frequency" title="Every XXXXX" v-if="pageTypeAndMode?.type === TransactionEditPageType.Template && transaction instanceof TransactionTemplate && transaction.templateType === TemplateType.Schedule.type"></f7-list-item>
|
|
<f7-list-item class="list-item-with-header-and-title list-item-title-hide-overflow list-item-no-item-after" header="Transaction Timezone" title="(UTC XX:XX) System Default" link="#" :no-chevron="mode === TransactionEditPageMode.View" v-if="pageTypeAndMode?.type === TransactionEditPageType.Transaction || (pageTypeAndMode?.type === TransactionEditPageType.Template && transaction instanceof TransactionTemplate && transaction.templateType === TemplateType.Schedule.type)"></f7-list-item>
|
|
<f7-list-item class="list-item-with-header-and-title list-item-title-hide-overflow" header="Geographic Location" title="No Location" v-if="pageTypeAndMode?.type === TransactionEditPageType.Transaction"></f7-list-item>
|
|
<f7-list-item header="Tags">
|
|
<template #footer>
|
|
<f7-block class="margin-top-half no-padding no-margin">
|
|
<f7-chip class="transaction-edit-tag" text="None"></f7-chip>
|
|
</f7-block>
|
|
</template>
|
|
</f7-list-item>
|
|
<f7-list-input type="textarea" label="Description" placeholder="Your transaction description (optional)"></f7-list-input>
|
|
</f7-list>
|
|
|
|
<f7-list form strong inset dividers class="margin-vertical" v-else-if="!loading">
|
|
<f7-list-input
|
|
type="text"
|
|
clear-button
|
|
:label="tt('Template Name')"
|
|
:placeholder="tt('Template Name')"
|
|
v-model:value="transaction.name"
|
|
v-if="pageTypeAndMode?.type === TransactionEditPageType.Template && transaction instanceof TransactionTemplate"
|
|
></f7-list-input>
|
|
|
|
<f7-list-item
|
|
class="transaction-edit-amount"
|
|
link="#" no-chevron
|
|
:class="sourceAmountClass"
|
|
:header="sourceAmountTitle"
|
|
:title="getDisplayAmount(transaction.sourceAmount, transaction.hideAmount, sourceAccountCurrency)"
|
|
@click="showSourceAmountSheet = true"
|
|
>
|
|
<number-pad-sheet :min-value="TRANSACTION_MIN_AMOUNT"
|
|
:max-value="TRANSACTION_MAX_AMOUNT"
|
|
:currency="sourceAccountCurrency"
|
|
v-model:show="showSourceAmountSheet"
|
|
v-model="transaction.sourceAmount"
|
|
></number-pad-sheet>
|
|
</f7-list-item>
|
|
|
|
<f7-list-item
|
|
class="transaction-edit-amount text-color-primary"
|
|
link="#" no-chevron
|
|
:class="destinationAmountClass"
|
|
:header="transferInAmountTitle"
|
|
:title="getDisplayAmount(transaction.destinationAmount, transaction.hideAmount, destinationAccountCurrency)"
|
|
@click="showDestinationAmountSheet = true"
|
|
v-if="transaction.type === TransactionType.Transfer"
|
|
>
|
|
<number-pad-sheet :min-value="TRANSACTION_MIN_AMOUNT"
|
|
:max-value="TRANSACTION_MAX_AMOUNT"
|
|
:currency="destinationAccountCurrency"
|
|
v-model:show="showDestinationAmountSheet"
|
|
v-model="transaction.destinationAmount"
|
|
></number-pad-sheet>
|
|
</f7-list-item>
|
|
|
|
<f7-list-item
|
|
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
|
key="expenseCategorySelection"
|
|
link="#" no-chevron
|
|
:class="{ 'disabled': !hasVisibleExpenseCategories, 'readonly': mode === TransactionEditPageMode.View }"
|
|
:header="tt('Category')"
|
|
@click="showCategorySheet = true"
|
|
v-if="transaction.type === TransactionType.Expense"
|
|
>
|
|
<template #title>
|
|
<div class="list-item-custom-title" v-if="hasVisibleExpenseCategories">
|
|
<span>{{ getTransactionPrimaryCategoryName(transaction.expenseCategoryId, allCategories[CategoryType.Expense]) }}</span>
|
|
<f7-icon class="category-separate-icon icon-with-direction" f7="chevron_right"></f7-icon>
|
|
<span>{{ getTransactionSecondaryCategoryName(transaction.expenseCategoryId, allCategories[CategoryType.Expense]) }}</span>
|
|
</div>
|
|
<div class="list-item-custom-title" v-else-if="!hasVisibleExpenseCategories">
|
|
<span>{{ tt('None') }}</span>
|
|
</div>
|
|
</template>
|
|
<tree-view-selection-sheet primary-key-field="id" primary-title-field="name"
|
|
primary-icon-field="icon" primary-icon-type="category" primary-color-field="color"
|
|
primary-hidden-field="hidden" primary-sub-items-field="subCategories"
|
|
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
|
secondary-hidden-field="hidden"
|
|
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
|
:default-expanded="settingsStore.appSettings.expandCategoryTreeByDefault"
|
|
:items="allCategories[CategoryType.Expense]"
|
|
v-model:show="showCategorySheet"
|
|
v-model="transaction.expenseCategoryId">
|
|
</tree-view-selection-sheet>
|
|
</f7-list-item>
|
|
|
|
<f7-list-item
|
|
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
|
key="incomeCategorySelection"
|
|
link="#" no-chevron
|
|
:class="{ 'disabled': !hasVisibleIncomeCategories, 'readonly': mode === TransactionEditPageMode.View }"
|
|
:header="tt('Category')"
|
|
@click="showCategorySheet = true"
|
|
v-if="transaction.type === TransactionType.Income"
|
|
>
|
|
<template #title>
|
|
<div class="list-item-custom-title" v-if="hasVisibleIncomeCategories">
|
|
<span>{{ getTransactionPrimaryCategoryName(transaction.incomeCategoryId, allCategories[CategoryType.Income]) }}</span>
|
|
<f7-icon class="category-separate-icon icon-with-direction" f7="chevron_right"></f7-icon>
|
|
<span>{{ getTransactionSecondaryCategoryName(transaction.incomeCategoryId, allCategories[CategoryType.Income]) }}</span>
|
|
</div>
|
|
<div class="list-item-custom-title" v-else-if="!hasVisibleIncomeCategories">
|
|
<span>{{ tt('None') }}</span>
|
|
</div>
|
|
</template>
|
|
<tree-view-selection-sheet primary-key-field="id" primary-title-field="name"
|
|
primary-icon-field="icon" primary-icon-type="category" primary-color-field="color"
|
|
primary-hidden-field="hidden" primary-sub-items-field="subCategories"
|
|
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
|
secondary-hidden-field="hidden"
|
|
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
|
:default-expanded="settingsStore.appSettings.expandCategoryTreeByDefault"
|
|
:items="allCategories[CategoryType.Income]"
|
|
v-model:show="showCategorySheet"
|
|
v-model="transaction.incomeCategoryId">
|
|
</tree-view-selection-sheet>
|
|
</f7-list-item>
|
|
|
|
<f7-list-item
|
|
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
|
key="transferCategorySelection"
|
|
link="#" no-chevron
|
|
:class="{ 'disabled': !hasVisibleTransferCategories, 'readonly': mode === TransactionEditPageMode.View }"
|
|
:header="tt('Category')"
|
|
@click="showCategorySheet = true"
|
|
v-if="transaction.type === TransactionType.Transfer"
|
|
>
|
|
<template #title>
|
|
<div class="list-item-custom-title" v-if="hasVisibleTransferCategories">
|
|
<span>{{ getTransactionPrimaryCategoryName(transaction.transferCategoryId, allCategories[CategoryType.Transfer]) }}</span>
|
|
<f7-icon class="category-separate-icon icon-with-direction" f7="chevron_right"></f7-icon>
|
|
<span>{{ getTransactionSecondaryCategoryName(transaction.transferCategoryId, allCategories[CategoryType.Transfer]) }}</span>
|
|
</div>
|
|
<div class="list-item-custom-title" v-else-if="!hasVisibleTransferCategories">
|
|
<span>{{ tt('None') }}</span>
|
|
</div>
|
|
</template>
|
|
<tree-view-selection-sheet primary-key-field="id" primary-title-field="name"
|
|
primary-icon-field="icon" primary-icon-type="category" primary-color-field="color"
|
|
primary-hidden-field="hidden" primary-sub-items-field="subCategories"
|
|
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
|
secondary-hidden-field="hidden"
|
|
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
|
:default-expanded="settingsStore.appSettings.expandCategoryTreeByDefault"
|
|
:items="allCategories[CategoryType.Transfer]"
|
|
v-model:show="showCategorySheet"
|
|
v-model="transaction.transferCategoryId">
|
|
</tree-view-selection-sheet>
|
|
</f7-list-item>
|
|
|
|
<f7-list-item
|
|
class="list-item-with-header-and-title"
|
|
link="#" no-chevron
|
|
:class="{ 'disabled': !allVisibleAccounts.length || (mode === TransactionEditPageMode.Edit && transaction.type === TransactionType.ModifyBalance), 'readonly': mode === TransactionEditPageMode.View }"
|
|
:header="tt(sourceAccountTitle)"
|
|
:title="sourceAccountName"
|
|
:footer="sourceAccountBalanceDisplay"
|
|
@click="showSourceAccountSheet = true"
|
|
>
|
|
<two-column-list-item-selection-sheet primary-key-field="id" primary-value-field="category"
|
|
primary-title-field="name" primary-footer-field="displayBalance"
|
|
primary-icon-field="icon" primary-icon-type="account"
|
|
primary-sub-items-field="accounts"
|
|
:primary-title-i18n="true"
|
|
secondary-key-field="id" secondary-value-field="id"
|
|
secondary-title-field="name" secondary-footer-field="displayBalance"
|
|
secondary-icon-field="icon" secondary-icon-type="account" secondary-color-field="color"
|
|
:enable-filter="true" :filter-placeholder="tt('Find account')" :filter-no-items-text="tt('No available account')"
|
|
:items="allVisibleCategorizedAccounts"
|
|
v-model:show="showSourceAccountSheet"
|
|
v-model="transaction.sourceAccountId">
|
|
</two-column-list-item-selection-sheet>
|
|
</f7-list-item>
|
|
|
|
<f7-list-item
|
|
class="list-item-with-header-and-title"
|
|
link="#" no-chevron
|
|
:class="{ 'disabled': !allVisibleAccounts.length, 'readonly': mode === TransactionEditPageMode.View }"
|
|
:header="tt('Destination Account')"
|
|
:title="destinationAccountName"
|
|
:footer="destinationAccountBalanceDisplay"
|
|
v-if="transaction.type === TransactionType.Transfer"
|
|
@click="showDestinationAccountSheet = true"
|
|
>
|
|
<two-column-list-item-selection-sheet primary-key-field="id" primary-value-field="category"
|
|
primary-title-field="name" primary-footer-field="displayBalance"
|
|
primary-icon-field="icon" primary-icon-type="account"
|
|
primary-sub-items-field="accounts"
|
|
:primary-title-i18n="true"
|
|
secondary-key-field="id" secondary-value-field="id"
|
|
secondary-title-field="name" secondary-footer-field="displayBalance"
|
|
secondary-icon-field="icon" secondary-icon-type="account" secondary-color-field="color"
|
|
:enable-filter="true" :filter-placeholder="tt('Find account')" :filter-no-items-text="tt('No available account')"
|
|
:items="allVisibleCategorizedAccounts"
|
|
v-model:show="showDestinationAccountSheet"
|
|
v-model="transaction.destinationAccountId">
|
|
</two-column-list-item-selection-sheet>
|
|
</f7-list-item>
|
|
|
|
<f7-list-item
|
|
class="transaction-edit-datetime list-item-with-header-and-title"
|
|
link="#" no-chevron
|
|
:class="{ 'disabled': mode === TransactionEditPageMode.Edit && transaction.type === TransactionType.ModifyBalance, 'readonly': mode === TransactionEditPageMode.View && transaction.utcOffset === currentTimezoneOffsetMinutes }"
|
|
v-if="pageTypeAndMode?.type === TransactionEditPageType.Transaction"
|
|
>
|
|
<template #header>
|
|
<div class="transaction-edit-datetime-header" @click="showDateTimeDialog('time')">{{ tt('Transaction Time') }}</div>
|
|
</template>
|
|
<template #title>
|
|
<div class="transaction-edit-datetime-title">
|
|
<div @click="showDateTimeDialog('date')">{{ transactionDisplayDate }}</div> <div class="transaction-edit-datetime-time" @click="showDateTimeDialog('time')">{{ transactionDisplayTime }}</div>
|
|
</div>
|
|
</template>
|
|
<date-time-selection-sheet :init-mode="transactionDateTimeSheetMode"
|
|
:timezone-utc-offset="transaction.utcOffset"
|
|
:model-value="transaction.time"
|
|
v-model:show="showTransactionDateTimeSheet"
|
|
@update:model-value="updateTransactionTime">
|
|
</date-time-selection-sheet>
|
|
</f7-list-item>
|
|
|
|
<f7-list-item
|
|
class="list-item-with-header-and-title"
|
|
link="#" no-chevron
|
|
:class="{ 'readonly': mode === TransactionEditPageMode.View }"
|
|
:header="tt('Scheduled Transaction Frequency')"
|
|
:title="transactionDisplayScheduledFrequency"
|
|
@click="showTransactionScheduledFrequencySheet = true"
|
|
v-if="pageTypeAndMode?.type === TransactionEditPageType.Template && transaction instanceof TransactionTemplate && transaction.templateType === TemplateType.Schedule.type"
|
|
>
|
|
<schedule-frequency-sheet v-model:show="showTransactionScheduledFrequencySheet"
|
|
v-model:type="transaction.scheduledFrequencyType"
|
|
v-model="transaction.scheduledFrequency">
|
|
</schedule-frequency-sheet>
|
|
</f7-list-item>
|
|
|
|
<f7-list-item
|
|
class="transaction-edit-datetime list-item-with-header-and-title"
|
|
link="#" no-chevron
|
|
:class="{ 'readonly': mode === TransactionEditPageMode.View }"
|
|
:header="tt('Start Date')"
|
|
:title="transactionDisplayScheduledStartDate"
|
|
@click="showScheduledStartDateSheet = true"
|
|
v-if="pageTypeAndMode?.type === TransactionEditPageType.Template && transaction instanceof TransactionTemplate && transaction.templateType === TemplateType.Schedule.type"
|
|
>
|
|
<date-selection-sheet v-model:show="showScheduledStartDateSheet"
|
|
v-model="transaction.scheduledStartDate">
|
|
</date-selection-sheet>
|
|
</f7-list-item>
|
|
|
|
<f7-list-item
|
|
class="transaction-edit-datetime list-item-with-header-and-title"
|
|
link="#" no-chevron
|
|
:class="{ 'readonly': mode === TransactionEditPageMode.View }"
|
|
:header="tt('End Date')"
|
|
:title="transactionDisplayScheduledEndDate"
|
|
@click="showScheduledEndDateSheet = true"
|
|
v-if="pageTypeAndMode?.type === TransactionEditPageType.Template && transaction instanceof TransactionTemplate && transaction.templateType === TemplateType.Schedule.type"
|
|
>
|
|
<date-selection-sheet v-model:show="showScheduledEndDateSheet"
|
|
v-model="transaction.scheduledEndDate">
|
|
</date-selection-sheet>
|
|
</f7-list-item>
|
|
|
|
<f7-list-item
|
|
:no-chevron="mode === TransactionEditPageMode.View"
|
|
link="#"
|
|
class="list-item-with-header-and-title list-item-title-hide-overflow list-item-no-item-after"
|
|
:class="{ 'disabled': mode === TransactionEditPageMode.Edit && transaction.type === TransactionType.ModifyBalance, 'readonly': mode === TransactionEditPageMode.View }"
|
|
:header="tt('Transaction Timezone')"
|
|
v-if="pageTypeAndMode?.type === TransactionEditPageType.Transaction || (pageTypeAndMode?.type === TransactionEditPageType.Template && transaction instanceof TransactionTemplate && transaction.templateType === TemplateType.Schedule.type)"
|
|
@click="showTimezonePopup = true"
|
|
>
|
|
<template #title>
|
|
<f7-block class="list-item-custom-title no-padding no-margin">
|
|
<span>{{ `(${transactionDisplayTimezone})` }}</span>
|
|
<span class="transaction-edit-timezone-name" v-if="transaction.timeZone || transaction.timeZone === ''">{{ transactionDisplayTimezoneName }}</span>
|
|
<span class="transaction-edit-timezone-name" v-else-if="!transaction.timeZone && transaction.timeZone !== ''">{{ transactionTimezoneTimeDifference }}</span>
|
|
</f7-block>
|
|
</template>
|
|
<list-item-selection-popup value-type="item"
|
|
key-field="name" value-field="name"
|
|
title-field="displayNameWithUtcOffset"
|
|
:title="tt('Transaction Timezone')"
|
|
:enable-filter="true"
|
|
:filter-placeholder="tt('Timezone')"
|
|
:filter-no-items-text="tt('No results')"
|
|
:items="allTimezones"
|
|
:model-value="transaction.timeZone"
|
|
v-model:show="showTimezonePopup"
|
|
@update:model-value="updateTransactionTimezone">
|
|
</list-item-selection-popup>
|
|
</f7-list-item>
|
|
|
|
<f7-list-item
|
|
link="#" no-chevron
|
|
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
|
:class="{ 'readonly': mode === TransactionEditPageMode.View && !transaction.geoLocation }"
|
|
:header="tt('Geographic Location')"
|
|
@click="showGeoLocationActionSheet = true"
|
|
v-if="pageTypeAndMode?.type === TransactionEditPageType.Transaction"
|
|
>
|
|
<template #title>
|
|
<f7-block class="list-item-custom-title no-padding no-margin">
|
|
<span v-if="transaction.geoLocation">{{ `(${formatCoordinate(transaction.geoLocation, coordinateDisplayType)})` }}</span>
|
|
<span v-else-if="!transaction.geoLocation">{{ geoLocationStatusInfo }}</span>
|
|
</f7-block>
|
|
</template>
|
|
|
|
<map-sheet :readonly="mode === TransactionEditPageMode.View"
|
|
v-model="transaction.geoLocation"
|
|
v-model:set-geo-location-by-click-map="setGeoLocationByClickMap"
|
|
v-model:show="showGeoLocationMapSheet">
|
|
</map-sheet>
|
|
</f7-list-item>
|
|
|
|
<f7-list-item
|
|
link="#" no-chevron
|
|
:class="{ 'readonly': mode === TransactionEditPageMode.View }"
|
|
:header="tt('Tags')"
|
|
@click="showTransactionTagSheet = true"
|
|
>
|
|
<transaction-tag-selection-sheet :allow-add-new-tag="true" :enable-filter="true"
|
|
v-model:show="showTransactionTagSheet"
|
|
v-model="transaction.tagIds">
|
|
</transaction-tag-selection-sheet>
|
|
|
|
<template #footer>
|
|
<f7-block class="margin-top-half no-padding no-margin" v-if="transaction.tagIds && transaction.tagIds.length">
|
|
<f7-chip media-text-color="var(--f7-chip-text-color)" class="transaction-edit-tag"
|
|
:text="allTagsMap[tagId]?.name ?? ''"
|
|
:key="tagId"
|
|
v-for="tagId in transaction.tagIds">
|
|
<template #media>
|
|
<f7-icon f7="number"></f7-icon>
|
|
</template>
|
|
</f7-chip>
|
|
</f7-block>
|
|
<f7-block class="margin-top-half no-padding no-margin" v-else-if="!transaction.tagIds || !transaction.tagIds.length">
|
|
<f7-chip class="transaction-edit-tag" :text="tt('None')">
|
|
</f7-chip>
|
|
</f7-block>
|
|
</template>
|
|
</f7-list-item>
|
|
|
|
<f7-list-item
|
|
link="#" no-chevron
|
|
:header="tt('Pictures')"
|
|
v-if="showTransactionPictures || (transaction.pictures && transaction.pictures.length > 0)"
|
|
>
|
|
<template #footer>
|
|
<f7-block class="margin-top-half no-padding no-margin" :class="{ 'readonly': submitting || uploadingPicture || removingPictureId }">
|
|
<swiper-container
|
|
:pagination="false"
|
|
:space-between="10"
|
|
:slides-per-view="'auto'"
|
|
class="transaction-pictures"
|
|
>
|
|
<swiper-slide class="transaction-picture-container" :key="picIdx"
|
|
v-for="(pictureInfo, picIdx) in transaction.pictures"
|
|
@click="viewOrRemovePicture(pictureInfo)">
|
|
<div class="transaction-picture">
|
|
<div class="display-flex justify-content-center align-items-center transaction-picture-control-backdrop"
|
|
v-if="mode === TransactionEditPageMode.Add || mode === TransactionEditPageMode.Edit">
|
|
<f7-icon class="picture-control-icon picture-remove-icon" f7="trash" v-if="pictureInfo.pictureId !== removingPictureId"></f7-icon>
|
|
<f7-preloader color="white" :size="28" v-if="pictureInfo.pictureId === removingPictureId" />
|
|
</div>
|
|
<img alt="picture" :src="getTransactionPictureUrl(pictureInfo)"/>
|
|
</div>
|
|
</swiper-slide>
|
|
<swiper-slide @click="showOpenPictureDialog" v-if="canAddTransactionPicture">
|
|
<div class="display-flex justify-content-center align-items-center transaction-picture transaction-picture-add">
|
|
<f7-icon class="picture-control-icon" f7="plus" v-if="!uploadingPicture"></f7-icon>
|
|
<f7-preloader :size="28" v-if="uploadingPicture" />
|
|
</div>
|
|
</swiper-slide>
|
|
</swiper-container>
|
|
</f7-block>
|
|
</template>
|
|
</f7-list-item>
|
|
|
|
<f7-list-input
|
|
type="textarea"
|
|
class="transaction-edit-comment"
|
|
style="height: auto"
|
|
:class="{ 'readonly': mode === TransactionEditPageMode.View }"
|
|
:label="tt('Description')"
|
|
:placeholder="mode !== TransactionEditPageMode.View ? tt('Your transaction description (optional)') : ''"
|
|
v-textarea-auto-size
|
|
v-model:value="transaction.comment"
|
|
></f7-list-input>
|
|
</f7-list>
|
|
|
|
<f7-actions close-by-outside-click close-on-escape :opened="showGeoLocationActionSheet" @actions:closed="showGeoLocationActionSheet = false">
|
|
<f7-actions-group>
|
|
<f7-actions-button v-if="mode !== TransactionEditPageMode.View" @click="updateGeoLocation(true)">{{ tt('Update Geographic Location') }}</f7-actions-button>
|
|
<f7-actions-button v-if="mode !== TransactionEditPageMode.View" @click="clearGeoLocation">{{ tt('Clear Geographic Location') }}</f7-actions-button>
|
|
</f7-actions-group>
|
|
<f7-actions-group v-if="!!getMapProvider()">
|
|
<f7-actions-button :class="{ 'disabled': !transaction.geoLocation }" @click="setGeoLocationByClickMap = false; showGeoLocationMapSheet = true">{{ tt('Show on the map') }}</f7-actions-button>
|
|
</f7-actions-group>
|
|
<f7-actions-group>
|
|
<f7-actions-button bold close>{{ tt('Cancel') }}</f7-actions-button>
|
|
</f7-actions-group>
|
|
</f7-actions>
|
|
|
|
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
|
|
<f7-actions-group v-if="mode !== TransactionEditPageMode.View && transaction.type === TransactionType.Transfer">
|
|
<f7-actions-button @click="swapTransactionData(true, false)">{{ tt('Swap Account') }}</f7-actions-button>
|
|
<f7-actions-button @click="swapTransactionData(false, true)">{{ tt('Swap Amount') }}</f7-actions-button>
|
|
<f7-actions-button @click="swapTransactionData(true, true)">{{ tt('Swap Account and Amount') }}</f7-actions-button>
|
|
</f7-actions-group>
|
|
<f7-actions-group v-if="mode !== TransactionEditPageMode.View">
|
|
<f7-actions-button v-if="isSupportClipboard && !isiOS()" @click="pasteAmount('sourceAmount')">{{ tt('Paste Amount') }}</f7-actions-button>
|
|
<f7-actions-button v-if="isSupportClipboard && !isiOS() && transaction.type === TransactionType.Transfer" @click="pasteAmount('destinationAmount')">{{ tt('Paste Destination Amount') }}</f7-actions-button>
|
|
<f7-actions-button v-if="transaction.hideAmount" @click="transaction.hideAmount = false">{{ tt('Show Amount') }}</f7-actions-button>
|
|
<f7-actions-button v-if="!transaction.hideAmount" @click="transaction.hideAmount = true">{{ tt('Hide Amount') }}</f7-actions-button>
|
|
</f7-actions-group>
|
|
<f7-actions-group v-if="pageTypeAndMode?.type === TransactionEditPageType.Transaction && (mode === TransactionEditPageMode.Add || mode === TransactionEditPageMode.Edit) && isTransactionPicturesEnabled() && !showTransactionPictures">
|
|
<f7-actions-button @click="showTransactionPictures = true">{{ tt('Add Picture') }}</f7-actions-button>
|
|
</f7-actions-group>
|
|
<f7-actions-group v-if="pageTypeAndMode?.type === TransactionEditPageType.Transaction && mode === TransactionEditPageMode.View && transaction.type !== TransactionType.ModifyBalance">
|
|
<f7-actions-button @click="navigateToEdit()">{{ tt('Edit') }}</f7-actions-button>
|
|
<f7-actions-button @click="duplicate(false, false)">{{ tt('Duplicate') }}</f7-actions-button>
|
|
<f7-actions-button @click="duplicate(true, false)">{{ tt('Duplicate (With Time)') }}</f7-actions-button>
|
|
<f7-actions-button @click="duplicate(false, true)" v-if="transaction.geoLocation">{{ tt('Duplicate (With Geographic Location)') }}</f7-actions-button>
|
|
<f7-actions-button @click="duplicate(true, true)" v-if="transaction.geoLocation">{{ tt('Duplicate (With Time and Geographic Location)') }}</f7-actions-button>
|
|
<f7-actions-button color="red" @click="remove()">{{ tt('Delete') }}</f7-actions-button>
|
|
</f7-actions-group>
|
|
<f7-actions-group>
|
|
<f7-actions-button bold close>{{ tt('Cancel') }}</f7-actions-button>
|
|
</f7-actions-group>
|
|
</f7-actions>
|
|
|
|
<template #fixed v-if="quickSaveButtonStyleType === TransactionQuickSaveButtonStyle.BottomLeftFloating.type || quickSaveButtonStyleType === TransactionQuickSaveButtonStyle.BottomCenterFloating.type || quickSaveButtonStyleType === TransactionQuickSaveButtonStyle.BottomRightFloating.type">
|
|
<f7-fab id="quick-save-button" :class="{ 'disabled': inputIsEmpty || submitting }" :position="quickSaveButtonFloatingPosition"
|
|
:text="tt(quickSaveButtonTitle)"
|
|
@click="quickSave" v-if="mode !== TransactionEditPageMode.View">
|
|
</f7-fab>
|
|
</template>
|
|
|
|
<f7-toolbar id="quick-save-button" tabbar bottom v-if="quickSaveButtonStyleType === TransactionQuickSaveButtonStyle.BottomFixed.type && mode !== TransactionEditPageMode.View">
|
|
<f7-link :class="{ 'disabled': inputIsEmpty || submitting }" @click="quickSave">
|
|
<span class="tabbar-primary-link">{{ tt(quickSaveButtonTitle) }}</span>
|
|
</f7-link>
|
|
</f7-toolbar>
|
|
|
|
<f7-popover class="quick-save-popover" target-el="#quick-save-button"
|
|
v-model:opened="showQuickSavePopover">
|
|
<f7-list>
|
|
<f7-list-item link="#" no-chevron popover-close
|
|
:title="tt(TransactionQuickAddButtonActionType.SaveAndGoBack.name)"
|
|
@click="save(AfterSaveAction.GoBack)"></f7-list-item>
|
|
<f7-list-item link="#" no-chevron popover-close
|
|
:title="tt(TransactionQuickAddButtonActionType.SaveAndAddNewTransaction.name)"
|
|
@click="save(AfterSaveAction.StayWithNewTransaction)"></f7-list-item>
|
|
<f7-list-item link="#" no-chevron popover-close
|
|
:title="tt(TransactionQuickAddButtonActionType.SaveAndKeepCurrentData.name)"
|
|
@click="save(AfterSaveAction.StayWithCurrentTransaction)"></f7-list-item>
|
|
</f7-list>
|
|
</f7-popover>
|
|
|
|
<f7-photo-browser ref="pictureBrowser" type="popup" navbar-of-text="/"
|
|
:navbar-show-count="true" :exposition="false"
|
|
:photos="transactionPictures" :thumbs="transactionThumbs" />
|
|
<input ref="pictureInput" type="file" style="display: none" :accept="`${SUPPORTED_IMAGE_EXTENSIONS};capture=camera`" @change="uploadPicture($event)" />
|
|
</f7-page>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, useTemplateRef } from 'vue';
|
|
import type { PhotoBrowser, Router } from 'framework7/types';
|
|
|
|
import { useI18n } from '@/locales/helpers.ts';
|
|
import { useI18nUIComponents, isiOS, showLoading, hideLoading } from '@/lib/ui/mobile.ts';
|
|
import {
|
|
TransactionEditPageMode,
|
|
TransactionEditPageType,
|
|
GeoLocationStatus,
|
|
AfterSaveAction,
|
|
useTransactionEditPageBase
|
|
} from '@/views/base/transactions/TransactionEditPageBase.ts';
|
|
|
|
import { useSettingsStore } from '@/stores/setting.ts';
|
|
import { useUserStore } from '@/stores/user.ts';
|
|
import { useAccountsStore } from '@/stores/account.ts';
|
|
import type { Account } from '@/models/account.ts';
|
|
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
|
import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
|
|
import { useTransactionsStore } from '@/stores/transaction.ts';
|
|
import { useTransactionTemplatesStore } from '@/stores/transactionTemplate.ts';
|
|
|
|
import { CategoryType } from '@/core/category.ts';
|
|
import {
|
|
TransactionType,
|
|
TransactionEditScopeType,
|
|
TransactionQuickSaveButtonStyle,
|
|
TransactionQuickAddButtonActionType
|
|
} from '@/core/transaction.ts';
|
|
import { ScheduledTemplateFrequencyType, TemplateType } from '@/core/template.ts';
|
|
|
|
import { TRANSACTION_MAX_AMOUNT, TRANSACTION_MIN_AMOUNT } from '@/consts/transaction.ts';
|
|
import { KnownErrorCode } from '@/consts/api.ts';
|
|
import { SUPPORTED_IMAGE_EXTENSIONS } from '@/consts/file.ts';
|
|
|
|
import { TransactionTemplate } from '@/models/transaction_template.ts';
|
|
import type { TransactionPictureInfoBasicResponse } from '@/models/transaction_picture_info.ts';
|
|
import { Transaction } from '@/models/transaction.ts';
|
|
|
|
import {
|
|
getTimezoneOffset,
|
|
getTimezoneOffsetMinutes,
|
|
parseDateTimeFromUnixTimeWithTimezoneOffset
|
|
} from '@/lib/datetime.ts';
|
|
import { formatCoordinate } from '@/lib/coordinate.ts';
|
|
import { generateRandomUUID } from '@/lib/misc.ts';
|
|
import { getTransactionPrimaryCategoryName, getTransactionSecondaryCategoryName } from '@/lib/category.ts';
|
|
import { type SetTransactionOptions } from '@/lib/transaction.ts';
|
|
import { getMapProvider, isTransactionPicturesEnabled } from '@/lib/server_settings.ts';
|
|
import logger from '@/lib/logger.ts';
|
|
|
|
const props = defineProps<{
|
|
f7route: Router.Route;
|
|
f7router: Router.Router;
|
|
}>();
|
|
|
|
const query = props.f7route.query;
|
|
const pageTypeAndMode = getPageTypeNameMode();
|
|
|
|
const {
|
|
tt,
|
|
getMultiMonthdayShortNames,
|
|
getMultiWeekdayLongNames,
|
|
formatDateTimeToLongDate,
|
|
formatDateTimeToLongTime,
|
|
formatGregorianTextualYearMonthDayToLongDate,
|
|
parseAmountFromLocalizedNumerals,
|
|
formatAmountToLocalizedNumeralsWithCurrency
|
|
} = useI18n();
|
|
const { showAlert, showConfirm, showToast, routeBackOnError } = useI18nUIComponents();
|
|
|
|
const {
|
|
mode,
|
|
isSupportGeoLocation,
|
|
editId,
|
|
addByTemplateId,
|
|
duplicateFromId,
|
|
clientSessionId,
|
|
loading,
|
|
submitting,
|
|
submitted,
|
|
uploadingPicture,
|
|
geoLocationStatus,
|
|
setGeoLocationByClickMap,
|
|
transaction,
|
|
numeralSystem,
|
|
currentTimezoneOffsetMinutes,
|
|
defaultCurrency,
|
|
firstDayOfWeek,
|
|
coordinateDisplayType,
|
|
allTimezones,
|
|
allVisibleAccounts,
|
|
allVisibleCategorizedAccounts,
|
|
allCategories,
|
|
allTagsMap,
|
|
firstVisibleAccountId,
|
|
hasVisibleExpenseCategories,
|
|
hasVisibleIncomeCategories,
|
|
hasVisibleTransferCategories,
|
|
canAddTransactionPicture,
|
|
title,
|
|
quickSaveButtonTitle,
|
|
sourceAmountTitle,
|
|
sourceAccountTitle,
|
|
transferInAmountTitle,
|
|
sourceAccountName,
|
|
destinationAccountName,
|
|
sourceAccountCurrency,
|
|
destinationAccountCurrency,
|
|
transactionDisplayTimezone,
|
|
transactionTimezoneTimeDifference,
|
|
geoLocationStatusInfo,
|
|
inputEmptyProblemMessage,
|
|
inputIsEmpty,
|
|
setTransactionModel,
|
|
updateTransactionModelByAfterSaveAction,
|
|
updateTransactionTime,
|
|
updateTransactionTimezone,
|
|
swapTransactionData,
|
|
getDisplayAmount,
|
|
getTransactionPictureUrl
|
|
} = useTransactionEditPageBase(pageTypeAndMode?.type || TransactionEditPageType.Transaction, pageTypeAndMode?.mode, query['type'] ? parseInt(query['type']) : undefined);
|
|
|
|
const settingsStore = useSettingsStore();
|
|
const userStore = useUserStore();
|
|
const accountsStore = useAccountsStore();
|
|
const transactionCategoriesStore = useTransactionCategoriesStore();
|
|
const transactionTagsStore = useTransactionTagsStore();
|
|
const transactionsStore = useTransactionsStore();
|
|
const transactionTemplatesStore = useTransactionTemplatesStore();
|
|
|
|
const pictureBrowser = useTemplateRef<PhotoBrowser.PhotoBrowser>('pictureBrowser');
|
|
const pictureInput = useTemplateRef<HTMLInputElement>('pictureInput');
|
|
|
|
const isSupportClipboard = !!navigator.clipboard;
|
|
|
|
const loadingError = ref<unknown | null>(null);
|
|
const isFirstEntry = ref<boolean>(true);
|
|
const removingPictureId = ref<string | null>(null);
|
|
const transactionDateTimeSheetMode = ref<string>('time');
|
|
const showTimeInDefaultTimezone = ref<boolean>(false);
|
|
const showQuickSavePopover = ref<boolean>(false);
|
|
const showTimezonePopup = ref<boolean>(false);
|
|
const showGeoLocationActionSheet = ref<boolean>(false);
|
|
const showMoreActionSheet = ref<boolean>(false);
|
|
const showSourceAmountSheet = ref<boolean>(false);
|
|
const showDestinationAmountSheet = ref<boolean>(false);
|
|
const showCategorySheet = ref<boolean>(false);
|
|
const showSourceAccountSheet = ref<boolean>(false);
|
|
const showDestinationAccountSheet = ref<boolean>(false);
|
|
const showTransactionDateTimeSheet = ref<boolean>(false);
|
|
const showTransactionScheduledFrequencySheet = ref<boolean>(false);
|
|
const showScheduledStartDateSheet = ref<boolean>(false);
|
|
const showScheduledEndDateSheet = ref<boolean>(false);
|
|
const showGeoLocationMapSheet = ref<boolean>(false);
|
|
const showTransactionTagSheet = ref<boolean>(false);
|
|
const showTransactionPictures = ref<boolean>(pageTypeAndMode?.type === TransactionEditPageType.Transaction
|
|
&& (pageTypeAndMode?.mode === TransactionEditPageMode.Add || pageTypeAndMode?.mode === TransactionEditPageMode.Edit)
|
|
&& settingsStore.appSettings.alwaysShowTransactionPicturesInMobileTransactionEditPage);
|
|
|
|
const quickSaveButtonStyleType = computed<number>(() => settingsStore.appSettings.quickSaveButtonStyleInMobileTransactionListPage);
|
|
const quickSaveButtonFloatingPosition = computed<string>(() => {
|
|
switch (settingsStore.appSettings.quickSaveButtonStyleInMobileTransactionListPage) {
|
|
case TransactionQuickSaveButtonStyle.BottomLeftFloating.type:
|
|
return 'left-bottom';
|
|
case TransactionQuickSaveButtonStyle.BottomCenterFloating.type:
|
|
return 'center-bottom';
|
|
case TransactionQuickSaveButtonStyle.BottomRightFloating.type:
|
|
return 'right-bottom';
|
|
default:
|
|
return 'right-bottom';
|
|
}
|
|
});
|
|
|
|
function getAccountBalanceDisplay(account: Account): string {
|
|
if (account.creditLimit) {
|
|
const outstanding = -account.balance;
|
|
const available = account.creditLimit + account.balance;
|
|
return formatAmountToLocalizedNumeralsWithCurrency(outstanding, account.currency)
|
|
+ ' · ' + tt('Available') + ' ' + formatAmountToLocalizedNumeralsWithCurrency(available, account.currency);
|
|
}
|
|
const displayBalance = account.isLiability ? -account.balance : account.balance;
|
|
return formatAmountToLocalizedNumeralsWithCurrency(displayBalance, account.currency);
|
|
}
|
|
|
|
const sourceAccountBalanceDisplay = computed<string>(() => {
|
|
if (!transaction.value.sourceAccountId) return '';
|
|
const account = allVisibleAccounts.value.find(a => a.id === transaction.value.sourceAccountId);
|
|
if (!account) return '';
|
|
return getAccountBalanceDisplay(account);
|
|
});
|
|
|
|
const destinationAccountBalanceDisplay = computed<string>(() => {
|
|
if (!transaction.value.destinationAccountId) return '';
|
|
const account = allVisibleAccounts.value.find(a => a.id === transaction.value.destinationAccountId);
|
|
if (!account) return '';
|
|
return getAccountBalanceDisplay(account);
|
|
});
|
|
|
|
const sourceAmountClass = computed<Record<string, boolean>>(() => {
|
|
const classes: Record<string, boolean> = {
|
|
'readonly': mode.value === TransactionEditPageMode.View,
|
|
'text-expense': transaction.value.type === TransactionType.Expense,
|
|
'text-income': transaction.value.type === TransactionType.Income,
|
|
'text-color-primary': transaction.value.type === TransactionType.Transfer
|
|
};
|
|
|
|
classes[getFontClassByAmount(transaction.value.sourceAmount)] = true;
|
|
|
|
return classes;
|
|
});
|
|
|
|
const destinationAmountClass = computed<Record<string, boolean>>(() => {
|
|
const classes: Record<string, boolean> = {
|
|
'readonly': mode.value === TransactionEditPageMode.View
|
|
};
|
|
|
|
classes[getFontClassByAmount(transaction.value.destinationAmount)] = true;
|
|
|
|
return classes;
|
|
});
|
|
|
|
const transactionDisplayDate = computed<string>(() => {
|
|
if (mode.value !== TransactionEditPageMode.View || !showTimeInDefaultTimezone.value) {
|
|
const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.value.time, transaction.value.utcOffset);
|
|
return formatDateTimeToLongDate(dateTime);
|
|
}
|
|
|
|
const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.value.time, getTimezoneOffsetMinutes(transaction.value.time));
|
|
return formatDateTimeToLongDate(dateTime);
|
|
});
|
|
|
|
const transactionDisplayTime = computed<string>(() => {
|
|
if (mode.value !== TransactionEditPageMode.View || !showTimeInDefaultTimezone.value) {
|
|
const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.value.time, transaction.value.utcOffset);
|
|
return formatDateTimeToLongTime(dateTime);
|
|
}
|
|
|
|
const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.value.time, getTimezoneOffsetMinutes(transaction.value.time));
|
|
const utcOffset = numeralSystem.value.replaceWesternArabicDigitsToLocalizedDigits(getTimezoneOffset(transaction.value.time));
|
|
return `${formatDateTimeToLongTime(dateTime)} (UTC${utcOffset})`;
|
|
});
|
|
|
|
const transactionDisplayTimezoneName = computed<string>(() => {
|
|
for (const timezone of allTimezones.value) {
|
|
if (timezone.name === transaction.value.timeZone) {
|
|
return timezone.displayName;
|
|
}
|
|
}
|
|
|
|
return '';
|
|
});
|
|
|
|
const transactionPictures = computed<Record<string, string | undefined>[]>(() => {
|
|
const thumbs: Record<string, string | undefined>[] = [];
|
|
|
|
if (!transaction.value.pictures || !transaction.value.pictures.length) {
|
|
return thumbs;
|
|
}
|
|
|
|
for (const picture of transaction.value.pictures) {
|
|
thumbs.push({
|
|
url: getTransactionPictureUrl(picture)
|
|
});
|
|
}
|
|
|
|
return thumbs;
|
|
});
|
|
|
|
const transactionThumbs = computed<(string | undefined)[]>(() => {
|
|
const thumbs: (string | undefined)[] = [];
|
|
|
|
if (!transaction.value.pictures || !transaction.value.pictures.length) {
|
|
return thumbs;
|
|
}
|
|
|
|
for (const picture of transaction.value.pictures) {
|
|
thumbs.push(getTransactionPictureUrl(picture));
|
|
}
|
|
|
|
return thumbs;
|
|
});
|
|
|
|
const transactionDisplayScheduledFrequency = computed<string>(() => {
|
|
if (pageTypeAndMode?.type !== TransactionEditPageType.Template) {
|
|
return '';
|
|
}
|
|
|
|
const template = transaction.value as TransactionTemplate;
|
|
|
|
if (template.scheduledFrequencyType === ScheduledTemplateFrequencyType.Disabled.type) {
|
|
return tt('Disabled');
|
|
}
|
|
|
|
const items = (template.scheduledFrequency || '').split(',');
|
|
const scheduledFrequencyValues: number[] = [];
|
|
|
|
for (const item of items) {
|
|
if (item) {
|
|
scheduledFrequencyValues.push(parseInt(item));
|
|
}
|
|
}
|
|
|
|
if (template.scheduledFrequencyType === ScheduledTemplateFrequencyType.Weekly.type) {
|
|
if (scheduledFrequencyValues.length) {
|
|
return tt('format.misc.everyMultiDaysOfWeek', {
|
|
days: getMultiWeekdayLongNames(scheduledFrequencyValues, firstDayOfWeek.value)
|
|
});
|
|
} else {
|
|
return tt('Weekly');
|
|
}
|
|
} else if (template.scheduledFrequencyType === ScheduledTemplateFrequencyType.Monthly.type) {
|
|
if (scheduledFrequencyValues.length) {
|
|
return tt('format.misc.everyMultiDaysOfMonth', {
|
|
days: getMultiMonthdayShortNames(scheduledFrequencyValues)
|
|
});
|
|
} else {
|
|
return tt('Monthly');
|
|
}
|
|
} else {
|
|
return '';
|
|
}
|
|
});
|
|
|
|
const transactionDisplayScheduledStartDate = computed<string>(() => {
|
|
if (pageTypeAndMode?.type !== TransactionEditPageType.Template) {
|
|
return '';
|
|
}
|
|
|
|
const template = transaction.value as TransactionTemplate;
|
|
|
|
if (template.scheduledStartDate) {
|
|
return formatGregorianTextualYearMonthDayToLongDate(template.scheduledStartDate);
|
|
} else {
|
|
return tt('No limit');
|
|
}
|
|
});
|
|
|
|
const transactionDisplayScheduledEndDate = computed<string>(() => {
|
|
if (pageTypeAndMode?.type !== TransactionEditPageType.Template) {
|
|
return '';
|
|
}
|
|
|
|
const template = transaction.value as TransactionTemplate;
|
|
|
|
if (template.scheduledEndDate) {
|
|
return formatGregorianTextualYearMonthDayToLongDate(template.scheduledEndDate);
|
|
} else {
|
|
return tt('No limit');
|
|
}
|
|
});
|
|
|
|
function getPageTypeNameMode(): { type: TransactionEditPageType, mode: TransactionEditPageMode } | null {
|
|
if (props.f7route.path === '/transaction/add') {
|
|
return {
|
|
type: TransactionEditPageType.Transaction,
|
|
mode: TransactionEditPageMode.Add
|
|
};
|
|
} else if (props.f7route.path === '/transaction/edit') {
|
|
return {
|
|
type: TransactionEditPageType.Transaction,
|
|
mode: TransactionEditPageMode.Edit
|
|
};
|
|
} else if (props.f7route.path === '/transaction/detail') {
|
|
return {
|
|
type: TransactionEditPageType.Transaction,
|
|
mode: TransactionEditPageMode.View
|
|
};
|
|
} else if (props.f7route.path === '/template/add') {
|
|
return {
|
|
type: TransactionEditPageType.Template,
|
|
mode: TransactionEditPageMode.Add
|
|
};
|
|
} else if (props.f7route.path === '/template/edit') {
|
|
return {
|
|
type: TransactionEditPageType.Template,
|
|
mode: TransactionEditPageMode.Edit
|
|
};
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function getFontClassByAmount(amount: number): string {
|
|
if (amount >= 100000000 || amount <= -100000000) {
|
|
return 'ebk-small-amount';
|
|
} else if (amount >= 1000000 || amount <= -1000000) {
|
|
return 'ebk-normal-amount';
|
|
} else {
|
|
return 'ebk-large-amount';
|
|
}
|
|
}
|
|
|
|
function getQueryTransactionOptions(): SetTransactionOptions {
|
|
return {
|
|
time: query['time'] ? parseInt(query['time']) : undefined,
|
|
type: query['type'] ? parseInt(query['type']) : 0,
|
|
categoryId: query['categoryId'],
|
|
accountId: query['accountId'],
|
|
destinationAccountId: query['destinationAccountId'],
|
|
amount: query['amount'] ? parseInt(query['amount']) : undefined,
|
|
destinationAmount: query['destinationAmount'] ? parseInt(query['destinationAmount']) : undefined,
|
|
tagIds: query['tagIds'],
|
|
comment: query['comment']
|
|
};
|
|
}
|
|
|
|
function init(): void {
|
|
if (!pageTypeAndMode) {
|
|
showToast('Parameter Invalid');
|
|
loadingError.value = 'Parameter Invalid';
|
|
return;
|
|
}
|
|
|
|
loading.value = true;
|
|
|
|
const promises: Promise<unknown>[] = [
|
|
accountsStore.loadAllAccounts({ force: false }),
|
|
transactionCategoriesStore.loadAllCategories({ force: false }),
|
|
transactionTagsStore.loadAllTags({ force: false }),
|
|
transactionTemplatesStore.loadAllTemplates({ force: false, templateType: TemplateType.Normal.type })
|
|
];
|
|
|
|
if (pageTypeAndMode.type === TransactionEditPageType.Transaction) {
|
|
if (query['id']) {
|
|
if (mode.value === TransactionEditPageMode.Edit) {
|
|
editId.value = query['id'];
|
|
} else if (mode.value === TransactionEditPageMode.Add) {
|
|
duplicateFromId.value = query['id'];
|
|
}
|
|
|
|
promises.push(transactionsStore.getTransaction({ transactionId: query['id'], withPictures: mode.value !== TransactionEditPageMode.Add }));
|
|
}
|
|
} else if (pageTypeAndMode.type === TransactionEditPageType.Template) {
|
|
const template = TransactionTemplate.createNewTransactionTemplate(transaction.value);
|
|
template.name = '';
|
|
|
|
if (query['templateType']) {
|
|
template.templateType = parseInt(query['templateType']);
|
|
}
|
|
|
|
if (template.templateType === TemplateType.Schedule.type) {
|
|
template.scheduledFrequencyType = ScheduledTemplateFrequencyType.Disabled.type;
|
|
template.scheduledFrequency = '';
|
|
}
|
|
|
|
transaction.value = template;
|
|
|
|
if (query['id']) {
|
|
if (mode.value === TransactionEditPageMode.Edit) {
|
|
editId.value = query['id'];
|
|
}
|
|
|
|
promises.push(transactionTemplatesStore.getTemplate({ templateId: query['id'] }));
|
|
}
|
|
}
|
|
|
|
const initOptions = getQueryTransactionOptions();
|
|
|
|
if (initOptions.type &&
|
|
initOptions.type >= TransactionType.Income &&
|
|
initOptions.type <= TransactionType.Transfer) {
|
|
transaction.value.type = initOptions.type;
|
|
} else if (initOptions.type === TransactionType.ModifyBalance &&
|
|
pageTypeAndMode.type === TransactionEditPageType.Transaction &&
|
|
mode.value === TransactionEditPageMode.View) {
|
|
transaction.value.type = initOptions.type;
|
|
}
|
|
|
|
if (mode.value === TransactionEditPageMode.Add) {
|
|
clientSessionId.value = generateRandomUUID();
|
|
}
|
|
|
|
Promise.all(promises).then(function (responses) {
|
|
if (query['id'] && !responses[4]) {
|
|
if (pageTypeAndMode.type === TransactionEditPageType.Transaction) {
|
|
showToast('Unable to retrieve transaction');
|
|
loadingError.value = 'Unable to retrieve transaction';
|
|
} else if (pageTypeAndMode.type === TransactionEditPageType.Template) {
|
|
showToast('Unable to retrieve template');
|
|
loadingError.value = 'Unable to retrieve template';
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
let fromTransaction: Transaction | TransactionTemplate | null = null;
|
|
|
|
if (pageTypeAndMode.type === TransactionEditPageType.Transaction) {
|
|
if (query['id'] && responses[4] instanceof Transaction) {
|
|
fromTransaction = responses[4];
|
|
} else if (query['templateId'] && transactionTemplatesStore.allTransactionTemplatesMap && transactionTemplatesStore.allTransactionTemplatesMap[TemplateType.Normal.type]) {
|
|
fromTransaction = (transactionTemplatesStore.allTransactionTemplatesMap[TemplateType.Normal.type] as Record<string, TransactionTemplate>)[query['templateId']] ?? null;
|
|
|
|
if (fromTransaction) {
|
|
addByTemplateId.value = fromTransaction.id;
|
|
}
|
|
} else if (query['noTransactionDraft'] !== 'true' && (settingsStore.appSettings.autoSaveTransactionDraft === 'enabled' || settingsStore.appSettings.autoSaveTransactionDraft === 'confirmation') && transactionsStore.transactionDraft) {
|
|
fromTransaction = Transaction.ofDraft(transactionsStore.transactionDraft);
|
|
}
|
|
} else if (pageTypeAndMode.type === TransactionEditPageType.Template && responses[4] instanceof TransactionTemplate) {
|
|
if (query['id']) {
|
|
fromTransaction = responses[4];
|
|
}
|
|
}
|
|
|
|
setTransactionModel(
|
|
fromTransaction,
|
|
initOptions,
|
|
pageTypeAndMode.type === TransactionEditPageType.Transaction && (mode.value === TransactionEditPageMode.Edit || mode.value === TransactionEditPageMode.View)
|
|
);
|
|
|
|
if (pageTypeAndMode.type === TransactionEditPageType.Transaction && query['id'] && responses[4] instanceof Transaction) {
|
|
if (fromTransaction && query['withTime'] && query['withTime'] === 'true') {
|
|
transaction.value.time = fromTransaction.time;
|
|
transaction.value.timeZone = fromTransaction.timeZone;
|
|
transaction.value.utcOffset = fromTransaction.utcOffset;
|
|
}
|
|
|
|
if (fromTransaction && query['withGeoLocation'] && query['withGeoLocation'] === 'true') {
|
|
transaction.value.setGeoLocation(fromTransaction.geoLocation);
|
|
}
|
|
} else if (pageTypeAndMode.type === TransactionEditPageType.Template && query['id'] && responses[4] instanceof TransactionTemplate) {
|
|
const template = responses[4];
|
|
transaction.value.id = template.id;
|
|
|
|
if (!(transaction.value instanceof TransactionTemplate)) {
|
|
transaction.value = TransactionTemplate.createNewTransactionTemplate(transaction.value);
|
|
}
|
|
|
|
(transaction.value as TransactionTemplate).fillFrom(template);
|
|
}
|
|
|
|
loading.value = false;
|
|
}).catch(error => {
|
|
logger.error('failed to load essential data for editing transaction', error);
|
|
|
|
if (error.processed) {
|
|
loading.value = false;
|
|
} else {
|
|
loadingError.value = error;
|
|
showToast(error.message || error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function save(afterAction: AfterSaveAction): void {
|
|
const router = props.f7router;
|
|
|
|
if (mode.value === TransactionEditPageMode.View) {
|
|
return;
|
|
}
|
|
|
|
const problemMessage = inputEmptyProblemMessage.value;
|
|
|
|
if (problemMessage) {
|
|
showAlert(problemMessage);
|
|
return;
|
|
}
|
|
|
|
if (pageTypeAndMode?.type === TransactionEditPageType.Transaction && (mode.value === TransactionEditPageMode.Add || mode.value === TransactionEditPageMode.Edit)) {
|
|
const doSubmit = function () {
|
|
submitting.value = true;
|
|
showLoading(() => submitting.value);
|
|
|
|
transactionsStore.saveTransaction({
|
|
transaction: transaction.value as Transaction,
|
|
defaultCurrency: defaultCurrency.value,
|
|
isEdit: mode.value === TransactionEditPageMode.Edit,
|
|
clientSessionId: clientSessionId.value
|
|
}).then(() => {
|
|
submitting.value = false;
|
|
submitted.value = true;
|
|
hideLoading();
|
|
|
|
if (mode.value === TransactionEditPageMode.Add && query['noTransactionDraft'] !== 'true' && !addByTemplateId.value && !duplicateFromId.value) {
|
|
transactionsStore.clearTransactionDraft();
|
|
}
|
|
|
|
if (mode.value === TransactionEditPageMode.Add && (afterAction === AfterSaveAction.StayWithNewTransaction || afterAction === AfterSaveAction.StayWithCurrentTransaction)) {
|
|
showToast('You have added a new transaction');
|
|
updateTransactionModelByAfterSaveAction(afterAction, getQueryTransactionOptions());
|
|
clientSessionId.value = generateRandomUUID();
|
|
} else {
|
|
if (mode.value === TransactionEditPageMode.Add) {
|
|
showToast('You have added a new transaction');
|
|
} else if (mode.value === TransactionEditPageMode.Edit) {
|
|
showToast('You have saved this transaction');
|
|
}
|
|
|
|
router.back();
|
|
}
|
|
}).catch(error => {
|
|
submitting.value = false;
|
|
hideLoading();
|
|
|
|
if (error.error && (error.error.errorCode === KnownErrorCode.TransactionCannotCreateInThisTime || error.error.errorCode === KnownErrorCode.TransactionCannotModifyInThisTime)) {
|
|
showConfirm('You have set this time range to prevent editing transactions. Would you like to change the editable transaction range to All?', () => {
|
|
submitting.value = true;
|
|
showLoading(() => submitting.value);
|
|
|
|
userStore.updateUserTransactionEditScope({
|
|
transactionEditScope: TransactionEditScopeType.All.type
|
|
}).then(() => {
|
|
submitting.value = false;
|
|
hideLoading();
|
|
|
|
showToast('Your editable transaction range has been set to All');
|
|
}).catch(error => {
|
|
submitting.value = false;
|
|
hideLoading();
|
|
|
|
if (!error.processed) {
|
|
showToast(error.message || error);
|
|
}
|
|
});
|
|
});
|
|
} else if (!error.processed) {
|
|
showToast(error.message || error);
|
|
}
|
|
});
|
|
};
|
|
|
|
if (transaction.value.sourceAmount === 0) {
|
|
showConfirm('Are you sure you want to save this transaction with a zero amount?', () => {
|
|
doSubmit();
|
|
});
|
|
} else {
|
|
doSubmit();
|
|
}
|
|
} else if (pageTypeAndMode?.type === TransactionEditPageType.Template && (mode.value === TransactionEditPageMode.Add || mode.value === TransactionEditPageMode.Edit)) {
|
|
submitting.value = true;
|
|
showLoading(() => submitting.value);
|
|
|
|
transactionTemplatesStore.saveTemplateContent({
|
|
template: transaction.value as TransactionTemplate,
|
|
isEdit: mode.value === TransactionEditPageMode.Edit,
|
|
clientSessionId: clientSessionId.value
|
|
}).then(() => {
|
|
submitting.value = false;
|
|
submitted.value = true;
|
|
hideLoading();
|
|
|
|
if (mode.value === TransactionEditPageMode.Add) {
|
|
showToast('You have added a new template');
|
|
} else if (mode.value === TransactionEditPageMode.Edit) {
|
|
showToast('You have saved this template');
|
|
}
|
|
|
|
router.back();
|
|
}).catch(error => {
|
|
submitting.value = false;
|
|
hideLoading();
|
|
|
|
if (!error.processed) {
|
|
showToast(error.message || error);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function quickSave(): void {
|
|
if (mode.value === TransactionEditPageMode.View) {
|
|
return;
|
|
}
|
|
|
|
if (pageTypeAndMode?.type === TransactionEditPageType.Transaction && mode.value === TransactionEditPageMode.Add) {
|
|
const quickAddActionType = settingsStore.appSettings.quickAddButtonActionInMobileTransactionEditPage;
|
|
|
|
if (quickAddActionType === TransactionQuickAddButtonActionType.OpenMenu.type) {
|
|
showQuickSavePopover.value = true;
|
|
return;
|
|
} else if (quickAddActionType === TransactionQuickAddButtonActionType.SaveAndAddNewTransaction.type) {
|
|
save(AfterSaveAction.StayWithNewTransaction);
|
|
return;
|
|
} else if (quickAddActionType === TransactionQuickAddButtonActionType.SaveAndKeepCurrentData.type) {
|
|
save(AfterSaveAction.StayWithCurrentTransaction);
|
|
return;
|
|
}
|
|
}
|
|
|
|
save(AfterSaveAction.GoBack);
|
|
}
|
|
|
|
function pasteAmount(type: 'sourceAmount' | 'destinationAmount'): void {
|
|
if (mode.value === TransactionEditPageMode.View || !isSupportClipboard) {
|
|
return;
|
|
}
|
|
|
|
navigator.clipboard.readText().then(text => {
|
|
if (!text) {
|
|
return;
|
|
}
|
|
|
|
const parsedAmount = parseAmountFromLocalizedNumerals(text);
|
|
|
|
if (Number.isNaN(parsedAmount) || !Number.isFinite(parsedAmount)) {
|
|
showToast('Cannot parse amount from clipboard');
|
|
return;
|
|
}
|
|
|
|
if (parsedAmount < TRANSACTION_MIN_AMOUNT || parsedAmount > TRANSACTION_MAX_AMOUNT) {
|
|
showToast('Numeric Overflow');
|
|
return;
|
|
}
|
|
|
|
if (type === 'sourceAmount') {
|
|
transaction.value.sourceAmount = parsedAmount;
|
|
} else if (type === 'destinationAmount') {
|
|
transaction.value.destinationAmount = parsedAmount;
|
|
}
|
|
}).catch(error => {
|
|
logger.error('failed to read clipboard text', error);
|
|
showToast('Unable to read clipboard text');
|
|
});
|
|
}
|
|
|
|
function updateGeoLocation(forceUpdate: boolean): void {
|
|
if (!isSupportGeoLocation) {
|
|
logger.warn('this browser does not support geo location');
|
|
|
|
if (forceUpdate) {
|
|
showToast('Unable to retrieve current position');
|
|
}
|
|
return;
|
|
}
|
|
|
|
navigator.geolocation.getCurrentPosition(function (position) {
|
|
if (!position || !position.coords) {
|
|
logger.error('current position is null');
|
|
geoLocationStatus.value = GeoLocationStatus.Error;
|
|
|
|
if (forceUpdate) {
|
|
showToast('Unable to retrieve current position');
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
geoLocationStatus.value = GeoLocationStatus.Success;
|
|
|
|
transaction.value.setLatitudeAndLongitude(position.coords.latitude, position.coords.longitude);
|
|
}, function (err) {
|
|
logger.error('cannot retrieve current position', err);
|
|
geoLocationStatus.value = GeoLocationStatus.Error;
|
|
|
|
if (forceUpdate) {
|
|
showToast('Unable to retrieve current position');
|
|
}
|
|
});
|
|
|
|
geoLocationStatus.value = GeoLocationStatus.Getting;
|
|
}
|
|
|
|
function clearGeoLocation(): void {
|
|
geoLocationStatus.value = null;
|
|
transaction.value.removeGeoLocation();
|
|
}
|
|
|
|
function showDateTimeDialog(sheetMode: string): void {
|
|
if (mode.value === TransactionEditPageMode.View) {
|
|
showTimeInDefaultTimezone.value = !showTimeInDefaultTimezone.value;
|
|
} else {
|
|
transactionDateTimeSheetMode.value = sheetMode;
|
|
showTransactionDateTimeSheet.value = true;
|
|
}
|
|
}
|
|
|
|
function showOpenPictureDialog(): void {
|
|
if (!canAddTransactionPicture.value || submitting.value) {
|
|
return;
|
|
}
|
|
|
|
pictureInput.value?.click();
|
|
}
|
|
|
|
function uploadPicture(event: Event): void {
|
|
if (!event || !event.target) {
|
|
return;
|
|
}
|
|
|
|
const el = event.target as HTMLInputElement;
|
|
|
|
if (!el.files || !el.files.length) {
|
|
return;
|
|
}
|
|
|
|
const pictureFile = el.files[0] as File;
|
|
|
|
el.value = '';
|
|
|
|
uploadingPicture.value = true;
|
|
submitting.value = true;
|
|
|
|
transactionsStore.uploadTransactionPicture({ pictureFile }).then(response => {
|
|
transaction.value.addPicture(response);
|
|
uploadingPicture.value = false;
|
|
submitting.value = false;
|
|
}).catch(error => {
|
|
uploadingPicture.value = false;
|
|
submitting.value = false;
|
|
|
|
if (!error.processed) {
|
|
showToast(error.message || error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function viewOrRemovePicture(pictureInfo: TransactionPictureInfoBasicResponse): void {
|
|
if (mode.value !== TransactionEditPageMode.Add && mode.value !== TransactionEditPageMode.Edit && transaction.value.pictures && transaction.value.pictures.length) {
|
|
pictureBrowser.value?.open();
|
|
return;
|
|
}
|
|
|
|
showConfirm('Are you sure you want to remove this transaction picture?', () => {
|
|
removingPictureId.value = pictureInfo.pictureId;
|
|
submitting.value = true;
|
|
|
|
transactionsStore.removeUnusedTransactionPicture({ pictureInfo }).then(response => {
|
|
if (response) {
|
|
transaction.value.removePicture(pictureInfo);
|
|
}
|
|
|
|
removingPictureId.value = '';
|
|
submitting.value = false;
|
|
}).catch(error => {
|
|
if (error.error && error.error.errorCode === KnownErrorCode.TransactionPictureNotFound) {
|
|
transaction.value.removePicture(pictureInfo);
|
|
} else if (!error.processed) {
|
|
showToast(error.message || error);
|
|
}
|
|
|
|
removingPictureId.value = '';
|
|
submitting.value = false;
|
|
});
|
|
});
|
|
}
|
|
|
|
function duplicate(withTime?: boolean, withGeoLocation?: boolean): void {
|
|
props.f7router.navigate(`/transaction/add?id=${transaction.value.id}&type=${transaction.value.type}&withTime=${withTime ?? false}&withGeoLocation=${withGeoLocation ?? false}`);
|
|
}
|
|
|
|
function navigateToEdit(): void {
|
|
props.f7router.navigate(`/transaction/edit?id=${transaction.value.id}&type=${transaction.value.type}`);
|
|
}
|
|
|
|
function remove(): void {
|
|
showConfirm('Are you sure you want to delete this transaction?', () => {
|
|
showLoading();
|
|
transactionsStore.deleteTransaction({
|
|
transaction: transaction.value,
|
|
defaultCurrency: defaultCurrency.value
|
|
}).then(() => {
|
|
hideLoading();
|
|
props.f7router.back();
|
|
}).catch(error => {
|
|
hideLoading();
|
|
if (!error.processed) {
|
|
showToast(error.message || error);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function onPageAfterIn(): void {
|
|
routeBackOnError(props.f7router, loadingError);
|
|
|
|
if (settingsStore.appSettings.autoGetCurrentGeoLocation && mode.value === TransactionEditPageMode.Add
|
|
&& !geoLocationStatus.value && !transaction.value.geoLocation) {
|
|
updateGeoLocation(false);
|
|
}
|
|
|
|
if (isFirstEntry.value) {
|
|
isFirstEntry.value = false;
|
|
return;
|
|
}
|
|
|
|
if (mode.value === TransactionEditPageMode.View && query['id']) {
|
|
transactionsStore.getTransaction({ transactionId: query['id'], withPictures: true }).then(t => {
|
|
setTransactionModel(t, {}, true);
|
|
}).catch(error => {
|
|
if (!error.processed) {
|
|
showToast(error.message || error);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function onPageBeforeOut(): void {
|
|
if (submitted.value || pageTypeAndMode?.type !== TransactionEditPageType.Transaction || mode.value !== TransactionEditPageMode.Add || query['noTransactionDraft'] === 'true' || addByTemplateId.value || duplicateFromId.value) {
|
|
return;
|
|
}
|
|
|
|
const initAmount: number | undefined = query['amount'] ? parseInt(query['amount']) : undefined;
|
|
|
|
if (settingsStore.appSettings.autoSaveTransactionDraft === 'confirmation') {
|
|
if (transactionsStore.isTransactionDraftModified(transaction.value, initAmount, query['categoryId'], query['accountId'], query['tagIds'], firstVisibleAccountId.value)) {
|
|
showConfirm('Do you want to save this transaction draft?', () => {
|
|
transactionsStore.saveTransactionDraft(transaction.value, initAmount, query['categoryId'], query['accountId'], query['tagIds'], firstVisibleAccountId.value);
|
|
}, () => {
|
|
transactionsStore.clearTransactionDraft();
|
|
});
|
|
} else {
|
|
transactionsStore.clearTransactionDraft();
|
|
}
|
|
} else if (settingsStore.appSettings.autoSaveTransactionDraft === 'enabled') {
|
|
transactionsStore.saveTransactionDraft(transaction.value, initAmount, query['categoryId'], query['accountId'], query['tagIds'], firstVisibleAccountId.value);
|
|
}
|
|
}
|
|
|
|
init();
|
|
</script>
|
|
|
|
<style>
|
|
.category-separate-icon.icon {
|
|
margin-inline-start: 5px;
|
|
margin-inline-end: 5px;
|
|
font-size: var(--ebk-category-separate-icon-font-size);
|
|
line-height: 16px;
|
|
color: var(--f7-color-gray-tint);
|
|
}
|
|
|
|
.transaction-edit-amount {
|
|
line-height: 53px;
|
|
}
|
|
|
|
.transaction-edit-amount .item-title {
|
|
font-weight: bolder;
|
|
}
|
|
|
|
.transaction-edit-amount .item-header {
|
|
padding-top: calc(var(--f7-typography-padding) / 2);
|
|
}
|
|
|
|
.transaction-edit-datetime .item-title {
|
|
width: 100%;
|
|
}
|
|
|
|
.transaction-edit-datetime .item-title > .item-header > .transaction-edit-datetime-header {
|
|
display: block;
|
|
width: 100%;
|
|
}
|
|
|
|
.transaction-edit-datetime .item-title > .transaction-edit-datetime-title {
|
|
display: flex;
|
|
width: 100%;
|
|
}
|
|
|
|
.transaction-edit-datetime .item-title > .transaction-edit-datetime-title > .transaction-edit-datetime-time {
|
|
flex-grow: 1;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.transaction-edit-timezone-name {
|
|
padding-inline-start: 4px;
|
|
}
|
|
|
|
.transaction-edit-tag {
|
|
--f7-chip-bg-color: var(--ebk-transaction-tag-chip-bg-color);
|
|
margin-inline-end: 4px;
|
|
max-width: 100%;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.chip.transaction-edit-tag .chip-media+.chip-label {
|
|
margin-inline-start: 0;
|
|
}
|
|
|
|
.chip.transaction-edit-tag .chip-media i.icon {
|
|
font-size: calc(var(--f7-chip-media-size) - 12px);
|
|
height: calc(var(--f7-chip-media-size) - 12px);
|
|
}
|
|
|
|
.transaction-pictures {
|
|
height: var(--ebk-transaction-picture-size);
|
|
}
|
|
|
|
.transaction-picture-container,
|
|
.transaction-picture {
|
|
width: var(--ebk-transaction-picture-size);
|
|
height: var(--ebk-transaction-picture-size);
|
|
}
|
|
|
|
.transaction-picture .transaction-picture-control-backdrop {
|
|
width: 100%;
|
|
height: 100%;
|
|
position: absolute;
|
|
z-index: 10;
|
|
background-color: rgba(0, 0, 0, 0.4);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.transaction-picture .picture-control-icon {
|
|
z-index: 15;
|
|
font-size: var(--ebk-transaction-picture-add-icon-size);
|
|
}
|
|
|
|
.transaction-picture .picture-remove-icon {
|
|
background-color: transparent;
|
|
color: rgba(255, 255, 255, 0.8);
|
|
font-size: var(--ebk-transaction-picture-remove-icon-size);
|
|
}
|
|
|
|
.transaction-picture > img {
|
|
object-fit: cover;
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.transaction-picture-add {
|
|
width: calc(var(--ebk-transaction-picture-size) - 2px);
|
|
height: calc(var(--ebk-transaction-picture-size) - 4px);
|
|
border: 2px dashed #ccc;
|
|
border-radius: 8px;
|
|
}
|
|
</style>
|