Dashboard-Ladezeit durch geteilte Datenabfragen optimiert

This commit is contained in:
2026-08-07 19:07:20 +02:00
parent d2e0b4ca0f
commit 9421c3221c
5 changed files with 69 additions and 18 deletions

View File

@@ -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)) {

View File

@@ -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)

View File

@@ -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

View File

@@ -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<DashboardCoreData> | null
}
const tenantCaches = new Map<string, DashboardCacheEntry>()
/**
* 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<DashboardCoreData> => {
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 }
}

View File

@@ -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")