mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 12:25:23 +02:00
feat: replace global EditPanel with focused 3-field panel in Missing Data
When editing from Missing Data view, only Classification, Launch Date and Ready to Order fields are shown in a lightweight slide-in panel. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
ce9e91bad1
commit
39869edcef
+2
-1
@@ -566,7 +566,8 @@ export default function App() {
|
||||
<MissingDataView
|
||||
data={appState.data}
|
||||
headers={appState.headers}
|
||||
onEdit={(index) => setEditingRowIndex(index)}
|
||||
onSaveRow={handleSaveRow}
|
||||
onCaptureState={captureState}
|
||||
/>
|
||||
)}
|
||||
{activeModule === 'history' && (
|
||||
|
||||
@@ -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<TabType>('missingClass');
|
||||
const [search, setSearch] = useState('');
|
||||
const [sortCol, setSortCol] = useState<number | null>(null);
|
||||
const [sortDesc, setSortDesc] = useState(false);
|
||||
const [page, setPage] = useState(1);
|
||||
const [editing, setEditing] = useState<EditingState | null>(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 (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex flex-wrap gap-2 mb-6">
|
||||
@@ -232,7 +260,7 @@ export function MissingDataView({ data, headers, onEdit }: MissingDataViewProps)
|
||||
)}
|
||||
<td className="px-3 py-2 text-right" style={{ width: 60 }}>
|
||||
<button
|
||||
onClick={() => onEdit(index)}
|
||||
onClick={() => openEdit(index, row)}
|
||||
className="p-1.5 text-slate-500 hover:text-indigo-400 hover:bg-indigo-400/10 rounded transition-colors"
|
||||
>
|
||||
<Edit2 className="w-4 h-4" />
|
||||
@@ -242,7 +270,7 @@ export function MissingDataView({ data, headers, onEdit }: MissingDataViewProps)
|
||||
))}
|
||||
{paginatedData.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={columns.length} className="px-4 py-8 text-center text-slate-500">
|
||||
<td colSpan={columns.length + 1} className="px-4 py-8 text-center text-slate-500">
|
||||
No items found.
|
||||
</td>
|
||||
</tr>
|
||||
@@ -272,6 +300,99 @@ export function MissingDataView({ data, headers, onEdit }: MissingDataViewProps)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Focused edit panel */}
|
||||
{editing && editingRow && (
|
||||
<>
|
||||
<div className="fixed inset-0 bg-slate-950/50 backdrop-blur-sm z-40" onClick={() => setEditing(null)} />
|
||||
<div className="fixed right-0 top-0 bottom-0 w-[400px] bg-slate-800 border-l border-slate-700 shadow-2xl z-50 flex flex-col animate-in slide-in-from-right duration-200">
|
||||
<div className="flex items-center justify-between p-6 border-b border-slate-700 bg-slate-800/50">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-white">Edit Fields</h2>
|
||||
<p className="text-xs text-slate-400 mt-0.5">
|
||||
{editingRow[COLUMNS.ARTICLE_NO]} — {editingRow[COLUMNS.ARTICLE_NAME]}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setEditing(null)}
|
||||
className="p-2 text-slate-400 hover:text-white hover:bg-slate-700 rounded-full transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-auto p-6 space-y-5">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-medium text-slate-400 uppercase tracking-wider">Classification</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editing.classification}
|
||||
onChange={e => 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"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{launchDateCol >= 0 && (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-medium text-slate-400 uppercase tracking-wider">{launchHeader}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editing.launchDate}
|
||||
onChange={e => 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"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{readyToOrderCol >= 0 && (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-medium text-slate-400 uppercase tracking-wider">{readyHeader}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editing.readyToOrder}
|
||||
onChange={e => 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"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-6 border-t border-slate-700 bg-slate-800/50 flex justify-end gap-3">
|
||||
<button
|
||||
onClick={() => setEditing(null)}
|
||||
className="px-4 py-2 text-sm font-medium text-slate-300 hover:text-white hover:bg-slate-700 rounded-md transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-md shadow-lg shadow-blue-900/20 transition-colors"
|
||||
>
|
||||
<Save className="w-4 h-4" />
|
||||
Queue Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user