"use client"; import React, { useState, useEffect, useRef } 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 { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Switch } from "@/components/ui/switch"; import { Button } from "@/components/ui/button"; import { Plus, X, Check, ChevronsUpDown, Database, Info, Link2, ExternalLink } from "lucide-react"; // allComponents는 현재 사용되지 않지만 향후 확장을 위해 props에 유지 import { EntitySearchInputConfig } from "./config"; import { tableManagementApi } from "@/lib/api/tableManagement"; import { tableTypeApi } from "@/lib/api/screen"; import { cascadingRelationApi, CascadingRelation } from "@/lib/api/cascadingRelation"; import { cn } from "@/lib/utils"; import Link from "next/link"; interface EntitySearchInputConfigPanelProps { config: EntitySearchInputConfig; onConfigChange: (config: EntitySearchInputConfig) => void; currentComponent?: any; // 테이블 패널에서 드래그한 컴포넌트 정보 allComponents?: any[]; // 현재 화면의 모든 컴포넌트 (연쇄 드롭다운 부모 감지용) } export function EntitySearchInputConfigPanel({ config, onConfigChange, currentComponent, allComponents = [], }: EntitySearchInputConfigPanelProps) { const [localConfig, setLocalConfig] = useState(config); const [allTables, setAllTables] = useState([]); const [tableColumns, setTableColumns] = useState([]); const [isLoadingTables, setIsLoadingTables] = useState(false); const [isLoadingColumns, setIsLoadingColumns] = useState(false); const [openTableCombo, setOpenTableCombo] = useState(false); const [openDisplayFieldCombo, setOpenDisplayFieldCombo] = useState(false); const [openValueFieldCombo, setOpenValueFieldCombo] = useState(false); // 연쇄 드롭다운 설정 상태 (SelectBasicConfigPanel과 동일) const [cascadingEnabled, setCascadingEnabled] = useState(!!config.cascadingRelationCode); // 연쇄관계 목록 const [relationList, setRelationList] = useState([]); const [loadingRelations, setLoadingRelations] = useState(false); // 테이블 타입 관리에서 설정된 참조 테이블 정보 const [referenceInfo, setReferenceInfo] = useState<{ referenceTable: string; referenceColumn: string; displayColumn: string; isLoading: boolean; isAutoLoaded: boolean; // 자동 로드되었는지 여부 error: string | null; }>({ referenceTable: "", referenceColumn: "", displayColumn: "", isLoading: false, isAutoLoaded: false, error: null, }); // 자동 설정 완료 여부 (중복 방지) const autoConfigApplied = useRef(false); // 테이블 패널에서 드래그한 컴포넌트인 경우, 참조 테이블 정보 자동 로드 useEffect(() => { const loadReferenceInfo = async () => { // currentComponent에서 소스 테이블/컬럼 정보 추출 const sourceTableName = currentComponent?.tableName || currentComponent?.sourceTableName; const sourceColumnName = currentComponent?.columnName || currentComponent?.sourceColumnName; if (!sourceTableName || !sourceColumnName) { return; } // 이미 config에 테이블명이 설정되어 있고, 자동 로드가 완료되었다면 스킵 if (config.tableName && autoConfigApplied.current) { return; } setReferenceInfo(prev => ({ ...prev, isLoading: true, error: null })); try { // 테이블 타입 관리에서 컬럼 정보 조회 const columns = await tableTypeApi.getColumns(sourceTableName); const columnInfo = columns.find((col: any) => (col.columnName || col.column_name) === sourceColumnName ); if (columnInfo) { const refTable = columnInfo.referenceTable || columnInfo.reference_table || ""; const refColumn = columnInfo.referenceColumn || columnInfo.reference_column || ""; const dispColumn = columnInfo.displayColumn || columnInfo.display_column || ""; // detailSettings에서도 정보 확인 (JSON 파싱) let detailSettings: any = {}; if (columnInfo.detailSettings) { try { if (typeof columnInfo.detailSettings === 'string') { detailSettings = JSON.parse(columnInfo.detailSettings); } else { detailSettings = columnInfo.detailSettings; } } catch { // JSON 파싱 실패 시 무시 } } const finalRefTable = refTable || detailSettings.referenceTable || ""; const finalRefColumn = refColumn || detailSettings.referenceColumn || "id"; const finalDispColumn = dispColumn || detailSettings.displayColumn || "name"; setReferenceInfo({ referenceTable: finalRefTable, referenceColumn: finalRefColumn, displayColumn: finalDispColumn, isLoading: false, isAutoLoaded: true, error: null, }); // 참조 테이블 정보로 config 자동 설정 (config에 아직 설정이 없는 경우만) if (finalRefTable && !config.tableName) { autoConfigApplied.current = true; const newConfig: EntitySearchInputConfig = { ...localConfig, tableName: finalRefTable, valueField: finalRefColumn, displayField: finalDispColumn, }; setLocalConfig(newConfig); onConfigChange(newConfig); } } else { setReferenceInfo({ referenceTable: "", referenceColumn: "", displayColumn: "", isLoading: false, isAutoLoaded: false, error: "컬럼 정보를 찾을 수 없습니다.", }); } } catch (error) { console.error("참조 테이블 정보 로드 실패:", error); setReferenceInfo({ referenceTable: "", referenceColumn: "", displayColumn: "", isLoading: false, isAutoLoaded: false, error: "참조 테이블 정보 로드 실패", }); } }; loadReferenceInfo(); }, [currentComponent?.tableName, currentComponent?.columnName, currentComponent?.sourceTableName, currentComponent?.sourceColumnName]); // 연쇄 관계 목록 로드 useEffect(() => { if (cascadingEnabled && relationList.length === 0) { loadRelationList(); } }, [cascadingEnabled]); // 연쇄 관계 목록 로드 함수 const loadRelationList = async () => { setLoadingRelations(true); try { const response = await cascadingRelationApi.getList("Y"); if (response.success && response.data) { setRelationList(response.data); } } catch (error) { console.error("연쇄 관계 목록 로드 실패:", error); } finally { setLoadingRelations(false); } }; // 전체 테이블 목록 로드 (수동 선택을 위해) useEffect(() => { const loadTables = async () => { setIsLoadingTables(true); try { const response = await tableManagementApi.getTableList(); if (response.success && response.data) { setAllTables(response.data); } } catch (error) { console.error("테이블 목록 로드 실패:", error); } finally { setIsLoadingTables(false); } }; loadTables(); }, []); // 선택된 테이블의 컬럼 목록 로드 useEffect(() => { const loadColumns = async () => { if (!localConfig.tableName) { setTableColumns([]); return; } setIsLoadingColumns(true); try { const response = await tableManagementApi.getColumnList(localConfig.tableName); if (response.success && response.data) { setTableColumns(response.data.columns); } } catch (error) { console.error("컬럼 목록 로드 실패:", error); setTableColumns([]); } finally { setIsLoadingColumns(false); } }; loadColumns(); }, [localConfig.tableName]); // 컴포넌트 변경 시 로컬 상태 동기화 useEffect(() => { setLocalConfig(config); // 연쇄 드롭다운 설정 동기화 setCascadingEnabled(!!config.cascadingRelationCode); }, [config]); const updateConfig = (updates: Partial) => { const newConfig = { ...localConfig, ...updates }; setLocalConfig(newConfig); onConfigChange(newConfig); }; // 연쇄 드롭다운 활성화/비활성화 const handleCascadingToggle = (enabled: boolean) => { setCascadingEnabled(enabled); if (!enabled) { // 비활성화 시 관계 설정 제거 const newConfig = { ...localConfig, cascadingRelationCode: undefined, cascadingRole: undefined, cascadingParentField: undefined, }; setLocalConfig(newConfig); onConfigChange(newConfig); } else { // 활성화 시 관계 목록 로드 loadRelationList(); } }; // 연쇄 관계 선택 (역할은 별도 선택) const handleRelationSelect = (code: string) => { const newConfig = { ...localConfig, cascadingRelationCode: code || undefined, cascadingRole: undefined, // 역할은 별도로 선택 cascadingParentField: undefined, }; setLocalConfig(newConfig); onConfigChange(newConfig); }; // 역할 변경 핸들러 const handleRoleChange = (role: "parent" | "child") => { const selectedRel = relationList.find(r => r.relation_code === localConfig.cascadingRelationCode); if (role === "parent" && selectedRel) { // 부모 역할: 부모 테이블 정보로 설정 const newConfig = { ...localConfig, cascadingRole: role, tableName: selectedRel.parent_table, valueField: selectedRel.parent_value_column, displayField: selectedRel.parent_label_column || selectedRel.parent_value_column, cascadingParentField: undefined, // 부모 역할이면 부모 필드 필요 없음 }; setLocalConfig(newConfig); onConfigChange(newConfig); } else if (role === "child" && selectedRel) { // 자식 역할: 자식 테이블 정보로 설정 const newConfig = { ...localConfig, cascadingRole: role, tableName: selectedRel.child_table, valueField: selectedRel.child_value_column, displayField: selectedRel.child_label_column || selectedRel.child_value_column, }; setLocalConfig(newConfig); onConfigChange(newConfig); } }; // 선택된 관계 정보 const selectedRelation = relationList.find(r => r.relation_code === localConfig.cascadingRelationCode); const addSearchField = () => { const fields = localConfig.searchFields || []; updateConfig({ searchFields: [...fields, ""] }); }; const updateSearchField = (index: number, value: string) => { const fields = [...(localConfig.searchFields || [])]; fields[index] = value; updateConfig({ searchFields: fields }); }; const removeSearchField = (index: number) => { const fields = [...(localConfig.searchFields || [])]; fields.splice(index, 1); updateConfig({ searchFields: fields }); }; const addModalColumn = () => { const columns = localConfig.modalColumns || []; updateConfig({ modalColumns: [...columns, ""] }); }; const updateModalColumn = (index: number, value: string) => { const columns = [...(localConfig.modalColumns || [])]; columns[index] = value; updateConfig({ modalColumns: columns }); }; const removeModalColumn = (index: number) => { const columns = [...(localConfig.modalColumns || [])]; columns.splice(index, 1); updateConfig({ modalColumns: columns }); }; const addAdditionalField = () => { const fields = localConfig.additionalFields || []; updateConfig({ additionalFields: [...fields, ""] }); }; const updateAdditionalField = (index: number, value: string) => { const fields = [...(localConfig.additionalFields || [])]; fields[index] = value; updateConfig({ additionalFields: fields }); }; const removeAdditionalField = (index: number) => { const fields = [...(localConfig.additionalFields || [])]; fields.splice(index, 1); updateConfig({ additionalFields: fields }); }; // 자동 로드된 참조 테이블 정보가 있는지 확인 const hasAutoReference = referenceInfo.isAutoLoaded && referenceInfo.referenceTable; return (
{/* 연쇄 드롭다운 설정 - SelectConfigPanel과 동일한 패턴 */}

연쇄 드롭다운

다른 필드의 값에 따라 옵션이 동적으로 변경됩니다. (예: 창고 선택 → 해당 창고의 위치만 표시)

{cascadingEnabled && (
{/* 관계 선택 */}
{/* 역할 선택 */} {localConfig.cascadingRelationCode && (

{localConfig.cascadingRole === "parent" ? "이 필드가 상위 선택 역할을 합니다. (예: 창고 선택)" : localConfig.cascadingRole === "child" ? "이 필드는 상위 필드 값에 따라 옵션이 변경됩니다. (예: 위치 선택)" : "이 필드의 역할을 선택하세요."}

)} {/* 부모 필드 설정 (자식 역할일 때만) */} {localConfig.cascadingRelationCode && localConfig.cascadingRole === "child" && (
updateConfig({ cascadingParentField: e.target.value || undefined })} placeholder="예: warehouse_code" className="text-xs" />

이 드롭다운의 옵션을 결정할 부모 필드의 컬럼명을 입력하세요.

)} {/* 선택된 관계 정보 표시 */} {selectedRelation && localConfig.cascadingRole && (
{localConfig.cascadingRole === "parent" ? ( <>
부모 역할 (상위 선택)
데이터 소스:{" "} {selectedRelation.parent_table}
저장 값:{" "} {selectedRelation.parent_value_column}
) : ( <>
자식 역할 (하위 선택)
데이터 소스:{" "} {selectedRelation.child_table}
저장 값:{" "} {selectedRelation.child_value_column}
필터 컬럼:{" "} {selectedRelation.child_filter_column}
)}
)} {/* 관계 관리 페이지 링크 */}
)}
{/* 구분선 - 연쇄 드롭다운 비활성화 시에만 표시 */} {!cascadingEnabled && (

아래에서 직접 테이블/필드를 설정하세요.

)} {/* 참조 테이블 자동 로드 정보 표시 */} {referenceInfo.isLoading && (

참조 테이블 정보 로딩 중...

)} {hasAutoReference && !cascadingEnabled && (
테이블 타입에서 자동 설정됨
참조 테이블:
{referenceInfo.referenceTable}
값 필드:
{referenceInfo.referenceColumn || "id"}
표시 필드:
{referenceInfo.displayColumn || "name"}

소스: {currentComponent?.tableName}.{currentComponent?.columnName}

)} {referenceInfo.error && !hasAutoReference && !cascadingEnabled && (

{referenceInfo.error}

테이블을 수동으로 선택하세요.

)}
테이블을 찾을 수 없습니다. {allTables.map((table) => ( { updateConfig({ tableName: table.tableName }); setOpenTableCombo(false); }} className="text-xs sm:text-sm" >
{table.displayName || table.tableName} {table.displayName && table.displayName !== table.tableName && ( {table.tableName} )}
))}
필드를 찾을 수 없습니다. {tableColumns.map((column) => ( { updateConfig({ displayField: column.columnName }); setOpenDisplayFieldCombo(false); }} className="text-xs sm:text-sm" >
{column.displayName || column.columnName} {column.displayName && column.displayName !== column.columnName && ( {column.columnName} )}
))}
필드를 찾을 수 없습니다. {tableColumns.map((column) => ( { updateConfig({ valueField: column.columnName }); setOpenValueFieldCombo(false); }} className="text-xs sm:text-sm" >
{column.displayName || column.columnName} {column.displayName && column.displayName !== column.columnName && ( {column.columnName} )}
))}

{localConfig.mode === "select" && "검색 가능한 드롭다운 형태로 표시됩니다."} {localConfig.mode === "modal" && "모달 팝업에서 데이터를 검색하고 선택합니다."} {(localConfig.mode === "combo" || !localConfig.mode) && "입력 필드와 검색 버튼이 함께 표시됩니다."} {localConfig.mode === "autocomplete" && "입력하면서 자동완성 목록이 표시됩니다."}

updateConfig({ multiple: checked }) } />

{localConfig.multiple ? "여러 항목을 선택할 수 있습니다. 값은 콤마로 구분됩니다." : "하나의 항목만 선택할 수 있습니다."}

updateConfig({ placeholder: e.target.value })} placeholder="검색..." className="h-8 text-xs sm:h-10 sm:text-sm" />
{(localConfig.mode === "modal" || localConfig.mode === "combo") && ( <>
updateConfig({ modalTitle: e.target.value })} placeholder="검색 및 선택" className="h-8 text-xs sm:h-10 sm:text-sm" />
{(localConfig.modalColumns || []).map((column, index) => (
))}
)}
{(localConfig.searchFields || []).map((field, index) => (
))}
updateConfig({ showAdditionalInfo: checked }) } />
{localConfig.showAdditionalInfo && (
{(localConfig.additionalFields || []).map((field, index) => (
))}
)}
); }