Merge branch 'main' into feature/screen-management

This commit is contained in:
kjs 2026-01-09 17:57:01 +09:00
commit 47b61a9a35
4 changed files with 49 additions and 15 deletions

View File

@ -175,13 +175,21 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
if (editData) {
console.log("📝 [ScreenModal] 수정 데이터 설정:", editData);
// 🆕 배열인 경우 (그룹 레코드) vs 단일 객체 처리
// 🆕 배열인 경우 두 가지 데이터를 설정:
// 1. formData: 첫 번째 요소(객체) - 일반 입력 필드용 (TextInput 등)
// 2. selectedData: 전체 배열 - 다중 항목 컴포넌트용 (SelectedItemsDetailInput 등)
if (Array.isArray(editData)) {
console.log(`📝 [ScreenModal] 그룹 레코드 ${editData.length}개 설정`);
setFormData(editData as any); // 배열 그대로 전달 (SelectedItemsDetailInput에서 처리)
setOriginalData(editData[0] || null); // 첫 번째 레코드를 원본으로 저장
const firstRecord = editData[0] || {};
console.log(`📝 [ScreenModal] 그룹 레코드 ${editData.length}개 설정:`, {
formData: "첫 번째 레코드 (일반 입력 필드용)",
selectedData: "전체 배열 (다중 항목 컴포넌트용)",
});
setFormData(firstRecord); // 🔧 일반 입력 필드용 (객체)
setSelectedData(editData); // 🔧 다중 항목 컴포넌트용 (배열) - groupedData로 전달됨
setOriginalData(firstRecord); // 첫 번째 레코드를 원본으로 저장
} else {
setFormData(editData);
setSelectedData([editData]); // 🔧 단일 객체도 배열로 변환하여 저장
setOriginalData(editData); // 🆕 원본 데이터 저장 (UPDATE 판단용)
}
} else {

View File

@ -281,10 +281,12 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
// 컴포넌트의 columnName에 해당하는 formData 값 추출
const fieldName = (component as any).columnName || component.id;
// modal-repeater-table은 배열 데이터를 다루므로 빈 배열로 초기화
// 다중 레코드를 다루는 컴포넌트는 배열 데이터로 초기화
let currentValue;
if (componentType === "modal-repeater-table" || componentType === "repeat-screen-modal") {
// EditModal에서 전달된 groupedData가 있으면 우선 사용
if (componentType === "modal-repeater-table" ||
componentType === "repeat-screen-modal" ||
componentType === "selected-items-detail-input") {
// EditModal/ScreenModal에서 전달된 groupedData가 있으면 우선 사용
currentValue = props.groupedData || formData?.[fieldName] || [];
} else {
currentValue = formData?.[fieldName] || "";

View File

@ -42,6 +42,8 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
screenId,
...props
}) => {
// 🆕 groupedData 추출 (DynamicComponentRenderer에서 전달)
const groupedData = (props as any).groupedData || (props as any)._groupedData;
// 🆕 URL 파라미터에서 dataSourceId 읽기
const searchParams = useSearchParams();
const urlDataSourceId = searchParams?.get("dataSourceId") || undefined;
@ -225,24 +227,32 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
// 🆕 모달 데이터를 ItemData 구조로 변환 (그룹별 구조)
useEffect(() => {
// 🆕 수정 모드: formData에서 데이터 로드 (URL에 mode=edit이 있으면)
// 🆕 수정 모드: groupedData 또는 formData에서 데이터 로드 (URL에 mode=edit이 있으면)
const urlParams = new URLSearchParams(window.location.search);
const mode = urlParams.get("mode");
if (mode === "edit" && formData) {
// 🔧 데이터 소스 우선순위: groupedData > formData (배열) > formData (객체)
const sourceData = groupedData && Array.isArray(groupedData) && groupedData.length > 0
? groupedData
: formData;
if (mode === "edit" && sourceData) {
// 배열인지 단일 객체인지 확인
const isArray = Array.isArray(formData);
const dataArray = isArray ? formData : [formData];
const isArray = Array.isArray(sourceData);
const dataArray = isArray ? sourceData : [sourceData];
if (dataArray.length === 0 || (dataArray.length === 1 && Object.keys(dataArray[0]).length === 0)) {
console.warn("⚠️ [SelectedItemsDetailInput] formData가 비어있음");
console.warn("⚠️ [SelectedItemsDetailInput] 데이터가 비어있음");
return;
}
console.log(
`📝 [SelectedItemsDetailInput] 수정 모드 - ${isArray ? "그룹 레코드" : "단일 레코드"} (${dataArray.length}개)`,
);
console.log("📝 [SelectedItemsDetailInput] formData (JSON):", JSON.stringify(dataArray, null, 2));
console.log("📝 [SelectedItemsDetailInput] 데이터 소스:", {
fromGroupedData: groupedData && Array.isArray(groupedData) && groupedData.length > 0,
dataArray: JSON.stringify(dataArray, null, 2),
});
const groups = componentConfig.fieldGroups || [];
const additionalFields = componentConfig.additionalFields || [];
@ -423,7 +433,7 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [modalData, component.id, componentConfig.fieldGroups, formData]); // formData 의존성 추가
}, [modalData, component.id, componentConfig.fieldGroups, formData, groupedData]); // groupedData 의존성 추가
// 🆕 Cartesian Product 생성 함수 (items에서 모든 그룹의 조합을 생성)
const generateCartesianProduct = useCallback(

View File

@ -51,6 +51,10 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
// 숨김 상태 (props에서 전달받은 값 우선 사용)
const isHidden = props.hidden !== undefined ? props.hidden : component.hidden || componentConfig.hidden || false;
// 수정 모드 여부 확인 (originalData가 있으면 수정 모드)
const originalData = props.originalData || (props as any)._originalData;
const isEditMode = originalData && Object.keys(originalData).length > 0;
// 자동생성된 값 상태
const [autoGeneratedValue, setAutoGeneratedValue] = useState<string>("");
@ -99,6 +103,16 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
return;
}
// 🆕 수정 모드일 때는 채번 규칙 스킵 (기존 값 유지)
if (isEditMode) {
console.log("⏭️ 수정 모드 - 채번 규칙 스킵:", {
columnName: component.columnName,
originalValue: originalData?.[component.columnName],
});
hasGeneratedRef.current = true; // 생성 완료로 표시하여 재실행 방지
return;
}
if (testAutoGeneration.enabled && testAutoGeneration.type !== "none") {
// 폼 데이터에 이미 값이 있으면 자동생성하지 않음
const currentFormValue = formData?.[component.columnName];
@ -171,7 +185,7 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
};
generateAutoValue();
}, [testAutoGeneration.enabled, testAutoGeneration.type, component.columnName, isInteractive]);
}, [testAutoGeneration.enabled, testAutoGeneration.type, component.columnName, isInteractive, isEditMode]);
// 실제 화면에서 숨김 처리된 컴포넌트는 렌더링하지 않음
if (isHidden && !isDesignMode) {