transaction tag list page supports dragging to change display order

This commit is contained in:
MaysWind
2023-07-20 23:48:27 +08:00
parent d9c8dd20e9
commit 8f7095ce19
+163 -85
View File
@@ -5,6 +5,11 @@
<template #title> <template #title>
<div class="d-flex align-center"> <div class="d-flex align-center">
<span>{{ $t('Transaction Tags') }}</span> <span>{{ $t('Transaction Tags') }}</span>
<v-btn class="ml-3" color="default" variant="outlined"
:disabled="loading || updating || hasEditingTag" @click="add">{{ $t('Add') }}</v-btn>
<v-btn class="ml-3" color="primary" variant="tonal"
:disabled="loading" @click="saveSortResult"
v-if="displayOrderModified">{{ $t('Save Display Order') }}</v-btn>
<v-btn density="compact" color="default" variant="text" <v-btn density="compact" color="default" variant="text"
class="ml-2" :icon="true" :disabled="loading || updating || hasEditingTag" class="ml-2" :icon="true" :disabled="loading || updating || hasEditingTag"
v-if="!loading" @click="reload"> v-if="!loading" @click="reload">
@@ -12,12 +17,6 @@
<v-tooltip activator="parent">{{ $t('Refresh') }}</v-tooltip> <v-tooltip activator="parent">{{ $t('Refresh') }}</v-tooltip>
</v-btn> </v-btn>
<v-progress-circular indeterminate size="24" class="ml-2" v-if="loading"></v-progress-circular> <v-progress-circular indeterminate size="24" class="ml-2" v-if="loading"></v-progress-circular>
<v-btn density="compact" color="default" variant="text"
class="ml-2" :icon="true" :disabled="loading || updating || hasEditingTag"
@click="add">
<v-icon :icon="icons.add" size="24" />
<v-tooltip activator="parent" v-if="!loading && !updating && !hasEditingTag">{{ $t('Add') }}</v-tooltip>
</v-btn>
<v-spacer/> <v-spacer/>
<v-btn density="comfortable" color="default" variant="text" class="ml-2" <v-btn density="comfortable" color="default" variant="text" class="ml-2"
:disabled="loading || updating || hasEditingTag" :icon="true"> :disabled="loading || updating || hasEditingTag" :icon="true">
@@ -44,98 +43,115 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody v-if="loading && noAvailableTag && !newTag">
<tr :key="itemIdx" v-for="itemIdx in (loading && noAvailableTag ? [ 1, 2, 3 ] : [])"> <tr :key="itemIdx" v-for="itemIdx in [ 1, 2, 3 ]">
<td class="px-0" colspan="2"> <td class="px-0" colspan="2">
<v-skeleton-loader type="text" :loading="true"></v-skeleton-loader> <v-skeleton-loader type="text" :loading="true"></v-skeleton-loader>
</td> </td>
</tr> </tr>
</tbody>
<tr v-if="!loading && noAvailableTag && !newTag"> <tbody v-if="!loading && noAvailableTag && !newTag">
<tr>
<td colspan="2">{{ $t('No available tag') }}</td> <td colspan="2">{{ $t('No available tag') }}</td>
</tr> </tr>
</tbody>
<template :key="tag.id" v-for="tag in tags"> <draggable-list tag="tbody"
<tr v-if="showHidden || !tag.hidden"> item-key="id"
<td> handle=".drag-handle"
<div class="d-flex align-center" v-if="editingTag.id !== tag.id"> ghost-class="dragging-item"
<v-badge class="right-bottom-icon" color="secondary" :disabled="noAvailableTag"
location="bottom right" offset-x="8" :icon="icons.hide" v-model="tags"
v-if="tag.hidden"> @change="onMove">
<v-icon size="24" start :icon="icons.tag"/> <template #item="{ element }">
</v-badge> <tr v-show="showHidden || !element.hidden">
<v-icon size="24" start :icon="icons.tag" v-else-if="!tag.hidden"/> <td>
{{ tag.name }} <div class="d-flex align-center" v-if="editingTag.id !== element.id">
</div>
<v-text-field
type="text"
clearable
density="compact"
variant="underlined"
:disabled="loading || updating"
:placeholder="$t('Tag Title')"
v-model="editingTag.name"
v-else-if="editingTag.id === tag.id"
@keyup.enter="save(newTag)"
>
<template #prepend>
<v-badge class="right-bottom-icon" color="secondary" <v-badge class="right-bottom-icon" color="secondary"
location="bottom right" offset-x="8" :icon="icons.hide" location="bottom right" offset-x="8" :icon="icons.hide"
v-if="tag.hidden"> v-if="element.hidden">
<v-icon size="24" start :icon="icons.tag"/> <v-icon size="24" start :icon="icons.tag"/>
</v-badge> </v-badge>
<v-icon size="24" start :icon="icons.tag" v-else-if="!tag.hidden"/> <v-icon size="24" start :icon="icons.tag" v-else-if="!element.hidden"/>
</template> {{ element.name }}
</v-text-field> </div>
</td> <v-text-field
<td class="text-uppercase text-right"> type="text"
<v-btn class="px-2" color="default" clearable
density="comfortable" variant="text" density="compact"
:prepend-icon="icons.edit" variant="underlined"
:loading="tagUpdating[tag.id]" :disabled="loading || updating"
:disabled="loading || updating" :placeholder="$t('Tag Title')"
v-if="editingTag.id !== tag.id" v-model="editingTag.name"
@click="edit(tag)"> v-else-if="editingTag.id === element.id"
{{ $t('Edit') }} @keyup.enter="save(editingTag)"
</v-btn> >
<v-btn class="px-2 ml-2" color="default" <template #prepend>
density="comfortable" variant="text" <v-badge class="right-bottom-icon" color="secondary"
:prepend-icon="tag.hidden ? icons.show : icons.hide" location="bottom right" offset-x="8" :icon="icons.hide"
:loading="tagHiding[tag.id]" v-if="element.hidden">
:disabled="loading || updating" <v-icon size="24" start :icon="icons.tag"/>
v-if="editingTag.id !== tag.id" </v-badge>
@click="hide(tag, !tag.hidden)"> <v-icon size="24" start :icon="icons.tag" v-else-if="!element.hidden"/>
{{ tag.hidden ? $t('Show') : $t('Hide') }} </template>
</v-btn> </v-text-field>
<v-btn class="px-2 ml-2" color="default" </td>
density="comfortable" variant="text" <td class="text-uppercase text-right">
:prepend-icon="icons.remove" <v-btn class="px-2" color="default"
:loading="tagRemoving[tag.id]" density="comfortable" variant="text"
:disabled="loading || updating" :prepend-icon="icons.edit"
v-if="editingTag.id !== tag.id" :loading="tagUpdating[element.id]"
@click="remove(tag)"> :disabled="loading || updating"
{{ $t('Delete') }} v-if="editingTag.id !== element.id"
</v-btn> @click="edit(element)">
<v-btn class="px-2" {{ $t('Edit') }}
density="comfortable" variant="text" </v-btn>
:prepend-icon="icons.confirm" <v-btn class="px-2 ml-2" color="default"
:loading="tagUpdating[tag.id]" density="comfortable" variant="text"
:disabled="loading || updating || !isTagModified(tag)" :prepend-icon="element.hidden ? icons.show : icons.hide"
v-if="editingTag.id === tag.id" @click="save(editingTag)"> :loading="tagHiding[element.id]"
{{ $t('Save') }} :disabled="loading || updating"
</v-btn> v-if="editingTag.id !== element.id"
<v-btn class="px-2 ml-2" color="default" @click="hide(element, !element.hidden)">
density="comfortable" variant="text" {{ element.hidden ? $t('Show') : $t('Hide') }}
:prepend-icon="icons.cancel" </v-btn>
:disabled="loading || updating" <v-btn class="px-2 ml-2" color="default"
v-if="editingTag.id === tag.id" @click="cancelSave(editingTag)"> density="comfortable" variant="text"
{{ $t('Cancel') }} :prepend-icon="icons.remove"
</v-btn> :loading="tagRemoving[element.id]"
</td> :disabled="loading || updating"
</tr> v-if="editingTag.id !== element.id"
</template> @click="remove(element)">
{{ $t('Delete') }}
</v-btn>
<v-btn class="px-2"
density="comfortable" variant="text"
:prepend-icon="icons.confirm"
:loading="tagUpdating[element.id]"
:disabled="loading || updating || !isTagModified(element)"
v-if="editingTag.id === element.id" @click="save(editingTag)">
{{ $t('Save') }}
</v-btn>
<v-btn class="px-2 ml-2" color="default"
density="comfortable" variant="text"
:prepend-icon="icons.cancel"
:disabled="loading || updating"
v-if="editingTag.id === element.id" @click="cancelSave(editingTag)">
{{ $t('Cancel') }}
</v-btn>
<span>
<v-icon :class="availableTagCount > 1 ? 'drag-handle' : 'disabled'"
:icon="icons.drag"/>
<v-tooltip activator="parent" v-if="availableTagCount > 1">{{ $t('Drag and Drop to Change Order') }}</v-tooltip>
</span>
</td>
</tr>
</template>
</draggable-list>
<tr v-if="newTag"> <tbody v-if="newTag">
<tr>
<td> <td>
<v-text-field type="text" color="primary" clearable <v-text-field type="text" color="primary" clearable
density="compact" variant="underlined" density="compact" variant="underlined"
@@ -161,6 +177,9 @@
@click="cancelSave(newTag)"> @click="cancelSave(newTag)">
{{ $t('Cancel') }} {{ $t('Cancel') }}
</v-btn> </v-btn>
<span>
<v-icon class="disabled" :icon="icons.drag"/>
</span>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -186,6 +205,7 @@ import {
mdiEyeOffOutline, mdiEyeOffOutline,
mdiEyeOutline, mdiEyeOutline,
mdiDeleteOutline, mdiDeleteOutline,
mdiDrag,
mdiDotsVertical, mdiDotsVertical,
mdiPound mdiPound
} from '@mdi/js'; } from '@mdi/js';
@@ -203,6 +223,7 @@ export default {
tagUpdating: {}, tagUpdating: {},
tagHiding: {}, tagHiding: {},
tagRemoving: {}, tagRemoving: {},
displayOrderModified: false,
showHidden: false, showHidden: false,
icons: { icons: {
refresh: mdiRefresh, refresh: mdiRefresh,
@@ -213,6 +234,7 @@ export default {
show: mdiEyeOutline, show: mdiEyeOutline,
hide: mdiEyeOffOutline, hide: mdiEyeOffOutline,
remove: mdiDeleteOutline, remove: mdiDeleteOutline,
drag: mdiDrag,
more: mdiDotsVertical, more: mdiDotsVertical,
tag: mdiPound tag: mdiPound
} }
@@ -232,6 +254,17 @@ export default {
return true; return true;
}, },
availableTagCount() {
let count = 0;
for (let i = 0; i < this.tags.length; i++) {
if (this.showHidden || !this.tags[i].hidden) {
count++;
}
}
return count;
},
hasEditingTag() { hasEditingTag() {
return !!(this.newTag || (this.editingTag.id && this.editingTag.id !== '')); return !!(this.newTag || (this.editingTag.id && this.editingTag.id !== ''));
} }
@@ -266,6 +299,8 @@ export default {
force: true force: true
}).then(() => { }).then(() => {
self.loading = false; self.loading = false;
self.displayOrderModified = false;
self.$refs.snackbar.showMessage('Tag list has been updated'); self.$refs.snackbar.showMessage('Tag list has been updated');
}).catch(error => { }).catch(error => {
self.loading = false; self.loading = false;
@@ -275,6 +310,49 @@ export default {
} }
}); });
}, },
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 tag');
return;
}
self.transactionTagsStore.changeTagDisplayOrder({
tagId: 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.transactionTagsStore.updateTagDisplayOrders().then(() => {
self.loading = false;
self.displayOrderModified = false;
}).catch(error => {
self.loading = false;
if (!error.processed) {
self.$refs.snackbar.showError(error);
}
});
},
add() { add() {
this.newTag = { this.newTag = {
name: '' name: ''