UNPKG

barneo-search-widget-lib

Version:

Библиотека для поиска по каталогу Barneo на Vue 3

356 lines (309 loc) 10.4 kB
import { ref, reactive, computed, watch, readonly } from "vue"; import type { SearchState, FilterState, SharedStateConfig, SharedStateEvents, } from "../types"; // Глобальный экземпляр sharedState let globalSharedState: ReturnType<typeof createSharedState> | null = null; /** * Создает экземпляр sharedState */ function createSharedState(config: SharedStateConfig = {}) { // Основное состояние поиска const searchState = reactive<SearchState>({ query: "", results: [], totalResults: 0, full_products: [], totalProducts: 0, filters: [], activeFilters: [], products: [], activeProducts: [], sort: "relevance", pagination: { limit_products: 20, limit_categories: 10, limit_brands: 10, offset_products: 0, }, isLoading: false, error: null, searchTimestamp: 0, isDeepSearch: false, }); // События для уведомления других виджетов const events = ref<SharedStateEvents | null>(null); // Вычисляемые свойства const hasResults = computed(() => searchState.results.length > 0); const hasActiveFilters = computed(() => searchState.activeFilters.length > 0); const isEmpty = computed(() => !searchState.query && !hasActiveFilters.value); /** * Устанавливает результаты поиска (вызывается из searchWidget) */ const setSearchResults = ( query: string, results: any[], isDeepSearch: boolean = false, fullProducts?: any[], totalProducts?: number ) => { searchState.query = query; searchState.results = results; searchState.totalResults = results.length; searchState.full_products = fullProducts || results; searchState.totalProducts = totalProducts || results.length; searchState.isDeepSearch = isDeepSearch; searchState.searchTimestamp = Date.now(); searchState.error = null; // Уведомляем другие виджеты events.value?.onSearchResults(searchState); }; /** * Устанавливает фильтры из API (вызывается из searchWidget) */ const setFilters = (filters: any[]) => { searchState.filters = filters; searchState.searchTimestamp = Date.now(); // Уведомляем другие виджеты events.value?.onSearchResults(searchState); }; /** * Устанавливает состояние загрузки */ const setLoading = (loading: boolean) => { searchState.isLoading = loading; events.value?.onSearchLoading(loading); }; /** * Устанавливает ошибку */ const setError = (error: string) => { searchState.error = error; events.value?.onSearchError(error); }; /** * Применяет фильтр (вызывается из filtersWidget) */ const applyFilter = (filter: FilterState) => { const existingFilterIndex = searchState.activeFilters.findIndex( (f) => f.id === filter.id ); if (existingFilterIndex >= 0) { searchState.activeFilters[existingFilterIndex] = filter; } else { searchState.activeFilters.push(filter); } searchState.searchTimestamp = Date.now(); // Уведомляем другие виджеты events.value?.onFilterApplied(filter); events.value?.onSearchResults(searchState); // Автоматическое обновление результатов при изменении фильтров if (config.autoUpdateOnFilterChange) { // Здесь можно добавить логику для автоматического обновления результатов } }; /** * Удаляет фильтр (вызывается из filtersWidget) */ const removeFilter = (filterId: string) => { const filterIndex = searchState.activeFilters.findIndex( (f) => f.id === filterId ); if (filterIndex >= 0) { const removedFilter = searchState.activeFilters[filterIndex]; searchState.activeFilters.splice(filterIndex, 1); searchState.searchTimestamp = Date.now(); // Уведомляем другие виджеты events.value?.onFilterRemoved(filterId); events.value?.onSearchResults(searchState); } }; /** * Устанавливает активные фильтры */ const setActiveFilters = (filters: FilterState[]) => { searchState.activeFilters = filters; searchState.searchTimestamp = Date.now(); }; /** * Очищает все фильтры */ const clearFilters = () => { searchState.filters = []; searchState.activeFilters = []; searchState.activeProducts = []; searchState.searchTimestamp = Date.now(); }; /** * Обновляет продукты (вызывается из productsWidget) */ const updateProducts = (products: any[]) => { searchState.full_products = products; searchState.totalProducts = products.length; searchState.searchTimestamp = Date.now(); // Уведомляем другие виджеты events.value?.onProductsUpdated(products); }; /** * Выбирает продукт (вызывается из productsWidget) */ const selectProduct = (product: any) => { // Уведомляем другие виджеты events.value?.onProductSelected(product); }; /** * Сбрасывает все состояние */ const reset = () => { searchState.query = ""; searchState.results = []; searchState.totalResults = 0; searchState.full_products = []; searchState.totalProducts = 0; searchState.filters = []; searchState.activeFilters = []; searchState.activeProducts = []; searchState.sort = "relevance"; searchState.pagination = { limit_products: 20, limit_categories: 10, limit_brands: 10, offset_products: 0, }; searchState.isLoading = false; searchState.error = null; searchState.searchTimestamp = 0; searchState.isDeepSearch = false; }; /** * Устанавливает сортировку */ const setSort = (sort: string) => { searchState.sort = sort; searchState.searchTimestamp = Date.now(); // Уведомляем другие виджеты events.value?.onSearchResults(searchState); }; /** * Устанавливает пагинацию */ const setPagination = (pagination: { limit_products?: number; limit_categories?: number; limit_brands?: number; offset_products?: number; }) => { searchState.pagination = { ...searchState.pagination, ...pagination, }; searchState.searchTimestamp = Date.now(); // Уведомляем другие виджеты events.value?.onSearchResults(searchState); }; /** * Сбрасывает пагинацию (возвращает на первую страницу) */ const resetPagination = () => { searchState.pagination.offset_products = 0; searchState.searchTimestamp = Date.now(); // Уведомляем другие виджеты events.value?.onSearchResults(searchState); }; /** * Устанавливает продуктовые фильтры */ const setProducts = (products: any[]) => { searchState.products = products; searchState.searchTimestamp = Date.now(); // Уведомляем другие виджеты events.value?.onSearchResults(searchState); }; /** * Очищает продуктовые фильтры */ const clearProducts = () => { searchState.products = []; searchState.searchTimestamp = Date.now(); // Уведомляем другие виджеты events.value?.onSearchResults(searchState); }; /** * Устанавливает активные продуктовые фильтры */ const setActiveProducts = (activeProducts: any[]) => { searchState.activeProducts = activeProducts; searchState.searchTimestamp = Date.now(); // Уведомляем другие виджеты events.value?.onSearchResults(searchState); }; /** * Очищает активные продуктовые фильтры */ const clearActiveProducts = () => { searchState.activeProducts = []; searchState.searchTimestamp = Date.now(); // Уведомляем другие виджеты events.value?.onSearchResults(searchState); }; /** * Регистрирует обработчики событий */ const registerEvents = (eventHandlers: SharedStateEvents) => { events.value = eventHandlers; }; /** * Отменяет регистрацию обработчиков событий */ const unregisterEvents = () => { events.value = null; }; return { // Состояние (только для чтения) searchState: readonly(searchState), // Вычисляемые свойства hasResults, hasActiveFilters, isEmpty, // Методы для управления состоянием setSearchResults, setFilters, setLoading, setError, applyFilter, removeFilter, setActiveFilters, clearFilters, setProducts, clearProducts, setActiveProducts, clearActiveProducts, updateProducts, selectProduct, reset, setSort, setPagination, resetPagination, // Методы для управления событиями registerEvents, unregisterEvents, }; } /** * Composable для управления общим состоянием между виджетами * * Этот composable предоставляет централизованное хранилище для обмена данными * между searchWidget, filtersWidget, productsWidget и sortWidget */ export function useSharedState(config?: SharedStateConfig) { // Создаем глобальный экземпляр, если он еще не существует if (!globalSharedState) { // Используем переданную конфигурацию или пустой объект const finalConfig = config || {}; globalSharedState = createSharedState(finalConfig); } return globalSharedState; }