Fix product performance grouped JSON build
This commit is contained in:
@@ -14,7 +14,8 @@
|
||||
@wheel.stop
|
||||
>
|
||||
<q-spinner-gears size="56px" color="primary" />
|
||||
<div class="page-busy-label">Yükleniyor...</div>
|
||||
<div class="page-busy-label">{{ loadingMessage }}</div>
|
||||
<div v-if="loadingElapsedLabel" class="page-busy-time">{{ loadingElapsedLabel }}</div>
|
||||
</div>
|
||||
</teleport>
|
||||
<div class="performance-top-bar row items-center justify-between q-col-gutter-xs q-mb-xs">
|
||||
@@ -113,7 +114,7 @@
|
||||
:rows="filteredGeneralRows"
|
||||
:columns="generalColumns"
|
||||
:loading="loading"
|
||||
:pagination="{ rowsPerPage: 0, sortBy: 'performance_score_total', descending: true }"
|
||||
v-model:pagination="tablePagination.general"
|
||||
virtual-scroll
|
||||
:virtual-scroll-item-size="productThumbVirtualItemSize"
|
||||
:virtual-scroll-slice-size="tableVirtualSliceSize"
|
||||
@@ -224,8 +225,8 @@
|
||||
class="performance-table sticky-dim-table sticky-dim-8 bg-white"
|
||||
:rows="displayOrderProductCustomerTableRows"
|
||||
:columns="orderProductCustomerColumns"
|
||||
:loading="loading || backendGroupedLoading"
|
||||
:pagination="{ rowsPerPage: 0, sortBy: 'order_usd', descending: true }"
|
||||
:loading="loading"
|
||||
v-model:pagination="tablePagination.order_product_customers"
|
||||
:sort-method="sortProductGroupedTableRows"
|
||||
virtual-scroll
|
||||
:virtual-scroll-item-size="productThumbVirtualItemSize"
|
||||
@@ -415,8 +416,8 @@
|
||||
class="performance-table sticky-dim-table sticky-dim-10 bg-white"
|
||||
:rows="displayOrderMarketDetailTableRows"
|
||||
:columns="orderMarketDetailColumns"
|
||||
:loading="loading || backendGroupedLoading"
|
||||
:pagination="{ rowsPerPage: 0, sortBy: 'order_date', descending: true }"
|
||||
:loading="loading"
|
||||
v-model:pagination="tablePagination.order_market_details"
|
||||
:sort-method="sortProductGroupedTableRows"
|
||||
virtual-scroll
|
||||
:virtual-scroll-item-size="64"
|
||||
@@ -558,8 +559,8 @@
|
||||
]"
|
||||
:rows="displayProductKpiTableRows"
|
||||
:columns="activeProductColumns"
|
||||
:loading="loading || backendGroupedLoading"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
v-model:pagination="activeProductPagination"
|
||||
:sort-method="sortProductGroupedTableRows"
|
||||
virtual-scroll
|
||||
:virtual-scroll-item-size="productThumbVirtualItemSize"
|
||||
@@ -714,8 +715,8 @@
|
||||
class="performance-table sticky-dim-table sticky-dim-11 bg-white"
|
||||
:rows="displayIdleTableRows"
|
||||
:columns="idleColumns"
|
||||
:loading="loading || backendGroupedLoading"
|
||||
:pagination="{ rowsPerPage: 0 }"
|
||||
:loading="loading"
|
||||
v-model:pagination="tablePagination.idle"
|
||||
:sort-method="sortProductGroupedTableRows"
|
||||
virtual-scroll
|
||||
:virtual-scroll-item-size="productThumbVirtualItemSize"
|
||||
@@ -902,7 +903,7 @@
|
||||
:rows="filteredMarketRows"
|
||||
:columns="marketColumns"
|
||||
:loading="loading"
|
||||
:pagination="{ rowsPerPage: 0 }"
|
||||
v-model:pagination="tablePagination.markets"
|
||||
virtual-scroll
|
||||
:virtual-scroll-item-size="64"
|
||||
:virtual-scroll-slice-size="tableVirtualSliceSize"
|
||||
@@ -1008,8 +1009,8 @@
|
||||
]"
|
||||
:rows="displayActiveSalesBreakdownTableRows"
|
||||
:columns="visibleSalesBreakdownColumns"
|
||||
:loading="loading || backendGroupedLoading"
|
||||
:pagination="{ rowsPerPage: 0, sortBy: 'performance_score_total', descending: true }"
|
||||
:loading="loading"
|
||||
v-model:pagination="activeSalesBreakdownPagination"
|
||||
:sort-method="sortProductGroupedTableRows"
|
||||
virtual-scroll
|
||||
:virtual-scroll-item-size="productThumbVirtualItemSize"
|
||||
@@ -1168,7 +1169,7 @@
|
||||
:rows="filteredCustomerRows"
|
||||
:columns="customerColumns"
|
||||
:loading="loading"
|
||||
:pagination="{ rowsPerPage: 0 }"
|
||||
v-model:pagination="tablePagination.customers"
|
||||
virtual-scroll
|
||||
:virtual-scroll-item-size="64"
|
||||
:virtual-scroll-slice-size="tableVirtualSliceSize"
|
||||
@@ -1270,7 +1271,7 @@
|
||||
:rows="filteredCountryRows"
|
||||
:columns="countryColumns"
|
||||
:loading="loading"
|
||||
:pagination="{ rowsPerPage: 0 }"
|
||||
v-model:pagination="tablePagination.countries"
|
||||
virtual-scroll
|
||||
:virtual-scroll-item-size="64"
|
||||
:virtual-scroll-slice-size="tableVirtualSliceSize"
|
||||
@@ -1566,6 +1567,7 @@ const selectedDetailMainGroup = ref('')
|
||||
const imageUrlByKey = ref({})
|
||||
const imageListByKey = ref({})
|
||||
const imageFailedKeys = ref(new Set())
|
||||
const imageLoadingKeys = ref(new Set())
|
||||
const imageDialog = ref(false)
|
||||
const imageDialogUrls = ref([])
|
||||
const imageDialogTitle = ref('')
|
||||
@@ -1584,6 +1586,9 @@ const backendGroupedLoading = ref(false)
|
||||
const imagePrimeLoading = ref(false)
|
||||
const filterBusy = ref(false)
|
||||
const bulkExpandLoading = ref(false)
|
||||
const loadingStartedAt = ref(0)
|
||||
const loadingNow = ref(0)
|
||||
const loadingStage = ref('')
|
||||
const generalRowsLoaded = ref(false)
|
||||
const detailLevelMenuOpen = ref(false)
|
||||
const topScrollbarRef = ref(null)
|
||||
@@ -1593,6 +1598,7 @@ let imagePrimeActiveRequests = 0
|
||||
let activeTableMiddleEl = null
|
||||
let topScrollbarResizeObserver = null
|
||||
let topScrollbarSyncing = false
|
||||
let loadingTimer = null
|
||||
|
||||
const columnFilters = reactive({})
|
||||
const columnFilterSearch = reactive({})
|
||||
@@ -1631,14 +1637,54 @@ const performanceTabs = [
|
||||
{ name: 'order_product_customers', icon: 'assignment_ind', label: 'Ürün > Piyasa > Müşteri Sipariş' },
|
||||
{ name: 'order_market_details', icon: 'receipt_long', label: 'Piyasa > Müşteri > Ürün Sipariş' }
|
||||
]
|
||||
const pageBusy = computed(() => loading.value || filterBusy.value || bulkExpandLoading.value)
|
||||
|
||||
const pagination = ref({
|
||||
page: 1,
|
||||
rowsPerPage: 0,
|
||||
sortBy: 'performance_score_total',
|
||||
descending: true
|
||||
const pageBusy = computed(() => loading.value)
|
||||
const loadingElapsedLabel = computed(() => {
|
||||
if (!loadingStartedAt.value || !loadingNow.value) return ''
|
||||
const seconds = Math.max(0, Math.floor((loadingNow.value - loadingStartedAt.value) / 1000))
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const rest = seconds % 60
|
||||
return minutes > 0 ? `${minutes}:${String(rest).padStart(2, '0')}` : `${seconds} sn`
|
||||
})
|
||||
const loadingMessage = computed(() => loadingStage.value || 'Rapor verileri yükleniyor...')
|
||||
|
||||
const tablePagination = reactive({
|
||||
general: { page: 1, rowsPerPage: 0, sortBy: 'performance_score_total', descending: true },
|
||||
products: { page: 1, rowsPerPage: 0, sortBy: 'performance_score_total', descending: true },
|
||||
product_detail: { page: 1, rowsPerPage: 0, sortBy: 'performance_score_total', descending: true },
|
||||
order_product_customers: { page: 1, rowsPerPage: 0, sortBy: 'order_usd', descending: true },
|
||||
order_market_details: { page: 1, rowsPerPage: 0, sortBy: 'order_date', descending: true },
|
||||
idle: { page: 1, rowsPerPage: 0, sortBy: 'idle_cost_usd', descending: true },
|
||||
markets: { page: 1, rowsPerPage: 0, sortBy: 'sales_usd_90d', descending: true },
|
||||
countries: { page: 1, rowsPerPage: 0, sortBy: 'sales_usd_90d', descending: true },
|
||||
customers: { page: 1, rowsPerPage: 0, sortBy: 'sales_usd_365d', descending: true },
|
||||
sales_color_yaka_market_customer: { page: 1, rowsPerPage: 0, sortBy: 'performance_score_total', descending: true },
|
||||
sales_product_country_segment_market_customer: { page: 1, rowsPerPage: 0, sortBy: 'performance_score_total', descending: true },
|
||||
sales_market_customer_product: { page: 1, rowsPerPage: 0, sortBy: 'performance_score_total', descending: true },
|
||||
sales_country_segment_market_customer_product: { page: 1, rowsPerPage: 0, sortBy: 'performance_score_total', descending: true }
|
||||
})
|
||||
|
||||
const activeProductPagination = computed({
|
||||
get: () => tablePagination[activeTab.value === 'product_detail' ? 'product_detail' : 'products'],
|
||||
set: value => {
|
||||
tablePagination[activeTab.value === 'product_detail' ? 'product_detail' : 'products'] = normalizeTablePagination(value)
|
||||
}
|
||||
})
|
||||
|
||||
const activeSalesBreakdownPagination = computed({
|
||||
get: () => tablePagination[activeTab.value] || tablePagination.sales_color_yaka_market_customer,
|
||||
set: value => {
|
||||
tablePagination[activeTab.value] = normalizeTablePagination(value)
|
||||
}
|
||||
})
|
||||
|
||||
function normalizeTablePagination (value) {
|
||||
return {
|
||||
page: Number(value?.page || 1),
|
||||
rowsPerPage: 0,
|
||||
sortBy: value?.sortBy || '',
|
||||
descending: value?.descending === true
|
||||
}
|
||||
}
|
||||
|
||||
const bucketOptions = [
|
||||
{ label: 'Yıldız Ürün', value: 'YILDIZ_URUN' },
|
||||
@@ -2739,8 +2785,7 @@ const productAutoExpandKeys = computed(() => {
|
||||
})
|
||||
|
||||
const allProductGroupsExpanded = computed(() => {
|
||||
const keys = productAutoExpandKeys.value
|
||||
return keys.length > 0 && keys.every(key => expandedGroups.value[key] === true)
|
||||
return selectedExpandThroughLevel.value >= autoExpandThroughLevel.value
|
||||
})
|
||||
|
||||
function appendGroupRows (out, sourceRows, level, parentKeys, levels = groupLevels) {
|
||||
@@ -2784,22 +2829,13 @@ function buildGroupedTableRows (tableKey, sourceRows) {
|
||||
}
|
||||
|
||||
function backendRowsForTab (tabKey, fallbackRows) {
|
||||
const rows = backendGroupedRows.value[tabKey]
|
||||
const backendRows = Array.isArray(rows) && rows.length ? rows : []
|
||||
if (hasActiveTableFilters(tabKey)) {
|
||||
return tabKey === 'product_detail'
|
||||
? filterRowsBySelectedDetailMainGroup(fallbackRows)
|
||||
: fallbackRows
|
||||
}
|
||||
const sourceRows = tabKey === 'product_detail'
|
||||
? filterRowsBySelectedDetailMainGroup(backendRows.length ? backendRows : fallbackRows)
|
||||
: (backendRows.length ? backendRows : fallbackRows)
|
||||
return sourceRows
|
||||
return tabKey === 'product_detail'
|
||||
? filterRowsBySelectedDetailMainGroup(fallbackRows)
|
||||
: fallbackRows
|
||||
}
|
||||
|
||||
function filterSourceRowsForTab (tabKey, fallbackRows) {
|
||||
const rows = backendGroupedRows.value[tabKey]
|
||||
return Array.isArray(rows) && rows.length ? rows : fallbackRows
|
||||
return fallbackRows
|
||||
}
|
||||
|
||||
function columnsForTableKey (tabKey) {
|
||||
@@ -3387,7 +3423,10 @@ function shouldSkipGroupValue (field, value) {
|
||||
}
|
||||
|
||||
function isGroupExpanded (key) {
|
||||
return expandedGroups.value[key] === true
|
||||
const tabKey = tabKeyFromGroupKey(key)
|
||||
const depth = groupKeyDepth(key)
|
||||
const level = selectedExpandThroughLevelForTab(tabKey)
|
||||
return depth >= 0 && level >= depth
|
||||
}
|
||||
|
||||
function groupKeyDepth (key) {
|
||||
@@ -3395,6 +3434,20 @@ function groupKeyDepth (key) {
|
||||
return Math.max(-1, parts.length - 2)
|
||||
}
|
||||
|
||||
function tabKeyFromGroupKey (key) {
|
||||
const first = String(key || '').split('|')[0] || ''
|
||||
return first.startsWith('tab:') ? first.slice(4) : activeTab.value
|
||||
}
|
||||
|
||||
function selectedExpandThroughLevelForTab (tabKey) {
|
||||
const levels = tabGroupLevels[tabKey] || groupLevels
|
||||
const selected = new Set(selectedExpandLevelKeysByTab[tabKey] || [])
|
||||
const indexes = levels
|
||||
.map((level, index) => selected.has(level.key) ? index : -1)
|
||||
.filter(index => index >= 0)
|
||||
return indexes.length ? Math.max(...indexes) : -1
|
||||
}
|
||||
|
||||
function activeTabPrefix () {
|
||||
return `tab:${activeTab.value}|`
|
||||
}
|
||||
@@ -3423,24 +3476,22 @@ function setSelectedExpandLevelsThrough (level) {
|
||||
|
||||
function applyActiveExpansionThroughLevel (level) {
|
||||
setSelectedExpandLevelsThrough(level)
|
||||
replaceActiveTabExpandedGroups(expandedLevelKeysThrough(level))
|
||||
replaceActiveTabExpandedGroups([])
|
||||
lastManualExpandedGroupKey.value = ''
|
||||
}
|
||||
|
||||
function toggleGroup (key) {
|
||||
const level = groupKeyDepth(key)
|
||||
applyActiveExpansionThroughLevel(isGroupExpanded(key) ? level - 1 : level)
|
||||
scheduleLoadBackendGroupedRows()
|
||||
}
|
||||
|
||||
async function collapseAllProductGroups () {
|
||||
if (bulkExpandLoading.value) return
|
||||
detailLevelMenuOpen.value = false
|
||||
applyActiveExpansionThroughLevel(-1)
|
||||
if (backendGroupedTimer) window.clearTimeout(backendGroupedTimer)
|
||||
bulkExpandLoading.value = true
|
||||
try {
|
||||
await loadBackendGroupedRows({ force: true })
|
||||
await nextTick()
|
||||
} finally {
|
||||
bulkExpandLoading.value = false
|
||||
}
|
||||
@@ -3449,21 +3500,10 @@ async function collapseAllProductGroups () {
|
||||
async function expandSelectedProductGroups () {
|
||||
if (bulkExpandLoading.value) return
|
||||
detailLevelMenuOpen.value = false
|
||||
const keys = productAutoExpandKeys.value
|
||||
const maxKeys = Math.min(maxBulkExpandKeys, maxBackendExpandedKeysForTab(activeTab.value))
|
||||
const expandableKeys = keys.slice(0, maxKeys)
|
||||
replaceActiveTabExpandedGroups(expandableKeys)
|
||||
lastManualExpandedGroupKey.value = ''
|
||||
if (keys.length > expandableKeys.length) {
|
||||
Notify.create({
|
||||
type: 'warning',
|
||||
message: `${formatNumber(expandableKeys.length, 0)} grup açıldı. Daha derin detaylar için satırları kademeli açın.`
|
||||
})
|
||||
}
|
||||
if (backendGroupedTimer) window.clearTimeout(backendGroupedTimer)
|
||||
applyActiveExpansionThroughLevel(autoExpandThroughLevel.value)
|
||||
bulkExpandLoading.value = true
|
||||
try {
|
||||
await loadBackendGroupedRows({ force: true })
|
||||
await nextTick()
|
||||
} finally {
|
||||
bulkExpandLoading.value = false
|
||||
}
|
||||
@@ -3649,12 +3689,7 @@ function runWithFilterBusy (apply) {
|
||||
function syncExpansionAfterFilterChange () {
|
||||
if (!backendGroupedSupportedTab(activeTab.value)) return
|
||||
const level = selectedExpandThroughLevel.value
|
||||
if (level < 0) {
|
||||
scheduleLoadBackendGroupedRows()
|
||||
return
|
||||
}
|
||||
applyActiveExpansionThroughLevel(autoExpandThroughLevel.value)
|
||||
scheduleLoadBackendGroupedRows()
|
||||
if (level >= 0) applyActiveExpansionThroughLevel(autoExpandThroughLevel.value)
|
||||
}
|
||||
|
||||
function filterRowsForTable (tableKey, sourceRows, sourceColumns) {
|
||||
@@ -4166,7 +4201,21 @@ function getCachedProductImageUrl (row) {
|
||||
if (!canShowProductImage(row)) return ''
|
||||
const key = productImageKey(row)
|
||||
if (imageFailedKeys.value.has(key)) return ''
|
||||
return imageUrlByKey.value[key] || performanceStore.imageUrl(key) || ''
|
||||
const url = imageUrlByKey.value[key] || performanceStore.imageUrl(key) || ''
|
||||
if (!url) queueVisibleProductImage(row)
|
||||
return url
|
||||
}
|
||||
|
||||
function queueVisibleProductImage (row) {
|
||||
const key = productImageKey(row)
|
||||
if (!key || imageLoadingKeys.value.has(key) || imageFailedKeys.value.has(key)) return
|
||||
if (Object.prototype.hasOwnProperty.call(imageListByKey.value, key) || performanceStore.hasImageKey(key)) return
|
||||
imageLoadingKeys.value = new Set([...imageLoadingKeys.value, key])
|
||||
void fetchProductImagesForRow(row).finally(() => {
|
||||
const next = new Set(imageLoadingKeys.value)
|
||||
next.delete(key)
|
||||
imageLoadingKeys.value = next
|
||||
})
|
||||
}
|
||||
|
||||
function markProductImageFailed (row) {
|
||||
@@ -4510,30 +4559,112 @@ async function timedProductPerformanceGet (label, url, config = {}) {
|
||||
|
||||
async function reload () {
|
||||
loading.value = true
|
||||
startLoadingTimer('Rapor verileri hazırlanıyor...')
|
||||
try {
|
||||
generalRowsLoaded.value = false
|
||||
generalRows.value = []
|
||||
orderProductCustomersLoaded.value = false
|
||||
orderProductCustomerRows.value = []
|
||||
orderMarketDetailsLoaded.value = false
|
||||
orderMarketDetailRows.value = []
|
||||
orderAnalysisLoaded.value = false
|
||||
orderAnalysisRows.value = []
|
||||
orderMarketRows.value = []
|
||||
orderCustomerRows.value = []
|
||||
marketRowsLoaded.value = false
|
||||
marketRows.value = []
|
||||
countryRowsLoaded.value = false
|
||||
countryRows.value = []
|
||||
customerRowsLoaded.value = false
|
||||
customerRows.value = []
|
||||
for (const key of salesBreakdownTabKeys) salesBreakdownRows[key] = []
|
||||
|
||||
loadingStage.value = 'Ana KPI ve özet yükleniyor...'
|
||||
const [summaryResp, listResp] = await Promise.all([
|
||||
timedProductPerformanceGet('summary', '/pricing/product-performance/summary', { timeout: 60000 }),
|
||||
timedProductPerformanceGet('summary', '/pricing/product-performance/summary', { timeout: 180000 }),
|
||||
timedProductPerformanceGet('product-kpi', '/pricing/product-performance', {
|
||||
params: {
|
||||
page: 1,
|
||||
limit: productKpiFetchLimit
|
||||
},
|
||||
timeout: 60000
|
||||
timeout: 180000
|
||||
})
|
||||
])
|
||||
summary.value = summaryResp?.data || {}
|
||||
rows.value = (Array.isArray(listResp?.data?.rows) ? listResp.data.rows : []).map(normalizeRow)
|
||||
await ensureActiveTabData(false)
|
||||
await loadBackendGroupedRows({ force: true })
|
||||
loadingStage.value = 'Tüm rapor sekmeleri cacheleniyor...'
|
||||
await preloadAllReportTabs()
|
||||
loadingStage.value = 'Rapor ekranı hazırlanıyor...'
|
||||
await nextTick()
|
||||
} catch (err) {
|
||||
Notify.create({ type: 'negative', message: err?.response?.data || err?.message || 'Ürün performans verisi alınamadı' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
stopLoadingTimer()
|
||||
}
|
||||
}
|
||||
|
||||
function startLoadingTimer (stage = '') {
|
||||
loadingStage.value = stage
|
||||
loadingStartedAt.value = Date.now()
|
||||
loadingNow.value = Date.now()
|
||||
if (loadingTimer) window.clearInterval(loadingTimer)
|
||||
loadingTimer = window.setInterval(() => {
|
||||
loadingNow.value = Date.now()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function stopLoadingTimer () {
|
||||
if (loadingTimer) {
|
||||
window.clearInterval(loadingTimer)
|
||||
loadingTimer = null
|
||||
}
|
||||
loadingStartedAt.value = 0
|
||||
loadingNow.value = 0
|
||||
loadingStage.value = ''
|
||||
}
|
||||
|
||||
async function preloadAllReportTabs () {
|
||||
const tasks = [
|
||||
loadGeneralRows(false),
|
||||
loadOrderProductCustomers(false),
|
||||
loadOrderMarketDetails(false),
|
||||
loadMarketRows(false),
|
||||
loadCountryRows(false),
|
||||
loadCustomerRows(false),
|
||||
loadAllSalesBreakdownRows(false)
|
||||
]
|
||||
const results = await Promise.allSettled(tasks)
|
||||
const failed = results.filter(item => item.status === 'rejected')
|
||||
if (failed.length) {
|
||||
console.warn('[ProductPerformance][preload] partial failure', failed.map(item => item.reason?.message || item.reason))
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAllSalesBreakdownRows (showLoading = true) {
|
||||
if (showLoading) loading.value = true
|
||||
try {
|
||||
await Promise.all(salesBreakdownTabKeys.map(key => loadSalesBreakdownRows(key, false)))
|
||||
} finally {
|
||||
if (showLoading) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSalesBreakdownRows (tabKey, showLoading = true) {
|
||||
if (!salesBreakdownTabKeys.includes(tabKey)) return
|
||||
if (Array.isArray(salesBreakdownRows[tabKey]) && salesBreakdownRows[tabKey].length) return
|
||||
if (showLoading) loading.value = true
|
||||
try {
|
||||
const mode = salesBreakdownModes[tabKey] || tabKey
|
||||
const resp = await timedProductPerformanceGet(`sales:${tabKey}`, '/pricing/product-performance/sales-breakdown', {
|
||||
params: { mode, limit: reportFetchLimit },
|
||||
timeout: 180000
|
||||
})
|
||||
salesBreakdownRows[tabKey] = (Array.isArray(resp?.data) ? resp.data : []).map(normalizeSalesBreakdownRow)
|
||||
} catch (err) {
|
||||
Notify.create({ type: 'negative', message: err?.response?.data || err?.message || 'Satış kırılım verisi alınamadı' })
|
||||
} finally {
|
||||
if (showLoading) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4541,7 +4672,7 @@ async function loadGeneralRows (showLoading = true) {
|
||||
if (generalRowsLoaded.value) return
|
||||
if (showLoading) loading.value = true
|
||||
try {
|
||||
const resp = await timedProductPerformanceGet('general', '/pricing/product-performance/general', { params: { limit: reportFetchLimit }, timeout: 90000 })
|
||||
const resp = await timedProductPerformanceGet('general', '/pricing/product-performance/general', { params: { limit: reportFetchLimit }, timeout: 180000 })
|
||||
const normalizedGeneralRows = (Array.isArray(resp?.data) ? resp.data : []).map(normalizeGeneralRow)
|
||||
generalRows.value = normalizedGeneralRows
|
||||
generalRowsLoaded.value = true
|
||||
@@ -4559,9 +4690,10 @@ async function loadGeneralRows (showLoading = true) {
|
||||
}
|
||||
|
||||
async function loadOrderProductCustomers (showLoading = true) {
|
||||
if (orderProductCustomersLoaded.value) return
|
||||
if (showLoading) loading.value = true
|
||||
try {
|
||||
const resp = await timedProductPerformanceGet('orders:product-customers', '/pricing/product-performance/orders/product-customers', { params: { limit: reportFetchLimit }, timeout: 90000 })
|
||||
const resp = await timedProductPerformanceGet('orders:product-customers', '/pricing/product-performance/orders/product-customers', { params: { limit: reportFetchLimit }, timeout: 180000 })
|
||||
orderProductCustomerRows.value = (Array.isArray(resp?.data) ? resp.data : []).map(normalizeOrderProductCustomerRow)
|
||||
orderProductCustomersLoaded.value = true
|
||||
} catch (err) {
|
||||
@@ -4572,9 +4704,10 @@ async function loadOrderProductCustomers (showLoading = true) {
|
||||
}
|
||||
|
||||
async function loadOrderMarketDetails (showLoading = true) {
|
||||
if (orderMarketDetailsLoaded.value) return
|
||||
if (showLoading) loading.value = true
|
||||
try {
|
||||
const resp = await timedProductPerformanceGet('orders:market-details', '/pricing/product-performance/orders/market-details', { params: { limit: reportFetchLimit }, timeout: 90000 })
|
||||
const resp = await timedProductPerformanceGet('orders:market-details', '/pricing/product-performance/orders/market-details', { params: { limit: reportFetchLimit }, timeout: 180000 })
|
||||
orderMarketDetailRows.value = (Array.isArray(resp?.data) ? resp.data : []).map(normalizeOrderMarketDetailRow)
|
||||
orderMarketDetailsLoaded.value = true
|
||||
} catch (err) {
|
||||
@@ -4588,7 +4721,7 @@ async function loadMarketRows (showLoading = true) {
|
||||
if (marketRowsLoaded.value) return
|
||||
if (showLoading) loading.value = true
|
||||
try {
|
||||
const resp = await timedProductPerformanceGet('markets', '/pricing/product-performance/markets', { params: { limit: reportFetchLimit }, timeout: 60000 })
|
||||
const resp = await timedProductPerformanceGet('markets', '/pricing/product-performance/markets', { params: { limit: reportFetchLimit }, timeout: 180000 })
|
||||
marketRows.value = Array.isArray(resp?.data) ? resp.data : []
|
||||
marketRowsLoaded.value = true
|
||||
} catch (err) {
|
||||
@@ -4602,7 +4735,7 @@ async function loadCountryRows (showLoading = true) {
|
||||
if (countryRowsLoaded.value) return
|
||||
if (showLoading) loading.value = true
|
||||
try {
|
||||
const resp = await timedProductPerformanceGet('countries', '/pricing/product-performance/countries', { params: { limit: reportFetchLimit }, timeout: 60000 })
|
||||
const resp = await timedProductPerformanceGet('countries', '/pricing/product-performance/countries', { params: { limit: reportFetchLimit }, timeout: 180000 })
|
||||
countryRows.value = (Array.isArray(resp?.data) ? resp.data : []).map(row => ({
|
||||
...row,
|
||||
country_key: `${row.country || '-'}|${row.customer_segment || '-'}|${row.market_key || '-'}`
|
||||
@@ -4621,7 +4754,7 @@ async function loadCustomerRows (showLoading = true) {
|
||||
try {
|
||||
const resp = await timedProductPerformanceGet('customers', '/pricing/product-performance/customers', {
|
||||
params: { limit: reportFetchLimit, breakdown: customerBreakdown.value },
|
||||
timeout: 60000
|
||||
timeout: 180000
|
||||
})
|
||||
customerRows.value = (Array.isArray(resp?.data) ? resp.data : []).map(row => ({
|
||||
...row,
|
||||
@@ -4641,6 +4774,9 @@ async function ensureActiveTabData (showLoading = true) {
|
||||
if (tab === 'markets') return loadMarketRows(showLoading)
|
||||
if (tab === 'countries') return loadCountryRows(showLoading)
|
||||
if (tab === 'customers') return loadCustomerRows(showLoading)
|
||||
if (tab === 'order_product_customers') return loadOrderProductCustomers(showLoading)
|
||||
if (tab === 'order_market_details') return loadOrderMarketDetails(showLoading)
|
||||
if (salesBreakdownTabKeys.includes(tab)) return loadSalesBreakdownRows(tab, showLoading)
|
||||
}
|
||||
|
||||
async function loadOrderAnalysis (showLoading = true) {
|
||||
@@ -4680,7 +4816,6 @@ function bucketColor (value) {
|
||||
|
||||
watch(activeTab, () => {
|
||||
void ensureActiveTabData(false)
|
||||
scheduleLoadBackendGroupedRows()
|
||||
setupTopScrollbarSync()
|
||||
})
|
||||
|
||||
@@ -4704,14 +4839,9 @@ watch(detailMainGroupOptions, () => {
|
||||
|
||||
watch(selectedDetailMainGroup, () => {
|
||||
if (activeTab.value === 'product_detail') {
|
||||
backendGroupedRows.value = {
|
||||
...backendGroupedRows.value,
|
||||
product_detail: []
|
||||
}
|
||||
void nextTick(() => {
|
||||
const level = selectedExpandThroughLevel.value
|
||||
applyActiveExpansionThroughLevel(level >= 0 ? autoExpandThroughLevel.value : -1)
|
||||
scheduleLoadBackendGroupedRows()
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -4733,6 +4863,7 @@ onMounted(() => {
|
||||
onBeforeUnmount(() => {
|
||||
cleanupTopScrollbarSync()
|
||||
if (backendGroupedTimer) window.clearTimeout(backendGroupedTimer)
|
||||
stopLoadingTimer()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -4762,6 +4893,12 @@ onBeforeUnmount(() => {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.page-busy-time {
|
||||
color: #5f6b7a;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.performance-top-bar {
|
||||
min-height: 34px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user