mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 13:35:25 +02:00
feat: reorganize tabs, add Article Details, and enhance filters/search
This commit is contained in:
@@ -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 ? (
|
||||
|
||||
Reference in New Issue
Block a user