diff --git a/src/automats/articleSearchAutomat.js b/src/automats/articleSearchAutomat.js index bc74741..2e68956 100644 --- a/src/automats/articleSearchAutomat.js +++ b/src/automats/articleSearchAutomat.js @@ -14,6 +14,8 @@ import { Automat } from 'react-automat'; import FlexSearch from 'flexsearch'; import articleIndexAutomat from './articleIndexAutomat'; import categoryAutomat from './categoryAutomat'; +import brandAutomat from './brandAutomat'; +import supplierAutomat from './supplierAutomat'; import { parseFilter } from '../components/article/searchFormat'; // #region Types & Initial State @@ -22,6 +24,8 @@ import { parseFilter } from '../components/article/searchFormat'; /** * @typedef {Object} ArticleSearchResult * @property {Record} categoryFacets Facet counts per category id. + * @property {Record} brandFacets Facet counts per brand. + * @property {Record} supplierFacets Facet counts per supplier. * @property {number} totalCount Number of matching records. * @property {Array} recordset Sliced matching article index records for the current page. * @property {number} offset Current 0-based offset. @@ -36,6 +40,8 @@ import { parseFilter } from '../components/article/searchFormat'; * @property {ArticleSearchResult} result Latest search outcome. * @property {string} target Active search term. * @property {string} filter Active category filter string. + * @property {string} brand Active brand filter string. + * @property {string} supplier Active supplier filter string. * @property {number} offset Current 0-based offset. * @property {number|null} pageSize Current page size. * @property {boolean} loading Whether a search or index load is in flight. @@ -44,6 +50,8 @@ import { parseFilter } from '../components/article/searchFormat'; /** @type {ArticleSearchResult} */ const emptyResult = { categoryFacets: {}, + brandFacets: {}, + supplierFacets: {}, totalCount: 0, recordset: [], offset: 0, @@ -58,6 +66,8 @@ const initialState = { result: emptyResult, target: '', filter: '', + brand: '', + supplier: '', offset: 0, pageSize: null, loading: false, @@ -565,6 +575,103 @@ function buildCategoryFacets(matchedArticles) { } // #endregion +// #region buildBrandFacets +// MARK: computes brand facet counts from matched articles +/** + * Computes facet counts for each brand represented in matched articles. + * + * @param {Array} matchedArticles Articles matching text query. + * @returns {Record} Brand facets. + */ +function buildBrandFacets(matchedArticles) { + const facets = {}; + const allBrands = brandAutomat.state?.brands ?? []; + const brandById = new Map(); + const brandByName = new Map(); + for (const b of allBrands) { + if (b.id != null) brandById.set(String(b.id), b); + if (b.name) brandByName.set(String(b.name).toLowerCase(), b); + } + + for (const article of matchedArticles) { + const brandObj = article.brand; + const bId = brandObj?.id ?? article.brandId; + const bName = brandObj?.name ?? article.brandName; + if (!bId && !bName) continue; + + const brandKey = bId != null ? String(bId) : String(bName); + const knownBrand = (bId != null ? brandById.get(String(bId)) : null) || + (bName ? brandByName.get(String(bName).toLowerCase()) : null); + + const name = knownBrand?.name || bName || `Marke ${brandKey}`; + const id = knownBrand?.id != null ? String(knownBrand.id) : brandKey; + const thumbHash = knownBrand?.pics?.[0]?.thumbHash || brandObj?.thumbHash || article.brandThumbHash || null; + + if (!facets[id]) { + facets[id] = { + id, + name, + thumbHash, + brand: knownBrand || brandObj || { id, name, thumbHash }, + count: 0, + }; + } + facets[id].count += 1; + } + + return facets; +} +// #endregion + +// #region buildSupplierFacets +// MARK: computes supplier facet counts from matched articles +/** + * Computes facet counts for each supplier represented in matched articles. + * + * @param {Array} matchedArticles Articles matching text query. + * @returns {Record} Supplier facets. + */ +function buildSupplierFacets(matchedArticles) { + const facets = {}; + const suppliers = supplierAutomat.state?.suppliers ?? []; + const articlesBySupplier = supplierAutomat.state?.articlesBySupplier ?? {}; + + const supplierArticleSets = new Map(); + for (const s of suppliers) { + const sId = String(s.id); + const list = articlesBySupplier[sId] ?? []; + const skuSet = new Set(); + for (const item of list) { + if (item.article?.sku) skuSet.add(String(item.article.sku).toLowerCase()); + if (item.sku) skuSet.add(String(item.sku).toLowerCase()); + } + supplierArticleSets.set(sId, skuSet); + } + + for (const article of matchedArticles) { + const artSku = String(article.sku ?? '').toLowerCase(); + if (!artSku) continue; + + for (const s of suppliers) { + const sId = String(s.id); + const skuSet = supplierArticleSets.get(sId); + if (skuSet && skuSet.has(artSku)) { + if (!facets[sId]) { + facets[sId] = { + id: sId, + name: s.name, + count: 0, + }; + } + facets[sId].count += 1; + } + } + } + + return facets; +} +// #endregion + // #endregion // #region Actions @@ -581,6 +688,8 @@ const actions = { const currentState = articleSearchAutomat.state; const target = params.target !== undefined ? String(params.target ?? '') : currentState.target; const filter = params.filter !== undefined ? String(params.filter ?? '') : currentState.filter; + const brand = params.brand !== undefined ? String(params.brand ?? '') : (currentState.brand ?? ''); + const supplier = params.supplier !== undefined ? String(params.supplier ?? '') : (currentState.supplier ?? ''); const pageSize = params.pageSize !== undefined ? params.pageSize : currentState.pageSize; const offset = params.offset !== undefined ? Math.max(0, Number(params.offset) || 0) : currentState.offset; @@ -592,8 +701,10 @@ const actions = { ? queryFlexSearch(query, articles) : articles; - // Build facets for all text matches (before category filtering) + // Build facets for all text matches (before facet filtering) const categoryFacets = buildCategoryFacets(textMatches); + const brandFacets = buildBrandFacets(textMatches); + const supplierFacets = buildSupplierFacets(textMatches); // Apply category filter if set (expanding to include all child categories) const selectedCategories = parseFilter(filter); @@ -606,11 +717,42 @@ const actions = { categoriesMap, categoryFacets, ); - filteredArticles = textMatches.filter((row) => + filteredArticles = filteredArticles.filter((row) => matchesCategory(row, expandedCategoryIds), ); } + // Apply brand filter if set + const selectedBrands = parseFilter(brand); + if (selectedBrands.length > 0) { + const brandSet = new Set(selectedBrands.map((b) => String(b).trim().toLowerCase())); + filteredArticles = filteredArticles.filter((row) => { + const bId = row.brand?.id ?? row.brandId; + const bName = row.brand?.name ?? row.brandName; + if (bId != null && brandSet.has(String(bId).toLowerCase())) return true; + if (bName && brandSet.has(String(bName).trim().toLowerCase())) return true; + return false; + }); + } + + // Apply supplier filter if set + const selectedSuppliers = parseFilter(supplier); + if (selectedSuppliers.length > 0) { + const articlesBySupplier = supplierAutomat.state?.articlesBySupplier ?? {}; + const allowedSkus = new Set(); + for (const sId of selectedSuppliers) { + const list = articlesBySupplier[String(sId)] ?? []; + for (const item of list) { + if (item.article?.sku) allowedSkus.add(String(item.article.sku).toLowerCase()); + if (item.sku) allowedSkus.add(String(item.sku).toLowerCase()); + } + } + filteredArticles = filteredArticles.filter((row) => { + const rowSku = String(row.sku ?? '').toLowerCase(); + return allowedSkus.has(rowSku); + }); + } + const sortedArticles = sortArticlesByRelevance(filteredArticles, query); const totalCount = sortedArticles.length; @@ -623,6 +765,8 @@ const actions = { const result = { categoryFacets, + brandFacets, + supplierFacets, totalCount, recordset, offset, @@ -636,6 +780,8 @@ const actions = { result, target, filter, + brand, + supplier, offset, pageSize: safePageSize, loading: articleIndexAutomat.state.status === 'loading', @@ -751,6 +897,16 @@ categoryAutomat.subscribe((catState) => { actions.runSearch(); } }); + +// Update facets when brands are loaded or updated. +brandAutomat.subscribe(() => { + actions.runSearch(); +}); + +// Update facets when suppliers or supplier articles are loaded or updated. +supplierAutomat.subscribe(() => { + actions.runSearch(); +}); // #endregion // #region Selectors & Exports diff --git a/src/automats/supplierAutomat.js b/src/automats/supplierAutomat.js index 31a5d5f..dbebc20 100644 --- a/src/automats/supplierAutomat.js +++ b/src/automats/supplierAutomat.js @@ -30,6 +30,8 @@ import authAutomat from './authAutomat'; /** @type {SupplierState} */ const initialState = { suppliers: [], + articlesBySupplier: {}, + articlesLoading: false, status: 'idle', error: null, }; @@ -62,6 +64,7 @@ function loadSuppliers() { try { const suppliers = await fetchSuppliers(); supplierAutomat.setState({ suppliers, status: 'ready', error: null }); + void actions.loadAllSupplierArticles().catch(() => {}); return suppliers; } catch (error) { supplierAutomat.setState({ status: 'failed', error }); @@ -92,6 +95,7 @@ const actions = { try { const suppliers = await fetchSuppliers(); supplierAutomat.setState({ suppliers, status: 'ready', error: null }); + void actions.loadAllSupplierArticles().catch(() => {}); return suppliers; } catch (error) { supplierAutomat.setState({ error }); @@ -99,6 +103,58 @@ const actions = { } }, + /** + * Loads articles for a single supplier, caching them in state.articlesBySupplier. + * + * @param {string|number} supplierId + * @returns {Promise>} + */ + async loadArticlesForSupplier(supplierId) { + const sId = String(supplierId); + if (supplierAutomat.state.articlesBySupplier?.[sId]) { + return supplierAutomat.state.articlesBySupplier[sId]; + } + const res = await supplierApi.getArticles(supplierId); + const articles = Array.isArray(res?.articles) ? res.articles : []; + supplierAutomat.setState({ + articlesBySupplier: { + ...(supplierAutomat.state.articlesBySupplier || {}), + [sId]: articles, + }, + }); + return articles; + }, + + /** + * Loads articles for all suppliers in the supplier list, caching them in state.articlesBySupplier. + * + * @returns {Promise>>} + */ + async loadAllSupplierArticles() { + const suppliers = supplierAutomat.state.suppliers; + if (!suppliers.length) return {}; + supplierAutomat.setState({ articlesLoading: true }); + try { + const results = await Promise.allSettled( + suppliers.map(async (s) => { + const res = await supplierApi.getArticles(s.id); + return { id: String(s.id), articles: Array.isArray(res?.articles) ? res.articles : [] }; + }), + ); + const nextMap = { ...(supplierAutomat.state.articlesBySupplier || {}) }; + for (const r of results) { + if (r.status === 'fulfilled') { + nextMap[r.value.id] = r.value.articles; + } + } + supplierAutomat.setState({ articlesBySupplier: nextMap, articlesLoading: false }); + return nextMap; + } catch { + supplierAutomat.setState({ articlesLoading: false }); + return supplierAutomat.state.articlesBySupplier || {}; + } + }, + /** * Creates a supplier and refreshes the cached list. * diff --git a/src/components/article/artikelQuery.js b/src/components/article/artikelQuery.js index 12748c8..62232db 100644 --- a/src/components/article/artikelQuery.js +++ b/src/components/article/artikelQuery.js @@ -4,14 +4,28 @@ export function queryFromParams(searchParams) { return { target: searchParams.get('target') ?? '', filter: searchParams.get('filter') ?? '', + brand: searchParams.get('brand') ?? '', + supplier: searchParams.get('supplier') ?? '', offset: Math.max(0, Number(searchParams.get('offset')) || 0), }; } -export function buildSearchUrl(target, filter = '', offset = 0) { +export function buildSearchUrl(target, filter = '', offset = 0, options = {}) { + let brand = ''; + let supplier = ''; + if (typeof options === 'string') { + brand = options; + supplier = arguments[4] || ''; + } else if (options && typeof options === 'object') { + brand = options.brand || ''; + supplier = options.supplier || ''; + } + const params = new URLSearchParams(); if (target) params.set('target', target); if (filter) params.set('filter', filter); + if (brand) params.set('brand', brand); + if (supplier) params.set('supplier', supplier); if (offset > 0) params.set('offset', String(offset)); const qs = params.toString(); return qs ? `/artikel?${qs}` : '/artikel'; @@ -26,7 +40,13 @@ export function readArtikelQuery() { } } -export function writeArtikelQuery({ target, filter = '', offset = 0 }) { +export function writeArtikelQuery({ + target, + filter = '', + offset = 0, + brand = '', + supplier = '', +}) { try { if (!target || target.trim().length < 3) { localStorage.removeItem(ARTIKEL_QUERY_KEY); @@ -34,7 +54,7 @@ export function writeArtikelQuery({ target, filter = '', offset = 0 }) { } localStorage.setItem( ARTIKEL_QUERY_KEY, - JSON.stringify({ target, filter, offset }), + JSON.stringify({ target, filter, offset, brand, supplier }), ); } catch { // ignore quota / private mode @@ -53,5 +73,8 @@ export function clearArtikelQuery() { export function artikelQueryPath() { const query = readArtikelQuery(); if (!query?.target || query.target.trim().length < 3) return '/artikel'; - return buildSearchUrl(query.target, query.filter ?? '', query.offset ?? 0); + return buildSearchUrl(query.target, query.filter ?? '', query.offset ?? 0, { + brand: query.brand ?? '', + supplier: query.supplier ?? '', + }); } diff --git a/src/routes/artikel.jsx b/src/routes/artikel.jsx index 1588563..57966fc 100644 --- a/src/routes/artikel.jsx +++ b/src/routes/artikel.jsx @@ -47,8 +47,10 @@ import { navigationAutomat, categoryAutomat, companyAutomat, + brandAutomat, + supplierAutomat, } from '../automats'; -import { BrandLabel } from '../components/brand'; +import { BrandLabel, BrandThumb } from '../components/brand'; import { SCRIM_LABEL } from '../colors'; // #endregion @@ -266,8 +268,12 @@ function initialSearchState(searchParams) { return { input, categoryFilterOpen: false, + brandFilterOpen: false, + supplierFilterOpen: false, error: automatResult.error, categoryFacets: automatResult.categoryFacets, + brandFacets: automatResult.brandFacets, + supplierFacets: automatResult.supplierFacets, totalCount: automatResult.totalCount, recordset: automatResult.recordset, pageSize: automatResult.pageSize, @@ -313,7 +319,27 @@ class ArtikelSearchPage extends Component { // #region Query Persistence toggleCategoryFilter = () => { - this.setState((prev) => ({ categoryFilterOpen: !prev.categoryFilterOpen })); + this.setState((prev) => ({ + categoryFilterOpen: !prev.categoryFilterOpen, + brandFilterOpen: false, + supplierFilterOpen: false, + })); + }; + + toggleBrandFilter = () => { + this.setState((prev) => ({ + brandFilterOpen: !prev.brandFilterOpen, + categoryFilterOpen: false, + supplierFilterOpen: false, + })); + }; + + toggleSupplierFilter = () => { + this.setState((prev) => ({ + supplierFilterOpen: !prev.supplierFilterOpen, + categoryFilterOpen: false, + brandFilterOpen: false, + })); }; handleOpenArticle = (row) => { @@ -327,6 +353,8 @@ class ArtikelSearchPage extends Component { writeArtikelQuery({ target, filter: query.filter, + brand: query.brand, + supplier: query.supplier, offset: query.offset, }); }; @@ -339,16 +367,30 @@ class ArtikelSearchPage extends Component { // Push search results into local component state. this.unsubSearch = articleSearchAutomat.subscribe(this, (state) => state.result); + this.unsubBrand = brandAutomat.subscribe(this, () => ({})); + this.unsubSupplier = supplierAutomat.subscribe(this, () => ({})); + + void brandAutomat.actions.load().catch(() => {}); + void supplierAutomat.actions.load().catch(() => {}); + void supplierAutomat.actions.loadAllSupplierArticles().catch(() => {}); // Initial search: kicks off index loading if idle and derives current items - const { filter: urlFilter, offset } = queryFromParams(this.props.searchParams); + const { + filter: urlFilter, + offset, + brand: urlBrand, + supplier: urlSupplier, + } = queryFromParams(this.props.searchParams); const hasFilterInUrl = this.props.searchParams.has('filter'); const navCategory = navigationAutomat.state.lastCategoryId; const filter = hasFilterInUrl ? urlFilter : (navCategory || urlFilter); const activeTarget = this.state.input; if (!hasFilterInUrl && navCategory) { - const syncedUrl = buildSearchUrl(activeTarget, filter, offset); + const syncedUrl = buildSearchUrl(activeTarget, filter, offset, { + brand: urlBrand, + supplier: urlSupplier, + }); setLastSearchTerm(activeTarget, syncedUrl); this.props.navigate(syncedUrl, { replace: true }); } else if (hasFilterInUrl) { @@ -357,10 +399,18 @@ class ArtikelSearchPage extends Component { setLastCategoryId(primaryCatId); } - setLastSearchTerm(activeTarget, buildSearchUrl(activeTarget, filter, offset)); + setLastSearchTerm( + activeTarget, + buildSearchUrl(activeTarget, filter, offset, { + brand: urlBrand, + supplier: urlSupplier, + }), + ); void articleSearchAutomat.actions.search({ target: activeTarget, filter, + brand: urlBrand, + supplier: urlSupplier, offset: 0, }).catch(() => {}); } @@ -369,10 +419,20 @@ class ArtikelSearchPage extends Component { document.removeEventListener('mouseup', preserveSearchFocus, true); this.unsubSearch?.(); this.unsubSearch = null; + this.unsubBrand?.(); + this.unsubBrand = null; + this.unsubSupplier?.(); + this.unsubSupplier = null; this.persistQuery(); const query = queryFromParams(this.props.searchParams); const target = this.state.input; - setLastSearchTerm(target, buildSearchUrl(target, query.filter, query.offset)); + setLastSearchTerm( + target, + buildSearchUrl(target, query.filter, query.offset, { + brand: query.brand, + supplier: query.supplier, + }), + ); } componentDidUpdate(prevProps) { @@ -389,6 +449,8 @@ class ArtikelSearchPage extends Component { if ( searchParams.get('filter') !== prev.get('filter') + || searchParams.get('brand') !== prev.get('brand') + || searchParams.get('supplier') !== prev.get('supplier') || searchParams.get('offset') !== prev.get('offset') ) { this.fetchResults(); @@ -397,13 +459,24 @@ class ArtikelSearchPage extends Component { // #endregion // #region Search & Navigation Handlers - updateParams = (nextTarget, nextFilter, nextOffset = 0) => { + updateParams = ( + nextTarget, + nextFilter, + nextOffset = 0, + nextBrand = this.props.searchParams.get('brand') ?? '', + nextSupplier = this.props.searchParams.get('supplier') ?? '', + ) => { writeArtikelQuery({ target: nextTarget, filter: nextFilter, + brand: nextBrand, + supplier: nextSupplier, offset: nextOffset, }); - const nextUrl = buildSearchUrl(nextTarget, nextFilter, nextOffset); + const nextUrl = buildSearchUrl(nextTarget, nextFilter, nextOffset, { + brand: nextBrand, + supplier: nextSupplier, + }); setLastSearchTerm(nextTarget, nextUrl); // Synchronize category with navigationAutomat and /kategorien/ @@ -427,14 +500,16 @@ class ArtikelSearchPage extends Component { handleInputChange = (event) => { const value = event.target.value; this.setState({ input: value }); - const { filter } = queryFromParams(this.props.searchParams); - const currentUrl = buildSearchUrl(value, filter, 0); + const { filter, brand, supplier } = queryFromParams(this.props.searchParams); + const currentUrl = buildSearchUrl(value, filter, 0, { brand, supplier }); setLastSearchTerm(value, currentUrl); - writeArtikelQuery({ target: value, filter, offset: 0 }); + writeArtikelQuery({ target: value, filter, brand, supplier, offset: 0 }); this.props.navigate(currentUrl, { replace: true }); void articleSearchAutomat.actions.search({ target: value, filter, + brand, + supplier, offset: 0, }).catch(() => {}); }; @@ -444,13 +519,15 @@ class ArtikelSearchPage extends Component { }; fetchResults = () => { - const { target, filter, offset } = queryFromParams(this.props.searchParams); + const { target, filter, brand, supplier, offset } = queryFromParams(this.props.searchParams); - writeArtikelQuery({ target, filter, offset }); + writeArtikelQuery({ target, filter, brand, supplier, offset }); void articleSearchAutomat.actions.search({ target: target.trim(), filter, + brand, + supplier, offset, }).catch(() => {}); }; @@ -461,13 +538,22 @@ class ArtikelSearchPage extends Component { const { input, categoryFilterOpen, + brandFilterOpen, + supplierFilterOpen, error, totalCount, categoryFacets, + brandFacets, + supplierFacets, recordset, } = this.state; const filterParam = this.props.searchParams.get('filter') ?? ''; + const brandParam = this.props.searchParams.get('brand') ?? ''; + const supplierParam = this.props.searchParams.get('supplier') ?? ''; + const categories = parseFilter(filterParam); + const selectedBrands = parseFilter(brandParam); + const selectedSuppliers = parseFilter(supplierParam); const rows = recordset ?? []; const categoriesMap = categoryAutomat.state?.categories; @@ -503,6 +589,20 @@ class ArtikelSearchPage extends Component { }) : []; + const brandFacetEntries = brandFacets + ? Object.values(brandFacets).sort((a, b) => { + if (b.count !== a.count) return b.count - a.count; + return String(a.name).localeCompare(String(b.name), 'de'); + }) + : []; + + const supplierFacetEntries = supplierFacets + ? Object.values(supplierFacets).sort((a, b) => { + if (b.count !== a.count) return b.count - a.count; + return String(a.name).localeCompare(String(b.name), 'de'); + }) + : []; + // Per-render values the static `renderItem` row reads through the context. resultRowsContext.categoryFacets = categoryFacets ?? {}; resultRowsContext.onOpenArticle = this.handleOpenArticle; @@ -517,6 +617,7 @@ class ArtikelSearchPage extends Component { gap: 1.5, alignItems: 'center', minHeight: 40, + flexWrap: 'wrap', }} > } endIcon={categoryFilterOpen ? : } @@ -565,6 +666,29 @@ class ArtikelSearchPage extends Component { > Kategorien + + + {categories.map((catId) => { const label = getCategoryBreadcrumbLabel( catId, @@ -574,13 +698,71 @@ class ArtikelSearchPage extends Component { ); return ( { const next = categories.filter((id) => id !== catId); - this.updateParams(input, buildFilter(next), 0); + this.updateParams( + input, + buildFilter(next), + 0, + buildFilter(selectedBrands), + buildFilter(selectedSuppliers), + ); + }} + sx={{ height: 40, borderRadius: 1.5, fontWeight: 500, flexShrink: 0 }} + /> + ); + })} + + {selectedBrands.map((brandId) => { + const label = + brandFacets?.[brandId]?.name || + brandAutomat.state.brands?.find((b) => String(b.id) === String(brandId) || b.name === brandId)?.name || + brandId; + return ( + { + const next = selectedBrands.filter((id) => id !== brandId); + this.updateParams( + input, + buildFilter(categories), + 0, + buildFilter(next), + buildFilter(selectedSuppliers), + ); + }} + sx={{ height: 40, borderRadius: 1.5, fontWeight: 500, flexShrink: 0 }} + /> + ); + })} + + {selectedSuppliers.map((supplierId) => { + const label = + supplierFacets?.[supplierId]?.name || + supplierAutomat.state.suppliers?.find((s) => String(s.id) === String(supplierId))?.name || + `Lieferant #${supplierId}`; + return ( + { + const next = selectedSuppliers.filter((id) => id !== supplierId); + this.updateParams( + input, + buildFilter(categories), + 0, + buildFilter(selectedBrands), + buildFilter(next), + ); }} sx={{ height: 40, borderRadius: 1.5, fontWeight: 500, flexShrink: 0 }} /> @@ -602,8 +784,6 @@ class ArtikelSearchPage extends Component { sx={{ width: 240, flexShrink: 0, - // No intrinsic height: the results tile drives the row height - // and the absolutely-filled panel matches it exactly. position: 'relative', }} > @@ -616,8 +796,6 @@ class ArtikelSearchPage extends Component { display: 'flex', flexDirection: 'column', overflow: 'hidden', - // Match PanelTile (PANEL_TILE_BORDER_RADIUS: borderRadius 4 → - // theme.shape.borderRadius × 4 = 16px) so both columns line up. borderRadius: 4, }} > @@ -654,7 +832,13 @@ class ArtikelSearchPage extends Component { onMouseDown={(e) => e.preventDefault()} onClick={() => { const next = selected ? [] : [id]; - this.updateParams(input, buildFilter(next), 0); + this.updateParams( + input, + buildFilter(next), + 0, + buildFilter(selectedBrands), + buildFilter(selectedSuppliers), + ); }} sx={{ pl: 2 + depth * 1.5 }} > @@ -673,6 +857,167 @@ class ArtikelSearchPage extends Component { )} + {brandFilterOpen && brandFacetEntries.length > 0 && ( + + + + + Marken + + + + + + + {brandFacetEntries.map(({ id, count, name, brand }) => { + const selected = selectedBrands.includes(String(id)); + return ( + e.preventDefault()} + onClick={() => { + const next = selected ? [] : [String(id)]; + this.updateParams( + input, + buildFilter(categories), + 0, + buildFilter(next), + buildFilter(selectedSuppliers), + ); + }} + sx={{ pl: 2 }} + > + {brand ? ( + + + + ) : null} + + {selected && ( + + )} + + ); + })} + + + + )} + + {supplierFilterOpen && supplierFacetEntries.length > 0 && ( + + + + + Lieferanten + + + + + + + {supplierFacetEntries.map(({ id, count, name }) => { + const selected = selectedSuppliers.includes(String(id)); + return ( + e.preventDefault()} + onClick={() => { + const next = selected ? [] : [String(id)]; + this.updateParams( + input, + buildFilter(categories), + 0, + buildFilter(selectedBrands), + buildFilter(next), + ); + }} + sx={{ pl: 2 }} + > + + {selected && ( + + )} + + ); + })} + + + + )} + {!error && input.trim().length > 0 && totalCount === 0 && ( Keine Artikel gefunden.