"use client"; import React, { useState, useEffect, useCallback } from "react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { Plus, X, GripVertical, Check, ChevronsUpDown, Table, Layers } from "lucide-react"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; import { RepeatScreenModalProps, CardRowConfig, CardColumnConfig, ColumnSourceConfig, ColumnTargetConfig, DataSourceConfig, GroupingConfig, AggregationConfig, TableLayoutConfig, TableColumnConfig, } from "./types"; import { tableManagementApi } from "@/lib/api/tableManagement"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { cn } from "@/lib/utils"; interface RepeatScreenModalConfigPanelProps { config: Partial; onChange: (config: Partial) => void; } // 검색 가능한 컬럼 선택기 (Combobox) - 240px 최적화 function SourceColumnSelector({ sourceTable, value, onChange, placeholder = "컬럼 선택", }: { sourceTable: string; value: string; onChange: (value: string) => void; placeholder?: string; showTableName?: boolean; }) { const [columns, setColumns] = useState<{ columnName: string; displayName?: string }[]>([]); const [isLoading, setIsLoading] = useState(false); const [open, setOpen] = useState(false); useEffect(() => { const loadColumns = async () => { if (!sourceTable) { setColumns([]); return; } setIsLoading(true); try { const response = await tableManagementApi.getColumnList(sourceTable); if (response.success && response.data) { setColumns(response.data.columns); } } catch (error) { console.error("컬럼 로드 실패:", error); setColumns([]); } finally { setIsLoading(false); } }; loadColumns(); }, [sourceTable]); const selectedColumn = columns.find((col) => col.columnName === value); const displayText = selectedColumn ? selectedColumn.columnName : placeholder; return ( 없음 {columns.map((col) => ( { onChange(col.columnName); setOpen(false); }} className="text-[10px] py-1" > {col.columnName} ))} ); } // 테이블 선택기 (Combobox) - 240px 최적화 function TableSelector({ value, onChange }: { value: string; onChange: (value: string) => void }) { const [tables, setTables] = useState<{ tableName: string; displayName?: string }[]>([]); const [isLoading, setIsLoading] = useState(false); const [open, setOpen] = useState(false); useEffect(() => { const loadTables = async () => { setIsLoading(true); try { const response = await tableManagementApi.getTableList(); if (response.success && response.data) { setTables(response.data.tables || []); } } catch (error) { console.error("테이블 로드 실패:", error); setTables([]); } finally { setIsLoading(false); } }; loadTables(); }, []); const selectedTable = (tables || []).find((t) => t.tableName === value); const displayText = selectedTable ? selectedTable.tableName : "테이블 선택"; return ( 없음 {tables.map((table) => ( { onChange(table.tableName); setOpen(false); }} className="text-[10px] py-1" > {table.tableName} ))} ); } export function RepeatScreenModalConfigPanel({ config, onChange }: RepeatScreenModalConfigPanelProps) { const [localConfig, setLocalConfig] = useState>({ cardLayout: [], dataSource: { sourceTable: "" }, saveMode: "all", cardSpacing: "24px", showCardBorder: true, cardTitle: "카드 {index}", cardMode: "simple", grouping: { enabled: false, groupByField: "", aggregations: [] }, tableLayout: { headerRows: [], tableColumns: [] }, ...config, }); const [allTables, setAllTables] = useState<{ tableName: string; displayName?: string }[]>([]); // 테이블 목록 로드 useEffect(() => { const loadTables = async () => { try { const response = await tableManagementApi.getTableList(); if (response.success && response.data) { setAllTables(response.data.tables || []); } } catch (error) { console.error("테이블 로드 실패:", error); } }; loadTables(); }, []); // Debounced update for input fields const updateConfigDebounced = useCallback( (updates: Partial) => { const timeoutId = setTimeout(() => { setLocalConfig((prev) => { const newConfig = { ...prev, ...updates }; onChange(newConfig); return newConfig; }); }, 500); return () => clearTimeout(timeoutId); }, [onChange] ); // Immediate update for select/switch fields const updateConfig = (updates: Partial) => { setLocalConfig((prev) => { const newConfig = { ...prev, ...updates }; onChange(newConfig); return newConfig; }); }; // === 그룹핑 관련 함수 === const updateGrouping = (updates: Partial) => { updateConfig({ grouping: { ...localConfig.grouping, enabled: localConfig.grouping?.enabled ?? false, groupByField: localConfig.grouping?.groupByField ?? "", aggregations: localConfig.grouping?.aggregations ?? [], ...updates, }, }); }; const addAggregation = () => { const newAgg: AggregationConfig = { sourceField: "", type: "sum", resultField: `agg_${Date.now()}`, label: "", }; updateGrouping({ aggregations: [...(localConfig.grouping?.aggregations || []), newAgg], }); }; const removeAggregation = (index: number) => { const newAggs = [...(localConfig.grouping?.aggregations || [])]; newAggs.splice(index, 1); updateGrouping({ aggregations: newAggs }); }; const updateAggregation = (index: number, updates: Partial) => { const newAggs = [...(localConfig.grouping?.aggregations || [])]; newAggs[index] = { ...newAggs[index], ...updates }; updateGrouping({ aggregations: newAggs }); }; // === 테이블 레이아웃 관련 함수 === const updateTableLayout = (updates: Partial) => { updateConfig({ tableLayout: { ...localConfig.tableLayout, headerRows: localConfig.tableLayout?.headerRows ?? [], tableColumns: localConfig.tableLayout?.tableColumns ?? [], ...updates, }, }); }; const addTableColumn = () => { const newCol: TableColumnConfig = { id: `tcol-${Date.now()}`, field: "", label: "", type: "text", width: "auto", editable: false, }; updateTableLayout({ tableColumns: [...(localConfig.tableLayout?.tableColumns || []), newCol], }); }; const removeTableColumn = (index: number) => { const newCols = [...(localConfig.tableLayout?.tableColumns || [])]; newCols.splice(index, 1); updateTableLayout({ tableColumns: newCols }); }; const updateTableColumn = (index: number, updates: Partial) => { const newCols = [...(localConfig.tableLayout?.tableColumns || [])]; newCols[index] = { ...newCols[index], ...updates }; updateTableLayout({ tableColumns: newCols }); }; // === 헤더 행 관련 함수 (simple 모드와 동일) === const addHeaderRow = () => { const newRow: CardRowConfig = { id: `hrow-${Date.now()}`, columns: [], gap: "16px", layout: "horizontal", }; updateTableLayout({ headerRows: [...(localConfig.tableLayout?.headerRows || []), newRow], }); }; const removeHeaderRow = (rowIndex: number) => { const newRows = [...(localConfig.tableLayout?.headerRows || [])]; newRows.splice(rowIndex, 1); updateTableLayout({ headerRows: newRows }); }; const updateHeaderRow = (rowIndex: number, updates: Partial) => { const newRows = [...(localConfig.tableLayout?.headerRows || [])]; newRows[rowIndex] = { ...newRows[rowIndex], ...updates }; updateTableLayout({ headerRows: newRows }); }; const addHeaderColumn = (rowIndex: number) => { const newRows = [...(localConfig.tableLayout?.headerRows || [])]; const newColumn: CardColumnConfig = { id: `hcol-${Date.now()}`, field: "", label: "", type: "text", width: "auto", editable: false, }; newRows[rowIndex].columns.push(newColumn); updateTableLayout({ headerRows: newRows }); }; const removeHeaderColumn = (rowIndex: number, colIndex: number) => { const newRows = [...(localConfig.tableLayout?.headerRows || [])]; newRows[rowIndex].columns.splice(colIndex, 1); updateTableLayout({ headerRows: newRows }); }; const updateHeaderColumn = (rowIndex: number, colIndex: number, updates: Partial) => { const newRows = [...(localConfig.tableLayout?.headerRows || [])]; newRows[rowIndex].columns[colIndex] = { ...newRows[rowIndex].columns[colIndex], ...updates }; updateTableLayout({ headerRows: newRows }); }; // === Simple 모드 행/컬럼 관련 함수 === const addRow = () => { const newRow: CardRowConfig = { id: `row-${Date.now()}`, columns: [], gap: "16px", layout: "horizontal", }; updateConfig({ cardLayout: [...(localConfig.cardLayout || []), newRow], }); }; const removeRow = (rowIndex: number) => { const newLayout = [...(localConfig.cardLayout || [])]; newLayout.splice(rowIndex, 1); updateConfig({ cardLayout: newLayout }); }; const updateRow = (rowIndex: number, updates: Partial) => { const newLayout = [...(localConfig.cardLayout || [])]; newLayout[rowIndex] = { ...newLayout[rowIndex], ...updates }; updateConfig({ cardLayout: newLayout }); }; const addColumn = (rowIndex: number) => { const newLayout = [...(localConfig.cardLayout || [])]; const newColumn: CardColumnConfig = { id: `col-${Date.now()}`, field: "", label: "", type: "text", width: "auto", editable: true, required: false, }; newLayout[rowIndex].columns.push(newColumn); updateConfig({ cardLayout: newLayout }); }; const removeColumn = (rowIndex: number, colIndex: number) => { const newLayout = [...(localConfig.cardLayout || [])]; newLayout[rowIndex].columns.splice(colIndex, 1); updateConfig({ cardLayout: newLayout }); }; const updateColumn = (rowIndex: number, colIndex: number, updates: Partial) => { const newLayout = [...(localConfig.cardLayout || [])]; newLayout[rowIndex].columns[colIndex] = { ...newLayout[rowIndex].columns[colIndex], ...updates, }; updateConfig({ cardLayout: newLayout }); }; return (
기본 소스 그룹 레이아웃 {/* === 기본 설정 탭 === */}

카드 설정

{ setLocalConfig((prev) => ({ ...prev, cardTitle: e.target.value })); updateConfigDebounced({ cardTitle: e.target.value }); }} placeholder="{part_code} - {part_name}" className="h-7 text-[10px]" />

{"{field}"}: 필드값 사용

updateConfig({ showCardBorder: checked })} className="scale-75" />
{/* 카드 모드 선택 */}