feat: initial commit
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
<!--
|
||||
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>
|
||||
Reference in New Issue
Block a user