mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 11:55:23 +02:00
feat: add MOQ filtering and sorting in PricingView
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
> react-example@0.0.0 lint
|
||||||
|
> tsc --noEmit
|
||||||
|
|
||||||
|
src/components/PricingView.tsx(200,25): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
|
||||||
|
src/components/PricingView.tsx(201,26): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
|
||||||
|
src/components/PricingView.tsx(203,23): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
|
||||||
|
src/components/PricingView.tsx(1042,16): error TS2741: Property 'triggerId' is missing in type '{ value: { terms: string[]; op: "and" | "or"; }; onChange: (value: { terms: string[]; op: "and" | "or"; } | ((prev: { terms: string[]; op: "and" | "or"; }) => { terms: string[]; op: "and" | "or"; })) => void; onClose: () => any; }' but required in type '{ value: { terms: string[]; op: "and" | "or"; }; onChange: (v: { terms: string[]; op: "and" | "or"; }) => void; onClose: () => void; triggerId: string; }'.
|
||||||
|
src/components/PricingView.tsx(1304,23): error TS2322: Type '{ triggerId: string; value: { terms: string[]; op: "and" | "or"; }; onChange: (value: { terms: string[]; op: "and" | "or"; } | ((prev: { terms: string[]; op: "and" | "or"; }) => { terms: string[]; op: "and" | "or"; })) => void; onClose: () => any; zIndex: number; }' is not assignable to type '{ value: { terms: string[]; op: "and" | "or"; }; onChange: (v: { terms: string[]; op: "and" | "or"; }) => void; onClose: () => void; triggerId: string; }'.
|
||||||
|
Property 'zIndex' does not exist on type '{ value: { terms: string[]; op: "and" | "or"; }; onChange: (v: { terms: string[]; op: "and" | "or"; }) => void; onClose: () => void; triggerId: string; }'.
|
||||||
|
src/components/PricingView.tsx(1339,23): error TS2322: Type '{ triggerId: string; value: { terms: string[]; op: "and" | "or"; }; onChange: (value: { terms: string[]; op: "and" | "or"; } | ((prev: { terms: string[]; op: "and" | "or"; }) => { terms: string[]; op: "and" | "or"; })) => void; onClose: () => any; zIndex: number; }' is not assignable to type '{ value: { terms: string[]; op: "and" | "or"; }; onChange: (v: { terms: string[]; op: "and" | "or"; }) => void; onClose: () => void; triggerId: string; }'.
|
||||||
|
Property 'zIndex' does not exist on type '{ value: { terms: string[]; op: "and" | "or"; }; onChange: (v: { terms: string[]; op: "and" | "or"; }) => void; onClose: () => void; triggerId: string; }'.
|
||||||
|
src/components/ProductDescriptions.tsx(205,21): error TS2304: Cannot find name 'TabType'.
|
||||||
|
src/types.ts(50,14): error TS2740: Type '{ ARTICLE_NO: string[]; ARTICLE_NAME: string[]; LINE: string[]; LICENSE: string[]; DETAILS_EN: string[]; DETAILS_DE: string[]; BARCODE: string[]; TARIFF_CODE: string[]; COUNTRY_ORIGIN: string[]; LONG_DE: string[]; ... 15 more ...; OUTER_H: string[]; }' is missing the following properties from type 'Record<"ASIN" | "ARTICLE_NO" | "ARTICLE_NAME" | "LINE" | "LICENSE" | "DETAILS_EN" | "DETAILS_DE" | "BARCODE" | "TARIFF_CODE" | "COUNTRY_ORIGIN" | "LONG_DE" | "LONG_EN" | "SHORT_DE" | ... 19 more ... | "ANNA_NOTE", string[]>': VERIFIED_DIMS, VALIDATED_CHECK, VALIDATED_NOTE, PRODUCT_TYPE, and 3 more.
|
||||||
@@ -101,6 +101,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
const [outerWFilter, setOuterWFilter] = usePersistentState<string[]>('pricing-outerWFilter', []);
|
const [outerWFilter, setOuterWFilter] = usePersistentState<string[]>('pricing-outerWFilter', []);
|
||||||
const [outerLFilter, setOuterLFilter] = usePersistentState<string[]>('pricing-outerLFilter', []);
|
const [outerLFilter, setOuterLFilter] = usePersistentState<string[]>('pricing-outerLFilter', []);
|
||||||
const [outerHFilter, setOuterHFilter] = usePersistentState<string[]>('pricing-outerHFilter', []);
|
const [outerHFilter, setOuterHFilter] = usePersistentState<string[]>('pricing-outerHFilter', []);
|
||||||
|
const [moqFilter, setMoqFilter] = usePersistentState<string[]>('pricing-moqFilter', []);
|
||||||
const [checkYingFilter, setCheckYingFilter] = usePersistentState<string[]>('pricing-checkYingFilter', []);
|
const [checkYingFilter, setCheckYingFilter] = usePersistentState<string[]>('pricing-checkYingFilter', []);
|
||||||
const [checkAnnaFilter, setCheckAnnaFilter] = usePersistentState<string[]>('pricing-checkAnnaFilter', []);
|
const [checkAnnaFilter, setCheckAnnaFilter] = usePersistentState<string[]>('pricing-checkAnnaFilter', []);
|
||||||
const [dynamicColFilters, setDynamicColFilters] = usePersistentState<Record<number, string[]>>('pricing-dynamicColFilters', {});
|
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)),
|
Array.from(new Set(data.map(r => String(r[COLUMNS.OUTER_H] || '')))).filter(v => v).sort((a, b) => Number(a) - Number(b)),
|
||||||
[data]);
|
[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(() =>
|
const uniqueItemToLogistic = useMemo(() =>
|
||||||
Array.from(new Set(data.map(r => String(r[COLUMNS.ITEM_TO_LOGISTIC] || '')))).filter(v => v).sort(),
|
Array.from(new Set(data.map(r => String(r[COLUMNS.ITEM_TO_LOGISTIC] || '')))).filter(v => v).sort(),
|
||||||
[data]);
|
[data]);
|
||||||
@@ -472,6 +477,9 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
if ((outerHFilter || []).length > 0) {
|
if ((outerHFilter || []).length > 0) {
|
||||||
result = result.filter(r => (outerHFilter || []).includes(String(r.row[COLUMNS.OUTER_H] || '')));
|
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) {
|
if ((itemToLogisticFilter || []).length > 0) {
|
||||||
result = result.filter(r => (itemToLogisticFilter || []).includes(String(r.row[COLUMNS.ITEM_TO_LOGISTIC] || '')));
|
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;
|
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 ──────────────────────────────────────────────────────────
|
// ── Sorted rows ──────────────────────────────────────────────────────────
|
||||||
const sortedRows = useMemo(() => {
|
const sortedRows = useMemo(() => {
|
||||||
@@ -522,6 +530,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
case 'outerW': colIndex = COLUMNS.OUTER_W; break;
|
case 'outerW': colIndex = COLUMNS.OUTER_W; break;
|
||||||
case 'outerL': colIndex = COLUMNS.OUTER_L; break;
|
case 'outerL': colIndex = COLUMNS.OUTER_L; break;
|
||||||
case 'outerH': colIndex = COLUMNS.OUTER_H; break;
|
case 'outerH': colIndex = COLUMNS.OUTER_H; break;
|
||||||
|
case 'moq': colIndex = COLUMNS.MOQ; break;
|
||||||
case 'itemToLogistic': colIndex = COLUMNS.ITEM_TO_LOGISTIC; break;
|
case 'itemToLogistic': colIndex = COLUMNS.ITEM_TO_LOGISTIC; break;
|
||||||
default: return filteredRows;
|
default: return filteredRows;
|
||||||
}
|
}
|
||||||
@@ -565,7 +574,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
}, [sortedRows, currentPage]);
|
}, [sortedRows, currentPage]);
|
||||||
|
|
||||||
// Keep ref in sync so drag handlers can read current visible indexes without stale closure
|
// 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);
|
const totalPages = Math.ceil(sortedRows.length / pageSize);
|
||||||
|
|
||||||
@@ -808,6 +817,11 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
COLUMNS.OUTER_W,
|
COLUMNS.OUTER_W,
|
||||||
COLUMNS.OUTER_L,
|
COLUMNS.OUTER_L,
|
||||||
COLUMNS.OUTER_H,
|
COLUMNS.OUTER_H,
|
||||||
|
COLUMNS.VALIDATED_CHECK,
|
||||||
|
COLUMNS.VALIDATED_NOTE,
|
||||||
|
COLUMNS.ANNA_CHECK,
|
||||||
|
COLUMNS.ANNA_NOTE,
|
||||||
|
COLUMNS.MOQ,
|
||||||
]);
|
]);
|
||||||
if (uvpIdx >= 0) set.add(uvpIdx);
|
if (uvpIdx >= 0) set.add(uvpIdx);
|
||||||
srpCols.forEach(c => set.add(c.index));
|
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">
|
<span className="truncate flex items-center gap-1">
|
||||||
{isPinned('moq') && <Pin className="w-3 h-3 text-blue-400 shrink-0" />}
|
{isPinned('moq') && <Pin className="w-3 h-3 text-blue-400 shrink-0" />}
|
||||||
MOQ <SortIcon current={sortConfig.key === 'moq' ? sortConfig.direction : null} /></span>
|
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>
|
</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); }} />
|
<ResizeHandle onMouseDown={e => { e.stopPropagation(); handleResizeStart(e, 'moq', columnWidths.moq); }} />
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
@@ -1863,22 +1899,29 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
const isValidated = !!row[COLUMNS.VALIDATED_CHECK];
|
const isValidated = !!row[COLUMNS.VALIDATED_CHECK];
|
||||||
const note = String(row[COLUMNS.VALIDATED_NOTE] || '');
|
const note = String(row[COLUMNS.VALIDATED_NOTE] || '');
|
||||||
|
|
||||||
|
const isRowSelected = selectedRows.has(dataIndex);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
key={dataIndex}
|
key={dataIndex}
|
||||||
|
onMouseDown={(e: React.MouseEvent<HTMLTableRowElement>) => handleRowMouseDown(e, dataIndex)}
|
||||||
|
onMouseEnter={() => handleRowMouseEnter(dataIndex)}
|
||||||
|
style={{ userSelect: 'none' }}
|
||||||
className={cn(
|
className={cn(
|
||||||
'border-b border-slate-700/50 transition-colors group relative hover:z-50',
|
'border-b border-slate-700/50 transition-colors group relative hover:z-50 cursor-default',
|
||||||
isValidated
|
isRowSelected
|
||||||
? 'bg-green-400/30 border-l-4 border-l-green-400' // Vibrant high-visibility green
|
? 'bg-blue-500/20 border-l-4 border-l-blue-400'
|
||||||
: saveStatus === 'error'
|
: isValidated
|
||||||
? 'bg-red-400/20 border-l-4 border-l-red-500'
|
? 'bg-green-400/30 border-l-4 border-l-green-400'
|
||||||
: saveStatus === 'pending'
|
: saveStatus === 'error'
|
||||||
? 'bg-amber-400/20 border-l-4 border-l-amber-400'
|
? 'bg-red-400/20 border-l-4 border-l-red-500'
|
||||||
: isCritical
|
: saveStatus === 'pending'
|
||||||
? 'bg-red-950/20'
|
? 'bg-amber-400/20 border-l-4 border-l-amber-400'
|
||||||
: pricingErrors.length > 0
|
: isCritical
|
||||||
? 'bg-amber-950/10'
|
? 'bg-red-950/20'
|
||||||
: ''
|
: pricingErrors.length > 0
|
||||||
|
? 'bg-amber-950/10'
|
||||||
|
: ''
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Art. No. */}
|
{/* Art. No. */}
|
||||||
@@ -2182,12 +2225,26 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
|
|
||||||
{/* ── Footer count & Pagination ── */}
|
{/* ── Footer count & Pagination ── */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-xs text-slate-500 pb-1">
|
<div className="flex items-center gap-3">
|
||||||
Showing {paginatedRows.length} of {sortedRows.length} products {totalPages > 1 && `(${currentPage}/${totalPages})`}
|
<p className="text-xs text-slate-500 pb-1">
|
||||||
{pricingEditableCols.length > 0 && (
|
Showing {paginatedRows.length} of {sortedRows.length} products {totalPages > 1 && `(${currentPage}/${totalPages})`}
|
||||||
<> · Click any <span className="text-blue-400">price cell</span> to edit inline</>
|
{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 && (
|
{totalPages > 1 && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user