mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 11:45:24 +02:00
feat: add MOQ column to PricingView positioned after container columns
This commit is contained in:
+42
-1
@@ -20,6 +20,34 @@ import { HistoryView } from './components/HistoryView';
|
|||||||
import { UndoToast } from './components/UndoToast';
|
import { UndoToast } from './components/UndoToast';
|
||||||
import { PendingValidationView } from './components/PendingValidationView';
|
import { PendingValidationView } from './components/PendingValidationView';
|
||||||
import { MissingDataView } from './components/MissingDataView';
|
import { MissingDataView } from './components/MissingDataView';
|
||||||
|
|
||||||
|
const FORCED_ZERO_STOCK_SKUS = new Set([
|
||||||
|
'11631VC', '1237VC', '1238VC', '1652VC', '1653VC', '1684VC', '1688VC', '1717VC',
|
||||||
|
'1718VC', '180VC', '1832VC', '2025VC', '2027VC', '2180VC', '2181VC', '2210VC',
|
||||||
|
'2260VC', '2355VC', '2356VC', '2360VC', '2400VC', '2401VC', '2402VC', '2405VC',
|
||||||
|
'2406VC', '2414VC', '2415VC', '2425VC', '2447VC', '2450VC', '2510VC', '2517VC',
|
||||||
|
'2522VC', '2611VC', '2648VC', '2660VC', '2665VC', '2857VC', '2858VC', '2990VC',
|
||||||
|
'3011VC', '3012VC', '3013VC', '3015VC', '3018VC', '3019VC', '3020VC', '3021VC',
|
||||||
|
'3027VC', '3030VC', '3035VC', '3038VC', '3041VC', '3043VC', '3044VC', '3045VC',
|
||||||
|
'3046VC', '3048VC', '3052VC', '3053VC', '3054VC', '3055VC', '3062VC', '3063VC',
|
||||||
|
'3064VC', '3069VC', '3070VC', '3071VC', '3072VC', '3074VC', '3089VC', '3116VC',
|
||||||
|
'3117VC', '3118VC', '3119VC', '3120VC', '3121VC', '3123VC', '3125VC', '3126VC',
|
||||||
|
'3127VC', '3130VC', '3133VC', '3134VC', '3135VC', '3200VC', '3201VC', '3230VC',
|
||||||
|
'3311VC', '3314VC', '3315VC', '3354VC', '3372VC', '3380VC', '3391VC', '3392VC',
|
||||||
|
'3423VC', '3432VC', '3437VC', '3484VC', '3487VC', '3555VC', '3557VC', '3558VC',
|
||||||
|
'3559VC', '3712VC', '3824VC', '4107VC', '4108VC', '4110VC', '4115VC', '4125VC',
|
||||||
|
'4130VC', '4134VC', '4140VC', '4155VC', '4158VC', '4165VC', '4231VC', '4232VC',
|
||||||
|
'4255VC', '4259VC', '4261VC', '4263VC', '4264VC', '4265VC', '4285VC', '4288VC',
|
||||||
|
'4330VC', '4361VC', '4442VC', '4445VC', '4462VC', '4528VC', '4531VC', '4534VC',
|
||||||
|
'4558VC', '4657VC', '4721VC', '4722VC', '4727VC', '4735VC', '4737VC', '4745VC',
|
||||||
|
'4752VC', '4758VC', '4765VC', '4785VC', '4790VC', '4801VC', '4815VC', '4874VC',
|
||||||
|
'4882VC', '5032VC', '5113VC', '5382VC', '5383VC', '5440VC', '5442VC', '5456VC',
|
||||||
|
'5540VC', '5571VC', '5572VC', '5574VC', '5575VC', '5588VC', '5592VC', '5685VC',
|
||||||
|
'5712VC', '5721VC', '5807VC', '5823VC', '5825VC', '5835VC', '5852VC', '5871VC',
|
||||||
|
'5872VC', '5873VC', '5874VC', '5882VC', '5883VC', '5885VC', '5987VC', '5991VC',
|
||||||
|
'5995VC', '6001VC', '6007VC', '6040VC', '6151VC', '6252VC', '6255VC', '6270VC',
|
||||||
|
'6471VC', '5658VCI'
|
||||||
|
]);
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [session, setSession] = useState<AuthSession | null>(() => getStoredSession());
|
const [session, setSession] = useState<AuthSession | null>(() => getStoredSession());
|
||||||
@@ -231,6 +259,11 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Force stock 0 for specific SKUs (User request)
|
||||||
|
if (FORCED_ZERO_STOCK_SKUS.has(articleNo)) {
|
||||||
|
finalRow[COLUMNS.ITEM_AVAILABLE] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
return finalRow.map((val: any, idx: number) => {
|
return finalRow.map((val: any, idx: number) => {
|
||||||
if (val === undefined || val === null || val === '') return val;
|
if (val === undefined || val === null || val === '') return val;
|
||||||
const header = (extendedHeaders[idx] || '').toLowerCase();
|
const header = (extendedHeaders[idx] || '').toLowerCase();
|
||||||
@@ -317,8 +350,9 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
|||||||
const headers = data[0];
|
const headers = data[0];
|
||||||
const rawRows = data.slice(1);
|
const rawRows = data.slice(1);
|
||||||
|
|
||||||
|
const resolvedCols = resolveColumnIndices(headers);
|
||||||
const processedRows = rawRows.map(row => {
|
const processedRows = rawRows.map(row => {
|
||||||
return row.map((val, idx) => {
|
const finalRow = row.map((val, idx) => {
|
||||||
if (val === undefined || val === null || val === '') return val;
|
if (val === undefined || val === null || val === '') return val;
|
||||||
const header = (headers[idx] || '').toLowerCase();
|
const header = (headers[idx] || '').toLowerCase();
|
||||||
|
|
||||||
@@ -356,6 +390,13 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
|
|||||||
}
|
}
|
||||||
return val;
|
return val;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Force stock 0 for specific SKUs (User request)
|
||||||
|
const articleNo = String(finalRow[resolvedCols.ARTICLE_NO] || '');
|
||||||
|
if (FORCED_ZERO_STOCK_SKUS.has(articleNo)) {
|
||||||
|
finalRow[resolvedCols.ITEM_AVAILABLE] = 0;
|
||||||
|
}
|
||||||
|
return finalRow;
|
||||||
});
|
});
|
||||||
|
|
||||||
setAppState({
|
setAppState({
|
||||||
|
|||||||
+165
-18
@@ -71,6 +71,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
outerW: 80,
|
outerW: 80,
|
||||||
outerL: 80,
|
outerL: 80,
|
||||||
outerH: 80,
|
outerH: 80,
|
||||||
|
moq: 80,
|
||||||
check: 100,
|
check: 100,
|
||||||
actions: 60,
|
actions: 60,
|
||||||
});
|
});
|
||||||
@@ -100,6 +101,8 @@ 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 [checkYingFilter, setCheckYingFilter] = usePersistentState<string[]>('pricing-checkYingFilter', []);
|
||||||
|
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', {});
|
||||||
const [weightIssueFilter, setWeightIssueFilter] = usePersistentState<'all' | 'with' | 'without'>('pricing-weightIssueFilter', 'all');
|
const [weightIssueFilter, setWeightIssueFilter] = usePersistentState<'all' | 'with' | 'without'>('pricing-weightIssueFilter', 'all');
|
||||||
const [selectedSearchItems, setSelectedSearchItems] = usePersistentState<Set<string>>('pricing-selectedSearchItems', new Set());
|
const [selectedSearchItems, setSelectedSearchItems] = usePersistentState<Set<string>>('pricing-selectedSearchItems', new Set());
|
||||||
@@ -111,6 +114,62 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
const [pinnedColumns, setPinnedColumns] = usePersistentState<Set<string>>('pricing-pinnedColumns', new Set(['articleNo', 'articleName']));
|
const [pinnedColumns, setPinnedColumns] = usePersistentState<Set<string>>('pricing-pinnedColumns', new Set(['articleNo', 'articleName']));
|
||||||
const [showPinPanel, setShowPinPanel] = useState(false);
|
const [showPinPanel, setShowPinPanel] = useState(false);
|
||||||
|
|
||||||
|
// Drag-to-select state
|
||||||
|
const [selectedRows, setSelectedRows] = useState<Set<number>>(new Set());
|
||||||
|
const isDraggingRef = useRef(false);
|
||||||
|
const dragStartIndexRef = useRef<number | null>(null);
|
||||||
|
const dragModeRef = useRef<'add' | 'remove'>('add');
|
||||||
|
const lastDragIndexRef = useRef<number | null>(null);
|
||||||
|
const visibleDataIndexesRef = useRef<number[]>([]);
|
||||||
|
|
||||||
|
const handleRowMouseDown = useCallback((e: React.MouseEvent, dataIndex: number) => {
|
||||||
|
if ((e.target as HTMLElement).closest('input, button, a')) return;
|
||||||
|
e.preventDefault();
|
||||||
|
isDraggingRef.current = true;
|
||||||
|
dragStartIndexRef.current = dataIndex;
|
||||||
|
lastDragIndexRef.current = dataIndex;
|
||||||
|
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
dragModeRef.current = selectedRows.has(dataIndex) ? 'remove' : 'add';
|
||||||
|
setSelectedRows(prev => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.has(dataIndex)) next.delete(dataIndex); else next.add(dataIndex);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dragModeRef.current = selectedRows.has(dataIndex) ? 'remove' : 'add';
|
||||||
|
setSelectedRows(new Set([dataIndex]));
|
||||||
|
}, [selectedRows]);
|
||||||
|
|
||||||
|
const handleRowMouseEnter = useCallback((dataIndex: number) => {
|
||||||
|
if (!isDraggingRef.current || dragStartIndexRef.current === null) return;
|
||||||
|
if (dataIndex === lastDragIndexRef.current) return;
|
||||||
|
lastDragIndexRef.current = dataIndex;
|
||||||
|
|
||||||
|
const start = dragStartIndexRef.current;
|
||||||
|
const end = dataIndex;
|
||||||
|
const [lo, hi] = start <= end ? [start, end] : [end, start];
|
||||||
|
|
||||||
|
setSelectedRows(prev => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
visibleDataIndexesRef.current.forEach((idx: number) => {
|
||||||
|
if (idx >= lo && idx <= hi) {
|
||||||
|
if (dragModeRef.current === 'add') next.add(idx);
|
||||||
|
else next.delete(idx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleMouseUp = () => { isDraggingRef.current = false; };
|
||||||
|
document.addEventListener('mouseup', handleMouseUp);
|
||||||
|
return () => document.removeEventListener('mouseup', handleMouseUp);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Close search dropdown on click outside
|
// Close search dropdown on click outside
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
@@ -313,6 +372,14 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
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]);
|
||||||
|
|
||||||
|
const uniqueCheckYing = useMemo(() =>
|
||||||
|
Array.from(new Set(data.map(r => r[COLUMNS.VALIDATED_CHECK] ? 'Checked' : 'Unchecked'))).sort(),
|
||||||
|
[data, COLUMNS.VALIDATED_CHECK]);
|
||||||
|
|
||||||
|
const uniqueCheckAnna = useMemo(() =>
|
||||||
|
Array.from(new Set(data.map(r => r[COLUMNS.ANNA_CHECK] ? 'Checked' : 'Unchecked'))).sort(),
|
||||||
|
[data, COLUMNS.ANNA_CHECK]);
|
||||||
|
|
||||||
// ── Filtered rows ─────────────────────────────────────────────────────────
|
// ── Filtered rows ─────────────────────────────────────────────────────────
|
||||||
const filteredRows = useMemo(() => {
|
const filteredRows = useMemo(() => {
|
||||||
let result = analyzedRows;
|
let result = analyzedRows;
|
||||||
@@ -408,6 +475,12 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
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] || '')));
|
||||||
}
|
}
|
||||||
|
if ((checkYingFilter || []).length > 0) {
|
||||||
|
result = result.filter(r => (checkYingFilter || []).includes(r.row[COLUMNS.VALIDATED_CHECK] ? 'Checked' : 'Unchecked'));
|
||||||
|
}
|
||||||
|
if ((checkAnnaFilter || []).length > 0) {
|
||||||
|
result = result.filter(r => (checkAnnaFilter || []).includes(r.row[COLUMNS.ANNA_CHECK] ? 'Checked' : 'Unchecked'));
|
||||||
|
}
|
||||||
|
|
||||||
// Dynamic column filters (SRP, container)
|
// Dynamic column filters (SRP, container)
|
||||||
(Object.keys(dynamicColFilters) as string[]).forEach(colIdx => {
|
(Object.keys(dynamicColFilters) as string[]).forEach(colIdx => {
|
||||||
@@ -491,6 +564,9 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
return sortedRows.slice(start, start + pageSize);
|
return sortedRows.slice(start, start + pageSize);
|
||||||
}, [sortedRows, currentPage]);
|
}, [sortedRows, currentPage]);
|
||||||
|
|
||||||
|
// Keep ref in sync so drag handlers can read current visible indexes without stale closure
|
||||||
|
visibleDataIndexesRef.current = paginatedRows.map(r => r.dataIndex);
|
||||||
|
|
||||||
const totalPages = Math.ceil(sortedRows.length / pageSize);
|
const totalPages = Math.ceil(sortedRows.length / pageSize);
|
||||||
|
|
||||||
const handleSort = (key: string | number) => {
|
const handleSort = (key: string | number) => {
|
||||||
@@ -591,6 +667,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
{ key: 'outerW', label: 'Outer W', index: COLUMNS.OUTER_W },
|
{ key: 'outerW', label: 'Outer W', index: COLUMNS.OUTER_W },
|
||||||
{ key: 'outerL', label: 'Outer L', index: COLUMNS.OUTER_L },
|
{ key: 'outerL', label: 'Outer L', index: COLUMNS.OUTER_L },
|
||||||
{ key: 'outerH', label: 'Outer H', index: COLUMNS.OUTER_H },
|
{ key: 'outerH', label: 'Outer H', index: COLUMNS.OUTER_H },
|
||||||
|
{ key: 'moq', label: 'MOQ', index: COLUMNS.MOQ },
|
||||||
];
|
];
|
||||||
pricingEditableCols.forEach(col => {
|
pricingEditableCols.forEach(col => {
|
||||||
cols.push({ key: `prc_${col.index}`, label: col.name, index: col.index });
|
cols.push({ key: `prc_${col.index}`, label: col.name, index: col.index });
|
||||||
@@ -615,7 +692,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
|
|
||||||
// Calculate sticky left position for each pinned column
|
// Calculate sticky left position for each pinned column
|
||||||
const getStickyLeft = useMemo(() => {
|
const getStickyLeft = useMemo(() => {
|
||||||
const baseOrder = ['articleNo', 'articleName', 'line', 'classification', 'productType', 'itemToLogistic', 'unitsOuter', 'outerW', 'outerL', 'outerH'];
|
const baseOrder = ['articleNo', 'articleName', 'line', 'classification', 'productType', 'itemToLogistic', 'unitsOuter', 'outerW', 'outerL', 'outerH', 'moq'];
|
||||||
const dynamicOrder: string[] = [];
|
const dynamicOrder: string[] = [];
|
||||||
pricingEditableCols.forEach(col => dynamicOrder.push(`prc_${col.index}`));
|
pricingEditableCols.forEach(col => dynamicOrder.push(`prc_${col.index}`));
|
||||||
containerCols.forEach(col => dynamicOrder.push(`con_${col.index}`));
|
containerCols.forEach(col => dynamicOrder.push(`con_${col.index}`));
|
||||||
@@ -640,7 +717,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
|
|
||||||
// Calculate z-index for sticky columns (so they stack correctly)
|
// Calculate z-index for sticky columns (so they stack correctly)
|
||||||
const getStickyRank = useMemo(() => {
|
const getStickyRank = useMemo(() => {
|
||||||
const baseOrder = ['articleNo', 'articleName', 'line', 'classification', 'productType', 'itemToLogistic', 'unitsOuter', 'outerW', 'outerL', 'outerH'];
|
const baseOrder = ['articleNo', 'articleName', 'line', 'classification', 'productType', 'itemToLogistic', 'unitsOuter', 'outerW', 'outerL', 'outerH', 'moq'];
|
||||||
const dynamicOrder: string[] = [];
|
const dynamicOrder: string[] = [];
|
||||||
pricingEditableCols.forEach(col => dynamicOrder.push(`prc_${col.index}`));
|
pricingEditableCols.forEach(col => dynamicOrder.push(`prc_${col.index}`));
|
||||||
containerCols.forEach(col => dynamicOrder.push(`con_${col.index}`));
|
containerCols.forEach(col => dynamicOrder.push(`con_${col.index}`));
|
||||||
@@ -1605,7 +1682,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
<ResizeHandle onMouseDown={e => { e.stopPropagation(); handleResizeStart(e, 'outerH', columnWidths.outerH); }} />
|
<ResizeHandle onMouseDown={e => { e.stopPropagation(); handleResizeStart(e, 'outerH', columnWidths.outerH); }} />
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
{containerCols.map(col => {
|
{containerCols.map(col => {
|
||||||
const colKey = `con_${col.index}`;
|
const colKey = `con_${col.index}`;
|
||||||
const width = columnWidths[colKey] ?? 110;
|
const width = columnWidths[colKey] ?? 110;
|
||||||
return (
|
return (
|
||||||
@@ -1640,24 +1717,41 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
<ColumnFilterPopover
|
<ColumnFilterPopover
|
||||||
triggerId={`filter-trigger-${colKey}`}
|
triggerId={`filter-trigger-${colKey}`}
|
||||||
uniqueValues={dynamicColUniqueValues[col.index] || []}
|
uniqueValues={dynamicColUniqueValues[col.index] || []}
|
||||||
selectedValues={dynamicColFilters[col.index] || []}
|
selectedValues={dynamicColFilters[col.index] || []}
|
||||||
onToggle={val => setDynamicColFilters(prev => {
|
onToggle={val => setDynamicColFilters(prev => {
|
||||||
const current = prev[col.index] || [];
|
const current = prev[col.index] || [];
|
||||||
return current.includes(val)
|
return current.includes(val)
|
||||||
? { ...prev, [col.index]: current.filter(v => v !== val) }
|
? { ...prev, [col.index]: current.filter(v => v !== val) }
|
||||||
: { ...prev, [col.index]: [...current, val] };
|
: { ...prev, [col.index]: [...current, val] };
|
||||||
})}
|
})}
|
||||||
onSelectAll={vals => setDynamicColFilters(prev => ({ ...prev, [col.index]: vals }))}
|
onSelectAll={vals => setDynamicColFilters(prev => ({ ...prev, [col.index]: vals }))}
|
||||||
onClear={() => { setDynamicColFilters(prev => { const next = { ...prev }; delete next[col.index]; return next; }); setOpenFilter(null); }}
|
onClear={() => { setDynamicColFilters(prev => { const next = { ...prev }; delete next[col.index]; return next; }); setOpenFilter(null); }}
|
||||||
onClose={() => setOpenFilter(null)}
|
onClose={() => setOpenFilter(null)}
|
||||||
zIndex={isPinned(colKey) ? 200 : 50}
|
zIndex={isPinned(colKey) ? 200 : 50}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ResizeHandle onMouseDown={e => { e.stopPropagation(); handleResizeStart(e, colKey, width); }} />
|
<ResizeHandle onMouseDown={e => { e.stopPropagation(); handleResizeStart(e, colKey, width); }} />
|
||||||
</th>
|
</th>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
<th style={{
|
||||||
|
width: columnWidths.moq,
|
||||||
|
...(isPinned('moq')
|
||||||
|
? { left: getStickyLeft('moq') ?? 0, zIndex: openFilter === 'moq' ? 500 : (getHeaderStickyRank('moq') ?? 0) }
|
||||||
|
: (openFilter === 'moq' ? { zIndex: 500, position: 'relative' } : {}))
|
||||||
|
}} className={cn(
|
||||||
|
"text-left px-3 py-3 text-xs font-semibold text-cyan-400 uppercase tracking-wider whitespace-nowrap group cursor-pointer hover:bg-slate-800/50 transition-colors relative",
|
||||||
|
isPinned('moq') && "sticky bg-slate-900"
|
||||||
|
)} onClick={() => handleSort('moq')}>
|
||||||
|
<div className="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" />}
|
||||||
|
MOQ <SortIcon current={sortConfig.key === 'moq' ? sortConfig.direction : null} /></span>
|
||||||
|
</div>
|
||||||
|
<ResizeHandle onMouseDown={e => { e.stopPropagation(); handleResizeStart(e, 'moq', columnWidths.moq); }} />
|
||||||
|
</th>
|
||||||
|
|
||||||
<th style={{ width: 180 }} className="text-left px-3 py-3 text-xs font-semibold text-red-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
<th style={{ width: 180 }} className="text-left px-3 py-3 text-xs font-semibold text-red-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span>Weight Issues</span>
|
<span>Weight Issues</span>
|
||||||
@@ -1690,12 +1784,60 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
</th>
|
</th>
|
||||||
|
|
||||||
<th style={{ width: columnWidths.check }} className="text-left px-3 py-3 text-xs font-semibold text-blue-400 uppercase tracking-wider group cursor-pointer hover:bg-slate-800/50 transition-colors relative" onClick={() => handleSort(COLUMNS.VALIDATED_CHECK)}>
|
<th style={{ width: columnWidths.check }} className="text-left px-3 py-3 text-xs font-semibold text-blue-400 uppercase tracking-wider group cursor-pointer hover:bg-slate-800/50 transition-colors relative" onClick={() => handleSort(COLUMNS.VALIDATED_CHECK)}>
|
||||||
<span className="flex items-center gap-1">Check Ying <SortIcon current={sortConfig.key === COLUMNS.VALIDATED_CHECK ? sortConfig.direction : null} /></span>
|
<div className="flex items-center gap-1">
|
||||||
|
<span className="flex items-center gap-1">Check Ying <SortIcon current={sortConfig.key === COLUMNS.VALIDATED_CHECK ? sortConfig.direction : null} /></span>
|
||||||
|
<button
|
||||||
|
id="filter-trigger-check"
|
||||||
|
onClick={(e) => { e.stopPropagation(); setOpenFilter(openFilter === 'check' ? null : 'check'); }}
|
||||||
|
className={cn(
|
||||||
|
'p-0.5 rounded hover:bg-slate-700 transition-colors shrink-0 filter-trigger-btn',
|
||||||
|
(checkYingFilter || []).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 === 'check' && (
|
||||||
|
<ColumnFilterPopover
|
||||||
|
triggerId="filter-trigger-check"
|
||||||
|
uniqueValues={uniqueCheckYing || []}
|
||||||
|
selectedValues={checkYingFilter || []}
|
||||||
|
onToggle={val => setCheckYingFilter(prev => (prev || []).includes(val) ? (prev || []).filter(v => v !== val) : [...(prev || []), val])}
|
||||||
|
onSelectAll={vals => setCheckYingFilter(vals || [])}
|
||||||
|
onClear={() => { setCheckYingFilter([]); setOpenFilter(null); }}
|
||||||
|
onClose={() => setOpenFilter(null)}
|
||||||
|
zIndex={50}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<ResizeHandle onMouseDown={e => { e.stopPropagation(); handleResizeStart(e, 'check', columnWidths.check); }} />
|
<ResizeHandle onMouseDown={e => { e.stopPropagation(); handleResizeStart(e, 'check', columnWidths.check); }} />
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
<th style={{ width: columnWidths.checkAnna ?? 100 }} className="text-left px-3 py-3 text-xs font-semibold text-pink-400 uppercase tracking-wider group cursor-pointer hover:bg-slate-800/50 transition-colors relative" onClick={() => handleSort(COLUMNS.ANNA_CHECK)}>
|
<th style={{ width: columnWidths.checkAnna ?? 100 }} className="text-left px-3 py-3 text-xs font-semibold text-pink-400 uppercase tracking-wider group cursor-pointer hover:bg-slate-800/50 transition-colors relative" onClick={() => handleSort(COLUMNS.ANNA_CHECK)}>
|
||||||
<span className="flex items-center gap-1">Check Anna <SortIcon current={sortConfig.key === COLUMNS.ANNA_CHECK ? sortConfig.direction : null} /></span>
|
<div className="flex items-center gap-1">
|
||||||
|
<span className="flex items-center gap-1">Check Anna <SortIcon current={sortConfig.key === COLUMNS.ANNA_CHECK ? sortConfig.direction : null} /></span>
|
||||||
|
<button
|
||||||
|
id="filter-trigger-checkAnna"
|
||||||
|
onClick={(e) => { e.stopPropagation(); setOpenFilter(openFilter === 'checkAnna' ? null : 'checkAnna'); }}
|
||||||
|
className={cn(
|
||||||
|
'p-0.5 rounded hover:bg-slate-700 transition-colors shrink-0 filter-trigger-btn',
|
||||||
|
(checkAnnaFilter || []).length > 0 ? 'text-pink-400 bg-pink-400/10' : 'text-slate-500 opacity-0 group-hover:opacity-100'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Filter className="w-3 h-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{openFilter === 'checkAnna' && (
|
||||||
|
<ColumnFilterPopover
|
||||||
|
triggerId="filter-trigger-checkAnna"
|
||||||
|
uniqueValues={uniqueCheckAnna || []}
|
||||||
|
selectedValues={checkAnnaFilter || []}
|
||||||
|
onToggle={val => setCheckAnnaFilter(prev => (prev || []).includes(val) ? (prev || []).filter(v => v !== val) : [...(prev || []), val])}
|
||||||
|
onSelectAll={vals => setCheckAnnaFilter(vals || [])}
|
||||||
|
onClear={() => { setCheckAnnaFilter([]); setOpenFilter(null); }}
|
||||||
|
onClose={() => setOpenFilter(null)}
|
||||||
|
zIndex={50}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<ResizeHandle onMouseDown={e => { e.stopPropagation(); handleResizeStart(e, 'checkAnna', columnWidths.checkAnna ?? 100); }} />
|
<ResizeHandle onMouseDown={e => { e.stopPropagation(); handleResizeStart(e, 'checkAnna', columnWidths.checkAnna ?? 100); }} />
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
@@ -1920,6 +2062,11 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{/* MOQ cell */}
|
||||||
|
<td className={cn("px-3 py-2.5 truncate", isPinned('moq') && "sticky bg-slate-800")} style={isPinned('moq') ? { left: getStickyLeft('moq') ?? 0, zIndex: getStickyRank('moq') ?? 0 } : {}}>
|
||||||
|
{row[COLUMNS.MOQ] !== undefined && row[COLUMNS.MOQ] !== null ? row[COLUMNS.MOQ] : '-'}
|
||||||
|
</td>
|
||||||
|
|
||||||
{/* Issues column (weight only) */}
|
{/* Issues column (weight only) */}
|
||||||
<td className="px-3 py-2 overflow-hidden">
|
<td className="px-3 py-2 overflow-hidden">
|
||||||
{(() => {
|
{(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user