Upgrade to vue3 (#16)

* upgrade to vue 3.x and framework7 8.x
* change calendar plugin to vue-datepicker
* disable export button when user does not hava any transaction
* implement new pin code input
* append thousands separator in amount in exchange rates page
This commit is contained in:
mayswind
2023-04-21 01:45:00 +08:00
committed by GitHub
parent 4b0f7d45e8
commit b1c765eb51
89 changed files with 8353 additions and 16671 deletions
+31 -19
View File
@@ -2,28 +2,22 @@
<f7-page>
<f7-navbar :title="$t('About')" :back-link="$t('Back')"></f7-navbar>
<f7-card>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item :title="$t('Version')" :after="version"></f7-list-item>
<f7-list-item :title="$t('Build Time')" :after="buildTime | moment($t('format.datetime.long'))" v-if="buildTime"></f7-list-item>
<f7-list-item external :title="$t('Official Website')" link="https://github.com/mayswind/ezbookkeeping" target="_blank"></f7-list-item>
<f7-list-item :title="$t('License')" link="#" popup-open=".license-popup"></f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-top">
<f7-list-item :title="$t('Version')" :after="version"></f7-list-item>
<f7-list-item :title="$t('Build Time')" :after="buildTime" v-if="buildTime"></f7-list-item>
<f7-list-item external :title="$t('Official Website')" link="https://github.com/mayswind/ezbookkeeping" target="_blank"></f7-list-item>
<f7-list-item :title="$t('License')" link="#" popup-open=".license-popup"></f7-list-item>
</f7-list>
<f7-popup class="license-popup">
<f7-popup push with-subnavbar swipe-to-close swipe-handler=".swipe-handler" class="license-popup">
<f7-page>
<f7-navbar>
<f7-nav-title :title="$t('License')"></f7-nav-title>
<f7-nav-right>
<f7-link popup-close :text="$t('Done')"></f7-link>
</f7-nav-right>
<div class="swipe-handler"></div>
<f7-subnavbar :title="$t('License') "></f7-subnavbar>
</f7-navbar>
<f7-block>
<f7-block strong outline>
<p>
<span v-for="(line, num) in licenseLines" :key="num"
<span :key="num" v-for="(line, num) in licenseLines"
:style="{ 'display': line ? 'initial' : 'block', 'padding' : line ? '0' : '0 0 1em 0' }">
{{ line }}
</span>
@@ -34,7 +28,7 @@
<span>All the third party software included or linked is redistributed under the terms and conditions of their original licenses.</span>
</p>
<p></p>
<p v-for="license in thirdPartyLicenses" :key="license.name">
<p :key="license.name" v-for="license in thirdPartyLicenses">
<strong>{{ license.name }}</strong>
<br v-if="license.copyright"/><span v-if="license.copyright">{{ license.copyright }}</span>
<br v-if="license.url"/><span class="work-break-all" v-if="license.url">{{ license.url }}</span>
@@ -53,7 +47,11 @@ export default {
return 'v' + this.$version;
},
buildTime() {
return this.$buildTime;
if (!this.$buildTime) {
return this.$buildTime;
}
return this.$utilities.formatUnixTime(this.$buildTime, this.$t('format.datetime.long'));
},
licenseLines() {
return this.$licenses.license.replaceAll(/\r/g, '').split('\n');
@@ -64,3 +62,17 @@ export default {
}
}
</script>
<style>
.license-popup .navbar-bg {
background-color: rgb(var(--f7-navbar-bg-color-rgb, var(--f7-bars-bg-color-rgb)));
}
.license-popup .subnavbar {
background-color: rgb(var(--f7-subnavbar-bg-color-rgb, var(--f7-bars-bg-color-rgb)));
}
.license-popup .subnavbar-title {
--f7-subnavbar-title-font-size: 30px;
}
</style>
+11 -23
View File
@@ -5,38 +5,26 @@
<f7-nav-title :title="$t('Application Lock')"></f7-nav-title>
</f7-navbar>
<f7-card v-if="isEnableApplicationLock">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item :title="$t('Status')" :after="$t('Enabled')"></f7-list-item>
<f7-list-item v-if="isSupportedWebAuthn">
<span>{{ $t('Face ID / Touch ID') }}</span>
<f7-toggle :checked="isEnableApplicationLockWebAuthn" @toggle:change="isEnableApplicationLockWebAuthn = $event"></f7-toggle>
</f7-list-item>
<f7-list-button @click="disable(null)">{{ $t('Disable') }}</f7-list-button>
</f7-list>
</f7-card-content>
</f7-card>
<f7-card v-else-if="!isEnableApplicationLock">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item :title="$t('Status')" :after="$t('Disabled')"></f7-list-item>
<f7-list-button @click="enable(null)">{{ $t('Enable') }}</f7-list-button>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-top">
<f7-list-item :title="$t('Status')" :after="$t(isEnableApplicationLock ? 'Enabled' : 'Disabled')"></f7-list-item>
<f7-list-item v-if="isEnableApplicationLock && isSupportedWebAuthn">
<span>{{ $t('Face ID / Touch ID') }}</span>
<f7-toggle :checked="isEnableApplicationLockWebAuthn" @toggle:change="isEnableApplicationLockWebAuthn = $event"></f7-toggle>
</f7-list-item>
<f7-list-button v-if="isEnableApplicationLock" @click="disable(null)">{{ $t('Disable') }}</f7-list-button>
<f7-list-button v-if="!isEnableApplicationLock" @click="enable(null)">{{ $t('Enable') }}</f7-list-button>
</f7-list>
<pin-code-input-sheet :title="$t('PIN Code')"
:hint="$t('Please input a new PIN code. PIN code would encrypt your local data, so you need input this PIN code when you launch this app. If this PIN code is lost, you should re-login.')"
:show.sync="showInputPinCodeSheetForEnable"
v-model:show="showInputPinCodeSheetForEnable"
v-model="currentPinCodeForEnable"
@pincode:confirm="enable">
</pin-code-input-sheet>
<pin-code-input-sheet :title="$t('PIN Code')"
:hint="$t('Please enter your current PIN code when disable application lock')"
:show.sync="showInputPinCodeSheetForDisable"
v-model:show="showInputPinCodeSheetForDisable"
v-model="currentPinCodeForDisable"
@pincode:confirm="disable">
</pin-code-input-sheet>
+95 -66
View File
@@ -8,73 +8,73 @@
</f7-nav-right>
</f7-navbar>
<f7-card>
<f7-card-content class="no-safe-areas" :padding="false" v-if="exchangeRatesData && exchangeRatesData.exchangeRates && exchangeRatesData.exchangeRates.length">
<f7-list>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:header="$t('Base Currency')"
smart-select :smart-select-params="{ openIn: 'popup', pageTitle: $t('Base Currency'), searchbar: true, searchbarPlaceholder: $t('Currency Name'), searchbarDisableText: $t('Cancel'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }"
>
<f7-block slot="title" class="no-padding no-margin">
<span>{{ $t(`currency.${baseCurrency}`) }}&nbsp;</span>
<small class="smaller">{{ baseCurrency }}</small>
</f7-block>
<select v-model="baseCurrency">
<option v-for="exchangeRate in availableExchangeRates"
:key="exchangeRate.currencyCode"
:value="exchangeRate.currencyCode">{{ exchangeRate.currencyDisplayName }}</option>
</select>
</f7-list-item>
<f7-list-item
class="currency-base-amount"
link="#" no-chevron
:style="{ fontSize: baseAmountFontSize + 'px' }"
:header="$t('Base Amount')"
:title="baseAmount | currency"
@click="showBaseAmountSheet = true"
>
<number-pad-sheet :min-value="$constants.transaction.minAmount"
:max-value="$constants.transaction.maxAmount"
:show.sync="showBaseAmountSheet"
v-model="baseAmount"
></number-pad-sheet>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-vertical" v-if="exchangeRatesData && exchangeRatesData.exchangeRates && exchangeRatesData.exchangeRates.length">
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:header="$t('Base Currency')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Currency Name'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Base Currency'), popupCloseLinkText: $t('Done') }"
>
<template #title>
<div class="no-padding no-margin">
<span>{{ $t(`currency.${baseCurrency}`) }}&nbsp;</span>
<small class="smaller">{{ baseCurrency }}</small>
</div>
</template>
<select v-model="baseCurrency">
<option :value="exchangeRate.currencyCode"
:key="exchangeRate.currencyCode"
v-for="exchangeRate in availableExchangeRates">{{ exchangeRate.currencyDisplayName }}</option>
</select>
</f7-list-item>
<f7-list-item
class="currency-base-amount"
link="#" no-chevron
:style="{ fontSize: baseAmountFontSize + 'px' }"
:header="$t('Base Amount')"
:title="displayBaseAmount"
@click="showBaseAmountSheet = true"
>
<number-pad-sheet :min-value="$constants.transaction.minAmount"
:max-value="$constants.transaction.maxAmount"
v-model:show="showBaseAmountSheet"
v-model="baseAmount"
></number-pad-sheet>
</f7-list-item>
</f7-list>
<f7-card>
<f7-card-content class="no-safe-areas" :padding="false" v-if="!exchangeRatesData || !exchangeRatesData.exchangeRates || !exchangeRatesData.exchangeRates.length">
<f7-list>
<f7-list-item :title="$t('No exchange rates data')"></f7-list-item>
</f7-list>
</f7-card-content>
<f7-card-content class="no-safe-areas" :padding="false" v-if="exchangeRatesData && exchangeRatesData.exchangeRates && exchangeRatesData.exchangeRates.length">
<f7-list>
<f7-list-item v-for="exchangeRate in availableExchangeRates" :key="exchangeRate.currencyCode"
:after="getConvertedAmount(exchangeRate) | exchangeRate"
swipeout>
<f7-block slot="title" class="no-padding no-margin">
<span style="margin-right: 5px">{{ exchangeRate.currencyDisplayName }}</span>
<small class="smaller">{{ exchangeRate.currencyCode }}</small>
</f7-block>
<f7-swipeout-actions right>
<f7-swipeout-button color="primary" close :text="$t('Set As Baseline')" @click="setAsBaseline(exchangeRate.currencyCode, getConvertedAmount(exchangeRate))"></f7-swipeout-button>
</f7-swipeout-actions>
</f7-list-item>
</f7-list>
</f7-card-content>
<f7-card-footer v-if="exchangeRatesData.exchangeRates && exchangeRatesData.exchangeRates.length">
<span>{{ $t('Last Updated') }}</span>
<span>{{ exchangeRatesData.updateTime | moment($t('format.date.long')) }}</span>
</f7-card-footer>
<f7-card-footer v-if="exchangeRatesData.exchangeRates && exchangeRatesData.exchangeRates.length">
<span>{{ $t('Data source') }}</span>
<f7-link external target="_blank" :href="exchangeRatesData.referenceUrl" v-if="exchangeRatesData.referenceUrl">{{ exchangeRatesData.dataSource }}</f7-link>
<span v-else-if="!exchangeRatesData.referenceUrl">{{ exchangeRatesData.dataSource }}</span>
</f7-card-footer>
</f7-card>
<f7-list strong inset dividers class="margin-vertical" v-if="!exchangeRatesData || !exchangeRatesData.exchangeRates || !exchangeRatesData.exchangeRates.length">
<f7-list-item :title="$t('No exchange rates data')"></f7-list-item>
</f7-list>
<f7-list strong inset dividers class="margin-vertical" v-if="exchangeRatesData && exchangeRatesData.exchangeRates && exchangeRatesData.exchangeRates.length">
<f7-list-item swipeout
:after="getDisplayConvertedAmount(exchangeRate)"
:key="exchangeRate.currencyCode" v-for="exchangeRate in availableExchangeRates">
<template #title>
<div class="no-padding no-margin">
<span style="margin-right: 5px">{{ exchangeRate.currencyDisplayName }}</span>
<small class="smaller">{{ exchangeRate.currencyCode }}</small>
</div>
</template>
<f7-swipeout-actions right>
<f7-swipeout-button color="primary" close :text="$t('Set As Baseline')" @click="setAsBaseline(exchangeRate.currencyCode, getConvertedAmount(exchangeRate))"></f7-swipeout-button>
</f7-swipeout-actions>
</f7-list-item>
</f7-list>
<f7-list strong inset dividers class="margin-vertical" v-if="exchangeRatesData && exchangeRatesData.exchangeRates && exchangeRatesData.exchangeRates.length">
<f7-list-item v-if="exchangeRatesDataUpdateTime">
<small>{{ $t('Last Updated') }}</small>
<small>{{ exchangeRatesDataUpdateTime }}</small>
</f7-list-item>
<f7-list-item>
<small>{{ $t('Data source') }}</small>
<small>
<f7-link external target="_blank" :href="exchangeRatesData.referenceUrl" v-if="exchangeRatesData.referenceUrl">{{ exchangeRatesData.dataSource }}</f7-link>
<span v-else-if="!exchangeRatesData.referenceUrl">{{ exchangeRatesData.dataSource }}</span>
</small>
</f7-list-item>
</f7-list>
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
<f7-actions-group>
@@ -106,6 +106,13 @@ export default {
exchangeRatesData() {
return this.$store.state.latestExchangeRates.data;
},
exchangeRatesDataUpdateTime() {
if (!this.exchangeRatesData) {
return '';
}
return this.$utilities.formatUnixTime(this.exchangeRatesData.updateTime, this.$t('format.date.long'));
},
exchangeRateMap() {
const exchangeRateMap = {};
@@ -143,6 +150,9 @@ export default {
return availableExchangeRates;
},
displayBaseAmount() {
return this.$locale.getDisplayCurrency(this.baseAmount);
},
baseAmountFontSize() {
return this.getFontSizeByAmount(this.baseAmount);
}
@@ -213,6 +223,25 @@ export default {
return this.$utilities.getExchangedAmount(this.baseAmount / 100, fromExchangeRate.rate, toExchangeRate.rate);
},
getDisplayConvertedAmount(toExchangeRate) {
const rateStr = this.getConvertedAmount(toExchangeRate).toString();
if (rateStr.indexOf('.') < 0) {
return this.$utilities.appendThousandsSeparator(rateStr);
} else {
let firstNonZeroPos = 0;
for (let i = 0; i < rateStr.length; i++) {
if (rateStr.charAt(i) !== '.' && rateStr.charAt(i) !== '0') {
firstNonZeroPos = Math.min(i + 4, rateStr.length);
break;
}
}
const trimmedRateStr = rateStr.substring(0, Math.max(6, Math.max(firstNonZeroPos, rateStr.indexOf('.') + 2)));
return this.$utilities.appendThousandsSeparator(trimmedRateStr);
}
},
setAsBaseline(currency, amount) {
if (!this.$utilities.isNumber(amount)) {
amount = '';
+186 -124
View File
@@ -13,14 +13,14 @@
<small>Expense</small>
</span>
<span class="card-header-content" v-else-if="!loading">
<span class="home-summary-month">{{ dateRange.thisMonth.startTime | moment('MMMM') }}</span>
<span class="home-summary-month">{{ displayDateRange.thisMonth.displayTime }}</span>
<span>·</span>
<small>{{ $t('Expense') }}</small>
</span>
</p>
<p class="no-margin">
<span class="month-expense" v-if="loading">0.00 USD</span>
<span class="month-expense" v-else-if="!loading">{{ thisMonthAmount.expenseAmount | amount(thisMonthAmount.incompleteExpenseAmount, showAmountInHomePage) | currency(defaultCurrency) }}</span>
<span class="month-expense" v-else-if="!loading">{{ transactionOverview.thisMonth.expenseAmount }}</span>
<f7-link class="margin-left-half" @click="toggleShowAmountInHomePage()">
<f7-icon :f7="showAmountInHomePage ? 'eye_slash_fill' : 'eye_fill'" size="18px"></f7-icon>
</f7-link>
@@ -29,137 +29,157 @@
<small class="home-summary-misc" v-if="loading">Monthly income 0.00 USD</small>
<small class="home-summary-misc" v-else-if="!loading">
<span>{{ $t('Monthly income') }}</span>
<span>{{ thisMonthAmount.incomeAmount | amount(thisMonthAmount.incompleteIncomeAmount, showAmountInHomePage) | currency(defaultCurrency) }}</span>
<span>{{ transactionOverview.thisMonth.incomeAmount }}</span>
</small>
</p>
</f7-card-header>
</f7-card>
<f7-card :class="{ 'skeleton-text': loading }">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item :link="'/transaction/list?dateType=' + $constants.datetime.allDateRanges.Today.type" chevron-center>
<div slot="media">
<f7-icon f7="calendar_today"></f7-icon>
<f7-list strong inset dividers class="margin-top" :class="{ 'skeleton-text': loading }">
<f7-list-item :link="'/transaction/list?dateType=' + $constants.datetime.allDateRanges.Today.type" chevron-center>
<template #media>
<f7-icon f7="calendar_today"></f7-icon>
</template>
<template #title>
<div class="padding-top-half">
<span v-if="loading">Today</span>
<span v-else-if="!loading">{{ $t('Today') }}</span>
</div>
</template>
<template #footer>
<div class="overview-transaction-footer padding-bottom-half">
<span v-if="loading">MM/DD/YYYY</span>
<span v-else-if="!loading">{{ displayDateRange.today.displayTime }}</span>
</div>
</template>
<template #after>
<div>
<div class="text-color-red">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.today && transactionOverview.today.valid">{{ transactionOverview.today.incomeAmount }}</small>
</div>
<div slot="title" class="padding-top-half">
<span v-if="loading">Today</span>
<span v-else-if="!loading">{{ $t('Today') }}</span>
<div class="text-color-teal">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.today && transactionOverview.today.valid">{{ transactionOverview.today.expenseAmount }}</small>
</div>
<div slot="footer" class="overview-transaction-footer padding-bottom-half">
<span v-if="loading">MM/DD/YYYY</span>
<span v-else-if="!loading">{{ dateRange.today.startTime | moment($t('format.date.long')) }}</span>
</div>
<div slot="after">
<div class="text-color-red">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.today">{{ transactionOverview.today.incomeAmount | amount(transactionOverview.today.incompleteIncomeAmount, showAmountInHomePage) | currency(defaultCurrency) }}</small>
</div>
<div class="text-color-teal">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.today">{{ transactionOverview.today.expenseAmount | amount(transactionOverview.today.incompleteExpenseAmount, showAmountInHomePage) | currency(defaultCurrency) }}</small>
</div>
</div>
</f7-list-item>
</div>
</template>
</f7-list-item>
<f7-list-item :link="'/transaction/list?dateType=' + $constants.datetime.allDateRanges.ThisWeek.type" chevron-center>
<div slot="media">
<f7-icon f7="calendar"></f7-icon>
<f7-list-item :link="'/transaction/list?dateType=' + $constants.datetime.allDateRanges.ThisWeek.type" chevron-center>
<template #media>
<f7-icon f7="calendar"></f7-icon>
</template>
<template #title>
<div class="padding-top-half">
<span v-if="loading">This Week</span>
<span v-else-if="!loading">{{ $t('This Week') }}</span>
</div>
</template>
<template #footer>
<div class="overview-transaction-footer padding-bottom-half">
<span v-if="loading">MM/DD</span>
<span v-else-if="!loading">{{ displayDateRange.thisWeek.startTime }}</span>
<span>-</span>
<span v-if="loading">MM/DD</span>
<span v-else-if="!loading">{{ displayDateRange.thisWeek.endTime }}</span>
</div>
</template>
<template #after>
<div>
<div class="text-color-red">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.thisWeek && transactionOverview.thisWeek.valid">{{ transactionOverview.thisWeek.incomeAmount }}</small>
</div>
<div slot="title" class="padding-top-half">
<span v-if="loading">This Week</span>
<span v-else-if="!loading">{{ $t('This Week') }}</span>
<div class="text-color-teal">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.thisWeek && transactionOverview.thisWeek.valid">{{ transactionOverview.thisWeek.expenseAmount }}</small>
</div>
<div slot="footer" class="overview-transaction-footer padding-bottom-half">
<span v-if="loading">MM/DD</span>
<span v-else-if="!loading">{{ dateRange.thisWeek.startTime | moment($t('format.monthDay.long')) }}</span>
<span>-</span>
<span v-if="loading">MM/DD</span>
<span v-else-if="!loading">{{ dateRange.thisWeek.endTime | moment($t('format.monthDay.long')) }}</span>
</div>
<div slot="after">
<div class="text-color-red">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.thisWeek">{{ transactionOverview.thisWeek.incomeAmount | amount(transactionOverview.thisWeek.incompleteIncomeAmount, showAmountInHomePage) | currency(defaultCurrency) }}</small>
</div>
<div class="text-color-teal">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.thisWeek">{{ transactionOverview.thisWeek.expenseAmount | amount(transactionOverview.thisWeek.incompleteExpenseAmount, showAmountInHomePage) | currency(defaultCurrency) }}</small>
</div>
</div>
</f7-list-item>
</div>
</template>
</f7-list-item>
<f7-list-item :link="'/transaction/list?dateType=' + $constants.datetime.allDateRanges.ThisMonth.type" chevron-center>
<div slot="media">
<f7-icon f7="calendar"></f7-icon>
<f7-list-item :link="'/transaction/list?dateType=' + $constants.datetime.allDateRanges.ThisMonth.type" chevron-center>
<template #media>
<f7-icon f7="calendar"></f7-icon>
</template>
<template #title>
<div class="padding-top-half">
<span v-if="loading">This Month</span>
<span v-else-if="!loading">{{ $t('This Month') }}</span>
</div>
</template>
<template #footer>
<div class="overview-transaction-footer padding-bottom-half">
<span v-if="loading">MM/DD</span>
<span v-else-if="!loading">{{ displayDateRange.thisMonth.startTime }}</span>
<span>-</span>
<span v-if="loading">MM/DD</span>
<span v-else-if="!loading">{{ displayDateRange.thisMonth.endTime }}</span>
</div>
</template>
<template #after>
<div>
<div class="text-color-red">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.thisMonth && transactionOverview.thisMonth.valid">{{ transactionOverview.thisMonth.incomeAmount }}</small>
</div>
<div slot="title" class="padding-top-half">
<span v-if="loading">This Month</span>
<span v-else-if="!loading">{{ $t('This Month') }}</span>
<div class="text-color-teal">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.thisMonth && transactionOverview.thisMonth.valid">{{ transactionOverview.thisMonth.expenseAmount }}</small>
</div>
<div slot="footer" class="overview-transaction-footer padding-bottom-half">
<span v-if="loading">MM/DD</span>
<span v-else-if="!loading">{{ dateRange.thisMonth.startTime | moment($t('format.monthDay.long')) }}</span>
<span>-</span>
<span v-if="loading">MM/DD</span>
<span v-else-if="!loading">{{ dateRange.thisMonth.endTime | moment($t('format.monthDay.long')) }}</span>
</div>
<div slot="after">
<div class="text-color-red">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.thisMonth">{{ transactionOverview.thisMonth.incomeAmount | amount(transactionOverview.thisMonth.incompleteIncomeAmount, showAmountInHomePage) | currency(defaultCurrency) }}</small>
</div>
<div class="text-color-teal">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.thisMonth">{{ transactionOverview.thisMonth.expenseAmount | amount(transactionOverview.thisMonth.incompleteExpenseAmount, showAmountInHomePage) | currency(defaultCurrency) }}</small>
</div>
</div>
</f7-list-item>
</div>
</template>
</f7-list-item>
<f7-list-item :link="'/transaction/list?dateType=' + $constants.datetime.allDateRanges.ThisYear.type" chevron-center>
<div slot="media">
<f7-icon f7="square_stack_3d_up"></f7-icon>
<f7-list-item :link="'/transaction/list?dateType=' + $constants.datetime.allDateRanges.ThisYear.type" chevron-center>
<template #media>
<f7-icon f7="square_stack_3d_up"></f7-icon>
</template>
<template #title>
<div class="padding-top-half">
<span v-if="loading">This Year</span>
<span v-else-if="!loading">{{ $t('This Year') }}</span>
</div>
</template>
<template #footer>
<div class="overview-transaction-footer padding-bottom-half">
<span v-if="loading">YYYY</span>
<span v-else-if="!loading">{{ displayDateRange.thisYear.displayTime }}</span>
</div>
</template>
<template #after>
<div>
<div class="text-color-red">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.thisYear && transactionOverview.thisYear.valid">{{ transactionOverview.thisYear.incomeAmount }}</small>
</div>
<div slot="title" class="padding-top-half">
<span v-if="loading">This Year</span>
<span v-else-if="!loading">{{ $t('This Year') }}</span>
<div class="text-color-teal">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.thisYear && transactionOverview.thisYear.valid">{{ transactionOverview.thisYear.expenseAmount }}</small>
</div>
<div slot="footer" class="overview-transaction-footer padding-bottom-half">
<span v-if="loading">YYYY</span>
<span v-else-if="!loading">{{ dateRange.thisYear.startTime | moment($t('format.year.long')) }}</span>
</div>
<div slot="after">
<div class="text-color-red">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.thisYear">{{ transactionOverview.thisYear.incomeAmount | amount(transactionOverview.thisYear.incompleteIncomeAmount, showAmountInHomePage) | currency(defaultCurrency) }}</small>
</div>
<div class="text-color-teal">
<small v-if="loading">0.00 USD</small>
<small v-else-if="!loading && transactionOverview.thisYear">{{ transactionOverview.thisYear.expenseAmount | amount(transactionOverview.thisYear.incompleteExpenseAmount, showAmountInHomePage) | currency(defaultCurrency) }}</small>
</div>
</div>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
</div>
</template>
</f7-list-item>
</f7-list>
<f7-toolbar tabbar labels bottom>
<f7-link href="/transaction/list">
<f7-toolbar tabbar icons bottom class="main-tabbar">
<f7-link class="link" href="/transaction/list">
<f7-icon f7="square_list"></f7-icon>
<span class="tabbar-label">{{ $t('Details') }}</span>
</f7-link>
<f7-link href="/account/list">
<f7-link class="link" href="/account/list">
<f7-icon f7="creditcard"></f7-icon>
<span class="tabbar-label">{{ $t('Accounts') }}</span>
</f7-link>
<f7-link href="/transaction/add">
<f7-link class="link" href="/transaction/add">
<f7-icon f7="plus_square" class="ebk-tarbar-big-icon"></f7-icon>
</f7-link>
<f7-link href="/statistic/transaction">
<f7-link class="link" href="/statistic/transaction">
<f7-icon f7="chart_pie"></f7-icon>
<span class="tabbar-label">{{ $t('Statistics') }}</span>
</f7-link>
<f7-link href="/settings">
<f7-link class="link" href="/settings">
<f7-icon f7="gear_alt"></f7-icon>
<span class="tabbar-label">{{ $t('Settings') }}</span>
</f7-link>
@@ -182,9 +202,6 @@ export default {
};
},
computed: {
transactionOverview() {
return this.$store.state.transactionOverview;
},
defaultCurrency() {
return this.$store.getters.currentUserDefaultCurrency;
},
@@ -213,17 +230,60 @@ export default {
}
};
},
thisMonthAmount() {
displayDateRange() {
const self = this;
return {
today: {
displayTime: self.$utilities.formatUnixTime(self.dateRange.today.startTime, self.$t('format.date.long')),
},
thisWeek: {
startTime: self.$utilities.formatUnixTime(self.dateRange.thisWeek.startTime, self.$t('format.monthDay.long')),
endTime: self.$utilities.formatUnixTime(self.dateRange.thisWeek.endTime, self.$t('format.monthDay.long'))
},
thisMonth: {
displayTime: self.$utilities.formatUnixTime(self.dateRange.thisMonth.startTime, 'MMMM'),
startTime: self.$utilities.formatUnixTime(self.dateRange.thisMonth.startTime, self.$t('format.monthDay.long')),
endTime: self.$utilities.formatUnixTime(self.dateRange.thisMonth.endTime, self.$t('format.monthDay.long'))
},
thisYear: {
displayTime: self.$utilities.formatUnixTime(self.dateRange.thisYear.startTime, self.$t('format.year.long'))
}
};
},
transactionOverview() {
// make sure this computed property refers these property, so these property can trigger this computed property to update
const isEnableThousandsSeparator = this.isEnableThousandsSeparator; // eslint-disable-line
const currencyDisplayMode = this.currencyDisplayMode; // eslint-disable-line
if (!this.$store.state.transactionOverview || !this.$store.state.transactionOverview.thisMonth) {
return {
incomeAmount: 0,
expenseAmount: 0,
incompleteIncomeAmount: false,
incompleteExpenseAmount: false
thisMonth: {
valid: false,
incomeAmount: this.getDisplayAmount(0, false),
expenseAmount: this.getDisplayAmount(0, false)
}
};
}
return this.$store.state.transactionOverview.thisMonth;
const originalOverview = this.$store.state.transactionOverview;
const displayOverview = {};
[ 'today', 'thisWeek', 'thisMonth', 'thisYear' ].forEach(key => {
if (!originalOverview[key]) {
return;
}
const item = originalOverview[key];
displayOverview[key] = {
valid: true,
incomeAmount: this.getDisplayAmount(item.incomeAmount, item.incompleteIncomeAmount),
expenseAmount: this.getDisplayAmount(item.expenseAmount, item.incompleteExpenseAmount)
};
});
return displayOverview;
}
},
created() {
@@ -292,15 +352,13 @@ export default {
toggleShowAmountInHomePage() {
this.showAmountInHomePage = !this.showAmountInHomePage;
this.$settings.setShowAmountInHomePage(this.showAmountInHomePage);
}
},
filters: {
amount(amount, incomplete, showAmount) {
if (!showAmount) {
return '***';
},
getDisplayAmount(amount, incomplete) {
if (!this.showAmountInHomePage) {
return this.$locale.getDisplayCurrency('***', this.defaultCurrency);
}
return amount + (incomplete ? '+' : '');
return this.$locale.getDisplayCurrency(amount, this.defaultCurrency) + (incomplete ? '+' : '');
}
}
}
@@ -331,11 +389,11 @@ export default {
margin-right: 0;
}
.theme-dark .home-summary-card {
.dark .home-summary-card {
background-color: var(--f7-theme-color);
}
.theme-dark .home-summary-card a {
.dark .home-summary-card a {
color: var(--f7-text-color);
opacity: 0.6;
}
@@ -349,7 +407,11 @@ export default {
margin-right: 4px;
}
.tabbar-labels i.ebk-tarbar-big-icon {
.tabbar.main-tabbar .link i + span.tabbar-label {
margin-top: 2px;
}
.tabbar.main-tabbar .link i.ebk-tarbar-big-icon {
font-size: 42px;
width: 42px;
height: 42px;
+31 -27
View File
@@ -1,19 +1,19 @@
<template>
<f7-page no-toolbar no-navbar no-swipeback login-screen>
<f7-login-screen-title>
<img class="login-page-logo" src="img/ezbookkeeping-192.png" />
<img alt="logo" class="login-page-logo" src="/img/ezbookkeeping-192.png" />
<f7-block class="margin-vertical-half">{{ $t('global.app.title') }}</f7-block>
</f7-login-screen-title>
<f7-list form>
<f7-list form dividers>
<f7-list-input
type="text"
autocomplete="username"
clear-button
:label="$t('Username')"
:placeholder="$t('Your username or email')"
:value="username"
@input="username = $event.target.value; tempToken = ''"
v-model:value="username"
@input="tempToken = ''"
></f7-list-input>
<f7-list-input
type="password"
@@ -21,9 +21,9 @@
clear-button
:label="$t('Password')"
:placeholder="$t('Your password')"
:value="password"
@input="password = $event.target.value; tempToken = ''"
@keyup.enter.native="loginByPressEnter"
v-model:value="password"
@input="tempToken = ''"
@keyup.enter="loginByPressEnter"
></f7-list-input>
</f7-list>
@@ -50,15 +50,17 @@
</f7-list>
<f7-popover class="lang-popover-menu">
<f7-list>
<f7-list dividers>
<f7-list-item
link="#" no-chevron popover-close
v-for="(lang, locale) in allLanguages"
:key="locale"
:title="lang.displayName"
:key="locale"
v-for="(lang, locale) in allLanguages"
@click="changeLanguage(locale)"
>
<f7-icon slot="after" class="list-item-checked-icon" f7="checkmark_alt" v-if="$i18n.locale === locale"></f7-icon>
<template #after>
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="$i18n.locale === locale"></f7-icon>
</template>
</f7-list-item>
</f7-list>
</f7-popover>
@@ -72,26 +74,28 @@
<div style="font-size: 18px"><b>{{ $t('Two-Factor Authentication') }}</b></div>
</div>
<div class="padding-horizontal padding-bottom">
<f7-list no-hairlines class="no-margin-top margin-bottom">
<f7-list no-hairlines strong class="no-margin">
<f7-list-input
type="number"
autocomplete="one-time-code"
outline
floating-label
clear-button
v-if="twoFAVerifyType === 'passcode'"
:label="$t('Passcode')"
:placeholder="$t('Passcode')"
:value="passcode"
@input="passcode = $event.target.value"
@keyup.enter.native="verify"
v-model:value="passcode"
@keyup.enter="verify"
></f7-list-input>
<f7-list-input
outline
floating-label
clear-button
v-if="twoFAVerifyType === 'backupcode'"
:label="$t('Backup Code')"
:placeholder="$t('Backup Code')"
:value="backupCode"
@input="backupCode = $event.target.value"
@keyup.enter.native="verify"
v-model:value="backupCode"
@keyup.enter="verify"
></f7-list-input>
</f7-list>
<f7-button large fill :class="{ 'disabled': twoFAInputIsEmpty || verifying }" :text="$t('Verify')" @click="verify"></f7-button>
@@ -106,6 +110,9 @@
<script>
export default {
props: [
'f7router'
],
data() {
return {
username: '',
@@ -124,7 +131,7 @@ export default {
return 'v' + this.$version;
},
allLanguages() {
return this.$locale.getAllLanguages();
return this.$locale.getAllLanguageInfos();
},
isUserRegistrationEnabled() {
return this.$settings.isUserRegistrationEnabled();
@@ -148,10 +155,10 @@ export default {
},
currentLanguageName() {
const currentLocale = this.$i18n.locale;
let lang = this.$locale.getLanguage(currentLocale);
let lang = this.$locale.getLanguageInfo(currentLocale);
if (!lang) {
lang = this.$locale.getLanguage(this.$locale.getDefaultLanguage());
lang = this.$locale.getLanguageInfo(this.$locale.getDefaultLanguage());
}
return lang.displayName;
@@ -160,7 +167,7 @@ export default {
methods: {
login() {
const self = this;
const router = self.$f7router;
const router = self.f7router;
if (!this.username) {
self.$alert('Username cannot be empty');
@@ -208,10 +215,7 @@ export default {
});
},
loginByPressEnter() {
const app = this.$f7;
const $$ = app.$;
if ($$('.modal-in').length) {
if (this.$ui.isModalShowing()) {
return;
}
@@ -219,7 +223,7 @@ export default {
},
verify() {
const self = this;
const router = self.$f7router;
const router = self.f7router;
if (self.twoFAInputIsEmpty || self.verifying) {
return;
+80 -85
View File
@@ -3,108 +3,103 @@
<f7-navbar :title="$t('Settings')" :back-link="$t('Back')"></f7-navbar>
<f7-block-title class="margin-top">{{ currentNickName }}</f7-block-title>
<f7-card>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item :title="$t('User Profile')" link="/user/profile"></f7-list-item>
<f7-list-item :title="$t('Transaction Categories')" link="/category/all"></f7-list-item>
<f7-list-item :title="$t('Transaction Tags')" link="/tag/list"></f7-list-item>
<f7-list-item :title="$t('Data Management')" link="/user/data/management"></f7-list-item>
<f7-list-item :title="$t('Two-Factor Authentication')" link="/user/2fa"></f7-list-item>
<f7-list-item :title="$t('Device & Sessions')" link="/user/sessions"></f7-list-item>
<f7-list-button :class="{ 'disabled': logouting }" @click="logout">{{ $t('Log Out') }}</f7-list-button>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers>
<f7-list-item :title="$t('User Profile')" link="/user/profile"></f7-list-item>
<f7-list-item :title="$t('Transaction Categories')" link="/category/all"></f7-list-item>
<f7-list-item :title="$t('Transaction Tags')" link="/tag/list"></f7-list-item>
<f7-list-item :title="$t('Data Management')" link="/user/data/management"></f7-list-item>
<f7-list-item :title="$t('Two-Factor Authentication')" link="/user/2fa"></f7-list-item>
<f7-list-item :title="$t('Device & Sessions')" link="/user/sessions"></f7-list-item>
<f7-list-button :class="{ 'disabled': logouting }" @click="logout">{{ $t('Log Out') }}</f7-list-button>
</f7-list>
<f7-block-title>{{ $t('Application') }}</f7-block-title>
<f7-card>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item
:key="currentLocale + '_lang'"
:title="$t('Language')"
smart-select :smart-select-params="{ openIn: 'popup', searchbar: true, searchbarPlaceholder: $t('Language'), searchbarDisableText: $t('Cancel'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }">
<select v-model="currentLocale">
<option v-for="(lang, locale) in allLanguages"
:key="locale"
:value="locale">{{ lang.displayName }}</option>
</select>
</f7-list-item>
<f7-list strong inset dividers>
<f7-list-item
:key="currentLocale + '_lang'"
:title="$t('Language')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Language'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), popupCloseLinkText: $t('Done') }">
<select v-model="currentLocale">
<option :value="locale"
:key="locale"
v-for="(lang, locale) in allLanguages">{{ lang.displayName }}</option>
</select>
</f7-list-item>
<f7-list-item
:key="currentLocale + '_timezone'"
:title="$t('Timezone')"
smart-select :smart-select-params="{ openIn: 'popup', searchbar: true, searchbarPlaceholder: $t('Timezone'), searchbarDisableText: $t('Cancel'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }">
<select v-model="currentTimezone">
<option v-for="timezone in allTimezones"
:key="timezone.name"
:value="timezone.name">{{ `(UTC${timezone.utcOffset}) ${timezone.displayName}` }}</option>
</select>
</f7-list-item>
<f7-list-item
:key="currentLocale + '_timezone'"
:title="$t('Timezone')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Timezone'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), popupCloseLinkText: $t('Done') }">
<select v-model="currentTimezone">
<option :value="timezone.name"
:key="timezone.name"
v-for="timezone in allTimezones">{{ `(UTC${timezone.utcOffset}) ${timezone.displayName}` }}</option>
</select>
</f7-list-item>
<f7-list-item :title="$t('Application Lock')" :after="isEnableApplicationLock ? $t('Enabled') : $t('Disabled')" link="/app_lock"></f7-list-item>
<f7-list-item :title="$t('Application Lock')" :after="isEnableApplicationLock ? $t('Enabled') : $t('Disabled')" link="/app_lock"></f7-list-item>
<f7-list-item :title="$t('Exchange Rates Data')" :after="exchangeRatesLastUpdateDate" link="/exchange_rates"></f7-list-item>
<f7-list-item :title="$t('Exchange Rates Data')" :after="exchangeRatesLastUpdateDate" link="/exchange_rates"></f7-list-item>
<f7-list-item>
<span>{{ $t('Auto Update Exchange Rates Data') }}</span>
<f7-toggle :checked="isAutoUpdateExchangeRatesData" @toggle:change="isAutoUpdateExchangeRatesData = $event"></f7-toggle>
</f7-list-item>
<f7-list-item>
<span>{{ $t('Auto Update Exchange Rates Data') }}</span>
<f7-toggle :checked="isAutoUpdateExchangeRatesData" @toggle:change="isAutoUpdateExchangeRatesData = $event"></f7-toggle>
</f7-list-item>
<f7-list-item>
<span>{{ $t('Enable Thousands Separator') }}</span>
<f7-toggle :checked="isEnableThousandsSeparator" @toggle:change="isEnableThousandsSeparator = $event"></f7-toggle>
</f7-list-item>
<f7-list-item>
<span>{{ $t('Enable Thousands Separator') }}</span>
<f7-toggle :checked="isEnableThousandsSeparator" @toggle:change="isEnableThousandsSeparator = $event"></f7-toggle>
</f7-list-item>
<f7-list-item
:key="currentLocale + '_currency_display'"
:title="$t('Currency Display Mode')"
smart-select :smart-select-params="{ openIn: 'popup', searchbar: true, searchbarPlaceholder: $t('Currency Display Mode'), searchbarDisableText: $t('Cancel'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }">
<select v-model="currencyDisplayMode">
<option :value="$constants.currency.allCurrencyDisplayModes.None">{{ $t('None') }}</option>
<option :value="$constants.currency.allCurrencyDisplayModes.Symbol">{{ $t('Currency Symbol') }}</option>
<option :value="$constants.currency.allCurrencyDisplayModes.Code">{{ $t('Currency Code') }}</option>
<option :value="$constants.currency.allCurrencyDisplayModes.Name">{{ $t('Currency Name') }}</option>
</select>
</f7-list-item>
<f7-list-item
:key="currentLocale + '_currency_display'"
:title="$t('Currency Display Mode')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Currency Display Mode'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), popupCloseLinkText: $t('Done') }">
<select v-model="currencyDisplayMode">
<option :value="$constants.currency.allCurrencyDisplayModes.None">{{ $t('None') }}</option>
<option :value="$constants.currency.allCurrencyDisplayModes.Symbol">{{ $t('Currency Symbol') }}</option>
<option :value="$constants.currency.allCurrencyDisplayModes.Code">{{ $t('Currency Code') }}</option>
<option :value="$constants.currency.allCurrencyDisplayModes.Name">{{ $t('Currency Name') }}</option>
</select>
</f7-list-item>
<f7-list-item>
<span>{{ $t('Show Amount In Home Page') }}</span>
<f7-toggle :checked="showAmountInHomePage" @toggle:change="showAmountInHomePage = $event"></f7-toggle>
</f7-list-item>
<f7-list-item>
<span>{{ $t('Show Amount In Home Page') }}</span>
<f7-toggle :checked="showAmountInHomePage" @toggle:change="showAmountInHomePage = $event"></f7-toggle>
</f7-list-item>
<f7-list-item>
<span>{{ $t('Show Account Balance') }}</span>
<f7-toggle :checked="showAccountBalance" @toggle:change="showAccountBalance = $event"></f7-toggle>
</f7-list-item>
<f7-list-item>
<span>{{ $t('Show Account Balance') }}</span>
<f7-toggle :checked="showAccountBalance" @toggle:change="showAccountBalance = $event"></f7-toggle>
</f7-list-item>
<f7-list-item>
<span>{{ $t('Show Total Amount In Transaction List Page') }}</span>
<f7-toggle :checked="showTotalAmountInTransactionListPage" @toggle:change="showTotalAmountInTransactionListPage = $event"></f7-toggle>
</f7-list-item>
<f7-list-item>
<span>{{ $t('Show Total Amount In Transaction List Page') }}</span>
<f7-toggle :checked="showTotalAmountInTransactionListPage" @toggle:change="showTotalAmountInTransactionListPage = $event"></f7-toggle>
</f7-list-item>
<f7-list-item :title="$t('Statistics Settings')" link="/statistic/settings"></f7-list-item>
<f7-list-item :title="$t('Statistics Settings')" link="/statistic/settings"></f7-list-item>
<f7-list-item>
<span>{{ $t('Enable Animate') }}</span>
<f7-toggle :checked="isEnableAnimate" @toggle:change="isEnableAnimate = $event"></f7-toggle>
</f7-list-item>
<f7-list-item>
<span>{{ $t('Enable Animate') }}</span>
<f7-toggle :checked="isEnableAnimate" @toggle:change="isEnableAnimate = $event"></f7-toggle>
</f7-list-item>
<f7-list-item>
<span>{{ $t('Enable Auto Dark Mode') }}</span>
<f7-toggle :checked="isEnableAutoDarkMode" @toggle:change="isEnableAutoDarkMode = $event"></f7-toggle>
</f7-list-item>
<f7-list-item>
<span>{{ $t('Enable Auto Dark Mode') }}</span>
<f7-toggle :checked="isEnableAutoDarkMode" @toggle:change="isEnableAutoDarkMode = $event"></f7-toggle>
</f7-list-item>
<f7-list-item :title="$t('About')" link="/about" :after="version"></f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list-item :title="$t('About')" link="/about" :after="version"></f7-list-item>
</f7-list>
</f7-page>
</template>
<script>
export default {
props: [
'f7router'
],
data() {
const self = this;
@@ -118,7 +113,7 @@ export default {
return 'v' + this.$version;
},
allLanguages() {
return this.$locale.getAllLanguages();
return this.$locale.getAllLanguageInfos();
},
allTimezones() {
return this.$locale.getAllTimezones(true);
@@ -223,7 +218,7 @@ export default {
},
logout() {
const self = this;
const router = self.$f7router;
const router = self.f7router;
self.$confirm('Are you sure you want to log out?', () => {
self.logouting = true;
@@ -234,7 +229,7 @@ export default {
self.$hideLoading();
self.$settings.clearSettings();
self.$locale.init();
self.$locale.initLocale();
router.navigate('/');
}).catch(error => {
+151 -156
View File
@@ -8,126 +8,112 @@
</f7-nav-right>
</f7-navbar>
<f7-card>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list form>
<f7-list-input
type="text"
autocomplete="username"
clear-button
:label="$t('Username')"
:placeholder="$t('Your username')"
:value="user.username"
@input="user.username = $event.target.value"
></f7-list-input>
<f7-list form strong inset dividers class="margin-top">
<f7-list-input
type="text"
autocomplete="username"
clear-button
:label="$t('Username')"
:placeholder="$t('Your username')"
v-model:value="user.username"
></f7-list-input>
<f7-list-input
type="password"
autocomplete="new-password"
clear-button
:label="$t('Password')"
:placeholder="$t('Your password, at least 6 characters')"
:value="user.password"
@input="user.password = $event.target.value"
></f7-list-input>
<f7-list-input
type="password"
autocomplete="new-password"
clear-button
:label="$t('Password')"
:placeholder="$t('Your password, at least 6 characters')"
v-model:value="user.password"
></f7-list-input>
<f7-list-input
type="password"
autocomplete="new-password"
clear-button
:label="$t('Confirmation Password')"
:placeholder="$t('Re-enter the password')"
:value="user.confirmPassword"
@input="user.confirmPassword = $event.target.value"
></f7-list-input>
<f7-list-input
type="password"
autocomplete="new-password"
clear-button
:label="$t('Confirmation Password')"
:placeholder="$t('Re-enter the password')"
v-model:value="user.confirmPassword"
></f7-list-input>
<f7-list-input
type="email"
autocomplete="email"
clear-button
:label="$t('E-mail')"
:placeholder="$t('Your email address')"
:value="user.email"
@input="user.email = $event.target.value"
></f7-list-input>
<f7-list-input
type="email"
autocomplete="email"
clear-button
:label="$t('E-mail')"
:placeholder="$t('Your email address')"
v-model:value="user.email"
></f7-list-input>
<f7-list-input
type="text"
autocomplete="nickname"
clear-button
:label="$t('Nickname')"
:placeholder="$t('Your nickname')"
:value="user.nickname"
@input="user.nickname = $event.target.value"
></f7-list-input>
<f7-list-input
type="text"
autocomplete="nickname"
clear-button
:label="$t('Nickname')"
:placeholder="$t('Your nickname')"
v-model:value="user.nickname"
></f7-list-input>
<f7-list-item class="ebk-list-item-error-info" v-if="inputIsInvalid" :footer="$t(inputInvalidProblemMessage)"></f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list-item class="ebk-list-item-error-info" v-if="inputIsInvalid" :footer="$t(inputInvalidProblemMessage)"></f7-list-item>
</f7-list>
<f7-card>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list form>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:key="currentLocale + '_lang'"
:header="$t('Language')"
:title="currentLocale | languageName"
smart-select :smart-select-params="{ openIn: 'popup', pageTitle: $t('Language'), searchbar: true, searchbarPlaceholder: $t('Language'), searchbarDisableText: $t('Cancel'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }"
>
<select v-model="currentLocale">
<option v-for="(lang, locale) in allLanguages"
:key="locale"
:value="locale">{{ lang.displayName }}</option>
</select>
</f7-list-item>
<f7-list strong inset dividers>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:key="currentLocale + '_lang'"
:header="$t('Language')"
:title="currentLanguageName"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Language'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Language'), popupCloseLinkText: $t('Done') }"
>
<select v-model="currentLocale">
<option :value="locale"
:key="locale"
v-for="(lang, locale) in allLanguages">{{ lang.displayName }}</option>
</select>
</f7-list-item>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:key="currentLocale + '_currency'"
:header="$t('Default Currency')"
smart-select :smart-select-params="{ openIn: 'popup', pageTitle: $t('Default Currency'), searchbar: true, searchbarPlaceholder: $t('Currency Name'), searchbarDisableText: $t('Cancel'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }"
>
<f7-block slot="title" class="no-padding no-margin">
<span>{{ $t(`currency.${user.defaultCurrency}`) }}&nbsp;</span>
<small class="smaller">{{ user.defaultCurrency }}</small>
</f7-block>
<select autocomplete="transaction-currency" v-model="user.defaultCurrency">
<option v-for="currency in allCurrencies"
:key="currency.code"
:value="currency.code">{{ currency.displayName }}</option>
</select>
</f7-list-item>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:key="currentLocale + '_currency'"
:header="$t('Default Currency')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Currency Name'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Default Currency'), popupCloseLinkText: $t('Done') }"
>
<template #title>
<f7-block class="no-padding no-margin">
<span>{{ $t(`currency.${user.defaultCurrency}`) }}&nbsp;</span>
<small class="smaller">{{ user.defaultCurrency }}</small>
</f7-block>
</template>
<select autocomplete="transaction-currency" v-model="user.defaultCurrency">
<option :value="currency.code"
:key="currency.code"
v-for="currency in allCurrencies">{{ currency.displayName }}</option>
</select>
</f7-list-item>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:key="currentLocale + '_firstDayOfWeek'"
:header="$t('First Day of Week')"
:title="user.firstDayOfWeek | optionName(allWeekDays, 'type', 'name') | format('datetime.#{value}.long') | localized"
smart-select :smart-select-params="{ openIn: 'popup', pageTitle: $t('First Day of Week'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }"
>
<select v-model="user.firstDayOfWeek">
<option v-for="weekDay in allWeekDays"
:key="weekDay.type"
:value="weekDay.type">{{ $t(`datetime.${weekDay.name}.long`) }}</option>
</select>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:key="currentLocale + '_firstDayOfWeek'"
:header="$t('First Day of Week')"
:title="getDayOfWeekName(user.firstDayOfWeek)"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Date'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('First Day of Week'), popupCloseLinkText: $t('Done') }"
>
<select v-model="user.firstDayOfWeek">
<option :value="weekDay.type"
:key="weekDay.type"
v-for="weekDay in allWeekDays">{{ $t(`datetime.${weekDay.name}.long`) }}</option>
</select>
</f7-list-item>
</f7-list>
<f7-card>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list form>
<f7-list-item :title="$t('Use preset transaction categories')" link="#" @click="showPresetCategories = true">
<f7-toggle :checked="usePresetCategories" @toggle:change="usePresetCategories = $event"></f7-toggle>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers>
<f7-list-item :title="$t('Use preset transaction categories')" link="#" @click="showPresetCategories = true">
<f7-toggle :checked="usePresetCategories" @toggle:change="usePresetCategories = $event"></f7-toggle>
</f7-list-item>
</f7-list>
<f7-popup :opened="showPresetCategories" @popup:closed="showPresetCategories = false">
<f7-popup push :close-on-escape="false" :opened="showPresetCategories"
@popup:closed="showPresetCategories = false">
<f7-page>
<f7-navbar>
<f7-nav-left>
@@ -140,39 +126,32 @@
<f7-link close @click="usePresetCategories = false; showPresetCategories = false" v-if="usePresetCategories">{{ $t('Disable') }}</f7-link>
</f7-nav-right>
</f7-navbar>
<f7-card v-for="(categories, categoryType) in presetCategories" :key="categoryType">
<f7-card-header>
<small class="card-header-content">
<span>{{ categoryType | categoryTypeName($constants.category.allCategoryTypes) | localized }}</span>
</small>
</f7-card-header>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list v-if="showPresetCategories">
<f7-list-item v-for="(category, idx) in categories"
:key="idx"
:accordion-item="!!category.subCategories.length"
:title="$t('category.' + category.name, currentLocale)">
<f7-icon slot="media"
:icon="category.categoryIconId | categoryIcon"
:style="category.color | categoryIconStyle('var(--default-icon-color)')">
</f7-icon>
<f7-block class="no-padding no-margin"
:key="categoryType" v-for="(categories, categoryType) in presetCategories">
<f7-block-title class="margin-top margin-horizontal">{{ getCategoryTypeName(categoryType) }}</f7-block-title>
<f7-list strong inset dividers v-if="showPresetCategories">
<f7-list-item :title="$t('category.' + category.name, currentLocale)"
:accordion-item="!!category.subCategories.length"
:key="idx"
v-for="(category, idx) in categories">
<template #media>
<ItemIcon icon-type="category" :icon-id="category.categoryIconId" :color="category.color"></ItemIcon>
</template>
<f7-accordion-content v-if="category.subCategories.length" class="padding-left">
<f7-list>
<f7-list-item v-for="(subCategory, subIdx) in category.subCategories"
:key="subIdx"
:title="$t('category.' + subCategory.name, currentLocale)">
<f7-icon slot="media"
:icon="subCategory.categoryIconId | categoryIcon"
:style="subCategory.color | categoryIconStyle('var(--default-icon-color)')">
</f7-icon>
</f7-list-item>
</f7-list>
</f7-accordion-content>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-accordion-content v-if="category.subCategories.length" class="padding-left">
<f7-list>
<f7-list-item :title="$t('category.' + subCategory.name, currentLocale)"
:key="subIdx"
v-for="(subCategory, subIdx) in category.subCategories">
<template #media>
<ItemIcon icon-type="category" :icon-id="subCategory.categoryIconId" :color="subCategory.color"></ItemIcon>
</template>
</f7-list-item>
</f7-list>
</f7-accordion-content>
</f7-list-item>
</f7-list>
</f7-block>
</f7-page>
<f7-actions close-by-outside-click close-on-escape :opened="showPresetCategoriesMoreActionSheet" @actions:closed="showPresetCategoriesMoreActionSheet = false">
@@ -187,7 +166,7 @@
<list-item-selection-sheet value-type="index"
title-field="displayName"
:items="allLanguages"
:show.sync="showPresetCategoriesChangeLocaleSheet"
v-model:show="showPresetCategoriesChangeLocaleSheet"
v-model="currentLocale">
</list-item-selection-sheet>
</f7-popup>
@@ -196,6 +175,9 @@
<script>
export default {
props: [
'f7router'
],
data() {
const self = this;
@@ -223,7 +205,7 @@ export default {
},
computed: {
allLanguages() {
return this.$locale.getAllLanguages();
return this.$locale.getAllLanguageInfos();
},
allCurrencies() {
return this.$locale.getAllCurrencies();
@@ -250,6 +232,15 @@ export default {
}
}
},
currentLanguageName() {
const languageInfo = this.$locale.getLanguageInfo(this.currentLocale);
if (!languageInfo) {
return '';
}
return languageInfo.displayName;
},
inputIsEmpty() {
return !!this.inputEmptyProblemMessage;
},
@@ -284,7 +275,7 @@ export default {
methods: {
submit() {
const self = this;
const router = self.$f7router;
const router = self.f7router;
let problemMessage = self.inputEmptyProblemMessage || self.inputInvalidProblemMessage;
@@ -384,21 +375,25 @@ export default {
self.$toast(error.message || error);
}
});
}
},
filters: {
categoryTypeName(categoryType, allCategoryTypes) {
},
getDayOfWeekName(dayOfWeek) {
const weekName = this.$utilities.getNameByKeyValue(this.$constants.datetime.allWeekDays, dayOfWeek, 'type', 'name');
const i18nWeekNameKey = `datetime.${weekName}.long`;
return this.$t(i18nWeekNameKey);
},
getCategoryTypeName(categoryType) {
switch (categoryType) {
case allCategoryTypes.Income.toString():
return 'Income Categories';
case allCategoryTypes.Expense.toString():
return 'Expense Categories';
case allCategoryTypes.Transfer.toString():
return 'Transfer Categories';
case this.$constants.category.allCategoryTypes.Income.toString():
return this.$t('Income Categories');
case this.$constants.category.allCategoryTypes.Expense.toString():
return this.$t('Expense Categories');
case this.$constants.category.allCategoryTypes.Transfer.toString():
return this.$t('Transfer Categories');
default:
return 'Transaction Categories';
return this.$t('Transaction Categories');
}
}
}
};
</script>
+32 -28
View File
@@ -1,21 +1,23 @@
<template>
<f7-page no-toolbar no-navbar no-swipeback login-screen>
<f7-login-screen-title>
<img class="login-page-logo" src="img/ezbookkeeping-192.png" />
<img alt="logo" class="login-page-logo" src="/img/ezbookkeeping-192.png" />
<f7-block class="margin-vertical-half">{{ $t('global.app.title') }}</f7-block>
</f7-login-screen-title>
<f7-list form>
<f7-list-item-row class="justify-content-center padding-vertical-half">
{{ $t('Unlock Application') }}
</f7-list-item-row>
<f7-list-item class="list-item-pincode-input">
<pincode-input secure :length="6" v-model="pinCode" @keyup.native="unlockByPin" />
<f7-list-item class="no-padding no-margin">
<template #inner>
<div class="display-flex justify-content-center full-line">{{ $t('Unlock Application') }}</div>
</template>
</f7-list-item>
<f7-list-item class="list-item-pincode-input padding-horizontal margin-horizontal">
<pin-code-input :secure="true" :length="6" v-model="pinCode" @pincode:confirm="unlockByPin" />
</f7-list-item>
</f7-list>
<f7-list>
<f7-list-button :class="{ 'disabled': !pinCodeValid }" :text="$t('Unlock By PIN Code')" @click="unlockByPin"></f7-list-button>
<f7-list-button :class="{ 'disabled': !isPinCodeValid(pinCode) }" :text="$t('Unlock By PIN Code')" @click="unlockByPin"></f7-list-button>
<f7-list-button v-if="isWebAuthnAvailable" :text="$t('Unlock By Face ID/Touch ID')" @click="unlockByWebAuthn"></f7-list-button>
<f7-block-footer>
<f7-link :text="$t('Re-login')" @click="relogin"></f7-link>
@@ -37,15 +39,17 @@
</f7-list>
<f7-popover class="lang-popover-menu">
<f7-list>
<f7-list dividers>
<f7-list-item
link="#" no-chevron popover-close
v-for="(lang, locale) in allLanguages"
:key="locale"
:title="lang.displayName"
:key="locale"
v-for="(lang, locale) in allLanguages"
@click="changeLanguage(locale)"
>
<f7-icon slot="after" class="list-item-checked-icon" f7="checkmark_alt" v-if="$i18n.locale === locale"></f7-icon>
<template #after>
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="$i18n.locale === locale"></f7-icon>
</template>
</f7-list-item>
</f7-list>
</f7-popover>
@@ -54,6 +58,9 @@
<script>
export default {
props: [
'f7router'
],
data() {
return {
pinCode: ''
@@ -64,22 +71,19 @@ export default {
return 'v' + this.$version;
},
allLanguages() {
return this.$locale.getAllLanguages();
return this.$locale.getAllLanguageInfos();
},
isWebAuthnAvailable() {
return this.$settings.isEnableApplicationLockWebAuthn()
&& this.$user.getWebAuthnCredentialId()
&& this.$webauthn.isSupported();
},
pinCodeValid() {
return this.pinCode && this.pinCode.length === 6;
},
currentLanguageName() {
const currentLocale = this.$i18n.locale;
let lang = this.$locale.getLanguage(currentLocale);
let lang = this.$locale.getLanguageInfo(currentLocale);
if (!lang) {
lang = this.$locale.getLanguage(this.$locale.getDefaultLanguage());
lang = this.$locale.getLanguageInfo(this.$locale.getDefaultLanguage());
}
return lang.displayName;
@@ -88,7 +92,7 @@ export default {
methods: {
unlockByWebAuthn() {
const self = this;
const router = self.$f7router;
const router = self.f7router;
if (!self.$settings.isEnableApplicationLockWebAuthn() || !self.$user.getWebAuthnCredentialId()) {
self.$toast('Face ID/Touch ID authentication is not enabled');
@@ -131,19 +135,16 @@ export default {
}
});
},
unlockByPin() {
const app = this.$f7;
const $$ = app.$;
if (!this.pinCodeValid) {
unlockByPin(pinCode) {
if (!this.isPinCodeValid(pinCode)) {
return;
}
if ($$('.modal-in').length) {
if (this.$ui.isModalShowing()) {
return;
}
const router = this.$f7router;
const router = this.f7router;
const user = this.$store.state.currentUserInfo;
if (!user || !user.username) {
@@ -152,7 +153,7 @@ export default {
}
try {
this.$user.unlockTokenByPinCode(user.username, this.pinCode);
this.$user.unlockTokenByPinCode(user.username, pinCode);
this.$store.dispatch('refreshTokenAndRevokeOldToken');
if (this.$settings.isAutoUpdateExchangeRatesData()) {
@@ -167,7 +168,7 @@ export default {
},
relogin() {
const self = this;
const router = self.$f7router;
const router = self.f7router;
self.$confirm('Are you sure you want to re-login?', () => {
self.$user.clearTokenAndUserInfo(true);
@@ -175,13 +176,16 @@ export default {
self.$store.dispatch('clearUserInfoState');
self.$store.dispatch('resetState');
self.$settings.clearSettings();
self.$locale.init();
self.$locale.initLocale();
router.navigate('/login', {
clearPreviousHistory: true
});
});
},
isPinCodeValid(pinCode) {
return pinCode && pinCode.length === 6;
},
changeLanguage(locale) {
this.$locale.setLanguage(locale);
}
+380 -304
View File
@@ -9,320 +9,393 @@
</f7-nav-right>
</f7-navbar>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item class="list-item-with-header-and-title" link="#" header="Account Category" title="Category"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title" link="#" header="Account Type" title="Account Type"></f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-vertical skeleton-text" v-if="loading">
<f7-list-item class="list-item-with-header-and-title" header="Account Category" title="Category"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title" header="Account Type" title="Account Type"></f7-list-item>
</f7-list>
<f7-card v-else-if="!loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list form>
<f7-list-item
class="list-item-with-header-and-title"
link="#"
:header="$t('Account Category')"
:title="account.category | optionName(allAccountCategories, 'id', 'name') | localized"
@click="showAccountCategorySheet = true"
>
<list-item-selection-sheet value-type="item"
key-field="id" value-field="id" title-field="name"
icon-field="defaultAccountIconId" icon-type="account"
:title-i18n="true"
:items="allAccountCategories"
:show.sync="showAccountCategorySheet"
v-model="account.category">
</list-item-selection-sheet>
</f7-list-item>
<f7-list form strong inset dividers class="margin-vertical" v-else-if="!loading">
<f7-list-item
link="#" no-chevron
class="list-item-with-header-and-title"
:header="$t('Account Category')"
:title="getAccountCategoryName(account.category)"
@click="showAccountCategorySheet = true"
>
<list-item-selection-sheet value-type="item"
key-field="id" value-field="id" title-field="name"
icon-field="defaultAccountIconId" icon-type="account"
:title-i18n="true"
:items="allAccountCategories"
v-model:show="showAccountCategorySheet"
v-model="account.category">
</list-item-selection-sheet>
</f7-list-item>
<f7-list-item
class="list-item-with-header-and-title"
link="#"
:class="{ 'disabled': editAccountId }"
:header="$t('Account Type')"
:title="account.type | optionName(allAccountTypes, 'id', 'name') | localized"
:no-chevron="!!editAccountId"
@click="showAccountTypeSheet = true"
>
<list-item-selection-sheet value-type="item"
key-field="id" value-field="id" title-field="name"
:items="allAccountTypes"
:title-i18n="true"
:show.sync="showAccountTypeSheet"
v-model="account.type">
</list-item-selection-sheet>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list-item
link="#" no-chevron
class="list-item-with-header-and-title"
:class="{ 'disabled': editAccountId }"
:header="$t('Account Type')"
:title="this.getAccountTypeName(account.type)"
@click="showAccountTypeSheet = true"
>
<list-item-selection-sheet value-type="item"
key-field="id" value-field="id" title-field="name"
:items="allAccountTypes"
:title-i18n="true"
v-model:show="showAccountTypeSheet"
v-model="account.type">
</list-item-selection-sheet>
</f7-list-item>
</f7-list>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-input label="Account Name" placeholder="Your account name"></f7-list-input>
<f7-list-item class="list-item-with-header-and-title" header="Account Icon" link="#">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"></f7-icon>
</f7-block>
</f7-list-item>
<f7-list-item class="list-item-with-header-and-title" header="Account Color" link="#">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"></f7-icon>
</f7-block>
</f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-no-item-after" header="Currency" title="Currency" link="#"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title" header="Account Balance" title="Balance" link="#"></f7-list-item>
<f7-list-item class="list-item-toggle" header="Visible" after="True"></f7-list-item>
<f7-list-input label="Description" type="textarea" placeholder="Your account description (optional)"></f7-list-input>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-vertical skeleton-text" v-if="loading">
<f7-list-input label="Account Name" placeholder="Your account name"></f7-list-input>
<f7-list-item class="list-item-with-header-and-title list-item-with-multi-item">
<template #default>
<div class="grid grid-cols-2">
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>Account Icon</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"></f7-icon>
</div>
</div>
</div>
</div>
</a>
</div>
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>Account Color</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"></f7-icon>
</div>
</div>
</div>
</div>
</a>
</div>
</div>
</template>
</f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-no-item-after" header="Currency" title="Currency" :link="editAccountId ? null : '#'"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title" header="Account Balance" title="Balance"></f7-list-item>
<f7-list-item class="list-item-toggle" header="Visible" after="True"></f7-list-item>
<f7-list-input label="Description" type="textarea" placeholder="Your account description (optional)"></f7-list-input>
</f7-list>
<f7-card v-else-if="!loading && account.type === $constants.account.allAccountTypes.SingleAccount">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list form>
<f7-list-input
type="text"
clear-button
:label="$t('Account Name')"
:placeholder="$t('Your account name')"
:value="account.name"
@input="account.name = $event.target.value"
></f7-list-input>
<f7-list form strong inset dividers class="margin-vertical" v-else-if="!loading && account.type === $constants.account.allAccountTypes.SingleAccount">
<f7-list-input
type="text"
clear-button
:label="$t('Account Name')"
:placeholder="$t('Your account name')"
v-model:value="account.name"
></f7-list-input>
<f7-list-item class="list-item-with-header-and-title"
:header="$t('Account Icon')" link="#"
@click="account.showIconSelectionSheet = true">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon :icon="account.icon | accountIcon"
:style="account.color | accountIconStyle('var(--default-icon-color)')"></f7-icon>
</f7-block>
<icon-selection-sheet :all-icon-infos="allAccountIcons"
:show.sync="account.showIconSelectionSheet"
:color="account.color"
v-model="account.icon"
></icon-selection-sheet>
</f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-with-multi-item">
<template #default>
<div class="grid grid-cols-2">
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#" @click="account.showIconSelectionSheet = true">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>{{ $t('Account Icon') }}</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<ItemIcon icon-type="account" :icon-id="account.icon" :color="account.color"></ItemIcon>
</div>
</div>
</div>
</div>
</a>
<f7-list-item class="list-item-with-header-and-title"
:header="$t('Account Color')" link="#"
@click="account.showColorSelectionSheet = true">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"
:style="account.color | accountIconStyle('var(--default-icon-color)')"></f7-icon>
</f7-block>
<color-selection-sheet :all-color-infos="allAccountColors"
:show.sync="account.showColorSelectionSheet"
v-model="account.color"
></color-selection-sheet>
</f7-list-item>
<icon-selection-sheet :all-icon-infos="allAccountIcons"
:color="account.color"
v-model:show="account.showIconSelectionSheet"
v-model="account.icon"
></icon-selection-sheet>
</div>
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#" @click="account.showColorSelectionSheet = true">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>{{ $t('Account Color') }}</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<ItemIcon icon-type="fixed-f7" icon-id="app_fill" :color="account.color"></ItemIcon>
</div>
</div>
</div>
</div>
</a>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:class="{ 'disabled': editAccountId }"
:header="$t('Currency')"
:no-chevron="!!editAccountId"
smart-select :smart-select-params="{ openIn: 'popup', searchbar: true, searchbarPlaceholder: $t('Currency Name'), searchbarDisableText: $t('Cancel'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }"
>
<f7-block slot="title" class="no-padding no-margin">
<span>{{ $t(`currency.${account.currency}`) }}&nbsp;</span>
<small class="smaller">{{ account.currency }}</small>
</f7-block>
<select autocomplete="transaction-currency" v-model="account.currency">
<option v-for="currency in allCurrencies"
:key="currency.code"
:value="currency.code">{{ currency.displayName }}</option>
</select>
</f7-list-item>
<color-selection-sheet :all-color-infos="allAccountColors"
v-model:show="account.showColorSelectionSheet"
v-model="account.color"
></color-selection-sheet>
</div>
</div>
</template>
</f7-list-item>
<f7-list-item
class="list-item-with-header-and-title"
:link="editAccountId ? null : '#'"
:class="{ 'disabled': editAccountId }"
:header="$t('Account Balance')"
:title="account.balance | currency(account.currency)"
@click="account.showBalanceSheet = true"
>
<number-pad-sheet :min-value="$constants.transaction.minAmount"
:max-value="$constants.transaction.maxAmount"
:show.sync="account.showBalanceSheet"
v-model="account.balance"
></number-pad-sheet>
</f7-list-item>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:class="{ 'disabled': editAccountId }"
:header="$t('Currency')"
:no-chevron="!!editAccountId"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Currency Name'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Currency Name'), popupCloseLinkText: $t('Done') }"
>
<template #title>
<div class="no-padding no-margin">
<span>{{ $t(`currency.${account.currency}`) }}&nbsp;</span>
<small class="smaller">{{ account.currency }}</small>
</div>
</template>
<select autocomplete="transaction-currency" v-model="account.currency">
<option :value="currency.code"
:key="currency.code"
v-for="currency in allCurrencies">{{ currency.displayName }}</option>
</select>
</f7-list-item>
<f7-list-item :header="$t('Visible')" v-if="editAccountId">
<f7-toggle :checked="account.visible" @toggle:change="account.visible = $event"></f7-toggle>
</f7-list-item>
<f7-list-item
link="#" no-chevron
class="list-item-with-header-and-title"
:class="{ 'disabled': editAccountId }"
:header="$t('Account Balance')"
:title="$locale.getDisplayCurrency(account.balance, account.currency)"
@click="account.showBalanceSheet = true"
>
<number-pad-sheet :min-value="$constants.transaction.minAmount"
:max-value="$constants.transaction.maxAmount"
v-model:show="account.showBalanceSheet"
v-model="account.balance"
></number-pad-sheet>
</f7-list-item>
<f7-list-input
type="textarea"
class="textarea-auto-size"
style="height: auto"
:label="$t('Description')"
:placeholder="$t('Your account description (optional)')"
:value="account.comment"
@input="account.comment = $event.target.value"
></f7-list-input>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list-item :header="$t('Visible')" v-if="editAccountId">
<f7-toggle :checked="account.visible" @toggle:change="account.visible = $event"></f7-toggle>
</f7-list-item>
<f7-card v-else-if="!loading && account.type === $constants.account.allAccountTypes.MultiSubAccounts">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list form>
<f7-list-input
type="text"
clear-button
:label="$t('Account Name')"
:placeholder="$t('Your account name')"
:value="account.name"
@input="account.name = $event.target.value"
></f7-list-input>
<f7-list-input
type="textarea"
style="height: auto"
:label="$t('Description')"
:placeholder="$t('Your account description (optional)')"
v-textarea-auto-size
v-model:value="account.comment"
></f7-list-input>
</f7-list>
<f7-list-item class="list-item-with-header-and-title"
:header="$t('Account Icon')" link="#"
@click="account.showIconSelectionSheet = true">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon :icon="account.icon | accountIcon"
:style="account.color | accountIconStyle('var(--default-icon-color)')"></f7-icon>
</f7-block>
<icon-selection-sheet :all-icon-infos="allAccountIcons"
:show.sync="account.showIconSelectionSheet"
:color="account.color"
v-model="account.icon"
></icon-selection-sheet>
</f7-list-item>
<f7-list form strong inset dividers class="margin-vertical" v-else-if="!loading && account.type === $constants.account.allAccountTypes.MultiSubAccounts">
<f7-list-input
type="text"
clear-button
:label="$t('Account Name')"
:placeholder="$t('Your account name')"
v-model:value="account.name"
></f7-list-input>
<f7-list-item class="list-item-with-header-and-title"
:header="$t('Account Color')" link="#"
@click="account.showColorSelectionSheet = true">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"
:style="account.color | accountIconStyle('var(--default-icon-color)')"></f7-icon>
</f7-block>
<color-selection-sheet :all-color-infos="allAccountColors"
:show.sync="account.showColorSelectionSheet"
v-model="account.color"
></color-selection-sheet>
</f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-with-multi-item">
<template #default>
<div class="grid grid-cols-2">
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#" @click="account.showIconSelectionSheet = true">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>{{ $t('Account Icon') }}</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<ItemIcon icon-type="account" :icon-id="account.icon" :color="account.color"></ItemIcon>
</div>
</div>
</div>
</div>
</a>
<f7-list-item :header="$t('Visible')" v-if="editAccountId">
<f7-toggle :checked="account.visible" @toggle:change="account.visible = $event"></f7-toggle>
</f7-list-item>
<icon-selection-sheet :all-icon-infos="allAccountIcons"
:color="account.color"
v-model:show="account.showIconSelectionSheet"
v-model="account.icon"
></icon-selection-sheet>
</div>
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#" @click="account.showColorSelectionSheet = true">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>{{ $t('Account Color') }}</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<ItemIcon icon-type="fixed-f7" icon-id="app_fill" :color="account.color"></ItemIcon>
</div>
</div>
</div>
</div>
</a>
<f7-list-input
type="textarea"
class="textarea-auto-size"
style="height: auto"
:label="$t('Description')"
:placeholder="$t('Your account description (optional)')"
:value="account.comment"
@input="account.comment = $event.target.value"
></f7-list-input>
</f7-list>
</f7-card-content>
</f7-card>
<color-selection-sheet :all-color-infos="allAccountColors"
v-model:show="account.showColorSelectionSheet"
v-model="account.color"
></color-selection-sheet>
</div>
</div>
</template>
</f7-list-item>
<f7-list-item :header="$t('Visible')" v-if="editAccountId">
<f7-toggle :checked="account.visible" @toggle:change="account.visible = $event"></f7-toggle>
</f7-list-item>
<f7-list-input
type="textarea"
style="height: auto"
:label="$t('Description')"
:placeholder="$t('Your account description (optional)')"
v-textarea-auto-size
v-model:value="account.comment"
></f7-list-input>
</f7-list>
<f7-block class="no-padding no-margin" v-if="!loading && account.type === $constants.account.allAccountTypes.MultiSubAccounts">
<f7-card v-for="(subAccount, idx) in subAccounts" :key="idx">
<f7-card-header>
<small class="subaccount-header-content">{{ $t('Sub Account') + ' #' + (idx + 1) }}</small>
<f7-button rasied fill color="red" icon-f7="trash" icon-size="16px"
<f7-list strong inset dividers class="subaccount-edit-list margin-vertical"
:key="idx"
v-for="(subAccount, idx) in subAccounts">
<f7-list-item group-title>
<small>{{ $t('Sub Account') + ' #' + (idx + 1) }}</small>
<f7-button rasied fill class="subaccount-delete-button" color="red" icon-f7="trash" icon-size="16px"
:tooltip="$t('Remove Sub Account')"
v-if="!editAccountId"
@click="removeSubAccount(subAccount, false)">
</f7-button>
</f7-card-header>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-input
type="text"
clear-button
:label="$t('Sub Account Name')"
:placeholder="$t('Your sub account name')"
:value="subAccount.name"
@input="subAccount.name = $event.target.value"
></f7-list-input>
</f7-list-item>
<f7-list-item class="list-item-with-header-and-title"
:header="$t('Sub Account Icon')" link="#"
@click="subAccount.showIconSelectionSheet = true">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon :icon="subAccount.icon | accountIcon"
:style="subAccount.color | accountIconStyle('var(--default-icon-color)')"></f7-icon>
</f7-block>
<icon-selection-sheet :all-icon-infos="allAccountIcons"
:show.sync="subAccount.showIconSelectionSheet"
:color="subAccount.color"
v-model="subAccount.icon"
></icon-selection-sheet>
</f7-list-item>
<f7-list-input
type="text"
clear-button
:label="$t('Sub Account Name')"
:placeholder="$t('Your sub account name')"
v-model:value="subAccount.name"
></f7-list-input>
<f7-list-item class="list-item-with-header-and-title"
:header="$t('Sub Account Color')" link="#"
@click="subAccount.showColorSelectionSheet = true">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"
:style="subAccount.color | accountIconStyle('var(--default-icon-color)')"></f7-icon>
</f7-block>
<color-selection-sheet :all-color-infos="allAccountColors"
:show.sync="subAccount.showColorSelectionSheet"
v-model="subAccount.color"
></color-selection-sheet>
</f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-with-multi-item">
<template #default>
<div class="grid grid-cols-2">
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#" @click="subAccount.showIconSelectionSheet = true">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>{{ $t('Sub Account Icon') }}</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<ItemIcon icon-type="account" :icon-id="subAccount.icon" :color="subAccount.color"></ItemIcon>
</div>
</div>
</div>
</div>
</a>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:class="{ 'disabled': editAccountId }"
:header="$t('Currency')"
:no-chevron="!!editAccountId"
smart-select :smart-select-params="{ openIn: 'popup', searchbar: true, searchbarPlaceholder: $t('Currency Name'), searchbarDisableText: $t('Cancel'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }"
>
<f7-block slot="title" class="no-padding no-margin">
<span>{{ $t(`currency.${subAccount.currency}`) }}&nbsp;</span>
<small class="smaller">{{ subAccount.currency }}</small>
</f7-block>
<select autocomplete="transaction-currency" v-model="subAccount.currency">
<option v-for="currency in allCurrencies"
:key="currency.code"
:value="currency.code">{{ currency.displayName }}</option>
</select>
</f7-list-item>
<icon-selection-sheet :all-icon-infos="allAccountIcons"
:color="subAccount.color"
v-model:show="subAccount.showIconSelectionSheet"
v-model="subAccount.icon"
></icon-selection-sheet>
</div>
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#" @click="subAccount.showColorSelectionSheet = true">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>{{ $t('Sub Account Color') }}</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<ItemIcon icon-type="fixed-f7" icon-id="app_fill" :color="subAccount.color"></ItemIcon>
</div>
</div>
</div>
</div>
</a>
<f7-list-item
class="list-item-with-header-and-title"
:link="editAccountId ? null : '#'"
:class="{ 'disabled': editAccountId }"
:header="$t('Sub Account Balance')"
:title="subAccount.balance | currency(subAccount.currency)"
@click="subAccount.showBalanceSheet = true"
>
<number-pad-sheet :min-value="$constants.transaction.minAmount"
:max-value="$constants.transaction.maxAmount"
:show.sync="subAccount.showBalanceSheet"
v-model="subAccount.balance"
></number-pad-sheet>
</f7-list-item>
<color-selection-sheet :all-color-infos="allAccountColors"
v-model:show="subAccount.showColorSelectionSheet"
v-model="subAccount.color"
></color-selection-sheet>
</div>
</div>
</template>
</f7-list-item>
<f7-list-item :header="$t('Visible')" v-if="editAccountId">
<f7-toggle :checked="subAccount.visible" @toggle:change="subAccount.visible = $event"></f7-toggle>
</f7-list-item>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:class="{ 'disabled': editAccountId }"
:header="$t('Currency')"
:no-chevron="!!editAccountId"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Currency Name'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Currency Name'), popupCloseLinkText: $t('Done') }"
>
<template #title>
<div class="no-padding no-margin">
<span>{{ $t(`currency.${subAccount.currency}`) }}&nbsp;</span>
<small class="smaller">{{ subAccount.currency }}</small>
</div>
</template>
<select autocomplete="transaction-currency" v-model="subAccount.currency">
<option :value="currency.code"
:key="currency.code"
v-for="currency in allCurrencies">{{ currency.displayName }}</option>
</select>
</f7-list-item>
<f7-list-input
type="textarea"
class="textarea-auto-size"
style="height: auto"
:label="$t('Description')"
:placeholder="$t('Your sub account description (optional)')"
:value="subAccount.comment"
@input="subAccount.comment = $event.target.value"
></f7-list-input>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list-item
link="#" no-chevron
class="list-item-with-header-and-title"
:class="{ 'disabled': editAccountId }"
:header="$t('Sub Account Balance')"
:title="$locale.getDisplayCurrency(subAccount.balance, subAccount.currency)"
@click="subAccount.showBalanceSheet = true"
>
<number-pad-sheet :min-value="$constants.transaction.minAmount"
:max-value="$constants.transaction.maxAmount"
v-model:show="subAccount.showBalanceSheet"
v-model="subAccount.balance"
></number-pad-sheet>
</f7-list-item>
<f7-list-item :header="$t('Visible')" v-if="editAccountId">
<f7-toggle :checked="subAccount.visible" @toggle:change="subAccount.visible = $event"></f7-toggle>
</f7-list-item>
<f7-list-input
type="textarea"
style="height: auto"
:label="$t('Description')"
:placeholder="$t('Your sub account description (optional)')"
v-textarea-auto-size
v-model:value="subAccount.comment"
></f7-list-input>
</f7-list>
</f7-block>
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
@@ -348,6 +421,10 @@
<script>
export default {
props: [
'f7route',
'f7router'
],
data() {
const self = this;
@@ -427,7 +504,7 @@ export default {
},
created() {
const self = this;
const query = self.$f7route.query;
const query = self.f7route.query;
if (query.id) {
self.loading = true;
@@ -484,12 +561,9 @@ export default {
self.loading = false;
}
},
updated: function () {
this.autoChangeCommentTextareaSize();
},
methods: {
onPageAfterIn() {
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
addSubAccount() {
const self = this;
@@ -536,7 +610,7 @@ export default {
},
save() {
const self = this;
const router = self.$f7router;
const router = self.f7router;
let problemMessage = self.getInputEmptyProblemMessage(self.account, false);
@@ -622,15 +696,13 @@ export default {
}
});
},
autoChangeCommentTextareaSize() {
const app = this.$f7;
const $$ = app.$;
$$('.textarea-auto-size textarea').each((idx, el) => {
el.scrollTop = 0;
el.style.height = '';
el.style.height = el.scrollHeight + 'px';
});
getAccountTypeName(accountType) {
const typeName = this.$utilities.getNameByKeyValue(this.allAccountTypes, accountType, 'id', 'name');
return this.$t(typeName);
},
getAccountCategoryName(accountCategory) {
const categoryName = this.$utilities.getNameByKeyValue(this.allAccountCategories, accountCategory, 'id', 'name');
return this.$t(categoryName);
},
chooseSuitableIcon(oldCategory, newCategory) {
const allCategories = this.$constants.account.allCategories;
@@ -688,7 +760,11 @@ export default {
</script>
<style>
.subaccount-header-content {
opacity: 0.6;
.subaccount-edit-list {
--f7-list-group-title-height: 40px;
}
.subaccount-delete-button {
margin-left: auto;
}
</style>
+115 -154
View File
@@ -18,7 +18,7 @@
</p>
<p class="no-margin">
<span class="net-assets" v-if="loading">0.00 USD</span>
<span class="net-assets" v-else-if="!loading">{{ netAssets | currency(defaultCurrency) }}</span>
<span class="net-assets" v-else-if="!loading">{{ $locale.getDisplayCurrency(netAssets, defaultCurrency) }}</span>
<f7-link class="margin-left-half" @click="toggleShowAccountBalance()">
<f7-icon :f7="showAccountBalance ? 'eye_slash_fill' : 'eye_fill'" size="18px"></f7-icon>
</f7-link>
@@ -29,155 +29,106 @@
</small>
<small class="account-overview-info" v-else-if="!loading">
<span>{{ $t('Total assets') }}</span>
<span>{{ totalAssets | currency(defaultCurrency) }}</span>
<span>{{ $locale.getDisplayCurrency(totalAssets, defaultCurrency) }}</span>
<span>|</span>
<span>{{ $t('Total liabilities') }}</span>
<span>{{ totalLiabilities | currency(defaultCurrency) }}</span>
<span>{{ $locale.getDisplayCurrency(totalLiabilities, defaultCurrency) }}</span>
</small>
</p>
</f7-card-header>
</f7-card>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-header>
<small class="card-header-content">Account Category</small>
</f7-card-header>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item class="nested-list-item" after="0.00 USD" link="#">
<f7-block slot="title" class="no-padding">
<div class="display-flex padding-top-half padding-bottom-half">
<f7-icon slot="media" f7="app_fill"></f7-icon>
<div class="nested-list-item-title">Account Name</div>
</div>
</f7-block>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="account-list margin-vertical skeleton-text"
:key="listIdx" v-for="listIdx in [ 1, 2, 3 ]" v-if="loading">
<f7-list-item group-title>
<small>Account Category</small>
</f7-list-item>
<f7-list-item class="nested-list-item" after="0.00 USD" link="#"
:key="itemIdx" v-for="itemIdx in (listIdx === 1 ? [ 1 ] : [ 1, 2 ])">
<template #title>
<div class="display-flex padding-top-half padding-bottom-half">
<f7-icon f7="app_fill"></f7-icon>
<div class="nested-list-item-title">Account Name</div>
</div>
</template>
</f7-list-item>
</f7-list>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-header>
<small class="card-header-content">Account Category 2</small>
</f7-card-header>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item class="nested-list-item" after="0.00 USD" link="#">
<f7-block slot="title" class="no-padding">
<div class="display-flex padding-top-half padding-bottom-half">
<f7-icon slot="media" f7="app_fill"></f7-icon>
<div class="nested-list-item-title">Account Name</div>
</div>
</f7-block>
</f7-list-item>
<f7-list-item class="nested-list-item" after="0.00 USD" link="#">
<f7-block slot="title" class="no-padding">
<div class="display-flex padding-top-half padding-bottom-half">
<f7-icon slot="media" f7="app_fill"></f7-icon>
<div class="nested-list-item-title">Account Name 2</div>
</div>
</f7-block>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-vertical" v-if="!loading && noAvailableAccount">
<f7-list-item :title="$t('No available account')"></f7-list-item>
</f7-list>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-header>
<small class="card-header-content">Account Category 3</small>
</f7-card-header>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item class="nested-list-item" after="0.00 USD" link="#">
<f7-block slot="title" class="no-padding">
<div class="display-flex padding-top-half padding-bottom-half">
<f7-icon slot="media" f7="app_fill"></f7-icon>
<div class="nested-list-item-title">Account Name</div>
<div :key="accountCategory.id"
v-for="accountCategory in allAccountCategories"
v-show="(showHidden && hasAccount(accountCategory, false)) || hasAccount(accountCategory, true)">
<f7-list strong inset dividers sortable class="account-list margin-vertical"
:sortable-enabled="sortable"
v-if="categorizedAccounts[accountCategory.id]"
@sortable:sort="onSort">
<f7-list-item group-title :sortable="false">
<small>
<span>{{ $t(accountCategory.name) }}</span>
<span style="margin-left: 10px">{{ $locale.getDisplayCurrency(accountCategoryTotalBalance(accountCategory), defaultCurrency) }}</span>
</small>
</f7-list-item>
<f7-list-item swipeout
:id="getAccountDomId(account)"
:class="{ 'nested-list-item': true, 'has-child-list-item': account.type === $constants.account.allAccountTypes.MultiSubAccounts }"
:after="$locale.getDisplayCurrency(accountBalance(account), account.currency)"
:link="!sortable ? '/transaction/list?accountId=' + account.id : null"
:key="account.id"
v-for="account in categorizedAccounts[accountCategory.id].accounts"
v-show="showHidden || !account.hidden"
@taphold="setSortable()"
>
<template #title>
<div class="display-flex padding-top-half padding-bottom-half">
<ItemIcon icon-type="account" :icon-id="account.icon" :color="account.color">
<f7-badge color="gray" class="right-bottom-icon" v-if="account.hidden">
<f7-icon f7="eye_slash_fill"></f7-icon>
</f7-badge>
</ItemIcon>
<div class="nested-list-item-title">
<span>{{ account.name }}</span>
<div class="item-footer" v-if="account.comment">{{ account.comment }}</div>
</div>
</f7-block>
</f7-list-item>
<f7-list-item class="nested-list-item" after="0.00 USD" link="#">
<f7-block slot="title" class="no-padding">
<div class="display-flex padding-top-half padding-bottom-half">
<f7-icon slot="media" f7="app_fill"></f7-icon>
<div class="nested-list-item-title">Account Name 2</div>
</div>
</f7-block>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-card v-if="!loading && noAvailableAccount">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item :title="$t('No available account')"></f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-card v-for="accountCategory in allAccountCategories" :key="accountCategory.id" v-show="(showHidden && hasAccount(accountCategory, false)) || hasAccount(accountCategory, true)">
<f7-card-header>
<small class="card-header-content">
<span>{{ $t(accountCategory.name) }}</span>
<span style="margin-left: 10px">{{ accountCategoryTotalBalance(accountCategory) | currency(defaultCurrency) }}</span>
</small>
</f7-card-header>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list class="account-list" sortable :sortable-enabled="sortable" @sortable:sort="onSort" v-if="categorizedAccounts[accountCategory.id]">
<f7-list-item v-for="account in categorizedAccounts[accountCategory.id].accounts" v-show="showHidden || !account.hidden"
:key="account.id" :id="account | accountDomId"
:class="{ 'nested-list-item': true, 'has-child-list-item': account.type === $constants.account.allAccountTypes.MultiSubAccounts }"
:after="accountBalance(account) | currency(account.currency)"
:link="!sortable ? '/transaction/list?accountId=' + account.id : null"
swipeout @taphold.native="setSortable()"
>
<f7-block slot="title" class="no-padding">
<div class="display-flex padding-top-half padding-bottom-half">
<f7-icon class="align-self-center" slot="media" :icon="account.icon | accountIcon"
:style="account.color | accountIconStyle('var(--default-icon-color)')">
<f7-badge color="gray" class="right-bottom-icon" v-if="account.hidden">
<f7-icon f7="eye_slash_fill"></f7-icon>
</f7-badge>
</f7-icon>
<div class="nested-list-item-title">
<span>{{ account.name }}</span>
<div class="item-footer" slot="footer" v-if="account.comment">{{ account.comment }}</div>
</div>
</div>
<li v-if="account.type === $constants.account.allAccountTypes.MultiSubAccounts">
<ul class="no-padding">
<f7-list-item class="no-sortable nested-list-item-child" v-for="subAccount in account.subAccounts" v-show="showHidden || !subAccount.hidden"
:key="subAccount.id" :id="subAccount | accountDomId"
:title="subAccount.name" :footer="subAccount.comment" :after="accountBalance(subAccount) | currency(subAccount.currency)"
:link="!sortable ? '/transaction/list?accountId=' + subAccount.id : null"
>
<f7-icon slot="media" :icon="subAccount.icon | accountIcon"
:style="subAccount.color | accountIconStyle('var(--default-icon-color)')">
</div>
<li v-if="account.type === $constants.account.allAccountTypes.MultiSubAccounts">
<ul class="no-padding">
<f7-list-item class="no-sortable nested-list-item-child"
:id="getAccountDomId(subAccount)"
:title="subAccount.name" :footer="subAccount.comment" :after="$locale.getDisplayCurrency(accountBalance(subAccount), subAccount.currency)"
:link="!sortable ? '/transaction/list?accountId=' + subAccount.id : null"
:key="subAccount.id"
v-for="subAccount in account.subAccounts"
v-show="showHidden || !subAccount.hidden"
>
<template #media>
<ItemIcon icon-type="account" :icon-id="subAccount.icon" :color="subAccount.color">
<f7-badge color="gray" class="right-bottom-icon" v-if="subAccount.hidden">
<f7-icon f7="eye_slash_fill"></f7-icon>
</f7-badge>
</f7-icon>
</f7-list-item>
</ul>
</li>
</f7-block>
<f7-swipeout-actions left v-if="sortable">
<f7-swipeout-button :color="account.hidden ? 'blue' : 'gray'" class="padding-left padding-right"
overswipe close @click="hide(account, !account.hidden)">
<f7-icon :f7="account.hidden ? 'eye' : 'eye_slash'"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
<f7-swipeout-actions right v-if="!sortable">
<f7-swipeout-button color="orange" close :text="$t('Edit')" @click="edit(account)"></f7-swipeout-button>
<f7-swipeout-button color="red" class="padding-left padding-right" @click="remove(account, false)">
<f7-icon f7="trash"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
</ItemIcon>
</template>
</f7-list-item>
</ul>
</li>
</template>
<f7-swipeout-actions left v-if="sortable">
<f7-swipeout-button :color="account.hidden ? 'blue' : 'gray'" class="padding-left padding-right"
overswipe close @click="hide(account, !account.hidden)">
<f7-icon :f7="account.hidden ? 'eye' : 'eye_slash'"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
<f7-swipeout-actions right v-if="!sortable">
<f7-swipeout-button color="orange" close :text="$t('Edit')" @click="edit(account)"></f7-swipeout-button>
<f7-swipeout-button color="red" class="padding-left padding-right" @click="remove(account, false)">
<f7-icon f7="trash"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
</f7-list-item>
</f7-list>
</div>
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
<f7-actions-group>
@@ -204,6 +155,9 @@
<script>
export default {
props: [
'f7router'
],
data() {
return {
loading: true,
@@ -353,7 +307,7 @@ export default {
this.reload(null);
}
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
reload(done) {
if (this.sortable) {
@@ -473,17 +427,22 @@ export default {
onSort(event) {
const self = this;
if (!event || !event.el || !event.el.id || event.el.id.indexOf('account_') !== 0) {
if (!event || !event.el || !event.el.id) {
self.$toast('Unable to move account');
return;
}
const id = event.el.id.substring(8); // account_
const id = self.parseAccountIdFromDomId(event.el.id);
if (!id) {
self.$toast('Unable to move account');
return;
}
self.$store.dispatch('changeAccountDisplayOrder', {
accountId: id,
from: event.from,
to: event.to
from: event.from - 1, // first item in the list is title, so the index need minus one
to: event.to - 1
}).then(() => {
self.displayOrderModified = true;
}).catch(error => {
@@ -519,7 +478,7 @@ export default {
});
},
edit(account) {
this.$f7router.navigate('/account/edit?id=' + account.id);
this.f7router.navigate('/account/edit?id=' + account.id);
},
hide(account, hidden) {
const self = this;
@@ -541,8 +500,6 @@ export default {
},
remove(account, confirm) {
const self = this;
const app = self.$f7;
const $$ = app.$;
if (!account) {
self.$alert('An error has occurred');
@@ -562,9 +519,7 @@ export default {
self.$store.dispatch('deleteAccount', {
account: account,
beforeResolve: (done) => {
app.swipeout.delete($$(`#${self.$options.filters.accountDomId(account)}`), () => {
done();
});
self.$ui.onSwipeoutDeleted(self.getAccountDomId(account), done);
}
}).then(() => {
self.$hideLoading();
@@ -575,11 +530,16 @@ export default {
self.$toast(error.message || error);
}
});
}
},
filters: {
accountDomId(account) {
},
getAccountDomId(account) {
return 'account_' + account.id;
},
parseAccountIdFromDomId(domId) {
if (!domId || domId.indexOf('account_') !== 0) {
return null;
}
return domId.substring(8); // account_
}
}
};
@@ -590,11 +550,11 @@ export default {
background-color: var(--f7-color-yellow);
}
.theme-dark .account-overview-card {
.dark .account-overview-card {
background-color: var(--f7-theme-color);
}
.theme-dark .account-overview-card a {
.dark .account-overview-card a {
color: var(--f7-text-color);
opacity: 0.6;
}
@@ -616,6 +576,7 @@ export default {
}
.account-list {
--f7-list-group-title-height: 36px;
--f7-list-item-footer-font-size: 13px;
}
+14 -19
View File
@@ -2,31 +2,26 @@
<f7-page ptr @ptr:refresh="reload" @page:afterin="onPageAfterIn">
<f7-navbar :title="$t('Transaction Categories')" :back-link="$t('Back')"></f7-navbar>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item title="Expense" link="#"></f7-list-item>
<f7-list-item title="Income" link="#"></f7-list-item>
<f7-list-item title="Transfer" link="#"></f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-top skeleton-text" v-if="loading">
<f7-list-item title="Expense" link="#"></f7-list-item>
<f7-list-item title="Income" link="#"></f7-list-item>
<f7-list-item title="Transfer" link="#"></f7-list-item>
</f7-list>
<f7-card v-else-if="!loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item :title="$t('Expense')" link="/category/list?type=2"></f7-list-item>
<f7-list-item :title="$t('Income')" link="/category/list?type=1"></f7-list-item>
<f7-list-item :title="$t('Transfer')" link="/category/list?type=3"></f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-top" v-else-if="!loading">
<f7-list-item :title="$t('Expense')" link="/category/list?type=2"></f7-list-item>
<f7-list-item :title="$t('Income')" link="/category/list?type=1"></f7-list-item>
<f7-list-item :title="$t('Transfer')" link="/category/list?type=3"></f7-list-item>
</f7-list>
</f7-page>
</template>
<script>
export default {
props: [
'f7router'
],
data() {
return {
loading: true,
@@ -53,7 +48,7 @@ export default {
},
methods: {
onPageAfterIn() {
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
reload(done) {
const self = this;
+119 -89
View File
@@ -8,91 +8,134 @@
</f7-nav-right>
</f7-navbar>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-input label="Category Name" placeholder="Your category name"></f7-list-input>
<f7-list-item class="list-item-with-header-and-title" header="Category Icon" link="#">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"></f7-icon>
</f7-block>
</f7-list-item>
<f7-list-item class="list-item-with-header-and-title" header="Category Color" link="#">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"></f7-icon>
</f7-block>
</f7-list-item>
<f7-list-item class="list-item-toggle" header="Visible" after="True"></f7-list-item>
<f7-list-input label="Description" type="textarea" placeholder="Your category description (optional)"></f7-list-input>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-top skeleton-text" v-if="loading">
<f7-list-input label="Category Name" placeholder="Your category name"></f7-list-input>
<f7-list-item class="list-item-with-header-and-title list-item-with-multi-item">
<template #default>
<div class="grid grid-cols-2">
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>Category Icon</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"></f7-icon>
</div>
</div>
</div>
</div>
</a>
</div>
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>Category Color</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"></f7-icon>
</div>
</div>
</div>
</div>
</a>
</div>
</div>
</template>
</f7-list-item>
<f7-list-item class="list-item-toggle" header="Visible" after="True"></f7-list-item>
<f7-list-input label="Description" type="textarea" placeholder="Your category description (optional)"></f7-list-input>
</f7-list>
<f7-card v-else-if="!loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list form>
<f7-list-input
type="text"
clear-button
:label="$t('Category Name')"
:placeholder="$t('Your category name')"
:value="category.name"
@input="category.name = $event.target.value"
></f7-list-input>
<f7-list form strong inset dividers class="margin-top" v-else-if="!loading">
<f7-list-input
type="text"
clear-button
:label="$t('Category Name')"
:placeholder="$t('Your category name')"
v-model:value="category.name"
></f7-list-input>
<f7-list-item class="list-item-with-header-and-title"
key="singleTypeCategoryIconSelection" link="#"
:header="$t('Category Icon')"
@click="category.showIconSelectionSheet = true">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon :icon="category.icon | categoryIcon"
:style="category.color | categoryIconStyle('var(--default-icon-color)')"></f7-icon>
</f7-block>
<icon-selection-sheet :all-icon-infos="allCategoryIcons"
:show.sync="category.showIconSelectionSheet"
:color="category.color"
v-model="category.icon"
></icon-selection-sheet>
</f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-with-multi-item">
<template #default>
<div class="grid grid-cols-2">
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#" @click="category.showIconSelectionSheet = true">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>{{ $t('Category Icon') }}</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<ItemIcon icon-type="category" :icon-id="category.icon" :color="category.color"></ItemIcon>
</div>
</div>
</div>
</div>
</a>
<f7-list-item class="list-item-with-header-and-title"
key="singleTypeCategoryColorSelection" link="#"
:header="$t('Category Color')"
@click="category.showColorSelectionSheet = true">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"
:style="category.color | categoryIconStyle('var(--default-icon-color)')"></f7-icon>
</f7-block>
<color-selection-sheet :all-color-infos="allCategoryColors"
:show.sync="category.showColorSelectionSheet"
v-model="category.color"
></color-selection-sheet>
</f7-list-item>
<icon-selection-sheet :all-icon-infos="allCategoryIcons"
:color="category.color"
v-model:show="category.showIconSelectionSheet"
v-model="category.icon"
></icon-selection-sheet>
</div>
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#" @click="category.showColorSelectionSheet = true">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>{{ $t('Category Color') }}</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<ItemIcon icon-type="fixed-f7" icon-id="app_fill" :color="category.color"></ItemIcon>
</div>
</div>
</div>
</div>
</a>
<f7-list-item :header="$t('Visible')" v-if="editCategoryId">
<f7-toggle :checked="category.visible" @toggle:change="category.visible = $event"></f7-toggle>
</f7-list-item>
<color-selection-sheet :all-color-infos="allCategoryColors"
v-model:show="category.showColorSelectionSheet"
v-model="category.color"
></color-selection-sheet>
</div>
</div>
</template>
</f7-list-item>
<f7-list-input
type="textarea"
class="textarea-auto-size"
style="height: auto"
:label="$t('Description')"
:placeholder="$t('Your category description (optional)')"
:value="category.comment"
@input="category.comment = $event.target.value"
></f7-list-input>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list-item :header="$t('Visible')" v-if="editCategoryId">
<f7-toggle :checked="category.visible" @toggle:change="category.visible = $event"></f7-toggle>
</f7-list-item>
<f7-list-input
type="textarea"
style="height: auto"
:label="$t('Description')"
:placeholder="$t('Your category description (optional)')"
v-textarea-auto-size
v-model:value="category.comment"
></f7-list-input>
</f7-list>
</f7-page>
</template>
<script>
export default {
props: [
'f7route',
'f7router'
],
data() {
const self = this;
const query = self.$f7route.query;
const query = self.f7route.query;
return {
editCategoryId: null,
@@ -150,7 +193,7 @@ export default {
},
created() {
const self = this;
const query = self.$f7route.query;
const query = self.f7route.query;
if (!query.id && !query.parentId) {
self.$toast('Parameter Invalid');
@@ -197,16 +240,13 @@ export default {
self.loading = false;
}
},
updated: function () {
this.autoChangeCommentTextareaSize();
},
methods: {
onPageAfterIn() {
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
save() {
const self = this;
const router = self.$f7router;
const router = self.f7router;
const problemMessage = self.inputEmptyProblemMessage;
@@ -253,16 +293,6 @@ export default {
self.$toast(error.message || error);
}
});
},
autoChangeCommentTextareaSize() {
const app = this.$f7;
const $$ = app.$;
$$('.textarea-auto-size textarea').each((idx, el) => {
el.scrollTop = 0;
el.style.height = '';
el.style.height = el.scrollHeight + 'px';
});
}
}
}
+74 -70
View File
@@ -10,63 +10,57 @@
</f7-nav-right>
</f7-navbar>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item title="Category Name">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</f7-list-item>
<f7-list-item title="Category Name 2">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</f7-list-item>
<f7-list-item title="Category Name 3">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-top skeleton-text" v-if="loading">
<f7-list-item title="Category Name"
:link="hasSubCategories ? '#' : null"
:key="itemIdx" v-for="itemIdx in [ 1, 2, 3 ]">
<template #media>
<f7-icon f7="app_fill"></f7-icon>
</template>
</f7-list-item>
</f7-list>
<f7-card v-else-if="!loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list v-if="noAvailableCategory">
<f7-list-item :title="$t('No available category')"></f7-list-item>
<f7-list-button v-if="hasSubCategories"
:title="$t('Add Default Categories')"
:href="'/category/preset?type=' + categoryType"></f7-list-button>
</f7-list>
<f7-list strong inset dividers class="margin-top" v-if="!loading && noAvailableCategory">
<f7-list-item :title="$t('No available category')"></f7-list-item>
<f7-list-button v-if="hasSubCategories"
:title="$t('Add Default Categories')"
:href="'/category/preset?type=' + categoryType"></f7-list-button>
</f7-list>
<f7-list class="category-list" sortable :sortable-enabled="sortable" @sortable:sort="onSort">
<f7-list-item v-for="category in categories"
:key="category.id"
:id="category | categoryDomId"
:title="category.name"
:footer="category.comment"
:link="hasSubCategories ? '/category/list?type=' + categoryType + '&id=' + category.id : null"
v-show="showHidden || !category.hidden"
swipeout @taphold.native="setSortable()">
<f7-icon slot="media"
:icon="category.icon | categoryIcon"
:style="category.color | categoryIconStyle('var(--default-icon-color)')">
<f7-badge color="gray" class="right-bottom-icon" v-if="category.hidden">
<f7-icon f7="eye_slash_fill"></f7-icon>
</f7-badge>
</f7-icon>
<f7-swipeout-actions left v-if="sortable">
<f7-swipeout-button :color="category.hidden ? 'blue' : 'gray'" class="padding-left padding-right"
overswipe close @click="hide(category, !category.hidden)">
<f7-icon :f7="category.hidden ? 'eye' : 'eye_slash'"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
<f7-swipeout-actions right v-if="!sortable">
<f7-swipeout-button color="orange" close :text="$t('Edit')" @click="edit(category)"></f7-swipeout-button>
<f7-swipeout-button color="red" class="padding-left padding-right" @click="remove(category, false)">
<f7-icon f7="trash"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers sortable class="margin-top category-list"
:sortable-enabled="sortable"
v-if="!loading"
@sortable:sort="onSort">
<f7-list-item swipeout
:id="getCategoryDomId(category)"
:title="category.name"
:footer="category.comment"
:link="hasSubCategories ? '/category/list?type=' + categoryType + '&id=' + category.id : null"
:key="category.id"
v-for="category in categories"
v-show="showHidden || !category.hidden"
@taphold="setSortable()">
<template #media>
<ItemIcon icon-type="category" :icon-id="category.icon" :color="category.color">
<f7-badge color="gray" class="right-bottom-icon" v-if="category.hidden">
<f7-icon f7="eye_slash_fill"></f7-icon>
</f7-badge>
</ItemIcon>
</template>
<f7-swipeout-actions left v-if="sortable">
<f7-swipeout-button :color="category.hidden ? 'blue' : 'gray'" class="padding-left padding-right"
overswipe close @click="hide(category, !category.hidden)">
<f7-icon :f7="category.hidden ? 'eye' : 'eye_slash'"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
<f7-swipeout-actions right v-if="!sortable">
<f7-swipeout-button color="orange" close :text="$t('Edit')" @click="edit(category)"></f7-swipeout-button>
<f7-swipeout-button color="red" class="padding-left padding-right" @click="remove(category, false)">
<f7-icon f7="trash"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
</f7-list-item>
</f7-list>
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
<f7-actions-group>
@@ -91,6 +85,10 @@
<script>
export default {
props: [
'f7route',
'f7router'
],
data() {
return {
hasSubCategories: false,
@@ -166,7 +164,7 @@ export default {
},
created() {
const self = this;
const query = self.$f7route.query;
const query = self.f7route.query;
self.categoryType = parseInt(query.type);
@@ -207,7 +205,7 @@ export default {
this.reload(null);
}
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
reload(done) {
if (this.sortable) {
@@ -245,12 +243,17 @@ export default {
onSort(event) {
const self = this;
if (!event || !event.el || !event.el.id || event.el.id.indexOf('category_') !== 0) {
this.$toast('Unable to move category');
if (!event || !event.el || !event.el.id) {
self.$toast('Unable to move category');
return;
}
const id = event.el.id.substring(9); // category_
const id = self.parseCategoryIdFromDomId(event.el.id);
if (!id) {
self.$toast('Unable to move category');
return;
}
self.$store.dispatch('changeCategoryDisplayOrder', {
categoryId: id,
@@ -294,7 +297,7 @@ export default {
});
},
edit(category) {
this.$f7router.navigate('/category/edit?id=' + category.id);
this.f7router.navigate('/category/edit?id=' + category.id);
},
hide(category, hidden) {
const self = this;
@@ -316,8 +319,6 @@ export default {
},
remove(category, confirm) {
const self = this;
const app = self.$f7;
const $$ = app.$;
if (!category) {
self.$alert('An error has occurred');
@@ -337,9 +338,7 @@ export default {
self.$store.dispatch('deleteCategory', {
category: category,
beforeResolve: (done) => {
app.swipeout.delete($$(`#${self.$options.filters.categoryDomId(category)}`), () => {
done();
});
self.$ui.onSwipeoutDeleted(self.getCategoryDomId(category), done);
}
}).then(() => {
self.$hideLoading();
@@ -350,11 +349,16 @@ export default {
self.$toast(error.message || error);
}
});
}
},
filters: {
categoryDomId(category) {
},
getCategoryDomId(category) {
return 'category_' + category.id;
},
parseCategoryIdFromDomId(domId) {
if (!domId || domId.indexOf('category_') !== 0) {
return null;
}
return domId.substring(9); // category_
}
}
};
+45 -50
View File
@@ -4,44 +4,37 @@
<f7-nav-left :back-link="$t('Back')"></f7-nav-left>
<f7-nav-title :title="$t('Default Categories')"></f7-nav-title>
<f7-nav-right>
<f7-link icon-f7="ellipsis" @click="showMoreActionSheet = true"></f7-link>
<f7-link :text="$t('Save')" :class="{ 'disabled': submitting }" @click="save"></f7-link>
<f7-link icon-f7="ellipsis" v-if="allCategories && allCategories.length" @click="showMoreActionSheet = true"></f7-link>
<f7-link :text="$t('Save')" :class="{ 'disabled': submitting }" v-if="allCategories && allCategories.length" @click="save"></f7-link>
</f7-nav-right>
</f7-navbar>
<f7-card v-for="categoryInfo in allCategories" :key="categoryInfo.type">
<f7-card-header>
<small class="card-header-content">
<span>{{ categoryInfo.type | categoryTypeName($constants.category.allCategoryTypes) | localized }}</span>
</small>
</f7-card-header>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item v-for="(category, idx) in categoryInfo.categories"
:key="idx"
:accordion-item="!!category.subCategories.length"
:title="$t('category.' + category.name, currentLocale)">
<f7-icon slot="media"
:icon="category.categoryIconId | categoryIcon"
:style="category.color | categoryIconStyle('var(--default-icon-color)')">
</f7-icon>
<f7-block class="no-padding no-margin" :key="categoryInfo.type" v-for="categoryInfo in allCategories">
<f7-block-title class="margin-top margin-horizontal">{{ getCategoryTypeName(categoryInfo.type) }}</f7-block-title>
<f7-accordion-content v-if="category.subCategories.length" class="padding-left">
<f7-list>
<f7-list-item v-for="(subCategory, subIdx) in category.subCategories"
:key="subIdx"
:title="$t('category.' + subCategory.name, currentLocale)">
<f7-icon slot="media"
:icon="subCategory.categoryIconId | categoryIcon"
:style="subCategory.color | categoryIconStyle('var(--default-icon-color)')">
</f7-icon>
</f7-list-item>
</f7-list>
</f7-accordion-content>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-top">
<f7-list-item :title="$t('category.' + category.name, currentLocale)"
:accordion-item="!!category.subCategories.length"
:key="idx"
v-for="(category, idx) in categoryInfo.categories">
<template #media>
<ItemIcon icon-type="category" :icon-id="category.categoryIconId" :color="category.color"></ItemIcon>
</template>
<f7-accordion-content v-if="category.subCategories.length" class="padding-left">
<f7-list>
<f7-list-item :title="$t('category.' + subCategory.name, currentLocale)"
:key="subIdx"
v-for="(subCategory, subIdx) in category.subCategories">
<template #media>
<ItemIcon icon-type="category" :icon-id="subCategory.categoryIconId" :color="subCategory.color"></ItemIcon>
</template>
</f7-list-item>
</f7-list>
</f7-accordion-content>
</f7-list-item>
</f7-list>
</f7-block>
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
<f7-actions-group>
@@ -55,7 +48,7 @@
<list-item-selection-sheet value-type="index"
title-field="displayName"
:items="allLanguages"
:show.sync="showChangeLocaleSheet"
v-model:show="showChangeLocaleSheet"
v-model="currentLocale">
</list-item-selection-sheet>
</f7-page>
@@ -63,6 +56,10 @@
<script>
export default {
props: [
'f7route',
'f7router'
],
data() {
const self = this;
@@ -78,12 +75,12 @@ export default {
},
computed: {
allLanguages() {
return this.$locale.getAllLanguages();
return this.$locale.getAllLanguageInfos();
}
},
created() {
const self = this;
const query = self.$f7route.query;
const query = self.f7route.query;
self.categoryType = parseInt(query.type);
@@ -112,7 +109,7 @@ export default {
},
methods: {
onPageAfterIn() {
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
getDefaultCategories(categoryType) {
switch (categoryType) {
@@ -128,7 +125,7 @@ export default {
},
save() {
const self = this;
const router = self.$f7router;
const router = self.f7router;
self.submitting = true;
self.$showLoading(() => self.submitting);
@@ -178,19 +175,17 @@ export default {
self.$toast(error.message || error);
}
});
}
},
filters: {
categoryTypeName(categoryType, allCategoryTypes) {
},
getCategoryTypeName(categoryType) {
switch (categoryType) {
case allCategoryTypes.Income:
return 'Income Categories';
case allCategoryTypes.Expense:
return 'Expense Categories';
case allCategoryTypes.Transfer:
return 'Transfer Categories';
case this.$constants.category.allCategoryTypes.Income:
return this.$t('Income Categories');
case this.$constants.category.allCategoryTypes.Expense:
return this.$t('Expense Categories');
case this.$constants.category.allCategoryTypes.Transfer:
return this.$t('Transfer Categories');
default:
return 'Transaction Categories';
return this.$t('Transaction Categories');
}
}
}
@@ -9,96 +9,93 @@
</f7-nav-right>
</f7-navbar>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-header>
<f7-accordion-toggle class="full-line">
<small class="card-header-content">
<span>Account Category</span>
</small>
<f7-icon class="card-chevron-icon float-right" f7="chevron_up"></f7-icon>
</f7-accordion-toggle>
</f7-card-header>
<f7-block class="combination-list-wrapper margin-vertical skeleton-text"
:key="blockIdx" v-for="blockIdx in [ 1, 2, 3 ]" v-if="loading">
<f7-accordion-item>
<f7-block-title>
<f7-accordion-toggle>
<f7-list strong inset dividers media-list
class="combination-list-header combination-list-opened">
<f7-list-item>
<template #title>
<span>Account Category</span>
<f7-icon class="combination-list-chevron-icon" f7="chevron_up"></f7-icon>
</template>
</f7-list-item>
</f7-list>
</f7-accordion-toggle>
</f7-block-title>
<f7-accordion-content style="height: auto">
<f7-list strong inset dividers accordion-list class="combination-list-content">
<f7-list-item checkbox class="disabled" title="Account Name"
:key="itemIdx" v-for="itemIdx in (blockIdx === 1 ? [ 1 ] : [ 1, 2 ])">
<template #media>
<f7-icon f7="app_fill"></f7-icon>
</template>
</f7-list-item>
</f7-list>
</f7-accordion-content>
</f7-accordion-item>
</f7-block>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item checkbox class="disabled" title="Account Name">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-block class="combination-list-wrapper margin-vertical"
:key="accountCategory.id"
v-for="accountCategory in allAccountCategories"
v-else-if="!loading">
<f7-accordion-item :opened="collapseStates[accountCategory.id].opened"
v-show="hasShownAccount(accountCategory)"
@accordion:open="collapseStates[accountCategory.id].opened = true"
@accordion:close="collapseStates[accountCategory.id].opened = false">
<f7-block-title>
<f7-accordion-toggle>
<f7-list strong inset dividers media-list
class="combination-list-header"
:class="collapseStates[accountCategory.id].opened ? 'combination-list-opened' : 'combination-list-closed'">
<f7-list-item>
<template #title>
<span>{{ $t(accountCategory.name) }}</span>
<f7-icon class="combination-list-chevron-icon" :f7="collapseStates[accountCategory.id].opened ? 'chevron_up' : 'chevron_down'"></f7-icon>
</template>
</f7-list-item>
</f7-list>
</f7-accordion-toggle>
</f7-block-title>
<f7-accordion-content :style="{ height: collapseStates[accountCategory.id].opened ? 'auto' : '' }">
<f7-list strong inset dividers accordion-list class="combination-list-content"
v-if="categorizedAccounts[accountCategory.id]">
<f7-list-item checkbox
:title="account.name"
:value="account.id"
:checked="isAccountOrSubAccountsAllChecked(account, filterAccountIds)"
:indeterminate="isAccountOrSubAccountsHasButNotAllChecked(account, filterAccountIds)"
:key="account.id"
v-for="account in categorizedAccounts[accountCategory.id].accounts"
v-show="!account.hidden"
@change="selectAccountOrSubAccounts">
<template #media>
<ItemIcon icon-type="account" :icon-id="account.icon" :color="account.color"></ItemIcon>
</template>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-header>
<f7-accordion-toggle class="full-line">
<small class="card-header-content">
<span>Account Category 2</span>
</small>
<f7-icon class="card-chevron-icon float-right" f7="chevron_up"></f7-icon>
</f7-accordion-toggle>
</f7-card-header>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item checkbox class="disabled" title="Account Name">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</f7-list-item>
<f7-list-item checkbox class="disabled" title="Account Name 2">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-block class="no-padding no-margin" v-if="!loading">
<f7-card v-for="accountCategory in allAccountCategories" :key="accountCategory.id" v-show="hasShownAccount(accountCategory)">
<f7-accordion-item :opened="collapseStates[accountCategory.id].opened"
@accordion:open="collapseStates[accountCategory.id].opened = true"
@accordion:close="collapseStates[accountCategory.id].opened = false">
<f7-card-header>
<f7-accordion-toggle class="full-line">
<small class="card-header-content">
<span>{{ $t(accountCategory.name) }}</span>
</small>
<f7-icon class="card-chevron-icon float-right" :f7="collapseStates[accountCategory.id].opened ? 'chevron_up' : 'chevron_down'"></f7-icon>
</f7-accordion-toggle>
</f7-card-header>
<f7-card-content class="no-safe-areas" :padding="false" accordion-list>
<f7-accordion-content :style="{ height: collapseStates[accountCategory.id].opened ? 'auto' : '' }">
<f7-list v-if="categorizedAccounts[accountCategory.id]">
<f7-list-item checkbox v-for="account in categorizedAccounts[accountCategory.id].accounts"
v-show="!account.hidden"
:key="account.id"
:title="account.name"
:value="account.id"
:checked="account | accountOrSubAccountsAllChecked(filterAccountIds)"
:indeterminate="account | accountOrSubAccountsHasButNotAllChecked(filterAccountIds)"
@change="selectAccountOrSubAccounts">
<f7-icon slot="media"
:icon="account.icon | accountIcon"
:style="account.color | accountIconStyle('var(--default-icon-color)')">
</f7-icon>
<ul slot="root" v-if="account.type === $constants.account.allAccountTypes.MultiSubAccounts" class="padding-left">
<f7-list-item checkbox v-for="subAccount in account.subAccounts"
v-show="!subAccount.hidden"
:key="subAccount.id"
:title="subAccount.name"
:value="subAccount.id"
:checked="subAccount | accountChecked(filterAccountIds) "
@change="selectAccount">
<f7-icon slot="media"
:icon="subAccount.icon | accountIcon"
:style="subAccount.color | accountIconStyle('var(--default-icon-color)')">
</f7-icon>
</f7-list-item>
</ul>
</f7-list-item>
</f7-list>
</f7-accordion-content>
</f7-card-content>
</f7-accordion-item>
</f7-card>
<template #root>
<ul v-if="account.type === $constants.account.allAccountTypes.MultiSubAccounts" class="padding-left">
<f7-list-item checkbox
:title="subAccount.name"
:value="subAccount.id"
:checked="isAccountChecked(subAccount, filterAccountIds)"
:key="subAccount.id"
v-for="subAccount in account.subAccounts"
v-show="!subAccount.hidden"
@change="selectAccount">
<template #media>
<ItemIcon icon-type="account" :icon-id="subAccount.icon" :color="subAccount.color"></ItemIcon>
</template>
</f7-list-item>
</ul>
</template>
</f7-list-item>
</f7-list>
</f7-accordion-content>
</f7-accordion-item>
</f7-block>
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
@@ -116,6 +113,10 @@
<script>
export default {
props: [
'f7route',
'f7router'
],
data: function () {
const self = this;
@@ -152,7 +153,7 @@ export default {
},
created() {
const self = this;
const query = self.$f7route.query;
const query = self.f7route.query;
self.modifyDefault = !!query.modifyDefault;
@@ -188,11 +189,11 @@ export default {
},
methods: {
onPageAfterIn() {
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
save() {
const self = this;
const router = self.$f7router;
const router = self.f7router;
const filteredAccountIds = {};
@@ -286,6 +287,39 @@ export default {
}
}
},
isAccountChecked(account, filterAccountIds) {
return !filterAccountIds[account.id];
},
isAccountOrSubAccountsAllChecked(account, filterAccountIds) {
if (!account.subAccounts) {
return !filterAccountIds[account.id];
}
for (let i = 0; i < account.subAccounts.length; i++) {
const subAccount = account.subAccounts[i];
if (filterAccountIds[subAccount.id]) {
return false;
}
}
return true;
},
isAccountOrSubAccountsHasButNotAllChecked(account, filterAccountIds) {
if (!account.subAccounts) {
return false;
}
let checkedCount = 0;
for (let i = 0; i < account.subAccounts.length; i++) {
const subAccount = account.subAccounts[i];
if (!filterAccountIds[subAccount.id]) {
checkedCount++;
}
}
return checkedCount > 0 && checkedCount < account.subAccounts.length;
},
hasShownAccount(accountCategory) {
if (!this.categorizedAccounts[accountCategory.id] ||
!this.categorizedAccounts[accountCategory.id].accounts ||
@@ -320,41 +354,6 @@ export default {
return collapseStates;
}
},
filters: {
accountChecked(account, filterAccountIds) {
return !filterAccountIds[account.id];
},
accountOrSubAccountsAllChecked(account, filterAccountIds) {
if (!account.subAccounts) {
return !filterAccountIds[account.id];
}
for (let i = 0; i < account.subAccounts.length; i++) {
const subAccount = account.subAccounts[i];
if (filterAccountIds[subAccount.id]) {
return false;
}
}
return true;
},
accountOrSubAccountsHasButNotAllChecked(account, filterAccountIds) {
if (!account.subAccounts) {
return false;
}
let checkedCount = 0;
for (let i = 0; i < account.subAccounts.length; i++) {
const subAccount = account.subAccounts[i];
if (!filterAccountIds[subAccount.id]) {
checkedCount++;
}
}
return checkedCount > 0 && checkedCount < account.subAccounts.length;
}
}
}
</script>
@@ -9,98 +9,101 @@
</f7-nav-right>
</f7-navbar>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-header>
<f7-accordion-toggle class="full-line">
<small class="card-header-content">
<span>Transaction Category</span>
</small>
<f7-icon class="card-chevron-icon float-right" f7="chevron_up"></f7-icon>
</f7-accordion-toggle>
</f7-card-header>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item checkbox class="disabled" title="Category Name">
<f7-icon slot="media" f7="app_fill"></f7-icon>
<ul slot="root" class="padding-left">
<f7-list-item checkbox class="disabled" title="Sub Category Name">
<f7-icon slot="media" f7="app_fill"></f7-icon>
<f7-block class="combination-list-wrapper margin-vertical skeleton-text"
:key="blockIdx" v-for="blockIdx in [ 1, 2 ]" v-if="loading">
<f7-accordion-item>
<f7-block-title>
<f7-accordion-toggle>
<f7-list strong inset dividers media-list
class="combination-list-header combination-list-opened">
<f7-list-item>
<template #title>
<span>Transaction Category</span>
<f7-icon class="combination-list-chevron-icon" f7="chevron_up"></f7-icon>
</template>
</f7-list-item>
<f7-list-item checkbox class="disabled" title="Sub Category Name 2">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</f7-list-item>
<f7-list-item checkbox class="disabled" title="Sub Category Name 3">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</f7-list-item>
</ul>
</f7-list-item>
<f7-list-item checkbox class="disabled" title="Category Name 2">
<f7-icon slot="media" f7="app_fill"></f7-icon>
<ul slot="root" class="padding-left">
<f7-list-item checkbox class="disabled" title="Sub Category Name">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</f7-list-item>
<f7-list-item checkbox class="disabled" title="Sub Category Name 2">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</f7-list-item>
<f7-list-item checkbox class="disabled" title="Sub Category Name 3">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</f7-list-item>
</ul>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
</f7-list>
</f7-accordion-toggle>
</f7-block-title>
<f7-accordion-content style="height: auto">
<f7-list strong inset dividers accordion-list class="combination-list-content">
<f7-list-item checkbox class="disabled" title="Category Name"
:key="itemIdx" v-for="itemIdx in [ 1, 2 ]">
<template #media>
<f7-icon f7="app_fill"></f7-icon>
</template>
<template #root>
<ul class="padding-left">
<f7-list-item checkbox class="disabled" title="Sub Category Name"
:key="subItemIdx" v-for="subItemIdx in [ 1, 2, 3 ]">
<template #media>
<f7-icon f7="app_fill"></f7-icon>
</template>
</f7-list-item>
</ul>
</template>
</f7-list-item>
</f7-list>
</f7-accordion-content>
</f7-accordion-item>
</f7-block>
<f7-block class="no-padding no-margin" v-if="!loading">
<f7-card v-for="(categories, categoryType) in allTransactionCategories" :key="categoryType">
<f7-accordion-item :opened="collapseStates[categoryType].opened"
@accordion:open="collapseStates[categoryType].opened = true"
@accordion:close="collapseStates[categoryType].opened = false">
<f7-card-header>
<f7-accordion-toggle class="full-line">
<small class="card-header-content">
<span>{{ categoryType | categoryTypeName($constants.category.allCategoryTypes) | localized }}</span>
</small>
<f7-icon class="card-chevron-icon float-right" :f7="collapseStates[categoryType].opened ? 'chevron_up' : 'chevron_down'"></f7-icon>
</f7-accordion-toggle>
</f7-card-header>
<f7-card-content class="no-safe-areas" :padding="false" accordion-list>
<f7-accordion-content :style="{ height: collapseStates[categoryType].opened ? 'auto' : '' }">
<f7-list>
<f7-list-item checkbox v-for="category in categories"
v-show="!category.hidden"
:key="category.id"
:title="category.name"
:value="category.id"
:checked="category | subCategoriesAllChecked(filterCategoryIds)"
:indeterminate="category | subCategoriesHasButNotAllChecked(filterCategoryIds)"
@change="selectSubCategories">
<f7-icon slot="media"
:icon="category.icon | categoryIcon"
:style="category.color | categoryIconStyle('var(--default-icon-color)')">
</f7-icon>
<f7-block class="combination-list-wrapper margin-vertical"
:key="categoryType"
v-for="(categories, categoryType) in allTransactionCategories"
v-else-if="!loading">
<f7-accordion-item :opened="collapseStates[categoryType].opened"
@accordion:open="collapseStates[categoryType].opened = true"
@accordion:close="collapseStates[categoryType].opened = false">
<f7-block-title>
<f7-accordion-toggle>
<f7-list strong inset dividers media-list
class="combination-list-header"
:class="collapseStates[categoryType].opened ? 'combination-list-opened' : 'combination-list-closed'">
<f7-list-item>
<template #title>
<span>{{ getCategoryTypeName(categoryType) }}</span>
<f7-icon class="combination-list-chevron-icon" :f7="collapseStates[categoryType].opened ? 'chevron_up' : 'chevron_down'"></f7-icon>
</template>
</f7-list-item>
</f7-list>
</f7-accordion-toggle>
</f7-block-title>
<f7-accordion-content :style="{ height: collapseStates[categoryType].opened ? 'auto' : '' }">
<f7-list strong inset dividers accordion-list class="combination-list-content">
<f7-list-item checkbox
:title="category.name"
:value="category.id"
:checked="isSubCategoriesAllChecked(category, filterCategoryIds)"
:indeterminate="isSubCategoriesHasButNotAllChecked(category, filterCategoryIds)"
:key="category.id"
v-for="category in categories"
v-show="!category.hidden"
@change="selectSubCategories">
<template #media>
<ItemIcon icon-type="category" :icon-id="category.icon" :color="category.color"></ItemIcon>
</template>
<ul slot="root" v-if="category.subCategories.length" class="padding-left">
<f7-list-item checkbox v-for="subCategory in category.subCategories"
v-show="!subCategory.hidden"
:key="subCategory.id"
:title="subCategory.name"
:value="subCategory.id"
:checked="subCategory | categoryChecked(filterCategoryIds) "
@change="selectCategory">
<f7-icon slot="media"
:icon="subCategory.icon | categoryIcon"
:style="subCategory.color | categoryIconStyle('var(--default-icon-color)')">
</f7-icon>
</f7-list-item>
</ul>
</f7-list-item>
</f7-list>
</f7-accordion-content>
</f7-card-content>
</f7-accordion-item>
</f7-card>
<template #root>
<ul v-if="category.subCategories.length" class="padding-left">
<f7-list-item checkbox
:title="subCategory.name"
:value="subCategory.id"
:checked="isCategoryChecked(subCategory, filterCategoryIds)"
:key="subCategory.id"
v-for="subCategory in category.subCategories"
v-show="!subCategory.hidden"
@change="selectCategory">
<template #media>
<ItemIcon icon-type="category" :icon-id="subCategory.icon" :color="subCategory.color"></ItemIcon>
</template>
</f7-list-item>
</ul>
</template>
</f7-list-item>
</f7-list>
</f7-accordion-content>
</f7-accordion-item>
</f7-block>
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
@@ -118,6 +121,10 @@
<script>
export default {
props: [
'f7route',
'f7router'
],
data: function () {
const self = this;
@@ -151,7 +158,7 @@ export default {
},
created() {
const self = this;
const query = self.$f7route.query;
const query = self.f7route.query;
self.modifyDefault = !!query.modifyDefault;
@@ -187,11 +194,11 @@ export default {
},
methods: {
onPageAfterIn() {
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
save() {
const self = this;
const router = self.$f7router;
const router = self.f7router;
const filteredCategoryIds = {};
@@ -277,6 +284,43 @@ export default {
}
}
},
getCategoryTypeName(categoryType) {
switch (categoryType) {
case this.$constants.category.allCategoryTypes.Income.toString():
return this.$t('Income Categories');
case this.$constants.category.allCategoryTypes.Expense.toString():
return this.$t('Expense Categories');
case this.$constants.category.allCategoryTypes.Transfer.toString():
return this.$t('Transfer Categories');
default:
return this.$t('Transaction Categories');
}
},
isCategoryChecked(category, filterCategoryIds) {
return !filterCategoryIds[category.id];
},
isSubCategoriesAllChecked(category, filterCategoryIds) {
for (let i = 0; i < category.subCategories.length; i++) {
const subCategory = category.subCategories[i];
if (filterCategoryIds[subCategory.id]) {
return false;
}
}
return true;
},
isSubCategoriesHasButNotAllChecked(category, filterCategoryIds) {
let checkedCount = 0;
for (let i = 0; i < category.subCategories.length; i++) {
const subCategory = category.subCategories[i];
if (!filterCategoryIds[subCategory.id]) {
checkedCount++;
}
}
return checkedCount > 0 && checkedCount < category.subCategories.length;
},
getCollapseStates() {
const collapseStates = {};
@@ -294,45 +338,6 @@ export default {
return collapseStates;
}
},
filters: {
categoryTypeName(categoryType, allCategoryTypes) {
switch (categoryType) {
case allCategoryTypes.Income.toString():
return 'Income Categories';
case allCategoryTypes.Expense.toString():
return 'Expense Categories';
case allCategoryTypes.Transfer.toString():
return 'Transfer Categories';
default:
return 'Transaction Categories';
}
},
categoryChecked(category, filterCategoryIds) {
return !filterCategoryIds[category.id];
},
subCategoriesAllChecked(category, filterCategoryIds) {
for (let i = 0; i < category.subCategories.length; i++) {
const subCategory = category.subCategories[i];
if (filterCategoryIds[subCategory.id]) {
return false;
}
}
return true;
},
subCategoriesHasButNotAllChecked(category, filterCategoryIds) {
let checkedCount = 0;
for (let i = 0; i < category.subCategories.length; i++) {
const subCategory = category.subCategories[i];
if (!filterCategoryIds[subCategory.id]) {
checkedCount++;
}
}
return checkedCount > 0 && checkedCount < category.subCategories.length;
}
}
}
</script>
+39 -43
View File
@@ -2,54 +2,50 @@
<f7-page>
<f7-navbar :title="$t('Statistics Settings')" :back-link="$t('Back')"></f7-navbar>
<f7-card>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item
:title="$t('Default Chart Type')"
smart-select :smart-select-params="{ openIn: 'popup', searchbar: true, searchbarPlaceholder: $t('Default Chart Type'), searchbarDisableText: $t('Cancel'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }">
<select v-model="defaultChartType">
<option :value="$constants.statistics.allChartTypes.Pie">{{ $t('Pie Chart') }}</option>
<option :value="$constants.statistics.allChartTypes.Bar">{{ $t('Bar Chart') }}</option>
</select>
</f7-list-item>
<f7-list strong inset dividers class="margin-top">
<f7-list-item
:title="$t('Default Chart Type')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Chart Type'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), popupCloseLinkText: $t('Done') }">
<select v-model="defaultChartType">
<option :value="$constants.statistics.allChartTypes.Pie">{{ $t('Pie Chart') }}</option>
<option :value="$constants.statistics.allChartTypes.Bar">{{ $t('Bar Chart') }}</option>
</select>
</f7-list-item>
<f7-list-item
:title="$t('Default Chart Data Type')"
smart-select :smart-select-params="{ openIn: 'popup', searchbar: true, searchbarPlaceholder: $t('Default Chart Data Type'), searchbarDisableText: $t('Cancel'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }">
<select v-model="defaultChartDataType">
<option v-for="chartDataType in allChartDataTypes"
:key="chartDataType.type"
:value="chartDataType.type">{{ $t(chartDataType.name) }}</option>
</select>
</f7-list-item>
<f7-list-item
:title="$t('Default Chart Data Type')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Chart Data Type'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), popupCloseLinkText: $t('Done') }">
<select v-model="defaultChartDataType">
<option :value="chartDataType.type"
:key="chartDataType.type"
v-for="chartDataType in allChartDataTypes">{{ $t(chartDataType.name) }}</option>
</select>
</f7-list-item>
<f7-list-item
:title="$t('Default Date Range')"
smart-select :smart-select-params="{ openIn: 'popup', searchbar: true, searchbarPlaceholder: $t('Default Date Range'), searchbarDisableText: $t('Cancel'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }">
<select v-model="defaultDateRange">
<option v-for="dateRange in allDateRanges"
:key="dateRange.type"
:value="dateRange.type">{{ $t(dateRange.name) }}</option>
</select>
</f7-list-item>
<f7-list-item
:title="$t('Default Date Range')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Date Range'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), popupCloseLinkText: $t('Done') }">
<select v-model="defaultDateRange">
<option :value="dateRange.type"
:key="dateRange.type"
v-for="dateRange in allDateRanges">{{ $t(dateRange.name) }}</option>
</select>
</f7-list-item>
<f7-list-item :title="$t('Default Account Filter')" link="/statistic/filter/account?modifyDefault=1"></f7-list-item>
<f7-list-item :title="$t('Default Account Filter')" link="/statistic/filter/account?modifyDefault=1"></f7-list-item>
<f7-list-item :title="$t('Default Transaction Category Filter')" link="/statistic/filter/category?modifyDefault=1"></f7-list-item>
<f7-list-item :title="$t('Default Transaction Category Filter')" link="/statistic/filter/category?modifyDefault=1"></f7-list-item>
<f7-list-item
:title="$t('Default Sort By')"
smart-select :smart-select-params="{ openIn: 'popup', searchbar: true, searchbarPlaceholder: $t('Default Sort By'), searchbarDisableText: $t('Cancel'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }">
<select v-model="defaultSortingType">
<option v-for="sortingType in allSortingTypes"
:key="sortingType.type"
:value="sortingType.type">{{ $t(sortingType.name) }}</option>
</select>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list-item
:title="$t('Default Sort By')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Sort By'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), popupCloseLinkText: $t('Done') }">
<select v-model="defaultSortingType">
<option :value="sortingType.type"
:key="sortingType.type"
v-for="sortingType in allSortingTypes">{{ $t(sortingType.name) }}</option>
</select>
</f7-list-item>
</f7-list>
</f7-page>
</template>
+158 -154
View File
@@ -4,7 +4,7 @@
<f7-nav-left :back-link="$t('Back')"></f7-nav-left>
<f7-nav-title>
<f7-link popover-open=".chart-data-type-popover-menu">
<span>{{ query.chartDataType | optionName(allChartDataTypes, 'type', 'name', 'Statistics') | localized }}</span>
<span>{{ queryChartDataTypeName }}</span>
<f7-icon size="14px" :f7="showChartDataTypePopover ? 'arrowtriangle_up_fill' : 'arrowtriangle_down_fill'"></f7-icon>
</f7-link>
</f7-nav-title>
@@ -15,11 +15,14 @@
<f7-popover class="chart-data-type-popover-menu" :opened="showChartDataTypePopover"
@popover:open="showChartDataTypePopover = true" @popover:close="showChartDataTypePopover = false">
<f7-list>
<f7-list-item
v-for="dataType in allChartDataTypes" :key="dataType.type"
:title="$t(dataType.name)" @click="setChartDataType(dataType.type)">
<f7-icon slot="after" class="list-item-checked-icon" f7="checkmark_alt" v-if="query.chartDataType === dataType.type"></f7-icon>
<f7-list dividers>
<f7-list-item :title="$t(dataType.name)"
:key="dataType.type"
v-for="dataType in allChartDataTypes"
@click="setChartDataType(dataType.type)">
<template #after>
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.chartDataType === dataType.type"></f7-icon>
</template>
</f7-list-item>
</f7-list>
</f7-popover>
@@ -28,7 +31,7 @@
<f7-card-header class="no-border display-block">
<div class="statistics-chart-header full-line text-align-right">
<span style="margin-right: 4px;">{{ $t('Sort By') }}</span>
<f7-link href="#" popover-open=".sorting-type-popover-menu">{{ query.sortingType | optionName(allSortingTypes, 'type', 'name', 'System Default') | localized }}</f7-link>
<f7-link href="#" popover-open=".sorting-type-popover-menu">{{ querySortingTypeName }}</f7-link>
</div>
</f7-card-header>
<f7-card-content class="pie-chart-container" style="margin-top: -6px" :padding="false">
@@ -61,10 +64,10 @@
@click="clickPieChartItem"
>
<text class="statistics-pie-chart-total-amount-title" v-if="statisticsData.items && statisticsData.items.length">
{{ query.chartDataType | totalAmountName(allChartDataTypes) | localized }}
{{ totalAmountName }}
</text>
<text class="statistics-pie-chart-total-amount-value" v-if="statisticsData.items && statisticsData.items.length">
{{ statisticsData.totalAmount | currency(defaultCurrency) | finalAmount(showAccountBalance, query.chartDataType, allChartDataTypes) | textLimit(16) }}
{{ getDisplayAmount(statisticsData.totalAmount, defaultCurrency, 16) }}
</text>
<text class="statistics-pie-chart-total-no-data" cy="50%" v-if="!statisticsData.items || !statisticsData.items.length">
{{ $t('No data') }}
@@ -77,128 +80,91 @@
<f7-card-header class="no-border display-block">
<div class="statistics-chart-header display-flex full-line justify-content-space-between">
<div>
{{ query.chartDataType | totalAmountName(allChartDataTypes) | localized }}
{{ totalAmountName }}
</div>
<div class="align-self-flex-end">
<span style="margin-right: 4px;">{{ $t('Sort By') }}</span>
<f7-link href="#" popover-open=".sorting-type-popover-menu">{{ query.sortingType | optionName(allSortingTypes, 'type', 'name', 'System Default') | localized }}</f7-link>
<f7-link href="#" popover-open=".sorting-type-popover-menu">{{ querySortingTypeName }}</f7-link>
</div>
</div>
<div class="display-flex full-line">
<div :class="{ 'statistics-list-item-overview-amount': true, 'text-color-teal': query.chartDataType === allChartDataTypes.ExpenseByAccount.type || query.chartDataType === allChartDataTypes.ExpenseByPrimaryCategory.type || query.chartDataType === allChartDataTypes.ExpenseBySecondaryCategory.type, 'text-color-red': query.chartDataType === allChartDataTypes.IncomeByAccount.type || query.chartDataType === allChartDataTypes.IncomeByPrimaryCategory.type || query.chartDataType === allChartDataTypes.IncomeBySecondaryCategory.type }">
<span v-if="!loading && statisticsData && statisticsData.items && statisticsData.items.length">
{{ statisticsData.totalAmount | currency(defaultCurrency) | finalAmount(showAccountBalance, query.chartDataType, allChartDataTypes) }}
{{ getDisplayAmount(statisticsData.totalAmount, defaultCurrency) }}
</span>
<span v-else-if="loading || !statisticsData || !statisticsData.items || !statisticsData.items.length">
{{ '---' | currency(defaultCurrency, true) }}
<span :class="{ 'skeleton-text': loading }" v-else-if="loading || !statisticsData || !statisticsData.items || !statisticsData.items.length">
***.**
</span>
</div>
</div>
</f7-card-header>
<f7-card-content class="no-safe-areas" style="margin-top: -14px" :padding="false">
<f7-card-content style="margin-top: -14px" :padding="false">
<f7-list class="statistics-list-item skeleton-text" v-if="loading">
<f7-list-item link="#">
<div slot="media" class="display-flex no-padding-horizontal">
<div class="display-flex align-items-center statistics-icon">
<f7-icon slot="media" f7="app_fill"></f7-icon>
<f7-list-item link="#" :key="itemIdx" v-for="itemIdx in [ 1, 2, 3 ]">
<template #media>
<div class="display-flex no-padding-horizontal">
<div class="display-flex align-items-center statistics-icon">
<f7-icon f7="app_fill"></f7-icon>
</div>
</div>
</div>
<div slot="title" class="statistics-list-item-text">
<span>Category Name 1</span>
<small class="statistics-percent">33.33</small>
</div>
<div slot="after">
</template>
<template #title>
<div class="statistics-list-item-text">
<span>Category Name</span>
<small class="statistics-percent">33.33</small>
</div>
</template>
<template #after>
<span>0.00 USD</span>
</div>
<div slot="inner-end" class="statistics-item-end">
<div class="statistics-percent-line">
<f7-progressbar></f7-progressbar>
</template>
<template #inner-end>
<div class="statistics-item-end">
<div class="statistics-percent-line">
<f7-progressbar></f7-progressbar>
</div>
</div>
</div>
</f7-list-item>
<f7-list-item link="#">
<div slot="media" class="display-flex no-padding-horizontal">
<div class="display-flex align-items-center statistics-icon">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</div>
</div>
<div slot="title" class="statistics-list-item-text">
<span>Category Name 2</span>
<small class="statistics-percent">33.33</small>
</div>
<div slot="after">
<span>0.00 USD</span>
</div>
<div slot="inner-end" class="statistics-item-end">
<div class="statistics-percent-line">
<f7-progressbar></f7-progressbar>
</div>
</div>
</f7-list-item>
<f7-list-item link="#">
<div slot="media" class="display-flex no-padding-horizontal">
<div class="display-flex align-items-center statistics-icon">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</div>
</div>
<div slot="title" class="statistics-list-item-text">
<span>Category Name 3</span>
<small class="statistics-percent">33.33</small>
</div>
<div slot="after">
<span>0.00 USD</span>
</div>
<div slot="inner-end" class="statistics-item-end">
<div class="statistics-percent-line">
<f7-progressbar></f7-progressbar>
</div>
</div>
</template>
</f7-list-item>
</f7-list>
<f7-list v-else-if="!loading && (!statisticsData || !statisticsData.items || !statisticsData.items.length)">
<f7-list-item :title="$t('No transaction data')"></f7-list-item>
</f7-list>
<f7-list v-else-if="!loading && statisticsData && statisticsData.items && statisticsData.items.length">
<f7-list-item v-for="(item, idx) in statisticsData.items" :key="idx"
class="statistics-list-item"
:link="item | itemLinkUrl(query, allChartDataTypes)"
<f7-list-item class="statistics-list-item"
:link="getItemLinkUrl(item)"
:key="idx"
v-for="(item, idx) in statisticsData.items"
v-show="!item.hidden"
>
<div slot="media" class="display-flex no-padding-horizontal">
<div class="display-flex align-items-center statistics-icon">
<f7-icon v-if="item.icon"
:icon="item.icon | icon(item.type)"
:style="item.color | iconStyle(item.type, 'var(--category-icon-color)')">
</f7-icon>
<f7-icon v-else-if="!item.icon"
f7="pencil_ellipsis_rectangle">
</f7-icon>
<template #media>
<div class="display-flex no-padding-horizontal">
<div class="display-flex align-items-center statistics-icon">
<ItemIcon icon-type="category" :icon-id="item.icon" :color="item.color" v-if="item.icon"></ItemIcon>
<f7-icon f7="pencil_ellipsis_rectangle" v-else-if="!item.icon"></f7-icon>
</div>
</div>
</div>
</template>
<div slot="title" class="statistics-list-item-text">
<span>{{ item.name }}</span>
<small class="statistics-percent" v-if="item.percent >= 0">{{ item.percent | percent(2, '&lt;0.01') }}</small>
</div>
<div slot="after">
<span>{{ item.totalAmount | currency(item.currency || defaultCurrency) | finalAmount(showAccountBalance, query.chartDataType, allChartDataTypes) }}</span>
</div>
<div slot="inner-end" class="statistics-item-end">
<div class="statistics-percent-line">
<f7-progressbar :progress="item.percent >= 0 ? item.percent : 0" :style="{ '--f7-progressbar-progress-color': (item.color ? '#' + item.color : '') } "></f7-progressbar>
<template #title>
<div class="statistics-list-item-text">
<span>{{ item.name }}</span>
<small class="statistics-percent" v-if="item.percent >= 0">{{ $utilities.formatPercent(item.percent, 2, '&lt;0.01') }}</small>
</div>
</div>
</template>
<template #after>
<span>{{ getDisplayAmount(item.totalAmount, (item.currency || defaultCurrency)) }}</span>
</template>
<template #inner-end>
<div class="statistics-item-end">
<div class="statistics-percent-line">
<f7-progressbar :progress="item.percent >= 0 ? item.percent : 0" :style="{ '--f7-progressbar-progress-color': (item.color ? '#' + item.color : '') } "></f7-progressbar>
</div>
</div>
</template>
</f7-list-item>
</f7-list>
</f7-card-content>
@@ -207,13 +173,15 @@
<f7-popover class="sorting-type-popover-menu" :opened="showSortingTypePopover"
@popover:open="scrollPopoverToSelectedItem"
@popover:opened="showSortingTypePopover = true" @popover:closed="showSortingTypePopover = false">
<f7-list>
<f7-list-item v-for="sortingType in allSortingTypes"
:key="sortingType.type"
<f7-list dividers>
<f7-list-item :title="$t(sortingType.name)"
:class="{ 'list-item-selected': query.sortingType === sortingType.type }"
:title="$t(sortingType.name)"
:key="sortingType.type"
v-for="sortingType in allSortingTypes"
@click="setSortingType(sortingType.type)">
<f7-icon slot="after" class="list-item-checked-icon" f7="checkmark_alt" v-if="query.sortingType === sortingType.type"></f7-icon>
<template #after>
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.sortingType === sortingType.type"></f7-icon>
</template>
</f7-list-item>
</f7-list>
</f7-popover>
@@ -239,28 +207,31 @@
<f7-popover class="date-popover-menu" :opened="showDatePopover"
@popover:open="scrollPopoverToSelectedItem"
@popover:opened="showDatePopover = true" @popover:closed="showDatePopover = false">
<f7-list>
<f7-list-item v-for="dateRange in allDateRanges"
:key="dateRange.type"
<f7-list dividers>
<f7-list-item :title="$t(dateRange.name)"
:class="{ 'list-item-selected': query.dateType === dateRange.type }"
:title="$t(dateRange.name)"
:key="dateRange.type"
v-for="dateRange in allDateRanges"
@click="setDateFilter(dateRange.type)">
<f7-icon slot="after" class="list-item-checked-icon" f7="checkmark_alt" v-if="query.dateType === dateRange.type"></f7-icon>
<div slot="footer"
v-if="dateRange.type === allDateRanges.Custom.type && query.dateType === allDateRanges.Custom.type && query.startTime && query.endTime">
<span>{{ query.startTime | moment($t('format.datetime.long-without-second')) }}</span>
<span>&nbsp;-&nbsp;</span>
<br/>
<span>{{ query.endTime | moment($t('format.datetime.long-without-second')) }}</span>
</div>
<template #after>
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.dateType === dateRange.type"></f7-icon>
</template>
<template #footer>
<div v-if="dateRange.type === allDateRanges.Custom.type && query.dateType === allDateRanges.Custom.type && query.startTime && query.endTime">
<span>{{ $utilities.formatUnixTime(query.startTime, $t('format.datetime.long-without-second')) }}</span>
<span>&nbsp;-&nbsp;</span>
<br/>
<span>{{ $utilities.formatUnixTime(query.endTime, $t('format.datetime.long-without-second')) }}</span>
</div>
</template>
</f7-list-item>
</f7-list>
</f7-popover>
<date-range-selection-sheet :title="$t('Custom Date Range')"
:show.sync="showCustomDateRangeSheet"
:min-time="query.startTime"
:max-time="query.endTime"
v-model:show="showCustomDateRangeSheet"
@dateRange:change="setCustomDateFilter">
</date-range-selection-sheet>
@@ -281,6 +252,9 @@
<script>
export default {
props: [
'f7router'
],
data() {
const self = this;
@@ -305,6 +279,14 @@ export default {
query() {
return this.$store.state.transactionStatisticsFilter;
},
queryChartDataTypeName() {
const queryChartDataTypeName = this.$utilities.getNameByKeyValue(this.allChartDataTypes, this.query.chartDataType, 'type', 'name', 'Statistics');
return this.$t(queryChartDataTypeName);
},
querySortingTypeName() {
const querySortingTypeName = this.$utilities.getNameByKeyValue(this.allSortingTypes, this.query.sortingType, 'type', 'name', 'System Default');
return this.$t(querySortingTypeName);
},
allChartDataTypes() {
return this.$constants.statistics.allChartDataTypes;
},
@@ -314,6 +296,23 @@ export default {
allDateRanges() {
return this.$constants.datetime.allDateRanges;
},
totalAmountName() {
if (this.query.chartDataType === this.allChartDataTypes.IncomeByAccount.type
|| this.query.chartDataType === this.allChartDataTypes.IncomeByPrimaryCategory.type
|| this.query.chartDataType === this.allChartDataTypes.IncomeBySecondaryCategory.type) {
return this.$t('Total Income');
} else if (this.query.chartDataType === this.allChartDataTypes.ExpenseByAccount.type
|| this.query.chartDataType === this.allChartDataTypes.ExpenseByPrimaryCategory.type
|| this.query.chartDataType === this.allChartDataTypes.ExpenseBySecondaryCategory.type) {
return this.$t('Total Expense');
} else if (this.query.chartDataType === this.allChartDataTypes.AccountTotalAssets.type) {
return this.$t('Total Assets');
} else if (this.query.chartDataType === this.allChartDataTypes.AccountTotalLiabilities.type) {
return this.$t('Total Liabilities');
}
return this.$t('Total Amount');
},
statisticsData() {
const self = this;
let combinedData = {
@@ -466,7 +465,7 @@ export default {
this.reload(null);
}
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
reload(done) {
const self = this;
@@ -649,16 +648,16 @@ export default {
return `${displayStartTime} ~ ${displayEndTime}`;
},
clickPieChartItem(item) {
this.$f7router.navigate(this.$options.filters.itemLinkUrl(item, this.query, this.allChartDataTypes));
this.f7router.navigate(this.getItemLinkUrl(item));
},
filterAccounts() {
this.$f7router.navigate('/statistic/filter/account');
this.f7router.navigate('/statistic/filter/account');
},
filterCategories() {
this.$f7router.navigate('/statistic/filter/category');
this.f7router.navigate('/statistic/filter/category');
},
settings() {
this.$f7router.navigate('/statistic/settings');
this.f7router.navigate('/statistic/settings');
},
scrollPopoverToSelectedItem(event) {
if (!event || !event.$el || !event.$el.length) {
@@ -680,48 +679,53 @@ export default {
}
container.scrollTop(targetPos);
}
},
filters: {
finalAmount(amount, isShowAccountBalance, dataType, allChartDataTypes) {
if (!isShowAccountBalance && (dataType === allChartDataTypes.AccountTotalAssets.type || dataType === allChartDataTypes.AccountTotalLiabilities.type)) {
},
getDisplayAmount(amount, currency, textLimit) {
amount = this.$locale.getDisplayCurrency(amount, currency);
if (!this.showAccountBalance
&& (this.query.chartDataType === this.allChartDataTypes.AccountTotalAssets.type
|| this.query.chartDataType === this.allChartDataTypes.AccountTotalLiabilities.type)
) {
return '***';
}
if (textLimit) {
this.$utilities.limitText(amount, textLimit);
}
return amount;
},
totalAmountName(dataType, allChartDataTypes) {
if (dataType === allChartDataTypes.IncomeByAccount.type || dataType === allChartDataTypes.IncomeByPrimaryCategory.type || dataType === allChartDataTypes.IncomeBySecondaryCategory.type) {
return 'Total Income';
} else if (dataType === allChartDataTypes.ExpenseByAccount.type || dataType === allChartDataTypes.ExpenseByPrimaryCategory.type || dataType === allChartDataTypes.ExpenseBySecondaryCategory.type) {
return 'Total Expense';
} else if (dataType === allChartDataTypes.AccountTotalAssets.type) {
return 'Total Assets';
} else if (dataType === allChartDataTypes.AccountTotalLiabilities.type) {
return 'Total Liabilities';
}
return 'Total Amount';
},
itemLinkUrl(item, query, allChartDataTypes) {
getItemLinkUrl(item) {
const querys = [];
if (query.chartDataType === allChartDataTypes.IncomeByAccount.type || query.chartDataType === allChartDataTypes.IncomeByPrimaryCategory.type || query.chartDataType === allChartDataTypes.IncomeBySecondaryCategory.type) {
if (this.query.chartDataType === this.allChartDataTypes.IncomeByAccount.type
|| this.query.chartDataType === this.allChartDataTypes.IncomeByPrimaryCategory.type
|| this.query.chartDataType === this.allChartDataTypes.IncomeBySecondaryCategory.type) {
querys.push('type=2');
} else if (query.chartDataType === allChartDataTypes.ExpenseByAccount.type || query.chartDataType === allChartDataTypes.ExpenseByPrimaryCategory.type || query.chartDataType === allChartDataTypes.ExpenseBySecondaryCategory.type) {
} else if (this.query.chartDataType === this.allChartDataTypes.ExpenseByAccount.type
|| this.query.chartDataType === this.allChartDataTypes.ExpenseByPrimaryCategory.type
|| this.query.chartDataType === this.allChartDataTypes.ExpenseBySecondaryCategory.type) {
querys.push('type=3');
}
if (query.chartDataType === allChartDataTypes.IncomeByAccount.type || query.chartDataType === allChartDataTypes.ExpenseByAccount.type || query.chartDataType === allChartDataTypes.AccountTotalAssets.type || query.chartDataType === allChartDataTypes.AccountTotalLiabilities.type) {
if (this.query.chartDataType === this.allChartDataTypes.IncomeByAccount.type
|| this.query.chartDataType === this.allChartDataTypes.ExpenseByAccount.type
|| this.query.chartDataType === this.allChartDataTypes.AccountTotalAssets.type
|| this.query.chartDataType === this.allChartDataTypes.AccountTotalLiabilities.type) {
querys.push('accountId=' + item.id);
} else if (query.chartDataType === allChartDataTypes.IncomeByPrimaryCategory.type || query.chartDataType === allChartDataTypes.IncomeBySecondaryCategory.type || query.chartDataType === allChartDataTypes.ExpenseByPrimaryCategory.type || query.chartDataType === allChartDataTypes.ExpenseBySecondaryCategory.type) {
} else if (this.query.chartDataType === this.allChartDataTypes.IncomeByPrimaryCategory.type
|| this.query.chartDataType === this.allChartDataTypes.IncomeBySecondaryCategory.type
|| this.query.chartDataType === this.allChartDataTypes.ExpenseByPrimaryCategory.type
|| this.query.chartDataType === this.allChartDataTypes.ExpenseBySecondaryCategory.type) {
querys.push('categoryId=' + item.id);
}
if (query.chartDataType !== allChartDataTypes.AccountTotalAssets.type && query.chartDataType !== allChartDataTypes.AccountTotalLiabilities.type) {
querys.push('dateType=' + query.dateType);
querys.push('minTime=' + query.startTime);
querys.push('maxTime=' + query.endTime);
if (this.query.chartDataType !== this.allChartDataTypes.AccountTotalAssets.type
&& this.query.chartDataType !== this.allChartDataTypes.AccountTotalLiabilities.type) {
querys.push('dateType=' + this.query.dateType);
querys.push('minTime=' + this.query.startTime);
querys.push('maxTime=' + this.query.endTime);
}
return '/transaction/list?' + querys.join('&');
@@ -808,7 +812,7 @@ export default {
--f7-progressbar-bg-color: #f8f8f8;
}
.theme-dark .statistics-percent-line .progressbar {
.dark .statistics-percent-line .progressbar {
--f7-progressbar-bg-color: #161616;
}
</style>
+130 -134
View File
@@ -10,132 +10,115 @@
</f7-nav-right>
</f7-navbar>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item>
<f7-block slot="title" class="no-padding">
<div class="display-flex">
<f7-icon slot="media" f7="number"></f7-icon>
<div class="transaction-tag-list-item-content list-item-valign-middle padding-left-half">Tag Name</div>
</div>
</f7-block>
</f7-list-item>
<f7-list-item>
<f7-block slot="title" class="no-padding">
<div class="display-flex">
<f7-icon slot="media" f7="number"></f7-icon>
<div class="transaction-tag-list-item-content list-item-valign-middle padding-left-half">Tag Name 2</div>
</div>
</f7-block>
</f7-list-item>
<f7-list-item>
<f7-block slot="title" class="no-padding">
<div class="display-flex">
<f7-icon slot="media" f7="number"></f7-icon>
<div class="transaction-tag-list-item-content list-item-valign-middle padding-left-half">Tag Name 3</div>
</div>
</f7-block>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="tag-item-list margin-top skeleton-text" v-if="loading">
<f7-list-item :key="itemIdx" v-for="itemIdx in [ 1, 2, 3 ]">
<template #media>
<f7-icon f7="number"></f7-icon>
</template>
<template #title>
<div class="display-flex">
<div class="transaction-tag-list-item-content list-item-valign-middle padding-left-half">Tag Name</div>
</div>
</template>
</f7-list-item>
</f7-list>
<f7-card v-else-if="!loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list v-if="noAvailableTag && !newTag">
<f7-list-item :title="$t('No available tag')"></f7-list-item>
</f7-list>
<f7-list strong inset dividers class="tag-item-list margin-top" v-if="!loading && noAvailableTag && !newTag">
<f7-list-item :title="$t('No available tag')"></f7-list-item>
</f7-list>
<f7-list sortable :sortable-enabled="sortable" @sortable:sort="onSort">
<f7-list-item v-for="tag in tags"
:key="tag.id"
:id="tag | tagDomId"
v-show="showHidden || !tag.hidden"
swipeout @taphold.native="setSortable()">
<f7-block slot="title" class="no-padding">
<div class="display-flex">
<f7-icon slot="media" f7="number">
<f7-badge color="gray" class="right-bottom-icon" v-if="tag.hidden">
<f7-icon f7="eye_slash_fill"></f7-icon>
</f7-badge>
</f7-icon>
<f7-list strong inset dividers sortable class="tag-item-list margin-top"
:sortable-enabled="sortable" @sortable:sort="onSort"
v-if="!loading">
<div class="transaction-tag-list-item-content list-item-valign-middle padding-left-half"
v-if="editingTag.id !== tag.id">
{{ tag.name }}
</div>
<f7-input class="list-title-input padding-left-half"
type="text"
:placeholder="$t('Tag Title')"
:value="editingTag.name"
v-else-if="editingTag.id === tag.id"
@input="editingTag.name = $event.target.value"
@keyup.enter.native="save(tag)">
</f7-input>
</div>
</f7-block>
<f7-button slot="after"
:class="{ 'no-padding': true, 'disabled': !isTagModified(tag) }"
raised fill
icon-f7="checkmark_alt"
color="blue"
v-if="editingTag.id === tag.id"
@click="save(editingTag)">
</f7-button>
<f7-button slot="after"
class="no-padding margin-left-half"
raised fill
icon-f7="xmark"
color="gray"
v-if="editingTag.id === tag.id"
@click="cancelSave(editingTag)">
</f7-button>
<f7-swipeout-actions left v-if="sortable && editingTag.id !== tag.id">
<f7-swipeout-button :color="tag.hidden ? 'blue' : 'gray'" class="padding-left padding-right"
overswipe close @click="hide(tag, !tag.hidden)">
<f7-icon :f7="tag.hidden ? 'eye' : 'eye_slash'"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
<f7-swipeout-actions right v-if="!sortable && editingTag.id !== tag.id">
<f7-swipeout-button color="orange" close :text="$t('Edit')" @click="edit(tag)"></f7-swipeout-button>
<f7-swipeout-button color="red" class="padding-left padding-right" @click="remove(tag, false)">
<f7-icon f7="trash"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
</f7-list-item>
<f7-list-item swipeout
:id="getTagDomId(tag)"
:key="tag.id"
v-for="tag in tags"
v-show="showHidden || !tag.hidden"
@taphold="setSortable()">
<template #media>
<f7-icon f7="number">
<f7-badge color="gray" class="right-bottom-icon" v-if="tag.hidden">
<f7-icon f7="eye_slash_fill"></f7-icon>
</f7-badge>
</f7-icon>
</template>
<template #title>
<div class="display-flex">
<div class="transaction-tag-list-item-content list-item-valign-middle padding-left-half"
v-if="editingTag.id !== tag.id">
{{ tag.name }}
</div>
<f7-input class="list-title-input padding-left-half"
type="text"
:placeholder="$t('Tag Title')"
v-else-if="editingTag.id === tag.id"
v-model:value="editingTag.name"
@keyup.enter="save(tag)">
</f7-input>
</div>
</template>
<template #after>
<f7-button :class="{ 'no-padding': true, 'disabled': !isTagModified(tag) }"
raised fill
icon-f7="checkmark_alt"
color="blue"
v-if="editingTag.id === tag.id"
@click="save(editingTag)">
</f7-button>
<f7-button class="no-padding margin-left-half"
raised fill
icon-f7="xmark"
color="gray"
v-if="editingTag.id === tag.id"
@click="cancelSave(editingTag)">
</f7-button>
</template>
<f7-swipeout-actions left v-if="sortable && editingTag.id !== tag.id">
<f7-swipeout-button :color="tag.hidden ? 'blue' : 'gray'" class="padding-left padding-right"
overswipe close @click="hide(tag, !tag.hidden)">
<f7-icon :f7="tag.hidden ? 'eye' : 'eye_slash'"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
<f7-swipeout-actions right v-if="!sortable && editingTag.id !== tag.id">
<f7-swipeout-button color="orange" close :text="$t('Edit')" @click="edit(tag)"></f7-swipeout-button>
<f7-swipeout-button color="red" class="padding-left padding-right" @click="remove(tag, false)">
<f7-icon f7="trash"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
</f7-list-item>
<f7-list-item v-if="newTag">
<f7-block slot="title" class="no-padding">
<div class="display-flex">
<f7-icon slot="media" f7="number"></f7-icon>
<f7-input class="list-title-input padding-left-half"
type="text"
:placeholder="$t('Tag Title')"
:value="newTag.name"
@input="newTag.name = $event.target.value"
@keyup.enter.native="save(newTag)">
</f7-input>
</div>
</f7-block>
<f7-button slot="after"
:class="{ 'no-padding': true, 'disabled': !isTagModified(newTag) }"
raised fill
icon-f7="checkmark_alt"
color="blue"
@click="save(newTag)">
</f7-button>
<f7-button slot="after"
class="no-padding margin-left-half"
raised fill
icon-f7="xmark"
color="gray"
@click="cancelSave(newTag)">
</f7-button>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list-item v-if="newTag">
<template #media>
<f7-icon f7="number"></f7-icon>
</template>
<template #title>
<div class="display-flex">
<f7-input class="list-title-input padding-left-half"
type="text"
:placeholder="$t('Tag Title')"
v-model:value="newTag.name"
@keyup.enter="save(newTag)">
</f7-input>
</div>
</template>
<template #after>
<f7-button :class="{ 'no-padding': true, 'disabled': !isTagModified(newTag) }"
raised fill
icon-f7="checkmark_alt"
color="blue"
@click="save(newTag)">
</f7-button>
<f7-button class="no-padding margin-left-half"
raised fill
icon-f7="xmark"
color="gray"
@click="cancelSave(newTag)">
</f7-button>
</template>
</f7-list-item>
</f7-list>
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
<f7-actions-group>
@@ -160,6 +143,9 @@
<script>
export default {
props: [
'f7router'
],
data() {
return {
newTag: null,
@@ -216,7 +202,7 @@ export default {
},
methods: {
onPageAfterIn() {
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
reload(done) {
if (this.sortable || this.hasEditingTag) {
@@ -254,12 +240,17 @@ export default {
onSort(event) {
const self = this;
if (!event || !event.el || !event.el.id || event.el.id.indexOf('tag_') !== 0) {
if (!event || !event.el || !event.el.id) {
self.$toast('Unable to move tag');
return;
}
const id = event.el.id.substring(4); // tag_
const id = self.parseTagIdFromDomId(event.el.id);
if (!id) {
self.$toast('Unable to move tag');
return;
}
self.$store.dispatch('changeTagDisplayOrder', {
tagId: id,
@@ -367,8 +358,6 @@ export default {
},
remove(tag, confirm) {
const self = this;
const app = self.$f7;
const $$ = app.$;
if (!tag) {
self.$alert('An error has occurred');
@@ -388,9 +377,7 @@ export default {
self.$store.dispatch('deleteTag', {
tag: tag,
beforeResolve: (done) => {
app.swipeout.delete($$(`#${self.$options.filters.tagDomId(tag)}`), () => {
done();
});
self.$ui.onSwipeoutDeleted(self.getTagDomId(tag), done);
}
}).then(() => {
self.$hideLoading();
@@ -401,17 +388,26 @@ export default {
self.$toast(error.message || error);
}
});
}
},
filters: {
tagDomId(category) {
},
getTagDomId(category) {
return 'tag_' + category.id;
},
parseTagIdFromDomId(domId) {
if (!domId || domId.indexOf('tag_') !== 0) {
return null;
}
return domId.substring(4); // tag_
}
}
}
</script>
<style>
.tag-item-list.list .item-media + .item-inner {
margin-left: 5px;
}
.transaction-tag-list-item-content {
overflow: hidden;
text-overflow: ellipsis;
+261 -287
View File
@@ -1,5 +1,5 @@
<template>
<f7-page @page:afterin="onPageAfterIn">
<f7-page with-subnavbar @page:afterin="onPageAfterIn">
<f7-navbar>
<f7-nav-left :back-link="$t('Back')"></f7-nav-left>
<f7-nav-title :title="$t(title)"></f7-nav-title>
@@ -17,255 +17,255 @@
</f7-subnavbar>
</f7-navbar>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item
class="transaction-edit-amount"
style="font-size: 40px;"
header="Expense Amount" title="0.00">
</f7-list-item>
<f7-list-item :no-chevron="mode === 'view'" class="list-item-with-header-and-title list-item-title-hide-overflow" header="Category" title="Category Names" link="#"></f7-list-item>
<f7-list-item :no-chevron="mode === 'view'" class="list-item-with-header-and-title" header="Account" title="Account Name" link="#"></f7-list-item>
<f7-list-input label="Transaction Time" placeholder="YYYY/MM/DD HH:mm"></f7-list-input>
<f7-list-item :no-chevron="mode === 'view'" class="list-item-with-header-and-title list-item-title-hide-overflow" header="Transaction Time Zone" title="(UTC XX:XX) System Default" link="#"></f7-list-item>
<f7-list-item :no-chevron="mode === 'view'" header="Tags" link="#">
<f7-block class="margin-top-half no-padding" slot="footer">
<f7-chip class="transaction-edit-tag" text="None"></f7-chip>
</f7-block>
</f7-list-item>
<f7-list-input type="textarea" label="Description" placeholder="Your transaction description (optional)"></f7-list-input>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-vertical skeleton-text" v-if="loading">
<f7-list-item
class="transaction-edit-amount"
style="font-size: 40px;"
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"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title" header="Account" title="Account Name"></f7-list-item>
<f7-list-input label="Transaction Time" placeholder="YYYY/MM/DD HH:mm"></f7-list-input>
<f7-list-item :no-chevron="mode === 'view'" class="list-item-with-header-and-title list-item-title-hide-overflow" header="Transaction Time Zone" title="(UTC XX:XX) System Default" link="#"></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-card v-else-if="!loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list form :class="{ 'readonly': mode === 'view' }">
<f7-list-item
class="transaction-edit-amount"
link="#" no-chevron
:class="{ 'color-theme-teal': transaction.type === $constants.transaction.allTransactionTypes.Expense, 'color-theme-red': transaction.type === $constants.transaction.allTransactionTypes.Income }"
:style="{ fontSize: sourceAmountFontSize + 'px' }"
:header="$t(sourceAmountName)"
:title="transaction.sourceAmount | finalAmount(transaction.hideAmount) | currency"
@click="showSourceAmountSheet = true"
>
<number-pad-sheet :min-value="$constants.transaction.minAmount"
:max-value="$constants.transaction.maxAmount"
:show.sync="showSourceAmountSheet"
v-model="transaction.sourceAmount"
></number-pad-sheet>
</f7-list-item>
<f7-list form strong inset dividers
class="margin-vertical" :class="{ 'readonly': mode === 'view' }"
v-else-if="!loading">
<f7-list-item
class="transaction-edit-amount"
link="#" no-chevron
:class="{ 'color-teal': transaction.type === $constants.transaction.allTransactionTypes.Expense, 'color-red': transaction.type === $constants.transaction.allTransactionTypes.Income }"
:style="{ fontSize: sourceAmountFontSize + 'px' }"
:header="$t(sourceAmountName)"
:title="getDisplayAmount(transaction.sourceAmount, transaction.hideAmount)"
@click="showSourceAmountSheet = true"
>
<number-pad-sheet :min-value="$constants.transaction.minAmount"
:max-value="$constants.transaction.maxAmount"
v-model:show="showSourceAmountSheet"
v-model="transaction.sourceAmount"
></number-pad-sheet>
</f7-list-item>
<f7-list-item
class="transaction-edit-amount"
link="#" no-chevron
:style="{ fontSize: destinationAmountFontSize + 'px' }"
:header="$t('Transfer In Amount')"
:title="transaction.destinationAmount | finalAmount(transaction.hideAmount) | currency"
@click="showDestinationAmountSheet = true"
v-if="transaction.type === $constants.transaction.allTransactionTypes.Transfer"
>
<number-pad-sheet :min-value="$constants.transaction.minAmount"
:max-value="$constants.transaction.maxAmount"
:show.sync="showDestinationAmountSheet"
v-model="transaction.destinationAmount"
></number-pad-sheet>
</f7-list-item>
<f7-list-item
class="transaction-edit-amount"
link="#" no-chevron
:style="{ fontSize: destinationAmountFontSize + 'px' }"
:header="$t('Transfer In Amount')"
:title="getDisplayAmount(transaction.destinationAmount, transaction.hideAmount)"
@click="showDestinationAmountSheet = true"
v-if="transaction.type === $constants.transaction.allTransactionTypes.Transfer"
>
<number-pad-sheet :min-value="$constants.transaction.minAmount"
:max-value="$constants.transaction.maxAmount"
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="mode === 'view'"
:class="{ 'disabled': !hasAvailableExpenseCategories }"
:header="$t('Category')"
@click="showCategorySheet = true"
v-if="transaction.type === $constants.transaction.allTransactionTypes.Expense"
>
<div slot="title" class="list-item-custom-title">
<span>{{ transaction.expenseCategory | primaryCategoryName(allCategories[$constants.category.allCategoryTypes.Expense]) }}</span>
<f7-icon class="category-separate-icon" f7="chevron_right"></f7-icon>
<span>{{ transaction.expenseCategory | secondaryCategoryName(allCategories[$constants.category.allCategoryTypes.Expense]) }}</span>
</div>
<tree-view-selection-sheet primary-key-field="id" primary-value-field="id" primary-title-field="name"
primary-icon-field="icon" primary-icon-type="category" primary-color-field="color"
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"
:items="allCategories[$constants.category.allCategoryTypes.Expense]"
:show.sync="showCategorySheet"
v-model="transaction.expenseCategory">
</tree-view-selection-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': !hasAvailableExpenseCategories }"
:header="$t('Category')"
@click="showCategorySheet = true"
v-if="transaction.type === $constants.transaction.allTransactionTypes.Expense"
>
<template #title>
<div class="list-item-custom-title">
<span>{{ getPrimaryCategoryName(transaction.expenseCategory, allCategories[$constants.category.allCategoryTypes.Expense]) }}</span>
<f7-icon class="category-separate-icon" f7="chevron_right"></f7-icon>
<span>{{ getSecondaryCategoryName(transaction.expenseCategory, allCategories[$constants.category.allCategoryTypes.Expense]) }}</span>
</div>
</template>
<tree-view-selection-sheet primary-key-field="id" primary-value-field="id" primary-title-field="name"
primary-icon-field="icon" primary-icon-type="category" primary-color-field="color"
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"
:items="allCategories[$constants.category.allCategoryTypes.Expense]"
v-model:show="showCategorySheet"
v-model="transaction.expenseCategory">
</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="mode === 'view'"
:class="{ 'disabled': !hasAvailableIncomeCategories }"
:header="$t('Category')"
@click="showCategorySheet = true"
v-if="transaction.type === $constants.transaction.allTransactionTypes.Income"
>
<div slot="title" class="list-item-custom-title">
<span>{{ transaction.incomeCategory | primaryCategoryName(allCategories[$constants.category.allCategoryTypes.Income]) }}</span>
<f7-icon class="category-separate-icon" f7="chevron_right"></f7-icon>
<span>{{ transaction.incomeCategory | secondaryCategoryName(allCategories[$constants.category.allCategoryTypes.Income]) }}</span>
</div>
<tree-view-selection-sheet primary-key-field="id" primary-value-field="id" primary-title-field="name"
primary-icon-field="icon" primary-icon-type="category" primary-color-field="color"
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"
:items="allCategories[$constants.category.allCategoryTypes.Income]"
:show.sync="showCategorySheet"
v-model="transaction.incomeCategory">
</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': !hasAvailableIncomeCategories }"
:header="$t('Category')"
@click="showCategorySheet = true"
v-if="transaction.type === $constants.transaction.allTransactionTypes.Income"
>
<template #title>
<div class="list-item-custom-title">
<span>{{ getPrimaryCategoryName(transaction.incomeCategory, allCategories[$constants.category.allCategoryTypes.Income]) }}</span>
<f7-icon class="category-separate-icon" f7="chevron_right"></f7-icon>
<span>{{ getSecondaryCategoryName(transaction.incomeCategory, allCategories[$constants.category.allCategoryTypes.Income]) }}</span>
</div>
</template>
<tree-view-selection-sheet primary-key-field="id" primary-value-field="id" primary-title-field="name"
primary-icon-field="icon" primary-icon-type="category" primary-color-field="color"
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"
:items="allCategories[$constants.category.allCategoryTypes.Income]"
v-model:show="showCategorySheet"
v-model="transaction.incomeCategory">
</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="mode === 'view'"
:class="{ 'disabled': !hasAvailableTransferCategories }"
:header="$t('Category')"
@click="showCategorySheet = true"
v-if="transaction.type === $constants.transaction.allTransactionTypes.Transfer"
>
<div slot="title" class="list-item-custom-title">
<span>{{ transaction.transferCategory | primaryCategoryName(allCategories[$constants.category.allCategoryTypes.Transfer]) }}</span>
<f7-icon class="category-separate-icon" f7="chevron_right"></f7-icon>
<span>{{ transaction.transferCategory | secondaryCategoryName(allCategories[$constants.category.allCategoryTypes.Transfer]) }}</span>
</div>
<tree-view-selection-sheet primary-key-field="id" primary-value-field="id" primary-title-field="name"
primary-icon-field="icon" primary-icon-type="category" primary-color-field="color"
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"
:items="allCategories[$constants.category.allCategoryTypes.Transfer]"
:show.sync="showCategorySheet"
v-model="transaction.transferCategory">
</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': !hasAvailableTransferCategories }"
:header="$t('Category')"
@click="showCategorySheet = true"
v-if="transaction.type === $constants.transaction.allTransactionTypes.Transfer"
>
<template #title>
<div class="list-item-custom-title">
<span>{{ getPrimaryCategoryName(transaction.transferCategory, allCategories[$constants.category.allCategoryTypes.Transfer]) }}</span>
<f7-icon class="category-separate-icon" f7="chevron_right"></f7-icon>
<span>{{ getSecondaryCategoryName(transaction.transferCategory, allCategories[$constants.category.allCategoryTypes.Transfer]) }}</span>
</div>
</template>
<tree-view-selection-sheet primary-key-field="id" primary-value-field="id" primary-title-field="name"
primary-icon-field="icon" primary-icon-type="category" primary-color-field="color"
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"
:items="allCategories[$constants.category.allCategoryTypes.Transfer]"
v-model:show="showCategorySheet"
v-model="transaction.transferCategory">
</tree-view-selection-sheet>
</f7-list-item>
<f7-list-item
class="list-item-with-header-and-title"
link="#" :no-chevron="mode === 'view'"
:class="{ 'disabled': !allVisibleAccounts.length }"
:header="$t(sourceAccountName)"
:title="transaction.sourceAccountId | optionName(allAccounts, 'id', 'name')"
@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"
:items="categorizedAccounts"
:show.sync="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 }"
:header="$t(sourceAccountName)"
:title="$utilities.getNameByKeyValue(allAccounts, transaction.sourceAccountId, 'id', 'name')"
@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"
:items="categorizedAccounts"
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="mode === 'view'"
:class="{ 'disabled': !allVisibleAccounts.length }"
:header="$t('Destination Account')"
:title="transaction.destinationAccountId | optionName(allAccounts, 'id', 'name')"
v-if="transaction.type === $constants.transaction.allTransactionTypes.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"
:items="categorizedAccounts"
:show.sync="showDestinationAccountSheet"
v-model="transaction.destinationAccountId">
</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 }"
:header="$t('Destination Account')"
:title="$utilities.getNameByKeyValue(allAccounts, transaction.destinationAccountId, 'id', 'name')"
v-if="transaction.type === $constants.transaction.allTransactionTypes.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"
:items="categorizedAccounts"
v-model:show="showDestinationAccountSheet"
v-model="transaction.destinationAccountId">
</two-column-list-item-selection-sheet>
</f7-list-item>
<f7-list-input
:label="$t('Transaction Time')"
type="datepicker"
class="transaction-edit-time"
:calendar-params="{
timePicker: true,
dateFormat: $t('input-format.datetime.long'),
firstDay: defaultFirstDayOfWeek,
toolbarCloseText: $t('Done'),
timePickerPlaceholder: $t('Select Time'),
timePickerFormat: $locale.getInputTimeIntlDateTimeFormatOptions(),
monthNames: $locale.getAllLongMonthNames(),
monthNamesShort: $locale.getAllShortMonthNames(),
dayNames: $locale.getAllLongWeekdayNames(),
dayNamesShort: $locale.getAllShortWeekdayNames()}"
:value="transaction.time"
@calendar:change="transaction.time = $event"
>
</f7-list-input>
<f7-list-item
class="list-item-with-header-and-title"
link="#" no-chevron
:header="$t('Transaction Time')"
:title="$utilities.formatUnixTime($utilities.getActualUnixTimeForStore(transaction.time, $utilities.getTimezoneOffsetMinutes(), $utilities.getBrowserTimezoneOffsetMinutes()), this.$t('format.datetime.long'))"
@click="showTransactionDateTimeSheet = true"
>
<date-time-selection-sheet v-model:show="showTransactionDateTimeSheet"
v-model="transaction.time">
</date-time-selection-sheet>
</f7-list-item>
<f7-list-item
:no-chevron="mode === 'view'"
class="list-item-with-header-and-title list-item-title-hide-overflow list-item-no-item-after"
:header="$t('Transaction Time Zone')"
smart-select :smart-select-params="{ openIn: 'popup', pageTitle: $t('Transaction Time Zone'), searchbar: true, searchbarPlaceholder: $t('Timezone'), searchbarDisableText: $t('Cancel'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }">
<select v-model="transaction.timeZone">
<option v-for="timezone in allTimezones"
:key="timezone.name"
:value="timezone.name">{{ `(UTC${timezone.utcOffset}) ${timezone.displayName}` }}</option>
</select>
<f7-block slot="title" class="list-item-custom-title no-padding">
<span>{{ transaction.utcOffset | utcOffset }}</span>
<span class="transaction-edit-timezone-name" v-if="transaction.timeZone || transaction.timeZone === ''">{{ transaction.timeZone | optionName(allTimezones, 'name', 'displayName') }}</span>
</f7-block>
</f7-list-item>
<f7-list-item
:no-chevron="mode === 'view'"
class="list-item-with-header-and-title list-item-title-hide-overflow list-item-no-item-after"
:header="$t('Transaction Time Zone')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Timezone'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Transaction Time Zone'), popupCloseLinkText: $t('Done') }">
<select v-model="transaction.timeZone">
<option :value="timezone.name"
:key="timezone.name"
v-for="timezone in allTimezones">{{ `(UTC${timezone.utcOffset}) ${timezone.displayName}` }}</option>
</select>
<template #title>
<f7-block class="list-item-custom-title no-padding no-margin">
<span>{{ `(UTC${$utilities.getUtcOffsetByUtcOffsetMinutes(transaction.utcOffset)})` }}</span>
<span class="transaction-edit-timezone-name" v-if="transaction.timeZone || transaction.timeZone === ''">{{ $utilities.getNameByKeyValue(allTimezones, transaction.timeZone, 'name', 'displayName') }}</span>
</f7-block>
</template>
</f7-list-item>
<f7-list-item
link="#" :no-chevron="mode === 'view'"
:header="$t('Tags')"
@click="showTransactionTagSheet = true"
>
<transaction-tag-selection-sheet :items="allTags"
:show.sync="showTransactionTagSheet"
v-model="transaction.tagIds">
</transaction-tag-selection-sheet>
<f7-list-item
link="#" no-chevron
:header="$t('Tags')"
@click="showTransactionTagSheet = true"
>
<transaction-tag-selection-sheet :items="allTags"
v-model:show="showTransactionTagSheet"
v-model="transaction.tagIds">
</transaction-tag-selection-sheet>
<f7-block class="margin-top-half no-padding" slot="footer" v-if="transaction.tagIds && transaction.tagIds.length">
<f7-chip class="transaction-edit-tag" media-bg-color="black"
v-for="tagId in transaction.tagIds"
:key="tagId"
:text="tagId | optionName(allTags, 'id', 'name')">
<f7-icon slot="media" f7="number"></f7-icon>
</f7-chip>
</f7-block>
<f7-block class="margin-top-half no-padding" slot="footer" v-else-if="!transaction.tagIds || !transaction.tagIds.length">
<f7-chip class="transaction-edit-tag" :text="$t('None')">
</f7-chip>
</f7-block>
</f7-list-item>
<template #footer>
<f7-block class="margin-top-half no-padding no-margin" v-if="transaction.tagIds && transaction.tagIds.length">
<f7-chip media-bg-color="black" class="transaction-edit-tag"
:text="$utilities.getNameByKeyValue(allTags, tagId, 'id', '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="$t('None')">
</f7-chip>
</f7-block>
</template>
</f7-list-item>
<f7-list-input
type="textarea"
class="transaction-edit-comment textarea-auto-size"
style="height: auto"
:label="$t('Description')"
:placeholder="mode !== 'view' ? $t('Your transaction description (optional)') : ''"
:value="transaction.comment"
@input="transaction.comment = $event.target.value"
></f7-list-input>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list-input
type="textarea"
class="transaction-edit-comment"
style="height: auto"
:label="$t('Description')"
:placeholder="mode !== 'view' ? $t('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="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
<f7-actions-group>
@@ -287,9 +287,13 @@
<script>
export default {
props: [
'f7route',
'f7router'
],
data() {
const self = this;
const query = self.$f7route.query;
const query = self.f7route.query;
const now = self.$utilities.getCurrentUnixTime();
const currentTimezone = self.$locale.getTimezone();
@@ -306,8 +310,7 @@ export default {
editTransactionId: null,
transaction: {
type: defaultType,
unixTime: now,
time: self.$utilities.getLocalDatetimeFromUnixTime(now),
time: now,
timeZone: currentTimezone,
utcOffset: self.$utilities.getTimezoneOffsetMinutes(currentTimezone),
expenseCategory: '',
@@ -331,6 +334,7 @@ export default {
showCategorySheet: false,
showSourceAccountSheet: false,
showDestinationAccountSheet: false,
showTransactionDateTimeSheet: false,
showTransactionTagSheet: false
};
},
@@ -407,9 +411,9 @@ export default {
const account = accountCategory.accounts[i];
if (this.showAccountBalance && account.isAsset) {
account.displayBalance = this.$options.filters.currency(account.balance, account.currency);
account.displayBalance = this.$locale.getDisplayCurrency(account.balance, account.currency);
} else if (this.showAccountBalance && account.isLiability) {
account.displayBalance = this.$options.filters.currency(-account.balance, account.currency);
account.displayBalance = this.$locale.getDisplayCurrency(-account.balance, account.currency);
} else {
account.displayBalance = '***';
}
@@ -448,7 +452,7 @@ export default {
totalBalance = totalBalance + '+';
}
accountCategory.displayBalance = this.$options.filters.currency(totalBalance, this.defaultCurrency);
accountCategory.displayBalance = this.$locale.getDisplayCurrency(totalBalance, this.defaultCurrency);
} else {
accountCategory.displayBalance = '***';
}
@@ -543,20 +547,6 @@ export default {
this.transaction.sourceAmount = newValue;
}
},
'transaction.time': function (newValue) {
if (this.$utilities.isArray(newValue)) {
newValue = newValue[0];
}
if (!newValue) {
newValue = this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getCurrentUnixTime());
this.transaction.time = [newValue];
}
if (this.$utilities.getUnixTimeFromLocalDatetime(newValue) !== this.transaction.unixTime) {
this.transaction.unixTime = this.$utilities.getUnixTimeFromLocalDatetime(newValue);
}
},
'transaction.timeZone': function (newValue) {
for (let i = 0; i < this.allTimezones.length; i++) {
if (this.allTimezones[i].name === newValue) {
@@ -568,11 +558,11 @@ export default {
},
created() {
const self = this;
const query = self.$f7route.query;
const query = self.f7route.query;
if (self.$f7route.path === '/transaction/edit') {
if (self.f7route.path === '/transaction/edit') {
self.mode = 'edit';
} else if (self.$f7route.path === '/transaction/detail') {
} else if (self.f7route.path === '/transaction/detail') {
self.mode = 'view';
}
@@ -695,8 +685,7 @@ export default {
if (self.mode === 'edit' || self.mode === 'view') {
self.transaction.utcOffset = transaction.utcOffset;
self.transaction.timeZone = null;
self.transaction.unixTime = self.$utilities.getDummyUnixTimeForLocalUsage(transaction.time, self.transaction.utcOffset, self.$utilities.getBrowserTimezoneOffsetMinutes());
self.transaction.time = [self.$utilities.getLocalDatetimeFromUnixTime(self.transaction.unixTime)];
self.transaction.time = self.$utilities.getDummyUnixTimeForLocalUsage(transaction.time, self.transaction.utcOffset, self.$utilities.getBrowserTimezoneOffsetMinutes());
}
self.transaction.sourceAccountId = transaction.sourceAccountId;
@@ -728,16 +717,13 @@ export default {
}
});
},
updated: function () {
this.autoChangeCommentTextareaSize();
},
methods: {
onPageAfterIn() {
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
save() {
const self = this;
const router = self.$f7router;
const router = self.f7router;
if (self.mode === 'view') {
return;
@@ -745,7 +731,7 @@ export default {
const submitTransaction = {
type: self.transaction.type,
time: self.$utilities.getActualUnixTimeForStore(self.transaction.unixTime, self.transaction.utcOffset, self.$utilities.getBrowserTimezoneOffsetMinutes()),
time: self.$utilities.getActualUnixTimeForStore(self.transaction.time, self.transaction.utcOffset, self.$utilities.getBrowserTimezoneOffsetMinutes()),
sourceAccountId: self.transaction.sourceAccountId,
sourceAmount: self.transaction.sourceAmount,
destinationAccountId: '0',
@@ -809,16 +795,6 @@ export default {
doSubmit();
}
},
autoChangeCommentTextareaSize() {
const app = this.$f7;
const $$ = app.$;
$$('.textarea-auto-size textarea').each((idx, el) => {
el.scrollTop = 0;
el.style.height = '';
el.style.height = el.scrollHeight + 'px';
});
},
isCategoryIdAvailable(categories, categoryId) {
if (!categories || !categories.length) {
return false;
@@ -853,17 +829,15 @@ export default {
} else {
return 40;
}
}
},
filters: {
finalAmount(amount, hideAmount) {
},
getDisplayAmount(amount, hideAmount) {
if (hideAmount) {
return '***';
return this.$locale.getDisplayCurrency('***');
}
return amount;
return this.$locale.getDisplayCurrency(amount);
},
primaryCategoryName(categoryId, allCategories) {
getPrimaryCategoryName(categoryId, allCategories) {
for (let i = 0; i < allCategories.length; i++) {
for (let j = 0; j < allCategories[i].subCategories.length; j++) {
const subCategory = allCategories[i].subCategories[j];
@@ -875,7 +849,7 @@ export default {
return '';
},
secondaryCategoryName(categoryId, allCategories) {
getSecondaryCategoryName(categoryId, allCategories) {
for (let i = 0; i < allCategories.length; i++) {
for (let j = 0; j < allCategories[i].subCategories.length; j++) {
const subCategory = allCategories[i].subCategories[j];
File diff suppressed because it is too large Load Diff
+43 -35
View File
@@ -2,39 +2,33 @@
<f7-page @page:afterin="onPageAfterIn">
<f7-navbar :title="$t('Data Management')" :back-link="$t('Back')"></f7-navbar>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item title="Accounts" after="Count"></f7-list-item>
<f7-list-item title="Transaction Categories" after="Count"></f7-list-item>
<f7-list-item title="Transaction Tags" after="Count"></f7-list-item>
<f7-list-item title="Transactions" after="Count"></f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-vertical skeleton-text" v-if="loading">
<f7-list-item title="Accounts" after="Count"></f7-list-item>
<f7-list-item title="Transaction Categories" after="Count"></f7-list-item>
<f7-list-item title="Transaction Tags" after="Count"></f7-list-item>
<f7-list-item title="Transactions" after="Count"></f7-list-item>
</f7-list>
<f7-card v-else-if="!loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item :title="$t('Accounts')" :after="dataStatistics.totalAccountCount"></f7-list-item>
<f7-list-item :title="$t('Transaction Categories')" :after="dataStatistics.totalTransactionCategoryCount"></f7-list-item>
<f7-list-item :title="$t('Transaction Tags')" :after="dataStatistics.totalTransactionTagCount"></f7-list-item>
<f7-list-item :title="$t('Transactions')" :after="dataStatistics.totalTransactionCount"></f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-vertical" v-else-if="!loading">
<f7-list-item :title="$t('Accounts')" :after="dataStatistics.totalAccountCount"></f7-list-item>
<f7-list-item :title="$t('Transaction Categories')" :after="dataStatistics.totalTransactionCategoryCount"></f7-list-item>
<f7-list-item :title="$t('Transaction Tags')" :after="dataStatistics.totalTransactionTagCount"></f7-list-item>
<f7-list-item :title="$t('Transactions')" :after="dataStatistics.totalTransactionCount"></f7-list-item>
</f7-list>
<f7-card>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-button @click="exportedData = null; showExportDataSheet = true" v-if="isDataExportingEnabled">{{ $t('Export Data') }}</f7-list-button>
<f7-list-button color="red" @click="clearData(null)">{{ $t('Clear User Data') }}</f7-list-button>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-vertical" :class="{ 'disabled': loading }">
<f7-list-button :class="{ 'disabled': !dataStatistics || !dataStatistics.totalTransactionCount || dataStatistics.totalTransactionCount === '0' }"
v-if="isDataExportingEnabled"
@click="exportedData = null; showExportDataSheet = true">{{ $t('Export Data') }}</f7-list-button>
<f7-list-button color="red" @click="clearData(null)">{{ $t('Clear User Data') }}</f7-list-button>
</f7-list>
<f7-sheet style="height:auto" :opened="showExportDataSheet" @sheet:closed="showExportDataSheet = false; exportedData = null;">
<f7-page-content>
<f7-sheet swipe-handler=".swipe-handler" style="height:auto"
:swipe-to-close="!exportingData" :close-on-escape="!exportingData"
:close-by-backdrop-click="!exportingData" :close-by-outside-click="!exportingData"
:opened="showExportDataSheet" @sheet:closed="showExportDataSheet = false; exportedData = null;">
<div class="swipe-handler"></div>
<f7-page-content class="margin-top no-padding-top">
<div class="display-flex padding justify-content-space-between align-items-center">
<div style="font-size: 18px"><b>{{ $t('Are you sure you want to export all data to csv file?') }}</b></div>
</div>
@@ -51,9 +45,9 @@
<password-input-sheet :title="$t('Are you sure you want to clear all data?')"
:hint="$t('You CANNOT undo this action. This will clear your accounts, categories, tags and transactions data. Please input your current password to confirm.')"
:show.sync="showInputPasswordSheetForClearData"
:confirm-disabled="clearingData"
:cancel-disabled="clearingData"
v-model:show="showInputPasswordSheetForClearData"
v-model="currentPasswordForClearData"
@password:confirm="clearData">
</password-input-sheet>
@@ -62,6 +56,9 @@
<script>
export default {
props: [
'f7router'
],
data() {
return {
loading: true,
@@ -76,9 +73,6 @@ export default {
};
},
computed: {
currentTimezoneOffsetMinutes() {
return this.$utilities.getTimezoneOffsetMinutes();
},
isDataExportingEnabled() {
return this.$settings.isDataExportingEnabled();
},
@@ -113,7 +107,7 @@ export default {
},
methods: {
onPageAfterIn() {
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
exportData() {
const self = this;
@@ -155,6 +149,20 @@ export default {
self.showInputPasswordSheetForClearData = false;
self.$toast('All user data has been cleared');
self.loading = true;
self.$store.dispatch('getUserDataStatistics').then(dataStatistics => {
self.dataStatistics = dataStatistics;
self.loading = false;
}).catch(error => {
if (error.processed) {
self.loading = false;
} else {
self.loadingError = error;
self.$toast(error.message || error);
}
});
}).catch(error => {
self.clearingData = false;
self.$hideLoading();
+83 -47
View File
@@ -4,46 +4,49 @@
<f7-nav-left :back-link="$t('Back')"></f7-nav-left>
<f7-nav-title :title="$t('Device & Sessions')"></f7-nav-title>
<f7-nav-right>
<f7-link :class="{ 'disabled': tokens.length < 2 }" :text="$t('Logout All')" @click="revokeAll"></f7-link>
<f7-link :class="{ 'disabled': sessions.length < 2 }" :text="$t('Logout All')" @click="revokeAll"></f7-link>
</f7-nav-right>
</f7-navbar>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list media-list>
<f7-list-item class="list-item-media-valign-middle"
title="Current"
text="Device Name (Browser xx.x.xxxx.xx)">
<f7-icon slot="media" f7="device_phone_portrait"></f7-icon>
<small slot="after">MM/DD/YYYY HH:mm:ss</small>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list media-list strong inset dividers class="margin-top skeleton-text" v-if="loading">
<f7-list-item class="list-item-media-valign-middle"
title="Current"
text="Device Name (Browser xx.x.xxxx.xx)">
<template #media>
<f7-icon f7="device_phone_portrait"></f7-icon>
</template>
<template #after>
<small>MM/DD/YYYY HH:mm:ss</small>
</template>
</f7-list-item>
</f7-list>
<f7-card v-else-if="!loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list media-list>
<f7-list-item class="list-item-media-valign-middle" swipeout
v-for="token in tokens"
:key="token.tokenId"
:id="token | tokenDomId"
:title="token | tokenTitle | localized"
:text="token | tokenDevice | localized">
<f7-icon slot="media" :f7="token | tokenIcon"></f7-icon>
<small slot="after">{{ token.createdAt | moment($t('format.datetime.long')) }}</small>
<f7-swipeout-actions right v-if="!token.isCurrent">
<f7-swipeout-button color="red" :text="$t('Log Out')" @click="revoke(token)"></f7-swipeout-button>
</f7-swipeout-actions>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list media-list strong inset dividers class="margin-top" v-else-if="!loading">
<f7-list-item class="list-item-media-valign-middle" swipeout
:id="session.domId"
:title="session.deviceType"
:text="session.deviceInfo"
:key="session.tokenId"
v-for="session in sessions">
<template #media>
<f7-icon :f7="session.icon"></f7-icon>
</template>
<template #after>
<small>{{ session.createdAt }}</small>
</template>
<f7-swipeout-actions right v-if="!session.isCurrent">
<f7-swipeout-button color="red" :text="$t('Log Out')" @click="revoke(session)"></f7-swipeout-button>
</f7-swipeout-actions>
</f7-list-item>
</f7-list>
</f7-page>
</template>
<script>
export default {
props: [
'f7router'
],
data() {
return {
tokens: [],
@@ -51,6 +54,31 @@ export default {
loadingError: null
};
},
computed: {
sessions() {
if (!this.tokens) {
return this.tokens;
}
const sessions = [];
for (let i = 0; i < this.tokens.length; i++) {
const token = this.tokens[i];
sessions.push({
tokenId: token.tokenId,
domId: this.getTokenDomId(token.tokenId),
isCurrent: token.isCurrent,
deviceType: this.$t(token.isCurrent ? 'Current' : 'Other Device'),
deviceInfo: this.$utilities.parseDeviceInfo(token.userAgent),
icon: this.getTokenIcon(token),
createdAt: this.$utilities.formatUnixTime(token.createdAt, this.$t('format.datetime.long'))
});
}
return sessions;
}
},
created() {
const self = this;
@@ -70,7 +98,7 @@ export default {
},
methods: {
onPageAfterIn() {
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
reload(done) {
const self = this;
@@ -91,22 +119,20 @@ export default {
}
});
},
revoke(token) {
revoke(session) {
const self = this;
const app = self.$f7;
const $$ = app.$;
self.$confirm('Are you sure you want to logout from this session?', () => {
self.$showLoading();
self.$store.dispatch('revokeToken', {
tokenId: token.tokenId
tokenId: session.tokenId
}).then(() => {
self.$hideLoading();
app.swipeout.delete($$(`#${self.$options.filters.tokenDomId(token)}`), () => {
self.$ui.onSwipeoutDeleted(self.getTokenDomId(session.tokenId), () => {
for (let i = 0; i < self.tokens.length; i++) {
if (self.tokens[i].tokenId === token.tokenId) {
if (self.tokens[i].tokenId === session.tokenId) {
self.tokens.splice(i, 1);
}
}
@@ -148,18 +174,28 @@ export default {
}
});
});
}
},
filters: {
tokenTitle(token) {
if (token.isCurrent) {
return 'Current';
},
getTokenIcon(token) {
const ua = this.$utilities.parseUserAgent(token.userAgent);
if (!ua || !ua.device) {
return this.$constants.icons.deviceIcons.desktop.f7Icon;
}
return 'Other Device';
if (ua.device.type === 'mobile') {
return this.$constants.icons.deviceIcons.mobile.f7Icon;
} else if (ua.device.type === 'wearable') {
return this.$constants.icons.deviceIcons.wearable.f7Icon;
} else if (ua.device.type === 'tablet') {
return this.$constants.icons.deviceIcons.tablet.f7Icon;
} else if (ua.device.type === 'smarttv') {
return this.$constants.icons.deviceIcons.tv.f7Icon;
} else {
return this.$constants.icons.deviceIcons.desktop.f7Icon;
}
},
tokenDomId(token) {
return 'token_' + token.tokenId.replace(/:/g, '_');
getTokenDomId(tokenId) {
return 'token_' + tokenId.replace(/:/g, '_');
}
}
};
+28 -38
View File
@@ -2,62 +2,44 @@
<f7-page @page:afterin="onPageAfterIn">
<f7-navbar :title="$t('Two-Factor Authentication')" :back-link="$t('Back')"></f7-navbar>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item title="Status" after="Unknown"></f7-list-item>
<f7-list-button class="disabled">Operate</f7-list-button>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-top skeleton-text" v-if="loading">
<f7-list-item title="Status" after="Unknown"></f7-list-item>
<f7-list-button class="disabled">Operate</f7-list-button>
</f7-list>
<f7-card v-else-if="!loading && status === true">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item :title="$t('Status')" :after="$t('Enabled')"></f7-list-item>
<f7-list-button :class="{ 'disabled': regenerating }" @click="regenerateBackupCode(null)">{{ $t('Regenerate Backup Codes') }}</f7-list-button>
<f7-list-button :class="{ 'disabled': disabling }" @click="disable(null)">{{ $t('Disable') }}</f7-list-button>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-top" v-else-if="!loading">
<f7-list-item :title="$t('Status')" :after="$t(status ? 'Enabled' : 'Disabled')"></f7-list-item>
<f7-list-button :class="{ 'disabled': regenerating }" v-if="status === true" @click="regenerateBackupCode(null)">{{ $t('Regenerate Backup Codes') }}</f7-list-button>
<f7-list-button :class="{ 'disabled': disabling }" v-if="status === true" @click="disable(null)">{{ $t('Disable') }}</f7-list-button>
<f7-list-button :class="{ 'disabled': enabling }" v-if="status === false" @click="enable">{{ $t('Enable') }}</f7-list-button>
</f7-list>
<f7-card v-else-if="!loading && status === false">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item :title="$t('Status')" :after="$t('Disabled')"></f7-list-item>
<f7-list-button :class="{ 'disabled': enabling }" @click="enable">{{ $t('Enable') }}</f7-list-button>
</f7-list>
</f7-card-content>
</f7-card>
<passcode-input-sheet :title="$t('Passcode')"
<passcode-input-sheet :title="$t('Enable Two-Factor Authentication')"
:hint="$t('Please use two factor authentication app scan the below qrcode and input current passcode')"
:show.sync="showInputPasscodeSheetForEnable"
:confirm-disabled="enableConfirming"
:cancel-disabled="enableConfirming"
v-model:show="showInputPasscodeSheetForEnable"
v-model="currentPasscodeForEnable"
@passcode:confirm="enableConfirm">
<div class="row">
<div class="col-100 text-align-center">
<img alt="qrcode" width="240px" height="240px" :src="new2FAQRCode" />
</div>
<div class="col-100 text-align-center">
<img alt="qrcode" class="img-qrcode" :src="new2FAQRCode" />
</div>
</passcode-input-sheet>
<password-input-sheet :title="$t('Current Password')"
<password-input-sheet :title="$t('Disable Two-Factor Authentication')"
:hint="$t('Please enter your current password when disable two factor authentication')"
:show.sync="showInputPasswordSheetForDisable"
:confirm-disabled="disabling"
:cancel-disabled="disabling"
v-model:show="showInputPasswordSheetForDisable"
v-model="currentPasswordForDisable"
@password:confirm="disable">
</password-input-sheet>
<password-input-sheet :title="$t('Current Password')"
<password-input-sheet :title="$t('Regenerate Backup Codes')"
:hint="$t('Please enter your current password when regenerate two factor authentication backup codes. If you regenerate backup codes, the old codes will be invalidated.')"
:show.sync="showInputPasswordSheetForRegenerate"
:confirm-disabled="regenerating"
:cancel-disabled="regenerating"
v-model:show="showInputPasswordSheetForRegenerate"
v-model="currentPasswordForRegenerate"
@password:confirm="regenerateBackupCode">
</password-input-sheet>
@@ -68,7 +50,7 @@
:information="currentBackupCode"
:row-count="10"
:enable-copy="true"
:show.sync="showBackupCodeSheet"
v-model:show="showBackupCodeSheet"
@info:copied="onBackupCodeCopied">
</information-sheet>
</f7-page>
@@ -76,6 +58,9 @@
<script>
export default {
props: [
'f7router'
],
data() {
return {
status: null,
@@ -116,7 +101,7 @@ export default {
},
methods: {
onPageAfterIn() {
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
enable() {
const self = this;
@@ -245,6 +230,11 @@ export default {
</script>
<style>
.img-qrcode {
width: 240px;
height: 240px
}
.backup-code-sheet .information-content {
font-family: monospace;
}
+123 -132
View File
@@ -8,152 +8,135 @@
</f7-nav-right>
</f7-navbar>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-input label="Password" placeholder="Your password"></f7-list-input>
<f7-list-input label="Confirmation Password" placeholder="Re-enter the password"></f7-list-input>
<f7-list-input label="E-mail" placeholder="Your email address"></f7-list-input>
<f7-list-input label="Nickname" placeholder="Your nickname"></f7-list-input>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-vertical skeleton-text" v-if="loading">
<f7-list-input label="Password" placeholder="Your password"></f7-list-input>
<f7-list-input label="Confirmation Password" placeholder="Re-enter the password"></f7-list-input>
<f7-list-input label="E-mail" placeholder="Your email address"></f7-list-input>
<f7-list-input label="Nickname" placeholder="Your nickname"></f7-list-input>
</f7-list>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item class="list-item-with-header-and-title list-item-no-item-after" header="Default Currency" title="Currency"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-no-item-after" header="First Day of Week" title="Week Day"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-no-item-after" header="Editable Transaction Scope" title="All"></f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-vertical skeleton-text" v-if="loading">
<f7-list-item class="list-item-with-header-and-title list-item-no-item-after" header="Default Currency" title="Currency" link="#"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-no-item-after" header="Default Account" title="Not Specified"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-no-item-after" header="First Day of Week" title="Week Day" link="#"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-no-item-after" header="Editable Transaction Scope" title="All" link="#"></f7-list-item>
</f7-list>
<f7-card v-if="!loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list form>
<f7-list-input
type="password"
autocomplete="new-password"
clear-button
:label="$t('Password')"
:placeholder="$t('Your password')"
:value="newProfile.password"
@input="newProfile.password = $event.target.value"
></f7-list-input>
<f7-list form strong inset dividers class="margin-vertical" v-if="!loading">
<f7-list-input
type="password"
autocomplete="new-password"
clear-button
:label="$t('Password')"
:placeholder="$t('Your password')"
v-model:value="newProfile.password"
></f7-list-input>
<f7-list-input
type="password"
autocomplete="new-password"
clear-button
:label="$t('Confirmation Password')"
:placeholder="$t('Re-enter the password')"
:value="newProfile.confirmPassword"
@input="newProfile.confirmPassword = $event.target.value"
></f7-list-input>
<f7-list-input
type="password"
autocomplete="new-password"
clear-button
:label="$t('Confirmation Password')"
:placeholder="$t('Re-enter the password')"
v-model:value="newProfile.confirmPassword"
></f7-list-input>
<f7-list-input
type="email"
autocomplete="email"
clear-button
:label="$t('E-mail')"
:placeholder="$t('Your email address')"
:value="newProfile.email"
@input="newProfile.email = $event.target.value"
></f7-list-input>
<f7-list-input
type="email"
autocomplete="email"
clear-button
:label="$t('E-mail')"
:placeholder="$t('Your email address')"
v-model:value="newProfile.email"
></f7-list-input>
<f7-list-input
type="text"
autocomplete="nickname"
clear-button
:label="$t('Nickname')"
:placeholder="$t('Your nickname')"
:value="newProfile.nickname"
@input="newProfile.nickname = $event.target.value"
></f7-list-input>
<f7-list-input
type="text"
autocomplete="nickname"
clear-button
:label="$t('Nickname')"
:placeholder="$t('Your nickname')"
v-model:value="newProfile.nickname"
></f7-list-input>
<f7-list-item class="ebk-list-item-error-info" v-if="inputIsInvalid" :footer="$t(inputInvalidProblemMessage)"></f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list-item class="ebk-list-item-error-info" v-if="inputIsInvalid" :footer="$t(inputInvalidProblemMessage)"></f7-list-item>
</f7-list>
<f7-card v-if="!loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list form>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:header="$t('Default Currency')"
smart-select :smart-select-params="{ openIn: 'popup', pageTitle: $t('Default Currency'), searchbar: true, searchbarPlaceholder: $t('Currency Name'), searchbarDisableText: $t('Cancel'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }"
>
<f7-block slot="title" class="no-padding no-margin">
<span>{{ $t(`currency.${newProfile.defaultCurrency}`) }}&nbsp;</span>
<small class="smaller">{{ newProfile.defaultCurrency }}</small>
</f7-block>
<select autocomplete="transaction-currency" v-model="newProfile.defaultCurrency">
<option v-for="currency in allCurrencies"
:key="currency.code"
:value="currency.code">{{ currency.displayName }}</option>
</select>
</f7-list-item>
<f7-list form strong inset dividers class="margin-vertical" v-if="!loading">
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:header="$t('Default Currency')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Currency Name'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Default Currency'), popupCloseLinkText: $t('Done') }"
>
<template #title>
<f7-block class="no-padding no-margin">
<span>{{ $t(`currency.${newProfile.defaultCurrency}`) }}&nbsp;</span>
<small class="smaller">{{ newProfile.defaultCurrency }}</small>
</f7-block>
</template>
<select autocomplete="transaction-currency" v-model="newProfile.defaultCurrency">
<option :value="currency.code"
:key="currency.code"
v-for="currency in allCurrencies">{{ currency.displayName }}</option>
</select>
</f7-list-item>
<f7-list-item
class="list-item-with-header-and-title"
link="#"
:class="{ 'disabled': !allVisibleAccounts.length }"
:header="$t('Default Account')"
:title="newProfile.defaultAccountId | optionName(allAccounts, 'id', 'name', $t('Not Specified'))"
@click="showAccountSheet = true"
>
<two-column-list-item-selection-sheet primary-key-field="id" primary-value-field="category"
primary-title-field="name"
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-icon-field="icon" secondary-icon-type="account" secondary-color-field="color"
:items="allCategorizedAccounts"
:show.sync="showAccountSheet"
v-model="newProfile.defaultAccountId">
</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 }"
:header="$t('Default Account')"
:title="$utilities.getNameByKeyValue(allAccounts, newProfile.defaultAccountId, 'id', 'name', $t('Not Specified'))"
@click="showAccountSheet = true"
>
<two-column-list-item-selection-sheet primary-key-field="id" primary-value-field="category"
primary-title-field="name"
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-icon-field="icon" secondary-icon-type="account" secondary-color-field="color"
:items="allCategorizedAccounts"
v-model:show="showAccountSheet"
v-model="newProfile.defaultAccountId">
</two-column-list-item-selection-sheet>
</f7-list-item>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:header="$t('First Day of Week')"
:title="newProfile.firstDayOfWeek | optionName(allWeekDays, 'type', 'name') | format('datetime.#{value}.long') | localized"
smart-select :smart-select-params="{ openIn: 'popup', pageTitle: $t('First Day of Week'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }"
>
<select v-model="newProfile.firstDayOfWeek">
<option v-for="weekDay in allWeekDays"
:key="weekDay.type"
:value="weekDay.type">{{ $t(`datetime.${weekDay.name}.long`) }}</option>
</select>
</f7-list-item>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:header="$t('First Day of Week')"
:title="getDayOfWeekName(newProfile.firstDayOfWeek)"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Date'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('First Day of Week'), popupCloseLinkText: $t('Done') }"
>
<select v-model="newProfile.firstDayOfWeek">
<option :value="weekDay.type"
:key="weekDay.type"
v-for="weekDay in allWeekDays">{{ $t(`datetime.${weekDay.name}.long`) }}</option>
</select>
</f7-list-item>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:header="$t('Editable Transaction Scope')"
:title="newProfile.transactionEditScope | optionName(allTransactionEditScopeTypes, 'value', 'name') | localized"
smart-select :smart-select-params="{ openIn: 'popup', pageTitle: $t('Editable Transaction Scope'), closeOnSelect: true, popupCloseLinkText: $t('Done'), scrollToSelectedItem: true }"
>
<select v-model="newProfile.transactionEditScope">
<option v-for="option in allTransactionEditScopeTypes"
:key="option.value"
:value="option.value">{{ $t(option.name) }}</option>
</select>
</f7-list-item>
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:header="$t('Editable Transaction Scope')"
:title="$t($utilities.getNameByKeyValue(allTransactionEditScopeTypes, newProfile.transactionEditScope, 'value', 'name'))"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Date Range'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Editable Transaction Scope'), popupCloseLinkText: $t('Done') }"
>
<select v-model="newProfile.transactionEditScope">
<option :value="option.value"
:key="option.value"
v-for="option in allTransactionEditScopeTypes">{{ $t(option.name) }}</option>
</select>
</f7-list-item>
<f7-list-item class="ebk-list-item-error-info" v-if="extendInputIsInvalid" :footer="$t(extendInputInvalidProblemMessage)"></f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list-item class="ebk-list-item-error-info" v-if="extendInputIsInvalid" :footer="$t(extendInputInvalidProblemMessage)"></f7-list-item>
</f7-list>
<password-input-sheet :title="$t('Current Password')"
:hint="$t('Please enter your current password when modifying your password')"
:show.sync="showInputPasswordSheet"
:confirm-disabled="saving"
:cancel-disabled="saving"
v-model:show="showInputPasswordSheet"
v-model="currentPassword"
@password:confirm="save()">
</password-input-sheet>
@@ -162,6 +145,9 @@
<script>
export default {
props: [
'f7router'
],
data() {
return {
newProfile: {
@@ -318,11 +304,11 @@ export default {
},
methods: {
onPageAfterIn() {
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
save() {
const self = this;
const router = self.$f7router;
const router = self.f7router;
self.showInputPasswordSheet = false;
@@ -360,6 +346,11 @@ export default {
self.$toast(error.message || error);
}
});
},
getDayOfWeekName(dayOfWeek) {
const weekName = this.$utilities.getNameByKeyValue(this.$constants.datetime.allWeekDays, dayOfWeek, 'type', 'name');
const i18nWeekNameKey = `datetime.${weekName}.long`;
return this.$t(i18nWeekNameKey);
}
}
};