diff --git a/client/src/components/production-list-table/production-list-table.component.jsx b/client/src/components/production-list-table/production-list-table.component.jsx index 60a545534..bf5b05d39 100644 --- a/client/src/components/production-list-table/production-list-table.component.jsx +++ b/client/src/components/production-list-table/production-list-table.component.jsx @@ -3,7 +3,7 @@ import { PageHeader } from "@ant-design/pro-layout"; import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react"; import { Button, Dropdown, Input, Space, Statistic, Table } from "antd"; import _ from "lodash"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { closestCenter, DndContext, DragOverlay, PointerSensor, useSensor, useSensors } from "@dnd-kit/core"; import { arrayMove, horizontalListSortingStrategy, SortableContext, useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; @@ -84,7 +84,14 @@ function DraggableHeaderCell(props) { export function ProductionListTable({ loading, data, refetch, bodyshop, technician, currentUser, isDarkMode }) { const [searchText, setSearchText] = useState(""); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + + // NEW: smoother resize + const [isResizing, setIsResizing] = useState(false); + const resizeRafRef = useRef(null); + const pendingResizeRef = useRef(null); + const [activeId, setActiveId] = useState(null); + const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { @@ -92,6 +99,7 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici } }) ); + const { treatments: { Production_List_Status_Colors, Enhanced_Payroll } } = useTreatmentsWithConfig({ @@ -99,8 +107,10 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici names: ["Production_List_Status_Colors", "Enhanced_Payroll"], splitKey: bodyshop.imexshopid }); + const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email); const defaultView = assoc?.default_prod_list_view; + const initialStateRef = useRef( (bodyshop.production_config && bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) || @@ -109,6 +119,7 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici filteredInfo: { text: "" } } ); + const initialColumnsRef = useRef( (initialStateRef.current && bodyshop?.production_config @@ -129,14 +140,30 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici })) || [] ); + const [state, setState] = useState(initialStateRef.current); const [columns, setColumns] = useState(initialColumnsRef.current); + const { t } = useTranslation(); + const matchingColumnConfig = useMemo(() => { return bodyshop?.production_config?.find((p) => p.name === defaultView); }, [bodyshop.production_config, defaultView]); + // NEW: cleanup RAF on unmount useEffect(() => { + return () => { + if (resizeRafRef.current) cancelAnimationFrame(resizeRafRef.current); + }; + }, []); + + useEffect(() => { + // NEW: while resizing, don’t regenerate columns + if (isResizing) return; + + // NEW: bail early BEFORE expensive ProductionListColumns(...) call + if (!_.isEqual(initialColumnsRef.current, columns)) return; + const newColumns = matchingColumnConfig?.columns.columnKeys.map((k) => { return { @@ -152,10 +179,8 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici width: k.width ?? 100 }; }) || []; - // Only update columns if they haven't been manually changed by the user - if (_.isEqual(initialColumnsRef.current, columns)) { - setColumns(newColumns); - } + + setColumns(newColumns); }, [ matchingColumnConfig, bodyshop, @@ -165,7 +190,8 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici Production_List_Status_Colors, refetch, state, - columns + columns, + isResizing ]); const handleTableChange = (pagination, filters, sorter) => { @@ -215,19 +241,54 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici logImEXEvent("production_list_remove_column", { key }); }; - const handleResize = - (index) => - (e, { size }) => { - const nextColumns = [...columns]; - nextColumns[index] = { - ...nextColumns[index], - width: size.width - }; - if (!_.isEqual(nextColumns, columns)) { - setColumns(nextColumns); + // NEW: commit widths via rAF (less jank) + const applyColumnWidth = useCallback((columnKey, width) => { + setColumns((prev) => { + const idx = prev.findIndex((c) => c.key === columnKey); + if (idx === -1) return prev; + + const currentWidth = prev[idx].width ?? 100; + if (currentWidth === width) return prev; + + const next = prev.slice(); + next[idx] = { ...next[idx], width }; + return next; + }); + }, []); + + const handleResize = useCallback( + (columnKey) => + (e, { size }) => { + pendingResizeRef.current = { columnKey, width: size.width }; + + if (resizeRafRef.current) return; + resizeRafRef.current = requestAnimationFrame(() => { + resizeRafRef.current = null; + const pending = pendingResizeRef.current; + if (!pending) return; + applyColumnWidth(pending.columnKey, pending.width); + }); + }, + [applyColumnWidth] + ); + + const handleResizeStart = useCallback(() => { + setIsResizing(true); + }, []); + + const handleResizeStop = useCallback( + (columnKey) => + (e, { size }) => { + setIsResizing(false); + + // Ensure final width is committed + applyColumnWidth(columnKey, size.width); + setHasUnsavedChanges(true); - } - }; + logImEXEvent("production_list_resize_column", { key: columnKey, width: size.width }); + }, + [applyColumnWidth] + ); const addColumn = (newColumn) => { const updatedColumns = [...columns, newColumn]; @@ -384,6 +445,9 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici onSave={() => { setHasUnsavedChanges(false); initialStateRef.current = state; + + // NEW: after saving, treat current columns as the baseline + initialColumnsRef.current = columns; }} /> { - if (!bodyshop.md_ro_statuses.production_colors) return null; - const color = bodyshop.md_ro_statuses.production_colors.find((x) => x.status === record.status); - if (!color) { - if (index % 2 === 0) - return { - style: { - backgroundColor: "var(--table-row-even-bg)" - } - }; - return null; - } - return { - className: "rowWithColor", - style: { - "--bgColor": color.color - ? `rgba(${color.color.r},${color.color.g},${color.color.b},${color.color.a || 1})` - : "var(--status-row-bg-fallback)" + {...(Production_List_Status_Colors.treatment === "on" && + !isResizing && { + onRow: (record, index) => { + if (!bodyshop.md_ro_statuses.production_colors) return null; + const color = bodyshop.md_ro_statuses.production_colors.find((x) => x.status === record.status); + if (!color) { + if (index % 2 === 0) + return { + style: { + backgroundColor: "var(--table-row-even-bg)" + } + }; + return null; } - }; - } - })} + return { + className: "rowWithColor", + style: { + "--bgColor": color.color + ? `rgba(${color.color.r},${color.color.g},${color.color.b},${color.color.a || 1})` + : "var(--status-row-bg-fallback)" + } + }; + } + })} components={{ header: { cell: DraggableHeaderCell } }} - columns={columns.map((c, index) => { + columns={columns.map((c) => { return { ...c, filteredValue: state.filteredInfo[c.key] || null, @@ -449,7 +514,9 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici onHeaderCell: (column) => ({ columnKey: column.key, width: column.width, - onResize: handleResize(index) + onResize: handleResize(column.key), + onResizeStart: handleResizeStart, + onResizeStop: handleResizeStop(column.key) }) }; })} @@ -460,6 +527,7 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici onChange={handleTableChange} /> + {activeId ? (
{ - const { onResize, width, dragAttributes, dragListeners, ...restProps } = props; + const { onResize, onResizeStart, onResizeStop, width, dragAttributes, dragListeners, ...restProps } = props; if (!width) { return ; @@ -10,11 +11,16 @@ const ResizableComponent = forwardRef((props, ref) => { return ( } + resizeHandles={["e"]} + handle={(axis, handleRef) => ( + + )} >