feat(filters): add filters for the main page
This commit is contained in:
108
spa/src/views/Filters.vue
Normal file
108
spa/src/views/Filters.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div ref="goodsRef" class="pb-10">
|
||||
<div class="flex flex-col">
|
||||
<header class="text-center shrink-0 p-3 font-bold text-xl">
|
||||
Фильтры
|
||||
</header>
|
||||
|
||||
<main class="mt-5 px-5 bg-base-200">
|
||||
<div
|
||||
v-if="filtersStore.draft?.rules && Object.keys(filtersStore.draft.rules).length > 0"
|
||||
v-for="(filter, filterId) in filtersStore.draft.rules"
|
||||
>
|
||||
<component
|
||||
v-if="componentMap[filterId]"
|
||||
:is="componentMap[filterId]"
|
||||
:filter="filter"
|
||||
/>
|
||||
|
||||
<p v-else>Not supported: {{ filterId }}</p>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
Нет фильтров
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {nextTick, onMounted, onUnmounted} from "vue";
|
||||
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
|
||||
import ProductPrice from "@/components/ProductFilters/Components/ProductPrice.vue";
|
||||
import ForMainPage from "@/components/ProductFilters/Components/ForMainPage.vue";
|
||||
import {useRouter} from "vue-router";
|
||||
import ProductCategory from "@/components/ProductFilters/Components/ProductCategory/ProductCategory.vue";
|
||||
|
||||
defineOptions({
|
||||
name: 'Filters'
|
||||
});
|
||||
|
||||
const componentMap = {
|
||||
RULE_PRODUCT_PRICE: ProductPrice,
|
||||
RULE_PRODUCT_FOR_MAIN_PAGE: ForMainPage,
|
||||
RULE_PRODUCT_CATEGORY: ProductCategory,
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
const emit = defineEmits(['close', 'apply']);
|
||||
|
||||
const filtersStore = useProductFiltersStore();
|
||||
|
||||
const mainButton = window.Telegram.WebApp.MainButton;
|
||||
const secondaryButton = window.Telegram.WebApp.SecondaryButton;
|
||||
const haptic = window.Telegram.WebApp.HapticFeedback;
|
||||
|
||||
const applyFilters = async () => {
|
||||
filtersStore.applied = JSON.parse(JSON.stringify(filtersStore.draft));
|
||||
console.debug('Filters: apply filters. Hash for router: ', filtersStore.paramsHashForRouter);
|
||||
haptic.impactOccurred('soft');
|
||||
await nextTick();
|
||||
router.back();
|
||||
}
|
||||
|
||||
const resetFilters = async () => {
|
||||
filtersStore.applied = await filtersStore.fetchFiltersForMainPage();
|
||||
console.debug('Filters: reset filters. Hash for router: ', filtersStore.paramsHashForRouter);
|
||||
haptic.notificationOccurred('success');
|
||||
await nextTick();
|
||||
window.scrollTo(0, 0);
|
||||
router.back();
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
console.debug('Filters: OnMounted');
|
||||
mainButton.setParams({
|
||||
text: 'Применить',
|
||||
is_active: true,
|
||||
is_visible: true,
|
||||
});
|
||||
mainButton.show();
|
||||
mainButton.onClick(applyFilters);
|
||||
|
||||
secondaryButton.setParams({
|
||||
text: 'Сбросить фильтры',
|
||||
is_active: true,
|
||||
is_visible: true,
|
||||
position: 'top',
|
||||
});
|
||||
secondaryButton.show();
|
||||
secondaryButton.onClick(resetFilters);
|
||||
|
||||
if (filtersStore.applied?.rules) {
|
||||
console.debug('Filters: Found applied filters.');
|
||||
filtersStore.draft = JSON.parse(JSON.stringify(filtersStore.applied));
|
||||
} else {
|
||||
console.debug('No filters. Load filters from server');
|
||||
filtersStore.draft = await filtersStore.fetchFiltersForMainPage();
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
mainButton.hide();
|
||||
secondaryButton.hide();
|
||||
mainButton.offClick(applyFilters);
|
||||
secondaryButton.offClick(resetFilters);
|
||||
});
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div ref="goodsRef">
|
||||
<div ref="goodsRef" class="pb-10">
|
||||
<CategoriesInline/>
|
||||
|
||||
<div class="px-5 fixed z-50 w-full opacity-90" style="bottom: var(--tg-safe-area-inset-bottom);">
|
||||
@@ -11,13 +11,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ProductsList/>
|
||||
<Filters
|
||||
v-if="isFiltersShow"
|
||||
:filters="productsStore.filters"
|
||||
@apply="applyFilters"
|
||||
@reset="resetFilters"
|
||||
@close="closeFilters"
|
||||
<ProductsList
|
||||
:products="products"
|
||||
:hasMore="hasMore"
|
||||
:isLoading="isLoading"
|
||||
:isLoadingMore="isLoadingMore"
|
||||
@loadMore="onLoadMore"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -26,59 +25,78 @@
|
||||
import ProductsList from "@/components/ProductsList.vue";
|
||||
import CategoriesInline from "../components/CategoriesInline.vue";
|
||||
import SearchInput from "@/components/SearchInput.vue";
|
||||
import Filters from "@/components/ProductFilters/Filters.vue";
|
||||
import {onMounted, onUnmounted, ref} from "vue";
|
||||
import {useProductsStore} from "@/stores/ProductsStore.js";
|
||||
import {onActivated, onMounted, ref, toRaw} from "vue";
|
||||
import IconFunnel from "@/components/Icons/IconFunnel.vue";
|
||||
import {FILTERS_MAIN_PAGE_DEFAULT} from "@/components/ProductFilters/filters.js";
|
||||
import {useRoute} from "vue-router";
|
||||
import {useRouter} from "vue-router";
|
||||
import ftch from "@/utils/ftch.js";
|
||||
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
|
||||
|
||||
const route = useRoute();
|
||||
const productsStore = useProductsStore();
|
||||
defineOptions({
|
||||
name: 'Home'
|
||||
});
|
||||
|
||||
const isFiltersShow = ref(false);
|
||||
const backButton = window.Telegram.WebApp.BackButton;
|
||||
const router = useRouter();
|
||||
const filtersStore = useProductFiltersStore();
|
||||
const haptic = window.Telegram.WebApp.HapticFeedback;
|
||||
|
||||
const products = ref([]);
|
||||
const hasMore = ref(false);
|
||||
const isLoading = ref(false);
|
||||
const isLoadingMore = ref(false);
|
||||
const page = ref(1);
|
||||
|
||||
function showFilters() {
|
||||
window.Telegram.WebApp.HapticFeedback.impactOccurred('soft');
|
||||
isFiltersShow.value = true;
|
||||
backButton.show();
|
||||
haptic.impactOccurred('soft');
|
||||
router.push({name: 'filters'});
|
||||
}
|
||||
|
||||
function closeFilters() {
|
||||
window.Telegram.WebApp.HapticFeedback.impactOccurred('rigid');
|
||||
isFiltersShow.value = false;
|
||||
}
|
||||
|
||||
async function applyFilters(newFilters) {
|
||||
closeFilters();
|
||||
console.log("Load products with new filters: ", newFilters);
|
||||
productsStore.page = 1;
|
||||
await productsStore.loadProducts(newFilters);
|
||||
}
|
||||
|
||||
async function resetFilters() {
|
||||
closeFilters();
|
||||
productsStore.reset();
|
||||
await productsStore.loadProducts(FILTERS_MAIN_PAGE_DEFAULT);
|
||||
}
|
||||
|
||||
function handleClickOutside(e) {
|
||||
if (!e.target.closest('input, textarea')) {
|
||||
document.activeElement?.blur()
|
||||
async function fetchProducts() {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
console.debug('Home: Load products for Main Page.');
|
||||
console.debug('Home: Fetch products from server using filters: ', toRaw(filtersStore.applied));
|
||||
const response = await ftch('products', null, toRaw({
|
||||
page: page.value,
|
||||
filters: filtersStore.applied,
|
||||
}));
|
||||
products.value = response.data;
|
||||
hasMore.value = response.meta.hasMore;
|
||||
console.debug('Home: Products for main page loaded.');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => document.removeEventListener('click', handleClickOutside));
|
||||
async function onLoadMore() {
|
||||
try {
|
||||
console.debug('Home: onLoadMore');
|
||||
if (isLoading.value === true || isLoadingMore.value === true || hasMore.value === false) return;
|
||||
isLoadingMore.value = true;
|
||||
page.value++;
|
||||
console.debug('Home: Load more for page ', page.value, ' using filters: ', toRaw(filtersStore.applied));
|
||||
const response = await ftch('products', null, toRaw({
|
||||
page: page.value,
|
||||
filters: filtersStore.applied,
|
||||
}));
|
||||
products.value.push(...response.data);
|
||||
hasMore.value = response.meta.hasMore;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
isLoadingMore.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
document.addEventListener('click', handleClickOutside);
|
||||
if (productsStore.filtersFullUrl !== route.fullPath) {
|
||||
productsStore.filtersFullUrl = route.fullPath;
|
||||
await productsStore.loadProducts(FILTERS_MAIN_PAGE_DEFAULT);
|
||||
} else {
|
||||
await productsStore.loadProducts(productsStore.filters ?? FILTERS_MAIN_PAGE_DEFAULT);
|
||||
}
|
||||
console.debug("Home: Home Mounted");
|
||||
console.debug("Home: Scroll top");
|
||||
await fetchProducts();
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
|
||||
onActivated(async () => {
|
||||
console.debug('Home: Activated Home');
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
<template>
|
||||
<div ref="goodsRef">
|
||||
<div ref="goodsRef" class="pb-10">
|
||||
<div class="px-5 fixed z-50 w-full opacity-90" style="bottom: var(--tg-safe-area-inset-bottom);">
|
||||
<div class="bg-base-300 flex justify-between p-2 rounded-xl shadow-md">
|
||||
<SearchInput/>
|
||||
</div>
|
||||
</div>
|
||||
<ProductsList/>
|
||||
<ProductsList
|
||||
:products="productsStore.products.data"
|
||||
:hasMore="productsStore.products.meta.hasMore"
|
||||
:isLoading="productsStore.isLoading"
|
||||
:isLoadingMore="productsStore.isLoadingMore"
|
||||
@loadMore="productsStore.loadMore"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -17,12 +23,17 @@ import {useRoute} from "vue-router";
|
||||
import {useProductsStore} from "@/stores/ProductsStore.js";
|
||||
import IconFunnel from "@/components/Icons/IconFunnel.vue";
|
||||
|
||||
defineOptions({
|
||||
name: 'Products'
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const productsStore = useProductsStore();
|
||||
|
||||
const categoryId = route.params.category_id ?? null;
|
||||
|
||||
onMounted(async () => {
|
||||
console.debug("Category Products Mounted");
|
||||
console.debug("Load products for category: ", categoryId);
|
||||
|
||||
if (productsStore.filtersFullUrl === route.fullPath) {
|
||||
|
||||
@@ -91,6 +91,5 @@ onUnmounted(() => {
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside);
|
||||
nextTick(() => searchInput.value.focus());
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user