product performance grouped kpi improvements

This commit is contained in:
M_Kececi
2026-07-03 02:55:58 +03:00
parent c7ff2328fb
commit 9f45160648
+206 -70
View File
@@ -95,7 +95,7 @@
:rows="filteredGeneralRows"
:columns="generalColumns"
:loading="loading"
:pagination="{ rowsPerPage: 100, sortBy: 'performance_score', descending: true }"
:pagination="{ rowsPerPage: 100, sortBy: 'performance_score_total', descending: true }"
virtual-scroll
:virtual-scroll-item-size="64"
>
@@ -971,7 +971,7 @@
:rows="displayActiveSalesBreakdownTableRows"
:columns="visibleSalesBreakdownColumns"
:loading="loading || backendGroupedLoading"
:pagination="{ rowsPerPage: 100, sortBy: 'performance_score', descending: true }"
:pagination="{ rowsPerPage: 100, sortBy: 'performance_score_total', descending: true }"
virtual-scroll
:virtual-scroll-item-size="64"
>
@@ -1551,7 +1551,7 @@ const pagination = ref({
page: 1,
rowsPerPage: 100,
rowsNumber: 0,
sortBy: 'performance_score',
sortBy: 'performance_score_total',
descending: true
})
@@ -1616,9 +1616,13 @@ const columns = [
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left' },
{ name: 'market_key', label: 'Piyasa', field: row => displayMarketName(row.market_key), align: 'left', sortable: true },
{ name: 'stock_qty', label: 'Stok', field: row => formatNumber(row.stock_qty, 0), align: 'right', sortable: true },
{ name: 'performance_score_90d', label: '90G Skor', field: row => formatNumber(row.performance_score_90d, 1), align: 'right', sortable: true },
{ name: 'sales_qty_90d', label: '90G Satış', field: row => formatNumber(row.sales_qty_90d, 0), align: 'right', sortable: true },
{ name: 'performance_score_180d', label: '180G Skor', field: row => formatNumber(row.performance_score_180d, 1), align: 'right', sortable: true },
{ name: 'sales_qty_180d', label: '180G Satış', field: row => formatNumber(row.sales_qty_180d, 0), align: 'right', sortable: true },
{ name: 'performance_score_365d', label: '360G Skor', field: row => formatNumber(row.performance_score_365d, 1), align: 'right', sortable: true },
{ name: 'sales_qty_365d', label: '360G Satış', field: row => formatNumber(row.sales_qty_365d, 0), align: 'right', sortable: true },
{ name: 'performance_score_total', label: 'Genel Skor', field: row => formatNumber(row.performance_score_total, 1), align: 'right', sortable: true },
{ name: 'sales_qty_total', label: 'Genel Satış', field: row => formatNumber(row.sales_qty_total, 0), align: 'right', sortable: true },
{ name: 'sales_usd_90d', label: '90G USD', field: 'sales_usd_90d', align: 'right', sortable: true },
{ name: 'sales_usd_180d', label: '180G USD', field: 'sales_usd_180d', align: 'right', sortable: true },
@@ -1677,6 +1681,24 @@ const periodOrder = {
total: 4
}
const hiddenTableColumns = new Set(['item_description', 'performance_score'])
const dimensionColumnNames = [
'product_code',
'color_code',
'yaka_kodu',
'item_description',
'kategori',
'askili_yan',
'urun_ilk_grubu',
'urun_ana_grubu',
'urun_alt_grubu',
'market_key',
'country',
'customer_segment',
'customer_code',
'customer_name'
]
const metricOrderFragments = [
'customer_score',
'performance_score',
@@ -1739,14 +1761,23 @@ function isTrailingInfoColumn (name) {
return ['performance_bucket', 'recommendation', 'last_sale_date', 'first_sale_date', 'is_overdue'].includes(name)
}
function nonPeriodColumnOrder (name) {
if (name === 'performance_score') return 2
if (name === 'stock_qty' || name === 'product_count') return 1
if (dimensionColumnNames.includes(name) || name === 'image') return 0
return 3
}
function orderedMetricColumns (sourceColumns) {
return [...sourceColumns].sort((a, b) => {
return sourceColumns.filter(col => !hiddenTableColumns.has(col.name)).sort((a, b) => {
const aPeriod = columnPeriod(a.name)
const bPeriod = columnPeriod(b.name)
const aTrailing = isTrailingInfoColumn(a.name)
const bTrailing = isTrailingInfoColumn(b.name)
if (aTrailing !== bTrailing) return aTrailing ? 1 : -1
if (!aPeriod && !bPeriod) return 0
if (!aPeriod && !bPeriod) {
return nonPeriodColumnOrder(a.name) - nonPeriodColumnOrder(b.name)
}
if (!aPeriod) return -1
if (!bPeriod) return 1
const periodDiff = periodOrder[aPeriod] - periodOrder[bPeriod]
@@ -2068,6 +2099,65 @@ const salesBreakdownColumns = [
{ name: 'recommendation', label: 'Öneri', field: 'recommendation', align: 'left' }
]
const productPeriodScoreColumns = [
{ name: 'performance_score_90d', label: '90G Ürün Skor', field: row => formatNumber(row.performance_score_90d, 1), align: 'right', sortable: true },
{ name: 'performance_score_180d', label: '180G Ürün Skor', field: row => formatNumber(row.performance_score_180d, 1), align: 'right', sortable: true },
{ name: 'performance_score_365d', label: '360G Ürün Skor', field: row => formatNumber(row.performance_score_365d, 1), align: 'right', sortable: true },
{ name: 'performance_score_total', label: 'Genel Ürün Skor', field: row => formatNumber(row.performance_score_total, 1), align: 'right', sortable: true }
]
const customerPeriodScoreColumns = [
{ name: 'customer_score_90d', label: '90G Müşteri Skor', field: row => formatNumber(row.customer_score_90d, 1), align: 'right', sortable: true },
{ name: 'customer_score_180d', label: '180G Müşteri Skor', field: row => formatNumber(row.customer_score_180d, 1), align: 'right', sortable: true },
{ name: 'customer_score_365d', label: '360G Müşteri Skor', field: row => formatNumber(row.customer_score_365d, 1), align: 'right', sortable: true },
{ name: 'customer_score_total', label: 'Genel Müşteri Skor', field: row => formatNumber(row.customer_score_total, 1), align: 'right', sortable: true }
]
function ensureColumns (targetColumns, columnsToEnsure) {
const existing = new Set(targetColumns.map(col => col.name))
for (const col of columnsToEnsure) {
if (!existing.has(col.name)) targetColumns.push(col)
}
}
function removeColumnsInPlace (targetColumns, names) {
const removeSet = new Set(names)
const next = targetColumns.filter(col => !removeSet.has(col.name))
targetColumns.splice(0, targetColumns.length, ...next)
return targetColumns
}
;[
columns,
generalColumns,
orderAnalysisColumns,
orderGroupColumns,
orderProductCustomerColumns,
orderMarketDetailColumns,
idleColumns,
marketColumns,
countryColumns,
salesBreakdownColumns
].forEach(target => ensureColumns(target, productPeriodScoreColumns))
;[
customerColumns,
salesBreakdownColumns
].forEach(target => ensureColumns(target, customerPeriodScoreColumns))
;[
generalColumns,
orderAnalysisColumns,
orderGroupColumns,
orderProductCustomerColumns,
orderMarketDetailColumns,
idleColumns,
marketColumns,
countryColumns,
customerColumns,
salesBreakdownColumns
].forEach(target => removeColumnsInPlace(target, ['image']))
;[
columns,
generalColumns,
@@ -2185,7 +2275,6 @@ const imageDialogInfoRows = computed(() => {
{ key: 'product', label: 'Ürün', value: productCode || '-' },
{ key: 'color', label: 'Renk', value: colorCode || '-' },
{ key: 'yaka', label: 'Yaka', value: yakaCode || '-' },
{ key: 'desc', label: 'Açıklama', value: row.item_description || '-' },
{ key: 'category', label: 'Kategori', value: row.kategori || '-' },
{ key: 'hanger', label: 'Askılı/Yan', value: row.askili_yan || '-' },
{ key: 'first', label: 'İlk Grup', value: row.urun_ilk_grubu || row.yas_grubu || '-' },
@@ -2205,7 +2294,6 @@ const groupLevels = [
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
{ key: 'product_code', label: 'Ürün' },
{ key: 'item_description', label: 'Açıklama' },
{ key: 'color_code', label: 'Renk' },
{ key: 'yaka_kodu', label: 'Yaka' },
{ key: 'market_key', label: 'Piyasa' }
@@ -2221,7 +2309,6 @@ const tabGroupLevels = {
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
{ key: 'product_code', label: 'Ürün' },
{ key: 'item_description', label: 'Açıklama' },
{ key: 'country', label: 'Ülke' },
{ key: 'market_key', label: 'Piyasa' },
{ key: 'customer_segment', label: 'Segment' },
@@ -2234,7 +2321,6 @@ const tabGroupLevels = {
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
{ key: 'product_code', label: 'Ürün' },
{ key: 'item_description', label: 'Açıklama' },
{ key: 'color_code', label: 'Renk' },
{ key: 'yaka_kodu', label: 'Yaka' }
],
@@ -2248,7 +2334,6 @@ const tabGroupLevels = {
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
{ key: 'product_code', label: 'Ürün' },
{ key: 'item_description', label: 'Açıklama' },
{ key: 'color_code', label: 'Renk' },
{ key: 'yaka_kodu', label: 'Yaka' }
],
@@ -2262,7 +2347,6 @@ const tabGroupLevels = {
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
{ key: 'product_code', label: 'Ürün' },
{ key: 'item_description', label: 'Açıklama' },
{ key: 'color_code', label: 'Renk' },
{ key: 'yaka_kodu', label: 'Yaka' }
],
@@ -2276,7 +2360,6 @@ const tabGroupLevels = {
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
{ key: 'product_code', label: 'Ürün' },
{ key: 'item_description', label: 'Açıklama' },
{ key: 'color_code', label: 'Renk' },
{ key: 'yaka_kodu', label: 'Yaka' }
],
@@ -2290,7 +2373,6 @@ const tabGroupLevels = {
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
{ key: 'product_code', label: 'Ürün' },
{ key: 'item_description', label: 'Açıklama' },
{ key: 'color_code', label: 'Renk' },
{ key: 'yaka_kodu', label: 'Yaka' }
],
@@ -2304,7 +2386,6 @@ const tabGroupLevels = {
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
{ key: 'product_code', label: 'Ürün' },
{ key: 'item_description', label: 'Açıklama' },
{ key: 'color_code', label: 'Renk' },
{ key: 'yaka_kodu', label: 'Yaka' }
]
@@ -2460,6 +2541,7 @@ function buildGroupedTableRows (tableKey, sourceRows) {
}
function backendRowsForTab (tabKey, fallbackRows) {
if (hasActiveTableFilters(tabKey)) return fallbackRows
const rows = backendGroupedRows.value[tabKey]
return Array.isArray(rows) && rows.length ? rows : fallbackRows
}
@@ -2658,6 +2740,10 @@ function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
out.customer_score_180d = customerSalesPeriodScore(periodMetricSource(out, '180d'))
out.customer_score_365d = customerSalesPeriodScore(periodMetricSource(out, '365d'))
out.customer_score_total = customerSalesPeriodScore(periodMetricSource(out, 'total'))
out.performance_score_90d = productSalesPeriodScore(productPeriodMetricSource(out, '90d'))
out.performance_score_180d = productSalesPeriodScore(productPeriodMetricSource(out, '180d'))
out.performance_score_365d = productSalesPeriodScore(productPeriodMetricSource(out, '365d'))
out.performance_score_total = productSalesPeriodScore(productPeriodMetricSource(out, 'total'))
out.performance_score = groupPerformanceScore(out, groupField)
}
@@ -2668,6 +2754,10 @@ function groupPerformanceScore (row, groupField = '') {
}
function salesGroupPerformanceScore (row) {
return productSalesPeriodScore(productPeriodMetricSource(row, '90d'))
}
function productSalesPeriodScore (row) {
const salesIndex = Number(row?.sales_index_90d || 0)
const margin = Number(row?.gross_margin_cost_90d ?? row?.gross_margin_90d ?? 0)
const invoices = Number(row?.invoice_count_90d || 0)
@@ -2682,6 +2772,22 @@ function salesGroupPerformanceScore (row) {
Math.min(10, Math.max(0, stockTurnover) * 5)
}
function productPeriodMetricSource (row, suffix) {
const salesUSD = Number(row?.[`sales_usd_${suffix}`] || 0)
const salesIndex = Number(row?.[`sales_index_${suffix}`] || 0) || (salesUSD > 0 ? salesUSD / 1000 : 0)
const qty = Number(row?.[`sales_qty_${suffix}`] || 0)
const stockQty = Number(row?.stock_qty || 0)
return {
sales_index_90d: salesIndex,
gross_margin_cost_90d: Number(row?.[`gross_margin_cost_${suffix}`] ?? 0),
gross_margin_90d: Number(row?.[`gross_margin_${suffix}`] ?? 0),
invoice_count_90d: Number(row?.[`invoice_count_${suffix}`] || 0),
sales_qty_90d: qty,
stock_turnover_90d: Number(row?.[`stock_turnover_${suffix}`] || 0) || (stockQty > 0 ? qty / stockQty : 0),
stock_qty: stockQty
}
}
function customerSalesGroupPerformanceScore (row) {
const periodToScore = {
90: Number(row?.customer_score_90d || 0),
@@ -2922,32 +3028,27 @@ function groupIndentPx (row) {
return '0px'
}
function groupShowsImage (row) {
return Number(row?.level ?? 0) < autoExpandThroughLevel.value
function hasProductVariantImageKey (row) {
const product = String(row?.image_product_code || row?.product_code || '').trim()
const color = String(row?.image_color_code || row?.color_code || '').trim()
return Boolean(product && color)
}
const groupDimensionFields = new Set([
'product_code',
'color_code',
'yaka_kodu',
'item_description',
'kategori',
'askili_yan',
'urun_ilk_grubu',
'urun_ana_grubu',
'urun_alt_grubu',
'market_key',
'country',
'customer_segment',
'customer_code',
'customer_name'
])
function canShowProductImage (row) {
return activeTab.value === 'products' && !row?.__group && hasProductVariantImageKey(row)
}
function groupShowsImage () {
return false
}
const groupDimensionFields = new Set(dimensionColumnNames)
function groupCellClass (colOrName) {
const name = colOrName?.name || colOrName
if (name === 'image') return 'product-image-cell'
if (colOrName?.align === 'right') return 'text-right'
return ['stock_qty', 'sales_qty_90d', 'sales_qty_180d', 'sales_qty_365d', 'sales_qty_total', 'sales_usd_90d', 'sales_usd_180d', 'sales_usd_365d', 'sales_usd_total', 'stock_days_90d', 'stock_days_180d', 'stock_days_total', 'stock_turnover_90d', 'stock_turnover_180d', 'stock_turnover_365d', 'stock_turnover_total', 'avg_price_usd_90d', 'avg_price_usd_180d', 'avg_price_usd_365d', 'avg_price_usd_total', 'base_price_usd', 'cost_price_usd', 'unit_profit_base_90d', 'unit_profit_cost_90d', 'unit_profit_base_180d', 'unit_profit_cost_180d', 'unit_profit_base_total', 'unit_profit_cost_total', 'gross_profit_usd_90d', 'gross_profit_usd_180d', 'gross_profit_usd_total', 'gross_margin_base_90d', 'gross_margin_cost_90d', 'gross_margin_base_180d', 'gross_margin_cost_180d', 'gross_margin_base_total', 'gross_margin_cost_total', 'gross_margin_90d', 'gross_margin_180d', 'market_count_90d', 'customer_count_90d', 'market_count_total', 'customer_count_total', 'sales_index_90d', 'sales_index_total', 'customer_score_90d', 'customer_score_180d', 'customer_score_365d', 'customer_score_total', 'performance_score'].includes(name)
return /(^|_)(score|qty|count|usd|days|turnover|index|margin)$/.test(name) || ['stock_qty', 'net_stock_after_order'].includes(name)
? 'text-right'
: ''
}
@@ -3017,6 +3118,11 @@ function selectedColumnFilters (tableKey, name) {
return Array.isArray(selected) ? selected : []
}
function hasActiveTableFilters (tableKey) {
const prefix = `${tableKey}:`
return Object.entries(columnFilters).some(([key, value]) => key.startsWith(prefix) && Array.isArray(value) && value.length > 0)
}
function isColumnFilterValueSelected (tableKey, name, value) {
return selectedColumnFilters(tableKey, name).includes(value)
}
@@ -3215,10 +3321,20 @@ function normalizeRow (row) {
const color = String(row?.color_code || '').trim()
const yaka = String(row?.yaka_kodu || '').trim()
const market = String(row?.market_key || '').trim()
return withProductMargins({
return withPeriodScores(withProductMargins({
...row,
row_key: `${product}|${color}|${yaka}|${market}`
})
}))
}
function withPeriodScores (row) {
const next = { ...row }
next.performance_score_90d = Number(next.performance_score_90d || productSalesPeriodScore(productPeriodMetricSource(next, '90d')))
next.performance_score_180d = Number(next.performance_score_180d || productSalesPeriodScore(productPeriodMetricSource(next, '180d')))
next.performance_score_365d = Number(next.performance_score_365d || productSalesPeriodScore(productPeriodMetricSource(next, '365d')))
next.performance_score_total = Number(next.performance_score_total || productSalesPeriodScore(productPeriodMetricSource(next, 'total')))
if (!Number(next.performance_score || 0)) next.performance_score = next.performance_score_90d
return next
}
function performanceVariantKey (row) {
@@ -3255,10 +3371,10 @@ function normalizeGeneralRow (row) {
const color = String(row?.color_code || '').trim()
const yaka = String(row?.yaka_kodu || '').trim()
const market = String(row?.market_key || '').trim()
return withGeneralMargins({
return withPeriodScores(withGeneralMargins({
...row,
row_key: `general|${product}|${color}|${yaka}|${market}`
})
}))
}
function normalizeOrderAnalysisRow (row) {
@@ -3266,19 +3382,19 @@ function normalizeOrderAnalysisRow (row) {
const color = String(row?.color_code || '').trim()
const yaka = String(row?.yaka_kodu || '').trim()
const market = String(row?.market_key || '').trim()
return {
return withOrderPeriodScores({
...row,
row_key: `orders|${product}|${color}|${yaka}|${market}`
}
})
}
function normalizeOrderGroupRow (row) {
const key = String(row?.group_key || '').trim()
const mode = String(row?.breakdown || '').trim()
return {
return withOrderPeriodScores({
...row,
row_key: `order-group|${mode}|${key}|${row?.market_key || ''}|${row?.customer_code || ''}`
}
})
}
function normalizeOrderProductCustomerRow (row) {
@@ -3287,21 +3403,32 @@ function normalizeOrderProductCustomerRow (row) {
const yaka = String(row?.yaka_kodu || '').trim()
const market = String(row?.market_key || '').trim()
const customer = String(row?.customer_code || '').trim()
return {
return withOrderPeriodScores({
...row,
row_key: `order-product-customer|${product}|${color}|${yaka}|${market}|${customer}`
}
})
}
function normalizeOrderMarketDetailRow (row) {
return {
return withOrderPeriodScores({
...row,
row_key: `order-market-detail|${row?.market_key || ''}|${row?.customer_code || ''}|${row?.order_number || ''}|${row?.product_code || ''}|${row?.color_code || ''}|${row?.yaka_kodu || ''}`
})
}
function withOrderPeriodScores (row) {
const next = { ...row }
const score = Number(next.performance_score || orderGroupPerformanceScore(next))
next.performance_score_90d = Number(next.performance_score_90d || score)
next.performance_score_180d = Number(next.performance_score_180d || score)
next.performance_score_365d = Number(next.performance_score_365d || score)
next.performance_score_total = Number(next.performance_score_total || score)
next.performance_score = score
return next
}
function normalizeSalesBreakdownRow (row) {
const next = { ...row }
const next = withPeriodScores({ ...row })
next.customer_score_90d = Number(next.customer_score_90d || customerSalesPeriodScore(periodMetricSource(next, '90d')))
next.customer_score_180d = Number(next.customer_score_180d || customerSalesPeriodScore(periodMetricSource(next, '180d')))
next.customer_score_365d = Number(next.customer_score_365d || customerSalesPeriodScore(periodMetricSource(next, '365d')))
@@ -3320,6 +3447,7 @@ function orderGroupLabel (row) {
}
function productImageKey (row) {
if (!hasProductVariantImageKey(row)) return ''
const product = String(row?.image_product_code || row?.product_code || '').trim()
const color = String(row?.image_color_code || row?.color_code || '').trim()
const yaka = String(row?.image_yaka_kodu || row?.yaka_kodu || '').trim()
@@ -3365,6 +3493,7 @@ function resolveProductImageUrl (item) {
}
async function fetchProductImagesForRow (row) {
if (!hasProductVariantImageKey(row)) return []
const key = productImageKey(row)
if (Object.prototype.hasOwnProperty.call(imageListByKey.value, key)) {
return imageListByKey.value[key]
@@ -3406,6 +3535,7 @@ async function fetchProductImagesForRow (row) {
}
function getCachedProductImageUrl (row) {
if (!canShowProductImage(row)) return ''
const key = productImageKey(row)
return imageUrlByKey.value[key] || performanceStore.imageUrl(key) || ''
}
@@ -3419,12 +3549,14 @@ function markProductImageFailed (row) {
}
async function primeProductImages (sourceRows) {
if (activeTab.value !== 'products') return
imagePrimeActiveRequests += 1
imagePrimeLoading.value = true
const items = []
const seen = new Set()
try {
for (const row of sourceRows || []) {
if (!canShowProductImage(row)) continue
const key = productImageKey(row)
const code = String(row?.image_product_code || row?.product_code || '').trim()
if (!key || !code || seen.has(key) || Object.prototype.hasOwnProperty.call(imageListByKey.value, key) || performanceStore.hasImageKey(key)) continue
@@ -3582,13 +3714,14 @@ async function loadBackendGroupedRows () {
try {
const startedAt = performance.now()
const rows = await performanceStore.fetchGroupedRows(params)
const normalizedRows = Array.isArray(rows) ? rows.map(row => normalizeGroupedDisplayRow(tabKey, row)) : []
backendGroupedRows.value = {
...backendGroupedRows.value,
[tabKey]: Array.isArray(rows) ? rows : []
[tabKey]: normalizedRows
}
console.info('[ProductPerformance][ui] grouped applied', {
tab: tabKey,
rows: Array.isArray(rows) ? rows.length : 0,
rows: normalizedRows.length,
elapsedSec: Number(((performance.now() - startedAt) / 1000).toFixed(2))
})
} catch (err) {
@@ -3598,6 +3731,20 @@ async function loadBackendGroupedRows () {
}
}
function normalizeGroupedDisplayRow (tabKey, row) {
if (tabKey === 'order_product_customers' || tabKey === 'order_market_details') {
return withOrderPeriodScores({ ...row })
}
const next = withPeriodScores({ ...row })
if (tabKey !== 'products' && tabKey !== 'idle') {
next.customer_score_90d = Number(next.customer_score_90d || customerSalesPeriodScore(periodMetricSource(next, '90d')))
next.customer_score_180d = Number(next.customer_score_180d || customerSalesPeriodScore(periodMetricSource(next, '180d')))
next.customer_score_365d = Number(next.customer_score_365d || customerSalesPeriodScore(periodMetricSource(next, '365d')))
next.customer_score_total = Number(next.customer_score_total || customerSalesPeriodScore(periodMetricSource(next, 'total')))
}
return next
}
function responseRowCount (data) {
if (Array.isArray(data)) return data.length
if (Array.isArray(data?.rows)) return data.rows.length
@@ -3687,7 +3834,6 @@ async function reload () {
await loadOrderMarketDetails(false)
}
await loadBackendGroupedRows()
await primeProductImages(rows.value)
} catch (err) {
Notify.create({ type: 'negative', message: err?.response?.data || err?.message || 'Ürün performans verisi alınamadı' })
} finally {
@@ -3701,7 +3847,6 @@ async function loadOrderProductCustomers (showLoading = true) {
const resp = await timedProductPerformanceGet('orders:product-customers', '/pricing/product-performance/orders/product-customers', { params: { limit: 500 }, timeout: 90000 })
orderProductCustomerRows.value = (Array.isArray(resp?.data) ? resp.data : []).map(normalizeOrderProductCustomerRow)
orderProductCustomersLoaded.value = true
await primeProductImages(orderProductCustomerRows.value)
} catch (err) {
Notify.create({ type: 'negative', message: err?.response?.data || err?.message || 'Ürün müşteri sipariş analizi alınamadı' })
} finally {
@@ -3734,7 +3879,6 @@ async function loadOrderAnalysis (showLoading = true) {
orderMarketRows.value = (Array.isArray(marketResp?.data) ? marketResp.data : []).map(normalizeOrderGroupRow)
orderCustomerRows.value = (Array.isArray(customerResp?.data) ? customerResp.data : []).map(normalizeOrderGroupRow)
orderAnalysisLoaded.value = true
await primeProductImages(orderAnalysisRows.value)
} catch (err) {
Notify.create({ type: 'negative', message: err?.response?.data || err?.message || 'Sipariş analiz verisi alınamadı' })
} finally {
@@ -4051,18 +4195,18 @@ onMounted(reload)
table-layout: fixed;
}
.product-breakdown-table :deep(.q-table th:nth-child(-n+11)),
.product-breakdown-table :deep(.q-table td:nth-child(-n+11)) {
.product-breakdown-table :deep(.q-table th:nth-child(-n+10)),
.product-breakdown-table :deep(.q-table td:nth-child(-n+10)) {
position: sticky;
z-index: 2;
}
.product-breakdown-table :deep(.q-table th:nth-child(-n+11)) {
.product-breakdown-table :deep(.q-table th:nth-child(-n+10)) {
z-index: 4;
background: #f8fbff;
}
.product-breakdown-table :deep(.q-table tbody tr:not(.group-row) td:nth-child(-n+11)) {
.product-breakdown-table :deep(.q-table tbody tr:not(.group-row) td:nth-child(-n+10)) {
background: #fff;
}
@@ -4125,30 +4269,22 @@ onMounted(reload)
.product-breakdown-table :deep(.q-table th:nth-child(8)),
.product-breakdown-table :deep(.q-table td:nth-child(8)) {
left: 922px;
width: 180px;
min-width: 180px;
max-width: 180px;
}
.product-breakdown-table :deep(.q-table th:nth-child(9)),
.product-breakdown-table :deep(.q-table td:nth-child(9)) {
left: 1102px;
width: 90px;
min-width: 90px;
max-width: 90px;
}
.product-breakdown-table :deep(.q-table th:nth-child(10)),
.product-breakdown-table :deep(.q-table td:nth-child(10)) {
left: 1192px;
.product-breakdown-table :deep(.q-table th:nth-child(9)),
.product-breakdown-table :deep(.q-table td:nth-child(9)) {
left: 1012px;
width: 80px;
min-width: 80px;
max-width: 80px;
}
.product-breakdown-table :deep(.q-table th:nth-child(11)),
.product-breakdown-table :deep(.q-table td:nth-child(11)) {
left: 1272px;
.product-breakdown-table :deep(.q-table th:nth-child(10)),
.product-breakdown-table :deep(.q-table td:nth-child(10)) {
left: 1092px;
width: 130px;
min-width: 130px;
max-width: 130px;
@@ -4177,8 +4313,8 @@ onMounted(reload)
white-space: nowrap;
}
.product-breakdown-table :deep(.q-table th:nth-child(n+12):not(.text-right)),
.product-breakdown-table :deep(.q-table td:nth-child(n+12):not(.text-right)),
.product-breakdown-table :deep(.q-table th:nth-child(n+11):not(.text-right)),
.product-breakdown-table :deep(.q-table td:nth-child(n+11):not(.text-right)),
.sticky-dim-table.sticky-dim-3 :deep(.q-table th:nth-child(n+4):not(.text-right)),
.sticky-dim-table.sticky-dim-3 :deep(.q-table td:nth-child(n+4):not(.text-right)),
.sticky-dim-table.sticky-dim-5 :deep(.q-table th:nth-child(n+6):not(.text-right)),