feat: add Pending Validation tab showing all pending changes

This commit is contained in:
Christian Vidal Wolf
2026-04-09 09:24:17 +02:00
parent a5c8076e18
commit 899016f0a0
5 changed files with 491 additions and 5 deletions
+157
View File
@@ -0,0 +1,157 @@
import React, { useState } from 'react';
import { ExcelRow, COLUMNS } from '../types';
import { Clock, Undo2, Package, Box, DollarSign, FileText, Search, Filter } from 'lucide-react';
import { cn } from '../lib/utils';
interface PendingValidationViewProps {
data: ExcelRow[];
pendingRows: Record<string, { rowIndex: number; originalData: ExcelRow; newData: ExcelRow; articleName: string }>;
rowStatuses: Record<string, string>;
onRevertRow: (articleNo: string) => void;
onEdit: (index: number) => void;
}
export function PendingValidationView({ data, pendingRows, rowStatuses, onRevertRow, onEdit }: PendingValidationViewProps) {
const [search, setSearch] = useState('');
const pendingEntries = Object.entries(pendingRows);
const filteredEntries = search
? pendingEntries.filter(([articleNo, { articleName }]) =>
articleNo.toLowerCase().includes(search.toLowerCase()) ||
articleName.toLowerCase().includes(search.toLowerCase())
)
: pendingEntries;
const getFieldDiff = (original: ExcelRow, updated: ExcelRow, colIndex: number) => {
const orig = original[colIndex];
const upd = updated[colIndex];
if (orig !== upd) {
return { from: String(orig ?? ''), to: String(upd ?? '') };
}
return null;
};
const getChangedFields = (original: ExcelRow, updated: ExcelRow) => {
const changes: { field: string; from: string; to: string }[] = [];
const fieldConfigs = [
{ col: COLUMNS.DETAILS_EN, label: 'Details EN' },
{ col: COLUMNS.DETAILS_DE, label: 'Details DE' },
{ col: COLUMNS.INNER_L, label: 'Inner L' },
{ col: COLUMNS.INNER_W, label: 'Inner W' },
{ col: COLUMNS.INNER_H, label: 'Inner H' },
{ col: COLUMNS.OUTER_L, label: 'Outer L' },
{ col: COLUMNS.OUTER_W, label: 'Outer W' },
{ col: COLUMNS.OUTER_H, label: 'Outer H' },
{ col: COLUMNS.UNITS_INNER, label: 'Units Inner' },
{ col: COLUMNS.UNITS_OUTER, label: 'Units Outer' },
{ col: COLUMNS.MOQ, label: 'MOQ' },
{ col: COLUMNS.BARCODE, label: 'Barcode' },
{ col: COLUMNS.TARIFF_CODE, label: 'Tariff Code' },
{ col: COLUMNS.COUNTRY_ORIGIN, label: 'Country' },
{ col: COLUMNS.RECOMMENDED_AGE, label: 'Recommended Age' },
];
fieldConfigs.forEach(({ col, label }) => {
const diff = getFieldDiff(original, updated, col);
if (diff) {
changes.push({ field: label, ...diff });
}
});
return changes;
};
return (
<div className="flex-1 flex flex-col h-full overflow-hidden">
<div className="flex items-center justify-between p-4 border-b border-slate-700">
<div className="flex items-center gap-3">
<Clock className="w-5 h-5 text-yellow-500" />
<h2 className="text-lg font-semibold text-white">Pending Validation</h2>
<span className="text-sm text-slate-500">
{pendingEntries.length} change{pendingEntries.length !== 1 ? 's' : ''} pending
</span>
</div>
<div className="flex items-center gap-2">
<div className="relative">
<Search className="w-4 h-4 text-slate-500 absolute left-3 top-1/2 -translate-y-1/2" />
<input
type="text"
placeholder="Search by article no or name..."
value={search}
onChange={e => setSearch(e.target.value)}
className="bg-slate-800 border border-slate-700 rounded-md pl-9 pr-3 py-2 text-sm text-white placeholder:text-slate-500 focus:outline-none focus:border-blue-500 w-64"
/>
</div>
</div>
</div>
{filteredEntries.length === 0 ? (
<div className="flex-1 flex flex-col items-center justify-center text-slate-500">
<Clock className="w-16 h-16 mb-4 opacity-30" />
{search ? (
<p>No pending changes match your search</p>
) : (
<p>No pending validation changes</p>
)}
</div>
) : (
<div className="flex-1 overflow-y-auto p-4 space-y-3">
{filteredEntries.map(([articleNo, { rowIndex, originalData, newData, articleName }]) => {
const changes = getChangedFields(originalData, newData);
const status = rowStatuses[articleNo];
return (
<div
key={articleNo}
className="border border-yellow-500/30 bg-yellow-500/5 rounded-lg overflow-hidden"
>
<div className="flex items-center justify-between p-4 bg-yellow-500/10 border-b border-yellow-500/20">
<div className="flex items-center gap-4">
<div className="font-mono text-sm text-blue-400 bg-blue-400/10 px-2 py-1 rounded">
{articleNo}
</div>
<div className="text-sm text-slate-300">{articleName}</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => onEdit(rowIndex)}
className="flex items-center gap-1.5 px-3 py-1.5 bg-slate-700 hover:bg-slate-600 text-slate-300 rounded text-xs font-medium transition-colors"
>
<FileText className="w-3 h-3" />
View/Edit
</button>
<button
onClick={() => onRevertRow(articleNo)}
className="flex items-center gap-1.5 px-3 py-1.5 bg-red-900/30 hover:bg-red-900/50 text-red-400 rounded text-xs font-medium transition-colors"
>
<Undo2 className="w-3 h-3" />
Undo
</button>
</div>
</div>
<div className="p-4">
<div className="text-xs font-medium text-slate-500 uppercase tracking-wider mb-3">
Changes ({changes.length})
</div>
<div className="space-y-2">
{changes.map((change, idx) => (
<div key={idx} className="flex items-center gap-3 text-sm">
<span className="text-slate-400 w-28 shrink-0">{change.field}:</span>
<span className="text-red-400 line-through opacity-70">{change.from}</span>
<span className="text-slate-500"></span>
<span className="text-green-400 font-medium">{change.to}</span>
</div>
))}
</div>
</div>
</div>
);
})}
</div>
)}
</div>
);
}