mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 17:55:25 +02:00
feat: add MOQ filtering and sorting in PricingView
This commit is contained in:
@@ -101,6 +101,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
const [outerWFilter, setOuterWFilter] = usePersistentState<string[]>('pricing-outerWFilter', []);
|
||||
const [outerLFilter, setOuterLFilter] = usePersistentState<string[]>('pricing-outerLFilter', []);
|
||||
const [outerHFilter, setOuterHFilter] = usePersistentState<string[]>('pricing-outerHFilter', []);
|
||||
const [moqFilter, setMoqFilter] = usePersistentState<string[]>('pricing-moqFilter', []);
|
||||
const [checkYingFilter, setCheckYingFilter] = usePersistentState<string[]>('pricing-checkYingFilter', []);
|
||||
const [checkAnnaFilter, setCheckAnnaFilter] = usePersistentState<string[]>('pricing-checkAnnaFilter', []);
|
||||
const [dynamicColFilters, setDynamicColFilters] = usePersistentState<Record<number, string[]>>('pricing-dynamicColFilters', {});
|
||||
@@ -368,6 +369,10 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
Array.from(new Set(data.map(r => String(r[COLUMNS.OUTER_H] || '')))).filter(v => v).sort((a, b) => Number(a) - Number(b)),
|
||||
[data]);
|
||||
|
||||
const uniqueMoq = useMemo(() =>
|
||||
Array.from(new Set(data.map(r => String(r[COLUMNS.MOQ] || '')))).filter(v => v).sort((a, b) => Number(a) - Number(b)),
|
||||
[data]);
|
||||
|
||||
const uniqueItemToLogistic = useMemo(() =>
|
||||
Array.from(new Set(data.map(r => String(r[COLUMNS.ITEM_TO_LOGISTIC] || '')))).filter(v => v).sort(),
|
||||
[data]);
|
||||
@@ -472,6 +477,9 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
if ((outerHFilter || []).length > 0) {
|
||||
result = result.filter(r => (outerHFilter || []).includes(String(r.row[COLUMNS.OUTER_H] || '')));
|
||||
}
|
||||
if ((moqFilter || []).length > 0) {
|
||||
result = result.filter(r => (moqFilter || []).includes(String(r.row[COLUMNS.MOQ] || '')));
|
||||
}
|
||||
if ((itemToLogisticFilter || []).length > 0) {
|
||||
result = result.filter(r => (itemToLogisticFilter || []).includes(String(r.row[COLUMNS.ITEM_TO_LOGISTIC] || '')));
|
||||
}
|
||||
@@ -499,7 +507,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [analyzedRows, filterMode, search, nameColFilter, articleNoColFilter, globalAdvancedFilter, lineMultiFilter, classificationFilter, productTypeFilter, unitsOuterFilter, outerWFilter, outerLFilter, outerHFilter, dynamicColFilters, weightIssueFilter, selectedSearchItems]);
|
||||
}, [analyzedRows, filterMode, search, nameColFilter, articleNoColFilter, globalAdvancedFilter, lineMultiFilter, classificationFilter, productTypeFilter, unitsOuterFilter, outerWFilter, outerLFilter, outerHFilter, moqFilter, dynamicColFilters, weightIssueFilter, selectedSearchItems]);
|
||||
|
||||
// ── Sorted rows ──────────────────────────────────────────────────────────
|
||||
const sortedRows = useMemo(() => {
|
||||
@@ -522,6 +530,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
case 'outerW': colIndex = COLUMNS.OUTER_W; break;
|
||||
case 'outerL': colIndex = COLUMNS.OUTER_L; break;
|
||||
case 'outerH': colIndex = COLUMNS.OUTER_H; break;
|
||||
case 'moq': colIndex = COLUMNS.MOQ; break;
|
||||
case 'itemToLogistic': colIndex = COLUMNS.ITEM_TO_LOGISTIC; break;
|
||||
default: return filteredRows;
|
||||
}
|
||||
@@ -565,7 +574,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
}, [sortedRows, currentPage]);
|
||||
|
||||
// Keep ref in sync so drag handlers can read current visible indexes without stale closure
|
||||
visibleDataIndexesRef.current = paginatedRows.map(r => r.dataIndex);
|
||||
visibleDataIndexesRef.current = paginatedRows.map((r: { dataIndex: number }) => r.dataIndex);
|
||||
|
||||
const totalPages = Math.ceil(sortedRows.length / pageSize);
|
||||
|
||||
@@ -808,6 +817,11 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
COLUMNS.OUTER_W,
|
||||
COLUMNS.OUTER_L,
|
||||
COLUMNS.OUTER_H,
|
||||
COLUMNS.VALIDATED_CHECK,
|
||||
COLUMNS.VALIDATED_NOTE,
|
||||
COLUMNS.ANNA_CHECK,
|
||||
COLUMNS.ANNA_NOTE,
|
||||
COLUMNS.MOQ,
|
||||
]);
|
||||
if (uvpIdx >= 0) set.add(uvpIdx);
|
||||
srpCols.forEach(c => set.add(c.index));
|
||||
@@ -1748,7 +1762,29 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
<span className="truncate flex items-center gap-1">
|
||||
{isPinned('moq') && <Pin className="w-3 h-3 text-blue-400 shrink-0" />}
|
||||
MOQ <SortIcon current={sortConfig.key === 'moq' ? sortConfig.direction : null} /></span>
|
||||
<button
|
||||
id="filter-trigger-moq"
|
||||
onClick={(e) => { e.stopPropagation(); setOpenFilter(openFilter === 'moq' ? null : 'moq'); }}
|
||||
className={cn(
|
||||
'ml-1 p-0.5 rounded transition-colors filter-trigger-btn',
|
||||
(moqFilter || []).length > 0 ? 'text-blue-400 bg-blue-400/10' : 'text-slate-500 opacity-0 group-hover:opacity-100'
|
||||
)}
|
||||
>
|
||||
<Filter className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
{openFilter === 'moq' && (
|
||||
<ColumnFilterPopover
|
||||
triggerId="filter-trigger-moq"
|
||||
uniqueValues={uniqueMoq || []}
|
||||
selectedValues={moqFilter || []}
|
||||
onToggle={val => setMoqFilter(prev => (prev || []).includes(val) ? (prev || []).filter(v => v !== val) : [...(prev || []), val])}
|
||||
onSelectAll={vals => setMoqFilter(vals || [])}
|
||||
onClear={() => { setMoqFilter([]); setOpenFilter(null); }}
|
||||
onClose={() => setOpenFilter(null)}
|
||||
zIndex={isPinned('moq') ? 200 : 50}
|
||||
/>
|
||||
)}
|
||||
<ResizeHandle onMouseDown={e => { e.stopPropagation(); handleResizeStart(e, 'moq', columnWidths.moq); }} />
|
||||
</th>
|
||||
|
||||
@@ -1863,22 +1899,29 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
const isValidated = !!row[COLUMNS.VALIDATED_CHECK];
|
||||
const note = String(row[COLUMNS.VALIDATED_NOTE] || '');
|
||||
|
||||
const isRowSelected = selectedRows.has(dataIndex);
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={dataIndex}
|
||||
onMouseDown={(e: React.MouseEvent<HTMLTableRowElement>) => handleRowMouseDown(e, dataIndex)}
|
||||
onMouseEnter={() => handleRowMouseEnter(dataIndex)}
|
||||
style={{ userSelect: 'none' }}
|
||||
className={cn(
|
||||
'border-b border-slate-700/50 transition-colors group relative hover:z-50',
|
||||
isValidated
|
||||
? 'bg-green-400/30 border-l-4 border-l-green-400' // Vibrant high-visibility green
|
||||
: saveStatus === 'error'
|
||||
? 'bg-red-400/20 border-l-4 border-l-red-500'
|
||||
: saveStatus === 'pending'
|
||||
? 'bg-amber-400/20 border-l-4 border-l-amber-400'
|
||||
: isCritical
|
||||
? 'bg-red-950/20'
|
||||
: pricingErrors.length > 0
|
||||
? 'bg-amber-950/10'
|
||||
: ''
|
||||
'border-b border-slate-700/50 transition-colors group relative hover:z-50 cursor-default',
|
||||
isRowSelected
|
||||
? 'bg-blue-500/20 border-l-4 border-l-blue-400'
|
||||
: isValidated
|
||||
? 'bg-green-400/30 border-l-4 border-l-green-400'
|
||||
: saveStatus === 'error'
|
||||
? 'bg-red-400/20 border-l-4 border-l-red-500'
|
||||
: saveStatus === 'pending'
|
||||
? 'bg-amber-400/20 border-l-4 border-l-amber-400'
|
||||
: isCritical
|
||||
? 'bg-red-950/20'
|
||||
: pricingErrors.length > 0
|
||||
? 'bg-amber-950/10'
|
||||
: ''
|
||||
)}
|
||||
>
|
||||
{/* Art. No. */}
|
||||
@@ -2182,12 +2225,26 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
||||
|
||||
{/* ── Footer count & Pagination ── */}
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-xs text-slate-500 pb-1">
|
||||
Showing {paginatedRows.length} of {sortedRows.length} products {totalPages > 1 && `(${currentPage}/${totalPages})`}
|
||||
{pricingEditableCols.length > 0 && (
|
||||
<> · Click any <span className="text-blue-400">price cell</span> to edit inline</>
|
||||
<div className="flex items-center gap-3">
|
||||
<p className="text-xs text-slate-500 pb-1">
|
||||
Showing {paginatedRows.length} of {sortedRows.length} products {totalPages > 1 && `(${currentPage}/${totalPages})`}
|
||||
{pricingEditableCols.length > 0 && (
|
||||
<> · Click any <span className="text-blue-400">price cell</span> to edit inline</>
|
||||
)}
|
||||
</p>
|
||||
{selectedRows.size > 0 && (
|
||||
<span className="flex items-center gap-1.5 text-xs text-blue-400 bg-blue-500/10 border border-blue-500/20 px-2 py-0.5 rounded mb-1">
|
||||
{selectedRows.size} selected
|
||||
<button
|
||||
onClick={() => setSelectedRows(new Set())}
|
||||
className="ml-1 text-blue-300 hover:text-white transition-colors"
|
||||
title="Clear selection"
|
||||
>
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user