Files
klz-cables.com/components/ProductList.tsx
2025-12-29 18:18:48 +01:00

216 lines
6.6 KiB
TypeScript

'use client';
import { Product } from '@/lib/data';
import { ProductCard } from '@/components/cards/ProductCard';
import { CardGrid } from '@/components/cards/CardGrid';
import { useState, useMemo } from 'react';
import { Button } from '@/components/ui';
interface ProductListProps {
products: Product[];
locale?: 'de' | 'en';
enableFiltering?: boolean;
enableSorting?: boolean;
enablePagination?: boolean;
itemsPerPage?: number;
}
export function ProductList({
products,
locale = 'de',
enableFiltering = false,
enableSorting = false,
enablePagination = false,
itemsPerPage = 12
}: ProductListProps) {
const [currentPage, setCurrentPage] = useState(1);
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc');
const [filterStock, setFilterStock] = useState<'all' | 'instock' | 'outofstock'>('all');
// Filter products
const filteredProducts = useMemo(() => {
let filtered = [...products];
// Filter by stock status
if (filterStock !== 'all') {
filtered = filtered.filter(p => p.stockStatus === filterStock);
}
// Sort products
if (enableSorting) {
filtered.sort((a, b) => {
const aPrice = parseFloat(a.regularPrice?.replace(/[^\d.]/g, '') || '0');
const bPrice = parseFloat(b.regularPrice?.replace(/[^\d.]/g, '') || '0');
if (sortOrder === 'asc') {
return aPrice - bPrice;
} else {
return bPrice - aPrice;
}
});
}
return filtered;
}, [products, filterStock, sortOrder, enableSorting]);
// Pagination
const paginatedProducts = useMemo(() => {
if (!enablePagination) return filteredProducts;
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
return filteredProducts.slice(startIndex, endIndex);
}, [filteredProducts, currentPage, itemsPerPage, enablePagination]);
const totalPages = enablePagination ? Math.ceil(filteredProducts.length / itemsPerPage) : 1;
// Handlers
const handleSortChange = () => {
setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc');
};
const handleFilterChange = (status: 'all' | 'instock' | 'outofstock') => {
setFilterStock(status);
setCurrentPage(1); // Reset to first page
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
window.scrollTo({ top: 0, behavior: 'smooth' });
};
// Loading state (for future use)
const [isLoading, setIsLoading] = useState(false);
if (products.length === 0) {
return (
<div className="text-center py-12">
<p className="text-gray-500 text-lg">
{locale === 'de' ? 'Keine Produkte gefunden' : 'No products found'}
</p>
<p className="text-gray-400 text-sm mt-2">
{locale === 'de'
? 'Es wurden keine Produkte für diese Kategorie gefunden.'
: 'No products were found for this category.'}
</p>
</div>
);
}
return (
<div className="space-y-6">
{/* Controls */}
{(enableFiltering || enableSorting) && (
<div className="flex flex-wrap items-center justify-between gap-4 bg-white p-4 rounded-lg border border-gray-200">
<div className="flex flex-wrap gap-2">
{enableFiltering && (
<div className="flex gap-2" role="group" aria-label="Stock filter">
<Button
size="sm"
variant={filterStock === 'all' ? 'primary' : 'outline'}
onClick={() => handleFilterChange('all')}
>
{locale === 'de' ? 'Alle' : 'All'}
</Button>
<Button
size="sm"
variant={filterStock === 'instock' ? 'primary' : 'outline'}
onClick={() => handleFilterChange('instock')}
>
{locale === 'de' ? 'Auf Lager' : 'In Stock'}
</Button>
<Button
size="sm"
variant={filterStock === 'outofstock' ? 'primary' : 'outline'}
onClick={() => handleFilterChange('outofstock')}
>
{locale === 'de' ? 'Nicht auf Lager' : 'Out of Stock'}
</Button>
</div>
)}
</div>
{enableSorting && (
<Button
size="sm"
variant="secondary"
onClick={handleSortChange}
icon={
<span className="text-xs">
{sortOrder === 'asc' ? '↑' : '↓'}
</span>
}
>
{locale === 'de'
? `Preis: ${sortOrder === 'asc' ? 'Aufsteigend' : 'Absteigend'}`
: `Price: ${sortOrder === 'asc' ? 'Ascending' : 'Descending'}`}
</Button>
)}
</div>
)}
{/* Results count */}
<div className="text-sm text-gray-600">
{locale === 'de'
? `${filteredProducts.length} Produkte gefunden`
: `${filteredProducts.length} products found`}
</div>
{/* Product Grid */}
<CardGrid
loading={isLoading}
emptyMessage={locale === 'de' ? 'Keine Produkte gefunden' : 'No products found'}
columns={3}
gap="md"
>
{paginatedProducts.map((product) => (
<ProductCard
key={product.id}
product={product}
locale={locale}
showPrice={true}
showStock={true}
showCategories={true}
showAddToCart={true}
showViewDetails={false}
size="md"
/>
))}
</CardGrid>
{/* Pagination */}
{enablePagination && totalPages > 1 && (
<div className="flex items-center justify-center gap-2 mt-8">
<Button
size="sm"
variant="outline"
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1}
>
{locale === 'de' ? 'Vorherige' : 'Previous'}
</Button>
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
<Button
key={page}
size="sm"
variant={currentPage === page ? 'primary' : 'outline'}
onClick={() => handlePageChange(page)}
>
{page}
</Button>
))}
<Button
size="sm"
variant="outline"
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages}
>
{locale === 'de' ? 'Nächste' : 'Next'}
</Button>
</div>
)}
</div>
);
}