fix(products): hide empty categories and stabilize catalog list

Only show active products, keep partial SSE lists from dropping items, and hide category tabs with no products.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-05 09:55:32 +03:30
co-authored by Cursor
parent 57780ae99c
commit 3e15cb1481
3 changed files with 93 additions and 15 deletions
@@ -35,24 +35,42 @@ function sortProductsByInventory(products: Product[]) {
}
export function changeProductsTab() {
const all = productsFirstItems.value
if (productsActiveKey.value !== 'همه') {
productsItems.value = productsFirstItems.value?.filter(
(val) => val.group_type === productsActiveKey.value,
)
const filtered = all?.filter((val) => val.group_type === productsActiveKey.value)
if (!filtered?.length) {
productsActiveKey.value = 'همه'
productsItems.value = all
return
}
productsItems.value = filtered
} else {
productsItems.value = productsFirstItems.value
productsItems.value = all
}
}
async function loadCategoryTabs() {
try {
productsTypeItems.value = await loadProductCategoryTabNames()
pruneEmptyTypeItems()
} catch (error) {
console.error('Product category tabs error:', error)
productsTypeItems.value = []
}
}
/** دسته‌ای که هیچ محصول فعالی ندارد از تب‌ها حذف شود */
function pruneEmptyTypeItems() {
const products = productsFirstItems.value
if (!products?.length) {
return
}
const present = new Set(
products.map((p) => p.group_type).filter((key): key is string => !!key),
)
productsTypeItems.value = productsTypeItems.value.filter((key) => present.has(key))
}
function productFileId(product: Product): number {
return Number(product.file_id ?? (product.file as { id?: number } | undefined)?.id ?? 0)
}
@@ -99,9 +117,57 @@ function mergeLiveProductList(incoming: Product[], current?: Product[]): Product
})
}
/** فقط محصول فعال ادمین (باز) — نه show لحظه‌ای ساعت/مظنه */
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
}
function applyProductsList(products: Product[]) {
const sorted = sortProductsByInventory(products)
const visible = products.filter(isActiveProduct)
const sorted = sortProductsByInventory(visible)
productsFirstItems.value = sorted
pruneEmptyTypeItems()
if (
productsActiveKey.value !== 'همه' &&
!sorted.some((p) => p.group_type === productsActiveKey.value)
) {
productsActiveKey.value = 'همه'
}
changeProductsTab()
productsPageLoading.value = false
}
@@ -122,10 +188,12 @@ export function applyProductsListFromSocket(products: Product[]) {
return
}
const merged = mergeLiveProductList(
products,
getProductsCatalogRef().value.length ? getProductsCatalogRef().value : productsFirstItems.value,
)
const base =
getProductsCatalogRef().value.length
? getProductsCatalogRef().value
: productsFirstItems.value
const withImages = mergeLiveProductList(products, base)
const merged = mergeSocketProductMembership(withImages, base)
getProductsCatalogRef().value = merged
applyProductsList(merged)
}
+8 -3
View File
@@ -19,7 +19,7 @@
>
<ARadioButton class="cats" value="همه">همه</ARadioButton>
<ARadioButton
v-for="item in productsTypeItems"
v-for="item in visibleTypeItems"
:key="item"
class="cats"
:value="item"
@@ -67,7 +67,7 @@
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
import router from '@/router'
import ProductItem from '@/views/base/products/components/ProductItem.vue'
@@ -82,7 +82,7 @@ import MobileProductsView from '@/views/base/products/mobile/MobileProductsView.
import { isProductVisible } from '@/utils/productPrice'
import {
productsActiveKey,
productsTypeItems,
productsFirstItems,
productsItems,
productsPageLoading,
changeProductsTab,
@@ -96,6 +96,11 @@ const selectedProduct = ref<Product>()
const viewVertical = ref(true)
const isDesktop = useDesktopBreakpoint()
const visibleTypeItems = computed(() => {
const all = productsFirstItems.value ?? []
return productsTypeItems.value.filter((key) => all.some((p) => p.group_type === key))
})
function clickProduct(item: Product) {
if (isProductVisible(item)) {
router.push({ name: 'productDetail', params: { id: item.id } })
@@ -13,7 +13,7 @@
>
<ARadioButton class="cats" value="همه">همه</ARadioButton>
<ARadioButton
v-for="item in productsTypeItems"
v-for="item in visibleTypeItems"
:key="item"
class="cats"
:value="item"
@@ -62,7 +62,7 @@
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { computed, ref } from 'vue'
import type { Product } from '@/models/products'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import ProductItem from '@/views/base/products/components/ProductItem.vue'
@@ -70,7 +70,7 @@ import ProductItemVertical from '@/views/base/products/components/ProductItemVer
import img from '@/assets/img/user_ly1.svg'
import {
productsActiveKey,
productsTypeItems,
productsFirstItems,
productsItems,
productsPageLoading,
changeProductsTab,
@@ -81,6 +81,11 @@ defineEmits<{
}>()
const viewVertical = ref(true)
const visibleTypeItems = computed(() => {
const all = productsFirstItems.value ?? []
return productsTypeItems.value.filter((key) => all.some((p) => p.group_type === key))
})
</script>
<style scoped>