Merge remote-tracking branch 'origin/master'

# Conflicts:
#	svc/queries/product_performance_sql.go
#	ui/src/pages/ProductPerformanceProfitability.vue
This commit is contained in:
M_Kececi
2026-07-03 20:40:35 +03:00
parent 9e22cc5ec5
commit 3b2bcda462
2 changed files with 291 additions and 53 deletions
+275 -49
View File
@@ -543,7 +543,12 @@
flat
bordered
row-key="row_key"
class="performance-table product-breakdown-table bg-white"
:class="[
'performance-table',
'product-breakdown-table',
'bg-white',
activeTab === 'product_detail' ? 'product-detail-table' : 'product-summary-table'
]"
:rows="displayProductKpiTableRows"
:columns="activeProductColumns"
:loading="loading || backendGroupedLoading"
@@ -984,7 +989,13 @@
flat
bordered
row-key="row_key"
class="performance-table sticky-dim-table sticky-dim-9 bg-white"
:class="[
'performance-table',
'sticky-dim-table',
salesBreakdownStickyClass,
activeTab === 'sales_color_yaka_market_customer' ? 'sales-color-yaka-table' : '',
'bg-white'
]"
:rows="displayActiveSalesBreakdownTableRows"
:columns="visibleSalesBreakdownColumns"
:loading="loading || backendGroupedLoading"
@@ -1564,8 +1575,8 @@ const expandedGroups = ref({})
const lastManualExpandedGroupKey = ref('')
const selectedExpandLevelKeysByTab = reactive({
products: ['urun_ilk_grubu', 'askili_yan', 'kategori', 'urun_ana_grubu', 'urun_alt_grubu'],
product_detail: ['urun_ana_grubu', 'urun_alt_grubu', 'product_code', 'color_code', 'yaka_kodu'],
sales_color_yaka_market_customer: ['color_code', 'yaka_kodu', 'urun_ilk_grubu', 'askili_yan'],
product_detail: ['urun_ana_grubu', 'urun_alt_grubu', 'product_code', 'color_yaka'],
sales_color_yaka_market_customer: ['color_yaka', 'urun_ilk_grubu', 'askili_yan'],
idle: ['urun_ilk_grubu', 'askili_yan', 'kategori', 'urun_ana_grubu', 'urun_alt_grubu'],
sales_product_country_segment_market_customer: ['market_key', 'customer_code', 'customer_name', 'urun_ilk_grubu', 'askili_yan'],
sales_market_customer_product: ['market_key', 'customer_code', 'customer_name', 'urun_ilk_grubu', 'askili_yan'],
@@ -1656,13 +1667,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: 'performance_score_90d', label: '90G Skor', field: row => formatNumber(row.performance_score_90d, 2), 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: 'performance_score_180d', label: '180G Skor', field: row => formatNumber(row.performance_score_180d, 2), 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: 'performance_score_365d', label: '360G Skor', field: row => formatNumber(row.performance_score_365d, 2), 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: 'performance_score_total', label: 'Genel Skor', field: row => formatNumber(row.performance_score_total, 2), 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 },
@@ -1703,7 +1714,7 @@ const columns = [
{ name: 'customer_count_total', label: 'Genel Müşteri', field: row => formatNumber(row.customer_count_total, 0), align: 'right', sortable: true },
{ name: 'sales_index_90d', label: 'Piyasa End.', field: row => formatNumber(row.sales_index_90d, 2), align: 'right', sortable: true },
{ name: 'sales_index_total', label: 'Genel Endeks', field: row => formatNumber(row.sales_index_total, 2), align: 'right', sortable: true },
{ name: 'performance_score', label: 'Skor', field: row => formatNumber(row.performance_score, 1), align: 'right', sortable: true },
{ name: 'performance_score', label: 'Skor', field: row => formatNumber(row.performance_score, 2), align: 'right', sortable: true },
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left' },
{ name: 'recommendation', label: 'Öneri', field: 'recommendation', align: 'left' }
]
@@ -1895,7 +1906,7 @@ const generalColumns = [
{ name: 'customer_count_total', label: 'Toplam Müşteri', field: row => formatNumber(row.customer_count_total, 0), align: 'right', sortable: true },
{ name: 'invoice_count_total', label: 'Fatura', field: row => formatNumber(row.invoice_count_total, 0), align: 'right', sortable: true },
{ name: 'sales_index_total', label: 'Genel Endeks', field: row => formatNumber(row.sales_index_total, 2), align: 'right', sortable: true },
{ name: 'performance_score', label: 'Genel Skor', field: row => formatNumber(row.performance_score, 1), align: 'right', sortable: true },
{ name: 'performance_score', label: 'Genel Skor', field: row => formatNumber(row.performance_score, 2), align: 'right', sortable: true },
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left', sortable: true },
{ name: 'first_sale_date', label: 'İlk Satış', field: 'first_sale_date', align: 'left', sortable: true },
{ name: 'last_sale_date', label: 'Son Satış', field: 'last_sale_date', align: 'left', sortable: true },
@@ -2089,7 +2100,7 @@ const customerColumns = [
{ name: 'sales_qty_90d', label: '90G Adet', field: row => formatNumber(row.sales_qty_90d, 0), align: 'right', sortable: true },
{ name: 'sales_usd_90d', label: '90G USD', field: 'sales_usd_90d', align: 'right', sortable: true },
{ name: 'avg_price_usd_90d', label: 'Ort. USD', field: 'avg_price_usd_90d', align: 'right', sortable: true },
{ name: 'customer_score_90d', label: '90G Müşteri Skor', field: row => formatNumber(row.customer_score_90d, 1), align: 'right', sortable: true },
{ name: 'customer_score_90d', label: '90G Müşteri Skor', field: row => formatNumber(row.customer_score_90d, 2), align: 'right', sortable: true },
{ name: 'base_price_usd_90d', label: '90G Taban', field: row => formatMoney(row.base_price_usd_90d, 'USD'), align: 'right', sortable: true },
{ name: 'cost_price_usd_90d', label: '90G Çıplak', field: row => formatMoney(row.cost_price_usd_90d, 'USD'), align: 'right', sortable: true },
{ name: 'gross_profit_base_usd_90d', label: '90G Taban K/Z', field: row => formatMoney(row.gross_profit_base_usd_90d, 'USD'), align: 'right', sortable: true },
@@ -2137,7 +2148,7 @@ const salesBreakdownColumns = [
{ name: 'gross_profit_cost_usd_180d', label: '180G Çıplak K/Z', field: row => formatMoney(row.gross_profit_cost_usd_180d, 'USD'), align: 'right', sortable: true },
{ name: 'gross_margin_base_180d', label: '180G Taban Marj', field: row => formatPercent(row.gross_margin_base_180d), align: 'right', sortable: true },
{ name: 'gross_margin_cost_180d', label: '180G Çıplak Marj', field: row => formatPercent(row.gross_margin_cost_180d), 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_180d', label: '180G Müşteri Skor', field: row => formatNumber(row.customer_score_180d, 2), align: 'right', sortable: true },
{ name: 'sales_qty_365d', label: '360G Adet', field: row => formatNumber(row.sales_qty_365d, 0), align: 'right', sortable: true },
{ name: 'sales_usd_365d', label: '360G USD', field: row => formatMoney(row.sales_usd_365d, 'USD'), align: 'right', sortable: true },
{ name: 'avg_price_usd_365d', label: '360G Ort. USD', field: row => formatMoney(row.avg_price_usd_365d, 'USD'), align: 'right', sortable: true },
@@ -2148,7 +2159,7 @@ const salesBreakdownColumns = [
{ name: 'gross_profit_cost_usd_365d', label: '360G Çıplak K/Z', field: row => formatMoney(row.gross_profit_cost_usd_365d, 'USD'), align: 'right', sortable: true },
{ name: 'gross_margin_base_365d', label: '360G Taban Marj', field: row => formatPercent(row.gross_margin_base_365d), align: 'right', sortable: true },
{ name: 'gross_margin_cost_365d', label: '360G Çıplak Marj', field: row => formatPercent(row.gross_margin_cost_365d), 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_365d', label: '360G Müşteri Skor', field: row => formatNumber(row.customer_score_365d, 2), align: 'right', sortable: true },
{ name: 'customer_count_total', label: 'Genel Müşteri', field: row => formatNumber(row.customer_count_total, 0), align: 'right', sortable: true },
{ name: 'invoice_count_total', label: 'Genel Fatura', field: row => formatNumber(row.invoice_count_total, 0), align: 'right', sortable: true },
{ name: 'sales_qty_total', label: 'Genel Adet', field: row => formatNumber(row.sales_qty_total, 0), align: 'right', sortable: true },
@@ -2161,26 +2172,26 @@ const salesBreakdownColumns = [
{ name: 'gross_profit_cost_usd_total', label: 'Genel Çıplak K/Z', field: row => formatMoney(row.gross_profit_cost_usd_total, 'USD'), align: 'right', sortable: true },
{ name: 'gross_margin_base_total', label: 'Genel Taban Marj', field: row => formatPercent(row.gross_margin_base_total), align: 'right', sortable: true },
{ name: 'gross_margin_cost_total', label: 'Genel Çıplak Marj', field: row => formatPercent(row.gross_margin_cost_total), 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 },
{ name: 'customer_score_total', label: 'Genel Müşteri Skor', field: row => formatNumber(row.customer_score_total, 2), align: 'right', sortable: true },
{ name: 'sales_index_90d', label: 'Endeks', field: row => formatNumber(row.sales_index_90d, 2), align: 'right', sortable: true },
{ name: 'performance_score', label: 'Skor', field: row => formatNumber(row.performance_score, 1), align: 'right', sortable: true },
{ name: 'performance_score', label: 'Skor', field: row => formatNumber(row.performance_score, 2), align: 'right', sortable: true },
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left', sortable: true },
{ name: 'last_sale_date', label: 'Son Satış', field: 'last_sale_date', align: 'left', sortable: true },
{ 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 }
{ name: 'performance_score_90d', label: '90G Ürün Skor', field: row => formatNumber(row.performance_score_90d, 2), align: 'right', sortable: true },
{ name: 'performance_score_180d', label: '180G Ürün Skor', field: row => formatNumber(row.performance_score_180d, 2), align: 'right', sortable: true },
{ name: 'performance_score_365d', label: '360G Ürün Skor', field: row => formatNumber(row.performance_score_365d, 2), align: 'right', sortable: true },
{ name: 'performance_score_total', label: 'Genel Ürün Skor', field: row => formatNumber(row.performance_score_total, 2), 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 }
{ name: 'customer_score_90d', label: '90G Müşteri Skor', field: row => formatNumber(row.customer_score_90d, 2), align: 'right', sortable: true },
{ name: 'customer_score_180d', label: '180G Müşteri Skor', field: row => formatNumber(row.customer_score_180d, 2), align: 'right', sortable: true },
{ name: 'customer_score_365d', label: '360G Müşteri Skor', field: row => formatNumber(row.customer_score_365d, 2), align: 'right', sortable: true },
{ name: 'customer_score_total', label: 'Genel Müşteri Skor', field: row => formatNumber(row.customer_score_total, 2), align: 'right', sortable: true }
]
function ensureColumns (targetColumns, columnsToEnsure) {
@@ -2278,7 +2289,27 @@ function ensureColorYakaColumn (targetColumns) {
attachColumnSorts(detailStockColumns)
attachColumnSorts(detailSalesColumns)
const visibleSalesBreakdownColumns = computed(() => orderedMetricColumns(salesBreakdownColumns.filter(visiblePeriodColumn)))
const visibleSalesBreakdownColumns = computed(() => {
const base = orderedMetricColumns(salesBreakdownColumns.filter(visiblePeriodColumn))
if (activeTab.value !== 'sales_color_yaka_market_customer') return base
const priority = ['performance_bucket', 'recommendation']
const pulled = priority
.map(name => base.find(col => col.name === name))
.filter(Boolean)
const rest = base.filter(col => !priority.includes(col.name))
const insertAfter = Math.max(
rest.findIndex(col => col.name === 'item_description'),
rest.findIndex(col => col.name === 'product_code')
)
if (insertAfter < 0) return [...pulled, ...rest]
return [
...rest.slice(0, insertAfter + 1),
...pulled,
...rest.slice(insertAfter + 1)
]
})
const salesBreakdownStickyClass = computed(() => activeTab.value === 'sales_color_yaka_market_customer' ? 'sticky-dim-9' : 'sticky-dim-9')
const summaryCards = computed(() => [
{ key: 'date', label: 'KPI Tarihi', value: summary.value.kpi_date || '-' },
@@ -2352,7 +2383,7 @@ const visibleOrderAnalysisColumns = computed(() => {
function performanceCardsForRow (row) {
const source = row || {}
return [
{ key: 'score', label: 'Skor', value: formatNumber(source.performance_score, 1) },
{ key: 'score', label: 'Skor', value: formatNumber(source.performance_score, 2) },
{ key: 'stock', label: 'Stok', value: formatNumber(source.stock_qty, 0) },
{ key: 'sales90', label: '90G Satış', value: formatNumber(source.sales_qty_90d, 0) },
{ key: 'sales180', label: '180G Satış', value: formatNumber(source.sales_qty_180d, 0) },
@@ -2411,13 +2442,11 @@ const tabGroupLevels = {
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu' },
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
{ key: 'product_code', label: 'Ürün' },
{ key: 'color_code', label: 'Renk' },
{ key: 'yaka_kodu', label: 'Yaka' },
{ key: 'color_yaka', label: 'Renk/Yaka' },
{ key: 'market_key', label: 'Piyasa' }
],
sales_color_yaka_market_customer: [
{ key: 'color_code', label: 'Renk' },
{ key: 'yaka_kodu', label: 'Yaka' },
{ key: 'color_yaka', label: 'Renk/Yaka' },
{ key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu' },
{ key: 'askili_yan', label: 'Askılı/Yan' },
{ key: 'kategori', label: 'Kategori' },
@@ -2531,7 +2560,8 @@ const selectedExpandThroughLevel = computed(() => {
const autoExpandThroughLevel = computed(() => {
const selectedLevel = selectedExpandThroughLevel.value
return selectedLevel >= 0 ? selectedLevel : activeGroupLevels.value.length - 1
const lastGroupLevel = Math.max(0, activeGroupLevels.value.length - 2)
return selectedLevel >= 0 ? Math.min(selectedLevel, lastGroupLevel) : Math.min(2, lastGroupLevel)
})
const tableFilterSourceMap = computed(() => ({
@@ -2880,8 +2910,8 @@ function makeGroupRow (key, level, groupDef, value, groupRows) {
avg_price_usd_180d: weightedAverage(groupRows, 'sales_usd_180d', 'sales_qty_180d'),
avg_price_usd_365d: weightedAverage(groupRows, 'sales_usd_365d', 'sales_qty_365d'),
avg_price_usd_total: weightedAverage(groupRows, 'sales_usd_total', 'sales_qty_total'),
base_price_usd: weightedAverageOrAverage(groupRows, 'base_price_usd', 'sales_qty_90d'),
cost_price_usd: weightedAverageOrAverage(groupRows, 'cost_price_usd', 'sales_qty_90d'),
base_price_usd: weightedAverageProductCost(groupRows, 'base_price_usd'),
cost_price_usd: weightedAverageProductCost(groupRows, 'cost_price_usd'),
unit_profit_base_90d: weightedAverageOrAverage(groupRows, 'unit_profit_base_90d', 'sales_qty_90d'),
unit_profit_cost_90d: weightedAverageOrAverage(groupRows, 'unit_profit_cost_90d', 'sales_qty_90d'),
unit_profit_base_180d: weightedAverageOrAverage(groupRows, 'unit_profit_base_180d', 'sales_qty_180d'),
@@ -2904,7 +2934,7 @@ function makeGroupRow (key, level, groupDef, value, groupRows) {
performance_bucket: dominantValue(groupRows, 'performance_bucket'),
...aggregate,
performance_score: groupPerformanceScore(aggregate, groupDef.key),
recommendation: `${formatNumber(groupRows.length, 0)} satır`
recommendation: dominantValue(groupRows, 'recommendation') || `${formatNumber(groupRows.length, 0)} satır`
}
}
@@ -2925,6 +2955,10 @@ function aggregateGroupFields (sourceRows, groupField = '') {
out[field] = distinctVariantStockCost(sourceRows)
continue
}
if (field === 'base_price_usd' || field === 'cost_price_usd') {
out[field] = weightedAverageProductCost(sourceRows, field)
continue
}
if (shouldSumField(field)) {
out[field] = sumRows(sourceRows, field)
} else if (shouldAverageField(field)) {
@@ -3205,6 +3239,38 @@ function weightedAverageOrAverage (sourceRows, valueField, qtyField) {
return 0
}
function weightedAverageProductCost (sourceRows, valueField) {
const salesRows = sourceRows
.map(row => ({
value: Number(row[valueField] || 0),
qty: Number(row.sales_qty_total || 0)
}))
.filter(row => Number.isFinite(row.value) && Number.isFinite(row.qty) && row.value > 0 && row.qty > 0)
const salesQty = salesRows.reduce((sum, row) => sum + row.qty, 0)
if (salesQty > 0) {
return salesRows.reduce((sum, row) => sum + row.value * row.qty, 0) / salesQty
}
const variants = new Map()
for (const row of sourceRows) {
const key = productVariantStockKey(row)
if (!key || variants.has(key)) continue
variants.set(key, row)
}
const stockSource = variants.size > 0 ? Array.from(variants.values()) : sourceRows
const stockRows = stockSource
.map(row => ({
value: Number(row[valueField] || 0),
qty: Number(row.stock_qty || 0)
}))
.filter(row => Number.isFinite(row.value) && Number.isFinite(row.qty) && row.value > 0 && row.qty > 0)
const stockQty = stockRows.reduce((sum, row) => sum + row.qty, 0)
if (stockQty > 0) {
return stockRows.reduce((sum, row) => sum + row.value * row.qty, 0) / stockQty
}
return 0
}
function marginFromSalesCost (salesUSD, qty, unitCost) {
const sales = Number(salesUSD || 0)
if (!sales) return 0
@@ -3280,13 +3346,13 @@ function expandSelectedProductGroups () {
function groupLabelColumnName (row) {
const field = String(row?.group_field || '')
if (field === 'color_code' || field === 'yaka_kodu') return 'color_yaka'
if (field === 'color_code' || field === 'yaka_kodu' || field === 'color_yaka') return 'color_yaka'
return field || 'product_code'
}
function groupDisplayLabel (row) {
const field = String(row?.group_field || '')
if (field === 'color_code' || field === 'yaka_kodu') {
if (field === 'color_code' || field === 'yaka_kodu' || field === 'color_yaka') {
return colorYakaValue(row) || row?.label || row?.group_value || ''
}
return row?.label || row?.group_value || ''
@@ -3303,13 +3369,13 @@ function hasProductVariantImageKey (row) {
}
function canShowProductImage (row) {
return activeTab.value === 'products' && hasProductVariantImageKey(row)
return isProductKpiTab.value && hasProductVariantImageKey(row)
}
function groupShowsImage (row) {
return activeTab.value === 'products' &&
return isProductKpiTab.value &&
row?.__group &&
['product_code', 'color_code', 'yaka_kodu'].includes(String(row.group_field || '')) &&
['product_code', 'color_code', 'yaka_kodu', 'color_yaka'].includes(String(row.group_field || '')) &&
hasProductVariantImageKey(row)
}
@@ -3328,7 +3394,7 @@ function formatGroupCell (row, colOrName) {
const name = colOrName?.name || colOrName
if (name === 'image') return ''
if (name === 'recommendation') return row.recommendation || ''
if (name === 'color_yaka' && ['color_code', 'yaka_kodu'].includes(String(row?.group_field || ''))) return colorYakaValue(row)
if (name === 'color_yaka' && ['color_code', 'yaka_kodu', 'color_yaka'].includes(String(row?.group_field || ''))) return colorYakaValue(row)
if (groupDimensionFields.has(name) && name !== row?.group_field) return ''
return formatTableCell(row, colOrName)
}
@@ -3635,7 +3701,7 @@ function formatTableCell (row, colOrName) {
if (isMoneyField(name)) return formatMoney(row[name], 'USD')
if (isPercentField(name)) return formatPercent(row[name])
if (isIntegerField(name)) return formatNumber(row[name], 0)
if (isDecimalField(name)) return formatNumber(row[name], name === 'performance_score' ? 1 : 2)
if (isDecimalField(name)) return formatNumber(row[name], 2)
if (typeof col?.field === 'function') return col.field(row)
return formatProductCell(row, name)
}
@@ -3679,7 +3745,7 @@ function formatProductCell (row, name) {
case 'sales_index_90d':
case 'sales_index_total':
case 'performance_score':
return formatNumber(row[name], name === 'performance_score' ? 1 : 2)
return formatNumber(row[name], 2)
case 'sales_usd_90d':
case 'sales_usd_180d':
case 'sales_usd_365d':
@@ -3955,7 +4021,7 @@ function markProductImageFailed (row) {
}
async function primeProductImages (sourceRows) {
if (activeTab.value !== 'products') return
if (!isProductKpiTab.value) return
imagePrimeActiveRequests += 1
imagePrimeLoading.value = true
const items = []
@@ -4570,6 +4636,15 @@ onMounted(reload)
.performance-table :deep(.q-table__middle) {
max-height: calc(100vh - 220px);
transform: rotateX(180deg);
}
.performance-table :deep(.q-table__middle > .q-table) {
transform: rotateX(180deg);
}
.performance-table :deep(.q-table__bottom) {
display: none;
}
.performance-table :deep(.q-table) {
@@ -4647,7 +4722,7 @@ onMounted(reload)
.performance-table :deep(.q-table tbody td .q-field),
.performance-table :deep(.q-table tbody td input),
.performance-table :deep(.q-table tbody td textarea) {
min-width: 54px;
min-width: 72px;
max-width: 100%;
}
@@ -4661,10 +4736,10 @@ onMounted(reload)
.performance-table :deep(.q-table th.color-yaka-cell),
.performance-table :deep(.q-table td.color-yaka-cell) {
width: 132px;
min-width: 132px;
max-width: 150px;
font-size: 10px;
width: 170px;
min-width: 170px;
max-width: 190px;
font-size: 10.5px;
}
.performance-table :deep(.q-table th:first-child),
@@ -4781,6 +4856,95 @@ onMounted(reload)
box-shadow: 8px 0 10px -10px rgba(17, 24, 39, 0.45);
}
.product-breakdown-table.product-detail-table :deep(.q-table) {
min-width: 2500px;
}
.product-breakdown-table.product-detail-table :deep(.q-table td:nth-child(n+8)) {
position: static;
left: auto;
z-index: auto;
box-shadow: none;
}
.product-breakdown-table.product-detail-table :deep(.q-table th:nth-child(n+8)) {
left: auto;
z-index: 1;
box-shadow: none;
}
.product-breakdown-table.product-detail-table :deep(.q-table th:nth-child(-n+7)),
.product-breakdown-table.product-detail-table :deep(.q-table td:nth-child(-n+7)) {
position: sticky;
z-index: 2;
}
.product-breakdown-table.product-detail-table :deep(.q-table th:nth-child(-n+7)) {
z-index: 4;
background: #f8fbff;
}
.product-breakdown-table.product-detail-table :deep(.q-table tbody tr:not(.group-row) td:nth-child(-n+7)) {
background: #fff;
}
.product-breakdown-table.product-detail-table :deep(.q-table th:nth-child(1)),
.product-breakdown-table.product-detail-table :deep(.q-table td:nth-child(1)) {
left: 0;
width: 72px;
min-width: 72px;
max-width: 72px;
}
.product-breakdown-table.product-detail-table :deep(.q-table th:nth-child(2)),
.product-breakdown-table.product-detail-table :deep(.q-table td:nth-child(2)) {
left: 72px;
width: 150px;
min-width: 150px;
max-width: 150px;
}
.product-breakdown-table.product-detail-table :deep(.q-table th:nth-child(3)),
.product-breakdown-table.product-detail-table :deep(.q-table td:nth-child(3)) {
left: 222px;
width: 150px;
min-width: 150px;
max-width: 150px;
}
.product-breakdown-table.product-detail-table :deep(.q-table th:nth-child(4)),
.product-breakdown-table.product-detail-table :deep(.q-table td:nth-child(4)) {
left: 372px;
width: 130px;
min-width: 130px;
max-width: 130px;
}
.product-breakdown-table.product-detail-table :deep(.q-table th:nth-child(5)),
.product-breakdown-table.product-detail-table :deep(.q-table td:nth-child(5)) {
left: 502px;
width: 170px;
min-width: 170px;
max-width: 170px;
}
.product-breakdown-table.product-detail-table :deep(.q-table th:nth-child(6)),
.product-breakdown-table.product-detail-table :deep(.q-table td:nth-child(6)) {
left: 672px;
width: 170px;
min-width: 170px;
max-width: 190px;
}
.product-breakdown-table.product-detail-table :deep(.q-table th:nth-child(7)),
.product-breakdown-table.product-detail-table :deep(.q-table td:nth-child(7)) {
left: 842px;
width: 120px;
min-width: 120px;
max-width: 120px;
box-shadow: 8px 0 10px -10px rgba(17, 24, 39, 0.45);
}
.sticky-dim-table :deep(.q-table) {
min-width: 1700px;
table-layout: fixed;
@@ -4797,12 +4961,17 @@ onMounted(reload)
.sticky-dim-table :deep(.q-table td.text-right),
.product-breakdown-table :deep(.q-table th.text-right),
.product-breakdown-table :deep(.q-table td.text-right) {
min-width: 98px;
width: 98px;
min-width: 124px;
width: 124px;
max-width: none;
white-space: nowrap;
}
.product-breakdown-table :deep(.q-table td.text-right) {
font-size: 10.5px;
font-variant-numeric: tabular-nums;
}
.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)),
@@ -4939,6 +5108,63 @@ onMounted(reload)
box-shadow: 8px 0 10px -10px rgba(17, 24, 39, 0.45);
}
.sales-color-yaka-table :deep(.q-table th:nth-child(1)),
.sales-color-yaka-table :deep(.q-table td:nth-child(1)) {
left: 0;
width: 170px;
min-width: 170px;
max-width: 190px;
}
.sales-color-yaka-table :deep(.q-table th:nth-child(2)),
.sales-color-yaka-table :deep(.q-table td:nth-child(2)) {
left: 170px;
}
.sales-color-yaka-table :deep(.q-table th:nth-child(3)),
.sales-color-yaka-table :deep(.q-table td:nth-child(3)) {
left: 288px;
}
.sales-color-yaka-table :deep(.q-table th:nth-child(4)),
.sales-color-yaka-table :deep(.q-table td:nth-child(4)) {
left: 406px;
}
.sales-color-yaka-table :deep(.q-table th:nth-child(5)),
.sales-color-yaka-table :deep(.q-table td:nth-child(5)) {
left: 524px;
}
.sales-color-yaka-table :deep(.q-table th:nth-child(6)),
.sales-color-yaka-table :deep(.q-table td:nth-child(6)) {
left: 642px;
width: 130px;
min-width: 130px;
}
.sales-color-yaka-table :deep(.q-table th:nth-child(7)),
.sales-color-yaka-table :deep(.q-table td:nth-child(7)) {
left: 772px;
width: 170px;
min-width: 170px;
}
.sales-color-yaka-table :deep(.q-table th:nth-child(8)),
.sales-color-yaka-table :deep(.q-table td:nth-child(8)) {
left: 942px;
width: 116px;
min-width: 116px;
}
.sales-color-yaka-table :deep(.q-table th:nth-child(9)),
.sales-color-yaka-table :deep(.q-table td:nth-child(9)) {
left: 1058px;
width: 190px;
min-width: 190px;
box-shadow: 8px 0 10px -10px rgba(17, 24, 39, 0.45);
}
.filterable-header-cell {
vertical-align: middle;
}