mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-15 15:37:33 +08:00
support transaction tag group
This commit is contained in:
+419
-29
@@ -1,7 +1,13 @@
|
||||
import { ref, computed } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { type BeforeResolveFunction, itemAndIndex } from '@/core/base.ts';
|
||||
import { type BeforeResolveFunction, itemAndIndex, values } from '@/core/base.ts';
|
||||
|
||||
import {
|
||||
type TransactionTagGroupInfoResponse,
|
||||
type TransactionTagGroupNewDisplayOrderRequest,
|
||||
TransactionTagGroup
|
||||
} from '@/models/transaction_tag_group.ts';
|
||||
|
||||
import {
|
||||
type TransactionTagCreateBatchRequest,
|
||||
@@ -16,52 +22,176 @@ import logger from '@/lib/logger.ts';
|
||||
import services, { type ApiResponsePromise } from '@/lib/services.ts';
|
||||
|
||||
export const useTransactionTagsStore = defineStore('transactionTags', () => {
|
||||
const allTransactionTagGroups = ref<TransactionTagGroup[]>([]);
|
||||
const allTransactionTagGroupsMap = ref<Record<string, TransactionTagGroup>>({});
|
||||
const allTransactionTags = ref<TransactionTag[]>([]);
|
||||
const allTransactionTagsMap = ref<Record<string, TransactionTag>>({});
|
||||
const allTransactionTagsByGroupMap = ref<Record<string, TransactionTag[]>>({});
|
||||
const transactionTagGroupListStateInvalid = ref<boolean>(true);
|
||||
const transactionTagListStateInvalid = ref<boolean>(true);
|
||||
|
||||
const allVisibleTags = computed<TransactionTag[]>(() => {
|
||||
const visibleTags: TransactionTag[] = [];
|
||||
|
||||
for (const tag of allTransactionTags.value) {
|
||||
if (!tag.hidden) {
|
||||
visibleTags.push(tag);
|
||||
}
|
||||
}
|
||||
|
||||
return visibleTags;
|
||||
});
|
||||
|
||||
const allAvailableTagsCount = computed<number>(() => allTransactionTags.value.length);
|
||||
const allVisibleTagsCount = computed<number>(() => allVisibleTags.value.length);
|
||||
|
||||
function loadTransactionTagGroupList(tagGroups: TransactionTagGroup[]): void {
|
||||
allTransactionTagGroups.value = tagGroups;
|
||||
allTransactionTagGroupsMap.value = {};
|
||||
|
||||
for (const tagGroup of tagGroups) {
|
||||
allTransactionTagGroupsMap.value[tagGroup.id] = tagGroup;
|
||||
}
|
||||
}
|
||||
|
||||
function loadTransactionTagList(tags: TransactionTag[]): void {
|
||||
allTransactionTags.value = tags;
|
||||
allTransactionTagsMap.value = {};
|
||||
allTransactionTagsByGroupMap.value = {};
|
||||
|
||||
for (const tag of tags) {
|
||||
allTransactionTagsMap.value[tag.id] = tag;
|
||||
}
|
||||
|
||||
for (const tag of tags) {
|
||||
let tagsInGroup = allTransactionTagsByGroupMap.value[tag.groupId];
|
||||
|
||||
if (!tagsInGroup) {
|
||||
tagsInGroup = [];
|
||||
allTransactionTagsByGroupMap.value[tag.groupId] = tagsInGroup;
|
||||
}
|
||||
|
||||
tagsInGroup.push(tag);
|
||||
}
|
||||
}
|
||||
|
||||
function addTagGroupToTransactionTagGroupList(tagGroup: TransactionTagGroup): void {
|
||||
allTransactionTagGroups.value.push(tagGroup);
|
||||
allTransactionTagGroupsMap.value[tagGroup.id] = tagGroup;
|
||||
}
|
||||
|
||||
function addTagToTransactionTagList(tag: TransactionTag): void {
|
||||
allTransactionTags.value.push(tag);
|
||||
allTransactionTagsMap.value[tag.id] = tag;
|
||||
|
||||
let tagsInGroup = allTransactionTagsByGroupMap.value[tag.groupId];
|
||||
|
||||
if (!tagsInGroup) {
|
||||
tagsInGroup = [];
|
||||
allTransactionTagsByGroupMap.value[tag.groupId] = tagsInGroup;
|
||||
}
|
||||
|
||||
tagsInGroup.push(tag);
|
||||
}
|
||||
|
||||
function updateTagInTransactionTagList(currentTag: TransactionTag): void {
|
||||
for (const [transactionTag, index] of itemAndIndex(allTransactionTags.value)) {
|
||||
if (transactionTag.id === currentTag.id) {
|
||||
allTransactionTags.value.splice(index, 1, currentTag);
|
||||
function updateTagGroupInTransactionTagGroupList(currentTagGroup: TransactionTagGroup): void {
|
||||
for (const [transactionTagGroup, index] of itemAndIndex(allTransactionTagGroups.value)) {
|
||||
if (transactionTagGroup.id === currentTagGroup.id) {
|
||||
allTransactionTagGroups.value.splice(index, 1, currentTagGroup);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
allTransactionTagsMap.value[currentTag.id] = currentTag;
|
||||
allTransactionTagGroupsMap.value[currentTagGroup.id] = currentTagGroup;
|
||||
}
|
||||
|
||||
function updateTagDisplayOrderInTransactionTagList({ from, to }: { from: number, to: number }): void {
|
||||
allTransactionTags.value.splice(to, 0, allTransactionTags.value.splice(from, 1)[0] as TransactionTag);
|
||||
function updateTagInTransactionTagList(currentTag: TransactionTag, oldTagGroupId?: string): void {
|
||||
// update in the main list
|
||||
for (const [transactionTag, index] of itemAndIndex(allTransactionTags.value)) {
|
||||
if (transactionTag.id === currentTag.id) {
|
||||
if (oldTagGroupId && oldTagGroupId !== currentTag.groupId) {
|
||||
allTransactionTags.value.splice(index, 1);
|
||||
} else {
|
||||
allTransactionTags.value.splice(index, 1, currentTag);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldTagGroupId && oldTagGroupId !== currentTag.groupId) {
|
||||
let insertIndex = allTransactionTags.value.length;
|
||||
|
||||
for (const [tag, index] of itemAndIndex(allTransactionTags.value)) {
|
||||
if (tag.groupId === currentTag.groupId) {
|
||||
insertIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
allTransactionTags.value.splice(insertIndex, 0, currentTag);
|
||||
}
|
||||
|
||||
// update in the map
|
||||
allTransactionTagsMap.value[currentTag.id] = currentTag;
|
||||
|
||||
// update in the group list
|
||||
for (const tags of values(allTransactionTagsByGroupMap.value)) {
|
||||
for (const [transactionTag, index] of itemAndIndex(tags)) {
|
||||
if (transactionTag.id === currentTag.id) {
|
||||
if (oldTagGroupId && oldTagGroupId !== currentTag.groupId) {
|
||||
tags.splice(index, 1);
|
||||
} else {
|
||||
tags.splice(index, 1, currentTag);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (oldTagGroupId && oldTagGroupId !== currentTag.groupId) {
|
||||
let newGroupTags = allTransactionTagsByGroupMap.value[currentTag.groupId];
|
||||
|
||||
if (!newGroupTags) {
|
||||
newGroupTags = [];
|
||||
allTransactionTagsByGroupMap.value[currentTag.groupId] = newGroupTags;
|
||||
}
|
||||
|
||||
newGroupTags.push(currentTag);
|
||||
}
|
||||
}
|
||||
|
||||
function updateTagGroupDisplayOrderInTransactionTagList({ from, to }: { from: number, to: number }): void {
|
||||
allTransactionTagGroups.value.splice(to, 0, allTransactionTagGroups.value.splice(from, 1)[0] as TransactionTagGroup);
|
||||
}
|
||||
|
||||
function updateTagDisplayOrderInTransactionTagList({ groupId, from, to }: { groupId: string, from: number, to: number }): void {
|
||||
// update in the group list
|
||||
const tagsInGroup = allTransactionTagsByGroupMap.value[groupId];
|
||||
|
||||
if (!tagsInGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fromTag = tagsInGroup[from];
|
||||
|
||||
if (!fromTag) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toTag = tagsInGroup[to];
|
||||
|
||||
if (!toTag) {
|
||||
return;
|
||||
}
|
||||
|
||||
tagsInGroup.splice(to, 0, tagsInGroup.splice(from, 1)[0] as TransactionTag);
|
||||
|
||||
// update in the main list
|
||||
let mainListFromIndex = -1;
|
||||
let mainListToIndex = -1;
|
||||
|
||||
for (const [tag, index] of itemAndIndex(allTransactionTags.value)) {
|
||||
if (tag.id === fromTag.id) {
|
||||
mainListFromIndex = index;
|
||||
}
|
||||
|
||||
if (tag.id === toTag.id) {
|
||||
mainListToIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
if (mainListFromIndex === -1 || mainListToIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
allTransactionTags.value.splice(mainListToIndex, 0, allTransactionTags.value.splice(mainListFromIndex, 1)[0] as TransactionTag);
|
||||
}
|
||||
|
||||
function updateTagVisibilityInTransactionTagList({ tag, hidden }: { tag: TransactionTag, hidden: boolean }): void {
|
||||
@@ -70,6 +200,19 @@ export const useTransactionTagsStore = defineStore('transactionTags', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function removeTagGroupFromTransactionTagGroupList(currentTagGroup: TransactionTagGroup): void {
|
||||
for (const [transactionTagGroup, index] of itemAndIndex(allTransactionTagGroups.value)) {
|
||||
if (transactionTagGroup.id === currentTagGroup.id) {
|
||||
allTransactionTagGroups.value.splice(index, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allTransactionTagGroupsMap.value[currentTagGroup.id]) {
|
||||
delete allTransactionTagGroupsMap.value[currentTagGroup.id];
|
||||
}
|
||||
}
|
||||
|
||||
function removeTagFromTransactionTagList(currentTag: TransactionTag): void {
|
||||
for (const [transactionTag, index] of itemAndIndex(allTransactionTags.value)) {
|
||||
if (transactionTag.id === currentTag.id) {
|
||||
@@ -81,6 +224,19 @@ export const useTransactionTagsStore = defineStore('transactionTags', () => {
|
||||
if (allTransactionTagsMap.value[currentTag.id]) {
|
||||
delete allTransactionTagsMap.value[currentTag.id];
|
||||
}
|
||||
|
||||
for (const tags of values(allTransactionTagsByGroupMap.value)) {
|
||||
for (const [transactionTag, index] of itemAndIndex(tags)) {
|
||||
if (transactionTag.id === currentTag.id) {
|
||||
tags.splice(index, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateTransactionTagGroupListInvalidState(invalidState: boolean): void {
|
||||
transactionTagGroupListStateInvalid.value = invalidState;
|
||||
}
|
||||
|
||||
function updateTransactionTagListInvalidState(invalidState: boolean): void {
|
||||
@@ -88,20 +244,85 @@ export const useTransactionTagsStore = defineStore('transactionTags', () => {
|
||||
}
|
||||
|
||||
function resetTransactionTags(): void {
|
||||
allTransactionTagGroups.value = [];
|
||||
allTransactionTagGroupsMap.value = {};
|
||||
allTransactionTags.value = [];
|
||||
allTransactionTagsMap.value = {};
|
||||
allTransactionTagsByGroupMap.value = {};
|
||||
transactionTagGroupListStateInvalid.value = true;
|
||||
transactionTagListStateInvalid.value = true;
|
||||
}
|
||||
|
||||
function loadAllTagGroups({ force }: { force?: boolean }): Promise<TransactionTagGroup[]> {
|
||||
if (!force && !transactionTagGroupListStateInvalid.value) {
|
||||
return new Promise((resolve) => {
|
||||
resolve(allTransactionTagGroups.value);
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
services.getAllTransactionTagGroups().then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to retrieve tag group list' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (transactionTagGroupListStateInvalid.value) {
|
||||
updateTransactionTagGroupListInvalidState(false);
|
||||
}
|
||||
|
||||
const transactionTagGroups = TransactionTagGroup.ofMulti(data.result);
|
||||
|
||||
loadTransactionTagGroupList(transactionTagGroups);
|
||||
|
||||
resolve(transactionTagGroups);
|
||||
}).catch(error => {
|
||||
logger.error('failed to load tag group list', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to retrieve tag group list' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadAllTags({ force }: { force?: boolean }): Promise<TransactionTag[]> {
|
||||
if (!force && !transactionTagListStateInvalid.value) {
|
||||
if (!force && !transactionTagGroupListStateInvalid.value && !transactionTagListStateInvalid.value) {
|
||||
return new Promise((resolve) => {
|
||||
resolve(allTransactionTags.value);
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
services.getAllTransactionTags().then(response => {
|
||||
services.getAllTransactionTagGroups().then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to retrieve tag list' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (transactionTagGroupListStateInvalid.value) {
|
||||
updateTransactionTagGroupListInvalidState(false);
|
||||
}
|
||||
|
||||
const transactionTagGroups = TransactionTagGroup.ofMulti(data.result);
|
||||
|
||||
loadTransactionTagGroupList(transactionTagGroups);
|
||||
|
||||
return services.getAllTransactionTags();
|
||||
}).then(response => {
|
||||
if (!response) {
|
||||
reject({ message: 'Unable to retrieve tag list' });
|
||||
return;
|
||||
}
|
||||
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
@@ -141,7 +362,159 @@ export const useTransactionTagsStore = defineStore('transactionTags', () => {
|
||||
});
|
||||
}
|
||||
|
||||
function saveTagGroup({ tagGroup }: { tagGroup: TransactionTagGroup }): Promise<TransactionTagGroup> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let promise: ApiResponsePromise<TransactionTagGroupInfoResponse>;
|
||||
|
||||
if (!tagGroup.id) {
|
||||
promise = services.addTransactionTagGroup(tagGroup.toCreateRequest());
|
||||
} else {
|
||||
promise = services.modifyTransactionTagGroup(tagGroup.toModifyRequest());
|
||||
}
|
||||
|
||||
promise.then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
if (!tagGroup.id) {
|
||||
reject({ message: 'Unable to add tag group' });
|
||||
} else {
|
||||
reject({ message: 'Unable to save tag group' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const transactionTagGroup = TransactionTagGroup.of(data.result);
|
||||
|
||||
if (!tagGroup.id) {
|
||||
addTagGroupToTransactionTagGroupList(transactionTagGroup);
|
||||
} else {
|
||||
updateTagGroupInTransactionTagGroupList(transactionTagGroup);
|
||||
}
|
||||
|
||||
resolve(transactionTagGroup);
|
||||
}).catch(error => {
|
||||
logger.error('failed to save tag group', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
if (!tagGroup.id) {
|
||||
reject({ message: 'Unable to add tag group' });
|
||||
} else {
|
||||
reject({ message: 'Unable to save tag group' });
|
||||
}
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function changeTagGroupDisplayOrder({ tagGroupId, from, to }: { tagGroupId: string, from: number, to: number }): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let currentTagGroup: TransactionTagGroup | null = null;
|
||||
|
||||
for (const transactionTagGroup of allTransactionTagGroups.value) {
|
||||
if (transactionTagGroup.id === tagGroupId) {
|
||||
currentTagGroup = transactionTagGroup;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentTagGroup || !allTransactionTagGroups.value[to]) {
|
||||
reject({ message: 'Unable to move tag group' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!transactionTagGroupListStateInvalid.value) {
|
||||
updateTransactionTagGroupListInvalidState(true);
|
||||
}
|
||||
|
||||
updateTagGroupDisplayOrderInTransactionTagList({ from, to });
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function updateTagGroupDisplayOrders(): Promise<boolean> {
|
||||
const newDisplayOrders: TransactionTagGroupNewDisplayOrderRequest[] = [];
|
||||
|
||||
for (const [transactionTagGroup, index] of itemAndIndex(allTransactionTagGroups.value)) {
|
||||
newDisplayOrders.push({
|
||||
id: transactionTagGroup.id,
|
||||
displayOrder: index + 1
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
services.moveTransactionTagGroup({
|
||||
newDisplayOrders: newDisplayOrders
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to move tag group' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (transactionTagGroupListStateInvalid.value) {
|
||||
updateTransactionTagGroupListInvalidState(false);
|
||||
}
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to save tag groups display order', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to move tag group' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deleteTagGroup({ tagGroup, beforeResolve }: { tagGroup: TransactionTagGroup, beforeResolve?: BeforeResolveFunction }): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.deleteTransactionTagGroup({
|
||||
id: tagGroup.id
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to delete this tag group' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (beforeResolve) {
|
||||
beforeResolve(() => {
|
||||
removeTagGroupFromTransactionTagGroupList(tagGroup);
|
||||
});
|
||||
} else {
|
||||
removeTagGroupFromTransactionTagGroupList(tagGroup);
|
||||
}
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to delete tag group', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to delete this tag group' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function saveTag({ tag }: { tag: TransactionTag }): Promise<TransactionTag> {
|
||||
const oldTagGroupId = allTransactionTagsMap.value[tag.id]?.groupId;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let promise: ApiResponsePromise<TransactionTagInfoResponse>;
|
||||
|
||||
@@ -168,7 +541,7 @@ export const useTransactionTagsStore = defineStore('transactionTags', () => {
|
||||
if (!tag.id) {
|
||||
addTagToTransactionTagList(transactionTag);
|
||||
} else {
|
||||
updateTagInTransactionTagList(transactionTag);
|
||||
updateTagInTransactionTagList(transactionTag, oldTagGroupId);
|
||||
}
|
||||
|
||||
resolve(transactionTag);
|
||||
@@ -241,16 +614,26 @@ export const useTransactionTagsStore = defineStore('transactionTags', () => {
|
||||
updateTransactionTagListInvalidState(true);
|
||||
}
|
||||
|
||||
updateTagDisplayOrderInTransactionTagList({ from, to });
|
||||
updateTagDisplayOrderInTransactionTagList({
|
||||
groupId: currentTag.groupId,
|
||||
from: from,
|
||||
to: to
|
||||
});
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function updateTagDisplayOrders(): Promise<boolean> {
|
||||
function updateTagDisplayOrders(groupId: string): Promise<boolean> {
|
||||
const tagsInGroup = allTransactionTagsByGroupMap.value[groupId];
|
||||
|
||||
if (!tagsInGroup) {
|
||||
return Promise.reject('Unable to move tag');
|
||||
}
|
||||
|
||||
const newDisplayOrders: TransactionTagNewDisplayOrderRequest[] = [];
|
||||
|
||||
for (const [transactionTag, index] of itemAndIndex(allTransactionTags.value)) {
|
||||
for (const [transactionTag, index] of itemAndIndex(tagsInGroup)) {
|
||||
newDisplayOrders.push({
|
||||
id: transactionTag.id,
|
||||
displayOrder: index + 1
|
||||
@@ -362,17 +745,24 @@ export const useTransactionTagsStore = defineStore('transactionTags', () => {
|
||||
|
||||
return {
|
||||
// states
|
||||
allTransactionTagGroups,
|
||||
allTransactionTagGroupsMap,
|
||||
allTransactionTags,
|
||||
allTransactionTagsMap,
|
||||
allTransactionTagsByGroupMap,
|
||||
transactionTagGroupListStateInvalid,
|
||||
transactionTagListStateInvalid,
|
||||
// computed states
|
||||
allVisibleTags,
|
||||
allAvailableTagsCount,
|
||||
allVisibleTagsCount,
|
||||
// functions
|
||||
updateTransactionTagListInvalidState,
|
||||
resetTransactionTags,
|
||||
loadAllTagGroups,
|
||||
loadAllTags,
|
||||
saveTagGroup,
|
||||
changeTagGroupDisplayOrder,
|
||||
updateTagGroupDisplayOrders,
|
||||
deleteTagGroup,
|
||||
saveTag,
|
||||
addTags,
|
||||
changeTagDisplayOrder,
|
||||
|
||||
Reference in New Issue
Block a user