119 lines
4.7 KiB
Vue
119 lines
4.7 KiB
Vue
<!--
|
|||
|
|
Ported from legacy henza_pwa/src/views/search/searchPage.vue. All filtering
|
||
|
|
happens client-side against the one flat product list (no server-side query
|
||
|
|
params exist for any of this — consistent with NewestView's own "single
|
||
|
|
fetch" adaptation from an earlier batch).
|
||
|
|
-->
|
||
|
|
<template>
|
||
|
|
<Spinner :spinning="loadingPage" tip="درحال دریافت اطلاعات">
|
||
|
|
<header class="w-full z-10 top-0 bg-white shadow-md fixed">
|
||
|
|
<div class="p-[16px]">
|
||
|
|
<div class="pb-[10px] flex w-fit justify-center items-center text-primary cursor-pointer" v-if="showFilter" @click="hideFilters()">
|
||
|
|
<Icon icon="vuesax-linear:arrow-right-1" class="ml-1" size="25" />
|
||
|
|
بازگشت
|
||
|
|
</div>
|
||
|
|
<SearchInput @search="search" place-holder="جستجوی دسته بندی، محصولات و ..." />
|
||
|
|
</div>
|
||
|
|
<div class="no-scrollbar flex overflow-x-auto my-[16px] mt-0 pr-[16px]" v-if="!showFilter">
|
||
|
|
<Button :type="('secondary' as any)" class="btn-secondary ml-[8px] whitespace-nowrap" v-if="filterKeys.length > 0" @click="showFilters">{{ `${filterKeys.length} فیلتر` }}</Button>
|
||
|
|
<Button :type="('secondary' as any)" class="ml-[8px] whitespace-nowrap" v-else @click="showFilters">اعمال فیلتر</Button>
|
||
|
|
<div class="flex-1 flex overflow-x-auto no-scrollbar pl-[16px]">
|
||
|
|
<Button :type="('secondary' as any)" :ghost="!!filters.query.cats" class="last:ml-0 ml-[8px] whitespace-nowrap" @click="setCats(undefined)">همه</Button>
|
||
|
|
<Button
|
||
|
|
:type="('secondary' as any)"
|
||
|
|
:ghost="!(filters.query.cats && filters.query.cats[0] === item.id)"
|
||
|
|
class="last:ml-0 ml-[8px] whitespace-nowrap"
|
||
|
|
v-for="item in availableCategories"
|
||
|
|
:key="item.id"
|
||
|
|
@click="setCats(item.id)"
|
||
|
|
>{{ item.title }}</Button
|
||
|
|
>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</header>
|
||
|
|
|
||
|
|
<Filters v-model:filterKeys="filterKeys" v-show="showFilter" v-model="filters.query" :products="allProducts" @change="hideFilters" />
|
||
|
|
|
||
|
|
<ProductList class="px-[16px] pb-[66px]" padding="146px" v-show="!showFilter" @pull="() => {}" :value="filteredProducts" :ciny="availableCollections" :loading="false" :flag="filteredProducts.length === 0" />
|
||
|
|
</Spinner>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup lang="ts">
|
||
|
|
import { computed, onMounted, ref } from 'vue'
|
||
|
|
import { Button } from 'ant-design-vue'
|
||
|
|
import Icon from '../../components/icon/Icon.vue'
|
||
|
|
import Spinner from '../../components/Spinner.vue'
|
||
|
|
import SearchInput from '../../components/search/SearchInput.vue'
|
||
|
|
import Filters from '../../components/search/Filters.vue'
|
||
|
|
import ProductList from '../../components/product/ProductList.vue'
|
||
|
|
import { useFiltersStore } from '../../stores/filtersStore'
|
||
|
|
import { useNavStore } from '../../stores/navStore'
|
||
|
|
import { api, type ProductSummary } from '../../api/client'
|
||
|
|
|
||
|
|
const filters = useFiltersStore()
|
||
|
|
const nav = useNavStore()
|
||
|
|
|
||
|
|
const loadingPage = ref(false)
|
||
|
|
const allProducts = ref<ProductSummary[]>([])
|
||
|
|
const showFilter = ref(false)
|
||
|
|
const filterKeys = ref<string[]>([])
|
||
|
|
|
||
|
|
const availableCategories = computed(() => {
|
||
|
|
const seen = new Map<string, { id: string; title: string }>()
|
||
|
|
for (const p of allProducts.value) {
|
||
|
|
if (p.category) seen.set(p.category.id, p.category)
|
||
|
|
}
|
||
|
|
return [...seen.values()]
|
||
|
|
})
|
||
|
|
|
||
|
|
const availableCollections = computed(() => {
|
||
|
|
const seen = new Map<string, { id: string; title: string }>()
|
||
|
|
for (const p of allProducts.value) {
|
||
|
|
for (const c of p.collections) seen.set(c.collection.id, { id: c.collection.id, title: c.collection.title })
|
||
|
|
}
|
||
|
|
return [...seen.values()]
|
||
|
|
})
|
||
|
|
|
||
|
|
const filteredProducts = computed(() => {
|
||
|
|
let list = allProducts.value
|
||
|
|
const q = filters.query
|
||
|
|
if (q.text) list = list.filter((p) => p.title.includes(q.text!))
|
||
|
|
if (q.cats && q.cats.length > 0) list = list.filter((p) => p.category && q.cats!.includes(p.category.id))
|
||
|
|
if (q.brands.length > 0) list = list.filter((p) => p.brand && q.brands.includes(p.brand.id))
|
||
|
|
if (q.limit_gram.length === 2) {
|
||
|
|
list = list.filter((p) => p.variants.some((v) => parseFloat(v.rashWeightGrams) >= q.limit_gram[0] && parseFloat(v.rashWeightGrams) <= q.limit_gram[1]))
|
||
|
|
}
|
||
|
|
return list
|
||
|
|
})
|
||
|
|
|
||
|
|
async function loadProducts() {
|
||
|
|
loadingPage.value = true
|
||
|
|
try {
|
||
|
|
allProducts.value = await api.listProducts()
|
||
|
|
} finally {
|
||
|
|
loadingPage.value = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function search(value: string | undefined) {
|
||
|
|
filters.query.text = value
|
||
|
|
}
|
||
|
|
function setCats(id: string | undefined) {
|
||
|
|
filters.query.cats = id ? [id] : undefined
|
||
|
|
}
|
||
|
|
function showFilters() {
|
||
|
|
showFilter.value = true
|
||
|
|
nav.setNavVisibility(false)
|
||
|
|
}
|
||
|
|
function hideFilters(event?: { resetFilters: boolean }) {
|
||
|
|
showFilter.value = false
|
||
|
|
nav.setNavVisibility(true)
|
||
|
|
if (event?.resetFilters) {
|
||
|
|
filters.reset()
|
||
|
|
filterKeys.value = []
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
onMounted(loadProducts)
|
||
|
|
</script>
|