mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-16 07:57:33 +08:00
add transaction template
This commit is contained in:
@@ -61,6 +61,12 @@
|
||||
<span class="nav-item-title">{{ $t('Transaction Tags') }}</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li class="nav-link">
|
||||
<router-link to="/template/list">
|
||||
<v-icon class="nav-item-icon" :icon="icons.templates"/>
|
||||
<span class="nav-item-title">{{ $t('Transaction Templates') }}</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li class="nav-section-title">
|
||||
<div class="title-wrapper">
|
||||
<span class="title-text">{{ $t('Miscellaneous') }}</span>
|
||||
@@ -196,6 +202,7 @@ import {
|
||||
mdiCreditCardOutline,
|
||||
mdiViewDashboardOutline,
|
||||
mdiTagOutline,
|
||||
mdiClipboardTextOutline,
|
||||
mdiChartPieOutline,
|
||||
mdiSwapHorizontal,
|
||||
mdiCogOutline,
|
||||
@@ -225,6 +232,7 @@ export default {
|
||||
accounts: mdiCreditCardOutline,
|
||||
categories: mdiViewDashboardOutline,
|
||||
tags: mdiTagOutline,
|
||||
templates: mdiClipboardTextOutline,
|
||||
statistics: mdiChartPieOutline,
|
||||
exchangeRates: mdiSwapHorizontal,
|
||||
settings: mdiCogOutline,
|
||||
|
||||
@@ -0,0 +1,585 @@
|
||||
<template>
|
||||
<v-row class="match-height">
|
||||
<v-col cols="12">
|
||||
<v-card>
|
||||
<template #title>
|
||||
<div class="title-and-toolbar d-flex align-center">
|
||||
<span>{{ $t('Transaction Templates') }}</span>
|
||||
<v-btn class="ml-3" color="default" variant="outlined"
|
||||
:disabled="loading || updating || hasEditingTemplateName" @click="add">{{ $t('Add') }}</v-btn>
|
||||
<v-btn class="ml-3" color="primary" variant="tonal"
|
||||
:disabled="loading || updating || hasEditingTemplateName" @click="saveSortResult"
|
||||
v-if="displayOrderModified">{{ $t('Save Display Order') }}</v-btn>
|
||||
<v-btn density="compact" color="default" variant="text" size="24"
|
||||
class="ml-2" :icon="true" :disabled="loading || updating || hasEditingTemplateName"
|
||||
:loading="loading" @click="reload">
|
||||
<template #loader>
|
||||
<v-progress-circular indeterminate size="20"/>
|
||||
</template>
|
||||
<v-icon :icon="icons.refresh" size="24" />
|
||||
<v-tooltip activator="parent">{{ $t('Refresh') }}</v-tooltip>
|
||||
</v-btn>
|
||||
<v-spacer/>
|
||||
<v-btn density="comfortable" color="default" variant="text" class="ml-2"
|
||||
:disabled="loading || updating || hasEditingTemplateName" :icon="true">
|
||||
<v-icon :icon="icons.more" />
|
||||
<v-menu activator="parent">
|
||||
<v-list>
|
||||
<v-list-item :prepend-icon="icons.show"
|
||||
:title="$t('Show Hidden Templates')"
|
||||
v-if="!showHidden" @click="showHidden = true"></v-list-item>
|
||||
<v-list-item :prepend-icon="icons.hide"
|
||||
:title="$t('Hide Hidden Templates')"
|
||||
v-if="showHidden" @click="showHidden = false"></v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<v-table class="transaction-templates-table table-striped" :hover="!loading">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<div class="d-flex align-center">
|
||||
<span>{{ $t('Template Name') }}</span>
|
||||
<v-spacer/>
|
||||
<span>{{ $t('Operation') }}</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody v-if="loading && noAvailableTemplate && !newTemplate">
|
||||
<tr :key="itemIdx" v-for="itemIdx in [ 1, 2, 3 ]">
|
||||
<td class="px-0">
|
||||
<v-skeleton-loader type="text" :loading="true"></v-skeleton-loader>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody v-if="!loading && noAvailableTemplate && !newTemplate">
|
||||
<tr>
|
||||
<td>{{ $t('No available template') }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<draggable-list tag="tbody"
|
||||
item-key="id"
|
||||
handle=".drag-handle"
|
||||
ghost-class="dragging-item"
|
||||
:class="{ 'has-bottom-border': newTemplate }"
|
||||
:disabled="noAvailableTemplate"
|
||||
v-model="templates"
|
||||
@change="onMove">
|
||||
<template #item="{ element }">
|
||||
<tr class="transaction-templates-table-row text-sm" v-if="showHidden || !element.hidden">
|
||||
<td>
|
||||
<div class="d-flex align-center">
|
||||
<div class="d-flex align-center" v-if="editingTemplateName.id !== element.id">
|
||||
<v-badge class="right-bottom-icon" color="secondary"
|
||||
location="bottom right" offset-x="8" :icon="icons.hide"
|
||||
v-if="element.hidden">
|
||||
<v-icon size="20" start :icon="icons.text"/>
|
||||
</v-badge>
|
||||
<v-icon size="20" start :icon="icons.text" v-else-if="!element.hidden"/>
|
||||
<span class="transaction-template-name">{{ element.name }}</span>
|
||||
</div>
|
||||
|
||||
<v-text-field class="w-100 mr-2" type="text"
|
||||
density="compact" variant="underlined"
|
||||
:disabled="loading || updating"
|
||||
:placeholder="$t('Template Name')"
|
||||
v-model="editingTemplateName.name"
|
||||
v-else-if="editingTemplateName.id === element.id"
|
||||
@keyup.enter="saveName(editingTemplateName)"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-badge class="right-bottom-icon" color="secondary"
|
||||
location="bottom right" offset-x="8" :icon="icons.hide"
|
||||
v-if="element.hidden">
|
||||
<v-icon size="20" start :icon="icons.text"/>
|
||||
</v-badge>
|
||||
<v-icon size="20" start :icon="icons.text" v-else-if="!element.hidden"/>
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<v-spacer/>
|
||||
|
||||
<v-btn class="px-2 ml-2" color="default"
|
||||
density="comfortable" variant="text"
|
||||
:class="{ 'd-none': loading, 'hover-display': !loading }"
|
||||
:prepend-icon="element.hidden ? icons.show : icons.hide"
|
||||
:loading="templateHiding[element.id]"
|
||||
:disabled="loading || updating"
|
||||
v-if="editingTemplateName.id !== element.id"
|
||||
@click="hide(element, !element.hidden)">
|
||||
<template #loader>
|
||||
<v-progress-circular indeterminate size="20" width="2"/>
|
||||
</template>
|
||||
{{ element.hidden ? $t('Show') : $t('Hide') }}
|
||||
</v-btn>
|
||||
<v-btn class="px-2" color="default"
|
||||
density="comfortable" variant="text"
|
||||
:class="{ 'd-none': loading, 'hover-display': !loading }"
|
||||
:prepend-icon="icons.edit"
|
||||
:loading="templateNameUpdating[element.id]"
|
||||
:disabled="loading || updating"
|
||||
v-if="editingTemplateName.id !== element.id"
|
||||
@click="modifyName(element)">
|
||||
<template #loader>
|
||||
<v-progress-circular indeterminate size="20" width="2"/>
|
||||
</template>
|
||||
{{ $t('Modify Name') }}
|
||||
</v-btn>
|
||||
<v-btn class="px-2" color="default"
|
||||
density="comfortable" variant="text"
|
||||
:class="{ 'd-none': loading, 'hover-display': !loading }"
|
||||
:prepend-icon="icons.edit"
|
||||
:loading="templateNameUpdating[element.id]"
|
||||
:disabled="loading || updating"
|
||||
v-if="editingTemplateName.id !== element.id"
|
||||
@click="edit(element)">
|
||||
<template #loader>
|
||||
<v-progress-circular indeterminate size="20" width="2"/>
|
||||
</template>
|
||||
{{ $t('Edit') }}
|
||||
</v-btn>
|
||||
<v-btn class="px-2" color="default"
|
||||
density="comfortable" variant="text"
|
||||
:class="{ 'd-none': loading, 'hover-display': !loading }"
|
||||
:prepend-icon="icons.remove"
|
||||
:loading="templateRemoving[element.id]"
|
||||
:disabled="loading || updating"
|
||||
v-if="editingTemplateName.id !== element.id"
|
||||
@click="remove(element)">
|
||||
<template #loader>
|
||||
<v-progress-circular indeterminate size="20" width="2"/>
|
||||
</template>
|
||||
{{ $t('Delete') }}
|
||||
</v-btn>
|
||||
<v-btn class="px-2"
|
||||
density="comfortable" variant="text"
|
||||
:prepend-icon="icons.confirm"
|
||||
:loading="templateNameUpdating[element.id]"
|
||||
:disabled="loading || updating || !isTemplateModified(element)"
|
||||
v-if="editingTemplateName.id === element.id" @click="saveName(editingTemplateName)">
|
||||
<template #loader>
|
||||
<v-progress-circular indeterminate size="20" width="2"/>
|
||||
</template>
|
||||
{{ $t('Save') }}
|
||||
</v-btn>
|
||||
<v-btn class="px-2" color="default"
|
||||
density="comfortable" variant="text"
|
||||
:prepend-icon="icons.cancel"
|
||||
:disabled="loading || updating"
|
||||
v-if="editingTemplateName.id === element.id" @click="cancelSaveName(editingTemplateName)">
|
||||
{{ $t('Cancel') }}
|
||||
</v-btn>
|
||||
<span class="ml-2">
|
||||
<v-icon :class="!loading && !updating && availableTemplateCount > 1 ? 'drag-handle' : 'disabled'"
|
||||
:icon="icons.drag"/>
|
||||
<v-tooltip activator="parent" v-if="!loading && !updating && availableTemplateCount > 1">{{ $t('Drag to Reorder') }}</v-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</draggable-list>
|
||||
|
||||
<tbody v-if="newTemplate">
|
||||
<tr class="text-sm" :class="{ 'even-row': availableTemplateCount & 1 === 1}">
|
||||
<td>
|
||||
<div class="d-flex align-center">
|
||||
<v-text-field class="w-100 mr-2" type="text" color="primary"
|
||||
density="compact" variant="underlined"
|
||||
:disabled="loading || updating" :placeholder="$t('Template Name')"
|
||||
v-model="newTemplate.name" @keyup.enter="saveName(newTemplate)">
|
||||
<template #prepend>
|
||||
<v-icon size="20" start :icon="icons.text"/>
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<v-spacer/>
|
||||
|
||||
<v-btn class="px-2" density="comfortable" variant="text"
|
||||
:prepend-icon="icons.confirm"
|
||||
:loading="templateNameUpdating[null]"
|
||||
:disabled="loading || updating || !isTemplateModified(newTemplate)"
|
||||
@click="saveName(newTemplate)">
|
||||
<template #loader>
|
||||
<v-progress-circular indeterminate size="20" width="2"/>
|
||||
</template>
|
||||
{{ $t('Save') }}
|
||||
</v-btn>
|
||||
<v-btn class="px-2" color="default"
|
||||
density="comfortable" variant="text"
|
||||
:prepend-icon="icons.cancel"
|
||||
:disabled="loading || updating"
|
||||
@click="cancelSaveName(newTemplate)">
|
||||
{{ $t('Cancel') }}
|
||||
</v-btn>
|
||||
<span class="ml-2">
|
||||
<v-icon class="disabled" :icon="icons.drag"/>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<edit-dialog ref="editDialog" :persistent="true" />
|
||||
|
||||
<confirm-dialog ref="confirmDialog"/>
|
||||
<snack-bar ref="snackbar" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EditDialog from '@/views/desktop/transactions/list/dialogs/EditDialog.vue';
|
||||
|
||||
import { mapStores } from 'pinia';
|
||||
import { useTransactionTemplatesStore } from '@/stores/transactionTemplate.js';
|
||||
|
||||
import templateConstants from '@/consts/template.js';
|
||||
import { generateRandomUUID } from '@/lib/misc.js';
|
||||
|
||||
import {
|
||||
mdiRefresh,
|
||||
mdiPlus,
|
||||
mdiPencilOutline,
|
||||
mdiCheck,
|
||||
mdiClose,
|
||||
mdiEyeOffOutline,
|
||||
mdiEyeOutline,
|
||||
mdiDeleteOutline,
|
||||
mdiDrag,
|
||||
mdiDotsVertical,
|
||||
mdiTextBoxOutline
|
||||
} from '@mdi/js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
templateType: templateConstants.allTemplateTypes.Normal,
|
||||
newTemplate: null,
|
||||
editingTemplateName: {},
|
||||
loading: true,
|
||||
updating: false,
|
||||
templateNameUpdating: {},
|
||||
templateHiding: {},
|
||||
templateRemoving: {},
|
||||
displayOrderModified: false,
|
||||
showHidden: false,
|
||||
icons: {
|
||||
refresh: mdiRefresh,
|
||||
add: mdiPlus,
|
||||
edit: mdiPencilOutline,
|
||||
confirm: mdiCheck,
|
||||
cancel: mdiClose,
|
||||
show: mdiEyeOutline,
|
||||
hide: mdiEyeOffOutline,
|
||||
remove: mdiDeleteOutline,
|
||||
drag: mdiDrag,
|
||||
more: mdiDotsVertical,
|
||||
text: mdiTextBoxOutline
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useTransactionTemplatesStore),
|
||||
templates() {
|
||||
return this.transactionTemplatesStore.allTransactionTemplates[this.templateType] || [];
|
||||
},
|
||||
noAvailableTemplate() {
|
||||
for (let i = 0; i < this.templates.length; i++) {
|
||||
if (this.showHidden || !this.templates[i].hidden) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
availableTemplateCount() {
|
||||
let count = 0;
|
||||
|
||||
for (let i = 0; i < this.templates.length; i++) {
|
||||
if (this.showHidden || !this.templates[i].hidden) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
},
|
||||
hasEditingTemplateName() {
|
||||
return !!(this.newTemplate || (this.editingTemplateName && this.editingTemplateName.id && this.editingTemplateName.id !== ''));
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const self = this;
|
||||
|
||||
self.loading = true;
|
||||
|
||||
self.transactionTemplatesStore.loadAllTemplates({
|
||||
templateType: self.templateType,
|
||||
force: false
|
||||
}).then(() => {
|
||||
self.loading = false;
|
||||
}).catch(error => {
|
||||
self.loading = false;
|
||||
|
||||
if (!error.processed) {
|
||||
self.$refs.snackbar.showError(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
reload() {
|
||||
if (this.hasEditingTemplateName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const self = this;
|
||||
self.loading = true;
|
||||
|
||||
self.transactionTemplatesStore.loadAllTemplates({
|
||||
templateType: self.templateType,
|
||||
force: true
|
||||
}).then(() => {
|
||||
self.loading = false;
|
||||
self.displayOrderModified = false;
|
||||
|
||||
self.$refs.snackbar.showMessage('Template list has been updated');
|
||||
}).catch(error => {
|
||||
self.loading = false;
|
||||
|
||||
if (!error.processed) {
|
||||
self.$refs.snackbar.showError(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
onMove(event) {
|
||||
if (!event || !event.moved) {
|
||||
return;
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const moveEvent = event.moved;
|
||||
|
||||
if (!moveEvent.element || !moveEvent.element.id) {
|
||||
self.$refs.snackbar.showMessage('Unable to move template');
|
||||
return;
|
||||
}
|
||||
|
||||
self.transactionTemplatesStore.changeTemplateDisplayOrder({
|
||||
templateType: self.templateType,
|
||||
templateId: moveEvent.element.id,
|
||||
from: moveEvent.oldIndex,
|
||||
to: moveEvent.newIndex
|
||||
}).then(() => {
|
||||
self.displayOrderModified = true;
|
||||
}).catch(error => {
|
||||
self.$refs.snackbar.showError(error);
|
||||
});
|
||||
},
|
||||
saveSortResult() {
|
||||
const self = this;
|
||||
|
||||
if (!self.displayOrderModified) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.loading = true;
|
||||
|
||||
self.transactionTemplatesStore.updateTemplateDisplayOrders({
|
||||
templateType: self.templateType
|
||||
}).then(() => {
|
||||
self.loading = false;
|
||||
self.displayOrderModified = false;
|
||||
}).catch(error => {
|
||||
self.loading = false;
|
||||
|
||||
if (!error.processed) {
|
||||
self.$refs.snackbar.showError(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
add() {
|
||||
this.newTemplate = {
|
||||
templateType: this.templateType,
|
||||
name: '',
|
||||
clientSessionId: generateRandomUUID()
|
||||
};
|
||||
},
|
||||
modifyName(template) {
|
||||
this.editingTemplateName.id = template.id;
|
||||
this.editingTemplateName.templateType = template.templateType;
|
||||
this.editingTemplateName.name = template.name;
|
||||
},
|
||||
saveName(template) {
|
||||
const self = this;
|
||||
|
||||
self.updating = true;
|
||||
self.templateNameUpdating[template.id || null] = true;
|
||||
|
||||
self.transactionTemplatesStore.saveTemplateName({
|
||||
template: template
|
||||
}).then(() => {
|
||||
self.updating = false;
|
||||
self.templateNameUpdating[template.id || null] = false;
|
||||
|
||||
if (template.id) {
|
||||
self.editingTemplateName = {};
|
||||
} else {
|
||||
self.newTemplate = null;
|
||||
}
|
||||
}).catch(error => {
|
||||
self.updating = false;
|
||||
self.templateNameUpdating[template.id || null] = false;
|
||||
|
||||
if (!error.processed) {
|
||||
self.$refs.snackbar.showError(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
cancelSaveName(template) {
|
||||
if (template.id) {
|
||||
this.editingTemplateName = {};
|
||||
} else {
|
||||
this.newTemplate = null;
|
||||
}
|
||||
},
|
||||
edit(template) {
|
||||
const self = this;
|
||||
|
||||
self.$refs.editDialog.open({
|
||||
editTemplateId: template.id,
|
||||
currentTemplate: {
|
||||
type: template.type,
|
||||
categoryId: template.categoryId,
|
||||
sourceAccountId: template.sourceAccountId,
|
||||
destinationAccountId: template.destinationAccountId,
|
||||
sourceAmount: template.sourceAmount,
|
||||
destinationAmount: template.destinationAmount,
|
||||
tagIds: template.tagIds,
|
||||
comment: template.comment
|
||||
}
|
||||
}).then(result => {
|
||||
if (result && result.message) {
|
||||
self.$refs.snackbar.showMessage(result.message);
|
||||
}
|
||||
|
||||
self.reload(false);
|
||||
}).catch(error => {
|
||||
if (error) {
|
||||
self.$refs.snackbar.showError(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
isTemplateModified(template) {
|
||||
if (template.id) {
|
||||
return this.editingTemplateName && this.editingTemplateName.name !== '' && this.editingTemplateName.name !== template.name;
|
||||
} else {
|
||||
return template.name !== '';
|
||||
}
|
||||
},
|
||||
hide(template, hidden) {
|
||||
const self = this;
|
||||
|
||||
self.updating = true;
|
||||
self.templateHiding[template.id] = true;
|
||||
|
||||
self.transactionTemplatesStore.hideTemplate({
|
||||
template: template,
|
||||
hidden: hidden
|
||||
}).then(() => {
|
||||
self.updating = false;
|
||||
self.templateHiding[template.id] = false;
|
||||
}).catch(error => {
|
||||
self.updating = false;
|
||||
self.templateHiding[template.id] = false;
|
||||
|
||||
if (!error.processed) {
|
||||
self.$refs.snackbar.showError(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
remove(template) {
|
||||
const self = this;
|
||||
|
||||
self.$refs.confirmDialog.open('Are you sure you want to delete this template?').then(() => {
|
||||
self.updating = true;
|
||||
self.templateRemoving[template.id] = true;
|
||||
|
||||
self.transactionTemplatesStore.deleteTemplate({
|
||||
template: template
|
||||
}).then(() => {
|
||||
self.updating = false;
|
||||
self.templateRemoving[template.id] = false;
|
||||
}).catch(error => {
|
||||
self.updating = false;
|
||||
self.templateRemoving[template.id] = false;
|
||||
|
||||
if (!error.processed) {
|
||||
self.$refs.snackbar.showError(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.transaction-templates-table tr.transaction-templates-table-row .hover-display {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.transaction-templates-table tr.transaction-templates-table-row:hover .hover-display {
|
||||
display: inline-grid;
|
||||
}
|
||||
|
||||
.transaction-templates-table tr:not(:last-child) > td > div {
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.transaction-templates-table .has-bottom-border tr:last-child > td > div {
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.transaction-templates-table tr.transaction-templates-table-row .right-bottom-icon .v-badge__badge {
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.transaction-templates-table .v-text-field .v-input__prepend {
|
||||
margin-right: 0;
|
||||
color: rgba(var(--v-theme-on-surface));
|
||||
}
|
||||
|
||||
.transaction-templates-table .v-text-field .v-input__prepend .v-badge > .v-badge__wrapper > .v-icon {
|
||||
opacity: var(--v-medium-emphasis-opacity);
|
||||
}
|
||||
|
||||
.transaction-templates-table .v-text-field.v-input--plain-underlined .v-input__prepend {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.transaction-templates-table .v-text-field .v-field__input {
|
||||
font-size: 0.875rem;
|
||||
padding-top: 0;
|
||||
color: rgba(var(--v-theme-on-surface));
|
||||
}
|
||||
|
||||
.transaction-templates-table .transaction-template-name {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.transaction-templates-table tr .v-text-field .v-field__input {
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
</style>
|
||||
@@ -8,7 +8,7 @@
|
||||
<v-progress-circular indeterminate size="22" class="ml-2" v-if="loading"></v-progress-circular>
|
||||
</div>
|
||||
<v-btn density="comfortable" color="default" variant="text" class="ml-2" :icon="true"
|
||||
:disabled="loading || submitting" v-if="mode !== 'view'">
|
||||
:disabled="loading || submitting" v-if="mode !== 'view' && (mode !== 'editTemplate' || transaction.type === allTransactionTypes.Transfer)">
|
||||
<v-icon :icon="icons.more" />
|
||||
<v-menu activator="parent">
|
||||
<v-list>
|
||||
@@ -24,13 +24,13 @@
|
||||
:title="$t('Swap Account and Amount')"
|
||||
v-if="transaction.type === allTransactionTypes.Transfer"
|
||||
@click="swapTransactionData(true, true)"></v-list-item>
|
||||
<v-divider v-if="transaction.type === allTransactionTypes.Transfer" />
|
||||
<v-divider v-if="mode !== 'editTemplate' && transaction.type === allTransactionTypes.Transfer" />
|
||||
<v-list-item :prepend-icon="icons.show"
|
||||
:title="$t('Show Amount')"
|
||||
v-if="transaction.hideAmount" @click="transaction.hideAmount = false"></v-list-item>
|
||||
v-if="mode !== 'editTemplate' && transaction.hideAmount" @click="transaction.hideAmount = false"></v-list-item>
|
||||
<v-list-item :prepend-icon="icons.hide"
|
||||
:title="$t('Hide Amount')"
|
||||
v-if="!transaction.hideAmount" @click="transaction.hideAmount = true"></v-list-item>
|
||||
v-if="mode !== 'editTemplate' && !transaction.hideAmount" @click="transaction.hideAmount = true"></v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
@@ -38,18 +38,18 @@
|
||||
</template>
|
||||
<v-card-text class="d-flex flex-column flex-md-row mt-md-4 pt-0">
|
||||
<div class="mb-4">
|
||||
<v-tabs class="v-tabs-pill" direction="vertical" :class="{ 'readonly': mode !== 'add' }"
|
||||
<v-tabs class="v-tabs-pill" direction="vertical" :class="{ 'readonly': mode !== 'add' && mode !== 'editTemplate' }"
|
||||
:disabled="loading || submitting" v-model="transaction.type">
|
||||
<v-tab :value="allTransactionTypes.Expense" :disabled="mode !== 'add' && transaction.type !== allTransactionTypes.Expense" v-if="transaction.type !== allTransactionTypes.ModifyBalance">
|
||||
<v-tab :value="allTransactionTypes.Expense" :disabled="mode !== 'add' && mode !== 'editTemplate' && transaction.type !== allTransactionTypes.Expense" v-if="transaction.type !== allTransactionTypes.ModifyBalance">
|
||||
<span>{{ $t('Expense') }}</span>
|
||||
</v-tab>
|
||||
<v-tab :value="allTransactionTypes.Income" :disabled="mode !== 'add' && transaction.type !== allTransactionTypes.Income" v-if="transaction.type !== allTransactionTypes.ModifyBalance">
|
||||
<v-tab :value="allTransactionTypes.Income" :disabled="mode !== 'add' && mode !== 'editTemplate' && transaction.type !== allTransactionTypes.Income" v-if="transaction.type !== allTransactionTypes.ModifyBalance">
|
||||
<span>{{ $t('Income') }}</span>
|
||||
</v-tab>
|
||||
<v-tab :value="allTransactionTypes.Transfer" :disabled="mode !== 'add' && transaction.type !== allTransactionTypes.Transfer" v-if="transaction.type !== allTransactionTypes.ModifyBalance">
|
||||
<v-tab :value="allTransactionTypes.Transfer" :disabled="mode !== 'add' && mode !== 'editTemplate' && transaction.type !== allTransactionTypes.Transfer" v-if="transaction.type !== allTransactionTypes.ModifyBalance">
|
||||
<span>{{ $t('Transfer') }}</span>
|
||||
</v-tab>
|
||||
<v-tab :value="allTransactionTypes.ModifyBalance" v-if="transaction.type === allTransactionTypes.ModifyBalance">
|
||||
<v-tab :value="allTransactionTypes.ModifyBalance" v-if="mode !== 'editTemplate' && transaction.type === allTransactionTypes.ModifyBalance">
|
||||
<span>{{ $t('Modify Balance') }}</span>
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
@@ -58,7 +58,7 @@
|
||||
<v-tab value="basicInfo">
|
||||
<span>{{ $t('Basic Information') }}</span>
|
||||
</v-tab>
|
||||
<v-tab value="map" :disabled="!transaction.geoLocation" v-if="mapProvider">
|
||||
<v-tab value="map" :disabled="!transaction.geoLocation" v-if="mode !== 'editTemplate' && mapProvider">
|
||||
<span>{{ $t('Location on Map') }}</span>
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
@@ -177,7 +177,7 @@
|
||||
v-model="transaction.destinationAccountId">
|
||||
</two-column-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-col cols="12" md="6" v-if="mode !== 'editTemplate'">
|
||||
<date-time-select
|
||||
:readonly="mode === 'view'"
|
||||
:disabled="loading || submitting"
|
||||
@@ -185,7 +185,7 @@
|
||||
v-model="transaction.time"
|
||||
@error="showDateTimeError" />
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-col cols="12" md="6" v-if="mode !== 'editTemplate'">
|
||||
<v-autocomplete
|
||||
class="transaction-edit-timezone"
|
||||
item-title="displayNameWithUtcOffset"
|
||||
@@ -207,7 +207,7 @@
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
</v-col>
|
||||
<v-col cols="12" md="12">
|
||||
<v-col cols="12" md="12" v-if="mode !== 'editTemplate'">
|
||||
<v-select
|
||||
persistent-placeholder
|
||||
:readonly="mode === 'view'"
|
||||
@@ -334,6 +334,7 @@ import { useAccountsStore } from '@/stores/account.js';
|
||||
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
|
||||
import { useTransactionTagsStore } from '@/stores/transactionTag.js';
|
||||
import { useTransactionsStore } from '@/stores/transaction.js';
|
||||
import { useTransactionTemplatesStore } from '@/stores/transactionTemplate.js';
|
||||
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
|
||||
|
||||
import categoryConstants from '@/consts/category.js';
|
||||
@@ -401,12 +402,14 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useSettingsStore, useUserStore, useAccountsStore, useTransactionCategoriesStore, useTransactionTagsStore, useTransactionsStore, useExchangeRatesStore),
|
||||
...mapStores(useSettingsStore, useUserStore, useAccountsStore, useTransactionCategoriesStore, useTransactionTagsStore, useTransactionsStore, useTransactionTemplatesStore, useExchangeRatesStore),
|
||||
title() {
|
||||
if (this.mode === 'add') {
|
||||
return 'Add Transaction';
|
||||
} else if (this.mode === 'edit') {
|
||||
return 'Edit Transaction';
|
||||
} else if (this.mode === 'editTemplate') {
|
||||
return 'Edit Transaction Template';
|
||||
} else {
|
||||
return 'Transaction Detail';
|
||||
}
|
||||
@@ -590,14 +593,14 @@ export default {
|
||||
}
|
||||
},
|
||||
'transaction.sourceAmount': function (newValue, oldValue) {
|
||||
if (this.mode === 'view') {
|
||||
if (this.mode === 'view' || this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.transactionsStore.setTransactionSuitableDestinationAmount(this.transaction, oldValue, newValue);
|
||||
},
|
||||
'transaction.destinationAmount': function (newValue) {
|
||||
if (this.mode === 'view') {
|
||||
if (this.mode === 'view' || this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -642,6 +645,15 @@ export default {
|
||||
self.editTransactionId = options.id;
|
||||
|
||||
promises.push(self.transactionsStore.getTransaction({ transactionId: self.editTransactionId }));
|
||||
} else if (options && options.editTemplateId) {
|
||||
if (options.currentTemplate) {
|
||||
self.setTransaction(options.currentTemplate, options, false, false);
|
||||
}
|
||||
|
||||
self.mode = 'editTemplate';
|
||||
self.transaction.id = options.editTemplateId;
|
||||
|
||||
promises.push(self.transactionTemplatesStore.getTemplate({ templateId: options.editTemplateId }));
|
||||
} else {
|
||||
self.mode = 'add';
|
||||
self.editTransactionId = null;
|
||||
@@ -671,10 +683,13 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.id && responses[3]) {
|
||||
if (options && options.id && responses[3]) {
|
||||
const transaction = responses[3];
|
||||
self.setTransaction(transaction, options, true, true);
|
||||
self.originalTransactionEditable = transaction.editable;
|
||||
} else if (options && options.editTemplateId && responses[3]) {
|
||||
const template = responses[3];
|
||||
self.setTransaction(template, options, false, false);
|
||||
} else {
|
||||
self.setTransaction(null, options, true, true);
|
||||
}
|
||||
@@ -701,31 +716,59 @@ export default {
|
||||
save() {
|
||||
const self = this;
|
||||
|
||||
if (self.mode === 'view') {
|
||||
return;
|
||||
}
|
||||
if (self.mode === 'add' || self.mode === 'edit') {
|
||||
const doSubmit = function () {
|
||||
self.submitting = true;
|
||||
|
||||
const doSubmit = function () {
|
||||
self.transactionsStore.saveTransaction({
|
||||
transaction: self.transaction,
|
||||
defaultCurrency: self.defaultCurrency,
|
||||
isEdit: self.mode === 'edit',
|
||||
clientSessionId: self.clientSessionId
|
||||
}).then(() => {
|
||||
self.submitting = false;
|
||||
|
||||
if (self.resolve) {
|
||||
if (self.mode === 'add') {
|
||||
self.resolve({
|
||||
message: 'You have added a new transaction'
|
||||
});
|
||||
} else if (self.mode === 'edit') {
|
||||
self.resolve({
|
||||
message: 'You have saved this transaction'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.showState = false;
|
||||
}).catch(error => {
|
||||
self.submitting = false;
|
||||
|
||||
if (!error.processed) {
|
||||
self.$refs.snackbar.showError(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (self.transaction.sourceAmount === 0) {
|
||||
self.$refs.confirmDialog.open('Are you sure you want to save this transaction with a zero amount?').then(() => {
|
||||
doSubmit();
|
||||
});
|
||||
} else {
|
||||
doSubmit();
|
||||
}
|
||||
} else if (self.mode === 'editTemplate') {
|
||||
self.submitting = true;
|
||||
|
||||
self.transactionsStore.saveTransaction({
|
||||
transaction: self.transaction,
|
||||
defaultCurrency: self.defaultCurrency,
|
||||
isEdit: self.mode === 'edit',
|
||||
clientSessionId: self.clientSessionId
|
||||
self.transactionTemplatesStore.modifyTemplateContent({
|
||||
template: self.transaction
|
||||
}).then(() => {
|
||||
self.submitting = false;
|
||||
|
||||
if (self.resolve) {
|
||||
if (self.mode === 'add') {
|
||||
self.resolve({
|
||||
message: 'You have added a new transaction'
|
||||
});
|
||||
} else if (self.mode === 'edit') {
|
||||
self.resolve({
|
||||
message: 'You have saved this transaction'
|
||||
});
|
||||
}
|
||||
self.resolve({
|
||||
message: 'You have saved this template'
|
||||
});
|
||||
}
|
||||
|
||||
self.showState = false;
|
||||
@@ -736,17 +779,13 @@ export default {
|
||||
self.$refs.snackbar.showError(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (self.transaction.sourceAmount === 0) {
|
||||
self.$refs.confirmDialog.open('Are you sure you want to save this transaction with a zero amount?').then(() => {
|
||||
doSubmit();
|
||||
});
|
||||
} else {
|
||||
doSubmit();
|
||||
}
|
||||
},
|
||||
duplicate() {
|
||||
if (this.mode !== 'view') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.editTransactionId = null;
|
||||
this.transaction.id = null;
|
||||
this.transaction.time = getCurrentUnixTime();
|
||||
@@ -756,11 +795,19 @@ export default {
|
||||
this.mode = 'add';
|
||||
},
|
||||
edit() {
|
||||
if (this.mode !== 'view') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.mode = 'edit';
|
||||
},
|
||||
remove() {
|
||||
const self = this;
|
||||
|
||||
if (self.mode !== 'view') {
|
||||
return;
|
||||
}
|
||||
|
||||
self.$refs.confirmDialog.open('Are you sure you want to delete this transaction?').then(() => {
|
||||
self.submitting = true;
|
||||
|
||||
@@ -873,7 +920,11 @@ export default {
|
||||
type: options.type,
|
||||
categoryId: options.categoryId,
|
||||
accountId: options.accountId,
|
||||
tagIds: options.tagIds
|
||||
destinationAccountId: options.destinationAccountId,
|
||||
amount: options.amount,
|
||||
destinationAmount: options.destinationAmount,
|
||||
tagIds: options.tagIds,
|
||||
comment: options.comment
|
||||
},
|
||||
setContextData,
|
||||
convertContextTime
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<f7-page>
|
||||
</f7-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'f7router'
|
||||
],
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user