diff --git a/src/App.tsx b/src/App.tsx index f074ee6..4c34031 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -566,7 +566,8 @@ export default function App() { setEditingRowIndex(index)} + onSaveRow={handleSaveRow} + onCaptureState={captureState} /> )} {activeModule === 'history' && ( diff --git a/src/components/MissingDataView.tsx b/src/components/MissingDataView.tsx index 009ba25..de70539 100644 --- a/src/components/MissingDataView.tsx +++ b/src/components/MissingDataView.tsx @@ -1,12 +1,13 @@ import React, { useState, useMemo } from 'react'; import { ExcelRow, COLUMNS } from '../types'; -import { Search, ChevronDown, ChevronUp, X, Edit2 } from 'lucide-react'; +import { Search, ChevronDown, ChevronUp, X, Edit2, Save } from 'lucide-react'; import { cn } from '../lib/utils'; interface MissingDataViewProps { data: ExcelRow[]; headers: string[]; - onEdit: (index: number) => void; + onSaveRow: (rowIndex: number, updatedRow: ExcelRow) => void; + onCaptureState: (message: string) => void; } function formatDateValue(val: any): string { @@ -26,11 +27,7 @@ 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) { - // valid date range — not empty - return false; - } - // number outside date range — treat as empty + if (val >= 25569 && val <= 60000) return false; return true; } const s = String(val).trim(); @@ -41,12 +38,20 @@ function isEmptyOrEpoch(val: any): boolean { type TabType = 'missingClass' | 'missingLaunch'; -export function MissingDataView({ data, headers, onEdit }: MissingDataViewProps) { +interface EditingState { + rowIndex: number; + classification: string; + launchDate: string; + readyToOrder: string; +} + +export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: MissingDataViewProps) { const [activeTab, setActiveTab] = useState('missingClass'); 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 pageSize = 100; const launchDateCol = useMemo(() => @@ -54,6 +59,9 @@ export function MissingDataView({ data, headers, onEdit }: MissingDataViewProps) 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 filteredData = useMemo(() => { let result = data.map((row, index) => ({ row, index })); @@ -107,14 +115,32 @@ export function MissingDataView({ data, headers, onEdit }: MissingDataViewProps) 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: 'missingClass', label: 'Missing Classification' }, { id: 'missingLaunch', label: 'Missing Launch Date' }, ]; - 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 }, @@ -123,6 +149,8 @@ export function MissingDataView({ data, headers, onEdit }: MissingDataViewProps) ...(readyToOrderCol >= 0 ? [{ col: readyToOrderCol, label: readyHeader, width: 130 }] : []), ]; + const editingRow = editing ? data[editing.rowIndex] : null; + return (
@@ -232,7 +260,7 @@ export function MissingDataView({ data, headers, onEdit }: MissingDataViewProps) )}
+ + {/* Focused edit panel */} + {editing && editingRow && ( + <> +
setEditing(null)} /> +
+
+
+

Edit Fields

+

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

+
+ +
+ +
+
+ + setEditing(prev => prev ? { ...prev, classification: e.target.value } : prev)} + placeholder="e.g. CORE, OOC..." + className={cn( + "w-full bg-slate-900 border rounded-md px-3 py-2.5 text-sm text-white focus:outline-none focus:ring-1 transition-colors", + editing.classification !== String(editingRow[COLUMNS.CLASSIFICATION] || '') + ? "border-blue-500 focus:ring-blue-500" + : "border-slate-700 focus:border-slate-500 focus:ring-slate-500" + )} + /> +
+ + {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" + )} + /> +
+ )} +
+ +
+ + +
+
+ + )}
); }