mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 16:55:24 +02:00
158 lines
6.6 KiB
TypeScript
158 lines
6.6 KiB
TypeScript
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>
|
||
|
|
);
|
||
|
|
}
|