fix(products): treat SSE list as full active catalog

Stop keeping deactivated products when the stream shrinks, update prices in place to avoid grid flicker, and only keep admin-active items.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-05 10:12:16 +03:30
co-authored by Cursor
parent 3e15cb1481
commit cb9762b0d6
@@ -117,48 +117,17 @@ function mergeLiveProductList(incoming: Product[], current?: Product[]): Product
})
}
/** فقط محصول فعال ادمین (باز) — نه show لحظه‌ای ساعت/مظنه */
/** فقط محصول فعال ادمین (باز) — فیلد خالی/undefined را فعال نگیر */
function isActiveProduct(product: Product): boolean {
const active = product.active as unknown
return active !== false && active !== 0 && active !== '0'
}
/** استریم گاهی لیست ناقص می‌فرستد — عضوهای قبلی را دور نریز. */
function mergeSocketProductMembership(incoming: Product[], current?: Product[]): Product[] {
if (!current?.length) return incoming
const incomingById = new Map(incoming.map((item) => [item.id, item]))
const currentIds = new Set(current.map((item) => item.id))
const hasAdded = incoming.some((item) => !currentIds.has(item.id))
const missingFromIncoming = current.some((item) => !incomingById.has(item.id))
if (missingFromIncoming && !hasAdded && incoming.length < current.length) {
return current.map((item) => {
const updated = incomingById.get(item.id)
return updated ? { ...item, ...updated } : item
})
}
const merged = incoming.map((item) => {
const prev = current.find((row) => row.id === item.id)
return prev ? { ...prev, ...item } : item
})
if (missingFromIncoming) {
for (const item of current) {
if (!incomingById.has(item.id)) {
merged.push(item)
}
}
}
return merged
return active === true || active === 1 || active === '1'
}
function applyProductsList(products: Product[]) {
const visible = products.filter(isActiveProduct)
const sorted = sortProductsByInventory(visible)
productsFirstItems.value = sorted
getProductsCatalogRef().value = sorted
pruneEmptyTypeItems()
if (
@@ -172,6 +141,46 @@ function applyProductsList(products: Product[]) {
productsPageLoading.value = false
}
/**
* SSE همیشه لیست کامل محصولات فعال را می‌فرستد.
* عضو جدید/حذف‌شده را از همان لیست بگیر؛ فقط عکس را از نسخهٔ قبلی نگه دار.
* آپدیت قیمت روی همان آبجکت‌ها تا گرید نپرد.
*/
function applySocketProductList(incoming: Product[], current?: Product[]) {
const next = sortProductsByInventory(
mergeLiveProductList(incoming, current).filter(isActiveProduct),
)
if (!current?.length) {
applyProductsList(next)
return
}
const nextById = new Map(next.map((item) => [item.id, item]))
const currentIds = new Set(current.map((item) => item.id))
const nextIds = new Set(next.map((item) => item.id))
const membershipChanged =
next.length !== current.length ||
next.some((item) => !currentIds.has(item.id)) ||
current.some((item) => !nextIds.has(item.id))
if (membershipChanged) {
applyProductsList(next)
return
}
current.forEach((item) => {
const updated = nextById.get(item.id)
if (updated) {
Object.assign(item, updated)
}
})
getProductsCatalogRef().value = current
changeProductsTab()
productsPageLoading.value = false
}
export async function refreshProductsPageData() {
try {
const fresh = await getProductsCatalog(true)
@@ -189,13 +198,10 @@ export function applyProductsListFromSocket(products: Product[]) {
}
const base =
getProductsCatalogRef().value.length
? getProductsCatalogRef().value
: productsFirstItems.value
const withImages = mergeLiveProductList(products, base)
const merged = mergeSocketProductMembership(withImages, base)
getProductsCatalogRef().value = merged
applyProductsList(merged)
productsFirstItems.value?.length
? productsFirstItems.value
: getProductsCatalogRef().value
applySocketProductList(products, base)
}
function startProductListPolling() {