From 39ecba581ffcdf477f14b183fc70a767673a8b9d Mon Sep 17 00:00:00 2001 From: padreug Date: Fri, 26 Sep 2025 00:03:23 +0200 Subject: [PATCH] Implement AND/OR filter logic in useCategoryFilter and update MarketPage UI - Added support for AND/OR filtering modes in the `useCategoryFilter` composable, allowing users to filter products based on all or any selected categories. - Introduced reactive state management for filter mode and updated the filtering logic to accommodate the new functionality. - Enhanced the MarketPage UI with a toggle for selecting filter modes, improving user experience and accessibility. - Updated ARIA attributes for better screen reader support in the filter mode selection. These changes significantly enhance the category filtering capabilities, providing users with more control over product visibility. Refactor CreateProductDialog and MarketPage for improved category handling - Updated CreateProductDialog to utilize `model-value` and `@update:model-value` for the CategoryInput component, enhancing reactivity in category selection. - Enhanced MarketPage filtering logic to support AND/OR modes, allowing for more flexible product filtering based on selected categories. - Improved category normalization and matching logic to ensure accurate filtering results. These changes streamline the category management and filtering processes, providing users with a more intuitive experience when creating and finding products. --- .../market/components/CreateProductDialog.vue | 5 +- .../market/composables/useCategoryFilter.ts | 51 +++++++++---- src/modules/market/views/MarketPage.vue | 74 +++++++++++++++++-- 3 files changed, 106 insertions(+), 24 deletions(-) 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: + + +
+
+