Add MarketFuzzySearch component for enhanced product searching
- Introduced a new MarketFuzzySearch component to provide an advanced search interface with keyboard shortcuts, search suggestions, and recent searches functionality. - Updated MarketPage and StallView to integrate the new fuzzy search component, replacing the previous search input implementations. - Enhanced search capabilities with configurable options for better user experience and product discovery. These changes improve the search functionality across the market module, making it easier for users to find products efficiently.
This commit is contained in:
parent
86d3133978
commit
8aa575ffb1
3 changed files with 470 additions and 34 deletions
|
|
@ -40,12 +40,13 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search Bar -->
|
||||
<!-- Enhanced Fuzzy Search Bar -->
|
||||
<div class="flex-1 max-w-md ml-8">
|
||||
<Input
|
||||
v-model="marketStore.searchText"
|
||||
type="text"
|
||||
placeholder="Search products..."
|
||||
<MarketFuzzySearch
|
||||
:data="marketStore.products"
|
||||
:options="searchOptions"
|
||||
@results="handleSearchResults"
|
||||
@filter-category="handleCategoryFilter"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -68,15 +69,15 @@
|
|||
</div>
|
||||
|
||||
<!-- No Products State -->
|
||||
<div v-if="isMarketReady && marketStore.filteredProducts.length === 0 && !(marketStore.isLoading ?? false)" class="text-center py-12">
|
||||
<div v-if="isMarketReady && productsToDisplay.length === 0 && !(marketStore.isLoading ?? false)" class="text-center py-12">
|
||||
<h3 class="text-xl font-semibold text-gray-600 mb-2">No products found</h3>
|
||||
<p class="text-gray-500">Try adjusting your search or filters</p>
|
||||
</div>
|
||||
|
||||
<!-- Product Grid -->
|
||||
<div v-if="isMarketReady && marketStore.filteredProducts.length > 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
<div v-if="isMarketReady && productsToDisplay.length > 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
<ProductCard
|
||||
v-for="product in marketStore.filteredProducts"
|
||||
v-for="product in productsToDisplay"
|
||||
:key="product.id"
|
||||
:product="product"
|
||||
@add-to-cart="addToCart"
|
||||
|
|
@ -97,18 +98,20 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, computed } from 'vue'
|
||||
import { onMounted, onUnmounted, computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useMarketStore } from '@/modules/market/stores/market'
|
||||
import { useMarket } from '../composables/useMarket'
|
||||
import { useMarketPreloader } from '../composables/useMarketPreloader'
|
||||
import { config } from '@/lib/config'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
|
||||
import { ShoppingCart } from 'lucide-vue-next'
|
||||
import MarketFuzzySearch from '../components/MarketFuzzySearch.vue'
|
||||
import ProductCard from '../components/ProductCard.vue'
|
||||
import type { Product } from '../types/market'
|
||||
import type { FuzzySearchOptions } from '@/composables/useFuzzySearch'
|
||||
|
||||
const router = useRouter()
|
||||
const marketStore = useMarketStore()
|
||||
|
|
@ -117,6 +120,29 @@ const marketPreloader = useMarketPreloader()
|
|||
|
||||
let unsubscribe: (() => void) | null = null
|
||||
|
||||
// Fuzzy search state
|
||||
const searchResults = ref<Product[]>([])
|
||||
|
||||
// Fuzzy search configuration for products and stalls
|
||||
const searchOptions: FuzzySearchOptions<Product> = {
|
||||
fuseOptions: {
|
||||
keys: [
|
||||
{ name: 'name', weight: 0.7 }, // Product name has highest weight
|
||||
{ name: 'stallName', weight: 0.5 }, // Stall name is important for discovery
|
||||
{ name: 'description', weight: 0.3 }, // Description provides context
|
||||
{ name: 'categories', weight: 0.4 } // Categories help with discovery
|
||||
],
|
||||
threshold: 0.6, // More tolerant of typos (0.0 = perfect match, 1.0 = match anything)
|
||||
ignoreLocation: true, // Don't care about where in the string the match is
|
||||
findAllMatches: true, // Find all matches, not just the first
|
||||
minMatchCharLength: 2, // Minimum length of a matched character sequence
|
||||
shouldSort: true // Sort results by score
|
||||
},
|
||||
resultLimit: 50, // Limit results for performance
|
||||
minSearchLength: 2, // Start searching after 2 characters
|
||||
matchAllWhenSearchEmpty: true
|
||||
}
|
||||
|
||||
// Check if we need to load market data
|
||||
const needsToLoadMarket = computed(() => {
|
||||
return !marketPreloader.isPreloaded.value &&
|
||||
|
|
@ -127,12 +153,34 @@ const needsToLoadMarket = computed(() => {
|
|||
// Check if market data is ready (either preloaded or loaded)
|
||||
const isMarketReady = computed(() => {
|
||||
const isLoading = marketStore.isLoading ?? false
|
||||
const ready = marketPreloader.isPreloaded.value ||
|
||||
const ready = marketPreloader.isPreloaded.value ||
|
||||
(marketStore.products.length > 0 && !isLoading)
|
||||
|
||||
|
||||
return ready
|
||||
})
|
||||
|
||||
// Products to display (either search results or filtered products)
|
||||
const productsToDisplay = computed(() => {
|
||||
// If we have search results (meaning user is searching), use those
|
||||
if (searchResults.value.length > 0 || searchResults.value.length === 0) {
|
||||
// Still need to apply category filters to search results
|
||||
let products = searchResults.value
|
||||
|
||||
// Apply category filters if any are selected
|
||||
const selectedCategories = marketStore.filterData.categories
|
||||
if (selectedCategories.length > 0) {
|
||||
products = products.filter(product =>
|
||||
product.categories?.some(cat => selectedCategories.includes(cat))
|
||||
)
|
||||
}
|
||||
|
||||
return products
|
||||
}
|
||||
|
||||
// Otherwise, use the store's filtered products
|
||||
return marketStore.filteredProducts
|
||||
})
|
||||
|
||||
const loadMarket = async () => {
|
||||
try {
|
||||
const naddr = config.market.defaultNaddr
|
||||
|
|
@ -174,6 +222,16 @@ const viewCart = () => {
|
|||
router.push('/cart')
|
||||
}
|
||||
|
||||
// Handle fuzzy search results
|
||||
const handleSearchResults = (results: Product[]) => {
|
||||
searchResults.value = results
|
||||
}
|
||||
|
||||
// Handle category filtering from fuzzy search
|
||||
const handleCategoryFilter = (category: string) => {
|
||||
marketStore.toggleCategoryFilter(category)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// Only load market if it hasn't been preloaded
|
||||
if (needsToLoadMarket.value) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue