import React, { useState, useMemo, useEffect } from 'react'; import { Filament } from '@/src/types/filament'; import { EnhancedFilters } from './EnhancedFilters'; import { colorService } from '@/src/services/api'; import { trackEvent } from './MatomoAnalytics'; interface FilamentTableV2Props { filaments: Filament[]; } const FilamentTableV2: React.FC = ({ filaments }) => { const [searchTerm, setSearchTerm] = useState(''); const [sortField, setSortField] = useState('boja'); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); const [availableColors, setAvailableColors] = useState | null>(null); const [filters, setFilters] = useState({ material: '', finish: '', color: '' }); // Fetch all available colors from API useEffect(() => { const fetchColors = async () => { try { const colors = await colorService.getAll(); setAvailableColors(colors); } catch (error) { console.error('Error fetching colors:', error); } }; fetchColors(); }, []); // Use filaments directly since they're already in the correct format const normalizedFilaments = useMemo(() => { return filaments; }, [filaments]); // Get unique values for filters (only from filaments with inventory) const uniqueValues = useMemo(() => { const filamentsWithInventory = normalizedFilaments.filter(f => f.kolicina > 0); return { materials: [...new Set(filamentsWithInventory.map(f => f.tip))].sort(), finishes: [...new Set(filamentsWithInventory.map(f => f.finish))].sort(), colors: [...new Set(filamentsWithInventory.map(f => f.boja))].sort() }; }, [normalizedFilaments]); // Filter and sort filaments const filteredAndSortedFilaments = useMemo(() => { let filtered = normalizedFilaments.filter(filament => { // First, filter out filaments with zero inventory if (filament.kolicina === 0) { return false; } // Search filter const searchLower = searchTerm.toLowerCase(); const matchesSearch = filament.tip.toLowerCase().includes(searchLower) || filament.finish.toLowerCase().includes(searchLower) || filament.boja.toLowerCase().includes(searchLower) || filament.cena.toLowerCase().includes(searchLower); // Other filters const matchesMaterial = !filters.material || filament.tip === filters.material; const matchesFinish = !filters.finish || filament.finish === filters.finish; const matchesColor = !filters.color || filament.boja === filters.color; return matchesSearch && matchesMaterial && matchesFinish && matchesColor; }); // Sort if (sortField) { filtered.sort((a, b) => { let aVal: any = a[sortField as keyof typeof a]; let bVal: any = b[sortField as keyof typeof b]; // Handle numeric values if (sortField === 'kolicina' || sortField === 'cena') { aVal = parseFloat(String(aVal)) || 0; bVal = parseFloat(String(bVal)) || 0; } // Handle spulna (extract numbers) else if (sortField === 'spulna') { const aMatch = String(aVal).match(/^(\d+)/); const bMatch = String(bVal).match(/^(\d+)/); aVal = aMatch ? parseInt(aMatch[1]) : 0; bVal = bMatch ? parseInt(bMatch[1]) : 0; } // String comparison for other fields else { aVal = String(aVal).toLowerCase(); bVal = String(bVal).toLowerCase(); } if (aVal < bVal) return sortOrder === 'asc' ? -1 : 1; if (aVal > bVal) return sortOrder === 'asc' ? 1 : -1; return 0; }); } return filtered; }, [normalizedFilaments, searchTerm, filters, sortField, sortOrder]); const handleSort = (field: string) => { if (sortField === field) { setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc'); } else { setSortField(field); setSortOrder('asc'); } trackEvent('Table', 'Sort', field); }; return (
{/* Search Bar */}
{ setSearchTerm(e.target.value); if (e.target.value) { trackEvent('Search', 'Filament Search', e.target.value); } }} className="w-full px-4 py-2 pl-10 pr-4 text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:border-blue-500" />
{/* Enhanced Filters */} {/* Table */}
{filteredAndSortedFilaments.map(filament => { return ( ); })}
handleSort('tip')} className="px-2 sm:px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"> Tip {sortField === 'tip' && (sortOrder === 'asc' ? '↑' : '↓')} handleSort('finish')} className="px-2 sm:px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"> Finiš {sortField === 'finish' && (sortOrder === 'asc' ? '↑' : '↓')} handleSort('boja')} className="px-2 sm:px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"> Boja {sortField === 'boja' && (sortOrder === 'asc' ? '↑' : '↓')} handleSort('refill')} className="px-2 sm:px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"> Refil {sortField === 'refill' && (sortOrder === 'asc' ? '↑' : '↓')} handleSort('spulna')} className="px-2 sm:px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"> Špulna {sortField === 'spulna' && (sortOrder === 'asc' ? '↑' : '↓')} handleSort('kolicina')} className="px-2 sm:px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"> Količina {sortField === 'kolicina' && (sortOrder === 'asc' ? '↑' : '↓')} handleSort('cena')} className="px-2 sm:px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800"> Cena {sortField === 'cena' && (sortOrder === 'asc' ? '↑' : '↓')}
{filament.tip} {filament.finish}
{filament.boja_hex && (
)} {filament.boja}
{filament.refill > 0 ? ( {filament.refill} ) : ( 0 )} {filament.spulna > 0 ? ( {filament.spulna} ) : ( 0 )} {filament.kolicina} {(() => { // First check if filament has custom prices stored const hasRefill = filament.refill > 0; const hasSpool = filament.spulna > 0; if (!hasRefill && !hasSpool) return '-'; // Parse prices from the cena field if available (format: "3499" or "3499/3999") let refillPrice = 3499; let spoolPrice = 3999; if (filament.cena) { const prices = filament.cena.split('/'); if (prices.length === 1) { // Single price - use for whatever is in stock refillPrice = parseInt(prices[0]) || 3499; spoolPrice = parseInt(prices[0]) || 3999; } else if (prices.length === 2) { // Two prices - refill/spool format refillPrice = parseInt(prices[0]) || 3499; spoolPrice = parseInt(prices[1]) || 3999; } } else { // Fallback to color defaults if no custom price const colorData = availableColors?.find(c => c.name === filament.boja); refillPrice = colorData?.cena_refill || 3499; spoolPrice = colorData?.cena_spulna || 3999; } // Calculate sale prices if applicable const saleActive = filament.sale_active && filament.sale_percentage; const saleRefillPrice = saleActive ? Math.round(refillPrice * (1 - filament.sale_percentage! / 100)) : refillPrice; const saleSpoolPrice = saleActive ? Math.round(spoolPrice * (1 - filament.sale_percentage! / 100)) : spoolPrice; return ( <> {hasRefill && ( {refillPrice.toLocaleString('sr-RS')} )} {hasRefill && saleActive && ( {saleRefillPrice.toLocaleString('sr-RS')} )} {hasRefill && hasSpool && /} {hasSpool && ( {spoolPrice.toLocaleString('sr-RS')} )} {hasSpool && saleActive && ( {saleSpoolPrice.toLocaleString('sr-RS')} )} RSD {saleActive && ( -{filament.sale_percentage}% )} ); })()}
Prikazano {filteredAndSortedFilaments.length} filamenata
); }; export { FilamentTableV2 };