mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 19:35:24 +02:00
feat: add Pending Validation tab showing all pending changes
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user