Dashboard-Ladezeit durch geteilte Datenabfragen optimiert
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
|||||||
} from "~/composables/useDepreciation"
|
} from "~/composables/useDepreciation"
|
||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
|
const { loadCoreData } = useDashboardData()
|
||||||
const summary = ref({
|
const summary = ref({
|
||||||
label: "",
|
label: "",
|
||||||
income: 0,
|
income: 0,
|
||||||
@@ -47,11 +48,11 @@ const loadSummary = async () => {
|
|||||||
end: dayjs().endOf("month")
|
end: dayjs().endOf("month")
|
||||||
}
|
}
|
||||||
|
|
||||||
const [docs, incoming, allocations] = await Promise.all([
|
const [coreData, allocations] = await Promise.all([
|
||||||
useEntities("createddocuments").select(),
|
loadCoreData(),
|
||||||
useEntities("incominginvoices").select(),
|
|
||||||
useEntities("statementallocations").select("*, bankstatement(*)")
|
useEntities("statementallocations").select("*, bankstatement(*)")
|
||||||
])
|
])
|
||||||
|
const { createdDocuments: docs, incomingInvoices: incoming } = coreData
|
||||||
|
|
||||||
const outputDocs = (docs || []).filter((doc: any) => {
|
const outputDocs = (docs || []).filter((doc: any) => {
|
||||||
if (!isRelevantOutputDocument(doc)) {
|
if (!isRelevantOutputDocument(doc)) {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const tempStore = useTempStore()
|
const tempStore = useTempStore()
|
||||||
|
const { loadCoreData } = useDashboardData()
|
||||||
const isMounted = ref(false)
|
const isMounted = ref(false)
|
||||||
|
|
||||||
const amountMode = ref("net")
|
const amountMode = ref("net")
|
||||||
@@ -82,10 +83,7 @@ watch([amountMode, granularity, selectedYear, selectedMonth], () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
const [docs, incoming] = await Promise.all([
|
const { createdDocuments: docs, incomingInvoices: incoming } = await loadCoreData()
|
||||||
useEntities("createddocuments").select(),
|
|
||||||
useEntities("incominginvoices").select()
|
|
||||||
])
|
|
||||||
|
|
||||||
incomeDocuments.value = (docs || []).filter((item) => item.state === "Gebucht" && ["invoices", "advanceInvoices", "cancellationInvoices"].includes(item.type))
|
incomeDocuments.value = (docs || []).filter((item) => item.state === "Gebucht" && ["invoices", "advanceInvoices", "cancellationInvoices"].includes(item.type))
|
||||||
expenseInvoices.value = (incoming || []).filter((item) => item.state === "Gebucht" && item.date)
|
expenseInvoices.value = (incoming || []).filter((item) => item.state === "Gebucht" && item.date)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
|
const { loadCoreData } = useDashboardData()
|
||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const summary = ref({
|
const summary = ref({
|
||||||
@@ -39,10 +40,7 @@ const loadSummary = async () => {
|
|||||||
const periodType = normalizeTaxEvaluationPeriod(auth.activeTenantData?.taxEvaluationPeriod)
|
const periodType = normalizeTaxEvaluationPeriod(auth.activeTenantData?.taxEvaluationPeriod)
|
||||||
const bounds = getTaxEvaluationPeriodBounds(dayjs(), periodType)
|
const bounds = getTaxEvaluationPeriodBounds(dayjs(), periodType)
|
||||||
|
|
||||||
const [docs, incoming] = await Promise.all([
|
const { createdDocuments: docs, incomingInvoices: incoming } = await loadCoreData()
|
||||||
useEntities("createddocuments").select(),
|
|
||||||
useEntities("incominginvoices").select()
|
|
||||||
])
|
|
||||||
|
|
||||||
const outputDocs = (docs || []).filter((doc: any) => {
|
const outputDocs = (docs || []).filter((doc: any) => {
|
||||||
if (doc?.state !== "Gebucht") return false
|
if (doc?.state !== "Gebucht") return false
|
||||||
|
|||||||
54
frontend/composables/useDashboardData.ts
Normal file
54
frontend/composables/useDashboardData.ts
Normal 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 }
|
||||||
|
}
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
import { setPageLayout } from "#app"
|
import { setPageLayout } from "#app"
|
||||||
import "gridstack/dist/gridstack.min.css"
|
import "gridstack/dist/gridstack.min.css"
|
||||||
|
|
||||||
import DisplayIncomeAndExpenditure from "~/components/displayIncomeAndExpenditure.vue"
|
const DisplayIncomeAndExpenditure = defineAsyncComponent(() => import("~/components/displayIncomeAndExpenditure.vue"))
|
||||||
import DisplayOpenBalances from "~/components/displayOpenBalances.vue"
|
const DisplayOpenBalances = defineAsyncComponent(() => import("~/components/displayOpenBalances.vue"))
|
||||||
import DisplayBankaccounts from "~/components/displayBankaccounts.vue"
|
const DisplayBankaccounts = defineAsyncComponent(() => import("~/components/displayBankaccounts.vue"))
|
||||||
import DisplayProjectsInPhases from "~/components/displayProjectsInPhases.vue"
|
const DisplayProjectsInPhases = defineAsyncComponent(() => import("~/components/displayProjectsInPhases.vue"))
|
||||||
import DisplayOpenTasks from "~/components/displayOpenTasks.vue"
|
const DisplayOpenTasks = defineAsyncComponent(() => import("~/components/displayOpenTasks.vue"))
|
||||||
import DisplayTaxSummary from "~/components/displayTaxSummary.vue"
|
const DisplayTaxSummary = defineAsyncComponent(() => import("~/components/displayTaxSummary.vue"))
|
||||||
import DisplayBWASummary from "~/components/displayBWASummary.vue"
|
const DisplayBWASummary = defineAsyncComponent(() => import("~/components/displayBWASummary.vue"))
|
||||||
|
|
||||||
setPageLayout("default")
|
setPageLayout("default")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user