"use client"; import React, { useState, useEffect } from "react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { Settings, Layout, ArrowRight, Database, Loader2, Check, ChevronsUpDown, Plus, Trash2, Link2 } from "lucide-react"; import { screenApi } from "@/lib/api/screen"; import { getTableColumns } from "@/lib/api/tableManagement"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { cn } from "@/lib/utils"; import type { ParentDataMapping, LinkedFilter } from "@/contexts/SplitPanelContext"; interface ScreenSplitPanelConfigPanelProps { config: any; onChange: (newConfig: any) => void; } export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSplitPanelConfigPanelProps) { // 화면 목록 상태 const [screens, setScreens] = useState([]); const [isLoadingScreens, setIsLoadingScreens] = useState(true); // Combobox 상태 const [leftOpen, setLeftOpen] = useState(false); const [rightOpen, setRightOpen] = useState(false); // 좌측 화면의 테이블 컬럼 목록 const [leftScreenColumns, setLeftScreenColumns] = useState>([]); const [isLoadingLeftColumns, setIsLoadingLeftColumns] = useState(false); // 우측 화면의 테이블 컬럼 목록 (테이블별로 그룹화) const [rightScreenTables, setRightScreenTables] = useState }>>([]); const [isLoadingRightColumns, setIsLoadingRightColumns] = useState(false); const [localConfig, setLocalConfig] = useState({ screenId: config.screenId || 0, leftScreenId: config.leftScreenId || 0, rightScreenId: config.rightScreenId || 0, splitRatio: config.splitRatio || 50, resizable: config.resizable ?? true, buttonLabel: config.buttonLabel || "데이터 전달", buttonPosition: config.buttonPosition || "center", parentDataMapping: config.parentDataMapping || [] as ParentDataMapping[], linkedFilters: config.linkedFilters || [] as LinkedFilter[], ...config, }); // config prop이 변경되면 localConfig 동기화 useEffect(() => { console.log("🔄 [ScreenSplitPanelConfigPanel] config prop 변경 감지:", config); setLocalConfig({ screenId: config.screenId || 0, leftScreenId: config.leftScreenId || 0, rightScreenId: config.rightScreenId || 0, splitRatio: config.splitRatio || 50, resizable: config.resizable ?? true, buttonLabel: config.buttonLabel || "데이터 전달", buttonPosition: config.buttonPosition || "center", parentDataMapping: config.parentDataMapping || [], linkedFilters: config.linkedFilters || [], ...config, }); }, [config]); // 좌측 화면이 변경되면 해당 화면의 테이블 컬럼 로드 useEffect(() => { const loadLeftScreenColumns = async () => { if (!localConfig.leftScreenId) { setLeftScreenColumns([]); return; } try { setIsLoadingLeftColumns(true); // 좌측 화면 정보 조회 const screenData = await screenApi.getScreen(localConfig.leftScreenId); if (!screenData?.tableName) { console.warn("좌측 화면에 테이블이 설정되지 않았습니다."); setLeftScreenColumns([]); return; } // 테이블 컬럼 조회 const columnsResponse = await getTableColumns(screenData.tableName); if (columnsResponse.success && columnsResponse.data?.columns) { const columns = columnsResponse.data.columns.map((col: any) => ({ columnName: col.column_name || col.columnName, columnLabel: col.column_label || col.columnLabel || col.column_name || col.columnName, })); setLeftScreenColumns(columns); console.log("📋 좌측 화면 컬럼 로드 완료:", columns.length); } } catch (error) { console.error("좌측 화면 컬럼 로드 실패:", error); setLeftScreenColumns([]); } finally { setIsLoadingLeftColumns(false); } }; loadLeftScreenColumns(); }, [localConfig.leftScreenId]); // 우측 화면이 변경되면 해당 화면 및 임베드된 화면들의 테이블 컬럼 로드 useEffect(() => { const loadRightScreenColumns = async () => { if (!localConfig.rightScreenId) { setRightScreenTables([]); return; } try { setIsLoadingRightColumns(true); const tables: Array<{ tableName: string; screenName: string; columns: Array<{ columnName: string; columnLabel: string }> }> = []; // 우측 화면 정보 조회 const screenData = await screenApi.getScreen(localConfig.rightScreenId); // 1. 메인 화면의 테이블 (있는 경우) if (screenData?.tableName) { const columnsResponse = await getTableColumns(screenData.tableName); if (columnsResponse.success && columnsResponse.data?.columns) { tables.push({ tableName: screenData.tableName, screenName: screenData.screenName || "메인 화면", columns: columnsResponse.data.columns.map((col: any) => ({ columnName: col.column_name || col.columnName, columnLabel: col.column_label || col.columnLabel || col.column_name || col.columnName, })), }); } } // 2. 레이아웃에서 임베드된 화면들의 테이블 찾기 (탭, 분할 패널 등) const layoutData = await screenApi.getLayout(localConfig.rightScreenId); const components = layoutData?.components || []; if (components.length > 0) { const embeddedScreenIds = new Set(); // 컴포넌트에서 임베드된 화면 ID 수집 const findEmbeddedScreens = (comps: any[]) => { for (const comp of comps) { const config = comp.componentConfig || {}; // TabsWidget의 탭들 if (comp.componentType === "tabs-widget" && config.tabs) { for (const tab of config.tabs) { if (tab.screenId) { embeddedScreenIds.add(tab.screenId); console.log("🔍 탭에서 화면 발견:", tab.screenId, tab.screenName); } } } // ScreenSplitPanel if (comp.componentType === "screen-split-panel") { if (config.leftScreenId) embeddedScreenIds.add(config.leftScreenId); if (config.rightScreenId) embeddedScreenIds.add(config.rightScreenId); } // EmbeddedScreen if (comp.componentType === "embedded-screen" && config.screenId) { embeddedScreenIds.add(config.screenId); } // 중첩된 컴포넌트 검색 if (comp.children) { findEmbeddedScreens(comp.children); } } }; findEmbeddedScreens(components); console.log("📋 발견된 임베드 화면 ID:", Array.from(embeddedScreenIds)); // 임베드된 화면들의 테이블 컬럼 로드 for (const embeddedScreenId of embeddedScreenIds) { try { const embeddedScreen = await screenApi.getScreen(embeddedScreenId); if (embeddedScreen?.tableName) { // 이미 추가된 테이블인지 확인 if (!tables.find(t => t.tableName === embeddedScreen.tableName)) { const columnsResponse = await getTableColumns(embeddedScreen.tableName); if (columnsResponse.success && columnsResponse.data?.columns) { tables.push({ tableName: embeddedScreen.tableName, screenName: embeddedScreen.screenName || `화면 ${embeddedScreenId}`, columns: columnsResponse.data.columns.map((col: any) => ({ columnName: col.column_name || col.columnName, columnLabel: col.column_label || col.columnLabel || col.column_name || col.columnName, })), }); console.log("✅ 테이블 추가:", embeddedScreen.tableName); } } } } catch (err) { console.warn(`임베드된 화면 ${embeddedScreenId} 로드 실패:`, err); } } } setRightScreenTables(tables); console.log("📋 우측 화면 테이블 로드 완료:", tables.map(t => t.tableName)); } catch (error) { console.error("우측 화면 컬럼 로드 실패:", error); setRightScreenTables([]); } finally { setIsLoadingRightColumns(false); } }; loadRightScreenColumns(); }, [localConfig.rightScreenId]); // 화면 목록 로드 useEffect(() => { const loadScreens = async () => { try { setIsLoadingScreens(true); const response = await screenApi.getScreens({ page: 1, size: 1000 }); if (response.data) { setScreens(response.data); } } catch (error) { console.error("화면 목록 로드 실패:", error); } finally { setIsLoadingScreens(false); } }; loadScreens(); }, []); const updateConfig = (key: string, value: any) => { const newConfig = { ...localConfig, [key]: value, }; setLocalConfig(newConfig); console.log("📝 [ScreenSplitPanelConfigPanel] 설정 변경:", { key, value, newConfig, hasOnChange: !!onChange, }); // 변경 즉시 부모에게 전달 if (onChange) { onChange(newConfig); } }; // 부모 데이터 매핑 추가 const addParentDataMapping = () => { const newMapping: ParentDataMapping = { sourceColumn: "", targetColumn: "", }; const newMappings = [...(localConfig.parentDataMapping || []), newMapping]; updateConfig("parentDataMapping", newMappings); }; // 부모 데이터 매핑 수정 const updateParentDataMapping = (index: number, field: keyof ParentDataMapping, value: string) => { const newMappings = [...(localConfig.parentDataMapping || [])]; newMappings[index] = { ...newMappings[index], [field]: value, }; updateConfig("parentDataMapping", newMappings); }; // 부모 데이터 매핑 삭제 const removeParentDataMapping = (index: number) => { const newMappings = (localConfig.parentDataMapping || []).filter((_: any, i: number) => i !== index); updateConfig("parentDataMapping", newMappings); }; // 연결 필터 추가 const addLinkedFilter = () => { const newFilter: LinkedFilter = { sourceColumn: "", targetColumn: "", }; const newFilters = [...(localConfig.linkedFilters || []), newFilter]; updateConfig("linkedFilters", newFilters); }; // 연결 필터 수정 const updateLinkedFilter = (index: number, field: keyof LinkedFilter, value: string) => { const newFilters = [...(localConfig.linkedFilters || [])]; newFilters[index] = { ...newFilters[index], [field]: value, }; updateConfig("linkedFilters", newFilters); }; // 연결 필터 삭제 const removeLinkedFilter = (index: number) => { const newFilters = (localConfig.linkedFilters || []).filter((_: any, i: number) => i !== index); updateConfig("linkedFilters", newFilters); }; return (
레이아웃 화면 연결필터 데이터전달 {/* 레이아웃 탭 */} 분할 비율 좌측과 우측 패널의 너비 비율을 설정합니다
{localConfig.splitRatio}%
updateConfig("splitRatio", parseInt(e.target.value))} className="h-2" />
20% 50% 80%

사용자가 패널 크기를 조절할 수 있습니다

updateConfig("resizable", checked)} />
{/* 화면 설정 탭 */} 임베드할 화면 선택 좌측과 우측에 표시할 화면을 선택합니다 {isLoadingScreens ? (
화면 목록 로딩 중...
) : ( <>
화면을 찾을 수 없습니다. {screens.map((screen) => ( { updateConfig("leftScreenId", screen.screenId); setLeftOpen(false); }} className="text-xs" >
{screen.screenName} {screen.screenCode}
))}

데이터를 선택할 소스 화면

화면을 찾을 수 없습니다. {screens.map((screen) => ( { updateConfig("rightScreenId", screen.screenId); setRightOpen(false); }} className="text-xs" >
{screen.screenName} {screen.screenCode}
))}

데이터를 받을 타겟 화면

데이터 전달 방법: 좌측 화면에 테이블과 버튼을 배치하고, 버튼의 액션을 "transferData"로 설정하세요.
버튼 설정에서 소스 컴포넌트(테이블), 타겟 화면, 필드 매핑을 지정할 수 있습니다.

)}
{/* 연결 필터 탭 */} 연결 필터 좌측 화면에서 행을 선택하면, 우측 화면의 테이블이 자동으로 필터링됩니다. {!localConfig.leftScreenId || !localConfig.rightScreenId ? (

먼저 "화면" 탭에서 좌측/우측 화면을 모두 선택하세요.

) : isLoadingLeftColumns || isLoadingRightColumns ? (
컬럼 정보 로딩 중...
) : leftScreenColumns.length === 0 || rightScreenTables.length === 0 ? (

{leftScreenColumns.length === 0 && "좌측 화면에 테이블이 설정되지 않았습니다. "} {rightScreenTables.length === 0 && "우측 화면에 테이블이 설정되지 않았습니다."}

) : ( <> {/* 연결 필터 설명 */}

예: 좌측에서 설비를 선택하면 → 우측 점검항목이 해당 설비의 항목만 표시됩니다.
좌측 equipment_code → 우측 equipment_code

{/* 필터 목록 */}
{(localConfig.linkedFilters || []).map((filter: LinkedFilter, index: number) => (
필터 #{index + 1}
))}
{/* 추가 버튼 */} {/* 현재 설정 표시 */}
{(localConfig.linkedFilters || []).length > 0 ? `${localConfig.linkedFilters.length}개 필터 설정됨` : "필터 없음 - 우측 화면에 모든 데이터가 표시됩니다"}
)}
{/* 데이터 전달 탭 */} 부모 데이터 자동 전달 좌측 화면에서 행을 선택하면, 우측 화면의 추가/저장 시 지정된 컬럼 값이 자동으로 포함됩니다. {!localConfig.leftScreenId || !localConfig.rightScreenId ? (

먼저 "화면 설정" 탭에서 좌측/우측 화면을 모두 선택하세요.

) : isLoadingLeftColumns || isLoadingRightColumns ? (
컬럼 정보 로딩 중...
) : leftScreenColumns.length === 0 || rightScreenTables.length === 0 ? (

{leftScreenColumns.length === 0 && "좌측 화면에 테이블이 설정되지 않았습니다. "} {rightScreenTables.length === 0 && "우측 화면에 테이블이 설정되지 않았습니다."}

) : ( <> {/* 우측 화면 테이블 목록 표시 */}

우측 화면에서 감지된 테이블 ({rightScreenTables.length}개):

    {rightScreenTables.map((table) => (
  • • {table.screenName}: {table.tableName}
  • ))}
{/* 매핑 목록 */}
{(localConfig.parentDataMapping || []).map((mapping: ParentDataMapping, index: number) => (
매핑 #{index + 1}
))}
{/* 매핑 추가 버튼 */} {/* 자동 매핑 안내 */}

자동 매핑: 좌측에서 선택한 데이터의 모든 컬럼이 우측 화면에 자동 전달됩니다.
동일한 컬럼명(예: equipment_code)이 있으면 별도 설정 없이 자동으로 매핑됩니다.

{/* 수동 매핑 안내 */}

수동 매핑 (선택사항):
컬럼명이 다른 경우에만 위에서 매핑을 추가하세요.
예: 좌측 user_id → 우측 created_by

)}
{/* 설정 요약 */} 현재 설정
좌측 화면: {localConfig.leftScreenId ? screens.find((s) => s.screenId === localConfig.leftScreenId)?.screenName || `ID: ${localConfig.leftScreenId}` : "미설정"}
우측 화면: {localConfig.rightScreenId ? screens.find((s) => s.screenId === localConfig.rightScreenId)?.screenName || `ID: ${localConfig.rightScreenId}` : "미설정"}
분할 비율: {localConfig.splitRatio}% / {100 - localConfig.splitRatio}%
크기 조절: {localConfig.resizable ? "가능" : "불가능"}
데이터 매핑: {(localConfig.parentDataMapping || []).length > 0 ? `${localConfig.parentDataMapping.length}개 설정` : "미설정"}
); }