From da4673f7c4edaccbd3a4f37b8f8b1c46922685b6 Mon Sep 17 00:00:00 2001 From: Christian Vidal Wolf Date: Wed, 8 Apr 2026 20:21:36 +0200 Subject: [PATCH] feat: resizable columns in ProductDescriptions and ArticleDetails --- src/components/ArticleDetails.tsx | 64 ++++++++++++++++++------ src/components/ProductDescriptions.tsx | 69 ++++++++++++++++++++------ 2 files changed, 102 insertions(+), 31 deletions(-) diff --git a/src/components/ArticleDetails.tsx b/src/components/ArticleDetails.tsx index 7ba48b5..e86f8cf 100644 --- a/src/components/ArticleDetails.tsx +++ b/src/components/ArticleDetails.tsx @@ -22,6 +22,14 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp const [page, setPage] = useState(1); const [columnFilters, setColumnFilters] = useState>({}); const [openFilterCol, setOpenFilterCol] = useState(null); + const [columnWidths, setColumnWidths] = useState>({ + [COLUMNS.ARTICLE_NO]: 100, + [COLUMNS.ARTICLE_NAME]: 200, + [COLUMNS.CLASSIFICATION]: 80, + [COLUMNS.ITEM_AVAILABLE]: 80, + [COLUMNS.DETAILS_DE]: 150, + [COLUMNS.DETAILS_EN]: 150, + }); const lines = useMemo(() => Array.from(new Set(data.map(r => r[COLUMNS.LINE]).filter(Boolean))), [data]); @@ -89,6 +97,25 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp } }; + const handleResize = (colIndex: number, e: React.MouseEvent) => { + e.preventDefault(); + const startX = e.pageX; + const startWidth = columnWidths[colIndex] || 100; + + const onMouseMove = (moveEvent: MouseEvent) => { + const newWidth = Math.max(60, startWidth + (moveEvent.pageX - startX)); + setColumnWidths(prev => ({ ...prev, [colIndex]: newWidth })); + }; + + const onMouseUp = () => { + window.removeEventListener('mousemove', onMouseMove); + window.removeEventListener('mouseup', onMouseUp); + }; + + window.addEventListener('mousemove', onMouseMove); + window.addEventListener('mouseup', onMouseUp); + }; + const getUniqueValues = (col: number) => { const values = data.map(r => String(r[col] || '')); return Array.from(new Set(values)).sort(); @@ -185,7 +212,7 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp
- +
{[ @@ -198,13 +225,14 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp ].map(({ col, label }) => ( + @@ -254,11 +288,11 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp isPending ? "bg-yellow-400/20 border-l-4 border-l-yellow-400" : "" )} > - - + - - - -
-
-
handleSort(col)}> +
+
handleSort(col)}> {label} {sortCol === col && ( - sortDesc ? : + sortDesc ? : )}
+ {/* Resizer handle */} +
handleResize(col, e)} + className="absolute right-0 top-0 bottom-0 w-1 cursor-col-resize hover:bg-indigo-500/50 group-hover:bg-slate-700/50 transition-colors z-20" + /> + {openFilterCol === col && ( ))} -
EditEdit
{row[COLUMNS.ARTICLE_NO]} + {row[COLUMNS.ARTICLE_NO]} {row[COLUMNS.ARTICLE_NAME]} + + + {row[COLUMNS.DETAILS_DE] ? ( -
{row[COLUMNS.DETAILS_DE]}
+
{row[COLUMNS.DETAILS_DE]}
) : getBadge(null)}
+ {row[COLUMNS.DETAILS_EN] ? ( -
{row[COLUMNS.DETAILS_EN]}
+
{row[COLUMNS.DETAILS_EN]}
) : getBadge(null)}
diff --git a/src/components/ProductDescriptions.tsx b/src/components/ProductDescriptions.tsx index 62ef47c..a71a280 100644 --- a/src/components/ProductDescriptions.tsx +++ b/src/components/ProductDescriptions.tsx @@ -26,6 +26,17 @@ export function ProductDescriptions({ data, onEdit, rowStatuses }: ProductDescri const [page, setPage] = useState(1); const [columnFilters, setColumnFilters] = useState>({}); const [openFilterCol, setOpenFilterCol] = useState(null); + const [columnWidths, setColumnWidths] = useState>({ + [COLUMNS.ARTICLE_NO]: 100, + [COLUMNS.ARTICLE_NAME]: 250, + [COLUMNS.LINE]: 80, + [COLUMNS.LICENSE]: 120, + [COLUMNS.CLASSIFICATION]: 100, + [COLUMNS.LONG_DE]: 80, + [COLUMNS.LONG_EN]: 80, + [COLUMNS.SHORT_DE]: 80, + [COLUMNS.SHORT_EN]: 80, + }); const lines = useMemo(() => Array.from(new Set(data.map(r => r[COLUMNS.LINE]).filter(Boolean))), [data]); const licenses = useMemo(() => Array.from(new Set(data.map(r => r[COLUMNS.LICENSE]).filter(Boolean))), [data]); @@ -116,6 +127,25 @@ export function ProductDescriptions({ data, onEdit, rowStatuses }: ProductDescri } }; + const handleResize = (colIndex: number, e: React.MouseEvent) => { + e.preventDefault(); + const startX = e.pageX; + const startWidth = columnWidths[colIndex] || 100; + + const onMouseMove = (moveEvent: MouseEvent) => { + const newWidth = Math.max(60, startWidth + (moveEvent.pageX - startX)); + setColumnWidths(prev => ({ ...prev, [colIndex]: newWidth })); + }; + + const onMouseUp = () => { + window.removeEventListener('mousemove', onMouseMove); + window.removeEventListener('mouseup', onMouseUp); + }; + + window.addEventListener('mousemove', onMouseMove); + window.addEventListener('mouseup', onMouseUp); + }; + const getUniqueValues = (col: number) => { // For description columns, return only 'Present' and 'Missing' if (DESCRIPTION_COLUMNS.includes(col)) { @@ -252,7 +282,7 @@ export function ProductDescriptions({ data, onEdit, rowStatuses }: ProductDescri
- +
{[ @@ -268,13 +298,14 @@ export function ProductDescriptions({ data, onEdit, rowStatuses }: ProductDescri ].map(({ col, label }) => ( + @@ -325,11 +362,11 @@ export function ProductDescriptions({ data, onEdit, rowStatuses }: ProductDescri isPending ? "bg-yellow-400/20 border-l-4 border-l-yellow-400" : "" )} > - - - - - + + + + - - - - + + + +
-
-
handleSort(col)}> +
+
handleSort(col)}> {label} {sortCol === col && ( - sortDesc ? : + sortDesc ? : )}
+ {/* Resizer handle */} +
handleResize(col, e)} + className="absolute right-0 top-0 bottom-0 w-1 cursor-col-resize hover:bg-blue-500/50 group-hover:bg-slate-700/50 transition-colors z-20" + /> + {openFilterCol === col && ( ))} -
ActionsActions
{row[COLUMNS.ARTICLE_NO]}{row[COLUMNS.ARTICLE_NAME]}{row[COLUMNS.LINE]}{row[COLUMNS.LICENSE] || '—'} + {row[COLUMNS.ARTICLE_NO]}{row[COLUMNS.ARTICLE_NAME]}{row[COLUMNS.LINE]}{row[COLUMNS.LICENSE] || '—'}