"use client"; /** * TableColumnDropZone — 테이블 데이터 연결 탭의 드롭 영역 * * 탭1에서 설정한 열 개수만큼 슬롯을 표시하고, * TableColumnPalette에서 드래그한 컬럼을 드롭하여 배치. * 이미 배치된 컬럼은 다른 슬롯으로 드래그하여 위치 교환 가능. * 배치된 컬럼에는 인라인으로 헤더명/숫자형식 설정 가능. */ import React from "react"; import { useDrag, useDrop } from "react-dnd"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { X, Columns, GripVertical } from "lucide-react"; import { TABLE_COLUMN_DND_TYPE } from "./TableColumnPalette"; import type { ComponentConfig } from "@/types/report"; type TableColumn = NonNullable[number]; export const TABLE_SLOT_DND_TYPE = "table-slot"; interface TableColumnDropZoneProps { columns: TableColumn[]; onUpdate: (idx: number, updates: Partial) => void; onDrop: (slotIndex: number, columnName: string, dataType: string) => void; onClear: (slotIndex: number) => void; onMove: (fromIndex: number, toIndex: number) => void; } interface SlotDragItem { type: typeof TABLE_SLOT_DND_TYPE; sourceIndex: number; } interface PaletteDragItem { columnName: string; dataType: string; } interface SlotProps { col: TableColumn; idx: number; onUpdate: (idx: number, updates: Partial) => void; onDrop: (slotIndex: number, columnName: string, dataType: string) => void; onClear: (slotIndex: number) => void; onMove: (fromIndex: number, toIndex: number) => void; } function DropSlot({ col, idx, onUpdate, onDrop, onClear, onMove }: SlotProps) { const isEmpty = !col.field; const [{ isDragging }, drag, preview] = useDrag(() => ({ type: TABLE_SLOT_DND_TYPE, item: { type: TABLE_SLOT_DND_TYPE, sourceIndex: idx } as SlotDragItem, canDrag: !isEmpty, collect: (monitor) => ({ isDragging: monitor.isDragging(), }), }), [idx, isEmpty]); const [{ isOver, canDrop: canDropHere }, drop] = useDrop(() => ({ accept: [TABLE_COLUMN_DND_TYPE, TABLE_SLOT_DND_TYPE], drop: (item: PaletteDragItem | SlotDragItem, monitor) => { const itemType = monitor.getItemType(); if (itemType === TABLE_SLOT_DND_TYPE) { const slotItem = item as SlotDragItem; if (slotItem.sourceIndex !== idx) { onMove(slotItem.sourceIndex, idx); } } else { const paletteItem = item as PaletteDragItem; onDrop(idx, paletteItem.columnName, paletteItem.dataType); } }, canDrop: (item, monitor) => { if (monitor.getItemType() === TABLE_SLOT_DND_TYPE) { return (item as SlotDragItem).sourceIndex !== idx; } return true; }, collect: (monitor) => ({ isOver: monitor.isOver(), canDrop: monitor.canDrop(), }), }), [idx, onDrop, onMove]); const isActive = isOver && canDropHere; const ref = (node: HTMLDivElement | null) => { preview(drop(node)); }; return (
{isEmpty ? (
{isActive ? "여기에 놓으세요" : "컬럼을 드래그하여 배치"} 열 {idx + 1}
) : (
{/* 드래그 핸들 + 컬럼명 배지 + 제거 버튼 */}
{col.field}
{/* 헤더명 */}
onUpdate(idx, { header: e.target.value })} className="h-7 text-xs" />
{/* 숫자형식 */}
)}
); } export function TableColumnDropZone({ columns, onUpdate, onDrop, onClear, onMove }: TableColumnDropZoneProps) { const filledCount = columns.filter((c) => c.field).length; return (
테이블 컬럼 배치 {filledCount}/{columns.length} 배치됨 {filledCount >= 2 && ( (드래그로 위치 변경 가능) )}
{columns.length === 0 ? (

레이아웃 탭에서 열을 먼저 추가하세요.

) : (
{columns.map((col, idx) => ( ))}
)}
); }