Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -77,33 +77,175 @@ function mapRow (raw, index, baseIndex = 0) {
|
||||
}
|
||||
}
|
||||
|
||||
function cloneRows (rows = []) {
|
||||
return rows.map((r) => ({ ...r }))
|
||||
}
|
||||
|
||||
function normalizeFilterList (list) {
|
||||
if (!Array.isArray(list)) return []
|
||||
return list.map((x) => toText(x)).filter(Boolean).sort()
|
||||
}
|
||||
|
||||
function normalizeFilters (filters = {}) {
|
||||
const keys = ['product_code', 'brand_group_selection', 'askili_yan', 'kategori', 'urun_ilk_grubu', 'urun_ana_grubu', 'urun_alt_grubu', 'icerik', 'karisim', 'marka']
|
||||
const out = {}
|
||||
for (const key of keys) out[key] = normalizeFilterList(filters[key])
|
||||
const q = toText(filters.q)
|
||||
if (q) out.q = q
|
||||
return out
|
||||
}
|
||||
|
||||
function makeCacheKey (limit, page, filters) {
|
||||
return JSON.stringify({
|
||||
limit: Number(limit) || 500,
|
||||
page: Number(page) || 1,
|
||||
filters: normalizeFilters(filters)
|
||||
})
|
||||
}
|
||||
|
||||
export const useProductPricingStore = defineStore('product-pricing-store', {
|
||||
state: () => ({
|
||||
rows: [],
|
||||
loading: false,
|
||||
error: '',
|
||||
hasMore: true
|
||||
hasMore: true,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
totalCount: 0,
|
||||
pageCache: {},
|
||||
cacheOrder: [],
|
||||
prefetchInFlight: {}
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async fetchRows (options = {}) {
|
||||
this.loading = true
|
||||
this.error = ''
|
||||
cachePut (key, value) {
|
||||
this.pageCache[key] = value
|
||||
this.cacheOrder = this.cacheOrder.filter((x) => x !== key)
|
||||
this.cacheOrder.push(key)
|
||||
while (this.cacheOrder.length > 24) {
|
||||
const oldest = this.cacheOrder.shift()
|
||||
if (oldest) delete this.pageCache[oldest]
|
||||
}
|
||||
},
|
||||
|
||||
cacheGet (key) {
|
||||
return this.pageCache[key] || null
|
||||
},
|
||||
|
||||
applyPageResult (payload = {}, requestedPage = 1) {
|
||||
const data = Array.isArray(payload?.rows) ? payload.rows : []
|
||||
this.rows = cloneRows(data)
|
||||
this.totalCount = Number.isFinite(payload?.totalCount) ? payload.totalCount : 0
|
||||
this.totalPages = Math.max(1, Number(payload?.totalPages || 1))
|
||||
this.page = Math.max(1, Number(payload?.page || requestedPage))
|
||||
this.hasMore = this.page < this.totalPages
|
||||
},
|
||||
|
||||
async prefetchPage (options = {}) {
|
||||
const limit = Number(options?.limit) > 0 ? Number(options.limit) : 500
|
||||
const afterProductCode = toText(options?.afterProductCode)
|
||||
const page = Number(options?.page) > 0 ? Number(options.page) : 1
|
||||
const filters = normalizeFilters(options?.filters || {})
|
||||
const key = makeCacheKey(limit, page, filters)
|
||||
if (this.pageCache[key]) return
|
||||
if (this.prefetchInFlight[key]) {
|
||||
await this.prefetchInFlight[key]
|
||||
return
|
||||
}
|
||||
const run = async () => {
|
||||
try {
|
||||
const params = { limit, page }
|
||||
for (const k of Object.keys(filters)) {
|
||||
if (k === 'q') {
|
||||
params.q = filters.q
|
||||
continue
|
||||
}
|
||||
if (Array.isArray(filters[k]) && filters[k].length > 0) {
|
||||
params[k] = filters[k].join(',')
|
||||
}
|
||||
}
|
||||
const res = await api.request({
|
||||
method: 'GET',
|
||||
url: '/pricing/products',
|
||||
params,
|
||||
timeout: 180000
|
||||
})
|
||||
const totalCount = Number(res?.headers?.['x-total-count'] || 0)
|
||||
const totalPages = Math.max(1, Number(res?.headers?.['x-total-pages'] || 1))
|
||||
const currentPage = Math.max(1, Number(res?.headers?.['x-page'] || page))
|
||||
const data = Array.isArray(res?.data) ? res.data : []
|
||||
const mapped = data.map((x, i) => mapRow(x, i, 0))
|
||||
this.cachePut(key, {
|
||||
rows: mapped,
|
||||
totalCount: Number.isFinite(totalCount) ? totalCount : 0,
|
||||
totalPages: Number.isFinite(totalPages) ? totalPages : 1,
|
||||
page: currentPage
|
||||
})
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
this.prefetchInFlight[key] = run()
|
||||
try {
|
||||
await this.prefetchInFlight[key]
|
||||
} finally {
|
||||
delete this.prefetchInFlight[key]
|
||||
}
|
||||
},
|
||||
|
||||
async fetchRows (options = {}) {
|
||||
const silent = Boolean(options?.silent)
|
||||
if (!silent) {
|
||||
this.loading = true
|
||||
this.error = ''
|
||||
}
|
||||
const limit = Number(options?.limit) > 0 ? Number(options.limit) : 500
|
||||
const page = Number(options?.page) > 0 ? Number(options.page) : 1
|
||||
const append = Boolean(options?.append)
|
||||
const baseIndex = append ? this.rows.length : 0
|
||||
const filters = normalizeFilters(options?.filters || {})
|
||||
const cacheKey = makeCacheKey(limit, page, filters)
|
||||
const startedAt = Date.now()
|
||||
console.info('[product-pricing][frontend] request:start', {
|
||||
at: new Date(startedAt).toISOString(),
|
||||
timeout_ms: 180000,
|
||||
limit,
|
||||
after_product_code: afterProductCode || null,
|
||||
page,
|
||||
append
|
||||
})
|
||||
try {
|
||||
const params = { limit }
|
||||
if (afterProductCode) params.after_product_code = afterProductCode
|
||||
if (options?.useCache !== false) {
|
||||
const inFlight = this.prefetchInFlight[cacheKey]
|
||||
if (inFlight) {
|
||||
await inFlight
|
||||
}
|
||||
const cached = this.cacheGet(cacheKey)
|
||||
if (cached) {
|
||||
this.applyPageResult(cached, page)
|
||||
console.info('[product-pricing][frontend] request:cache-hit', {
|
||||
page: this.page,
|
||||
total_pages: this.totalPages,
|
||||
row_count: this.rows.length,
|
||||
duration_ms: Date.now() - startedAt
|
||||
})
|
||||
return {
|
||||
traceId: null,
|
||||
fetched: this.rows.length,
|
||||
hasMore: this.hasMore,
|
||||
page: this.page,
|
||||
totalPages: this.totalPages,
|
||||
totalCount: this.totalCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const params = { limit, page }
|
||||
for (const key of Object.keys(filters)) {
|
||||
if (key === 'q') {
|
||||
params.q = filters.q
|
||||
continue
|
||||
}
|
||||
const list = filters[key]
|
||||
if (Array.isArray(list) && list.length > 0) params[key] = list.join(',')
|
||||
}
|
||||
const res = await api.request({
|
||||
method: 'GET',
|
||||
url: '/pricing/products',
|
||||
@@ -111,44 +253,48 @@ export const useProductPricingStore = defineStore('product-pricing-store', {
|
||||
timeout: 180000
|
||||
})
|
||||
const traceId = res?.headers?.['x-trace-id'] || null
|
||||
const hasMoreHeader = String(res?.headers?.['x-has-more'] || '').toLowerCase()
|
||||
const nextCursorHeader = toText(res?.headers?.['x-next-cursor'])
|
||||
const totalCount = Number(res?.headers?.['x-total-count'] || 0)
|
||||
const totalPages = Math.max(1, Number(res?.headers?.['x-total-pages'] || 1))
|
||||
const currentPage = Math.max(1, Number(res?.headers?.['x-page'] || page))
|
||||
const data = Array.isArray(res?.data) ? res.data : []
|
||||
const mapped = data.map((x, i) => mapRow(x, i, baseIndex))
|
||||
const fallbackNextCursor = mapped.length > 0
|
||||
? toText(mapped[mapped.length - 1]?.productCode)
|
||||
: ''
|
||||
const nextCursor = nextCursorHeader || fallbackNextCursor
|
||||
if (append) {
|
||||
const merged = [...this.rows]
|
||||
const seen = new Set(this.rows.map((x) => x?.productCode))
|
||||
for (const row of mapped) {
|
||||
const key = row?.productCode
|
||||
if (key && seen.has(key)) continue
|
||||
merged.push(row)
|
||||
if (key) seen.add(key)
|
||||
}
|
||||
this.rows = merged
|
||||
} else {
|
||||
this.rows = mapped
|
||||
const payload = {
|
||||
rows: mapped,
|
||||
totalCount: Number.isFinite(totalCount) ? totalCount : 0,
|
||||
totalPages: Number.isFinite(totalPages) ? totalPages : 1,
|
||||
page: Number.isFinite(currentPage) ? currentPage : page
|
||||
}
|
||||
this.cachePut(cacheKey, payload)
|
||||
this.applyPageResult(payload, page)
|
||||
|
||||
// Background prefetch for next page to reduce perceived wait on page change.
|
||||
if (this.page < this.totalPages) {
|
||||
void this.prefetchPage({
|
||||
limit,
|
||||
page: this.page + 1,
|
||||
filters
|
||||
})
|
||||
}
|
||||
this.hasMore = hasMoreHeader ? hasMoreHeader === 'true' : mapped.length === limit
|
||||
console.info('[product-pricing][frontend] request:success', {
|
||||
trace_id: traceId,
|
||||
duration_ms: Date.now() - startedAt,
|
||||
row_count: this.rows.length,
|
||||
fetched_count: mapped.length,
|
||||
has_more: this.hasMore,
|
||||
next_cursor: nextCursor || null
|
||||
page: this.page,
|
||||
total_pages: this.totalPages,
|
||||
total_count: this.totalCount
|
||||
})
|
||||
return {
|
||||
traceId,
|
||||
fetched: mapped.length,
|
||||
hasMore: this.hasMore,
|
||||
nextCursor
|
||||
page: this.page,
|
||||
totalPages: this.totalPages,
|
||||
totalCount: this.totalCount
|
||||
}
|
||||
} catch (err) {
|
||||
if (!append) this.rows = []
|
||||
this.rows = []
|
||||
this.hasMore = false
|
||||
const msg = err?.response?.data || err?.message || 'Urun fiyatlandirma listesi alinamadi'
|
||||
this.error = toText(msg)
|
||||
@@ -161,7 +307,7 @@ export const useProductPricingStore = defineStore('product-pricing-store', {
|
||||
})
|
||||
throw err
|
||||
} finally {
|
||||
this.loading = false
|
||||
if (!silent) this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user