From e8e0675d7d953058438084c237f1c10501c69365 Mon Sep 17 00:00:00 2001 From: Alexandr SaVBaD Waltz Date: Sun, 1 Mar 2026 22:00:34 +0300 Subject: [PATCH] feat(storefront): enhance store filter and price handling for improved UX Enhanced store filters with a refined price range slider, accommodating category-specific min/max prices and dynamic updates. Optimized reactivity for product filtering by consolidating watchers into a unified approach. Adjusted UI elements for consistent spacing and modern icon usage in filters. - Added `minMaxPrices` and debounce logic to improve price filtering performance. - Updated filter UI with collapsible headers and better styling for usability. - Refactored multiple watchers into a single handler for better efficiency. - Introduced global constants for currency symbol usage. Breaking Changes: Components relying on price filters must adapt to new props and event names (`filterMinPrice`, `filterMaxPrice`). Styles may require alignment with refined SCSS rules for filters. --- storefront/app/assets/styles/main.scss | 1 + storefront/app/assets/styles/ui/slider.scss | 17 ++ .../app/components/base/header/index.vue | 1 + storefront/app/components/store/filter.vue | 197 +++++++++++------- storefront/app/components/store/index.vue | 56 ++--- storefront/app/components/store/top.vue | 1 - .../categories/useCategoryBySlug.ts | 2 + storefront/app/composables/store/useStore.ts | 2 +- storefront/app/pages/product/[slug].vue | 47 +++-- storefront/i18n/locales/en-gb.json | 3 +- storefront/i18n/locales/ru-ru.json | 3 +- 11 files changed, 207 insertions(+), 123 deletions(-) create mode 100644 storefront/app/assets/styles/ui/slider.scss diff --git a/storefront/app/assets/styles/main.scss b/storefront/app/assets/styles/main.scss index 91455306..c5e9bc76 100644 --- a/storefront/app/assets/styles/main.scss +++ b/storefront/app/assets/styles/main.scss @@ -9,5 +9,6 @@ @use "ui/notification"; @use "ui/rating"; @use "ui/select"; +@use "ui/slider"; diff --git a/storefront/app/assets/styles/ui/slider.scss b/storefront/app/assets/styles/ui/slider.scss new file mode 100644 index 00000000..66b89b9c --- /dev/null +++ b/storefront/app/assets/styles/ui/slider.scss @@ -0,0 +1,17 @@ +.el-slider { + width: 90% !important; + margin-inline: auto; + height: 30px !important; +} + +.el-slider__bar, .el-slider__runway { + height: 4px !important; +} +.el-slider__button-wrapper { + width: 34px !important; + height: 34px !important; +} +.el-slider__button { + width: 16px !important; + height: 16px !important; +} \ No newline at end of file diff --git a/storefront/app/components/base/header/index.vue b/storefront/app/components/base/header/index.vue index ecf2c2cc..d94bf31f 100644 --- a/storefront/app/components/base/header/index.vue +++ b/storefront/app/components/base/header/index.vue @@ -55,6 +55,7 @@
{{ t('store.filters.title') }}

{{ t('buttons.clearAll') }}

- - - -
-
- - - + + + +
+
+ + + +
+
- -
- - - -
    -
  • - + + +
      +
    • - {{ value }} - -
    • -
    -
    - - + + {{ value }} + +
  • +
+
+ +
@@ -78,6 +91,7 @@ import type {IStoreFilters} from '@types'; import {useFilters} from '@composables/store'; import {useRouteQuery} from '@vueuse/router'; +import {CURRENCY} from "~/constants"; const appStore = useAppStore(); const { t } = useI18n(); @@ -133,9 +147,6 @@ const initializeInputs = () => { const max = props.initialMaxPrice ?? categoryMax.value; priceRange.value = [min, max]; - - const { min: floatMin, max: floatMax } = getFloatMinMax(); - floatRange.value = [floatMin, floatMax]; }; const handlePriceInput = useDebounceFn((value: string | number, type: 'min' | 'max') => { @@ -179,10 +190,6 @@ watch(priceRange, () => { debouncedPriceUpdate(); }, { deep: true }); -watch(floatRange, () => { - debouncedFilterApply(); -}, { deep: true }); - watch(selectedMap, () => { debouncedFilterApply(); }, { deep: true }); @@ -230,18 +237,18 @@ watch( { immediate: true } ); -const formatPriceTooltip = (value: number) => `€${value.toFixed(2)}`; +const formatPriceTooltip = (value: number) => `${CURRENCY}${value.toFixed(2)}`; \ No newline at end of file diff --git a/storefront/app/components/store/index.vue b/storefront/app/components/store/index.vue index 6332c4b9..0f6cf0f0 100644 --- a/storefront/app/components/store/index.vue +++ b/storefront/app/components/store/index.vue @@ -3,9 +3,13 @@
categorySeoMeta.value || brandSeoMeta.value); @@ -147,6 +151,13 @@ async function onFiltersChange(newFilters: Record) { await getProducts(); } +const updateMinPrice = useDebounceFn((filteredPrice) => { + minPrice.value = filteredPrice; +}, 500); +const updateMaxPrice = useDebounceFn((filteredPrice) => { + maxPrice.value = filteredPrice; +}, 500); + useIntersectionObserver( observer, async ([{ isIntersecting }]) => { @@ -157,30 +168,19 @@ useIntersectionObserver( }, ); -watch(orderBy, async (newVal) => { - variables.orderBy = newVal || ''; - variables.productAfter = ''; - products.value = []; - await getProducts(); -}); -watch(attributes, async (newVal) => { - variables.attributes = newVal; - variables.productAfter = ''; - products.value = []; - await getProducts(); -}); -watch(minPrice, async (newVal) => { - variables.minPrice = newVal || 0; - variables.productAfter = ''; - products.value = []; - await getProducts(); -}); -watch(maxPrice, async (newVal) => { - variables.maxPrice = newVal || 500000; - variables.productAfter = ''; - products.value = []; - await getProducts(); -}); +watch( + [orderBy, attributes, minPrice, maxPrice], + async ([newOrder, newAttr, newMin, newMax]) => { + variables.orderBy = newOrder || ''; + variables.attributes = newAttr; + variables.minPrice = Number(newMin) || 0; + variables.maxPrice = Number(newMax) || 500000; + variables.productAfter = ''; + products.value = []; + + await getProducts(); + } +);