좌측 패널(마스터)-우측 패널(디테일) 분할 레이아웃 컴포넌트 추가 EditModal에 isCreateMode 플래그 추가하여 INSERT/UPDATE 분기 처리 dataFilter 기반 정확한 조인 필터링 구현 좌측 패널 선택 데이터를 모달로 자동 전달하는 dataTransferFields 설정 지원 ConfigPanel에서 테이블, 컬럼, 조인 설정 가능
685 lines
24 KiB
TypeScript
685 lines
24 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect, useCallback } from "react";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Switch } from "@/components/ui/switch";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import {
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
} from "@/components/ui/popover";
|
|
import {
|
|
Command,
|
|
CommandEmpty,
|
|
CommandGroup,
|
|
CommandInput,
|
|
CommandItem,
|
|
CommandList,
|
|
} from "@/components/ui/command";
|
|
import { Check, ChevronsUpDown, Plus, X } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import { apiClient } from "@/lib/api/client";
|
|
import type { SplitPanelLayout2Config, ColumnConfig, DataTransferField } from "./types";
|
|
|
|
// lodash set 대체 함수
|
|
const setPath = (obj: any, path: string, value: any): any => {
|
|
const keys = path.split(".");
|
|
const result = { ...obj };
|
|
let current = result;
|
|
|
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
const key = keys[i];
|
|
current[key] = current[key] ? { ...current[key] } : {};
|
|
current = current[key];
|
|
}
|
|
|
|
current[keys[keys.length - 1]] = value;
|
|
return result;
|
|
};
|
|
|
|
interface SplitPanelLayout2ConfigPanelProps {
|
|
config: SplitPanelLayout2Config;
|
|
onChange: (config: SplitPanelLayout2Config) => void;
|
|
}
|
|
|
|
interface TableInfo {
|
|
table_name: string;
|
|
table_comment?: string;
|
|
}
|
|
|
|
interface ColumnInfo {
|
|
column_name: string;
|
|
data_type: string;
|
|
column_comment?: string;
|
|
}
|
|
|
|
interface ScreenInfo {
|
|
screen_id: number;
|
|
screen_name: string;
|
|
screen_code: string;
|
|
}
|
|
|
|
export const SplitPanelLayout2ConfigPanel: React.FC<SplitPanelLayout2ConfigPanelProps> = ({
|
|
config,
|
|
onChange,
|
|
}) => {
|
|
// updateConfig 헬퍼 함수: 경로 기반으로 config를 업데이트
|
|
const updateConfig = useCallback((path: string, value: any) => {
|
|
console.log(`[SplitPanelLayout2ConfigPanel] updateConfig: ${path} =`, value);
|
|
const newConfig = setPath(config, path, value);
|
|
console.log("[SplitPanelLayout2ConfigPanel] newConfig:", newConfig);
|
|
onChange(newConfig);
|
|
}, [config, onChange]);
|
|
|
|
// 상태
|
|
const [tables, setTables] = useState<TableInfo[]>([]);
|
|
const [leftColumns, setLeftColumns] = useState<ColumnInfo[]>([]);
|
|
const [rightColumns, setRightColumns] = useState<ColumnInfo[]>([]);
|
|
const [screens, setScreens] = useState<ScreenInfo[]>([]);
|
|
const [tablesLoading, setTablesLoading] = useState(false);
|
|
const [screensLoading, setScreensLoading] = useState(false);
|
|
|
|
// Popover 상태
|
|
const [leftTableOpen, setLeftTableOpen] = useState(false);
|
|
const [rightTableOpen, setRightTableOpen] = useState(false);
|
|
const [leftModalOpen, setLeftModalOpen] = useState(false);
|
|
const [rightModalOpen, setRightModalOpen] = useState(false);
|
|
|
|
// 테이블 목록 로드
|
|
const loadTables = useCallback(async () => {
|
|
setTablesLoading(true);
|
|
try {
|
|
const response = await apiClient.get("/table/list?userLang=KR");
|
|
const tableList = response.data?.data || response.data || [];
|
|
if (Array.isArray(tableList)) {
|
|
setTables(tableList);
|
|
}
|
|
} catch (error) {
|
|
console.error("테이블 목록 로드 실패:", error);
|
|
} finally {
|
|
setTablesLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
// 화면 목록 로드
|
|
const loadScreens = useCallback(async () => {
|
|
setScreensLoading(true);
|
|
try {
|
|
const response = await apiClient.get("/screen/list");
|
|
console.log("[loadScreens] API 응답:", response.data);
|
|
const screenList = response.data?.data || response.data || [];
|
|
if (Array.isArray(screenList)) {
|
|
const transformedScreens = screenList.map((s: any) => ({
|
|
screen_id: s.screen_id || s.id,
|
|
screen_name: s.screen_name || s.name,
|
|
screen_code: s.screen_code || s.code || "",
|
|
}));
|
|
console.log("[loadScreens] 변환된 화면 목록:", transformedScreens);
|
|
setScreens(transformedScreens);
|
|
}
|
|
} catch (error) {
|
|
console.error("화면 목록 로드 실패:", error);
|
|
} finally {
|
|
setScreensLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
// 컬럼 목록 로드
|
|
const loadColumns = useCallback(async (tableName: string, side: "left" | "right") => {
|
|
if (!tableName) return;
|
|
try {
|
|
const response = await apiClient.get(`/table/${tableName}/columns`);
|
|
const columnList = response.data?.data || response.data || [];
|
|
if (Array.isArray(columnList)) {
|
|
if (side === "left") {
|
|
setLeftColumns(columnList);
|
|
} else {
|
|
setRightColumns(columnList);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(`${side} 컬럼 목록 로드 실패:`, error);
|
|
}
|
|
}, []);
|
|
|
|
// 초기 로드
|
|
useEffect(() => {
|
|
loadTables();
|
|
loadScreens();
|
|
}, [loadTables, loadScreens]);
|
|
|
|
// 테이블 변경 시 컬럼 로드
|
|
useEffect(() => {
|
|
if (config.leftPanel?.tableName) {
|
|
loadColumns(config.leftPanel.tableName, "left");
|
|
}
|
|
}, [config.leftPanel?.tableName, loadColumns]);
|
|
|
|
useEffect(() => {
|
|
if (config.rightPanel?.tableName) {
|
|
loadColumns(config.rightPanel.tableName, "right");
|
|
}
|
|
}, [config.rightPanel?.tableName, loadColumns]);
|
|
|
|
// 테이블 선택 컴포넌트
|
|
const TableSelect: React.FC<{
|
|
value: string;
|
|
onValueChange: (value: string) => void;
|
|
placeholder: string;
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
}> = ({ value, onValueChange, placeholder, open, onOpenChange }) => (
|
|
<Popover open={open} onOpenChange={onOpenChange}>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
role="combobox"
|
|
aria-expanded={open}
|
|
disabled={tablesLoading}
|
|
className="w-full justify-between h-9 text-sm"
|
|
>
|
|
{tablesLoading ? (
|
|
"로딩 중..."
|
|
) : value ? (
|
|
tables.find((t) => t.table_name === value)?.table_comment || value
|
|
) : (
|
|
placeholder
|
|
)}
|
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-full p-0" align="start">
|
|
<Command>
|
|
<CommandInput placeholder="테이블 검색..." className="h-9" />
|
|
<CommandList>
|
|
<CommandEmpty>테이블이 없습니다</CommandEmpty>
|
|
<CommandGroup>
|
|
{tables.map((table) => (
|
|
<CommandItem
|
|
key={table.table_name}
|
|
value={table.table_name}
|
|
onSelect={(selectedValue) => {
|
|
onValueChange(selectedValue);
|
|
onOpenChange(false);
|
|
}}
|
|
>
|
|
<Check
|
|
className={cn(
|
|
"mr-2 h-4 w-4",
|
|
value === table.table_name ? "opacity-100" : "opacity-0"
|
|
)}
|
|
/>
|
|
<span className="flex flex-col">
|
|
<span>{table.table_comment || table.table_name}</span>
|
|
<span className="text-xs text-muted-foreground">{table.table_name}</span>
|
|
</span>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
);
|
|
|
|
// 화면 선택 컴포넌트
|
|
const ScreenSelect: React.FC<{
|
|
value: number | undefined;
|
|
onValueChange: (value: number | undefined) => void;
|
|
placeholder: string;
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
}> = ({ value, onValueChange, placeholder, open, onOpenChange }) => (
|
|
<Popover open={open} onOpenChange={onOpenChange}>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
role="combobox"
|
|
aria-expanded={open}
|
|
disabled={screensLoading}
|
|
className="w-full justify-between h-9 text-sm"
|
|
>
|
|
{screensLoading ? (
|
|
"로딩 중..."
|
|
) : value ? (
|
|
screens.find((s) => s.screen_id === value)?.screen_name || `화면 ${value}`
|
|
) : (
|
|
placeholder
|
|
)}
|
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-full p-0" align="start">
|
|
<Command>
|
|
<CommandInput placeholder="화면 검색..." className="h-9" />
|
|
<CommandList>
|
|
<CommandEmpty>화면이 없습니다</CommandEmpty>
|
|
<CommandGroup>
|
|
{screens.map((screen, index) => (
|
|
<CommandItem
|
|
key={`screen-${screen.screen_id ?? index}`}
|
|
value={`${screen.screen_id}-${screen.screen_name}`}
|
|
onSelect={(selectedValue: string) => {
|
|
const screenId = parseInt(selectedValue.split("-")[0]);
|
|
console.log("[ScreenSelect] onSelect:", { selectedValue, screenId, screen });
|
|
onValueChange(screenId);
|
|
onOpenChange(false);
|
|
}}
|
|
className="flex items-center"
|
|
>
|
|
<div className="flex items-center w-full">
|
|
<Check
|
|
className={cn(
|
|
"mr-2 h-4 w-4 flex-shrink-0",
|
|
value === screen.screen_id ? "opacity-100" : "opacity-0"
|
|
)}
|
|
/>
|
|
<span className="flex flex-col">
|
|
<span>{screen.screen_name}</span>
|
|
<span className="text-xs text-muted-foreground">{screen.screen_code}</span>
|
|
</span>
|
|
</div>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
);
|
|
|
|
// 컬럼 선택 컴포넌트
|
|
const ColumnSelect: React.FC<{
|
|
columns: ColumnInfo[];
|
|
value: string;
|
|
onValueChange: (value: string) => void;
|
|
placeholder: string;
|
|
}> = ({ columns, value, onValueChange, placeholder }) => (
|
|
<Select value={value || ""} onValueChange={onValueChange}>
|
|
<SelectTrigger className="h-9 text-sm">
|
|
<SelectValue placeholder={placeholder} />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{columns.map((col) => (
|
|
<SelectItem key={col.column_name} value={col.column_name}>
|
|
{col.column_comment || col.column_name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
);
|
|
|
|
// 표시 컬럼 추가
|
|
const addDisplayColumn = (side: "left" | "right") => {
|
|
const path = side === "left" ? "leftPanel.displayColumns" : "rightPanel.displayColumns";
|
|
const currentColumns = side === "left"
|
|
? config.leftPanel?.displayColumns || []
|
|
: config.rightPanel?.displayColumns || [];
|
|
|
|
updateConfig(path, [...currentColumns, { name: "", label: "" }]);
|
|
};
|
|
|
|
// 표시 컬럼 삭제
|
|
const removeDisplayColumn = (side: "left" | "right", index: number) => {
|
|
const path = side === "left" ? "leftPanel.displayColumns" : "rightPanel.displayColumns";
|
|
const currentColumns = side === "left"
|
|
? config.leftPanel?.displayColumns || []
|
|
: config.rightPanel?.displayColumns || [];
|
|
|
|
updateConfig(path, currentColumns.filter((_, i) => i !== index));
|
|
};
|
|
|
|
// 표시 컬럼 업데이트
|
|
const updateDisplayColumn = (side: "left" | "right", index: number, field: keyof ColumnConfig, value: any) => {
|
|
const path = side === "left" ? "leftPanel.displayColumns" : "rightPanel.displayColumns";
|
|
const currentColumns = side === "left"
|
|
? [...(config.leftPanel?.displayColumns || [])]
|
|
: [...(config.rightPanel?.displayColumns || [])];
|
|
|
|
if (currentColumns[index]) {
|
|
currentColumns[index] = { ...currentColumns[index], [field]: value };
|
|
updateConfig(path, currentColumns);
|
|
}
|
|
};
|
|
|
|
// 데이터 전달 필드 추가
|
|
const addDataTransferField = () => {
|
|
const currentFields = config.dataTransferFields || [];
|
|
updateConfig("dataTransferFields", [...currentFields, { sourceColumn: "", targetColumn: "" }]);
|
|
};
|
|
|
|
// 데이터 전달 필드 삭제
|
|
const removeDataTransferField = (index: number) => {
|
|
const currentFields = config.dataTransferFields || [];
|
|
updateConfig("dataTransferFields", currentFields.filter((_, i) => i !== index));
|
|
};
|
|
|
|
// 데이터 전달 필드 업데이트
|
|
const updateDataTransferField = (index: number, field: keyof DataTransferField, value: string) => {
|
|
const currentFields = [...(config.dataTransferFields || [])];
|
|
if (currentFields[index]) {
|
|
currentFields[index] = { ...currentFields[index], [field]: value };
|
|
updateConfig("dataTransferFields", currentFields);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6 p-1">
|
|
{/* 좌측 패널 설정 */}
|
|
<div className="space-y-4">
|
|
<h4 className="font-medium text-sm border-b pb-2">좌측 패널 설정 (마스터)</h4>
|
|
|
|
<div className="space-y-3">
|
|
<div>
|
|
<Label className="text-xs">패널 제목</Label>
|
|
<Input
|
|
value={config.leftPanel?.title || ""}
|
|
onChange={(e) => updateConfig("leftPanel.title", e.target.value)}
|
|
placeholder="부서"
|
|
className="h-9 text-sm"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label className="text-xs">테이블 선택</Label>
|
|
<TableSelect
|
|
value={config.leftPanel?.tableName || ""}
|
|
onValueChange={(value) => updateConfig("leftPanel.tableName", value)}
|
|
placeholder="테이블 선택"
|
|
open={leftTableOpen}
|
|
onOpenChange={setLeftTableOpen}
|
|
/>
|
|
</div>
|
|
|
|
{/* 표시 컬럼 */}
|
|
<div>
|
|
<div className="flex items-center justify-between mb-2">
|
|
<Label className="text-xs">표시할 컬럼</Label>
|
|
<Button size="sm" variant="ghost" className="h-6 text-xs" onClick={() => addDisplayColumn("left")}>
|
|
<Plus className="h-3 w-3 mr-1" />
|
|
추가
|
|
</Button>
|
|
</div>
|
|
<div className="space-y-2">
|
|
{(config.leftPanel?.displayColumns || []).map((col, index) => (
|
|
<div key={index} className="flex gap-2 items-center">
|
|
<ColumnSelect
|
|
columns={leftColumns}
|
|
value={col.name}
|
|
onValueChange={(value) => updateDisplayColumn("left", index, "name", value)}
|
|
placeholder="컬럼"
|
|
/>
|
|
<Input
|
|
value={col.label || ""}
|
|
onChange={(e) => updateDisplayColumn("left", index, "label", e.target.value)}
|
|
placeholder="라벨"
|
|
className="h-9 text-sm flex-1"
|
|
/>
|
|
<Button size="sm" variant="ghost" className="h-9 w-9 p-0" onClick={() => removeDisplayColumn("left", index)}>
|
|
<X className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs">검색 표시</Label>
|
|
<Switch
|
|
checked={config.leftPanel?.showSearch || false}
|
|
onCheckedChange={(checked) => updateConfig("leftPanel.showSearch", checked)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs">추가 버튼 표시</Label>
|
|
<Switch
|
|
checked={config.leftPanel?.showAddButton || false}
|
|
onCheckedChange={(checked) => updateConfig("leftPanel.showAddButton", checked)}
|
|
/>
|
|
</div>
|
|
|
|
{config.leftPanel?.showAddButton && (
|
|
<>
|
|
<div>
|
|
<Label className="text-xs">추가 버튼 라벨</Label>
|
|
<Input
|
|
value={config.leftPanel?.addButtonLabel || ""}
|
|
onChange={(e) => updateConfig("leftPanel.addButtonLabel", e.target.value)}
|
|
placeholder="추가"
|
|
className="h-9 text-sm"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label className="text-xs">모달 화면 선택</Label>
|
|
<ScreenSelect
|
|
value={config.leftPanel?.addModalScreenId}
|
|
onValueChange={(value) => updateConfig("leftPanel.addModalScreenId", value)}
|
|
placeholder="모달 화면 선택"
|
|
open={leftModalOpen}
|
|
onOpenChange={setLeftModalOpen}
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 우측 패널 설정 */}
|
|
<div className="space-y-4">
|
|
<h4 className="font-medium text-sm border-b pb-2">우측 패널 설정 (상세)</h4>
|
|
|
|
<div className="space-y-3">
|
|
<div>
|
|
<Label className="text-xs">패널 제목</Label>
|
|
<Input
|
|
value={config.rightPanel?.title || ""}
|
|
onChange={(e) => updateConfig("rightPanel.title", e.target.value)}
|
|
placeholder="사원"
|
|
className="h-9 text-sm"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label className="text-xs">테이블 선택</Label>
|
|
<TableSelect
|
|
value={config.rightPanel?.tableName || ""}
|
|
onValueChange={(value) => updateConfig("rightPanel.tableName", value)}
|
|
placeholder="테이블 선택"
|
|
open={rightTableOpen}
|
|
onOpenChange={setRightTableOpen}
|
|
/>
|
|
</div>
|
|
|
|
{/* 표시 컬럼 */}
|
|
<div>
|
|
<div className="flex items-center justify-between mb-2">
|
|
<Label className="text-xs">표시할 컬럼</Label>
|
|
<Button size="sm" variant="ghost" className="h-6 text-xs" onClick={() => addDisplayColumn("right")}>
|
|
<Plus className="h-3 w-3 mr-1" />
|
|
추가
|
|
</Button>
|
|
</div>
|
|
<div className="space-y-2">
|
|
{(config.rightPanel?.displayColumns || []).map((col, index) => (
|
|
<div key={index} className="flex gap-2 items-center">
|
|
<ColumnSelect
|
|
columns={rightColumns}
|
|
value={col.name}
|
|
onValueChange={(value) => updateDisplayColumn("right", index, "name", value)}
|
|
placeholder="컬럼"
|
|
/>
|
|
<Input
|
|
value={col.label || ""}
|
|
onChange={(e) => updateDisplayColumn("right", index, "label", e.target.value)}
|
|
placeholder="라벨"
|
|
className="h-9 text-sm flex-1"
|
|
/>
|
|
<Button size="sm" variant="ghost" className="h-9 w-9 p-0" onClick={() => removeDisplayColumn("right", index)}>
|
|
<X className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs">검색 표시</Label>
|
|
<Switch
|
|
checked={config.rightPanel?.showSearch || false}
|
|
onCheckedChange={(checked) => updateConfig("rightPanel.showSearch", checked)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs">추가 버튼 표시</Label>
|
|
<Switch
|
|
checked={config.rightPanel?.showAddButton || false}
|
|
onCheckedChange={(checked) => updateConfig("rightPanel.showAddButton", checked)}
|
|
/>
|
|
</div>
|
|
|
|
{config.rightPanel?.showAddButton && (
|
|
<>
|
|
<div>
|
|
<Label className="text-xs">추가 버튼 라벨</Label>
|
|
<Input
|
|
value={config.rightPanel?.addButtonLabel || ""}
|
|
onChange={(e) => updateConfig("rightPanel.addButtonLabel", e.target.value)}
|
|
placeholder="추가"
|
|
className="h-9 text-sm"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label className="text-xs">모달 화면 선택</Label>
|
|
<ScreenSelect
|
|
value={config.rightPanel?.addModalScreenId}
|
|
onValueChange={(value) => updateConfig("rightPanel.addModalScreenId", value)}
|
|
placeholder="모달 화면 선택"
|
|
open={rightModalOpen}
|
|
onOpenChange={setRightModalOpen}
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 연결 설정 */}
|
|
<div className="space-y-4">
|
|
<h4 className="font-medium text-sm border-b pb-2">연결 설정 (조인)</h4>
|
|
|
|
<div className="space-y-3">
|
|
<div>
|
|
<Label className="text-xs">좌측 테이블 조인 컬럼</Label>
|
|
<ColumnSelect
|
|
columns={leftColumns}
|
|
value={config.joinConfig?.leftColumn || ""}
|
|
onValueChange={(value) => updateConfig("joinConfig.leftColumn", value)}
|
|
placeholder="조인 컬럼 선택"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label className="text-xs">우측 테이블 조인 컬럼</Label>
|
|
<ColumnSelect
|
|
columns={rightColumns}
|
|
value={config.joinConfig?.rightColumn || ""}
|
|
onValueChange={(value) => updateConfig("joinConfig.rightColumn", value)}
|
|
placeholder="조인 컬럼 선택"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 데이터 전달 설정 */}
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between border-b pb-2">
|
|
<h4 className="font-medium text-sm">데이터 전달 설정</h4>
|
|
<Button size="sm" variant="ghost" className="h-6 text-xs" onClick={addDataTransferField}>
|
|
<Plus className="h-3 w-3 mr-1" />
|
|
추가
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
{(config.dataTransferFields || []).map((field, index) => (
|
|
<div key={index} className="space-y-2 p-3 border rounded-md">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs font-medium">필드 {index + 1}</span>
|
|
<Button size="sm" variant="ghost" className="h-6 w-6 p-0" onClick={() => removeDataTransferField(index)}>
|
|
<X className="h-3 w-3" />
|
|
</Button>
|
|
</div>
|
|
<div>
|
|
<Label className="text-xs">소스 컬럼 (좌측 패널)</Label>
|
|
<ColumnSelect
|
|
columns={leftColumns}
|
|
value={field.sourceColumn}
|
|
onValueChange={(value) => updateDataTransferField(index, "sourceColumn", value)}
|
|
placeholder="소스 컬럼"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label className="text-xs">타겟 컬럼 (모달 필드명)</Label>
|
|
<Input
|
|
value={field.targetColumn}
|
|
onChange={(e) => updateDataTransferField(index, "targetColumn", e.target.value)}
|
|
placeholder="모달에서 사용할 필드명"
|
|
className="h-9 text-sm"
|
|
/>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 레이아웃 설정 */}
|
|
<div className="space-y-4">
|
|
<h4 className="font-medium text-sm border-b pb-2">레이아웃 설정</h4>
|
|
|
|
<div className="space-y-3">
|
|
<div>
|
|
<Label className="text-xs">좌우 비율 (좌측 %)</Label>
|
|
<Input
|
|
type="number"
|
|
value={config.splitRatio || 30}
|
|
onChange={(e) => updateConfig("splitRatio", parseInt(e.target.value) || 30)}
|
|
min={10}
|
|
max={90}
|
|
className="h-9 text-sm"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs">크기 조절 가능</Label>
|
|
<Switch
|
|
checked={config.resizable !== false}
|
|
onCheckedChange={(checked) => updateConfig("resizable", checked)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs">자동 데이터 로드</Label>
|
|
<Switch
|
|
checked={config.autoLoad !== false}
|
|
onCheckedChange={(checked) => updateConfig("autoLoad", checked)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SplitPanelLayout2ConfigPanel;
|