diff --git a/src/modules/market/components/CreateProductDialog.vue b/src/modules/market/components/CreateProductDialog.vue index 7e192d8..636cff7 100644 --- a/src/modules/market/components/CreateProductDialog.vue +++ b/src/modules/market/components/CreateProductDialog.vue @@ -105,13 +105,14 @@ - + Categories Add categories to help customers find your product >(new Set()) + // Filter mode state (reactive) + const filterMode = ref<'any' | 'all'>(options.mode || 'any') + // Computed property for all available categories with counts const allCategories = computed(() => { const categoryMap = new Map() @@ -49,7 +53,7 @@ export function useCategoryFilter( .sort((a, b) => b.count - a.count) // Sort by popularity }) - // Optimized product filtering with early returns and efficient lookups + // Optimized product filtering with AND/OR logic const filteredProducts = computed(() => { const selectedSet = selectedCategories.value @@ -58,26 +62,34 @@ export function useCategoryFilter( return products.value } - // Convert selected categories to array for faster iteration in some cases - const selectedArray = Array.from(selectedSet) - return products.value.filter(product => { // Handle empty categories if (!product.categories?.length) { return options.includeEmpty || false } - // Check if product has any selected category (optimized) - for (const cat of product.categories) { - if (cat && cat.trim()) { - const normalizedCategory = options.caseSensitive ? cat : cat.toLowerCase() - if (selectedSet.has(normalizedCategory)) { - return true // Early return on first match - } - } + // Normalize product categories + const productCategories = product.categories + .filter(cat => cat && cat.trim()) + .map(cat => options.caseSensitive ? cat : cat.toLowerCase()) + + if (productCategories.length === 0) { + return options.includeEmpty || false } - return false + // Count matches between product categories and selected categories + const matchingCategories = productCategories.filter(cat => + selectedSet.has(cat) + ) + + // Apply AND/OR logic + if (filterMode.value === 'all') { + // AND logic: Product must have ALL selected categories + return matchingCategories.length === selectedSet.size + } else { + // OR logic: Product must have ANY selected category + return matchingCategories.length > 0 + } }) }) @@ -139,6 +151,14 @@ export function useCategoryFilter( return selectedCategories.value.has(normalizedCategory) } + const setFilterMode = (mode: 'any' | 'all') => { + filterMode.value = mode + } + + const toggleFilterMode = () => { + filterMode.value = filterMode.value === 'any' ? 'all' : 'any' + } + // Category statistics const categoryStats = computed(() => ({ totalCategories: allCategories.value.length, @@ -150,6 +170,7 @@ export function useCategoryFilter( return { // State (readonly to prevent external mutation) selectedCategories: readonly(selectedCategories), + filterMode: readonly(filterMode), allCategories, filteredProducts, selectedCount, @@ -163,7 +184,9 @@ export function useCategoryFilter( removeCategory, clearAllCategories, selectMultipleCategories, - isSelected + isSelected, + setFilterMode, + toggleFilterMode } } diff --git a/src/modules/market/views/MarketPage.vue b/src/modules/market/views/MarketPage.vue index 868cf5d..1c972cc 100644 --- a/src/modules/market/views/MarketPage.vue +++ b/src/modules/market/views/MarketPage.vue @@ -61,9 +61,42 @@ aria-labelledby="category-filters-heading" >
-

- Browse by Category -

+
+

+ Browse by Category +

+ + +
+ Match: + + +
+
+