feat: reorganize tabs, add Article Details, and enhance filters/search

This commit is contained in:
Christian Vidal Wolf
2026-04-08 16:10:14 +02:00
parent 935cd5b510
commit 24042a6aed
7 changed files with 397 additions and 61 deletions
+60 -31
View File
@@ -43,6 +43,7 @@ function findCol(headers: string[], ...keywords: string[]): number {
export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit }: PricingViewProps) {
const [filterMode, setFilterMode] = useState<FilterMode>('all_errors');
const [search, setSearch] = useState('');
const [editingCell, setEditingCell] = useState<EditingCell | null>(null);
const [savingCell, setSavingCell] = useState<{ rowIndex: number; colIndex: number } | null>(null);
const inputRef = useRef<HTMLInputElement>(null);
@@ -129,13 +130,26 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit }
// ── Filtered rows ─────────────────────────────────────────────────────────
const filteredRows = useMemo(() => {
let result = analyzedRows;
// Mode filter
switch (filterMode) {
case 'all_errors': return analyzedRows.filter(r => r.hasErrors);
case 'pricing_errors': return analyzedRows.filter(r => r.pricingErrors.length > 0);
case 'units_errors': return analyzedRows.filter(r => r.unitErrors.length > 0);
default: return analyzedRows;
case 'all_errors': result = analyzedRows.filter(r => r.hasErrors); break;
case 'pricing_errors': result = analyzedRows.filter(r => r.pricingErrors.length > 0); break;
case 'units_errors': result = analyzedRows.filter(r => r.unitErrors.length > 0); break;
}
}, [analyzedRows, filterMode]);
// Search filter
if (search) {
const s = search.toLowerCase();
result = result.filter(r =>
String(r.row[COLUMNS.ARTICLE_NO] || '').toLowerCase().includes(s) ||
String(r.row[COLUMNS.ARTICLE_NAME] || '').toLowerCase().includes(s)
);
}
return result;
}, [analyzedRows, filterMode, search]);
// ── Inline edit helpers ───────────────────────────────────────────────────
const startEdit = (rowIndex: number, colIndex: number, currentValue: string) => {
@@ -228,6 +242,47 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit }
return (
<div className="flex flex-col h-full gap-4">
<div className="flex items-center justify-between gap-4">
{/* ── Search bar ── */}
<div className="flex-1 max-w-md relative">
<input
type="text"
placeholder="Search SKU or Name..."
value={search}
onChange={e => setSearch(e.target.value)}
className="w-full pl-4 pr-10 py-2 bg-slate-800 border border-slate-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-all"
/>
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-500">
<Package className="w-4 h-4" />
</div>
</div>
{/* ── Filter tabs ── */}
<div className="flex items-center gap-1 bg-slate-800/60 rounded-lg p-1 border border-slate-700/50 w-fit">
{FILTERS.map(f => (
<button
key={f.id}
onClick={() => setFilterMode(f.id)}
className={cn(
'flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-colors',
filterMode === f.id
? 'bg-slate-700 text-white shadow-sm'
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-700/40'
)}
>
{f.label}
<span className={cn(
'text-xs font-bold px-1.5 py-0.5 rounded-full min-w-[22px] text-center',
filterMode === f.id
? 'bg-slate-600 text-white'
: f.count > 0 ? `${f.color} bg-current/10` : 'text-slate-500'
)}>
{f.count}
</span>
</button>
))}
</div>
</div>
{/* ── Column detection warning ── */}
{missingCols.length > 0 && (
@@ -248,32 +303,6 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit }
<StatCard label="All OK" value={stats.allOk} icon={<CheckCircle2 className="w-4 h-4" />} color="emerald" />
</div>
{/* ── Filter tabs ── */}
<div className="flex items-center gap-1 bg-slate-800/60 rounded-lg p-1 border border-slate-700/50 w-fit">
{FILTERS.map(f => (
<button
key={f.id}
onClick={() => setFilterMode(f.id)}
className={cn(
'flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-colors',
filterMode === f.id
? 'bg-slate-700 text-white shadow-sm'
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-700/40'
)}
>
{f.label}
<span className={cn(
'text-xs font-bold px-1.5 py-0.5 rounded-full min-w-[22px] text-center',
filterMode === f.id
? 'bg-slate-600 text-white'
: f.count > 0 ? `${f.color} bg-current/10` : 'text-slate-500'
)}>
{f.count}
</span>
</button>
))}
</div>
{/* ── Table ── */}
<div className="flex-1 overflow-auto bg-slate-800 rounded-xl border border-slate-700 shadow-xl">
{filteredRows.length === 0 ? (