Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-06-19 00:49:47 +03:00
parent 7512e7fe7c
commit 55e36366c3
7 changed files with 413 additions and 90 deletions

View File

@@ -321,6 +321,137 @@
</div>
</q-menu>
</q-btn>
<q-btn
v-else-if="col.name === 'variantCodes'"
flat
dense
round
size="8px"
icon="filter_alt"
:color="selectedVariantCodes.length > 0 ? 'primary' : 'grey-7'"
:disable="pageBusy"
class="header-filter-btn"
@click.stop
>
<q-badge v-if="selectedVariantCodes.length > 0" color="primary" floating rounded>
{{ selectedVariantCodes.length }}
</q-badge>
<q-menu anchor="bottom right" self="top right" :offset="[0, 4]">
<div class="excel-filter-menu">
<q-input
v-model="variantFilterSearch"
dense
outlined
clearable
class="excel-filter-select"
placeholder="Varyant ara"
/>
<div class="excel-filter-actions row items-center justify-between q-pt-xs">
<q-btn flat dense size="sm" label="Tumunu Sec" :disable="pageBusy || filteredVariantOptions.length === 0" @click="selectAllVariantOptions" />
<q-btn flat dense size="sm" label="Temizle" :disable="pageBusy || selectedVariantCodes.length === 0" @click="clearVariantOptions" />
</div>
<q-virtual-scroll
v-if="filteredVariantOptions.length > 0"
class="excel-filter-options"
:items="filteredVariantOptions"
:virtual-scroll-item-size="32"
separator
>
<template #default="{ item: option }">
<q-item
:key="`variant-${option.value}`"
dense
clickable
:disable="pageBusy"
class="excel-filter-option"
@click="toggleVariantValue(option.value)"
>
<q-item-section avatar>
<q-checkbox
dense
size="sm"
:model-value="selectedVariantCodeSet.has(option.value)"
:disable="pageBusy"
@update:model-value="() => toggleVariantValue(option.value)"
@click.stop
/>
</q-item-section>
<q-item-section>
<q-item-label>{{ option.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-virtual-scroll>
<div v-else class="excel-filter-empty">Sonuc yok</div>
</div>
</q-menu>
</q-btn>
<q-btn
v-else-if="isLocalFilterableColumn(col.name)"
flat
dense
round
size="8px"
icon="filter_alt"
:color="getColumnFilterValues(col.name).length > 0 ? 'primary' : 'grey-7'"
:disable="pageBusy"
class="header-filter-btn"
@click.stop
>
<q-badge v-if="getColumnFilterValues(col.name).length > 0" color="primary" floating rounded>
{{ getColumnFilterValues(col.name).length }}
</q-badge>
<q-menu anchor="bottom right" self="top right" :offset="[0, 4]">
<div class="excel-filter-menu">
<q-input
:model-value="columnFilterSearch[col.name] || ''"
dense
outlined
clearable
class="excel-filter-select"
placeholder="Filtre ara"
@update:model-value="(val) => setColumnFilterSearch(col.name, val)"
/>
<div class="excel-filter-actions row items-center justify-between q-pt-xs">
<q-btn flat dense size="sm" label="Tumunu Sec" :disable="pageBusy || getFilteredColumnOptions(col).length === 0" @click="selectAllColumnOptions(col)" />
<q-btn flat dense size="sm" label="Temizle" :disable="pageBusy || getColumnFilterValues(col.name).length === 0" @click="clearColumnOptions(col.name)" />
</div>
<q-virtual-scroll
v-if="getFilteredColumnOptions(col).length > 0"
class="excel-filter-options"
:items="getFilteredColumnOptions(col)"
:virtual-scroll-item-size="32"
separator
>
<template #default="{ item: option }">
<q-item
:key="`${col.name}-${option.value}`"
dense
clickable
:disable="pageBusy"
class="excel-filter-option"
@click="toggleColumnFilterValue(col.name, option.value)"
>
<q-item-section avatar>
<q-checkbox
dense
size="sm"
:model-value="getColumnFilterSet(col.name).has(option.value)"
:disable="pageBusy"
@update:model-value="() => toggleColumnFilterValue(col.name, option.value)"
@click.stop
/>
</q-item-section>
<q-item-section>
<q-item-label>{{ option.label || '-' }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-virtual-scroll>
<div v-else class="excel-filter-empty">Sonuc yok</div>
</div>
</q-menu>
</q-btn>
<span v-else class="header-filter-ghost"></span>
</div>
</q-th>
@@ -503,7 +634,11 @@ const topUrunIlkGrubu = ref(null)
const topUrunAnaGrubu = ref(null)
const selectedProductCodes = ref([])
const selectedCampaignLabels = ref([])
const selectedVariantCodes = ref([])
const campaignFilterSearch = ref('')
const variantFilterSearch = ref('')
const columnFilters = ref({})
const columnFilterSearch = ref({})
const selectedPriceOptions = ref(['usd5', 'try5'])
const leftDetailsExpanded = ref(true)
@@ -537,6 +672,7 @@ const fullscreenImages = computed(() => productCardImages.value || [])
const selectedPriceSet = computed(() => new Set(selectedPriceOptions.value || []))
const selectedProductCodeSet = computed(() => new Set(selectedProductCodes.value || []))
const selectedCampaignLabelSet = computed(() => new Set(selectedCampaignLabels.value || []))
const selectedVariantCodeSet = computed(() => new Set(selectedVariantCodes.value || []))
const pageBusy = computed(() => loading.value || renderPending.value)
const canFetch = computed(() => Boolean(topUrunIlkGrubu.value || topUrunAnaGrubu.value || selectedProductCodes.value.length > 0))
const showGuidanceOverlay = computed(() => !loading.value && rows.value.length === 0 && error.value === GUIDANCE_MSG)
@@ -559,6 +695,21 @@ const filteredCampaignOptions = computed(() => {
const list = campaignOptions.value
return q ? list.filter((x) => x.label.toLocaleLowerCase('tr').includes(q)) : list
})
const variantOptions = computed(() => {
const uniq = new Set()
for (const row of rows.value || []) {
const val = toText(row?.variantCodes)
if (val) uniq.add(val)
}
return Array.from(uniq)
.sort((a, b) => variantCodeCollator.compare(a, b))
.map((value) => ({ label: value, value }))
})
const filteredVariantOptions = computed(() => {
const q = toText(variantFilterSearch.value).toLocaleLowerCase('tr')
const list = variantOptions.value
return q ? list.filter((x) => x.label.toLocaleLowerCase('tr').includes(q)) : list
})
function toText (value) {
return String(value ?? '').trim()
@@ -763,6 +914,79 @@ function clearCampaignOptions () {
selectedCampaignLabels.value = []
}
function toggleVariantValue (value) {
const v = toText(value)
if (!v) return
const set = new Set(selectedVariantCodes.value || [])
if (set.has(v)) set.delete(v)
else set.add(v)
selectedVariantCodes.value = Array.from(set).sort((a, b) => variantCodeCollator.compare(a, b))
}
function selectAllVariantOptions () {
const set = new Set(selectedVariantCodes.value || [])
for (const option of filteredVariantOptions.value) {
const v = toText(option.value)
if (v) set.add(v)
}
selectedVariantCodes.value = Array.from(set).sort((a, b) => variantCodeCollator.compare(a, b))
}
function clearVariantOptions () {
selectedVariantCodes.value = []
}
function isLocalFilterableColumn (name) {
if (!name || ['image', 'productCode', 'variantCodes', 'campaignLabel'].includes(name)) return false
return true
}
function getColumnFilterValues (name) {
const list = columnFilters.value?.[name]
return Array.isArray(list) ? list : []
}
function getColumnFilterSet (name) {
return new Set(getColumnFilterValues(name))
}
function setColumnFilterSearch (name, value) {
columnFilterSearch.value = { ...columnFilterSearch.value, [name]: toText(value) }
}
function getColumnOptions (col) {
const uniq = new Set()
for (const row of rows.value || []) {
uniq.add(exportCell(row, col))
}
return Array.from(uniq)
.sort((a, b) => String(a).localeCompare(String(b), 'tr', { numeric: true, sensitivity: 'base' }))
.map((value) => ({ label: value || '-', value }))
}
function getFilteredColumnOptions (col) {
const q = toText(columnFilterSearch.value?.[col.name]).toLocaleLowerCase('tr')
const list = getColumnOptions(col)
return q ? list.filter((x) => String(x.label || '').toLocaleLowerCase('tr').includes(q)) : list
}
function toggleColumnFilterValue (name, value) {
const set = new Set(getColumnFilterValues(name))
if (set.has(value)) set.delete(value)
else set.add(value)
columnFilters.value = { ...columnFilters.value, [name]: Array.from(set) }
}
function selectAllColumnOptions (col) {
const set = new Set(getColumnFilterValues(col.name))
for (const option of getFilteredColumnOptions(col)) set.add(option.value)
columnFilters.value = { ...columnFilters.value, [col.name]: Array.from(set) }
}
function clearColumnOptions (name) {
columnFilters.value = { ...columnFilters.value, [name]: [] }
}
function onTopUrunIlkGrubuChange () {
topUrunAnaGrubu.value = null
void fetchServerFilterOptions('urunAnaGrubu', '')
@@ -947,6 +1171,10 @@ function resetSelections () {
topUrunIlkGrubu.value = null
topUrunAnaGrubu.value = null
selectedProductCodes.value = []
selectedCampaignLabels.value = []
selectedVariantCodes.value = []
columnFilters.value = {}
columnFilterSearch.value = {}
rows.value = []
error.value = GUIDANCE_MSG
currentPage.value = 1
@@ -1031,8 +1259,25 @@ const visibleColumns = computed(() => allColumns.filter((c) => {
const filteredRows = computed(() => {
const campaignSet = selectedCampaignLabelSet.value
if (campaignSet.size === 0) return rows.value || []
return (rows.value || []).filter((row) => campaignSet.has(toText(row?.campaignLabel)))
const variantSet = selectedVariantCodeSet.value
const localFilters = columnFilters.value || {}
let list = rows.value || []
if (campaignSet.size > 0) {
list = list.filter((row) => campaignSet.has(toText(row?.campaignLabel)))
}
if (variantSet.size > 0) {
list = list.filter((row) => variantSet.has(toText(row?.variantCodes)))
}
const active = Object.entries(localFilters).filter(([, values]) => Array.isArray(values) && values.length > 0)
if (active.length > 0) {
const byName = new Map(allColumns.map((c) => [c.name, c]))
list = list.filter((row) => active.every(([name, values]) => {
const col = byName.get(name)
if (!col) return true
return new Set(values).has(exportCell(row, col))
}))
}
return list
})
const tableMinWidth = computed(() => visibleColumns.value.reduce((sum, c) => sum + extractWidth(c.style), 0))
const tableStyle = computed(() => ({

View File

@@ -884,6 +884,7 @@ const currentPage = ref(1)
let reloadTimer = null
const variantRows = ref([])
const variantRowsCache = new Map()
const variantCodeCollator = new Intl.Collator('tr', { numeric: true, sensitivity: 'base' })
const VARIANT_ROWS_CACHE_LIMIT = 16
const GUIDANCE_MSG = "Calismak icin once Urun Ilk Grubu veya Urun Ana Grubu Secin ve GRUPLARI GETIR'e Basin."
@@ -2678,7 +2679,7 @@ async function buildVariantRowsForProductPage (baseProductRows = []) {
})
continue
}
vs.sort((a, b) => String(a?.variant_code || '').localeCompare(String(b?.variant_code || ''), 'tr'))
vs.sort((a, b) => variantCodeCollator.compare(String(a?.variant_code || ''), String(b?.variant_code || '')))
for (const v of vs) {
const d1 = Number(v?.dim1 || 0)
const d3 = v?.dim3 == null ? null : Number(v?.dim3 || 0)