migration wip
This commit is contained in:
@@ -1,78 +1,216 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
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' }: ProductListProps) {
|
||||
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-8 text-gray-500">
|
||||
{locale === 'de' ? 'Keine Produkte gefunden' : 'No products found'}
|
||||
<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="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{products.map((product) => (
|
||||
<Link
|
||||
key={product.id}
|
||||
href={product.path}
|
||||
className="group block bg-white rounded-lg border border-gray-200 overflow-hidden hover:shadow-lg transition-shadow"
|
||||
>
|
||||
{product.featuredImage && (
|
||||
<div className="aspect-video bg-gray-100 overflow-hidden">
|
||||
<img
|
||||
src={product.featuredImage}
|
||||
alt={product.name}
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-200"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-4">
|
||||
<h3 className="font-semibold text-lg mb-2 text-gray-900 group-hover:text-blue-600 transition-colors">
|
||||
{product.name}
|
||||
</h3>
|
||||
{product.shortDescriptionHtml && (
|
||||
<div
|
||||
className="text-sm text-gray-600 mb-3 line-clamp-2"
|
||||
dangerouslySetInnerHTML={{ __html: product.shortDescriptionHtml }}
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex gap-2">
|
||||
{product.regularPrice && (
|
||||
<span className={`font-bold ${product.salePrice ? 'text-gray-400 line-through text-sm' : 'text-blue-600'}`}>
|
||||
{product.regularPrice}
|
||||
</span>
|
||||
)}
|
||||
{product.salePrice && (
|
||||
<span className="font-bold text-red-600">
|
||||
{product.salePrice}
|
||||
</span>
|
||||
)}
|
||||
<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>
|
||||
{product.stockStatus && (
|
||||
<span className={`text-xs px-2 py-1 rounded ${
|
||||
product.stockStatus === 'instock'
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-red-100 text-red-800'
|
||||
}`}>
|
||||
{product.stockStatus === 'instock'
|
||||
? (locale === 'de' ? 'Auf Lager' : 'In Stock')
|
||||
: (locale === 'de' ? 'Nicht auf Lager' : 'Out of Stock')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
|
||||
{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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user