This commit is contained in:
DDD1542
2026-01-15 17:36:41 +09:00
4 changed files with 72 additions and 30 deletions

View File

@@ -309,17 +309,10 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
// 🆕 그룹 데이터 조회 함수
const loadGroupData = async () => {
if (!modalState.tableName || !modalState.groupByColumns || modalState.groupByColumns.length === 0) {
// console.warn("테이블명 또는 그룹핑 컬럼이 없습니다.");
return;
}
try {
// console.log("🔍 그룹 데이터 조회 시작:", {
// tableName: modalState.tableName,
// groupByColumns: modalState.groupByColumns,
// editData: modalState.editData,
// });
// 그룹핑 컬럼 값 추출 (예: order_no = "ORD-20251124-001")
const groupValues: Record<string, any> = {};
modalState.groupByColumns.forEach((column) => {
@@ -329,15 +322,9 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
});
if (Object.keys(groupValues).length === 0) {
// console.warn("그룹핑 컬럼 값이 없습니다:", modalState.groupByColumns);
return;
}
// console.log("🔍 그룹 조회 요청:", {
// tableName: modalState.tableName,
// groupValues,
// });
// 같은 그룹의 모든 레코드 조회 (entityJoinApi 사용)
const { entityJoinApi } = await import("@/lib/api/entityJoin");
const response = await entityJoinApi.getTableDataWithJoins(modalState.tableName, {
@@ -347,23 +334,19 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
enableEntityJoin: true,
});
// console.log("🔍 그룹 조회 응답:", response);
// entityJoinApi는 배열 또는 { data: [] } 형식으로 반환
const dataArray = Array.isArray(response) ? response : response?.data || [];
if (dataArray.length > 0) {
// console.log("✅ 그룹 데이터 조회 성공:", dataArray.length, "건");
setGroupData(dataArray);
setOriginalGroupData(JSON.parse(JSON.stringify(dataArray))); // Deep copy
toast.info(`${dataArray.length}개의 관련 품목을 불러왔습니다.`);
} else {
console.warn("그룹 데이터가 없습니다:", response);
setGroupData([modalState.editData]); // 기본값: 선택된 행만
setOriginalGroupData([JSON.parse(JSON.stringify(modalState.editData))]);
}
} catch (error: any) {
console.error("그룹 데이터 조회 오류:", error);
console.error("그룹 데이터 조회 오류:", error);
toast.error("관련 데이터를 불러오는 중 오류가 발생했습니다.");
setGroupData([modalState.editData]); // 기본값: 선택된 행만
setOriginalGroupData([JSON.parse(JSON.stringify(modalState.editData))]);
@@ -1043,17 +1026,18 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
const groupedDataProp = groupData.length > 0 ? groupData : undefined;
// 🆕 UniversalFormModal이 있는지 확인 (자체 저장 로직 사용)
// 최상위 컴포넌트 또는 조건부 컨테이너 내부 화면에 universal-form-modal이 있는지 확인
// 최상위 컴포넌트에 universal-form-modal이 있는지 확인
// ⚠️ 수정: conditional-container는 제외 (groupData가 있으면 EditModal.handleSave 사용)
const hasUniversalFormModal = screenData.components.some(
(c) => {
// 최상위에 universal-form-modal이 있는 경우
// 최상위에 universal-form-modal이 있는 경우만 자체 저장 로직 사용
if (c.componentType === "universal-form-modal") return true;
// 조건부 컨테이너 내부에 universal-form-modal이 있는 경우
// (조건부 컨테이너가 있으면 내부 화면에서 universal-form-modal을 사용하는 것으로 가정)
if (c.componentType === "conditional-container") return true;
return false;
}
);
// 🆕 그룹 데이터가 있으면 EditModal.handleSave 사용 (일괄 저장)
const shouldUseEditModalSave = groupData.length > 0 || !hasUniversalFormModal;
// 🔑 첨부파일 컴포넌트가 행(레코드) 단위로 파일을 저장할 수 있도록 tableName 추가
const enrichedFormData = {
@@ -1095,9 +1079,9 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
id: modalState.screenId!,
tableName: screenData.screenInfo?.tableName,
}}
// 🆕 UniversalFormModal이 으면 onSave 전달 안 함 (자체 저장 로직 사용)
// ModalRepeaterTable만 있으면 기존대로 onSave 전달 (호환성 유지)
onSave={hasUniversalFormModal ? undefined : handleSave}
// 🆕 그룹 데이터가 있거나 UniversalFormModal이 으면 EditModal.handleSave 사용
// groupData가 있으면 일괄 저장을 위해 반드시 EditModal.handleSave 사용
onSave={shouldUseEditModalSave ? handleSave : undefined}
isInModal={true}
// 🆕 그룹 데이터를 ModalRepeaterTable에 전달
groupedData={groupedDataProp}

View File

@@ -180,8 +180,11 @@ export function ModalRepeaterTableComponent({
filterCondition: propFilterCondition,
companyCode: propCompanyCode,
// 🆕 그룹 데이터 (EditModal에서 전달, 같은 그룹의 여러 품목)
groupedData,
...props
}: ModalRepeaterTableComponentProps) {
}: ModalRepeaterTableComponentProps & { groupedData?: Record<string, any>[] }) {
// ✅ config 또는 component.config 또는 개별 prop 우선순위로 병합
const componentConfig = {
...config,
@@ -208,9 +211,16 @@ export function ModalRepeaterTableComponent({
// 모달 필터 설정
const modalFilters = componentConfig?.modalFilters || [];
// ✅ value는 formData[columnName] 우선, 없으면 prop 사용
// ✅ value는 groupedData 우선, 없으면 formData[columnName], 없으면 prop 사용
const columnName = component?.columnName;
const externalValue = (columnName && formData?.[columnName]) || componentConfig?.value || propValue || [];
// 🆕 groupedData가 전달되면 (EditModal에서 그룹 조회 결과) 우선 사용
const externalValue = (() => {
if (groupedData && groupedData.length > 0) {
return groupedData;
}
return (columnName && formData?.[columnName]) || componentConfig?.value || propValue || [];
})();
// 빈 객체 판단 함수 (수정 모달의 실제 데이터는 유지)
const isEmptyRow = (item: any): boolean => {

View File

@@ -80,7 +80,14 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
// "테이블명.컬럼명" 형식을 "원본컬럼_조인컬럼명" 형식으로 변환하여 데이터 접근
const getEntityJoinValue = useCallback(
(item: any, columnName: string, entityColumnMap?: Record<string, string>): any => {
// 직접 매칭 시도
// 🆕 백엔드가 제공하는 _label 필드 우선 사용
// 백엔드는 "표시 컬럼"이 설정된 경우 columnName_label을 자동 생성
const labelKey = `${columnName}_label`;
if (item[labelKey] !== undefined && item[labelKey] !== "" && item[labelKey] !== null) {
return item[labelKey];
}
// 직접 매칭 시도 (JOIN된 값이 없으면 원본 값 반환)
if (item[columnName] !== undefined) {
return item[columnName];
}

View File

@@ -708,6 +708,47 @@ export class ButtonActionExecutor {
if (repeaterJsonKeys.length > 0) {
console.log("🔄 [handleSave] RepeaterFieldGroup JSON 문자열 감지:", repeaterJsonKeys);
// 🎯 채번 규칙 할당 처리 (RepeaterFieldGroup 저장 전에 실행)
console.log("🔍 [handleSave-RepeaterFieldGroup] 채번 규칙 할당 체크 시작");
const fieldsWithNumberingRepeater: Record<string, string> = {};
// formData에서 채번 규칙이 설정된 필드 찾기
for (const [key, value] of Object.entries(context.formData)) {
if (key.endsWith("_numberingRuleId") && value) {
const fieldName = key.replace("_numberingRuleId", "");
fieldsWithNumberingRepeater[fieldName] = value as string;
console.log(`🎯 [handleSave-RepeaterFieldGroup] 채번 필드 발견: ${fieldName} → 규칙 ${value}`);
}
}
console.log("📋 [handleSave-RepeaterFieldGroup] 채번 규칙이 설정된 필드:", fieldsWithNumberingRepeater);
// 채번 규칙이 있는 필드에 대해 allocateCode 호출
if (Object.keys(fieldsWithNumberingRepeater).length > 0) {
console.log("🎯 [handleSave-RepeaterFieldGroup] 채번 규칙 할당 시작 (allocateCode 호출)");
const { allocateNumberingCode } = await import("@/lib/api/numberingRule");
for (const [fieldName, ruleId] of Object.entries(fieldsWithNumberingRepeater)) {
try {
console.log(`🔄 [handleSave-RepeaterFieldGroup] ${fieldName} 필드에 대해 allocateCode 호출: ${ruleId}`);
const allocateResult = await allocateNumberingCode(ruleId);
if (allocateResult.success && allocateResult.data?.generatedCode) {
const newCode = allocateResult.data.generatedCode;
console.log(`✅ [handleSave-RepeaterFieldGroup] ${fieldName} 새 코드 할당: ${context.formData[fieldName]}${newCode}`);
context.formData[fieldName] = newCode;
} else {
console.warn(`⚠️ [handleSave-RepeaterFieldGroup] ${fieldName} 코드 할당 실패:`, allocateResult.error);
}
} catch (allocateError) {
console.error(`❌ [handleSave-RepeaterFieldGroup] ${fieldName} 코드 할당 오류:`, allocateError);
}
}
}
console.log("✅ [handleSave-RepeaterFieldGroup] 채번 규칙 할당 완료");
// 🆕 상단 폼 데이터(마스터 정보) 추출
// RepeaterFieldGroup JSON과 컴포넌트 키를 제외한 나머지가 마스터 정보
const masterFields: Record<string, any> = {};