774 lines
33 KiB
Vue
774 lines
33 KiB
Vue
<template>
|
|
<v-row class="match-height">
|
|
<v-col cols="12">
|
|
<v-card>
|
|
<v-layout>
|
|
<v-navigation-drawer :permanent="alwaysShowNav" v-model="showNav">
|
|
<div class="mx-6 my-4">
|
|
<btn-vertical-group :disabled="loading || updating" :buttons="allTabs" v-model="activeTab" />
|
|
</div>
|
|
<v-divider />
|
|
<v-tabs show-arrows
|
|
class="scrollable-vertical-tabs"
|
|
style="max-height: calc(100% - 150px)"
|
|
direction="vertical"
|
|
:prev-icon="mdiMenuUp" :next-icon="mdiMenuDown"
|
|
:key="currentExplorer.id" :disabled="loading || updating"
|
|
:model-value="currentExplorer.id">
|
|
<v-tab class="tab-text-truncate" key="new" value="" @click="createNewExplorer">
|
|
<span class="text-truncate">{{ tt('New Explorer') }}</span>
|
|
</v-tab>
|
|
<v-tab class="tab-text-truncate" :key="explorer.id" :value="explorer.id"
|
|
:disabled="loading || updating"
|
|
v-for="explorer in allExplorers"
|
|
@click="loadExplorer(explorer.id)">
|
|
<span class="text-truncate">{{ explorer.name || tt('Untitled Explorer') }}</span>
|
|
</v-tab>
|
|
<template v-if="loading && (!allExplorers || allExplorers.length < 1)">
|
|
<v-skeleton-loader class="skeleton-no-margin mx-5 mt-4 mb-3" type="text"
|
|
:key="itemIdx" :loading="true" v-for="itemIdx in [ 1, 2, 3, 4, 5 ]"></v-skeleton-loader>
|
|
</template>
|
|
</v-tabs>
|
|
</v-navigation-drawer>
|
|
<v-main>
|
|
<v-card variant="flat" min-height="800">
|
|
<template #title>
|
|
<div class="title-and-toolbar d-flex align-center">
|
|
<v-btn class="me-3 d-md-none" density="compact" color="default" variant="plain"
|
|
:ripple="false" :icon="true" @click="showNav = !showNav">
|
|
<v-icon :icon="mdiMenu" size="24" />
|
|
</v-btn>
|
|
<span>{{ tt('Insights Explorer') }}</span>
|
|
<v-btn-group class="ms-4" color="default" density="comfortable" variant="outlined" divided>
|
|
<v-btn class="button-icon-with-direction" :icon="mdiArrowLeft"
|
|
:disabled="loading || updating || !canShiftDateRange"
|
|
@click="shiftDateRange(-1)"/>
|
|
<v-menu location="bottom" max-height="500">
|
|
<template #activator="{ props }">
|
|
<v-btn :disabled="loading || updating"
|
|
v-bind="props">{{ displayQueryDateRangeName }}</v-btn>
|
|
</template>
|
|
<v-list :selected="[currentFilter.dateRangeType]">
|
|
<v-list-item :key="dateRange.type" :value="dateRange.type"
|
|
:append-icon="(currentFilter.dateRangeType === dateRange.type ? mdiCheck : undefined)"
|
|
v-for="dateRange in allDateRanges">
|
|
<v-list-item-title class="cursor-pointer"
|
|
@click="setDateFilter(dateRange.type)">
|
|
<div class="d-flex align-center">
|
|
<span>{{ dateRange.displayName }}</span>
|
|
</div>
|
|
<div class="statistics-custom-datetime-range smaller" v-if="dateRange.isUserCustomRange && currentFilter.dateRangeType === dateRange.type && !!currentFilter.startTime && !!currentFilter.endTime">
|
|
<span>{{ displayQueryStartTime }}</span>
|
|
<span> - </span>
|
|
<br/>
|
|
<span>{{ displayQueryEndTime }}</span>
|
|
</div>
|
|
</v-list-item-title>
|
|
</v-list-item>
|
|
</v-list>
|
|
</v-menu>
|
|
<v-btn class="button-icon-with-direction" :icon="mdiArrowRight"
|
|
:disabled="loading || updating || !canShiftDateRange"
|
|
@click="shiftDateRange(1)"/>
|
|
</v-btn-group>
|
|
|
|
<v-btn density="compact" color="default" variant="text" size="24"
|
|
class="ms-2" :icon="true" :loading="loading" :disabled="updating" @click="reload(true)">
|
|
<template #loader>
|
|
<v-progress-circular indeterminate size="20"/>
|
|
</template>
|
|
<v-icon :icon="mdiRefresh" size="24" />
|
|
<v-tooltip activator="parent">{{ tt('Refresh') }}</v-tooltip>
|
|
</v-btn>
|
|
<v-spacer/>
|
|
<v-btn class="ms-3"
|
|
:color="isCurrentExplorerModified ? 'primary' : 'default'"
|
|
:variant="isCurrentExplorerModified ? 'elevated' : 'outlined'"
|
|
:disabled="loading || updating" @click="saveExplorer(false)">
|
|
{{ tt('Save Explorer') }}
|
|
<v-progress-circular indeterminate size="22" class="ms-2" v-if="updating"></v-progress-circular>
|
|
<v-menu activator="parent" :open-on-hover="true">
|
|
<v-list>
|
|
<v-list-item @click="saveExplorer(true)">
|
|
<v-list-item-title>{{ tt('Save As New Explorer') }}</v-list-item-title>
|
|
</v-list-item>
|
|
<v-list-item @click="restoreExplorer()" v-if="currentExplorer.id">
|
|
<v-list-item-title>{{ tt('Restore to Last Saved') }}</v-list-item-title>
|
|
</v-list-item>
|
|
</v-list>
|
|
</v-menu>
|
|
</v-btn>
|
|
<v-btn density="comfortable" color="default" variant="text" class="ms-2"
|
|
:disabled="loading || updating" :icon="true">
|
|
<v-icon :icon="mdiDotsVertical" />
|
|
<v-menu activator="parent">
|
|
<v-list>
|
|
<v-list-subheader :title="tt('Timezone Used for Date Range')"
|
|
v-if="activeTab === 'query'"/>
|
|
<template v-if="activeTab === 'query'">
|
|
<v-list-item :key="timezoneType.type" :value="timezoneType.type"
|
|
:prepend-icon="timezoneTypeIconMap[timezoneType.type]"
|
|
:append-icon="(currentExplorer.timezoneUsedForDateRange === timezoneType.type ? mdiCheck : undefined)"
|
|
:title="timezoneType.displayName"
|
|
v-for="timezoneType in allTimezoneTypesUsedForDateRange"
|
|
@click="currentExplorer.timezoneUsedForDateRange = timezoneType.type"></v-list-item>
|
|
</template>
|
|
<v-list-item :prepend-icon="mdiExport"
|
|
:title="tt('Export Results')"
|
|
:disabled="loading || updating || (activeTab === 'table' && (!filteredTransactionsInDataTable || filteredTransactionsInDataTable.length < 1))"
|
|
@click="exportResults"
|
|
v-if="activeTab === 'table' || activeTab === 'chart'"></v-list-item>
|
|
<v-divider class="my-2" v-if="currentExplorer.id" />
|
|
<v-list-item :prepend-icon="mdiPencilOutline" @click="setExplorerName" v-if="currentExplorer.id">
|
|
<v-list-item-title>{{ tt('Rename Explorer') }}</v-list-item-title>
|
|
</v-list-item>
|
|
<v-list-item :prepend-icon="mdiEyeOffOutline" @click="hideExplorer(true)" v-if="currentExplorer.id && !currentExplorer.hidden">
|
|
<v-list-item-title>{{ tt('Hide Explorer') }}</v-list-item-title>
|
|
</v-list-item>
|
|
<v-list-item :prepend-icon="mdiEyeOutline" @click="hideExplorer(false)" v-if="currentExplorer.id && currentExplorer.hidden">
|
|
<v-list-item-title>{{ tt('Unhide Explorer') }}</v-list-item-title>
|
|
</v-list-item>
|
|
<v-list-item :prepend-icon="mdiDeleteOutline" @click="removeExplorer" v-if="currentExplorer.id">
|
|
<v-list-item-title>{{ tt('Delete Explorer') }}</v-list-item-title>
|
|
</v-list-item>
|
|
<v-divider class="my-2"/>
|
|
<v-list-item :prepend-icon="mdiSort"
|
|
:disabled="!allExplorers || allExplorers.length < 2"
|
|
:title="tt('Change Explorer Display Order')"
|
|
@click="showChangeExplorerDisplayOrderDialog"></v-list-item>
|
|
</v-list>
|
|
</v-menu>
|
|
</v-btn>
|
|
</div>
|
|
</template>
|
|
|
|
<v-window class="d-flex flex-grow-1 disable-tab-transition w-100-window-container" v-model="activeTab">
|
|
<v-window-item value="query">
|
|
<explorer-query-tab :loading="loading" :disabled="loading || updating" />
|
|
</v-window-item>
|
|
<v-window-item value="table">
|
|
<explorer-data-table-tab ref="explorerDataTableTab"
|
|
:loading="loading" :disabled="loading || updating"
|
|
@click:transaction="onShowTransaction" />
|
|
</v-window-item>
|
|
<v-window-item value="chart">
|
|
<explorer-chart-tab ref="explorerChartTab"
|
|
:loading="loading" :disabled="loading || updating" />
|
|
</v-window-item>
|
|
</v-window>
|
|
</v-card>
|
|
</v-main>
|
|
</v-layout>
|
|
</v-card>
|
|
</v-col>
|
|
</v-row>
|
|
|
|
<date-range-selection-dialog :title="tt('Custom Date Range')"
|
|
:min-time="currentFilter.startTime"
|
|
:max-time="currentFilter.endTime"
|
|
v-model:show="showCustomDateRangeDialog"
|
|
@dateRange:change="setCustomDateFilter"
|
|
@error="onShowDateRangeError" />
|
|
|
|
<explorer-list-dialog ref="explorerListDialog" />
|
|
<edit-dialog ref="editDialog" :type="TransactionEditPageType.Transaction" />
|
|
<export-dialog ref="exportDialog" />
|
|
|
|
<rename-dialog ref="renameDialog"
|
|
:default-title="tt('Rename Explorer')"
|
|
:label="tt('Explorer Name')" :placeholder="tt('Explorer Name')" />
|
|
<confirm-dialog ref="confirmDialog"/>
|
|
<snack-bar ref="snackbar" />
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import RenameDialog from '@/components/desktop/RenameDialog.vue';
|
|
import ConfirmDialog from '@/components/desktop/ConfirmDialog.vue';
|
|
import SnackBar from '@/components/desktop/SnackBar.vue';
|
|
import ExplorerQueryTab from '@/views/desktop/insights/tabs/ExplorerQueryTab.vue';
|
|
import ExplorerDataTableTab from '@/views/desktop/insights/tabs/ExplorerDataTableTab.vue';
|
|
import ExplorerChartTab from '@/views/desktop/insights/tabs/ExplorerChartTab.vue';
|
|
import ExplorerListDialog from '@/views/desktop/insights/dialogs/ExplorerListDialog.vue';
|
|
import EditDialog from '@/views/desktop/transactions/list/dialogs/EditDialog.vue';
|
|
import ExportDialog from '@/views/desktop/statistics/transaction/dialogs/ExportDialog.vue';
|
|
|
|
import { ref, computed, useTemplateRef, watch, nextTick } from 'vue';
|
|
import { useRouter, onBeforeRouteUpdate } from 'vue-router';
|
|
import { useDisplay } from 'vuetify';
|
|
|
|
import { useI18n } from '@/locales/helpers.ts';
|
|
import { TransactionEditPageType } from '@/views/base/transactions/TransactionEditPageBase.ts';
|
|
|
|
import { useUserStore } from '@/stores/user.ts';
|
|
import { useAccountsStore } from '@/stores/account.ts';
|
|
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
|
import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
|
|
import { type TransactionExplorerPartialFilter, type TransactionExplorerFilter, useExplorersStore } from '@/stores/explorer.ts';
|
|
|
|
import type { TypeAndDisplayName } from '@/core/base.ts';
|
|
import { type WeekDayValue, type LocalizedDateRange, DateRangeScene, DateRange } from '@/core/datetime.ts';
|
|
import { TimezoneTypeForStatistics } from '@/core/timezone.ts';
|
|
import { KnownErrorCode } from '@/consts/api.ts';
|
|
|
|
import { type TransactionInsightDataItem, Transaction } from '@/models/transaction.ts';
|
|
import { InsightsExplorerBasicInfo, InsightsExplorer } from '@/models/explorer.ts';
|
|
|
|
import {
|
|
parseDateTimeFromUnixTime,
|
|
getShiftedDateRangeAndDateType,
|
|
getDateTypeByDateRange,
|
|
getDateRangeByDateType
|
|
} from '@/lib/datetime.ts';
|
|
|
|
import { generateRandomUUID } from '@/lib/misc.ts';
|
|
|
|
import {
|
|
mdiMenu,
|
|
mdiArrowLeft,
|
|
mdiArrowRight,
|
|
mdiMenuUp,
|
|
mdiMenuDown,
|
|
mdiCheck,
|
|
mdiRefresh,
|
|
mdiDotsVertical,
|
|
mdiPencilOutline,
|
|
mdiEyeOutline,
|
|
mdiEyeOffOutline,
|
|
mdiDeleteOutline,
|
|
mdiSort,
|
|
mdiHomeClockOutline,
|
|
mdiInvoiceTextClockOutline,
|
|
mdiExport
|
|
} from '@mdi/js';
|
|
|
|
interface InsightsExplorerProps {
|
|
initId?: string;
|
|
initActiveTab?: string,
|
|
initDateRangeType?: string,
|
|
initStartTime?: string,
|
|
initEndTime?: string,
|
|
}
|
|
|
|
const props = defineProps<InsightsExplorerProps>();
|
|
|
|
type ExplorerPageTabType = 'query' | 'table' | 'chart';
|
|
|
|
type RenameDialogType = InstanceType<typeof RenameDialog>;
|
|
type ConfirmDialogType = InstanceType<typeof ConfirmDialog>;
|
|
type SnackBarType = InstanceType<typeof SnackBar>;
|
|
type ExplorerDataTableTabType = InstanceType<typeof ExplorerDataTableTab>;
|
|
type ExplorerChartTabType = InstanceType<typeof ExplorerChartTab>;
|
|
type ExplorerListDialogType = InstanceType<typeof ExplorerListDialog>;
|
|
type EditDialogType = InstanceType<typeof EditDialog>;
|
|
type ExportDialogType = InstanceType<typeof ExportDialog>;
|
|
|
|
const router = useRouter();
|
|
const display = useDisplay();
|
|
|
|
const {
|
|
tt,
|
|
getAllDateRanges,
|
|
getAllTimezoneTypesUsedForStatistics,
|
|
formatDateTimeToLongDateTime,
|
|
formatDateRange
|
|
} = useI18n();
|
|
|
|
const userStore = useUserStore();
|
|
const accountsStore = useAccountsStore();
|
|
const transactionCategoriesStore = useTransactionCategoriesStore();
|
|
const transactionTagsStore = useTransactionTagsStore();
|
|
const explorersStore = useExplorersStore();
|
|
|
|
const timezoneTypeIconMap = {
|
|
[TimezoneTypeForStatistics.ApplicationTimezone.type]: mdiHomeClockOutline,
|
|
[TimezoneTypeForStatistics.TransactionTimezone.type]: mdiInvoiceTextClockOutline
|
|
};
|
|
|
|
const renameDialog = useTemplateRef<RenameDialogType>('renameDialog');
|
|
const confirmDialog = useTemplateRef<ConfirmDialogType>('confirmDialog');
|
|
const snackbar = useTemplateRef<SnackBarType>('snackbar');
|
|
const explorerDataTableTab = useTemplateRef<ExplorerDataTableTabType>('explorerDataTableTab');
|
|
const explorerChartTab = useTemplateRef<ExplorerChartTabType>('explorerChartTab');
|
|
const explorerListDialog = useTemplateRef<ExplorerListDialogType>('explorerListDialog');
|
|
const exportDialog = useTemplateRef<ExportDialogType>('exportDialog');
|
|
const editDialog = useTemplateRef<EditDialogType>('editDialog');
|
|
|
|
const loading = ref<boolean>(true);
|
|
const initing = ref<boolean>(true);
|
|
const updating = ref<boolean>(false);
|
|
const clientSessionId = ref<string>('');
|
|
const isCurrentExplorerModified = ref<boolean>(false);
|
|
const alwaysShowNav = ref<boolean>(display.mdAndUp.value);
|
|
const showNav = ref<boolean>(display.mdAndUp.value);
|
|
const activeTab = ref<ExplorerPageTabType>('query');
|
|
const showCustomDateRangeDialog = ref<boolean>(false);
|
|
|
|
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
|
|
const fiscalYearStart = computed<number>(() => userStore.currentUserFiscalYearStart);
|
|
|
|
const allExplorers = computed<InsightsExplorerBasicInfo[]>(() => {
|
|
const ret: InsightsExplorerBasicInfo[] = [];
|
|
let hasCurrentExplorer = false;
|
|
|
|
for (const explorer of explorersStore.allInsightsExplorerBasicInfos) {
|
|
if (!explorer.hidden || (explorer.id && explorer.id === currentExplorer.value.id)) {
|
|
ret.push(explorer);
|
|
|
|
if (explorer.id && explorer.id === currentExplorer.value.id) {
|
|
hasCurrentExplorer = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!hasCurrentExplorer && currentExplorer.value && currentExplorer.value.id) {
|
|
ret.push(InsightsExplorerBasicInfo.of(currentExplorer.value));
|
|
}
|
|
|
|
return ret;
|
|
});
|
|
const currentFilter = computed<TransactionExplorerFilter>(() => explorersStore.transactionExplorerFilter);
|
|
const currentExplorer = computed<InsightsExplorer>(() => explorersStore.currentInsightsExplorer);
|
|
const filteredTransactionsInDataTable = computed<TransactionInsightDataItem[]>(() => explorersStore.filteredTransactionsInDataTable);
|
|
|
|
const allDateRanges = computed<LocalizedDateRange[]>(() => getAllDateRanges(DateRangeScene.InsightsExplorer, true));
|
|
const allTimezoneTypesUsedForDateRange = computed<TypeAndDisplayName[]>(() => getAllTimezoneTypesUsedForStatistics());
|
|
const canShiftDateRange = computed<boolean>(() => currentFilter.value.dateRangeType !== DateRange.All.type);
|
|
const displayQueryDateRangeName = computed<string>(() => formatDateRange(currentFilter.value.dateRangeType, currentFilter.value.startTime, currentFilter.value.endTime));
|
|
const displayQueryStartTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(currentFilter.value.startTime)));
|
|
const displayQueryEndTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(currentFilter.value.endTime)));
|
|
|
|
const allTabs = computed<{ name: string, value: ExplorerPageTabType }[]>(() => {
|
|
return [
|
|
{
|
|
name: tt('Query'),
|
|
value: 'query'
|
|
},
|
|
{
|
|
name: tt('Data Table'),
|
|
value: 'table'
|
|
},
|
|
{
|
|
name: tt('Chart'),
|
|
value: 'chart'
|
|
}
|
|
];
|
|
});
|
|
|
|
function getFilterLinkUrl(): string {
|
|
return `/insights/explorer?${explorersStore.getTransactionExplorerPageParams(currentExplorer.value.id, activeTab.value)}`;
|
|
}
|
|
|
|
function init(initProps: InsightsExplorerProps): void {
|
|
clientSessionId.value = generateRandomUUID();
|
|
|
|
const filter: TransactionExplorerPartialFilter = {
|
|
dateRangeType: initProps.initDateRangeType ? parseInt(initProps.initDateRangeType) : undefined,
|
|
startTime: initProps.initStartTime ? parseInt(initProps.initStartTime) : undefined,
|
|
endTime: initProps.initEndTime ? parseInt(initProps.initEndTime) : undefined
|
|
};
|
|
|
|
let needReload = false;
|
|
|
|
if (filter.dateRangeType !== currentFilter.value.dateRangeType) {
|
|
needReload = true;
|
|
} else if (filter.dateRangeType === DateRange.Custom.type) {
|
|
if (filter.startTime !== currentFilter.value.startTime
|
|
|| filter.endTime !== currentFilter.value.endTime) {
|
|
needReload = true;
|
|
}
|
|
}
|
|
|
|
if (initProps.initActiveTab === 'query' || initProps.initActiveTab === 'table' || initProps.initActiveTab === 'chart') {
|
|
if (initProps.initActiveTab !== activeTab.value) {
|
|
activeTab.value = initProps.initActiveTab;
|
|
}
|
|
} else {
|
|
activeTab.value = 'query';
|
|
}
|
|
|
|
explorersStore.initTransactionExplorerFilter(filter);
|
|
|
|
if (initProps.initId) {
|
|
if (explorersStore.currentInsightsExplorer.id !== initProps.initId) {
|
|
needReload = true;
|
|
}
|
|
} else if (!initProps.initId && !initProps.initActiveTab && !initProps.initDateRangeType && !initProps.initStartTime && !initProps.initEndTime) { // first time open the page
|
|
explorersStore.updateCurrentInsightsExplorer(InsightsExplorer.createNewExplorer(generateRandomUUID()));
|
|
isCurrentExplorerModified.value = true;
|
|
}
|
|
|
|
if (!needReload && !explorersStore.transactionExplorerStateInvalid && !explorersStore.insightsExplorerListStateInvalid) {
|
|
loading.value = false;
|
|
initing.value = false;
|
|
return;
|
|
}
|
|
|
|
Promise.all([
|
|
accountsStore.loadAllAccounts({ force: false }),
|
|
transactionCategoriesStore.loadAllCategories({ force: false }),
|
|
transactionTagsStore.loadAllTags({ force: false }),
|
|
explorersStore.loadAllInsightsExplorerBasicInfos({ force: false })
|
|
]).then(() => {
|
|
const promises: Promise<unknown>[] = [
|
|
explorersStore.loadAllTransactions({ force: false })
|
|
];
|
|
|
|
if (initProps.initId && explorersStore.currentInsightsExplorer.id !== initProps.initId) {
|
|
const loadExplorerPromise = loadExplorer(initProps.initId, false, true);
|
|
|
|
if (loadExplorerPromise) {
|
|
promises.push(loadExplorerPromise);
|
|
}
|
|
}
|
|
|
|
return Promise.all(promises);
|
|
}).then(() => {
|
|
loading.value = false;
|
|
initing.value = false;
|
|
}).catch(error => {
|
|
loading.value = false;
|
|
initing.value = false;
|
|
|
|
if (!error.processed) {
|
|
snackbar.value?.showError(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function reload(force: boolean): Promise<unknown> | null {
|
|
loading.value = true;
|
|
|
|
return explorersStore.loadAllTransactions({
|
|
force: force
|
|
}).then(() => {
|
|
loading.value = false;
|
|
|
|
if (force) {
|
|
snackbar.value?.showMessage('Data has been updated');
|
|
}
|
|
}).catch(error => {
|
|
loading.value = false;
|
|
|
|
if (!error.processed) {
|
|
snackbar.value?.showError(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function createNewExplorer(): void {
|
|
if (!currentExplorer.value.id) {
|
|
return;
|
|
}
|
|
|
|
explorersStore.updateCurrentInsightsExplorer(InsightsExplorer.createNewExplorer(generateRandomUUID()));
|
|
isCurrentExplorerModified.value = true;
|
|
router.push(getFilterLinkUrl());
|
|
}
|
|
|
|
function loadExplorer(explorerId: string, force?: boolean, init?: boolean): Promise<unknown> | null {
|
|
if (!force && currentExplorer.value && currentExplorer.value.id === explorerId) {
|
|
return null;
|
|
}
|
|
|
|
if (!init) {
|
|
loading.value = true;
|
|
}
|
|
|
|
return explorersStore.getInsightsExplorer({
|
|
explorerId: explorerId
|
|
}).then(explorer => {
|
|
explorersStore.updateCurrentInsightsExplorer(explorer);
|
|
|
|
nextTick(() => {
|
|
isCurrentExplorerModified.value = false;
|
|
});
|
|
|
|
if (!init) {
|
|
loading.value = false;
|
|
}
|
|
|
|
router.push(getFilterLinkUrl());
|
|
}).catch(error => {
|
|
if (!init) {
|
|
loading.value = false;
|
|
}
|
|
|
|
if (!error.processed) {
|
|
snackbar.value?.showError(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function showChangeExplorerDisplayOrderDialog(): void {
|
|
explorerListDialog.value?.open().then(() => {
|
|
if (explorersStore.insightsExplorerListStateInvalid) {
|
|
loading.value = true;
|
|
|
|
explorersStore.loadAllInsightsExplorerBasicInfos({
|
|
force: false
|
|
}).then(() => {
|
|
loading.value = false;
|
|
}).catch(() => {
|
|
loading.value = false;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function saveExplorer(saveAs?: boolean): void {
|
|
if (saveAs || !currentExplorer.value.name) {
|
|
renameDialog.value?.open(currentExplorer.value.name || '', tt('Set Explorer Name')).then((newName: string) => {
|
|
currentExplorer.value.name = newName;
|
|
doSaveExplorer(saveAs);
|
|
})
|
|
} else {
|
|
doSaveExplorer(saveAs);
|
|
}
|
|
}
|
|
|
|
function doSaveExplorer(saveAs?: boolean): Promise<unknown> {
|
|
const oldExplorerId = currentExplorer.value.id;
|
|
|
|
updating.value = true;
|
|
|
|
return explorersStore.saveInsightsExplorer({
|
|
explorer: currentExplorer.value,
|
|
saveAs: saveAs,
|
|
clientSessionId: clientSessionId.value
|
|
}).then(newExplorer => {
|
|
updating.value = false;
|
|
clientSessionId.value = generateRandomUUID();
|
|
explorersStore.updateCurrentInsightsExplorer(newExplorer);
|
|
|
|
nextTick(() => {
|
|
isCurrentExplorerModified.value = false;
|
|
});
|
|
|
|
if (oldExplorerId !== newExplorer.id) {
|
|
router.push(getFilterLinkUrl());
|
|
}
|
|
}).catch(error => {
|
|
updating.value = false;
|
|
|
|
if (!error.processed) {
|
|
snackbar.value?.showError(error);
|
|
|
|
if (error.error && error.error.errorCode === KnownErrorCode.NothingWillBeUpdated) {
|
|
nextTick(() => {
|
|
isCurrentExplorerModified.value = false;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function restoreExplorer(): void {
|
|
if (!currentExplorer.value.id) {
|
|
return;
|
|
}
|
|
|
|
confirmDialog.value?.open('Are you sure you want to restore to last saved state? All unsaved changes will be lost.').then(() => {
|
|
loadExplorer(currentExplorer.value.id, true);
|
|
});
|
|
}
|
|
|
|
function setExplorerName(): void {
|
|
renameDialog.value?.open(currentExplorer.value.name || '').then((newName: string) => {
|
|
currentExplorer.value.name = newName;
|
|
});
|
|
}
|
|
|
|
function hideExplorer(hidden: boolean): void {
|
|
updating.value = true;
|
|
|
|
explorersStore.hideInsightsExplorer({
|
|
explorer: currentExplorer.value,
|
|
hidden: hidden
|
|
}).then(() => {
|
|
updating.value = false;
|
|
currentExplorer.value.hidden = hidden;
|
|
|
|
nextTick(() => {
|
|
isCurrentExplorerModified.value = false;
|
|
});
|
|
}).catch(error => {
|
|
updating.value = false;
|
|
|
|
if (!error.processed) {
|
|
snackbar.value?.showError(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function removeExplorer(): void {
|
|
if (!currentExplorer.value.id) {
|
|
return;
|
|
}
|
|
|
|
confirmDialog.value?.open('Are you sure you want to delete this explorer?').then(() => {
|
|
updating.value = true;
|
|
|
|
explorersStore.deleteInsightsExplorer({
|
|
explorer: currentExplorer.value
|
|
}).then(() => {
|
|
updating.value = false;
|
|
createNewExplorer();
|
|
}).catch(error => {
|
|
updating.value = false;
|
|
|
|
if (!error.processed) {
|
|
snackbar.value?.showError(error);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function exportResults(): void {
|
|
if (activeTab.value === 'table' && filteredTransactionsInDataTable.value) {
|
|
const results = explorerDataTableTab.value?.buildExportResults();
|
|
|
|
if (results) {
|
|
exportDialog.value?.open(results);
|
|
}
|
|
} else if (activeTab.value === 'chart') {
|
|
const results = explorerChartTab.value?.buildExportResults();
|
|
|
|
if (results) {
|
|
exportDialog.value?.open(results);
|
|
}
|
|
}
|
|
}
|
|
|
|
function setDateFilter(dateType: number): void {
|
|
if (dateType === DateRange.Custom.type) { // Custom
|
|
showCustomDateRangeDialog.value = true;
|
|
return;
|
|
} else if (currentFilter.value.dateRangeType === dateType) {
|
|
return;
|
|
}
|
|
|
|
const dateRange = getDateRangeByDateType(dateType, firstDayOfWeek.value, fiscalYearStart.value);
|
|
|
|
if (!dateRange) {
|
|
return;
|
|
}
|
|
|
|
const changed = explorersStore.updateTransactionExplorerFilter({
|
|
dateRangeType: dateRange.dateType,
|
|
startTime: dateRange.minTime,
|
|
endTime: dateRange.maxTime
|
|
});
|
|
|
|
if (changed) {
|
|
loading.value = true;
|
|
explorersStore.updateTransactionExplorerInvalidState(true);
|
|
router.push(getFilterLinkUrl());
|
|
}
|
|
}
|
|
|
|
function setCustomDateFilter(startTime: number, endTime: number): void {
|
|
if (!startTime || !endTime) {
|
|
return;
|
|
}
|
|
|
|
const chartDateType = getDateTypeByDateRange(startTime, endTime, firstDayOfWeek.value, fiscalYearStart.value, DateRangeScene.InsightsExplorer);
|
|
|
|
const changed = explorersStore.updateTransactionExplorerFilter({
|
|
dateRangeType: chartDateType,
|
|
startTime: startTime,
|
|
endTime: endTime
|
|
});
|
|
|
|
showCustomDateRangeDialog.value = false;
|
|
|
|
if (changed) {
|
|
loading.value = true;
|
|
explorersStore.updateTransactionExplorerInvalidState(true);
|
|
router.push(getFilterLinkUrl());
|
|
}
|
|
}
|
|
|
|
function shiftDateRange(scale: number): void {
|
|
if (currentFilter.value.dateRangeType === DateRange.All.type) {
|
|
return;
|
|
}
|
|
|
|
const newDateRange = getShiftedDateRangeAndDateType(currentFilter.value.startTime, currentFilter.value.endTime, scale, firstDayOfWeek.value, fiscalYearStart.value, DateRangeScene.Normal);
|
|
|
|
const changed = explorersStore.updateTransactionExplorerFilter({
|
|
dateRangeType: newDateRange.dateType,
|
|
startTime: newDateRange.minTime,
|
|
endTime: newDateRange.maxTime
|
|
});
|
|
|
|
if (changed) {
|
|
loading.value = true;
|
|
explorersStore.updateTransactionExplorerInvalidState(true);
|
|
router.push(getFilterLinkUrl());
|
|
}
|
|
}
|
|
|
|
function onShowTransaction(transaction: TransactionInsightDataItem): void {
|
|
editDialog.value?.open({
|
|
id: transaction.id,
|
|
currentTransaction: Transaction.of(transaction)
|
|
}).then(result => {
|
|
if (result && result.message) {
|
|
snackbar.value?.showMessage(result.message);
|
|
}
|
|
|
|
reload(false);
|
|
}).catch(error => {
|
|
if (error) {
|
|
snackbar.value?.showError(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function onShowDateRangeError(message: string): void {
|
|
snackbar.value?.showError(message);
|
|
}
|
|
|
|
onBeforeRouteUpdate((to) => {
|
|
if (to.query) {
|
|
init({
|
|
initId: (to.query['id'] as string | null) || undefined,
|
|
initActiveTab: (to.query['activeTab'] as string | null) || undefined,
|
|
initDateRangeType: (to.query['dateRangeType'] as string | null) || undefined,
|
|
initStartTime: (to.query['startTime'] as string | null) || undefined,
|
|
initEndTime: (to.query['endTime'] as string | null) || undefined,
|
|
});
|
|
} else {
|
|
init({});
|
|
}
|
|
});
|
|
|
|
watch(() => display.mdAndUp.value, (newValue) => {
|
|
alwaysShowNav.value = newValue;
|
|
|
|
if (!showNav.value) {
|
|
showNav.value = newValue;
|
|
}
|
|
});
|
|
|
|
watch(activeTab, () => {
|
|
if (initing.value || loading.value) {
|
|
return;
|
|
}
|
|
|
|
router.push(getFilterLinkUrl());
|
|
});
|
|
|
|
watch(currentExplorer, () => {
|
|
if (initing.value || loading.value) {
|
|
return;
|
|
}
|
|
|
|
isCurrentExplorerModified.value = true;
|
|
}, {
|
|
deep: true
|
|
});
|
|
|
|
init(props);
|
|
</script>
|