Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -2,58 +2,106 @@
|
||||
<q-page class="q-pa-xs pricing-page">
|
||||
<div class="top-bar row items-center justify-between q-mb-xs">
|
||||
<div class="text-subtitle1 text-weight-bold">Urun Fiyatlandirma</div>
|
||||
<div class="row items-center q-gutter-xs">
|
||||
<q-btn-dropdown color="secondary" outline icon="view_module" label="Doviz Gorunumu" :auto-close="false">
|
||||
<q-list dense class="currency-menu-list">
|
||||
<q-item clickable @click="selectAllCurrencies">
|
||||
<q-item-section>Tumunu Sec</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable @click="clearAllCurrencies">
|
||||
<q-item-section>Tumunu Temizle</q-item-section>
|
||||
</q-item>
|
||||
<q-separator />
|
||||
<q-item v-for="option in currencyOptions" :key="option.value" clickable @click="toggleCurrencyRow(option.value)">
|
||||
<q-item-section avatar>
|
||||
<q-checkbox
|
||||
:model-value="isCurrencySelected(option.value)"
|
||||
dense
|
||||
@update:model-value="(val) => toggleCurrency(option.value, val)"
|
||||
@click.stop
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section>{{ option.label }}</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
<q-btn
|
||||
flat
|
||||
:color="showSelectedOnly ? 'primary' : 'grey-7'"
|
||||
:icon="showSelectedOnly ? 'checklist_rtl' : 'list_alt'"
|
||||
:label="showSelectedOnly ? `Secililer (${selectedRowCount})` : 'Secili Olanlari Getir'"
|
||||
:disable="!showSelectedOnly && selectedRowCount === 0"
|
||||
@click="toggleShowSelectedOnly"
|
||||
/>
|
||||
<q-btn flat color="grey-7" icon="restart_alt" label="Filtreleri Sifirla" @click="resetAll" />
|
||||
<q-btn color="primary" icon="refresh" label="Veriyi Yenile" :loading="store.loading" @click="reloadData" />
|
||||
<q-btn
|
||||
color="primary"
|
||||
outline
|
||||
icon="edit_note"
|
||||
label="Secili Olanlari Toplu Degistir"
|
||||
:disable="selectedRowCount === 0"
|
||||
@click="bulkDialogOpen = true"
|
||||
/>
|
||||
<q-pagination
|
||||
v-model="currentPage"
|
||||
color="primary"
|
||||
:max="Math.max(1, store.totalPages || 1)"
|
||||
:max-pages="8"
|
||||
boundary-links
|
||||
direction-links
|
||||
@update:model-value="onPageChange"
|
||||
/>
|
||||
<div class="text-caption text-grey-8">
|
||||
Sayfa {{ currentPage }} / {{ Math.max(1, store.totalPages || 1) }} - Toplam {{ store.totalCount || 0 }} urun kodu
|
||||
<div class="top-actions">
|
||||
<div class="row items-center q-gutter-xs top-actions-row">
|
||||
<q-select
|
||||
v-model="topUrunIlkGrubu"
|
||||
dense
|
||||
outlined
|
||||
clearable
|
||||
emit-value
|
||||
map-options
|
||||
:options="topUrunIlkGrubuOptions"
|
||||
:loading="Boolean(serverFilterLoading.urunIlkGrubu)"
|
||||
label="Urun Ilk Grubu"
|
||||
style="min-width: 220px"
|
||||
@filter="onTopFilterSearchUrunIlkGrubu"
|
||||
@update:model-value="onTopUrunIlkGrubuChange"
|
||||
/>
|
||||
<q-select
|
||||
v-model="topUrunAnaGrubu"
|
||||
dense
|
||||
outlined
|
||||
clearable
|
||||
multiple
|
||||
use-chips
|
||||
emit-value
|
||||
map-options
|
||||
:options="topUrunAnaGrubuOptions"
|
||||
:loading="Boolean(serverFilterLoading.urunAnaGrubu)"
|
||||
label="Urun Ana Grubu (max 3)"
|
||||
style="min-width: 260px"
|
||||
@filter="onTopFilterSearchUrunAnaGrubu"
|
||||
@update:model-value="onTopUrunAnaGrubuChange"
|
||||
/>
|
||||
<q-btn
|
||||
color="primary"
|
||||
icon="filter_alt"
|
||||
label="Gruplari Getir"
|
||||
:disable="!canFetchByGroup"
|
||||
:loading="store.loading"
|
||||
@click="reloadData({ page: 1 })"
|
||||
/>
|
||||
<q-btn
|
||||
flat
|
||||
color="grey-7"
|
||||
icon="restart_alt"
|
||||
label="Secimleri Sifirla"
|
||||
@click="resetGroupSelections"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="row items-center q-gutter-xs top-actions-row">
|
||||
<q-btn-dropdown color="secondary" outline icon="view_module" label="Doviz Gorunumu" :auto-close="false">
|
||||
<q-list dense class="currency-menu-list">
|
||||
<q-item clickable @click="selectAllCurrencies">
|
||||
<q-item-section>Tumunu Sec</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable @click="clearAllCurrencies">
|
||||
<q-item-section>Tumunu Temizle</q-item-section>
|
||||
</q-item>
|
||||
<q-separator />
|
||||
<q-item v-for="option in currencyOptions" :key="option.value" clickable @click="toggleCurrencyRow(option.value)">
|
||||
<q-item-section avatar>
|
||||
<q-checkbox
|
||||
:model-value="isCurrencySelected(option.value)"
|
||||
dense
|
||||
@update:model-value="(val) => toggleCurrency(option.value, val)"
|
||||
@click.stop
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section>{{ option.label }}</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
<q-btn
|
||||
flat
|
||||
:color="showSelectedOnly ? 'primary' : 'grey-7'"
|
||||
:icon="showSelectedOnly ? 'checklist_rtl' : 'list_alt'"
|
||||
:label="showSelectedOnly ? `Secililer (${selectedRowCount})` : 'Secili Olanlari Getir'"
|
||||
:disable="!showSelectedOnly && selectedRowCount === 0"
|
||||
@click="toggleShowSelectedOnly"
|
||||
/>
|
||||
<q-btn
|
||||
color="primary"
|
||||
outline
|
||||
icon="edit_note"
|
||||
label="Secili Olanlari Toplu Degistir"
|
||||
:disable="selectedRowCount === 0"
|
||||
@click="bulkDialogOpen = true"
|
||||
/>
|
||||
<q-pagination
|
||||
v-model="currentPage"
|
||||
color="primary"
|
||||
:max="Math.max(1, store.totalPages || 1)"
|
||||
:max-pages="8"
|
||||
boundary-links
|
||||
direction-links
|
||||
@update:model-value="onPageChange"
|
||||
/>
|
||||
<div class="text-caption text-grey-8">
|
||||
Sayfa {{ currentPage }} / {{ Math.max(1, store.totalPages || 1) }} - Toplam {{ store.totalCount || 0 }} urun kodu
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,7 +154,12 @@
|
||||
<q-badge v-if="hasFilter(col.field)" color="primary" floating rounded>
|
||||
{{ getFilterBadgeValue(col.field) }}
|
||||
</q-badge>
|
||||
<q-menu anchor="bottom right" self="top right" :offset="[0, 4]">
|
||||
<q-menu
|
||||
anchor="bottom right"
|
||||
self="top right"
|
||||
:offset="[0, 4]"
|
||||
@before-show="() => onFilterMenuBeforeShow(col.field)"
|
||||
>
|
||||
<div v-if="isMultiSelectFilterField(col.field)" class="excel-filter-menu">
|
||||
<q-input
|
||||
v-model="columnFilterSearch[col.field]"
|
||||
@@ -333,6 +386,24 @@
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template #body-cell-lastCostingDate="props">
|
||||
<q-td
|
||||
:props="props"
|
||||
:class="[
|
||||
{ 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) },
|
||||
{ 'cell-danger': needsCosting(props.row) }
|
||||
]"
|
||||
:style="getBodyCellStyle(props.col)"
|
||||
>
|
||||
<span :class="['date-cell-text', { 'text-white': needsCosting(props.row) }]">
|
||||
{{ formatDateDisplay(props.value) }}
|
||||
</span>
|
||||
<q-tooltip v-if="needsCosting(props.row)" anchor="top middle" self="bottom middle" :offset="[0, 6]">
|
||||
Stok girisinden sonra maliyetlendirme yapilmamis. Urun Ilk Grubu: {{ props.row.urunIlkGrubu || '-' }}
|
||||
</q-tooltip>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template #body-cell-lastPricingDate="props">
|
||||
<q-td
|
||||
:props="props"
|
||||
@@ -351,16 +422,9 @@
|
||||
:class="{ 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }"
|
||||
:style="getBodyCellStyle(props.col)"
|
||||
>
|
||||
<select
|
||||
class="native-cell-select"
|
||||
:value="props.row.brandGroupSelection"
|
||||
@change="(e) => onBrandGroupSelectionChange(props.row, e.target.value)"
|
||||
>
|
||||
<option value="">Seciniz</option>
|
||||
<option v-for="opt in brandGroupOptions" :key="opt.value" :value="opt.value">
|
||||
{{ opt.label }}
|
||||
</option>
|
||||
</select>
|
||||
<span class="cell-text" :title="props.row.brandGroupSelection || ''">
|
||||
{{ props.row.brandGroupSelection || '' }}
|
||||
</span>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
@@ -428,9 +492,10 @@
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useProductPricingStore } from 'src/stores/ProductPricingStore'
|
||||
import api from 'src/services/api'
|
||||
|
||||
const store = useProductPricingStore()
|
||||
const PAGE_LIMIT = 500
|
||||
const PAGE_LIMIT = 250
|
||||
const currentPage = ref(1)
|
||||
let reloadTimer = null
|
||||
|
||||
@@ -440,11 +505,7 @@ const multipliers = [1, 1.03, 1.06, 1.09, 1.12, 1.15]
|
||||
const rowHeight = 31
|
||||
const headerHeight = 72
|
||||
|
||||
const brandGroupOptions = [
|
||||
{ label: 'MARKA GRUBU A', value: 'MARKA GRUBU A' },
|
||||
{ label: 'MARKA GRUBU B', value: 'MARKA GRUBU B' },
|
||||
{ label: 'MARKA GRUBU C', value: 'MARKA GRUBU C' }
|
||||
]
|
||||
// Marka grubu artik Marka Siniflandirma modulunden (mk_brandgrp) gelir ve listede sadece goruntulenir.
|
||||
|
||||
const currencyOptions = [
|
||||
{ label: 'USD', value: 'USD' },
|
||||
@@ -454,7 +515,7 @@ const currencyOptions = [
|
||||
|
||||
const multiFilterColumns = [
|
||||
{ field: 'productCode', label: 'Urun Kodu' },
|
||||
{ field: 'brandGroupSelection', label: 'Marka Grubu Secimi' },
|
||||
{ field: 'brandGroupSelection', label: 'Marka Grubu' },
|
||||
{ field: 'marka', label: 'Marka' },
|
||||
{ field: 'askiliYan', label: 'Askili Yan' },
|
||||
{ field: 'kategori', label: 'Kategori' },
|
||||
@@ -466,7 +527,6 @@ const multiFilterColumns = [
|
||||
]
|
||||
const serverBackedMultiFilterFields = new Set([
|
||||
'productCode',
|
||||
'brandGroupSelection',
|
||||
'marka',
|
||||
'askiliYan',
|
||||
'kategori',
|
||||
@@ -526,6 +586,130 @@ const columnFilterSearch = ref({
|
||||
icerik: '',
|
||||
karisim: ''
|
||||
})
|
||||
|
||||
const serverFilterOptionMap = ref({})
|
||||
const serverFilterLoading = ref({})
|
||||
const serverFilterLastQuery = ref({})
|
||||
const serverFilterTimers = {}
|
||||
|
||||
const topUrunIlkGrubu = ref(null)
|
||||
const topUrunAnaGrubu = ref([])
|
||||
|
||||
const topUrunIlkGrubuOptions = computed(() => serverFilterOptionMap.value.urunIlkGrubu || [])
|
||||
const topUrunAnaGrubuOptions = computed(() => serverFilterOptionMap.value.urunAnaGrubu || [])
|
||||
const canFetchByGroup = computed(() => {
|
||||
return Boolean(String(topUrunIlkGrubu.value || '').trim()) || (topUrunAnaGrubu.value?.length || 0) > 0
|
||||
})
|
||||
|
||||
async function fetchServerFilterOptions (field, { force = false } = {}) {
|
||||
if (!serverBackedMultiFilterFields.has(field)) return
|
||||
const q = String(columnFilterSearch.value[field] || '').trim()
|
||||
const lastQ = String(serverFilterLastQuery.value[field] || '')
|
||||
const hasCached = Array.isArray(serverFilterOptionMap.value[field]) && serverFilterOptionMap.value[field].length > 0
|
||||
if (!force && hasCached && q === lastQ) return
|
||||
if (serverFilterLoading.value[field]) return
|
||||
|
||||
serverFilterLoading.value = { ...serverFilterLoading.value, [field]: true }
|
||||
serverFilterLastQuery.value = { ...serverFilterLastQuery.value, [field]: q }
|
||||
try {
|
||||
const params = { field, q, limit: 160 }
|
||||
// Cascade scope for Urun Ana Grubu options.
|
||||
if (field === 'urunAnaGrubu') {
|
||||
const ilk = String(topUrunIlkGrubu.value || '').trim()
|
||||
if (ilk) params.urun_ilk_grubu = [ilk]
|
||||
}
|
||||
const res = await api.get('/pricing/products/options', { params })
|
||||
const items = Array.isArray(res?.data?.items) ? res.data.items : []
|
||||
serverFilterOptionMap.value = {
|
||||
...serverFilterOptionMap.value,
|
||||
[field]: items.map((x) => ({
|
||||
label: String(x?.label ?? x?.value ?? '').trim(),
|
||||
value: String(x?.value ?? x?.label ?? '').trim()
|
||||
})).filter((x) => x.value)
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[product-pricing][ui] filter options error', {
|
||||
field,
|
||||
q,
|
||||
message: String(err?.message || err || 'options failed')
|
||||
})
|
||||
serverFilterOptionMap.value = { ...serverFilterOptionMap.value, [field]: [] }
|
||||
} finally {
|
||||
serverFilterLoading.value = { ...serverFilterLoading.value, [field]: false }
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleServerFilterOptionsFetch (field) {
|
||||
if (!serverBackedMultiFilterFields.has(field)) return
|
||||
if (serverFilterTimers[field]) clearTimeout(serverFilterTimers[field])
|
||||
serverFilterTimers[field] = setTimeout(() => {
|
||||
serverFilterTimers[field] = null
|
||||
void fetchServerFilterOptions(field)
|
||||
}, 220)
|
||||
}
|
||||
|
||||
function onFilterMenuBeforeShow (field) {
|
||||
if (!serverBackedMultiFilterFields.has(field)) return
|
||||
void fetchServerFilterOptions(field)
|
||||
}
|
||||
|
||||
function onTopFilterSearchUrunIlkGrubu (val, update) {
|
||||
update(() => {
|
||||
columnFilterSearch.value = { ...columnFilterSearch.value, urunIlkGrubu: String(val || '') }
|
||||
scheduleServerFilterOptionsFetch('urunIlkGrubu')
|
||||
})
|
||||
}
|
||||
|
||||
function onTopFilterSearchUrunAnaGrubu (val, update) {
|
||||
update(() => {
|
||||
columnFilterSearch.value = { ...columnFilterSearch.value, urunAnaGrubu: String(val || '') }
|
||||
scheduleServerFilterOptionsFetch('urunAnaGrubu')
|
||||
})
|
||||
}
|
||||
|
||||
function applyTopGroupFiltersToColumnFilters () {
|
||||
// Enforce max 3 selection for Urun Ana Grubu.
|
||||
const nextAna = Array.isArray(topUrunAnaGrubu.value) ? topUrunAnaGrubu.value.slice(0, 3) : []
|
||||
if (nextAna.length !== (topUrunAnaGrubu.value || []).length) topUrunAnaGrubu.value = nextAna
|
||||
const ilk = String(topUrunIlkGrubu.value || '').trim()
|
||||
|
||||
columnFilters.value = {
|
||||
...columnFilters.value,
|
||||
urunIlkGrubu: ilk ? [ilk] : [],
|
||||
urunAnaGrubu: nextAna
|
||||
}
|
||||
}
|
||||
|
||||
function onTopUrunIlkGrubuChange () {
|
||||
// Cascade: when Ilk Grubu changes, clear Ana Grubu selection and refetch options scoped by Ilk Grubu.
|
||||
topUrunAnaGrubu.value = []
|
||||
applyTopGroupFiltersToColumnFilters()
|
||||
void fetchServerFilterOptions('urunAnaGrubu', { force: true })
|
||||
}
|
||||
|
||||
function onTopUrunAnaGrubuChange () {
|
||||
applyTopGroupFiltersToColumnFilters()
|
||||
}
|
||||
|
||||
function resetGroupSelections () {
|
||||
topUrunIlkGrubu.value = null
|
||||
topUrunAnaGrubu.value = []
|
||||
applyTopGroupFiltersToColumnFilters()
|
||||
// Keep other local filters cleared too, so page is "clean render".
|
||||
store.rows = []
|
||||
store.error = 'Performans icin once Urun Ilk Grubu veya Urun Ana Grubu secin.'
|
||||
store.totalCount = 0
|
||||
store.totalPages = 1
|
||||
store.page = 1
|
||||
store.hasMore = false
|
||||
}
|
||||
|
||||
for (const field of Array.from(serverBackedMultiFilterFields)) {
|
||||
watch(
|
||||
() => columnFilterSearch.value[field],
|
||||
() => { scheduleServerFilterOptionsFetch(field) }
|
||||
)
|
||||
}
|
||||
const numberRangeFilters = ref({
|
||||
stockQty: { min: '', max: '' }
|
||||
})
|
||||
@@ -608,6 +792,7 @@ const allColumns = [
|
||||
col('calcAction', 'HESAPLA', 'calcAction', 72, { align: 'center', classes: 'ps-col' }),
|
||||
col('stockQty', 'STOK ADET', 'stockQty', 72, { align: 'right', sortable: true, classes: 'ps-col stock-col' }),
|
||||
col('stockEntryDate', 'STOK GIRIS TARIHI', 'stockEntryDate', 92, { align: 'center', sortable: true, classes: 'ps-col date-col' }),
|
||||
col('lastCostingDate', 'SON MALIYETLENDIRME', 'lastCostingDate', 110, { align: 'center', sortable: true, classes: 'ps-col date-col' }),
|
||||
col('lastPricingDate', 'SON FIYATLANDIRMA TARIHI', 'lastPricingDate', 108, { align: 'center', sortable: true, classes: 'ps-col date-col' }),
|
||||
col('askiliYan', 'ASKILI YAN', 'askiliYan', 54, { sortable: true, classes: 'ps-col' }),
|
||||
col('kategori', 'KATEGORI', 'kategori', 54, { sortable: true, classes: 'ps-col' }),
|
||||
@@ -880,6 +1065,9 @@ function clearRangeFilter (field) {
|
||||
|
||||
function getFilterOptionsForField (field) {
|
||||
if (isValueSelectFilterField(field)) return filteredValueFilterOptionMap.value[field] || []
|
||||
if (serverBackedMultiFilterFields.has(field)) {
|
||||
return serverFilterOptionMap.value[field] || []
|
||||
}
|
||||
return filteredFilterOptionMap.value[field] || []
|
||||
}
|
||||
|
||||
@@ -1025,6 +1213,14 @@ function needsRepricing (row) {
|
||||
return lastPricingDate < stockEntryDate
|
||||
}
|
||||
|
||||
function needsCosting (row) {
|
||||
const stockEntryDate = String(row?.stockEntryDate || '').trim()
|
||||
const lastCostingDate = String(row?.lastCostingDate || '').trim()
|
||||
if (!stockEntryDate) return false
|
||||
if (!lastCostingDate) return true
|
||||
return lastCostingDate < stockEntryDate
|
||||
}
|
||||
|
||||
function recalcByBasePrice (row) {
|
||||
row.basePriceTry = round2((row.basePriceUsd * usdToTry) + row.expenseForBasePrice)
|
||||
multipliers.forEach((multiplier, index) => {
|
||||
@@ -1047,7 +1243,7 @@ function calculateRow (row) {
|
||||
}
|
||||
|
||||
function onBrandGroupSelectionChange (row, val) {
|
||||
store.updateBrandGroupSelection(row, val)
|
||||
// no-op (read-only)
|
||||
}
|
||||
|
||||
function isRowSelected (rowKey) {
|
||||
@@ -1150,12 +1346,20 @@ function clearAllCurrencies () {
|
||||
}
|
||||
|
||||
function onPaginationChange (next) {
|
||||
const prevSortBy = tablePagination.value.sortBy
|
||||
const prevDesc = tablePagination.value.descending
|
||||
tablePagination.value = {
|
||||
...tablePagination.value,
|
||||
...(next || {}),
|
||||
page: 1,
|
||||
rowsPerPage: 0
|
||||
}
|
||||
const nextSortBy = tablePagination.value.sortBy
|
||||
const nextDesc = tablePagination.value.descending
|
||||
if (nextSortBy !== prevSortBy || nextDesc !== prevDesc) {
|
||||
currentPage.value = 1
|
||||
void reloadData({ page: 1 })
|
||||
}
|
||||
}
|
||||
|
||||
function buildServerFilters () {
|
||||
@@ -1181,17 +1385,41 @@ function scheduleReload () {
|
||||
}, 180)
|
||||
}
|
||||
|
||||
async function fetchChunk ({ page = 1 } = {}) {
|
||||
const result = await store.fetchRows({
|
||||
limit: PAGE_LIMIT,
|
||||
page,
|
||||
append: false,
|
||||
silent: false,
|
||||
filters: buildServerFilters()
|
||||
})
|
||||
currentPage.value = Number(result?.page) || page
|
||||
return Number(result?.fetched) || 0
|
||||
}
|
||||
async function fetchChunk ({ page = 1 } = {}) {
|
||||
const filters = buildServerFilters()
|
||||
const hasAnyFilter = Object.values(filters).some((v) => Array.isArray(v) && v.length > 0)
|
||||
const hasPrimaryFilter = (filters.urun_ilk_grubu?.length || 0) > 0 || (filters.urun_ana_grubu?.length || 0) > 0
|
||||
if (!hasAnyFilter) {
|
||||
// This endpoint is expensive without filters; require the user to scope down first.
|
||||
store.rows = []
|
||||
store.error = 'Liste cok buyuk. Lutfen en az bir filtre secin (or: Urun Ilk Grubu / Urun Ana Grubu / Urun Kodu).'
|
||||
store.totalCount = 0
|
||||
store.totalPages = 1
|
||||
store.page = 1
|
||||
store.hasMore = false
|
||||
return 0
|
||||
}
|
||||
if (!hasPrimaryFilter) {
|
||||
store.rows = []
|
||||
store.error = 'Performans icin once Urun Ilk Grubu veya Urun Ana Grubu secin.'
|
||||
store.totalCount = 0
|
||||
store.totalPages = 1
|
||||
store.page = 1
|
||||
store.hasMore = false
|
||||
return 0
|
||||
}
|
||||
const result = await store.fetchRows({
|
||||
limit: PAGE_LIMIT,
|
||||
page,
|
||||
append: false,
|
||||
silent: false,
|
||||
filters,
|
||||
sortBy: tablePagination.value.sortBy,
|
||||
descending: tablePagination.value.descending
|
||||
})
|
||||
currentPage.value = Number(result?.page) || page
|
||||
return Number(result?.fetched) || 0
|
||||
}
|
||||
|
||||
async function reloadData ({ page = 1 } = {}) {
|
||||
const startedAt = Date.now()
|
||||
@@ -1211,9 +1439,10 @@ async function reloadData ({ page = 1 } = {}) {
|
||||
row_count: Array.isArray(store.rows) ? store.rows.length : 0,
|
||||
has_error: Boolean(store.error)
|
||||
})
|
||||
selectedMap.value = {}
|
||||
}
|
||||
|
||||
// Full "fetch all pages" is intentionally avoided; keep server-side paging for performance.
|
||||
|
||||
function onPageChange (page) {
|
||||
const p = Number(page) > 0 ? Number(page) : 1
|
||||
if (store.loading) return
|
||||
@@ -1223,7 +1452,16 @@ function onPageChange (page) {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await reloadData({ page: currentPage.value })
|
||||
// Prefetch a couple of common filters so the first open is not empty.
|
||||
void fetchServerFilterOptions('urunIlkGrubu')
|
||||
void fetchServerFilterOptions('urunAnaGrubu')
|
||||
// Do not auto-fetch listing on mount; user must scope by group first.
|
||||
store.rows = []
|
||||
store.error = 'Performans icin once Urun Ilk Grubu veya Urun Ana Grubu secin.'
|
||||
store.totalCount = 0
|
||||
store.totalPages = 1
|
||||
store.page = 1
|
||||
store.hasMore = false
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@@ -1233,11 +1471,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
[columnFilters],
|
||||
() => { scheduleReload() },
|
||||
{ deep: true }
|
||||
)
|
||||
// NOTE: Listing fetch is intentionally manual via "Gruplari Getir" for performance.
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -1256,6 +1490,18 @@ watch(
|
||||
min-width: 170px;
|
||||
}
|
||||
|
||||
.top-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.top-actions-row {
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.table-wrap {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
@@ -1489,6 +1735,10 @@ watch(
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
.cell-danger {
|
||||
background: #c62828 !important;
|
||||
}
|
||||
|
||||
.pricing-table :deep(th.selection-col),
|
||||
.pricing-table :deep(td.selection-col) {
|
||||
background: #fff;
|
||||
|
||||
Reference in New Issue
Block a user