From 33ba13b0708cafd8e490a5f73f567082aa4a203e Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 12 Nov 2025 11:15:44 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=20=EC=84=A4=EC=A0=95=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 체크박스 컬럼 위치 보존 (드래그 순서 변경 시 맨 오른쪽으로 이동하는 문제 해결) - 사용자별 컬럼 설정 localStorage 저장 및 불러오기 기능 추가 - useAuth 훅으로 userId 가져오기 - 초기 로드 시 저장된 설정 자동 복원 --- .../table-options/ColumnVisibilityPanel.tsx | 49 +++- .../screen/table-options/FilterPanel.tsx | 16 +- .../screen/table-options/GroupingPanel.tsx | 214 +++++++++++------- .../table-list/TableListComponent.tsx | 114 +++++++--- 4 files changed, 267 insertions(+), 126 deletions(-) diff --git a/frontend/components/screen/table-options/ColumnVisibilityPanel.tsx b/frontend/components/screen/table-options/ColumnVisibilityPanel.tsx index aea67622..2373aa0a 100644 --- a/frontend/components/screen/table-options/ColumnVisibilityPanel.tsx +++ b/frontend/components/screen/table-options/ColumnVisibilityPanel.tsx @@ -17,20 +17,19 @@ import { GripVertical, Eye, EyeOff } from "lucide-react"; import { ColumnVisibility } from "@/types/table-options"; interface Props { - tableId: string; - open: boolean; - onOpenChange: (open: boolean) => void; + isOpen: boolean; + onClose: () => void; } export const ColumnVisibilityPanel: React.FC = ({ - tableId, - open, - onOpenChange, + isOpen, + onClose, }) => { - const { getTable } = useTableOptions(); - const table = getTable(tableId); + const { getTable, selectedTableId } = useTableOptions(); + const table = selectedTableId ? getTable(selectedTableId) : undefined; const [localColumns, setLocalColumns] = useState([]); + const [draggedIndex, setDraggedIndex] = useState(null); // 테이블 정보 로드 useEffect(() => { @@ -62,9 +61,31 @@ export const ColumnVisibilityPanel: React.FC = ({ ); }; + const moveColumn = (fromIndex: number, toIndex: number) => { + const newColumns = [...localColumns]; + const [movedItem] = newColumns.splice(fromIndex, 1); + newColumns.splice(toIndex, 0, movedItem); + setLocalColumns(newColumns); + }; + + const handleDragStart = (index: number) => { + setDraggedIndex(index); + }; + + const handleDragOver = (e: React.DragEvent, index: number) => { + e.preventDefault(); + if (draggedIndex === null || draggedIndex === index) return; + moveColumn(draggedIndex, index); + setDraggedIndex(index); + }; + + const handleDragEnd = () => { + setDraggedIndex(null); + }; + const handleApply = () => { table?.onColumnVisibilityChange(localColumns); - onOpenChange(false); + onClose(); }; const handleReset = () => { @@ -83,7 +104,7 @@ export const ColumnVisibilityPanel: React.FC = ({ const visibleCount = localColumns.filter((col) => col.visible).length; return ( - + @@ -114,14 +135,18 @@ export const ColumnVisibilityPanel: React.FC = ({ {/* 컬럼 리스트 */}
- {localColumns.map((col) => { + {localColumns.map((col, index) => { const columnMeta = table?.columns.find( (c) => c.columnName === col.columnName ); return (
handleDragStart(index)} + onDragOver={(e) => handleDragOver(e, index)} + onDragEnd={handleDragEnd} + className="flex items-center gap-3 rounded-lg border bg-background p-3 transition-colors hover:bg-muted/50 cursor-move" > {/* 드래그 핸들 */} diff --git a/frontend/components/screen/table-options/FilterPanel.tsx b/frontend/components/screen/table-options/FilterPanel.tsx index 8af199f5..c20a0115 100644 --- a/frontend/components/screen/table-options/FilterPanel.tsx +++ b/frontend/components/screen/table-options/FilterPanel.tsx @@ -22,18 +22,16 @@ import { Plus, X } from "lucide-react"; import { TableFilter } from "@/types/table-options"; interface Props { - tableId: string; - open: boolean; - onOpenChange: (open: boolean) => void; + isOpen: boolean; + onClose: () => void; } export const FilterPanel: React.FC = ({ - tableId, - open, - onOpenChange, + isOpen, + onClose, }) => { - const { getTable } = useTableOptions(); - const table = getTable(tableId); + const { getTable, selectedTableId } = useTableOptions(); + const table = selectedTableId ? getTable(selectedTableId) : undefined; const [activeFilters, setActiveFilters] = useState([]); @@ -87,7 +85,7 @@ export const FilterPanel: React.FC = ({ }; return ( - + diff --git a/frontend/components/screen/table-options/GroupingPanel.tsx b/frontend/components/screen/table-options/GroupingPanel.tsx index fb2bb22d..0495991d 100644 --- a/frontend/components/screen/table-options/GroupingPanel.tsx +++ b/frontend/components/screen/table-options/GroupingPanel.tsx @@ -11,23 +11,22 @@ import { import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { ScrollArea } from "@/components/ui/scroll-area"; -import { ArrowRight } from "lucide-react"; +import { ArrowRight, GripVertical, X } from "lucide-react"; interface Props { - tableId: string; - open: boolean; - onOpenChange: (open: boolean) => void; + isOpen: boolean; + onClose: () => void; } export const GroupingPanel: React.FC = ({ - tableId, - open, - onOpenChange, + isOpen, + onClose, }) => { - const { getTable } = useTableOptions(); - const table = getTable(tableId); + const { getTable, selectedTableId } = useTableOptions(); + const table = selectedTableId ? getTable(selectedTableId) : undefined; const [selectedColumns, setSelectedColumns] = useState([]); + const [draggedIndex, setDraggedIndex] = useState(null); const toggleColumn = (columnName: string) => { if (selectedColumns.includes(columnName)) { @@ -37,9 +36,35 @@ export const GroupingPanel: React.FC = ({ } }; + const removeColumn = (columnName: string) => { + setSelectedColumns(selectedColumns.filter((c) => c !== columnName)); + }; + + const moveColumn = (fromIndex: number, toIndex: number) => { + const newColumns = [...selectedColumns]; + const [movedItem] = newColumns.splice(fromIndex, 1); + newColumns.splice(toIndex, 0, movedItem); + setSelectedColumns(newColumns); + }; + + const handleDragStart = (index: number) => { + setDraggedIndex(index); + }; + + const handleDragOver = (e: React.DragEvent, index: number) => { + e.preventDefault(); + if (draggedIndex === null || draggedIndex === index) return; + moveColumn(draggedIndex, index); + setDraggedIndex(index); + }; + + const handleDragEnd = () => { + setDraggedIndex(null); + }; + const applyGrouping = () => { table?.onGroupChange(selectedColumns); - onOpenChange(false); + onClose(); }; const clearGrouping = () => { @@ -48,7 +73,7 @@ export const GroupingPanel: React.FC = ({ }; return ( - + 그룹 설정 @@ -58,89 +83,126 @@ export const GroupingPanel: React.FC = ({
- {/* 상태 표시 */} -
-
- {selectedColumns.length}개 컬럼으로 그룹화 -
- -
- - {/* 컬럼 리스트 */} - -
- {table?.columns.map((col) => { - const isSelected = selectedColumns.includes(col.columnName); - const order = selectedColumns.indexOf(col.columnName) + 1; - - return ( -
- toggleColumn(col.columnName)} - /> - -
-
- {col.columnLabel} -
-
- {col.columnName} -
-
- - {isSelected && ( -
- {order}번째 -
- )} -
- ); - })} -
-
- - {/* 그룹 순서 미리보기 */} + {/* 선택된 컬럼 (드래그 가능) */} {selectedColumns.length > 0 && ( -
-
- 그룹화 순서 +
+
+
+ 그룹화 순서 ({selectedColumns.length}개) +
+
-
+
{selectedColumns.map((colName, index) => { const col = table?.columns.find( (c) => c.columnName === colName ); + if (!col) return null; + return ( - -
- {col?.columnLabel} +
handleDragStart(index)} + onDragOver={(e) => handleDragOver(e, index)} + onDragEnd={handleDragEnd} + className="flex items-center gap-2 rounded-lg border bg-primary/5 p-2 sm:p-3 transition-colors hover:bg-primary/10 cursor-move" + > + + +
+ {index + 1}
- {index < selectedColumns.length - 1 && ( - - )} - + +
+
+ {col.columnLabel} +
+
+ + +
); })}
+ + {/* 그룹화 순서 미리보기 */} +
+
+ {selectedColumns.map((colName, index) => { + const col = table?.columns.find( + (c) => c.columnName === colName + ); + return ( + + {col?.columnLabel} + {index < selectedColumns.length - 1 && ( + + )} + + ); + })} +
+
)} + + {/* 사용 가능한 컬럼 */} +
+
+ 사용 가능한 컬럼 +
+ 0 ? "h-[280px] sm:h-[320px]" : "h-[400px] sm:h-[450px]"}> +
+ {table?.columns + .filter((col) => !selectedColumns.includes(col.columnName)) + .map((col) => { + return ( +
toggleColumn(col.columnName)} + > + toggleColumn(col.columnName)} + className="flex-shrink-0" + /> + +
+
+ {col.columnLabel} +
+
+ {col.columnName} +
+
+
+ ); + })} +
+
+