import React, { useState, useMemo } from 'react'; import { ExcelRow, COLUMNS } from '../types'; import { Search, ChevronDown, ChevronUp, X, Edit2, Save } from 'lucide-react'; import { cn } from '../lib/utils'; import { ColumnFilterPopover } from './ColumnFilterPopover'; interface MissingDataViewProps { data: ExcelRow[]; headers: string[]; onSaveRow: (rowIndex: number, updatedRow: ExcelRow) => void; onCaptureState: (message: string) => void; } function formatDateValue(val: any): string { if (val === null || val === undefined || val === '') return ''; if (typeof val === 'number') { if (val >= 25569 && val <= 60000) { const excelEpoch = new Date(1899, 11, 30); const date = new Date(excelEpoch.getTime() + val * 86400000); return date.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' }); } return ''; } return String(val); } function isEmptyOrEpoch(val: any): boolean { if (val === null || val === undefined || val === '') return true; if (typeof val === 'number') { if (val === 0 || val === 1) return true; if (val >= 25569 && val <= 60000) return false; return true; } const s = String(val).trim(); if (s === '' || s === '0' || s === '1') return true; if (s.endsWith('/1900')) return true; return false; } type TabType = 'missingLaunch'; interface EditingState { rowIndex: number; classification: string; launchDate: string; readyToOrder: string; } export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: MissingDataViewProps) { const [activeTab, setActiveTab] = useState('missingLaunch'); const [search, setSearch] = useState(''); const [sortCol, setSortCol] = useState(null); const [sortDesc, setSortDesc] = useState(false); const [page, setPage] = useState(1); const [editing, setEditing] = useState(null); const [columnFilters, setColumnFilters] = useState>({}); const [openFilter, setOpenFilter] = useState(null); const pageSize = 100; const launchDateCol = useMemo(() => headers.findIndex(h => h.toLowerCase().includes('launch')), [headers]); const readyToOrderCol = useMemo(() => headers.findIndex(h => h.toLowerCase().includes('ready')), [headers]); const launchHeader = launchDateCol >= 0 ? headers[launchDateCol] : 'Launch Date'; const readyHeader = readyToOrderCol >= 0 ? headers[readyToOrderCol] : 'Ready to Order'; const columns = [ { col: COLUMNS.ARTICLE_NO, label: 'SKU', width: 100 }, { col: COLUMNS.ARTICLE_NAME, label: 'Name', width: 220 }, { col: COLUMNS.CLASSIFICATION, label: 'Classification', width: 130 }, ...(launchDateCol >= 0 ? [{ col: launchDateCol, label: launchHeader, width: 130 }] : []), ...(readyToOrderCol >= 0 ? [{ col: readyToOrderCol, label: readyHeader, width: 130 }] : []), ]; const columnUniqueValues = useMemo(() => { const cols = columns.map(c => c.col); const result: Record> = {}; cols.forEach(col => result[col] = new Set()); data.forEach(row => { cols.forEach(col => { let val: any = row[col]; if (col === launchDateCol || col === readyToOrderCol) { val = formatDateValue(val) || String(val ?? ''); } else { val = String(val ?? ''); } if (val) result[col].add(val); }); }); return result; }, [data, columns, launchDateCol, readyToOrderCol]); const getUniqueValues = (col: number): string[] => { const values = columnUniqueValues[col]; if (!values) return []; return Array.from(values).sort() as string[]; }; const filteredData = useMemo(() => { let result = data.map((row, index) => ({ row, index })); if (activeTab === 'missingLaunch') { result = result.filter(r => { const val = launchDateCol >= 0 ? r.row[launchDateCol] : undefined; return isEmptyOrEpoch(val); }); } 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) ); } (Object.entries(columnFilters) as [string, string[]][]).forEach(([colIdx, filterValues]) => { if (!filterValues || filterValues.length === 0) return; const colIdxNum = parseInt(colIdx); result = result.filter(r => { const val: any = r.row[colIdxNum]; const displayVal = colIdxNum === launchDateCol || colIdxNum === readyToOrderCol ? formatDateValue(val) || String(val ?? '') : String(val ?? ''); return filterValues.includes(displayVal); }); }); 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, sortCol, sortDesc, launchDateCol, columnFilters]); 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 openEdit = (rowIndex: number, row: ExcelRow) => { setEditing({ rowIndex, classification: String(row[COLUMNS.CLASSIFICATION] || ''), launchDate: launchDateCol >= 0 ? formatDateValue(row[launchDateCol]) || String(row[launchDateCol] ?? '') : '', readyToOrder: readyToOrderCol >= 0 ? formatDateValue(row[readyToOrderCol]) || String(row[readyToOrderCol] ?? '') : '', }); }; const handleSave = () => { if (!editing) return; const row = data[editing.rowIndex]; onCaptureState(`Updated product ${row[COLUMNS.ARTICLE_NO]}`); const newRow = [...row]; newRow[COLUMNS.CLASSIFICATION] = editing.classification; if (launchDateCol >= 0) newRow[launchDateCol] = editing.launchDate; if (readyToOrderCol >= 0) newRow[readyToOrderCol] = editing.readyToOrder; onSaveRow(editing.rowIndex, newRow); setEditing(null); }; const tabs: { id: TabType; label: string }[] = [ { id: 'missingLaunch', label: 'Missing Launch Date' }, ]; const editingRow = editing ? data[editing.rowIndex] : null; return (
{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 && ( )}
{filteredData.length} items
{columns.map(({ col, label, width }) => { const selectedFilters = columnFilters[col] || []; const allValues = getUniqueValues(col); const filterCount = selectedFilters.length; return ( );})} {paginatedData.map(({ row, index }) => ( {launchDateCol >= 0 && ( )} {readyToOrderCol >= 0 && ( )} ))} {paginatedData.length === 0 && ( )}
handleSort(col)} > {label} {sortCol === col && ( sortDesc ? : )}
{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)} /> )}
{row[COLUMNS.ARTICLE_NO]} {row[COLUMNS.ARTICLE_NAME]} {row[COLUMNS.CLASSIFICATION] && String(row[COLUMNS.CLASSIFICATION]).trim() !== '' ? ( {row[COLUMNS.CLASSIFICATION]} ) : ( Empty )} {isEmptyOrEpoch(row[launchDateCol]) ? ( Empty ) : ( formatDateValue(row[launchDateCol]) || String(row[launchDateCol]) )} {row[readyToOrderCol] !== null && row[readyToOrderCol] !== undefined && row[readyToOrderCol] !== '' ? ( formatDateValue(row[readyToOrderCol]) || String(row[readyToOrderCol]) ) : ( )}
No items found.
Showing {paginatedData.length} of {filteredData.length} items
Page {page} of {totalPages || 1}
{/* Focused edit panel */} {editing && editingRow && ( <>
setEditing(null)} />

Edit Fields

{editingRow[COLUMNS.ARTICLE_NO]} — {editingRow[COLUMNS.ARTICLE_NAME]}

{launchDateCol >= 0 && (
setEditing(prev => prev ? { ...prev, launchDate: e.target.value } : prev)} placeholder="DD/MM/YYYY" className={cn( "w-full bg-slate-900 border rounded-md px-3 py-2.5 text-sm text-white font-mono focus:outline-none focus:ring-1 transition-colors", editing.launchDate !== (formatDateValue(editingRow[launchDateCol]) || String(editingRow[launchDateCol] ?? '')) ? "border-blue-500 focus:ring-blue-500" : "border-slate-700 focus:border-slate-500 focus:ring-slate-500" )} />
)} {readyToOrderCol >= 0 && (
setEditing(prev => prev ? { ...prev, readyToOrder: e.target.value } : prev)} placeholder="DD/MM/YYYY" className={cn( "w-full bg-slate-900 border rounded-md px-3 py-2.5 text-sm text-white font-mono focus:outline-none focus:ring-1 transition-colors", editing.readyToOrder !== (formatDateValue(editingRow[readyToOrderCol]) || String(editingRow[readyToOrderCol] ?? '')) ? "border-blue-500 focus:ring-blue-500" : "border-slate-700 focus:border-slate-500 focus:ring-slate-500" )} />
)}
)}
); }