import React, { Component } from 'react'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import Grid from '@mui/material/Grid'; import Pagination from '@mui/material/Pagination'; import FormControl from '@mui/material/FormControl'; import InputLabel from '@mui/material/InputLabel'; import Select from '@mui/material/Select'; import MenuItem from '@mui/material/MenuItem'; import Chip from '@mui/material/Chip'; import Stack from '@mui/material/Stack'; import Product from './Product.js'; import { removeSessionSetting } from '../utils/sessionStorage.js'; import { withI18n } from '../i18n/withTranslation.js'; // Sort products by fuzzy similarity to their name/description function sortProductsByFuzzySimilarity(products, searchTerm) { console.log('sortProductsByFuzzySimilarity',products,searchTerm); // Create an array that preserves the product object and its searchable text const productsWithText = products.map(product => { const searchableText = `${product.name || ''} ${product.description || ''}`; return { product, searchableText }; }); // Sort products based on their searchable text similarity productsWithText.sort((a, b) => { const scoreA = getFuzzySimilarityScore(a.searchableText, searchTerm); const scoreB = getFuzzySimilarityScore(b.searchableText, searchTerm); return scoreB - scoreA; // Higher scores first }); // Return just the sorted product objects return productsWithText.map(item => item.product); } // Calculate a similarity score between text and search term function getFuzzySimilarityScore(text, searchTerm) { const searchWords = searchTerm.toLowerCase().split(/\W+/).filter(Boolean); const textWords = text.toLowerCase().split(/\W+/).filter(Boolean); let totalScore = 0; for (let searchWord of searchWords) { // Exact matches get highest priority if (textWords.includes(searchWord)) { totalScore += 2; continue; } // Partial matches get scored based on similarity let bestMatch = 0; for (let textWord of textWords) { if (textWord.includes(searchWord) || searchWord.includes(textWord)) { const similarity = Math.min(searchWord.length, textWord.length) / Math.max(searchWord.length, textWord.length); if (similarity > bestMatch) bestMatch = similarity; } } totalScore += bestMatch; } return totalScore; } class ProductList extends Component { constructor(props) { super(props); this.state = { viewMode: window.productListViewMode || 'grid', products:[], page: window.productListPage || 1, itemsPerPage: window.productListItemsPerPage || 20, sortBy: window.currentSearchQuery ? 'searchField' : 'name' }; } componentDidMount() { this.handleSearchQuery = () => { this.setState({ sortBy: window.currentSearchQuery ? 'searchField' : 'name' }); }; window.addEventListener('search-query-change', this.handleSearchQuery); } componentWillUnmount() { window.removeEventListener('search-query-change', this.handleSearchQuery); } handleViewModeChange = (viewMode) => { this.setState({ viewMode }); window.productListViewMode = viewMode; } handlePageChange = (event, value) => { this.setState({ page: value }); window.productListPage = value; } componentDidUpdate() { const currentPageCapacity = this.state.itemsPerPage === 'all' ? Infinity : this.state.itemsPerPage; if(this.props.products.length > 0 ) if (this.props.products.length < (currentPageCapacity * (this.state.page-1)) ) { if(this.state.page != 1) this.setState({ page: 1 }); window.productListPage = 1; } } handleProductsPerPageChange = (event) => { const newItemsPerPage = event.target.value; const newState = { itemsPerPage: newItemsPerPage }; window.productListItemsPerPage = newItemsPerPage; if(newItemsPerPage!=='all'){ const newTotalPages = Math.ceil(this.props.products.length / newItemsPerPage); if (this.state.page > newTotalPages) { newState.page = newTotalPages; window.productListPage = newTotalPages; } } this.setState(newState); } handleSortChange = (event) => { const sortBy = event.target.value; this.setState({ sortBy }); } renderPagination = (pages, page) => { // Make pagination invisible when there are zero products to avoid layout shifts const hasProducts = this.props.products.length > 0; return ( {(this.state.itemsPerPage==='all')?null: } ); } // Check if filters are active hasActiveFilters = () => { return ( (this.props.activeAttributeFilters && this.props.activeAttributeFilters.length > 0) || (this.props.activeManufacturerFilters && this.props.activeManufacturerFilters.length > 0) || (this.props.activeAvailabilityFilters && this.props.activeAvailabilityFilters.length > 0) ); } // Render message when no products found but filters are active renderNoProductsMessage = () => { const hasFiltersActive = this.hasActiveFilters(); const hasUnfilteredProducts = this.props.totalProductCount > 0; if (this.props.products.length === 0 && hasUnfilteredProducts && hasFiltersActive) { return ( {this.props.t ? this.props.t('product.removeFiltersToSee') : 'Entferne Filter um Produkte zu sehen'} ); } return null; } // Helper function for correct pluralization getProductCountText = () => { const filteredCount = this.props.products.length; const totalCount = this.props.totalProductCount; const isFiltered = totalCount !== filteredCount; if (!isFiltered) { // No filters applied if (filteredCount === 0) return this.props.t ? this.props.t('product.countDisplay.noProducts') : "0 Produkte"; if (filteredCount === 1) return this.props.t ? this.props.t('product.countDisplay.oneProduct') : "1 Produkt"; return this.props.t ? this.props.t('product.countDisplay.multipleProducts', { count: filteredCount }) : `${filteredCount} Produkte`; } else { // Filters applied if (totalCount === 0) return this.props.t ? this.props.t('product.countDisplay.noProducts') : "0 Produkte"; if (totalCount === 1) return this.props.t ? this.props.t('product.countDisplay.filteredOneProduct', { filtered: filteredCount }) : `${filteredCount} von 1 Produkt`; return this.props.t ? this.props.t('product.countDisplay.filteredProducts', { filtered: filteredCount, total: totalCount }) : `${filteredCount} von ${totalCount} Produkten`; } } render() { //console.log('products',this.props.activeAttributeFilters,this.props.activeManufacturerFilters,window.currentSearchQuery,this.state.sortBy); const filteredProducts = (this.state.sortBy==='searchField')&&(window.currentSearchQuery)?sortProductsByFuzzySimilarity(this.props.products, window.currentSearchQuery):this.state.sortBy==='name'?this.props.products:this.props.products.sort((a,b)=>{ if(this.state.sortBy==='price-low-high'){ return a.price-b.price; } if(this.state.sortBy==='price-high-low'){ return b.price-a.price; } }); const products = this.state.itemsPerPage==='all'?[...filteredProducts]:filteredProducts.slice((this.state.page - 1) * this.state.itemsPerPage , this.state.page * this.state.itemsPerPage); return ( {this.props.activeAvailabilityFilters && this.props.activeAvailabilityFilters.map((filter,index) => ( { if (filter.id === '1') { // Add "auf Lager" filter by setting the sessionStorage item to '1' sessionStorage.setItem('filter_availability', '1'); } else { // Remove "Neu" or "Bald verfügbar" filters removeSessionSetting(`filter_availability_${filter.id}`); } this.props.onFilterChange(); }} onDelete={() => { if (filter.id === '1') { // Add "auf Lager" filter by setting the sessionStorage item to '1' sessionStorage.setItem('filter_availability', '1'); } else { // Remove "Neu" or "Bald verfügbar" filters removeSessionSetting(`filter_availability_${filter.id}`); } this.props.onFilterChange(); }} clickable /> ))} {this.props.activeAttributeFilters.map((filter,index) => ( { removeSessionSetting(`filter_attribute_${filter.id}`); this.props.onFilterChange(); }} onDelete={() => { removeSessionSetting(`filter_attribute_${filter.id}`); this.props.onFilterChange(); }} clickable /> ))} {this.props.activeManufacturerFilters.map((filter,index) => ( { removeSessionSetting(`filter_manufacturer_${filter.value}`); this.props.onFilterChange(); }} onDelete={() => { removeSessionSetting(`filter_manufacturer_${filter.value}`); this.props.onFilterChange(); }} clickable /> ))} {/* Sort Dropdown */} {this.props.t ? this.props.t('filters.sorting') : 'Sortierung'} {/* Per Page Dropdown */} {this.props.t ? this.props.t('filters.perPage') : 'pro Seite'} {/* Product count info - mobile only */} {this.getProductCountText()} { this.renderPagination(Math.ceil(filteredProducts.length / this.state.itemsPerPage), this.state.page) } {/*this.props.dataType == 'category' && (<>Kategorie: {this.props.dataParam})}*/} {this.props.dataType == 'search' && (this.props.t ? this.props.t('search.searchResultsFor', { query: this.props.dataParam }) : `Suchergebnisse für: "${this.props.dataParam}"`)} {this.getProductCountText()} {this.renderNoProductsMessage()} {products.map((product, index) => ( ))} {/* Bottom pagination */} {this.renderPagination(Math.ceil(filteredProducts.length / this.state.itemsPerPage), this.state.page)} ); } } export default withI18n()(ProductList);