import React, { useState, useEffect, useRef } from "react"; import { commonCodeApi } from "../../../api/commonCode"; import { tableTypeApi } from "../../../api/screen"; interface Option { value: string; label: string; } export interface SelectBasicComponentProps { component: any; componentConfig: any; screenId?: number; onUpdate?: (field: string, value: any) => void; isSelected?: boolean; isDesignMode?: boolean; className?: string; style?: React.CSSProperties; onClick?: () => void; onDragStart?: () => void; onDragEnd?: () => void; [key: string]: any; } // πŸš€ μ „μ—­ μƒνƒœ 관리: λͺ¨λ“  μ»΄ν¬λ„ŒνŠΈκ°€ κ³΅μœ ν•˜λŠ” μƒνƒœ interface GlobalState { tableCategories: Map; // tableName.columnName -> codeCategory codeOptions: Map; // codeCategory -> options activeRequests: Map>; // μ§„ν–‰ 쀑인 μš”μ²­λ“€ subscribers: Set<() => void>; // μƒνƒœ λ³€κ²½ κ΅¬λ…μžλ“€ } const globalState: GlobalState = { tableCategories: new Map(), codeOptions: new Map(), activeRequests: new Map(), subscribers: new Set(), }; // μ „μ—­ μƒνƒœ λ³€κ²½ μ•Œλ¦Ό const notifyStateChange = () => { globalState.subscribers.forEach((callback) => callback()); }; // μΊμ‹œ 유효 μ‹œκ°„ (5λΆ„) const CACHE_DURATION = 5 * 60 * 1000; // πŸ”§ μ „μ—­ ν…Œμ΄λΈ” μ½”λ“œ μΉ΄ν…Œκ³ λ¦¬ λ‘œλ”© (쀑볡 λ°©μ§€) const loadGlobalTableCodeCategory = async (tableName: string, columnName: string): Promise => { const key = `${tableName}.${columnName}`; // 이미 μ§„ν–‰ 쀑인 μš”μ²­μ΄ 있으면 λŒ€κΈ° if (globalState.activeRequests.has(`table_${key}`)) { try { await globalState.activeRequests.get(`table_${key}`); } catch (error) { console.error(`❌ ν…Œμ΄λΈ” μ„€μ • λ‘œλ”© λŒ€κΈ° 쀑 였λ₯˜:`, error); } } // μΊμ‹œλœ 값이 있으면 λ°˜ν™˜ if (globalState.tableCategories.has(key)) { const cachedCategory = globalState.tableCategories.get(key); console.log(`βœ… μΊμ‹œλœ ν…Œμ΄λΈ” μ„€μ • μ‚¬μš©: ${key} -> ${cachedCategory}`); return cachedCategory || null; } // μƒˆλ‘œμš΄ μš”μ²­ 생성 const request = (async () => { try { console.log(`πŸ” ν…Œμ΄λΈ” μ½”λ“œ μΉ΄ν…Œκ³ λ¦¬ 쑰회: ${key}`); const columns = await tableTypeApi.getColumns(tableName); const targetColumn = columns.find((col) => col.columnName === columnName); const codeCategory = targetColumn?.codeCategory && targetColumn.codeCategory !== "none" ? targetColumn.codeCategory : null; // μ „μ—­ μƒνƒœμ— μ €μž₯ globalState.tableCategories.set(key, codeCategory || ""); console.log(`βœ… ν…Œμ΄λΈ” μ„€μ • 쑰회 μ™„λ£Œ: ${key} -> ${codeCategory}`); // μƒνƒœ λ³€κ²½ μ•Œλ¦Ό notifyStateChange(); return codeCategory; } catch (error) { console.error(`❌ ν…Œμ΄λΈ” μ½”λ“œ μΉ΄ν…Œκ³ λ¦¬ 쑰회 μ‹€νŒ¨: ${key}`, error); return null; } finally { globalState.activeRequests.delete(`table_${key}`); } })(); globalState.activeRequests.set(`table_${key}`, request); return request; }; // πŸ”§ μ „μ—­ μ½”λ“œ μ˜΅μ…˜ λ‘œλ”© (쀑볡 λ°©μ§€) const loadGlobalCodeOptions = async (codeCategory: string): Promise => { if (!codeCategory || codeCategory === "none") { return []; } // 이미 μ§„ν–‰ 쀑인 μš”μ²­μ΄ 있으면 λŒ€κΈ° if (globalState.activeRequests.has(`code_${codeCategory}`)) { try { await globalState.activeRequests.get(`code_${codeCategory}`); } catch (error) { console.error(`❌ μ½”λ“œ μ˜΅μ…˜ λ‘œλ”© λŒ€κΈ° 쀑 였λ₯˜:`, error); } } // μΊμ‹œλœ 값이 μœ νš¨ν•˜λ©΄ λ°˜ν™˜ const cached = globalState.codeOptions.get(codeCategory); if (cached && Date.now() - cached.timestamp < CACHE_DURATION) { console.log(`βœ… μΊμ‹œλœ μ½”λ“œ μ˜΅μ…˜ μ‚¬μš©: ${codeCategory} (${cached.options.length}개)`); return cached.options; } // μƒˆλ‘œμš΄ μš”μ²­ 생성 const request = (async () => { try { console.log(`πŸ”„ μ½”λ“œ μ˜΅μ…˜ λ‘œλ”©: ${codeCategory}`); const response = await commonCodeApi.codes.getList(codeCategory, { isActive: true }); console.log(`πŸ” [API 응닡 원본] ${codeCategory}:`, { response, success: response.success, data: response.data, dataType: typeof response.data, isArray: Array.isArray(response.data), dataLength: response.data?.length, firstItem: response.data?.[0], }); if (response.success && response.data) { const options = response.data.map((code: any, index: number) => { console.log(`πŸ” [μ½”λ“œ λ§€ν•‘] ${index}:`, { originalCode: code, codeKeys: Object.keys(code), values: Object.values(code), // κ°€λŠ₯ν•œ λͺ¨λ“  ν•„λ“œ 확인 code: code.code, codeName: code.codeName, name: code.name, label: code.label, // λŒ€λ¬Έμž 버전 CODE: code.CODE, CODE_NAME: code.CODE_NAME, NAME: code.NAME, LABEL: code.LABEL, // μŠ€λ„€μ΄ν¬ μΌ€μ΄μŠ€ code_name: code.code_name, code_value: code.code_value, // 기타 κ°€λŠ₯ν•œ ν•„λ“œλ“€ value: code.value, text: code.text, title: code.title, description: code.description, }); // μ‹€μ œ κ°’ μ°ΎκΈ° μ‹œλ„ (μš°μ„ μˆœμœ„ 순) const actualValue = code.code || code.CODE || code.value || code.code_value || `code_${index}`; const actualLabel = code.codeName || code.name || code.CODE_NAME || code.NAME || code.label || code.LABEL || code.text || code.title || code.description || actualValue; console.log(`✨ [μ΅œμ’… λ§€ν•‘] ${index}:`, { actualValue, actualLabel, hasValue: !!actualValue, hasLabel: !!actualLabel, }); return { value: actualValue, label: actualLabel, }; }); console.log(`πŸ” [μ΅œμ’… μ˜΅μ…˜ λ°°μ—΄] ${codeCategory}:`, { optionsLength: options.length, options: options.map((opt, idx) => ({ index: idx, value: opt.value, label: opt.label, hasLabel: !!opt.label, hasValue: !!opt.value, })), }); // μ „μ—­ μƒνƒœμ— μ €μž₯ globalState.codeOptions.set(codeCategory, { options, timestamp: Date.now(), }); console.log(`βœ… μ½”λ“œ μ˜΅μ…˜ λ‘œλ”© μ™„λ£Œ: ${codeCategory} (${options.length}개)`); // μƒνƒœ λ³€κ²½ μ•Œλ¦Ό notifyStateChange(); return options; } else { console.log(`⚠️ 빈 응닡: ${codeCategory}`); return []; } } catch (error) { console.error(`❌ μ½”λ“œ μ˜΅μ…˜ λ‘œλ”© μ‹€νŒ¨: ${codeCategory}`, error); return []; } finally { globalState.activeRequests.delete(`code_${codeCategory}`); } })(); globalState.activeRequests.set(`code_${codeCategory}`, request); return request; }; const SelectBasicComponent: React.FC = ({ component, componentConfig, screenId, onUpdate, isSelected = false, isDesignMode = false, className, style, onClick, onDragStart, onDragEnd, ...props }) => { const [isOpen, setIsOpen] = useState(false); const [selectedValue, setSelectedValue] = useState(componentConfig?.value || ""); const [selectedLabel, setSelectedLabel] = useState(""); const [codeOptions, setCodeOptions] = useState([]); const [isLoadingCodes, setIsLoadingCodes] = useState(false); const [dynamicCodeCategory, setDynamicCodeCategory] = useState(null); const [globalStateVersion, setGlobalStateVersion] = useState(0); // μ „μ—­ μƒνƒœ λ³€κ²½ κ°μ§€μš© const selectRef = useRef(null); // μ½”λ“œ μΉ΄ν…Œκ³ λ¦¬ κ²°μ •: 동적 μΉ΄ν…Œκ³ λ¦¬ > μ„€μ • μΉ΄ν…Œκ³ λ¦¬ const codeCategory = dynamicCodeCategory || componentConfig?.codeCategory; // πŸš€ μ „μ—­ μƒνƒœ ꡬ독 및 동기화 useEffect(() => { const updateFromGlobalState = () => { setGlobalStateVersion((prev) => prev + 1); }; // μ „μ—­ μƒνƒœ λ³€κ²½ ꡬ독 globalState.subscribers.add(updateFromGlobalState); return () => { globalState.subscribers.delete(updateFromGlobalState); }; }, []); // πŸ”§ ν…Œμ΄λΈ” μ½”λ“œ μΉ΄ν…Œκ³ λ¦¬ λ‘œλ“œ (μ „μ—­ μƒνƒœ μ‚¬μš©) const loadTableCodeCategory = async () => { if (!component.tableName || !component.columnName) return; try { console.log(`πŸ” [${component.id}] μ „μ—­ ν…Œμ΄λΈ” μ½”λ“œ μΉ΄ν…Œκ³ λ¦¬ 쑰회`); const category = await loadGlobalTableCodeCategory(component.tableName, component.columnName); if (category !== dynamicCodeCategory) { console.log(`πŸ”„ [${component.id}] μ½”λ“œ μΉ΄ν…Œκ³ λ¦¬ λ³€κ²½: ${dynamicCodeCategory} β†’ ${category}`); setDynamicCodeCategory(category); } } catch (error) { console.error(`❌ [${component.id}] ν…Œμ΄λΈ” μ½”λ“œ μΉ΄ν…Œκ³ λ¦¬ 쑰회 μ‹€νŒ¨:`, error); } }; // πŸ”§ μ½”λ“œ μ˜΅μ…˜ λ‘œλ“œ (μ „μ—­ μƒνƒœ μ‚¬μš©) const loadCodeOptions = async (category: string) => { if (!category || category === "none") { setCodeOptions([]); setIsLoadingCodes(false); return; } try { setIsLoadingCodes(true); console.log(`πŸ”„ [${component.id}] μ „μ—­ μ½”λ“œ μ˜΅μ…˜ λ‘œλ”©: ${category}`); const options = await loadGlobalCodeOptions(category); setCodeOptions(options); console.log(`βœ… [${component.id}] μ½”λ“œ μ˜΅μ…˜ μ—…λ°μ΄νŠΈ μ™„λ£Œ: ${category} (${options.length}개)`); } catch (error) { console.error(`❌ [${component.id}] μ½”λ“œ μ˜΅μ…˜ λ‘œλ”© μ‹€νŒ¨:`, error); setCodeOptions([]); } finally { setIsLoadingCodes(false); } }; // 초기 ν…Œμ΄λΈ” μ½”λ“œ μΉ΄ν…Œκ³ λ¦¬ λ‘œλ“œ useEffect(() => { loadTableCodeCategory(); }, [component.tableName, component.columnName]); // μ „μ—­ μƒνƒœ λ³€κ²½ μ‹œ 동기화 useEffect(() => { if (component.tableName && component.columnName) { const key = `${component.tableName}.${component.columnName}`; const cachedCategory = globalState.tableCategories.get(key); if (cachedCategory && cachedCategory !== dynamicCodeCategory) { console.log(`πŸ”„ [${component.id}] μ „μ—­ μƒνƒœ 동기화: ${dynamicCodeCategory} β†’ ${cachedCategory}`); setDynamicCodeCategory(cachedCategory || null); } } }, [globalStateVersion, component.tableName, component.columnName]); // μ½”λ“œ μΉ΄ν…Œκ³ λ¦¬ λ³€κ²½ μ‹œ μ˜΅μ…˜ λ‘œλ“œ useEffect(() => { if (codeCategory && codeCategory !== "none") { // μ „μ—­ μΊμ‹œλœ μ˜΅μ…˜λΆ€ν„° 확인 const cached = globalState.codeOptions.get(codeCategory); if (cached && Date.now() - cached.timestamp < CACHE_DURATION) { console.log(`πŸš€ [${component.id}] μ „μ—­ μΊμ‹œ μ¦‰μ‹œ 적용: ${codeCategory} (${cached.options.length}개)`); setCodeOptions(cached.options); setIsLoadingCodes(false); } else { loadCodeOptions(codeCategory); } } else { setCodeOptions([]); setIsLoadingCodes(false); } }, [codeCategory]); // μ „μ—­ μƒνƒœμ—μ„œ μ½”λ“œ μ˜΅μ…˜ λ³€κ²½ 감지 useEffect(() => { if (codeCategory) { const cached = globalState.codeOptions.get(codeCategory); if (cached && Date.now() - cached.timestamp < CACHE_DURATION) { if (JSON.stringify(cached.options) !== JSON.stringify(codeOptions)) { console.log(`πŸ”„ [${component.id}] μ „μ—­ μ˜΅μ…˜ λ³€κ²½ 감지: ${codeCategory}`); setCodeOptions(cached.options); } } } }, [globalStateVersion, codeCategory]); // μ„ νƒλœ 값에 λ”°λ₯Έ 라벨 μ—…λ°μ΄νŠΈ useEffect(() => { const getAllOptions = () => { const configOptions = componentConfig.options || []; return [...codeOptions, ...configOptions]; }; const options = getAllOptions(); const selectedOption = options.find((option) => option.value === selectedValue); const newLabel = selectedOption?.label || ""; if (newLabel !== selectedLabel) { setSelectedLabel(newLabel); } }, [selectedValue, codeOptions, componentConfig.options]); // 클릭 이벀트 ν•Έλ“€λŸ¬ (μ „μ—­ μƒνƒœ μƒˆλ‘œκ³ μΉ¨) const handleToggle = () => { if (isDesignMode) return; console.log(`πŸ–±οΈ [${component.id}] λ“œλ‘­λ‹€μš΄ ν† κΈ€: ${isOpen} β†’ ${!isOpen}`); console.log(`πŸ“Š [${component.id}] ν˜„μž¬ μƒνƒœ:`, { isDesignMode, isLoadingCodes, allOptionsLength: allOptions.length, allOptions: allOptions.map((o) => ({ value: o.value, label: o.label })), }); // λ“œλ‘­λ‹€μš΄μ„ μ—΄ λ•Œ μ „μ—­ μƒνƒœ μƒˆλ‘œκ³ μΉ¨ if (!isOpen) { console.log(`πŸ–±οΈ [${component.id}] μ…€λ ‰νŠΈλ°•μŠ€ 클릭 - μ „μ—­ μƒνƒœ μƒˆλ‘œκ³ μΉ¨`); // ν…Œμ΄λΈ” μ„€μ • μΊμ‹œ λ¬΄νš¨ν™” ν›„ μž¬λ‘œλ“œ if (component.tableName && component.columnName) { const key = `${component.tableName}.${component.columnName}`; globalState.tableCategories.delete(key); // ν˜„μž¬ μ½”λ“œ μΉ΄ν…Œκ³ λ¦¬μ˜ μΊμ‹œλ„ λ¬΄νš¨ν™” if (dynamicCodeCategory) { globalState.codeOptions.delete(dynamicCodeCategory); console.log(`πŸ—‘οΈ [${component.id}] μ½”λ“œ μ˜΅μ…˜ μΊμ‹œ λ¬΄νš¨ν™”: ${dynamicCodeCategory}`); // κ°•μ œλ‘œ μƒˆλ‘œμš΄ API 호좜 μˆ˜ν–‰ console.log(`πŸ”„ [${component.id}] κ°•μ œ μ½”λ“œ μ˜΅μ…˜ μž¬λ‘œλ“œ μ‹œμž‘: ${dynamicCodeCategory}`); loadCodeOptions(dynamicCodeCategory); } loadTableCodeCategory(); } } setIsOpen(!isOpen); }; // μ˜΅μ…˜ 선택 ν•Έλ“€λŸ¬ const handleOptionSelect = (value: string, label: string) => { setSelectedValue(value); setSelectedLabel(label); setIsOpen(false); if (onUpdate) { onUpdate("value", value); } console.log(`βœ… [${component.id}] μ˜΅μ…˜ 선택:`, { value, label }); }; // μ™ΈλΆ€ 클릭 μ‹œ λ“œλ‘­λ‹€μš΄ λ‹«κΈ° useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (selectRef.current && !selectRef.current.contains(event.target as Node)) { setIsOpen(false); } }; if (isOpen) { document.addEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [isOpen]); // πŸš€ μ‹€μ‹œκ°„ μ—…λ°μ΄νŠΈλ₯Ό μœ„ν•œ 이벀트 λ¦¬μŠ€λ„ˆ useEffect(() => { const handleFocus = () => { console.log(`πŸ‘οΈ [${component.id}] μœˆλ„μš° 포컀슀 - μ „μ—­ μƒνƒœ μƒˆλ‘œκ³ μΉ¨`); if (component.tableName && component.columnName) { const key = `${component.tableName}.${component.columnName}`; globalState.tableCategories.delete(key); // μΊμ‹œ λ¬΄νš¨ν™” loadTableCodeCategory(); } }; const handleVisibilityChange = () => { if (!document.hidden) { console.log(`πŸ‘οΈ [${component.id}] νŽ˜μ΄μ§€ κ°€μ‹œμ„± λ³€κ²½ - μ „μ—­ μƒνƒœ μƒˆλ‘œκ³ μΉ¨`); if (component.tableName && component.columnName) { const key = `${component.tableName}.${component.columnName}`; globalState.tableCategories.delete(key); // μΊμ‹œ λ¬΄νš¨ν™” loadTableCodeCategory(); } } }; window.addEventListener("focus", handleFocus); document.addEventListener("visibilitychange", handleVisibilityChange); return () => { window.removeEventListener("focus", handleFocus); document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, [component.tableName, component.columnName]); // λͺ¨λ“  μ˜΅μ…˜ κ°€μ Έμ˜€κΈ° const getAllOptions = () => { const configOptions = componentConfig.options || []; console.log(`πŸ”§ [${component.id}] μ˜΅μ…˜ 병합:`, { codeOptionsLength: codeOptions.length, codeOptions: codeOptions.map((o) => ({ value: o.value, label: o.label })), configOptionsLength: configOptions.length, configOptions: configOptions.map((o) => ({ value: o.value, label: o.label })), }); return [...codeOptions, ...configOptions]; }; const allOptions = getAllOptions(); const placeholder = componentConfig.placeholder || "μ„ νƒν•˜μ„Έμš”"; return (
{/* μ»€μŠ€ν…€ μ…€λ ‰νŠΈ λ°•μŠ€ */}
{selectedLabel || placeholder} {/* λ“œλ‘­λ‹€μš΄ μ•„μ΄μ½˜ */}
{/* λ“œλ‘­λ‹€μš΄ μ˜΅μ…˜ */} {isOpen && !isDesignMode && (
{(() => { console.log(`🎨 [${component.id}] λ“œλ‘­λ‹€μš΄ λ Œλ”λ§:`, { isOpen, isDesignMode, isLoadingCodes, allOptionsLength: allOptions.length, allOptions: allOptions.map((o) => ({ value: o.value, label: o.label })), }); return null; })()} {isLoadingCodes ? (
λ‘œλ”© 쀑...
) : allOptions.length > 0 ? ( allOptions.map((option, index) => (
handleOptionSelect(option.value, option.label)} > {option.label || option.value || `μ˜΅μ…˜ ${index + 1}`}
)) ) : (
μ˜΅μ…˜μ΄ μ—†μŠ΅λ‹ˆλ‹€
)}
)}
); }; // Wrapper μ»΄ν¬λ„ŒνŠΈ (κΈ°μ‘΄ ν˜Έν™˜μ„±μ„ μœ„ν•΄) export const SelectBasicWrapper = SelectBasicComponent; // κΈ°λ³Έ export export { SelectBasicComponent };