import React, { useState, useMemo } from 'react'; import { ExcelRow } from '../types'; import { useColumns } from '../contexts/ColumnsContext'; import { Search, Filter, Edit2, ChevronDown, ChevronUp, Info, Package, X, Maximize2 } from 'lucide-react'; import { cn } from '../lib/utils'; import { ColumnFilterPopover } from './ColumnFilterPopover'; interface ArticleDetailsProps { data: ExcelRow[]; onEdit: (index: number) => void; rowStatuses: Record; } type TabType = 'all' | 'missingDetailsDE' | 'missingDetailsEN' | 'missingAnyDetails' | 'lowStock'; export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProps) { const COLUMNS = useColumns(); const [activeTab, setActiveTab] = useState('all'); const [search, setSearch] = useState(''); const [lineFilter, setLineFilter] = useState(''); const [sortCol, setSortCol] = useState(null); const [sortDesc, setSortDesc] = useState(false); const [pageSize, setPageSize] = useState(100); const [page, setPage] = useState(1); const [columnFilters, setColumnFilters] = useState>({}); const [openFilterCol, setOpenFilterCol] = useState(null); const [isFullscreen, setIsFullscreen] = useState(false); const [columnWidths, setColumnWidths] = useState>({ [COLUMNS.ARTICLE_NO]: 100, [COLUMNS.ARTICLE_NAME]: 200, [COLUMNS.CLASSIFICATION]: 80, [COLUMNS.ITEM_AVAILABLE]: 80, [COLUMNS.DETAILS_DE]: 150, [COLUMNS.DETAILS_EN]: 150, }); const lines = useMemo(() => Array.from(new Set(data.map(r => r[COLUMNS.LINE]).filter(Boolean))), [data]); const filteredData = useMemo(() => { let result = data.map((row, index) => ({ row, index })); // Tab filter if (activeTab === 'missingDetailsDE') result = result.filter(r => !r.row[COLUMNS.DETAILS_DE]); if (activeTab === 'missingDetailsEN') result = result.filter(r => !r.row[COLUMNS.DETAILS_EN]); if (activeTab === 'missingAnyDetails') result = result.filter(r => !r.row[COLUMNS.DETAILS_DE] || !r.row[COLUMNS.DETAILS_EN]); if (activeTab === 'lowStock') result = result.filter(r => Number(r.row[COLUMNS.ITEM_AVAILABLE] || 0) <= 0); // Search filter if (search) { const s = search.toLowerCase(); result = result.filter(r => String(r.row[COLUMNS.ARTICLE_NO] || '').toLowerCase().includes(s) || String(r.row[COLUMNS.ARTICLE_NAME] || '').toLowerCase().includes(s) ); } // Dropdown filters if (lineFilter) result = result.filter(r => r.row[COLUMNS.LINE] === lineFilter); // Column-specific filters (Excel-like) Object.entries(columnFilters).forEach(([colIdx, selectedValues]) => { const vals = selectedValues as string[]; if (vals.length > 0) { result = result.filter(r => vals.includes(String(r.row[Number(colIdx)] || ''))); } }); // Sorting if (sortCol !== null) { result.sort((a, b) => { const valA = a.row[sortCol]; const valB = b.row[sortCol]; if (typeof valA === 'number' && typeof valB === 'number') { return sortDesc ? valB - valA : valA - valB; } const sA = String(valA || ''); const sB = String(valB || ''); return sortDesc ? sB.localeCompare(sA) : sA.localeCompare(sB); }); } return result; }, [data, activeTab, search, lineFilter, columnFilters, sortCol, sortDesc]); const paginatedData = useMemo(() => { const start = (page - 1) * pageSize; return filteredData.slice(start, start + pageSize); }, [filteredData, page, pageSize]); const totalPages = Math.ceil(filteredData.length / pageSize); const handleSort = (col: number) => { if (sortCol === col) { setSortDesc(!sortDesc); } else { setSortCol(col); setSortDesc(false); } }; const handleResize = (colIndex: number, e: React.MouseEvent) => { e.preventDefault(); const startX = e.pageX; const startWidth = columnWidths[colIndex] || 100; const onMouseMove = (moveEvent: MouseEvent) => { const newWidth = Math.max(60, startWidth + (moveEvent.pageX - startX)); setColumnWidths(prev => ({ ...prev, [colIndex]: newWidth })); }; const onMouseUp = () => { window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('mouseup', onMouseUp); }; window.addEventListener('mousemove', onMouseMove); window.addEventListener('mouseup', onMouseUp); }; const getUniqueValues = (col: number) => { const values = data.map(r => String(r[col] || '')); return Array.from(new Set(values)).sort(); }; const toggleColumnFilter = (col: number, value: string) => { setColumnFilters(prev => { const current = prev[col] || []; const next = current.includes(value) ? current.filter(v => v !== value) : [...current, value]; return { ...prev, [col]: next }; }); setPage(1); }; const setBatchColumnFilter = (col: number, values: string[]) => { setColumnFilters(prev => ({ ...prev, [col]: values })); setPage(1); }; const getBadge = (val: any, type: 'success' | 'warning' | 'error' | 'info' = 'info') => { if (!val) return Empty; const styles = { success: "bg-green-500/10 text-green-400 border-green-500/20", warning: "bg-yellow-500/10 text-yellow-400 border-yellow-500/20", error: "bg-red-500/10 text-red-400 border-red-500/20", info: "bg-blue-500/10 text-blue-400 border-blue-500/20" }; return {val}; }; const tabs: { id: TabType; label: string }[] = [ { id: 'all', label: 'All Articles' }, { id: 'missingDetailsDE', label: 'No Details DE' }, { id: 'missingDetailsEN', label: 'No Details EN' }, { id: 'missingAnyDetails', label: 'Missing Details' }, { id: 'lowStock', label: 'Out of Stock' }, ]; return (
{isFullscreen && ( )}
{tabs.map(tab => ( ))}
{ setSearch(e.target.value); setPage(1); }} className="w-full pl-9 pr-10 py-2 bg-slate-900/50 border border-slate-700 rounded-md text-sm text-white focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500" /> {search && ( )}
{(Object.values(columnFilters) as string[][]).some(v => v.length > 0) && ( )}
{[ { col: COLUMNS.ARTICLE_NO, label: 'SKU' }, { col: COLUMNS.ARTICLE_NAME, label: 'Name' }, { col: COLUMNS.CLASSIFICATION, label: 'Class' }, { col: COLUMNS.ITEM_AVAILABLE, label: 'Stock' }, { col: COLUMNS.DETAILS_DE, label: 'Details DE' }, { col: COLUMNS.DETAILS_EN, label: 'Details EN' }, ].map(({ col, label }) => ( ))} {paginatedData.map(({ row, index }) => { const saveStatus = rowStatuses[String(row[COLUMNS.ARTICLE_NO])]; return ( ); })} {paginatedData.length === 0 && ( )}
handleSort(col)}> {label} {sortCol === col && ( sortDesc ? : )}
{/* Resizer handle */}
handleResize(col, e)} className="absolute right-0 top-0 bottom-0 w-1 cursor-col-resize hover:bg-indigo-500/50 group-hover:bg-slate-700/50 transition-colors z-20" /> {openFilterCol === col && ( toggleColumnFilter(col, val)} onSelectAll={(vals) => setBatchColumnFilter(col, vals)} onClear={() => { setColumnFilters(prev => { const next = { ...prev }; delete next[col]; return next; }); setOpenFilterCol(null); }} onClose={() => setOpenFilterCol(null)} /> )}
Edit
{row[COLUMNS.ARTICLE_NO]} {row[COLUMNS.ARTICLE_NAME]} {row[COLUMNS.CLASSIFICATION] || '—'} {row[COLUMNS.ITEM_AVAILABLE] || 0} {row[COLUMNS.DETAILS_DE] ? (
{row[COLUMNS.DETAILS_DE]}
) : getBadge(null)}
{row[COLUMNS.DETAILS_EN] ? (
{row[COLUMNS.DETAILS_EN]}
) : getBadge(null)}
No articles found.
Showing {paginatedData.length} of {filteredData.length} articles
Page {page} of {totalPages || 1}
); }