diff --git a/frontend/components/displayBWASummary.vue b/frontend/components/displayBWASummary.vue index 16f4ef4..0acf563 100644 --- a/frontend/components/displayBWASummary.vue +++ b/frontend/components/displayBWASummary.vue @@ -11,6 +11,7 @@ import { } from "~/composables/useDepreciation" const loading = ref(true) +const { loadCoreData } = useDashboardData() const summary = ref({ label: "", income: 0, @@ -47,11 +48,11 @@ const loadSummary = async () => { end: dayjs().endOf("month") } - const [docs, incoming, allocations] = await Promise.all([ - useEntities("createddocuments").select(), - useEntities("incominginvoices").select(), + const [coreData, allocations] = await Promise.all([ + loadCoreData(), useEntities("statementallocations").select("*, bankstatement(*)") ]) + const { createdDocuments: docs, incomingInvoices: incoming } = coreData const outputDocs = (docs || []).filter((doc: any) => { if (!isRelevantOutputDocument(doc)) { diff --git a/frontend/components/displayIncomeAndExpenditure.vue b/frontend/components/displayIncomeAndExpenditure.vue index c934f63..863405d 100644 --- a/frontend/components/displayIncomeAndExpenditure.vue +++ b/frontend/components/displayIncomeAndExpenditure.vue @@ -17,6 +17,7 @@ const props = defineProps({ }) const tempStore = useTempStore() +const { loadCoreData } = useDashboardData() const isMounted = ref(false) const amountMode = ref("net") @@ -82,10 +83,7 @@ watch([amountMode, granularity, selectedYear, selectedMonth], () => { }) const loadData = async () => { - const [docs, incoming] = await Promise.all([ - useEntities("createddocuments").select(), - useEntities("incominginvoices").select() - ]) + const { createdDocuments: docs, incomingInvoices: incoming } = await loadCoreData() incomeDocuments.value = (docs || []).filter((item) => item.state === "Gebucht" && ["invoices", "advanceInvoices", "cancellationInvoices"].includes(item.type)) expenseInvoices.value = (incoming || []).filter((item) => item.state === "Gebucht" && item.date) diff --git a/frontend/components/displayTaxSummary.vue b/frontend/components/displayTaxSummary.vue index 7adf9ac..db99e5b 100644 --- a/frontend/components/displayTaxSummary.vue +++ b/frontend/components/displayTaxSummary.vue @@ -13,6 +13,7 @@ import { dayjs.extend(customParseFormat) const auth = useAuthStore() +const { loadCoreData } = useDashboardData() const loading = ref(true) const summary = ref({ @@ -39,10 +40,7 @@ const loadSummary = async () => { const periodType = normalizeTaxEvaluationPeriod(auth.activeTenantData?.taxEvaluationPeriod) const bounds = getTaxEvaluationPeriodBounds(dayjs(), periodType) - const [docs, incoming] = await Promise.all([ - useEntities("createddocuments").select(), - useEntities("incominginvoices").select() - ]) + const { createdDocuments: docs, incomingInvoices: incoming } = await loadCoreData() const outputDocs = (docs || []).filter((doc: any) => { if (doc?.state !== "Gebucht") return false diff --git a/frontend/composables/useDashboardData.ts b/frontend/composables/useDashboardData.ts new file mode 100644 index 0000000..d92acf8 --- /dev/null +++ b/frontend/composables/useDashboardData.ts @@ -0,0 +1,54 @@ +type DashboardCoreData = { + createdDocuments: any[] + incomingInvoices: any[] +} + +const CACHE_TTL_MS = 30_000 + +type DashboardCacheEntry = { + cachedAt: number + data: DashboardCoreData | null + pendingRequest: Promise | null +} + +const tenantCaches = new Map() + +/** + * Bündelt die großen, von mehreren Dashboard-Karten benötigten Abfragen. + * So werden identische Requests beim parallelen Mounten nur einmal ausgeführt. + */ +export const useDashboardData = () => { + const auth = useAuthStore() + + const getTenantKey = () => String(auth.activeTenant || auth.activeTenantData?.id || "default") + + const loadCoreData = async (force = false): Promise => { + const tenantKey = getTenantKey() + const cache = tenantCaches.get(tenantKey) || { cachedAt: 0, data: null, pendingRequest: null } + tenantCaches.set(tenantKey, cache) + const cacheIsFresh = cache.data && Date.now() - cache.cachedAt < CACHE_TTL_MS + + if (!force && cacheIsFresh) return cache.data + if (!force && cache.pendingRequest) return cache.pendingRequest + + cache.pendingRequest = Promise.all([ + useEntities("createddocuments").select(), + useEntities("incominginvoices").select() + ]) + .then(([createdDocuments, incomingInvoices]) => { + cache.data = { + createdDocuments: createdDocuments || [], + incomingInvoices: incomingInvoices || [] + } + cache.cachedAt = Date.now() + return cache.data + }) + .finally(() => { + cache.pendingRequest = null + }) + + return cache.pendingRequest + } + + return { loadCoreData } +} diff --git a/frontend/pages/index.client.vue b/frontend/pages/index.client.vue index 2e8f8f6..8a54db8 100644 --- a/frontend/pages/index.client.vue +++ b/frontend/pages/index.client.vue @@ -2,13 +2,13 @@ import { setPageLayout } from "#app" import "gridstack/dist/gridstack.min.css" -import DisplayIncomeAndExpenditure from "~/components/displayIncomeAndExpenditure.vue" -import DisplayOpenBalances from "~/components/displayOpenBalances.vue" -import DisplayBankaccounts from "~/components/displayBankaccounts.vue" -import DisplayProjectsInPhases from "~/components/displayProjectsInPhases.vue" -import DisplayOpenTasks from "~/components/displayOpenTasks.vue" -import DisplayTaxSummary from "~/components/displayTaxSummary.vue" -import DisplayBWASummary from "~/components/displayBWASummary.vue" +const DisplayIncomeAndExpenditure = defineAsyncComponent(() => import("~/components/displayIncomeAndExpenditure.vue")) +const DisplayOpenBalances = defineAsyncComponent(() => import("~/components/displayOpenBalances.vue")) +const DisplayBankaccounts = defineAsyncComponent(() => import("~/components/displayBankaccounts.vue")) +const DisplayProjectsInPhases = defineAsyncComponent(() => import("~/components/displayProjectsInPhases.vue")) +const DisplayOpenTasks = defineAsyncComponent(() => import("~/components/displayOpenTasks.vue")) +const DisplayTaxSummary = defineAsyncComponent(() => import("~/components/displayTaxSummary.vue")) +const DisplayBWASummary = defineAsyncComponent(() => import("~/components/displayBWASummary.vue")) setPageLayout("default")