테스트테이블 생성 및 오류 수정

This commit is contained in:
kjs
2025-09-19 02:15:21 +09:00
parent ddcecfd5e2
commit f7d884568b
20 changed files with 1024 additions and 180 deletions

View File

@@ -1,6 +1,7 @@
import React, { useState, useEffect, useRef } from "react";
import { commonCodeApi } from "../../../api/commonCode";
import { tableTypeApi } from "../../../api/screen";
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
interface Option {
value: string;
@@ -14,11 +15,14 @@ export interface SelectBasicComponentProps {
onUpdate?: (field: string, value: any) => void;
isSelected?: boolean;
isDesignMode?: boolean;
isInteractive?: boolean;
onFormDataChange?: (fieldName: string, value: any) => void;
className?: string;
style?: React.CSSProperties;
onClick?: () => void;
onDragStart?: () => void;
onDragEnd?: () => void;
value?: any; // 외부에서 전달받는 값
[key: string]: any;
}
@@ -164,6 +168,7 @@ const loadGlobalCodeOptions = async (codeCategory: string): Promise<Option[]> =>
const actualValue = code.code || code.CODE || code.value || code.code_value || `code_${index}`;
const actualLabel =
code.codeName ||
code.code_name || // 스네이크 케이스 추가!
code.name ||
code.CODE_NAME ||
code.NAME ||
@@ -233,16 +238,34 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
onUpdate,
isSelected = false,
isDesignMode = false,
isInteractive = false,
onFormDataChange,
className,
style,
onClick,
onDragStart,
onDragEnd,
value: externalValue, // 명시적으로 value prop 받기
...props
}) => {
const [isOpen, setIsOpen] = useState(false);
const [selectedValue, setSelectedValue] = useState(componentConfig?.value || "");
// webTypeConfig 또는 componentConfig 사용 (DynamicWebTypeRenderer 호환성)
const config = (props as any).webTypeConfig || componentConfig || {};
// 외부에서 전달받은 value가 있으면 우선 사용, 없으면 config.value 사용
const [selectedValue, setSelectedValue] = useState(externalValue || config?.value || "");
const [selectedLabel, setSelectedLabel] = useState("");
console.log("🔍 SelectBasicComponent 초기화:", {
componentId: component.id,
externalValue,
componentConfigValue: componentConfig?.value,
webTypeConfigValue: (props as any).webTypeConfig?.value,
configValue: config?.value,
finalSelectedValue: externalValue || config?.value || "",
props: Object.keys(props),
});
const [codeOptions, setCodeOptions] = useState<Option[]>([]);
const [isLoadingCodes, setIsLoadingCodes] = useState(false);
const [dynamicCodeCategory, setDynamicCodeCategory] = useState<string | null>(null);
@@ -250,7 +273,25 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
const selectRef = useRef<HTMLDivElement>(null);
// 코드 카테고리 결정: 동적 카테고리 > 설정 카테고리
const codeCategory = dynamicCodeCategory || componentConfig?.codeCategory;
const codeCategory = dynamicCodeCategory || config?.codeCategory;
// 외부 value prop 변경 시 selectedValue 업데이트
useEffect(() => {
const newValue = externalValue || config?.value || "";
// 값이 실제로 다른 경우에만 업데이트 (빈 문자열도 유효한 값으로 처리)
if (newValue !== selectedValue) {
console.log(`🔄 SelectBasicComponent value 업데이트: "${selectedValue}" → "${newValue}"`);
console.log(`🔍 업데이트 조건 분석:`, {
externalValue,
componentConfigValue: componentConfig?.value,
configValue: config?.value,
newValue,
selectedValue,
shouldUpdate: newValue !== selectedValue,
});
setSelectedValue(newValue);
}
}, [externalValue, config?.value]);
// 🚀 전역 상태 구독 및 동기화
useEffect(() => {
@@ -359,7 +400,7 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
// 선택된 값에 따른 라벨 업데이트
useEffect(() => {
const getAllOptions = () => {
const configOptions = componentConfig.options || [];
const configOptions = config.options || [];
return [...codeOptions, ...configOptions];
};
@@ -370,7 +411,7 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
if (newLabel !== selectedLabel) {
setSelectedLabel(newLabel);
}
}, [selectedValue, codeOptions, componentConfig.options]);
}, [selectedValue, codeOptions, config.options]);
// 클릭 이벤트 핸들러 (전역 상태 새로고침)
const handleToggle = () => {
@@ -416,10 +457,23 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
setSelectedLabel(label);
setIsOpen(false);
// 디자인 모드에서의 컴포넌트 속성 업데이트
if (onUpdate) {
onUpdate("value", value);
}
// 인터랙티브 모드에서 폼 데이터 업데이트 (TextInputComponent와 동일한 로직)
if (isInteractive && onFormDataChange && component.columnName) {
console.log(`📤 SelectBasicComponent -> onFormDataChange 호출: ${component.columnName} = "${value}"`);
onFormDataChange(component.columnName, value);
} else {
console.log("❌ SelectBasicComponent onFormDataChange 조건 미충족:", {
isInteractive,
hasOnFormDataChange: !!onFormDataChange,
hasColumnName: !!component.columnName,
});
}
console.log(`✅ [${component.id}] 옵션 선택:`, { value, label });
};
@@ -473,7 +527,7 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
// 모든 옵션 가져오기
const getAllOptions = () => {
const configOptions = componentConfig.options || [];
const configOptions = config.options || [];
console.log(`🔧 [${component.id}] 옵션 병합:`, {
codeOptionsLength: codeOptions.length,
codeOptions: codeOptions.map((o) => ({ value: o.value, label: o.label })),
@@ -486,6 +540,24 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
const allOptions = getAllOptions();
const placeholder = componentConfig.placeholder || "선택하세요";
// DOM props에서 React 전용 props 필터링
const {
component: _component,
componentConfig: _componentConfig,
screenId: _screenId,
onUpdate: _onUpdate,
isSelected: _isSelected,
isDesignMode: _isDesignMode,
className: _className,
style: _style,
onClick: _onClick,
onDragStart: _onDragStart,
onDragEnd: _onDragEnd,
...otherProps
} = props;
const safeDomProps = filterDOMProps(otherProps);
return (
<div
ref={selectRef}
@@ -494,8 +566,27 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
onClick={onClick}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
{...props}
{...safeDomProps}
>
{/* 라벨 렌더링 */}
{component.label && component.style?.labelDisplay !== false && (
<label
style={{
position: "absolute",
top: "-25px",
left: "0px",
fontSize: component.style?.labelFontSize || "14px",
color: component.style?.labelColor || "#374151",
fontWeight: "500",
// isInteractive 모드에서는 사용자 스타일 우선 적용
...(isInteractive && component.style ? component.style : {}),
}}
>
{component.label}
{component.required && <span style={{ color: "#ef4444" }}>*</span>}
</label>
)}
{/* 커스텀 셀렉트 박스 */}
<div
className={`flex w-full cursor-pointer items-center justify-between rounded-md border border-gray-300 bg-white px-3 py-2 ${isDesignMode ? "pointer-events-none" : "hover:border-gray-400"} ${isSelected ? "ring-2 ring-blue-500" : ""} ${isOpen ? "border-blue-500" : ""} `}
@@ -520,11 +611,11 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
{/* 드롭다운 옵션 */}
{isOpen && !isDesignMode && (
<div
className="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border border-gray-300 bg-white shadow-lg"
className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md border border-gray-300 bg-white shadow-lg"
style={{
backgroundColor: "white",
color: "black",
zIndex: 9999,
zIndex: 99999, // 더 높은 z-index로 설정
}}
>
{(() => {