From 3eac70947861f058d7499444f4360057f8782e55 Mon Sep 17 00:00:00 2001 From: dohyeons Date: Mon, 20 Oct 2025 15:43:17 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=9C=84?= =?UTF-8?q?=EC=A0=AF,=20=EC=B0=A8=ED=8A=B8,=20=EB=B7=B0=EC=96=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/ListWidgetConfigModal.tsx | 35 +++--- .../widgets/list-widget/ColumnSelector.tsx | 101 ++++++++++++++++-- .../list-widget/ManualColumnEditor.tsx | 64 ++++++++++- .../components/dashboard/DashboardViewer.tsx | 6 +- 4 files changed, 172 insertions(+), 34 deletions(-) diff --git a/frontend/components/admin/dashboard/widgets/ListWidgetConfigModal.tsx b/frontend/components/admin/dashboard/widgets/ListWidgetConfigModal.tsx index e182f433..debe0c2e 100644 --- a/frontend/components/admin/dashboard/widgets/ListWidgetConfigModal.tsx +++ b/frontend/components/admin/dashboard/widgets/ListWidgetConfigModal.tsx @@ -95,24 +95,21 @@ export function ListWidgetConfigModal({ isOpen, element, onClose, onSave }: List }, []); // 쿼리 실행 결과 처리 - const handleQueryTest = useCallback( - (result: QueryResult) => { - setQueryResult(result); + const handleQueryTest = useCallback((result: QueryResult) => { + setQueryResult(result); - // 자동 모드이고 기존 컬럼이 없을 때만 자동 생성 - if (listConfig.columnMode === "auto" && result.columns.length > 0 && listConfig.columns.length === 0) { - const autoColumns: ListColumn[] = result.columns.map((col, idx) => ({ - id: `col_${idx}`, - label: col, - field: col, - align: "left", - visible: true, - })); - setListConfig((prev) => ({ ...prev, columns: autoColumns })); - } - }, - [listConfig.columnMode, listConfig.columns.length], - ); + // 쿼리 실행할 때마다 컬럼 초기화 후 자동 생성 + if (result.columns.length > 0) { + const autoColumns: ListColumn[] = result.columns.map((col, idx) => ({ + id: `col_${idx}`, + label: col, + field: col, + align: "left", + visible: true, + })); + setListConfig((prev) => ({ ...prev, columns: autoColumns })); + } + }, []); // 다음 단계 const handleNext = () => { @@ -176,9 +173,7 @@ export function ListWidgetConfigModal({ isOpen, element, onClose, onSave }: List {/* 참고: 리스트 위젯은 제목이 항상 표시됩니다 */} -
- 💡 리스트 위젯은 제목이 항상 표시됩니다 -
+
💡 리스트 위젯은 제목이 항상 표시됩니다
{/* 진행 상태 표시 */} diff --git a/frontend/components/admin/dashboard/widgets/list-widget/ColumnSelector.tsx b/frontend/components/admin/dashboard/widgets/list-widget/ColumnSelector.tsx index f1700dbf..37b0f157 100644 --- a/frontend/components/admin/dashboard/widgets/list-widget/ColumnSelector.tsx +++ b/frontend/components/admin/dashboard/widgets/list-widget/ColumnSelector.tsx @@ -1,6 +1,6 @@ "use client"; -import React from "react"; +import React, { useState } from "react"; import { ListColumn } from "../../types"; import { Card } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; @@ -21,8 +21,12 @@ interface ColumnSelectorProps { * - 쿼리 결과에서 컬럼 선택 * - 컬럼명 변경 * - 정렬, 너비, 정렬 방향 설정 + * - 드래그 앤 드롭으로 순서 변경 */ export function ColumnSelector({ availableColumns, selectedColumns, sampleData, onChange }: ColumnSelectorProps) { + const [draggedIndex, setDraggedIndex] = useState(null); + const [dragOverIndex, setDragOverIndex] = useState(null); + // 컬럼 선택/해제 const handleToggle = (field: string) => { const exists = selectedColumns.find((col) => col.field === field); @@ -50,17 +54,53 @@ export function ColumnSelector({ availableColumns, selectedColumns, sampleData, onChange(selectedColumns.map((col) => (col.field === field ? { ...col, align } : col))); }; + // 드래그 시작 + const handleDragStart = (index: number) => { + setDraggedIndex(index); + }; + + // 드래그 오버 - 실시간으로 순서 변경하여 UI 업데이트 + const handleDragOver = (e: React.DragEvent, hoverIndex: number) => { + e.preventDefault(); + if (draggedIndex === null || draggedIndex === hoverIndex) return; + + setDragOverIndex(hoverIndex); + + const newColumns = [...selectedColumns]; + const draggedItem = newColumns[draggedIndex]; + newColumns.splice(draggedIndex, 1); + newColumns.splice(hoverIndex, 0, draggedItem); + + setDraggedIndex(hoverIndex); + onChange(newColumns); + }; + + // 드롭 + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + setDraggedIndex(null); + setDragOverIndex(null); + }; + + // 드래그 종료 + const handleDragEnd = () => { + setDraggedIndex(null); + setDragOverIndex(null); + }; + return (

컬럼 선택 및 설정

-

표시할 컬럼을 선택하고 이름을 변경하세요

+

+ 표시할 컬럼을 선택하고 이름을 변경하세요. 드래그하여 순서를 변경할 수 있습니다. +

- {availableColumns.map((field) => { - const selectedCol = selectedColumns.find((col) => col.field === field); - const isSelected = !!selectedCol; + {/* 선택된 컬럼을 먼저 순서대로 표시 */} + {selectedColumns.map((selectedCol, columnIndex) => { + const field = selectedCol.field; const preview = sampleData[field]; const previewText = preview !== undefined && preview !== null @@ -68,19 +108,36 @@ export function ColumnSelector({ availableColumns, selectedColumns, sampleData, ? JSON.stringify(preview).substring(0, 30) : String(preview).substring(0, 30) : ""; + const isSelected = true; + const isDraggable = true; return (
{ + if (isDraggable) { + handleDragStart(columnIndex); + e.currentTarget.style.cursor = "grabbing"; + } + }} + onDragOver={(e) => isDraggable && handleDragOver(e, columnIndex)} + onDrop={handleDrop} + onDragEnd={(e) => { + handleDragEnd(); + e.currentTarget.style.cursor = "grab"; + }} + className={`rounded-lg border p-4 transition-all ${ isSelected ? "border-blue-300 bg-blue-50" : "border-gray-200" + } ${isDraggable ? "cursor-grab active:cursor-grabbing" : ""} ${ + draggedIndex === columnIndex ? "opacity-50" : "" }`} >
handleToggle(field)} className="mt-1" />
- + {field} {previewText && (예: {previewText})}
@@ -122,6 +179,36 @@ export function ColumnSelector({ availableColumns, selectedColumns, sampleData,
); })} + + {/* 선택되지 않은 컬럼들을 아래에 표시 */} + {availableColumns + .filter((field) => !selectedColumns.find((col) => col.field === field)) + .map((field) => { + const preview = sampleData[field]; + const previewText = + preview !== undefined && preview !== null + ? typeof preview === "object" + ? JSON.stringify(preview).substring(0, 30) + : String(preview).substring(0, 30) + : ""; + const isSelected = false; + const isDraggable = false; + + return ( +
+
+ handleToggle(field)} className="mt-1" /> +
+
+ + {field} + {previewText && (예: {previewText})} +
+
+
+
+ ); + })}
{selectedColumns.length === 0 && ( diff --git a/frontend/components/admin/dashboard/widgets/list-widget/ManualColumnEditor.tsx b/frontend/components/admin/dashboard/widgets/list-widget/ManualColumnEditor.tsx index 1d564816..24fe030c 100644 --- a/frontend/components/admin/dashboard/widgets/list-widget/ManualColumnEditor.tsx +++ b/frontend/components/admin/dashboard/widgets/list-widget/ManualColumnEditor.tsx @@ -1,6 +1,6 @@ "use client"; -import React from "react"; +import React, { useState } from "react"; import { ListColumn } from "../../types"; import { Card } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; @@ -19,8 +19,12 @@ interface ManualColumnEditorProps { * 수동 컬럼 편집 컴포넌트 * - 사용자가 직접 컬럼 추가/삭제 * - 컬럼명과 데이터 필드 직접 매핑 + * - 드래그 앤 드롭으로 순서 변경 */ export function ManualColumnEditor({ availableFields, columns, onChange }: ManualColumnEditorProps) { + const [draggedIndex, setDraggedIndex] = useState(null); + const [dragOverIndex, setDragOverIndex] = useState(null); + // 새 컬럼 추가 const handleAddColumn = () => { const newCol: ListColumn = { @@ -43,12 +47,48 @@ export function ManualColumnEditor({ availableFields, columns, onChange }: Manua onChange(columns.map((col) => (col.id === id ? { ...col, ...updates } : col))); }; + // 드래그 시작 + const handleDragStart = (index: number) => { + setDraggedIndex(index); + }; + + // 드래그 오버 - 실시간으로 순서 변경하여 UI 업데이트 + const handleDragOver = (e: React.DragEvent, hoverIndex: number) => { + e.preventDefault(); + if (draggedIndex === null || draggedIndex === hoverIndex) return; + + setDragOverIndex(hoverIndex); + + const newColumns = [...columns]; + const draggedItem = newColumns[draggedIndex]; + newColumns.splice(draggedIndex, 1); + newColumns.splice(hoverIndex, 0, draggedItem); + + setDraggedIndex(hoverIndex); + onChange(newColumns); + }; + + // 드롭 + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + setDraggedIndex(null); + setDragOverIndex(null); + }; + + // 드래그 종료 + const handleDragEnd = () => { + setDraggedIndex(null); + setDragOverIndex(null); + }; + return (

수동 컬럼 편집

-

직접 컬럼을 추가하고 데이터 필드를 매핑하세요

+

+ 직접 컬럼을 추가하고 데이터 필드를 매핑하세요. 드래그하여 순서를 변경할 수 있습니다. +