import React, { useState, useMemo } from 'react'; import { ExcelRow } from '../types'; import { useColumns } from '../contexts/ColumnsContext'; import { Search, ChevronDown, ChevronUp, X, Save, Check } from 'lucide-react'; import { cn } from '../lib/utils'; import { ColumnFilterPopover } from './ColumnFilterPopover'; import { usePersistentState } from '../contexts/FilterContext'; const COSMETIC_LINES = ['INKEE', 'BATH FUN', 'TOP FASHION', 'SENSES', 'BODYNESS']; interface CosmeticItemsViewProps { data: ExcelRow[]; headers: string[]; onSaveRow: (rowIndex: number, updatedRow: ExcelRow) => void; onCaptureState: (message: string) => void; rowStatuses: Record; } export function CosmeticItemsView({ data, headers, onSaveRow, onCaptureState, rowStatuses }: CosmeticItemsViewProps) { const COLUMNS = useColumns(); const [search, setSearch] = usePersistentState('cosmeticItems-search', ''); const [sortCol, setSortCol] = usePersistentState('cosmeticItems-sortCol', null); const [sortDesc, setSortDesc] = usePersistentState('cosmeticItems-sortDesc', false); const [page, setPage] = useState(1); const [columnFilters, setColumnFilters] = usePersistentState>('cosmeticItems-columnFilters', {}); const [openFilter, setOpenFilter] = useState(null); const [editingCpnp, setEditingCpnp] = useState<{ rowIndex: number; value: string } | null>(null); const pageSize = 100; const columns = [ { col: COLUMNS.ARTICLE_NO, label: 'SKU', width: 110 }, { col: COLUMNS.ARTICLE_NAME, label: 'Name', width: 240 }, { col: COLUMNS.LINE, label: 'Line', width: 120 }, { col: COLUMNS.ITEM_AVAILABLE, label: 'Item Available', width: 120 }, { col: COLUMNS.CPNP_NO, label: 'CPNP No.', width: 160 }, ]; const columnUniqueValues = useMemo(() => { const result: Record> = {}; columns.forEach(c => { result[c.col] = new Set(); }); data.forEach(row => { const lineVal = String(row[COLUMNS.LINE] ?? '').trim().toUpperCase(); if (!COSMETIC_LINES.some(l => lineVal === l)) return; columns.forEach(({ col }) => { result[col].add(String(row[col] ?? '').replace(/\s+/g, ' ').trim()); }); }); return result; }, [data, COLUMNS]); const getUniqueValues = (col: number): string[] => Array.from(columnUniqueValues[col] as Set ?? []).sort((a, b) => { const na = parseFloat(a), nb = parseFloat(b); if (!isNaN(na) && !isNaN(nb)) return na - nb; return a.localeCompare(b); }); const filteredData = useMemo(() => { let result = data.map((row, index) => ({ row, index })); // Filter: only cosmetic lines result = result.filter(({ row }) => { const lineVal = String(row[COLUMNS.LINE] ?? '').trim().toUpperCase(); return COSMETIC_LINES.some(l => lineVal === l); }); // Global search if (search) { const terms = search.toLowerCase().split(/\s+/).filter(Boolean); result = result.filter(({ row }) => { const articleNo = String(row[COLUMNS.ARTICLE_NO] || '').toLowerCase(); const articleName = String(row[COLUMNS.ARTICLE_NAME] || '').toLowerCase(); return terms.every(t => articleNo.includes(t) || articleName.includes(t)); }); } // Column filters (Object.entries(columnFilters) as [string, string[]][]).forEach(([colIdx, filterValues]) => { if (!filterValues || filterValues.length === 0) return; const colNum = parseInt(colIdx); const knownCount = columnUniqueValues[colNum]?.size ?? 0; // All known values selected → no filtering needed if (filterValues.length >= knownCount) return; const normalize = (v: any) => String(v ?? '').replace(/\s+/g, ' ').trim(); const filterSet = new Set(filterValues.map(normalize)); result = result.filter(({ row }) => filterSet.has(normalize(row[colNum]))); }); // Sort 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, search, sortCol, sortDesc, columnFilters, COLUMNS, columnUniqueValues]); const paginatedData = useMemo(() => { const start = (page - 1) * pageSize; return filteredData.slice(start, start + pageSize); }, [filteredData, page]); const totalPages = Math.ceil(filteredData.length / pageSize); const handleSort = (col: number) => { if (sortCol === col) setSortDesc(d => !d); else { setSortCol(col); setSortDesc(false); } }; const startEditCpnp = (rowIndex: number, currentValue: any) => { setEditingCpnp({ rowIndex, value: String(currentValue ?? '') }); }; const saveCpnp = (rowIndex: number) => { if (!editingCpnp || editingCpnp.rowIndex !== rowIndex) return; const row = data[rowIndex]; onCaptureState(`Updated CPNP No. for ${row[COLUMNS.ARTICLE_NO]}`); const newRow = [...row]; newRow[COLUMNS.CPNP_NO] = editingCpnp.value.trim(); onSaveRow(rowIndex, newRow); setEditingCpnp(null); }; const cancelEditCpnp = () => setEditingCpnp(null); const lineBadgeColor = (line: string) => { const l = String(line).toUpperCase(); if (l === 'INKEE') return 'bg-pink-500/10 text-pink-400 border-pink-500/20'; if (l === 'BATH FUN') return 'bg-cyan-500/10 text-cyan-400 border-cyan-500/20'; if (l === 'TOP FASHION') return 'bg-purple-500/10 text-purple-400 border-purple-500/20'; if (l === 'SENSES') return 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20'; return 'bg-slate-700/50 text-slate-400 border-slate-600/50'; }; return (
{ 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 && ( )}
{filteredData.length} items · Lines: {COSMETIC_LINES.join(', ')}
{columns.map(({ col, label, width }) => { const selectedFilters = columnFilters[col] || []; const filterCount = selectedFilters.length; const isCpnp = col === COLUMNS.CPNP_NO; return ( ); })} {paginatedData.map(({ row, index }) => { const articleNo = String(row[COLUMNS.ARTICLE_NO] ?? ''); const status = rowStatuses[articleNo]; const isEditing = editingCpnp?.rowIndex === index; return ( ); })} {paginatedData.length === 0 && ( )}
handleSort(col)} > {label} {sortCol === col && ( sortDesc ? : )} {isCpnp && ( (editable) )}
{openFilter === col && ( setColumnFilters(prev => { const current = prev[col] || []; if (current.includes(val)) { return { ...prev, [col]: current.filter(v => v !== val) }; } return { ...prev, [col]: [...current, val] }; })} onSelectAll={(vals) => setColumnFilters(prev => ({ ...prev, [col]: vals }))} onClear={() => { setColumnFilters(prev => { const n = { ...prev }; delete n[col]; return n; }); }} onClose={() => setOpenFilter(null)} /> )}
{articleNo} {row[COLUMNS.ARTICLE_NAME]} {row[COLUMNS.LINE]} {row[COLUMNS.ITEM_AVAILABLE]} {isEditing ? (
setEditingCpnp(prev => prev ? { ...prev, value: e.target.value } : prev)} onKeyDown={e => { if (e.key === 'Enter') saveCpnp(index); if (e.key === 'Escape') cancelEditCpnp(); }} className="flex-1 min-w-0 bg-slate-900 border border-indigo-500 rounded px-2 py-1 text-xs text-white focus:outline-none focus:ring-1 focus:ring-indigo-500" placeholder="Enter CPNP No." />
) : (() => { const cpnpVal = String(row[COLUMNS.CPNP_NO] ?? '').trim(); return cpnpVal ? ( ) : ( ); })()}
No cosmetic items pending CPNP registration.
Showing {paginatedData.length} of {filteredData.length} items
Page {page} of {totalPages || 1}
); }