Compare commits
4 Commits
75b5530d04
...
5e27d21257
| Author | SHA1 | Date |
|---|---|---|
|
|
5e27d21257 | |
|
|
efc9175fec | |
|
|
eb61506acd | |
|
|
cded99d644 |
|
|
@ -775,7 +775,8 @@ export async function getTableData(
|
|||
const userField = autoFilter?.userField || "companyCode";
|
||||
const userValue = (req.user as any)[userField];
|
||||
|
||||
if (userValue) {
|
||||
// 🆕 최고 관리자(company_code = '*')는 모든 회사 데이터 조회 가능
|
||||
if (userValue && userValue !== "*") {
|
||||
enhancedSearch[filterColumn] = userValue;
|
||||
|
||||
logger.info("🔍 현재 사용자 필터 적용:", {
|
||||
|
|
@ -784,6 +785,10 @@ export async function getTableData(
|
|||
userValue,
|
||||
tableName,
|
||||
});
|
||||
} else if (userValue === "*") {
|
||||
logger.info("🔓 최고 관리자 - 회사 필터 미적용 (모든 회사 데이터 조회)", {
|
||||
tableName,
|
||||
});
|
||||
} else {
|
||||
logger.warn("⚠️ 사용자 정보 필드 값 없음:", {
|
||||
userField,
|
||||
|
|
@ -792,6 +797,9 @@ export async function getTableData(
|
|||
}
|
||||
}
|
||||
|
||||
// 🆕 최종 검색 조건 로그
|
||||
logger.info(`🔍 최종 검색 조건 (enhancedSearch):`, JSON.stringify(enhancedSearch));
|
||||
|
||||
// 데이터 조회
|
||||
const result = await tableManagementService.getTableData(tableName, {
|
||||
page: parseInt(page),
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ function ScreenViewPage() {
|
|||
// 편집 모달 이벤트 리스너 등록
|
||||
useEffect(() => {
|
||||
const handleOpenEditModal = (event: CustomEvent) => {
|
||||
console.log("🎭 편집 모달 열기 이벤트 수신:", event.detail);
|
||||
// console.log("🎭 편집 모달 열기 이벤트 수신:", event.detail);
|
||||
|
||||
setEditModalConfig({
|
||||
screenId: event.detail.screenId,
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
|||
// dataSourceId 파라미터 제거
|
||||
currentUrl.searchParams.delete("dataSourceId");
|
||||
window.history.pushState({}, "", currentUrl.toString());
|
||||
console.log("🧹 URL 파라미터 제거");
|
||||
// console.log("🧹 URL 파라미터 제거");
|
||||
}
|
||||
|
||||
setModalState({
|
||||
|
|
@ -277,7 +277,7 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
|||
setSelectedData([]); // 🆕 선택된 데이터 초기화
|
||||
setContinuousMode(false);
|
||||
localStorage.setItem("screenModal_continuousMode", "false"); // localStorage에 저장
|
||||
console.log("🔄 연속 모드 초기화: false");
|
||||
// console.log("🔄 연속 모드 초기화: false");
|
||||
};
|
||||
|
||||
// 저장 성공 이벤트 처리 (연속 등록 모드 지원)
|
||||
|
|
@ -285,36 +285,36 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
|||
// 🆕 모달이 열린 후 500ms 이내의 저장 성공 이벤트는 무시 (이전 이벤트 방지)
|
||||
const timeSinceOpen = Date.now() - modalOpenedAtRef.current;
|
||||
if (timeSinceOpen < 500) {
|
||||
console.log("⏭️ [ScreenModal] 모달 열린 직후 저장 성공 이벤트 무시:", { timeSinceOpen });
|
||||
// console.log("⏭️ [ScreenModal] 모달 열린 직후 저장 성공 이벤트 무시:", { timeSinceOpen });
|
||||
return;
|
||||
}
|
||||
|
||||
const isContinuousMode = continuousMode;
|
||||
console.log("💾 저장 성공 이벤트 수신");
|
||||
console.log("📌 현재 연속 모드 상태:", isContinuousMode);
|
||||
console.log("📌 localStorage:", localStorage.getItem("screenModal_continuousMode"));
|
||||
// console.log("💾 저장 성공 이벤트 수신");
|
||||
// console.log("📌 현재 연속 모드 상태:", isContinuousMode);
|
||||
// console.log("📌 localStorage:", localStorage.getItem("screenModal_continuousMode"));
|
||||
|
||||
if (isContinuousMode) {
|
||||
// 연속 모드: 폼만 초기화하고 모달은 유지
|
||||
console.log("✅ 연속 모드 활성화 - 폼 초기화 및 화면 리셋");
|
||||
// console.log("✅ 연속 모드 활성화 - 폼 초기화 및 화면 리셋");
|
||||
|
||||
// 1. 폼 데이터 초기화
|
||||
setFormData({});
|
||||
|
||||
// 2. 리셋 키 변경 (컴포넌트 강제 리마운트)
|
||||
setResetKey((prev) => prev + 1);
|
||||
console.log("🔄 resetKey 증가 - 컴포넌트 리마운트");
|
||||
// console.log("🔄 resetKey 증가 - 컴포넌트 리마운트");
|
||||
|
||||
// 3. 화면 데이터 다시 로드 (채번 규칙 새로 생성)
|
||||
if (modalState.screenId) {
|
||||
console.log("🔄 화면 데이터 다시 로드:", modalState.screenId);
|
||||
// console.log("🔄 화면 데이터 다시 로드:", modalState.screenId);
|
||||
loadScreenData(modalState.screenId);
|
||||
}
|
||||
|
||||
toast.success("저장되었습니다. 계속 입력하세요.");
|
||||
} else {
|
||||
// 일반 모드: 모달 닫기
|
||||
console.log("❌ 일반 모드 - 모달 닫기");
|
||||
// console.log("❌ 일반 모드 - 모달 닫기");
|
||||
handleCloseModal();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -190,14 +190,14 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
const innerLayoutData = await screenApi.getLayout(section.screenId);
|
||||
saveButton = findSaveButtonInComponents(innerLayoutData?.components || []);
|
||||
if (saveButton) {
|
||||
console.log("[EditModal] 조건부 컨테이너 내부에서 저장 버튼 발견:", {
|
||||
sectionScreenId: section.screenId,
|
||||
sectionLabel: section.label,
|
||||
});
|
||||
// console.log("[EditModal] 조건부 컨테이너 내부에서 저장 버튼 발견:", {
|
||||
// sectionScreenId: section.screenId,
|
||||
// sectionLabel: section.label,
|
||||
// });
|
||||
break;
|
||||
}
|
||||
} catch (innerError) {
|
||||
console.warn("[EditModal] 내부 화면 레이아웃 조회 실패:", section.screenId);
|
||||
// console.warn("[EditModal] 내부 화면 레이아웃 조회 실패:", section.screenId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -207,7 +207,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
}
|
||||
|
||||
if (!saveButton) {
|
||||
console.log("[EditModal] 저장 버튼을 찾을 수 없음:", targetScreenId);
|
||||
// console.log("[EditModal] 저장 버튼을 찾을 수 없음:", targetScreenId);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -219,14 +219,14 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
dataflowConfig: webTypeConfig.dataflowConfig,
|
||||
dataflowTiming: webTypeConfig.dataflowConfig?.flowConfig?.executionTiming || "after",
|
||||
};
|
||||
console.log("[EditModal] 저장 버튼 제어로직 설정 발견:", config);
|
||||
// console.log("[EditModal] 저장 버튼 제어로직 설정 발견:", config);
|
||||
return config;
|
||||
}
|
||||
|
||||
console.log("[EditModal] 저장 버튼에 제어로직 설정 없음");
|
||||
// console.log("[EditModal] 저장 버튼에 제어로직 설정 없음");
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.warn("[EditModal] 저장 버튼 설정 조회 실패:", error);
|
||||
// console.warn("[EditModal] 저장 버튼 설정 조회 실패:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
@ -309,16 +309,16 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
// 🆕 그룹 데이터 조회 함수
|
||||
const loadGroupData = async () => {
|
||||
if (!modalState.tableName || !modalState.groupByColumns || modalState.groupByColumns.length === 0) {
|
||||
console.warn("테이블명 또는 그룹핑 컬럼이 없습니다.");
|
||||
// console.warn("테이블명 또는 그룹핑 컬럼이 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("🔍 그룹 데이터 조회 시작:", {
|
||||
tableName: modalState.tableName,
|
||||
groupByColumns: modalState.groupByColumns,
|
||||
editData: modalState.editData,
|
||||
});
|
||||
// console.log("🔍 그룹 데이터 조회 시작:", {
|
||||
// tableName: modalState.tableName,
|
||||
// groupByColumns: modalState.groupByColumns,
|
||||
// editData: modalState.editData,
|
||||
// });
|
||||
|
||||
// 그룹핑 컬럼 값 추출 (예: order_no = "ORD-20251124-001")
|
||||
const groupValues: Record<string, any> = {};
|
||||
|
|
@ -329,14 +329,14 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
});
|
||||
|
||||
if (Object.keys(groupValues).length === 0) {
|
||||
console.warn("그룹핑 컬럼 값이 없습니다:", modalState.groupByColumns);
|
||||
// console.warn("그룹핑 컬럼 값이 없습니다:", modalState.groupByColumns);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("🔍 그룹 조회 요청:", {
|
||||
tableName: modalState.tableName,
|
||||
groupValues,
|
||||
});
|
||||
// console.log("🔍 그룹 조회 요청:", {
|
||||
// tableName: modalState.tableName,
|
||||
// groupValues,
|
||||
// });
|
||||
|
||||
// 같은 그룹의 모든 레코드 조회 (entityJoinApi 사용)
|
||||
const { entityJoinApi } = await import("@/lib/api/entityJoin");
|
||||
|
|
@ -347,13 +347,13 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
enableEntityJoin: true,
|
||||
});
|
||||
|
||||
console.log("🔍 그룹 조회 응답:", response);
|
||||
// console.log("🔍 그룹 조회 응답:", response);
|
||||
|
||||
// entityJoinApi는 배열 또는 { data: [] } 형식으로 반환
|
||||
const dataArray = Array.isArray(response) ? response : response?.data || [];
|
||||
|
||||
if (dataArray.length > 0) {
|
||||
console.log("✅ 그룹 데이터 조회 성공:", dataArray.length, "건");
|
||||
// console.log("✅ 그룹 데이터 조회 성공:", dataArray.length, "건");
|
||||
setGroupData(dataArray);
|
||||
setOriginalGroupData(JSON.parse(JSON.stringify(dataArray))); // Deep copy
|
||||
toast.info(`${dataArray.length}개의 관련 품목을 불러왔습니다.`);
|
||||
|
|
@ -374,7 +374,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
try {
|
||||
setLoading(true);
|
||||
|
||||
console.log("화면 데이터 로딩 시작:", screenId);
|
||||
// console.log("화면 데이터 로딩 시작:", screenId);
|
||||
|
||||
// 화면 정보와 레이아웃 데이터 로딩
|
||||
const [screenInfo, layoutData] = await Promise.all([
|
||||
|
|
@ -382,7 +382,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
screenApi.getLayout(screenId),
|
||||
]);
|
||||
|
||||
console.log("API 응답:", { screenInfo, layoutData });
|
||||
// console.log("API 응답:", { screenInfo, layoutData });
|
||||
|
||||
if (screenInfo && layoutData) {
|
||||
const components = layoutData.components || [];
|
||||
|
|
@ -395,11 +395,11 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
components,
|
||||
screenInfo: screenInfo,
|
||||
});
|
||||
console.log("화면 데이터 설정 완료:", {
|
||||
componentsCount: components.length,
|
||||
dimensions,
|
||||
screenInfo,
|
||||
});
|
||||
// console.log("화면 데이터 설정 완료:", {
|
||||
// componentsCount: components.length,
|
||||
// dimensions,
|
||||
// screenInfo,
|
||||
// });
|
||||
} else {
|
||||
throw new Error("화면 데이터가 없습니다");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -306,11 +306,11 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
|||
useEffect(() => {
|
||||
const newData = splitPanelContext?.selectedLeftData ?? null;
|
||||
setTrackedSelectedLeftData(newData);
|
||||
console.log("🔄 [ButtonPrimary] selectedLeftData 변경 감지:", {
|
||||
label: component.label,
|
||||
hasData: !!newData,
|
||||
dataKeys: newData ? Object.keys(newData) : [],
|
||||
});
|
||||
// console.log("🔄 [ButtonPrimary] selectedLeftData 변경 감지:", {
|
||||
// label: component.label,
|
||||
// hasData: !!newData,
|
||||
// dataKeys: newData ? Object.keys(newData) : [],
|
||||
// });
|
||||
}, [splitPanelContext?.selectedLeftData, component.label]);
|
||||
|
||||
// modalDataStore 상태 구독 (실시간 업데이트)
|
||||
|
|
|
|||
|
|
@ -53,11 +53,11 @@ export function useEntitySearch({
|
|||
limit: pagination.limit.toString(),
|
||||
});
|
||||
|
||||
console.log("[useEntitySearch] 검색 실행:", {
|
||||
tableName,
|
||||
filterCondition: filterConditionRef.current,
|
||||
searchText: text,
|
||||
});
|
||||
// console.log("[useEntitySearch] 검색 실행:", {
|
||||
// tableName,
|
||||
// filterCondition: filterConditionRef.current,
|
||||
// searchText: text,
|
||||
// });
|
||||
|
||||
const response = await apiClient.get<EntitySearchResponse>(
|
||||
`/entity-search/${tableName}?${params.toString()}`
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { RepeatScreenModalDefinition } from "./index";
|
|||
// 컴포넌트 자동 등록
|
||||
if (typeof window !== "undefined") {
|
||||
ComponentRegistry.registerComponent(RepeatScreenModalDefinition);
|
||||
console.log("✅ RepeatScreenModal 컴포넌트 등록 완료");
|
||||
// console.log("✅ RepeatScreenModal 컴포넌트 등록 완료");
|
||||
}
|
||||
|
||||
export {};
|
||||
|
|
|
|||
|
|
@ -205,12 +205,12 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
const splitPanelId = `split-panel-${component.id}`;
|
||||
|
||||
// 디버깅: Context 연결 상태 확인
|
||||
console.log("🔗 [SplitPanelLayout] Context 연결 상태:", {
|
||||
componentId: component.id,
|
||||
splitPanelId,
|
||||
hasRegisterFunc: typeof ctxRegisterSplitPanel === "function",
|
||||
splitPanelsSize: splitPanelContext.splitPanels?.size ?? "없음",
|
||||
});
|
||||
// console.log("🔗 [SplitPanelLayout] Context 연결 상태:", {
|
||||
// componentId: component.id,
|
||||
// splitPanelId,
|
||||
// hasRegisterFunc: typeof ctxRegisterSplitPanel === "function",
|
||||
// splitPanelsSize: splitPanelContext.splitPanels?.size ?? "없음",
|
||||
// });
|
||||
|
||||
// Context에 분할 패널 등록 (좌표 정보 포함) - 마운트 시 1회만 실행
|
||||
const ctxRegisterRef = useRef(ctxRegisterSplitPanel);
|
||||
|
|
@ -235,15 +235,15 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
isDragging: false,
|
||||
};
|
||||
|
||||
console.log("📦 [SplitPanelLayout] Context에 분할 패널 등록:", {
|
||||
splitPanelId,
|
||||
panelInfo,
|
||||
});
|
||||
// console.log("📦 [SplitPanelLayout] Context에 분할 패널 등록:", {
|
||||
// splitPanelId,
|
||||
// panelInfo,
|
||||
// });
|
||||
|
||||
ctxRegisterRef.current(splitPanelId, panelInfo);
|
||||
|
||||
return () => {
|
||||
console.log("📦 [SplitPanelLayout] Context에서 분할 패널 해제:", splitPanelId);
|
||||
// console.log("📦 [SplitPanelLayout] Context에서 분할 패널 해제:", splitPanelId);
|
||||
ctxUnregisterRef.current(splitPanelId);
|
||||
};
|
||||
// 마운트/언마운트 시에만 실행, 위치/크기 변경은 별도 업데이트로 처리
|
||||
|
|
@ -311,11 +311,11 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
|
||||
// 🆕 그룹별 합산된 데이터 계산
|
||||
const summedLeftData = useMemo(() => {
|
||||
console.log("🔍 [그룹합산] leftGroupSumConfig:", leftGroupSumConfig);
|
||||
// console.log("🔍 [그룹합산] leftGroupSumConfig:", leftGroupSumConfig);
|
||||
|
||||
// 그룹핑이 비활성화되었거나 그룹 기준 컬럼이 없으면 원본 데이터 반환
|
||||
if (!leftGroupSumConfig?.enabled || !leftGroupSumConfig?.groupByColumn) {
|
||||
console.log("🔍 [그룹합산] 그룹핑 비활성화 - 원본 데이터 반환");
|
||||
// console.log("🔍 [그룹합산] 그룹핑 비활성화 - 원본 데이터 반환");
|
||||
return leftData;
|
||||
}
|
||||
|
||||
|
|
@ -756,8 +756,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
}
|
||||
});
|
||||
|
||||
console.log("🔗 [분할패널] additionalJoinColumns:", additionalJoinColumns);
|
||||
console.log("🔗 [분할패널] configuredColumns:", configuredColumns);
|
||||
// console.log("🔗 [분할패널] additionalJoinColumns:", additionalJoinColumns);
|
||||
// console.log("🔗 [분할패널] configuredColumns:", configuredColumns);
|
||||
|
||||
const result = await entityJoinApi.getTableDataWithJoins(leftTableName, {
|
||||
page: 1,
|
||||
|
|
@ -769,10 +769,10 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
});
|
||||
|
||||
// 🔍 디버깅: API 응답 데이터의 키 확인
|
||||
if (result.data && result.data.length > 0) {
|
||||
console.log("🔗 [분할패널] API 응답 첫 번째 데이터 키:", Object.keys(result.data[0]));
|
||||
console.log("🔗 [분할패널] API 응답 첫 번째 데이터:", result.data[0]);
|
||||
}
|
||||
// if (result.data && result.data.length > 0) {
|
||||
// console.log("🔗 [분할패널] API 응답 첫 번째 데이터 키:", Object.keys(result.data[0]));
|
||||
// console.log("🔗 [분할패널] API 응답 첫 번째 데이터:", result.data[0]);
|
||||
// }
|
||||
|
||||
// 가나다순 정렬 (좌측 패널의 표시 컬럼 기준)
|
||||
const leftColumn = componentConfig.rightPanel?.relation?.leftColumn;
|
||||
|
|
@ -1000,7 +1000,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
if (leftTableName && !isDesignMode) {
|
||||
import("@/stores/modalDataStore").then(({ useModalDataStore }) => {
|
||||
useModalDataStore.getState().setData(leftTableName, [item]);
|
||||
console.log(`✅ 분할 패널 좌측 선택: ${leftTableName}`, item);
|
||||
// console.log(`✅ 분할 패널 좌측 선택: ${leftTableName}`, item);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
@ -1198,7 +1198,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
}
|
||||
});
|
||||
setLeftColumnLabels(labels);
|
||||
console.log("✅ 좌측 컬럼 라벨 로드:", labels);
|
||||
// console.log("✅ 좌측 컬럼 라벨 로드:", labels);
|
||||
} catch (error) {
|
||||
console.error("좌측 테이블 컬럼 라벨 로드 실패:", error);
|
||||
}
|
||||
|
|
@ -1227,7 +1227,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
}
|
||||
});
|
||||
setRightColumnLabels(labels);
|
||||
console.log("✅ 우측 컬럼 라벨 로드:", labels);
|
||||
// console.log("✅ 우측 컬럼 라벨 로드:", labels);
|
||||
} catch (error) {
|
||||
console.error("우측 테이블 컬럼 정보 로드 실패:", error);
|
||||
}
|
||||
|
|
@ -1269,7 +1269,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
};
|
||||
});
|
||||
mappings[columnName] = valueMap;
|
||||
console.log(`✅ 좌측 카테고리 매핑 로드 [${columnName}]:`, valueMap);
|
||||
// console.log(`✅ 좌측 카테고리 매핑 로드 [${columnName}]:`, valueMap);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`좌측 카테고리 값 조회 실패 [${columnName}]:`, error);
|
||||
|
|
@ -1307,7 +1307,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
}
|
||||
});
|
||||
|
||||
console.log("🔍 우측 패널 카테고리 로드 대상 테이블:", Array.from(tablesToLoad));
|
||||
// console.log("🔍 우측 패널 카테고리 로드 대상 테이블:", Array.from(tablesToLoad));
|
||||
|
||||
// 각 테이블에 대해 카테고리 매핑 로드
|
||||
for (const tableName of tablesToLoad) {
|
||||
|
|
@ -1940,7 +1940,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
useEffect(() => {
|
||||
const handleRefreshTable = () => {
|
||||
if (!isDesignMode) {
|
||||
console.log("🔄 [SplitPanel] refreshTable 이벤트 수신 - 데이터 새로고침");
|
||||
// console.log("🔄 [SplitPanel] refreshTable 이벤트 수신 - 데이터 새로고침");
|
||||
loadLeftData();
|
||||
// 선택된 항목이 있으면 우측 패널도 새로고침
|
||||
if (selectedLeftItem) {
|
||||
|
|
@ -2104,12 +2104,12 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
(() => {
|
||||
// 🆕 그룹별 합산된 데이터 사용
|
||||
const dataSource = summedLeftData;
|
||||
console.log(
|
||||
"🔍 [테이블모드 렌더링] dataSource 개수:",
|
||||
dataSource.length,
|
||||
"leftGroupSumConfig:",
|
||||
leftGroupSumConfig,
|
||||
);
|
||||
// console.log(
|
||||
// "🔍 [테이블모드 렌더링] dataSource 개수:",
|
||||
// dataSource.length,
|
||||
// "leftGroupSumConfig:",
|
||||
// leftGroupSumConfig,
|
||||
// );
|
||||
|
||||
// 🔧 로컬 검색 필터 적용
|
||||
const filteredData = leftSearchQuery
|
||||
|
|
|
|||
|
|
@ -816,13 +816,22 @@ export function TableSectionRenderer({
|
|||
// 이미 초기화되었으면 스킵
|
||||
if (initialDataLoadedRef.current) return;
|
||||
|
||||
const tableSectionKey = `_tableSection_${sectionId}`;
|
||||
const tableSectionKey = `__tableSection_${sectionId}`;
|
||||
const initialData = formData[tableSectionKey];
|
||||
|
||||
console.log("[TableSectionRenderer] 초기 데이터 확인:", {
|
||||
sectionId,
|
||||
tableSectionKey,
|
||||
hasInitialData: !!initialData,
|
||||
initialDataLength: Array.isArray(initialData) ? initialData.length : 0,
|
||||
formDataKeys: Object.keys(formData).filter(k => k.startsWith("__tableSection_")),
|
||||
});
|
||||
|
||||
if (Array.isArray(initialData) && initialData.length > 0) {
|
||||
console.log("[TableSectionRenderer] 초기 데이터 로드:", {
|
||||
sectionId,
|
||||
itemCount: initialData.length,
|
||||
firstItem: initialData[0],
|
||||
});
|
||||
setTableData(initialData);
|
||||
initialDataLoadedRef.current = true;
|
||||
|
|
@ -1327,7 +1336,7 @@ export function TableSectionRenderer({
|
|||
}
|
||||
}
|
||||
}
|
||||
console.log("[TableSectionRenderer] baseFilterCondition:", condition, "preFilters:", filters?.preFilters);
|
||||
// console.log("[TableSectionRenderer] baseFilterCondition:", condition, "preFilters:", filters?.preFilters);
|
||||
return condition;
|
||||
}, [filters?.preFilters]);
|
||||
|
||||
|
|
|
|||
|
|
@ -220,26 +220,25 @@ export function UniversalFormModalComponent({
|
|||
|
||||
// 초기화 - 최초 마운트 시 또는 initialData가 변경되었을 때 실행
|
||||
useEffect(() => {
|
||||
console.log("[UniversalFormModal] useEffect 시작", {
|
||||
initialData,
|
||||
hasInitialized: hasInitialized.current,
|
||||
lastInitializedId: lastInitializedId.current,
|
||||
});
|
||||
// console.log("[UniversalFormModal] useEffect 시작", {
|
||||
// initialData,
|
||||
// hasInitialized: hasInitialized.current,
|
||||
// lastInitializedId: lastInitializedId.current,
|
||||
// });
|
||||
|
||||
// initialData에서 ID 값 추출 (id, ID, objid 등)
|
||||
const currentId = initialData?.id || initialData?.ID || initialData?.objid;
|
||||
const currentIdString = currentId !== undefined ? String(currentId) : undefined;
|
||||
|
||||
// 생성 모드에서 부모로부터 전달받은 데이터 해시 (ID가 없을 때만)
|
||||
const createModeDataHash = !currentIdString && initialData && Object.keys(initialData).length > 0
|
||||
? JSON.stringify(initialData)
|
||||
: undefined;
|
||||
const createModeDataHash =
|
||||
!currentIdString && initialData && Object.keys(initialData).length > 0 ? JSON.stringify(initialData) : undefined;
|
||||
|
||||
// 이미 초기화되었고, ID가 동일하고, 생성 모드 데이터도 동일하면 스킵
|
||||
if (hasInitialized.current && lastInitializedId.current === currentIdString) {
|
||||
// 생성 모드에서 데이터가 새로 전달된 경우는 재초기화 필요
|
||||
if (!createModeDataHash || capturedInitialData.current) {
|
||||
console.log("[UniversalFormModal] 초기화 스킵 - 이미 초기화됨");
|
||||
// console.log("[UniversalFormModal] 초기화 스킵 - 이미 초기화됨");
|
||||
// 🆕 채번 플래그가 true인데 formData에 값이 없으면 재생성 필요
|
||||
// (컴포넌트 remount로 인해 state가 초기화된 경우)
|
||||
return;
|
||||
|
|
@ -249,18 +248,18 @@ export function UniversalFormModalComponent({
|
|||
// 🆕 컴포넌트 remount 감지: hasInitialized가 true인데 formData가 비어있으면 재초기화
|
||||
// (React의 Strict Mode나 EmbeddedScreen 리렌더링으로 인한 remount)
|
||||
if (hasInitialized.current && !currentIdString) {
|
||||
console.log("[UniversalFormModal] 컴포넌트 remount 감지 - 채번 플래그 초기화");
|
||||
// console.log("[UniversalFormModal] 컴포넌트 remount 감지 - 채번 플래그 초기화");
|
||||
numberingGeneratedRef.current = false;
|
||||
isGeneratingRef.current = false;
|
||||
}
|
||||
|
||||
// 🆕 수정 모드: initialData에 데이터가 있으면서 ID가 변경된 경우 재초기화
|
||||
if (hasInitialized.current && currentIdString && lastInitializedId.current !== currentIdString) {
|
||||
console.log("[UniversalFormModal] ID 변경 감지 - 재초기화:", {
|
||||
prevId: lastInitializedId.current,
|
||||
newId: currentIdString,
|
||||
initialData: initialData,
|
||||
});
|
||||
// console.log("[UniversalFormModal] ID 변경 감지 - 재초기화:", {
|
||||
// prevId: lastInitializedId.current,
|
||||
// newId: currentIdString,
|
||||
// initialData: initialData,
|
||||
// });
|
||||
// 채번 플래그 초기화 (새 항목이므로)
|
||||
numberingGeneratedRef.current = false;
|
||||
isGeneratingRef.current = false;
|
||||
|
|
@ -270,10 +269,10 @@ export function UniversalFormModalComponent({
|
|||
if (initialData && Object.keys(initialData).length > 0) {
|
||||
capturedInitialData.current = JSON.parse(JSON.stringify(initialData)); // 깊은 복사
|
||||
lastInitializedId.current = currentIdString;
|
||||
console.log("[UniversalFormModal] 초기 데이터 캡처:", capturedInitialData.current);
|
||||
// console.log("[UniversalFormModal] 초기 데이터 캡처:", capturedInitialData.current);
|
||||
}
|
||||
|
||||
console.log("[UniversalFormModal] initializeForm 호출 예정");
|
||||
// console.log("[UniversalFormModal] initializeForm 호출 예정");
|
||||
hasInitialized.current = true;
|
||||
initializeForm();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
@ -283,7 +282,7 @@ export function UniversalFormModalComponent({
|
|||
useEffect(() => {
|
||||
if (!hasInitialized.current) return; // 최초 초기화 전이면 스킵
|
||||
|
||||
console.log("[useEffect config 변경] 재초기화 스킵 (채번 중복 방지)");
|
||||
// console.log("[useEffect config 변경] 재초기화 스킵 (채번 중복 방지)");
|
||||
// initializeForm(); // 주석 처리 - config 변경 시 재초기화 안 함 (채번 중복 방지)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [config]);
|
||||
|
|
@ -291,7 +290,7 @@ export function UniversalFormModalComponent({
|
|||
// 컴포넌트 unmount 시 채번 플래그 초기화
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
console.log("[채번] 컴포넌트 unmount - 플래그 초기화");
|
||||
// console.log("[채번] 컴포넌트 unmount - 플래그 초기화");
|
||||
numberingGeneratedRef.current = false;
|
||||
isGeneratingRef.current = false;
|
||||
};
|
||||
|
|
@ -363,14 +362,14 @@ export function UniversalFormModalComponent({
|
|||
// 테이블 타입 섹션 찾기
|
||||
const tableSection = config.sections.find((s) => s.type === "table");
|
||||
if (!tableSection) {
|
||||
console.log("[UniversalFormModal] 테이블 섹션 없음 - _groupedData 무시");
|
||||
// console.log("[UniversalFormModal] 테이블 섹션 없음 - _groupedData 무시");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("[UniversalFormModal] 수정 모드 - 테이블 섹션 초기화:", {
|
||||
sectionId: tableSection.id,
|
||||
itemCount: _groupedData.length,
|
||||
});
|
||||
// console.log("[UniversalFormModal] 수정 모드 - 테이블 섹션 초기화:", {
|
||||
// sectionId: tableSection.id,
|
||||
// itemCount: _groupedData.length,
|
||||
// });
|
||||
|
||||
// 원본 데이터 저장 (수정/삭제 추적용)
|
||||
setOriginalGroupedData(JSON.parse(JSON.stringify(_groupedData)));
|
||||
|
|
@ -425,31 +424,31 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
|
||||
if (isGeneratingRef.current) {
|
||||
console.log("[채번] 생성 진행 중 - 스킵");
|
||||
// console.log("[채번] 생성 진행 중 - 스킵");
|
||||
return;
|
||||
}
|
||||
|
||||
isGeneratingRef.current = true; // 진행 중 표시
|
||||
console.log("[채번] 생성 시작", { sectionsCount: config.sections.length });
|
||||
// console.log("[채번] 생성 시작", { sectionsCount: config.sections.length });
|
||||
|
||||
const updatedData = { ...currentFormData };
|
||||
let hasChanges = false;
|
||||
|
||||
for (const section of config.sections) {
|
||||
console.log("[채번] 섹션 검사:", section.title, { type: section.type, repeatable: section.repeatable, fieldsCount: section.fields?.length });
|
||||
// console.log("[채번] 섹션 검사:", section.title, { type: section.type, repeatable: section.repeatable, fieldsCount: section.fields?.length });
|
||||
if (section.repeatable || section.type === "table") continue;
|
||||
|
||||
for (const field of (section.fields || [])) {
|
||||
for (const field of section.fields || []) {
|
||||
// generateOnOpen은 기본값 true (undefined일 경우 true로 처리)
|
||||
const shouldGenerateOnOpen = field.numberingRule?.generateOnOpen !== false;
|
||||
console.log("[채번] 필드 검사:", field.columnName, {
|
||||
hasNumberingRule: !!field.numberingRule,
|
||||
enabled: field.numberingRule?.enabled,
|
||||
generateOnOpen: field.numberingRule?.generateOnOpen,
|
||||
shouldGenerateOnOpen,
|
||||
ruleId: field.numberingRule?.ruleId,
|
||||
currentValue: updatedData[field.columnName],
|
||||
});
|
||||
// console.log("[채번] 필드 검사:", field.columnName, {
|
||||
// hasNumberingRule: !!field.numberingRule,
|
||||
// enabled: field.numberingRule?.enabled,
|
||||
// generateOnOpen: field.numberingRule?.generateOnOpen,
|
||||
// shouldGenerateOnOpen,
|
||||
// ruleId: field.numberingRule?.ruleId,
|
||||
// currentValue: updatedData[field.columnName],
|
||||
// });
|
||||
if (
|
||||
field.numberingRule?.enabled &&
|
||||
shouldGenerateOnOpen &&
|
||||
|
|
@ -457,7 +456,7 @@ export function UniversalFormModalComponent({
|
|||
!updatedData[field.columnName]
|
||||
) {
|
||||
try {
|
||||
console.log(`[채번 미리보기 API 호출] ${field.columnName}, ruleId: ${field.numberingRule.ruleId}`);
|
||||
// console.log(`[채번 미리보기 API 호출] ${field.columnName}, ruleId: ${field.numberingRule.ruleId}`);
|
||||
// generateOnOpen: 미리보기만 표시 (DB 시퀀스 증가 안 함)
|
||||
const response = await previewNumberingCode(field.numberingRule.ruleId);
|
||||
if (response.success && response.data?.generatedCode) {
|
||||
|
|
@ -476,10 +475,10 @@ export function UniversalFormModalComponent({
|
|||
|
||||
hasChanges = true;
|
||||
numberingGeneratedRef.current = true; // 생성 완료 표시
|
||||
console.log(
|
||||
`[채번 미리보기 완료] ${field.columnName} = ${generatedCode} (저장 시 실제 할당)`,
|
||||
);
|
||||
console.log(`[채번 규칙 ID 저장] ${ruleIdKey} = ${field.numberingRule.ruleId}`);
|
||||
// console.log(
|
||||
// `[채번 미리보기 완료] ${field.columnName} = ${response.data.generatedCode} (저장 시 실제 할당)`,
|
||||
// );
|
||||
// console.log(`[채번 규칙 ID 저장] ${ruleIdKey} = ${field.numberingRule.ruleId}`);
|
||||
|
||||
// 부모 컴포넌트에도 ruleId 전달 (ModalRepeaterTable → ScreenModal)
|
||||
if (onChange) {
|
||||
|
|
@ -487,7 +486,7 @@ export function UniversalFormModalComponent({
|
|||
...updatedData,
|
||||
[ruleIdKey]: field.numberingRule.ruleId,
|
||||
});
|
||||
console.log(`[채번] 부모에게 ruleId 전달: ${ruleIdKey}`);
|
||||
// console.log(`[채번] 부모에게 ruleId 전달: ${ruleIdKey}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -508,17 +507,17 @@ export function UniversalFormModalComponent({
|
|||
|
||||
// 폼 초기화
|
||||
const initializeForm = useCallback(async () => {
|
||||
console.log("[initializeForm] 시작");
|
||||
// console.log("[initializeForm] 시작");
|
||||
|
||||
// 캡처된 initialData 사용 (props로 전달된 initialData가 아닌)
|
||||
const effectiveInitialData = capturedInitialData.current || initialData;
|
||||
|
||||
console.log("[initializeForm] 초기 데이터:", {
|
||||
capturedInitialData: capturedInitialData.current,
|
||||
initialData: initialData,
|
||||
effectiveInitialData: effectiveInitialData,
|
||||
hasData: effectiveInitialData && Object.keys(effectiveInitialData).length > 0,
|
||||
});
|
||||
// console.log("[initializeForm] 초기 데이터:", {
|
||||
// capturedInitialData: capturedInitialData.current,
|
||||
// initialData: initialData,
|
||||
// effectiveInitialData: effectiveInitialData,
|
||||
// hasData: effectiveInitialData && Object.keys(effectiveInitialData).length > 0,
|
||||
// });
|
||||
|
||||
const newFormData: FormDataState = {};
|
||||
const newRepeatSections: { [sectionId: string]: RepeatSectionItem[] } = {};
|
||||
|
|
@ -545,7 +544,7 @@ export function UniversalFormModalComponent({
|
|||
continue;
|
||||
} else {
|
||||
// 일반 섹션 필드 초기화
|
||||
for (const field of (section.fields || [])) {
|
||||
for (const field of section.fields || []) {
|
||||
// 기본값 설정
|
||||
let value = field.defaultValue ?? "";
|
||||
|
||||
|
|
@ -573,7 +572,9 @@ export function UniversalFormModalComponent({
|
|||
const triggerValue = effectiveInitialData[group.triggerField];
|
||||
if (triggerValue === group.triggerValueOnAdd) {
|
||||
newActivatedGroups.add(key);
|
||||
console.log(`[initializeForm] 옵셔널 그룹 자동 활성화: ${key}, triggerField=${group.triggerField}, value=${triggerValue}`);
|
||||
console.log(
|
||||
`[initializeForm] 옵셔널 그룹 자동 활성화: ${key}, triggerField=${group.triggerField}, value=${triggerValue}`,
|
||||
);
|
||||
|
||||
// 활성화된 그룹의 필드값도 초기화
|
||||
for (const field of group.fields || []) {
|
||||
|
|
@ -599,6 +600,244 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
}
|
||||
|
||||
// 🆕 테이블 섹션(type: "table") 디테일 데이터 로드 (마스터-디테일 구조)
|
||||
// 수정 모드일 때 디테일 테이블에서 데이터 가져오기
|
||||
if (effectiveInitialData) {
|
||||
console.log("[initializeForm] 테이블 섹션 디테일 로드 시작", {
|
||||
sectionsCount: config.sections.length,
|
||||
effectiveInitialDataKeys: Object.keys(effectiveInitialData),
|
||||
});
|
||||
|
||||
for (const section of config.sections) {
|
||||
if (section.type !== "table" || !section.tableConfig) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tableConfig = section.tableConfig;
|
||||
const editConfig = tableConfig.editConfig;
|
||||
const saveConfig = tableConfig.saveConfig;
|
||||
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id} 검사:`, {
|
||||
hasEditConfig: !!editConfig,
|
||||
loadOnEdit: editConfig?.loadOnEdit,
|
||||
hasSaveConfig: !!saveConfig,
|
||||
targetTable: saveConfig?.targetTable,
|
||||
linkColumn: editConfig?.linkColumn,
|
||||
});
|
||||
|
||||
// 수정 모드 로드 설정 확인 (기본값: true)
|
||||
if (editConfig?.loadOnEdit === false) {
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: loadOnEdit=false, 스킵`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 디테일 테이블과 연결 정보 확인
|
||||
const detailTable = saveConfig?.targetTable;
|
||||
let linkColumn = editConfig?.linkColumn;
|
||||
|
||||
if (!detailTable) {
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: saveConfig.targetTable 미설정, 스킵`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// linkColumn이 설정되지 않았으면, 디테일 테이블 컬럼 정보 조회하여 자동 감지
|
||||
if (!linkColumn?.masterField || !linkColumn?.detailField) {
|
||||
try {
|
||||
// 마스터 테이블명 확인 (saveConfig에서)
|
||||
// 1. customApiSave.multiTable.mainTable.tableName (다중 테이블 저장)
|
||||
// 2. saveConfig.tableName (단일 테이블 저장)
|
||||
const masterTable =
|
||||
config.saveConfig?.customApiSave?.multiTable?.mainTable?.tableName || config.saveConfig?.tableName;
|
||||
|
||||
// 디테일 테이블의 컬럼 목록 조회
|
||||
const columnsResponse = await apiClient.get(`/table-management/tables/${detailTable}/columns`);
|
||||
|
||||
if (columnsResponse.data?.success && columnsResponse.data?.data) {
|
||||
// API 응답 구조: { success, data: { columns: [...], total, page, ... } }
|
||||
const columnsArray = columnsResponse.data.data.columns || columnsResponse.data.data || [];
|
||||
const detailColumnsData = Array.isArray(columnsArray) ? columnsArray : [];
|
||||
const detailColumns = detailColumnsData.map((col: any) => col.column_name || col.columnName);
|
||||
const masterKeys = Object.keys(effectiveInitialData);
|
||||
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: 연결 필드 자동 감지`, {
|
||||
masterTable,
|
||||
detailTable,
|
||||
detailColumnsCount: detailColumnsData.length,
|
||||
});
|
||||
|
||||
// 방법 1: 엔티티 관계 기반 감지 (정확)
|
||||
// 디테일 테이블에서 마스터 테이블을 참조하는 엔티티 컬럼 찾기
|
||||
if (masterTable) {
|
||||
for (const col of detailColumnsData) {
|
||||
const colName = col.column_name || col.columnName;
|
||||
const inputType = col.input_type || col.inputType;
|
||||
|
||||
// 엔티티 타입 컬럼 확인
|
||||
if (inputType === "entity") {
|
||||
// reference_table 또는 detail_settings에서 참조 테이블 확인
|
||||
let refTable = col.reference_table || col.referenceTable;
|
||||
|
||||
// detail_settings에서 referenceTable 확인
|
||||
if (!refTable && col.detail_settings) {
|
||||
try {
|
||||
const settings =
|
||||
typeof col.detail_settings === "string"
|
||||
? JSON.parse(col.detail_settings)
|
||||
: col.detail_settings;
|
||||
refTable = settings.referenceTable;
|
||||
} catch {
|
||||
// JSON 파싱 실패 무시
|
||||
}
|
||||
}
|
||||
|
||||
// 마스터 테이블을 참조하는 컬럼 발견
|
||||
if (refTable === masterTable) {
|
||||
// 참조 컬럼 확인 (마스터 테이블의 어떤 컬럼을 참조하는지)
|
||||
let refColumn = col.reference_column || col.referenceColumn;
|
||||
if (!refColumn && col.detail_settings) {
|
||||
try {
|
||||
const settings =
|
||||
typeof col.detail_settings === "string"
|
||||
? JSON.parse(col.detail_settings)
|
||||
: col.detail_settings;
|
||||
refColumn = settings.referenceColumn;
|
||||
} catch {
|
||||
// JSON 파싱 실패 무시
|
||||
}
|
||||
}
|
||||
|
||||
// 마스터 데이터에 해당 컬럼 값이 있는지 확인
|
||||
if (refColumn && effectiveInitialData[refColumn]) {
|
||||
console.log(
|
||||
`[initializeForm] 테이블 섹션 ${section.id}: 엔티티 관계 감지 - ${colName} → ${masterTable}.${refColumn}`,
|
||||
);
|
||||
linkColumn = { masterField: refColumn, detailField: colName };
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 방법 2: 공통 컬럼 패턴 기반 감지 (폴백)
|
||||
// 엔티티 관계가 없으면 공통 컬럼명 패턴으로 찾기
|
||||
if (!linkColumn) {
|
||||
const priorityPatterns = ["_no", "_number", "_code", "_id"];
|
||||
|
||||
for (const pattern of priorityPatterns) {
|
||||
for (const masterKey of masterKeys) {
|
||||
if (
|
||||
masterKey.endsWith(pattern) &&
|
||||
detailColumns.includes(masterKey) &&
|
||||
effectiveInitialData[masterKey] &&
|
||||
masterKey !== "id" &&
|
||||
masterKey !== "company_code"
|
||||
) {
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: 공통 컬럼 패턴 감지 - ${masterKey}`);
|
||||
linkColumn = { masterField: masterKey, detailField: masterKey };
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (linkColumn) break;
|
||||
}
|
||||
}
|
||||
|
||||
// 방법 3: 일반 공통 컬럼 (마지막 폴백)
|
||||
if (!linkColumn) {
|
||||
for (const masterKey of masterKeys) {
|
||||
if (
|
||||
detailColumns.includes(masterKey) &&
|
||||
effectiveInitialData[masterKey] &&
|
||||
masterKey !== "id" &&
|
||||
masterKey !== "company_code" &&
|
||||
!masterKey.startsWith("__")
|
||||
) {
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: 공통 컬럼 감지 - ${masterKey}`);
|
||||
linkColumn = { masterField: masterKey, detailField: masterKey };
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[initializeForm] 테이블 섹션 ${section.id}: 컬럼 정보 조회 실패`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (!linkColumn?.masterField || !linkColumn?.detailField) {
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: linkColumn 미설정 및 자동 감지 실패, 스킵`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 마스터 테이블의 연결 필드 값 가져오기
|
||||
const masterValue = effectiveInitialData[linkColumn.masterField];
|
||||
if (!masterValue) {
|
||||
console.log(
|
||||
`[initializeForm] 테이블 섹션 ${section.id}: masterField(${linkColumn.masterField}) 값 없음, 스킵`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: 디테일 데이터 로드 시작`, {
|
||||
detailTable,
|
||||
linkColumn,
|
||||
masterValue,
|
||||
});
|
||||
|
||||
// 디테일 테이블에서 데이터 조회
|
||||
// operator: "equals"를 사용하여 정확히 일치하는 값만 검색 (엔티티 타입 컬럼에서 중요)
|
||||
const searchCondition: Record<string, any> = {
|
||||
[linkColumn.detailField]: { value: masterValue, operator: "equals" },
|
||||
};
|
||||
|
||||
console.log(
|
||||
`[initializeForm] 테이블 섹션 ${section.id}: API 요청 - URL: /table-management/tables/${detailTable}/data`,
|
||||
);
|
||||
console.log(
|
||||
`[initializeForm] 테이블 섹션 ${section.id}: API 요청 - search:`,
|
||||
JSON.stringify(searchCondition),
|
||||
);
|
||||
|
||||
const response = await apiClient.post(`/table-management/tables/${detailTable}/data`, {
|
||||
search: searchCondition, // filters가 아닌 search로 전달
|
||||
page: 1,
|
||||
size: 1000, // pageSize가 아닌 size로 전달
|
||||
autoFilter: { enabled: true }, // 멀티테넌시 필터 적용
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[initializeForm] 테이블 섹션 ${section.id}: API 응답 - success: ${response.data?.success}, total: ${response.data?.data?.total}, dataLength: ${response.data?.data?.data?.length}`,
|
||||
);
|
||||
|
||||
if (response.data?.success) {
|
||||
// 다양한 응답 구조 처리
|
||||
let items: any[] = [];
|
||||
const data = response.data.data;
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
items = data;
|
||||
} else if (data?.items && Array.isArray(data.items)) {
|
||||
items = data.items;
|
||||
} else if (data?.rows && Array.isArray(data.rows)) {
|
||||
items = data.rows;
|
||||
} else if (data?.data && Array.isArray(data.data)) {
|
||||
items = data.data;
|
||||
}
|
||||
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: ${items.length}건 로드됨`, items);
|
||||
|
||||
// 테이블 섹션 데이터를 formData에 저장 (TableSectionRenderer에서 사용)
|
||||
const tableSectionKey = `__tableSection_${section.id}`;
|
||||
newFormData[tableSectionKey] = items;
|
||||
console.log(`[initializeForm] 테이블 섹션 ${section.id}: formData[${tableSectionKey}]에 저장됨`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[initializeForm] 테이블 섹션 ${section.id}: 디테일 데이터 로드 실패`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setFormData(newFormData);
|
||||
setRepeatSections(newRepeatSections);
|
||||
setCollapsedSections(newCollapsed);
|
||||
|
|
@ -682,9 +921,9 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
|
||||
// 채번규칙 자동 생성
|
||||
console.log("[initializeForm] generateNumberingValues 호출");
|
||||
// console.log("[initializeForm] generateNumberingValues 호출");
|
||||
await generateNumberingValues(newFormData);
|
||||
console.log("[initializeForm] 완료");
|
||||
// console.log("[initializeForm] 완료");
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [config]); // initialData는 의존성에서 제거 (capturedInitialData.current 사용)
|
||||
|
||||
|
|
@ -695,7 +934,7 @@ export function UniversalFormModalComponent({
|
|||
_index: index,
|
||||
};
|
||||
|
||||
for (const field of (section.fields || [])) {
|
||||
for (const field of section.fields || []) {
|
||||
item[field.columnName] = field.defaultValue ?? "";
|
||||
}
|
||||
|
||||
|
|
@ -727,11 +966,7 @@ export function UniversalFormModalComponent({
|
|||
const newData = { ...prev, [columnName]: value };
|
||||
|
||||
// 채번규칙이 활성화된 필드이고, "사용자 수정 가능"이 ON인 경우
|
||||
if (
|
||||
fieldConfig?.numberingRule?.enabled &&
|
||||
fieldConfig?.numberingRule?.editable &&
|
||||
originalNumberingValue
|
||||
) {
|
||||
if (fieldConfig?.numberingRule?.enabled && fieldConfig?.numberingRule?.editable && originalNumberingValue) {
|
||||
// 사용자가 값을 수정했으면 (원본과 다르면) ruleId 제거 → 수동 모드
|
||||
if (value !== originalNumberingValue) {
|
||||
delete newData[ruleIdKey];
|
||||
|
|
@ -825,47 +1060,53 @@ export function UniversalFormModalComponent({
|
|||
}, []);
|
||||
|
||||
// 옵셔널 필드 그룹 활성화
|
||||
const activateOptionalFieldGroup = useCallback((sectionId: string, groupId: string) => {
|
||||
const section = config.sections.find((s) => s.id === sectionId);
|
||||
const group = section?.optionalFieldGroups?.find((g) => g.id === groupId);
|
||||
if (!group) return;
|
||||
const activateOptionalFieldGroup = useCallback(
|
||||
(sectionId: string, groupId: string) => {
|
||||
const section = config.sections.find((s) => s.id === sectionId);
|
||||
const group = section?.optionalFieldGroups?.find((g) => g.id === groupId);
|
||||
if (!group) return;
|
||||
|
||||
const key = `${sectionId}-${groupId}`;
|
||||
setActivatedOptionalFieldGroups((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.add(key);
|
||||
return newSet;
|
||||
});
|
||||
const key = `${sectionId}-${groupId}`;
|
||||
setActivatedOptionalFieldGroups((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.add(key);
|
||||
return newSet;
|
||||
});
|
||||
|
||||
// 연동 필드 값 변경 (추가 시)
|
||||
if (group.triggerField && group.triggerValueOnAdd !== undefined) {
|
||||
handleFieldChange(group.triggerField, group.triggerValueOnAdd);
|
||||
}
|
||||
}, [config, handleFieldChange]);
|
||||
// 연동 필드 값 변경 (추가 시)
|
||||
if (group.triggerField && group.triggerValueOnAdd !== undefined) {
|
||||
handleFieldChange(group.triggerField, group.triggerValueOnAdd);
|
||||
}
|
||||
},
|
||||
[config, handleFieldChange],
|
||||
);
|
||||
|
||||
// 옵셔널 필드 그룹 비활성화
|
||||
const deactivateOptionalFieldGroup = useCallback((sectionId: string, groupId: string) => {
|
||||
const section = config.sections.find((s) => s.id === sectionId);
|
||||
const group = section?.optionalFieldGroups?.find((g) => g.id === groupId);
|
||||
if (!group) return;
|
||||
const deactivateOptionalFieldGroup = useCallback(
|
||||
(sectionId: string, groupId: string) => {
|
||||
const section = config.sections.find((s) => s.id === sectionId);
|
||||
const group = section?.optionalFieldGroups?.find((g) => g.id === groupId);
|
||||
if (!group) return;
|
||||
|
||||
const key = `${sectionId}-${groupId}`;
|
||||
setActivatedOptionalFieldGroups((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(key);
|
||||
return newSet;
|
||||
});
|
||||
const key = `${sectionId}-${groupId}`;
|
||||
setActivatedOptionalFieldGroups((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(key);
|
||||
return newSet;
|
||||
});
|
||||
|
||||
// 연동 필드 값 변경 (제거 시)
|
||||
if (group.triggerField && group.triggerValueOnRemove !== undefined) {
|
||||
handleFieldChange(group.triggerField, group.triggerValueOnRemove);
|
||||
}
|
||||
// 연동 필드 값 변경 (제거 시)
|
||||
if (group.triggerField && group.triggerValueOnRemove !== undefined) {
|
||||
handleFieldChange(group.triggerField, group.triggerValueOnRemove);
|
||||
}
|
||||
|
||||
// 옵셔널 필드 그룹 필드 값 초기화
|
||||
(group.fields || []).forEach((field) => {
|
||||
handleFieldChange(field.columnName, field.defaultValue || "");
|
||||
});
|
||||
}, [config, handleFieldChange]);
|
||||
// 옵셔널 필드 그룹 필드 값 초기화
|
||||
(group.fields || []).forEach((field) => {
|
||||
handleFieldChange(field.columnName, field.defaultValue || "");
|
||||
});
|
||||
},
|
||||
[config, handleFieldChange],
|
||||
);
|
||||
|
||||
// Select 옵션 로드
|
||||
const loadSelectOptions = useCallback(
|
||||
|
|
@ -911,9 +1152,7 @@ export function UniversalFormModalComponent({
|
|||
// categoryKey 형식: "tableName.columnName"
|
||||
const [categoryTable, categoryColumn] = optionConfig.categoryKey.split(".");
|
||||
if (categoryTable && categoryColumn) {
|
||||
const response = await apiClient.get(
|
||||
`/table-categories/${categoryTable}/${categoryColumn}/values`
|
||||
);
|
||||
const response = await apiClient.get(`/table-categories/${categoryTable}/${categoryColumn}/values`);
|
||||
if (response.data?.success && response.data?.data) {
|
||||
// 코드값을 DB에 저장하고 라벨값을 화면에 표시
|
||||
options = response.data.data.map((item: any) => ({
|
||||
|
|
@ -992,7 +1231,7 @@ export function UniversalFormModalComponent({
|
|||
for (const section of config.sections) {
|
||||
if (section.repeatable || section.type === "table") continue; // 반복 섹션 및 테이블 섹션은 별도 검증
|
||||
|
||||
for (const field of (section.fields || [])) {
|
||||
for (const field of section.fields || []) {
|
||||
if (field.required && !field.hidden && !field.numberingRule?.hidden) {
|
||||
const value = formData[field.columnName];
|
||||
if (value === undefined || value === null || value === "") {
|
||||
|
|
@ -1029,7 +1268,7 @@ export function UniversalFormModalComponent({
|
|||
// 테이블 타입 섹션은 건너뛰기
|
||||
if (section.type === "table") continue;
|
||||
|
||||
for (const field of (section.fields || [])) {
|
||||
for (const field of section.fields || []) {
|
||||
if (field.numberingRule?.enabled && field.numberingRule?.ruleId) {
|
||||
const ruleIdKey = `${field.columnName}_numberingRuleId`;
|
||||
const hasRuleId = dataToSave[ruleIdKey]; // 사용자가 수정하지 않았으면 ruleId 유지됨
|
||||
|
|
@ -1070,22 +1309,30 @@ export function UniversalFormModalComponent({
|
|||
|
||||
// 별도 테이블에 저장해야 하는 테이블 섹션 목록
|
||||
const tableSectionsForSeparateTable = config.sections.filter(
|
||||
(s) => s.type === "table" &&
|
||||
(s) =>
|
||||
s.type === "table" &&
|
||||
s.tableConfig?.saveConfig?.targetTable &&
|
||||
s.tableConfig.saveConfig.targetTable !== config.saveConfig.tableName
|
||||
s.tableConfig.saveConfig.targetTable !== config.saveConfig.tableName,
|
||||
);
|
||||
|
||||
// 테이블 섹션이 있고 메인 테이블에 품목별로 저장하는 경우 (공통 + 개별 병합 저장)
|
||||
// targetTable이 없거나 메인 테이블과 같은 경우
|
||||
const tableSectionsForMainTable = config.sections.filter(
|
||||
(s) => s.type === "table" &&
|
||||
(s) =>
|
||||
s.type === "table" &&
|
||||
(!s.tableConfig?.saveConfig?.targetTable ||
|
||||
s.tableConfig.saveConfig.targetTable === config.saveConfig.tableName)
|
||||
s.tableConfig.saveConfig.targetTable === config.saveConfig.tableName),
|
||||
);
|
||||
|
||||
console.log("[saveSingleRow] 메인 테이블:", config.saveConfig.tableName);
|
||||
console.log("[saveSingleRow] 메인 테이블에 저장할 테이블 섹션:", tableSectionsForMainTable.map(s => s.id));
|
||||
console.log("[saveSingleRow] 별도 테이블에 저장할 테이블 섹션:", tableSectionsForSeparateTable.map(s => s.id));
|
||||
console.log(
|
||||
"[saveSingleRow] 메인 테이블에 저장할 테이블 섹션:",
|
||||
tableSectionsForMainTable.map((s) => s.id),
|
||||
);
|
||||
console.log(
|
||||
"[saveSingleRow] 별도 테이블에 저장할 테이블 섹션:",
|
||||
tableSectionsForSeparateTable.map((s) => s.id),
|
||||
);
|
||||
console.log("[saveSingleRow] 테이블 섹션 데이터 키:", Object.keys(tableSectionData));
|
||||
console.log("[saveSingleRow] dataToSave 키:", Object.keys(dataToSave));
|
||||
|
||||
|
|
@ -1132,7 +1379,7 @@ export function UniversalFormModalComponent({
|
|||
|
||||
const response = await apiClient.post(
|
||||
`/table-management/tables/${config.saveConfig.tableName}/add`,
|
||||
rowToSave
|
||||
rowToSave,
|
||||
);
|
||||
|
||||
if (!response.data?.success) {
|
||||
|
|
@ -1239,7 +1486,7 @@ export function UniversalFormModalComponent({
|
|||
|
||||
const saveResponse = await apiClient.post(
|
||||
`/table-management/tables/${section.tableConfig.saveConfig.targetTable}/add`,
|
||||
itemToSave
|
||||
itemToSave,
|
||||
);
|
||||
|
||||
if (!saveResponse.data?.success) {
|
||||
|
|
@ -1249,7 +1496,13 @@ export function UniversalFormModalComponent({
|
|||
}
|
||||
}
|
||||
}
|
||||
}, [config.sections, config.saveConfig.tableName, config.saveConfig.primaryKeyColumn, config.saveConfig.sectionSaveModes, formData]);
|
||||
}, [
|
||||
config.sections,
|
||||
config.saveConfig.tableName,
|
||||
config.saveConfig.primaryKeyColumn,
|
||||
config.saveConfig.sectionSaveModes,
|
||||
formData,
|
||||
]);
|
||||
|
||||
// 다중 행 저장 (겸직 등)
|
||||
const saveMultipleRows = useCallback(async () => {
|
||||
|
|
@ -1325,7 +1578,7 @@ export function UniversalFormModalComponent({
|
|||
for (const section of config.sections) {
|
||||
if (section.repeatable || section.type === "table") continue;
|
||||
|
||||
for (const field of (section.fields || [])) {
|
||||
for (const field of section.fields || []) {
|
||||
if (field.numberingRule?.enabled && field.numberingRule?.ruleId) {
|
||||
// generateOnSave 또는 generateOnOpen 모두 저장 시 실제 순번 할당
|
||||
const shouldAllocate = field.numberingRule.generateOnSave || field.numberingRule.generateOnOpen;
|
||||
|
|
@ -1400,7 +1653,7 @@ export function UniversalFormModalComponent({
|
|||
for (const section of config.sections) {
|
||||
if (section.repeatable || section.type === "table") continue;
|
||||
|
||||
for (const field of (section.fields || [])) {
|
||||
for (const field of section.fields || []) {
|
||||
// 채번규칙이 활성화된 필드 처리
|
||||
if (field.numberingRule?.enabled && field.numberingRule?.ruleId) {
|
||||
// 신규 생성이거나 값이 없는 경우에만 채번
|
||||
|
|
@ -1758,9 +2011,7 @@ export function UniversalFormModalComponent({
|
|||
// 메인 표시 컬럼 (displayColumn)
|
||||
const mainDisplayVal = row[lfg.displayColumn || ""] || "";
|
||||
// 서브 표시 컬럼 (subDisplayColumn이 있으면 사용, 없으면 valueColumn 사용)
|
||||
const subDisplayVal = lfg.subDisplayColumn
|
||||
? (row[lfg.subDisplayColumn] || "")
|
||||
: (row[valueColumn] || "");
|
||||
const subDisplayVal = lfg.subDisplayColumn ? row[lfg.subDisplayColumn] || "" : row[valueColumn] || "";
|
||||
|
||||
switch (lfg.displayFormat) {
|
||||
case "code_name":
|
||||
|
|
@ -1779,7 +2030,10 @@ export function UniversalFormModalComponent({
|
|||
matches.forEach((match) => {
|
||||
const columnName = match.slice(1, -1); // { } 제거
|
||||
const columnValue = row[columnName];
|
||||
result = result.replace(match, columnValue !== undefined && columnValue !== null ? String(columnValue) : "");
|
||||
result = result.replace(
|
||||
match,
|
||||
columnValue !== undefined && columnValue !== null ? String(columnValue) : "",
|
||||
);
|
||||
});
|
||||
}
|
||||
return result;
|
||||
|
|
@ -1836,7 +2090,12 @@ export function UniversalFormModalComponent({
|
|||
<SelectContent>
|
||||
{sourceData.length > 0 ? (
|
||||
sourceData
|
||||
.filter((row) => row[valueColumn] !== null && row[valueColumn] !== undefined && String(row[valueColumn]) !== "")
|
||||
.filter(
|
||||
(row) =>
|
||||
row[valueColumn] !== null &&
|
||||
row[valueColumn] !== undefined &&
|
||||
String(row[valueColumn]) !== "",
|
||||
)
|
||||
.map((row, index) => (
|
||||
<SelectItem key={`${row[valueColumn]}_${index}`} value={String(row[valueColumn])}>
|
||||
{getDisplayText(row)}
|
||||
|
|
@ -2100,9 +2359,7 @@ export function UniversalFormModalComponent({
|
|||
{/* 옵셔널 필드 그룹 렌더링 */}
|
||||
{section.optionalFieldGroups && section.optionalFieldGroups.length > 0 && (
|
||||
<div className="mt-4 space-y-3">
|
||||
{section.optionalFieldGroups.map((group) =>
|
||||
renderOptionalFieldGroup(section, group, sectionColumns)
|
||||
)}
|
||||
{section.optionalFieldGroups.map((group) => renderOptionalFieldGroup(section, group, sectionColumns))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
|
|
@ -2130,7 +2387,7 @@ export function UniversalFormModalComponent({
|
|||
const renderOptionalFieldGroup = (
|
||||
section: FormSectionConfig,
|
||||
group: OptionalFieldGroupConfig,
|
||||
sectionColumns: number
|
||||
sectionColumns: number,
|
||||
) => {
|
||||
const key = `${section.id}-${group.id}`;
|
||||
const isActivated = activatedOptionalFieldGroups.has(key);
|
||||
|
|
@ -2149,9 +2406,7 @@ export function UniversalFormModalComponent({
|
|||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-muted-foreground text-sm font-medium">{group.title}</p>
|
||||
{group.description && (
|
||||
<p className="text-muted-foreground/70 mt-0.5 text-xs">{group.description}</p>
|
||||
)}
|
||||
{group.description && <p className="text-muted-foreground/70 mt-0.5 text-xs">{group.description}</p>}
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
|
|
@ -2190,16 +2445,10 @@ export function UniversalFormModalComponent({
|
|||
<div className="flex items-center justify-between p-3">
|
||||
<CollapsibleTrigger asChild>
|
||||
<button className="flex items-center gap-2 text-left hover:opacity-80">
|
||||
{isCollapsed ? (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
)}
|
||||
{isCollapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
||||
<div>
|
||||
<p className="text-sm font-medium">{group.title}</p>
|
||||
{group.description && (
|
||||
<p className="text-muted-foreground mt-0.5 text-xs">{group.description}</p>
|
||||
)}
|
||||
{group.description && <p className="text-muted-foreground mt-0.5 text-xs">{group.description}</p>}
|
||||
</div>
|
||||
</button>
|
||||
</CollapsibleTrigger>
|
||||
|
|
@ -2229,8 +2478,8 @@ export function UniversalFormModalComponent({
|
|||
formData[field.columnName],
|
||||
(value) => handleFieldChange(field.columnName, value),
|
||||
`${section.id}-${group.id}-${field.id}`,
|
||||
groupColumns
|
||||
)
|
||||
groupColumns,
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
|
|
@ -2244,9 +2493,7 @@ export function UniversalFormModalComponent({
|
|||
<div className="mb-3 flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium">{group.title}</p>
|
||||
{group.description && (
|
||||
<p className="text-muted-foreground mt-0.5 text-xs">{group.description}</p>
|
||||
)}
|
||||
{group.description && <p className="text-muted-foreground mt-0.5 text-xs">{group.description}</p>}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -2273,8 +2520,8 @@ export function UniversalFormModalComponent({
|
|||
formData[field.columnName],
|
||||
(value) => handleFieldChange(field.columnName, value),
|
||||
`${section.id}-${group.id}-${field.id}`,
|
||||
groupColumns
|
||||
)
|
||||
groupColumns,
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2402,7 +2649,8 @@ export function UniversalFormModalComponent({
|
|||
<div className="text-muted-foreground text-center">
|
||||
<p className="font-medium">{config.modal.title || "범용 폼 모달"}</p>
|
||||
<p className="mt-1 text-xs">
|
||||
{config.sections.length}개 섹션 |{config.sections.reduce((acc, s) => acc + (s.fields?.length || 0), 0)}개 필드
|
||||
{config.sections.length}개 섹션 |{config.sections.reduce((acc, s) => acc + (s.fields?.length || 0), 0)}개
|
||||
필드
|
||||
</p>
|
||||
<p className="mt-1 text-xs">저장 테이블: {config.saveConfig.tableName || "(미설정)"}</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3022,11 +3022,11 @@ export class ButtonActionExecutor {
|
|||
comp.componentType === "split-panel-layout",
|
||||
);
|
||||
}
|
||||
console.log("🔍 [openEditModal] 분할 패널 확인:", {
|
||||
targetScreenId: config.targetScreenId,
|
||||
hasSplitPanel,
|
||||
componentTypes: layoutData?.components?.map((c: any) => c.type || c.componentType) || [],
|
||||
});
|
||||
// console.log("🔍 [openEditModal] 분할 패널 확인:", {
|
||||
// targetScreenId: config.targetScreenId,
|
||||
// hasSplitPanel,
|
||||
// componentTypes: layoutData?.components?.map((c: any) => c.type || c.componentType) || [],
|
||||
// });
|
||||
} catch (error) {
|
||||
console.warn("레이아웃 정보를 가져오지 못했습니다:", error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ export const useModalDataStore = create<ModalDataState>()(
|
|||
dataRegistry: {},
|
||||
|
||||
setData: (sourceId, items) => {
|
||||
console.log("📦 [ModalDataStore] 데이터 저장:", { sourceId, itemCount: items.length, items });
|
||||
// console.log("📦 [ModalDataStore] 데이터 저장:", { sourceId, itemCount: items.length, items });
|
||||
set((state) => ({
|
||||
dataRegistry: {
|
||||
...state.dataRegistry,
|
||||
|
|
|
|||
Loading…
Reference in New Issue