Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-06-18 23:57:25 +03:00
parent b59889bbdb
commit 81d1af61be

View File

@@ -363,8 +363,7 @@
:class="[props.col.classes, { 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }]" :class="[props.col.classes, { 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }]"
:style="getBodyCellStyle(props.col)" :style="getBodyCellStyle(props.col)"
> >
<q-badge v-if="props.row.campaignLabel" color="primary" outline :label="props.row.campaignLabel" /> <q-badge v-if="props.row.campaignLabel" color="primary" outline :label="props.row.campaignLabel" class="campaign-badge" />
<span v-else class="text-grey-6">-</span>
</q-td> </q-td>
</template> </template>
@@ -505,7 +504,7 @@ const topUrunAnaGrubu = ref(null)
const selectedProductCodes = ref([]) const selectedProductCodes = ref([])
const selectedCampaignLabels = ref([]) const selectedCampaignLabels = ref([])
const campaignFilterSearch = ref('') const campaignFilterSearch = ref('')
const selectedPriceOptions = ref(priceOptions.map((x) => x.value)) const selectedPriceOptions = ref(['usd5', 'try5'])
const leftDetailsExpanded = ref(true) const leftDetailsExpanded = ref(true)
const rows = ref([]) const rows = ref([])
@@ -815,7 +814,7 @@ async function reloadData ({ page = 1 } = {}) {
} }
rows.value = buildRows(products, variants) rows.value = buildRows(products, variants)
error.value = '' error.value = ''
void loadImagesForRows(rows.value.slice(0, 120)) void loadImagesForRows(rows.value)
await nextTick() await nextTick()
} catch (err) { } catch (err) {
rows.value = [] rows.value = []
@@ -836,10 +835,18 @@ async function loadImagesForRows (list) {
seen.add(key) seen.add(key)
targets.push({ row, key }) targets.push({ row, key })
} }
await Promise.all(targets.map(async ({ row, key }) => { const concurrency = 12
let cursor = 0
let loaded = 0
const workers = Array.from({ length: Math.min(concurrency, targets.length) }, async () => {
for (;;) {
const target = targets[cursor]
cursor += 1
if (!target) return
const { row, key } = target
if (imageCache.has(key)) { if (imageCache.has(key)) {
row.imageUrl = imageCache.get(key) row.imageUrl = imageCache.get(key)
return continue
} }
try { try {
const res = await api.get('/product-images', { const res = await api.get('/product-images', {
@@ -858,7 +865,11 @@ async function loadImagesForRows (list) {
} catch { } catch {
imageCache.set(key, '') imageCache.set(key, '')
} }
})) loaded += 1
if (loaded % 12 === 0) rows.value = [...rows.value]
}
})
await Promise.all(workers)
rows.value = [...rows.value] rows.value = [...rows.value]
} }
@@ -978,26 +989,29 @@ function col (name, label, field, width, extra = {}) {
const allColumns = [ const allColumns = [
col('image', '', 'imageUrl', 108, { align: 'center', classes: 'image-col sticky-col' }), col('image', '', 'imageUrl', 108, { align: 'center', classes: 'image-col sticky-col' }),
col('brandGroupSelection', 'MARKA GRUBU', 'brandGroupSelection', 86, { classes: 'ps-col sticky-col' }), col('brandGroupSelection', 'MARKA GRUBU', 'brandGroupSelection', 86, { classes: 'ps-col sticky-col' }),
col('marka', 'MARKA', 'marka', 62, { sortable: true, classes: 'ps-col sticky-col' }), col('marka', 'MARKA', 'marka', 72, { sortable: true, classes: 'ps-col sticky-col' }),
col('productCode', 'URUN KODU', 'productCode', 112, { sortable: true, classes: 'ps-col product-code-col sticky-col' }), col('productCode', 'URUN KODU', 'productCode', 112, { sortable: true, classes: 'ps-col product-code-col sticky-col' }),
col('variantCodes', 'VARYANT', 'variantCodes', 112, { classes: 'ps-col variant-col sticky-col' }), col('variantCodes', 'VARYANT', 'variantCodes', 128, { classes: 'ps-col variant-col sticky-col' }),
col('variantStocks', 'STOK', 'stockQty', 62, { align: 'right', sortable: true, classes: 'ps-col variant-stock-col sticky-col' }), col('variantStocks', 'STOK', 'stockQty', 72, { align: 'right', sortable: true, classes: 'ps-col variant-stock-col sticky-col' }),
col('campaignLabel', 'KAMPANYA', 'campaignLabel', 150, { classes: 'ps-col campaign-col sticky-col' }), col('campaignLabel', 'KAMPANYA', 'campaignLabel', 150, { classes: 'ps-col campaign-col sticky-col' }),
col('campaignRate', 'IND %', 'campaignRate', 58, { align: 'right', classes: 'ps-col campaign-rate-col sticky-col' }), col('campaignRate', 'IND %', 'campaignRate', 64, { align: 'right', classes: 'ps-col campaign-rate-col sticky-col' }),
col('askiliYan', 'ASKILI YAN', 'askiliYan', 58, { sortable: true, classes: 'ps-col' }), col('askiliYan', 'ASKILI YAN', 'askiliYan', 72, { sortable: true, classes: 'ps-col' }),
col('kategori', 'KATEGORI', 'kategori', 58, { sortable: true, classes: 'ps-col' }), col('kategori', 'KATEGORI', 'kategori', 82, { sortable: true, classes: 'ps-col' }),
col('urunIlkGrubu', 'URUN ILK GRUBU', 'urunIlkGrubu', 70, { sortable: true, classes: 'ps-col' }), col('urunIlkGrubu', 'URUN ILK GRUBU', 'urunIlkGrubu', 88, { sortable: true, classes: 'ps-col' }),
col('urunAnaGrubu', 'URUN ANA GRUBU', 'urunAnaGrubu', 74, { sortable: true, classes: 'ps-col' }), col('urunAnaGrubu', 'URUN ANA GRUBU', 'urunAnaGrubu', 96, { sortable: true, classes: 'ps-col' }),
col('urunAltGrubu', 'URUN ALT GRUBU', 'urunAltGrubu', 74, { sortable: true, classes: 'ps-col' }), col('urunAltGrubu', 'URUN ALT GRUBU', 'urunAltGrubu', 96, { sortable: true, classes: 'ps-col' }),
col('icerik', 'ICERIK', 'icerik', 66, { sortable: true, classes: 'ps-col' }), col('icerik', 'ICERIK', 'icerik', 112, { sortable: true, classes: 'ps-col' }),
col('karisim', 'KARISIM', 'karisim', 66, { sortable: true, classes: 'ps-col' }), col('karisim', 'KARISIM', 'karisim', 96, { sortable: true, classes: 'ps-col' }),
...campaignPairs.flatMap((p) => [ ...campaignPairs.flatMap((p) => [
col(p.base, p.base.toUpperCase().replace(/([A-Z]+)(\d)/, '$1 $2'), p.base, 78, { align: 'right', classes: `${p.base.slice(0, 3)}-col` }), col(p.base, p.base.toUpperCase().replace(/([A-Z]+)(\d)/, '$1 $2'), p.base, 78, { align: 'right', classes: `${p.base.slice(0, 3)}-col` }),
col(p.derived, `${p.base.toUpperCase().replace(/([A-Z]+)(\d)/, '$1 $2')} KMP`, p.derived, 88, { align: 'right', classes: `${p.base.slice(0, 3)}-col campaign-price-col` }) col(p.derived, `${p.base.toUpperCase().replace(/([A-Z]+)(\d)/, '$1 $2')} KMP`, p.derived, 88, { align: 'right', classes: `${p.base.slice(0, 3)}-col campaign-price-col` })
]) ])
] ]
const hideableLeftDetailColumnNames = new Set([ const compactHiddenColumnNames = new Set([
'variantStocks',
'campaignLabel',
'campaignRate',
'askiliYan', 'askiliYan',
'kategori', 'kategori',
'urunIlkGrubu', 'urunIlkGrubu',
@@ -1010,7 +1024,7 @@ const hideableLeftDetailColumnNames = new Set([
const visibleColumns = computed(() => allColumns.filter((c) => { const visibleColumns = computed(() => allColumns.filter((c) => {
if (/^(usd|eur|try)[1-6]$/.test(c.name)) return selectedPriceSet.value.has(c.name) if (/^(usd|eur|try)[1-6]$/.test(c.name)) return selectedPriceSet.value.has(c.name)
if (/^(usd|eur|try)[1-6]Campaign$/.test(c.name)) return selectedPriceSet.value.has(c.name.replace(/Campaign$/, '')) if (/^(usd|eur|try)[1-6]Campaign$/.test(c.name)) return selectedPriceSet.value.has(c.name.replace(/Campaign$/, ''))
if (!leftDetailsExpanded.value && hideableLeftDetailColumnNames.has(c.name)) return false if (!leftDetailsExpanded.value && compactHiddenColumnNames.has(c.name)) return false
return true return true
})) }))
@@ -1027,9 +1041,30 @@ const tableStyle = computed(() => ({
})) }))
const stickyColumnNames = computed(() => { const stickyColumnNames = computed(() => {
const visible = new Set(visibleColumns.value.map((x) => x.name)) const visible = new Set(visibleColumns.value.map((x) => x.name))
return ['image', 'brandGroupSelection', 'marka', 'productCode', 'variantCodes', 'variantStocks', 'campaignLabel', 'campaignRate'].filter((x) => visible.has(x)) const expanded = [
'image',
'brandGroupSelection',
'marka',
'productCode',
'variantCodes',
'variantStocks',
'campaignLabel',
'campaignRate',
'askiliYan',
'kategori',
'urunIlkGrubu',
'urunAnaGrubu',
'urunAltGrubu',
'icerik',
'karisim'
]
const compact = ['image', 'brandGroupSelection', 'marka', 'productCode', 'variantCodes']
return (leftDetailsExpanded.value ? expanded : compact).filter((x) => visible.has(x))
})
const stickyBoundaryColumnName = computed(() => {
const list = stickyColumnNames.value
return list.length ? list[list.length - 1] : ''
}) })
const stickyBoundaryColumnName = 'campaignRate'
const stickyColumnNameSet = computed(() => new Set(stickyColumnNames.value)) const stickyColumnNameSet = computed(() => new Set(stickyColumnNames.value))
const stickyLeftMap = computed(() => { const stickyLeftMap = computed(() => {
const map = {} const map = {}
@@ -1043,8 +1078,9 @@ const stickyLeftMap = computed(() => {
return map return map
}) })
const stickyScrollComp = computed(() => { const stickyScrollComp = computed(() => {
const boundaryCol = allColumns.find((x) => x.name === stickyBoundaryColumnName) const boundaryName = stickyBoundaryColumnName.value
return ((stickyLeftMap.value[stickyBoundaryColumnName] || 0) + extractWidth(boundaryCol?.style)) * 1.2 const boundaryCol = allColumns.find((x) => x.name === boundaryName)
return ((stickyLeftMap.value[boundaryName] || 0) + extractWidth(boundaryCol?.style)) * 1.2
}) })
function isStickyCol (name) { function isStickyCol (name) {
@@ -1052,7 +1088,7 @@ function isStickyCol (name) {
} }
function isStickyBoundary (name) { function isStickyBoundary (name) {
return name === stickyBoundaryColumnName return name === stickyBoundaryColumnName.value
} }
function getHeaderCellStyle (col) { function getHeaderCellStyle (col) {
@@ -1081,12 +1117,11 @@ function exportCell (row, col) {
} }
function exportVisibleExcel () { function exportVisibleExcel () {
const cols = visibleColumns.value const cols = visibleColumns.value.filter((c) => c.name !== 'image')
const body = filteredRows.value.map((row) => `<tr>${cols.map((c) => { const body = filteredRows.value.map((row) => `<tr>${cols.map((c) => {
if (c.name === 'image' && row.imageUrl) return `<td><img src="${row.imageUrl}" width="100" height="100"></td>`
return `<td>${escapeHtml(exportCell(row, c))}</td>` return `<td>${escapeHtml(exportCell(row, c))}</td>`
}).join('')}</tr>`).join('') }).join('')}</tr>`).join('')
const html = `<!doctype html><html><head><meta charset="utf-8"></head><body><table border="1"><thead><tr>${cols.map((c) => `<th>${escapeHtml(c.label || 'Gorsel')}</th>`).join('')}</tr></thead><tbody>${body}</tbody></table></body></html>` const html = `<!doctype html><html><head><meta charset="utf-8"></head><body><table border="1"><thead><tr>${cols.map((c) => `<th>${escapeHtml(c.label)}</th>`).join('')}</tr></thead><tbody>${body}</tbody></table></body></html>`
const blob = new Blob([html], { type: 'application/vnd.ms-excel;charset=utf-8' }) const blob = new Blob([html], { type: 'application/vnd.ms-excel;charset=utf-8' })
const url = URL.createObjectURL(blob) const url = URL.createObjectURL(blob)
const a = document.createElement('a') const a = document.createElement('a')
@@ -1184,7 +1219,7 @@ onMounted(() => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
--pricing-row-height: 108px; --pricing-row-height: 108px;
--pricing-header-height: 72px; --pricing-header-height: 88px;
--pricing-table-height: calc(100vh - 156px); --pricing-table-height: calc(100vh - 156px);
} }
@@ -1315,6 +1350,13 @@ onMounted(() => {
border-bottom: 1px solid rgba(0, 0, 0, 0.08) !important; border-bottom: 1px solid rgba(0, 0, 0, 0.08) !important;
} }
.pricing-table :deep(td) {
overflow: hidden !important;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle !important;
}
.pricing-table :deep(th), .pricing-table :deep(th),
.pricing-table :deep(.q-table thead tr), .pricing-table :deep(.q-table thead tr),
.pricing-table :deep(.q-table thead tr.header-row-fixed), .pricing-table :deep(.q-table thead tr.header-row-fixed),
@@ -1328,9 +1370,10 @@ onMounted(() => {
.pricing-table :deep(th) { .pricing-table :deep(th) {
padding-top: 0; padding-top: 0;
padding-bottom: 0; padding-bottom: 0;
white-space: nowrap; white-space: normal;
word-break: normal; word-break: break-word;
text-overflow: ellipsis; overflow-wrap: anywhere;
overflow: hidden;
text-align: center; text-align: center;
font-size: 10px; font-size: 10px;
font-weight: 800; font-weight: 800;
@@ -1411,15 +1454,22 @@ onMounted(() => {
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
text-align: center; text-align: center;
text-overflow: ellipsis;
white-space: normal; white-space: normal;
word-break: break-word;
overflow-wrap: anywhere;
font-weight: 800; font-weight: 800;
line-height: 1.15; line-height: 1.12;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 4;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
.campaign-badge {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.header-filter-btn { .header-filter-btn {
width: 20px; width: 20px;
height: 20px; height: 20px;