diff --git a/src/components/PricingView.tsx b/src/components/PricingView.tsx index 3e48717..a99af10 100644 --- a/src/components/PricingView.tsx +++ b/src/components/PricingView.tsx @@ -83,6 +83,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit, const [editingType, setEditingType] = useState<{ rowIndex: number; value: string } | null>(null); const [typeSuggestions, setTypeSuggestions] = useState([]); const typeInputRef = useRef(null); + const [sortConfig, setSortConfig] = useState<{ key: string | number; direction: 'asc' | 'desc' | null }>({ key: null, direction: null }); const [isSearchOpen, setIsSearchOpen] = useState(false); const [selectedSearchItems, setSelectedSearchItems] = useState>(new Set()); @@ -332,6 +333,70 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit, return result; }, [analyzedRows, filterMode, search, nameColFilter, lineMultiFilter, classificationFilter, unitsOuterFilter, outerWFilter, outerLFilter, outerHFilter, dynamicColFilters]); + // ── Sorted rows ────────────────────────────────────────────────────────── + const sortedRows = useMemo(() => { + if (sortConfig.key === null || sortConfig.direction === null) return filteredRows; + + const { key, direction } = sortConfig; + let colIndex: number; + + if (typeof key === 'number') { + colIndex = key; + } else { + // Map string keys to COLUMNS indices + switch (key) { + case 'articleNo': colIndex = COLUMNS.ARTICLE_NO; break; + case 'articleName': colIndex = COLUMNS.ARTICLE_NAME; break; + case 'line': colIndex = COLUMNS.LINE; break; + case 'classification': colIndex = COLUMNS.CLASSIFICATION; break; + case 'productType': colIndex = COLUMNS.PRODUCT_TYPE; break; + case 'unitsOuter': colIndex = COLUMNS.UNITS_OUTER; break; + case 'outerW': colIndex = COLUMNS.OUTER_W; break; + case 'outerL': colIndex = COLUMNS.OUTER_L; break; + case 'outerH': colIndex = COLUMNS.OUTER_H; break; + default: return filteredRows; + } + } + + const sorted = [...filteredRows].sort((a, b) => { + const valA = a.row[colIndex]; + const valB = b.row[colIndex]; + + // Handle null/undefined + if (valA === valB) return 0; + if (valA === null || valA === undefined || valA === '') return 1; + if (valB === null || valB === undefined || valB === '') return -1; + + // Handle numeric comparison + const numA = typeof valA === 'number' ? valA : parseFloat(String(valA).replace(',', '.')); + const numB = typeof valB === 'number' ? valB : parseFloat(String(valB).replace(',', '.')); + + if (!isNaN(numA) && !isNaN(numB)) { + return direction === 'asc' ? numA - numB : numB - numA; + } + + // Handle string comparison + const strA = String(valA).toLowerCase(); + const strB = String(valB).toLowerCase(); + + if (strA < strB) return direction === 'asc' ? -1 : 1; + if (strA > strB) return direction === 'asc' ? 1 : -1; + return 0; + }); + + return sorted; + }, [filteredRows, sortConfig]); + + const handleSort = (key: string | number) => { + setSortConfig(prev => { + if (prev.key === key) { + if (prev.direction === 'asc') return { key, direction: 'desc' }; + return { key: null, direction: null }; + } + return { key, direction: 'asc' }; + }); + }; + // ── Inline edit helpers ─────────────────────────────────────────────────── const startEdit = (rowIndex: number, colIndex: number, currentValue: string) => { setEditingCell({ rowIndex, colIndex, value: currentValue }); @@ -691,7 +756,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit, {/* ── Table ── */}
- {filteredRows.length === 0 ? ( + {sortedRows.length === 0 ? (

No issues found

@@ -699,19 +764,19 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
) : ( - + - - - - - {pricingEditableCols.map(col => { const colKey = `prc_${col.index}`; const width = columnWidths[colKey] ?? 100; return ( - ); })} - - - - {containerCols.map(col => { const colKey = `con_${col.index}`; const width = columnWidths[colKey] ?? 110; return ( - ); })} - {matrixCols.map(col => { const colKey = `mat_${col.index}`; const width = columnWidths[colKey] ?? 120; return ( - ); })} - {filteredRows.map(({ row, dataIndex, pricingErrors, unitErrors, isCritical }) => { + {sortedRows.map(({ row, dataIndex, pricingErrors, unitErrors, isCritical }) => { const saveStatus = rowStatuses[String(row[COLUMNS.ARTICLE_NO])]; const isValidated = !!row[COLUMNS.VALIDATED_CHECK]; const note = String(row[COLUMNS.VALIDATED_NOTE] || ''); @@ -1247,6 +1312,22 @@ function TextFilterPopover({ value, onChange, onClose }: { ); } +// ── Sort Icon Helper ─────────────────────────────────────────────────── +function SortIcon({ current }: { current: 'asc' | 'desc' | null }) { + return ( + + + + + ); +} + // ── Resize Handle Component ───────────────────────────────────────────── function ResizeHandle({ onMouseDown }: { onMouseDown: (e: React.MouseEvent) => void }) { return (
-
- Art. No. +
handleSort('articleNo')}> +
+ Art. No.
- handleResizeStart(e, 'articleNo', columnWidths.articleNo)} /> + { e.stopPropagation(); handleResizeStart(e, 'articleNo', columnWidths.articleNo); }} />
+ handleSort('articleName')}>
- Article Name + Article Name
+ handleSort('line')}>
- Line + Line
+ handleSort('classification')}>
- Classification + Classification
- Type - handleResizeStart(e, 'productType', columnWidths.productType)} /> + handleSort('productType')}> + Type + { e.stopPropagation(); handleResizeStart(e, 'productType', columnWidths.productType); }} /> + handleSort(col.index)}>
- {col.name} + {col.name}
+ handleSort('unitsOuter')}>
- Units/Outer + Units/Outer
+ handleSort('outerW')}>
- Outer W + Outer W
+ handleSort('outerL')}>
- Outer L + Outer L
+ handleSort('outerH')}>
- Outer H + Outer H
+ handleSort(col.index)}>
- {col.name} + {col.name}
- Check - handleResizeStart(e, 'check', columnWidths.check)} /> + handleSort(COLUMNS.VALIDATED_CHECK)}> + Check + { e.stopPropagation(); handleResizeStart(e, 'check', columnWidths.check); }} /> - {col.name} - handleResizeStart(e, colKey, width)} /> + handleSort(col.index)}> + {col.name} + { e.stopPropagation(); handleResizeStart(e, colKey, width); }} /> - handleResizeStart(e, 'actions', columnWidths.actions)} /> + { e.stopPropagation(); handleResizeStart(e, 'actions', columnWidths.actions); }} />