From 3332c87293f6ced6337c8f5d66e06b7510b8ca18 Mon Sep 17 00:00:00 2001 From: kjs Date: Mon, 3 Nov 2025 12:18:50 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=BB=AC=EB=9F=BC=20=EB=A6=AC=EC=82=AC?= =?UTF-8?q?=EC=9D=B4=EC=A6=88=20=EB=AC=B4=ED=95=9C=20=EB=A6=AC=EB=A0=8C?= =?UTF-8?q?=EB=8D=94=EB=A7=81=20=EB=B0=8F=20=EC=9B=90=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EB=B3=B5=EA=B7=80=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ref callback에서 state 업데이트 제거 - useEffect + setTimeout으로 초기 너비 측정 (한 번만) - hasInitializedWidths useRef로 중복 측정 방지 - columnRefs useRef로 DOM 직접 참조 - 드래그 중 리렌더링 없이 DOM만 직접 조작 - 부드럽고 정확한 리사이즈 구현 완료 --- .../screen/InteractiveDataTable.tsx | 46 +++++++++++++------ .../table-list/TableListComponent.tsx | 44 ++++++++++++------ 2 files changed, 64 insertions(+), 26 deletions(-) diff --git a/frontend/components/screen/InteractiveDataTable.tsx b/frontend/components/screen/InteractiveDataTable.tsx index c9b24892..5d31da83 100644 --- a/frontend/components/screen/InteractiveDataTable.tsx +++ b/frontend/components/screen/InteractiveDataTable.tsx @@ -107,6 +107,8 @@ export const InteractiveDataTable: React.FC = ({ const [total, setTotal] = useState(0); const [selectedRows, setSelectedRows] = useState>(new Set()); const [columnWidths, setColumnWidths] = useState>({}); + const hasInitializedWidthsRef = useRef(false); + const columnRefs = useRef>({}); // SaveModal 상태 (등록/수정 통합) const [showSaveModal, setShowSaveModal] = useState(false); @@ -409,6 +411,35 @@ export const InteractiveDataTable: React.FC = ({ // 페이지 크기 설정 const pageSize = component.pagination?.pageSize || 10; + // 초기 컬럼 너비 측정 (한 번만) + useEffect(() => { + if (!hasInitializedWidthsRef.current && visibleColumns.length > 0) { + // 약간의 지연을 두고 DOM이 완전히 렌더링된 후 측정 + const timer = setTimeout(() => { + const newWidths: Record = {}; + let hasAnyWidth = false; + + visibleColumns.forEach((column) => { + const thElement = columnRefs.current[column.id]; + if (thElement) { + const measuredWidth = thElement.offsetWidth; + if (measuredWidth > 0) { + newWidths[column.id] = measuredWidth; + hasAnyWidth = true; + } + } + }); + + if (hasAnyWidth) { + setColumnWidths(newWidths); + hasInitializedWidthsRef.current = true; + } + }, 100); + + return () => clearTimeout(timer); + } + }, [visibleColumns]); + // 데이터 로드 함수 const loadData = useCallback( async (page: number = 1, searchParams: Record = {}) => { @@ -1939,18 +1970,7 @@ export const InteractiveDataTable: React.FC = ({ return ( { - // 첫 렌더링 시 실제 너비를 측정해서 상태에 저장 - if (el && !columnWidth) { - const measuredWidth = el.offsetWidth; - if (measuredWidth > 0) { - setColumnWidths(prev => ({ - ...prev, - [column.id]: measuredWidth - })); - } - } - }} + ref={(el) => (columnRefs.current[column.id] = el)} className="relative bg-gradient-to-r from-gray-50 to-slate-50 px-4 font-semibold text-gray-700 select-none" style={{ width: columnWidth ? `${columnWidth}px` : undefined, @@ -1968,7 +1988,7 @@ export const InteractiveDataTable: React.FC = ({ e.preventDefault(); e.stopPropagation(); - const thElement = e.currentTarget.parentElement as HTMLTableCellElement; + const thElement = columnRefs.current[column.id]; if (!thElement) return; const startX = e.clientX; diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index 6a753734..10e17fa5 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -247,6 +247,7 @@ export const TableListComponent: React.FC = ({ const [columnWidths, setColumnWidths] = useState>({}); const columnRefs = useRef>({}); const [isAllSelected, setIsAllSelected] = useState(false); + const hasInitializedWidths = useRef(false); // 필터 설정 관련 상태 const [isFilterSettingOpen, setIsFilterSettingOpen] = useState(false); @@ -794,6 +795,35 @@ export const TableListComponent: React.FC = ({ } }, [tableConfig.refreshInterval, isDesignMode]); + // 초기 컬럼 너비 측정 (한 번만) + useEffect(() => { + if (!hasInitializedWidths.current && visibleColumns.length > 0) { + // 약간의 지연을 두고 DOM이 완전히 렌더링된 후 측정 + const timer = setTimeout(() => { + const newWidths: Record = {}; + let hasAnyWidth = false; + + visibleColumns.forEach((column) => { + const thElement = columnRefs.current[column.columnName]; + if (thElement) { + const measuredWidth = thElement.offsetWidth; + if (measuredWidth > 0) { + newWidths[column.columnName] = measuredWidth; + hasAnyWidth = true; + } + } + }); + + if (hasAnyWidth) { + setColumnWidths(newWidths); + hasInitializedWidths.current = true; + } + }, 100); + + return () => clearTimeout(timer); + } + }, [visibleColumns]); + // ======================================== // 페이지네이션 JSX // ======================================== @@ -1027,19 +1057,7 @@ export const TableListComponent: React.FC = ({ return ( { - columnRefs.current[column.columnName] = el; - // 첫 렌더링 시 실제 너비를 측정해서 상태에 저장 - if (el && !columnWidth) { - const measuredWidth = el.offsetWidth; - if (measuredWidth > 0) { - setColumnWidths(prev => ({ - ...prev, - [column.columnName]: measuredWidth - })); - } - } - }} + ref={(el) => (columnRefs.current[column.columnName] = el)} className={cn( "relative h-10 px-2 py-2 text-xs font-semibold text-foreground overflow-hidden text-ellipsis bg-background select-none sm:h-12 sm:px-6 sm:py-3 sm:text-sm sm:whitespace-nowrap", column.sortable && "cursor-pointer"