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 (
수동 컬럼 편집
-
직접 컬럼을 추가하고 데이터 필드를 매핑하세요
+
+ 직접 컬럼을 추가하고 데이터 필드를 매핑하세요. 드래그하여 순서를 변경할 수 있습니다.
+