Merge remote-tracking branch 'origin/main' into ksh
This commit is contained in:
commit
ad39374e54
|
|
@ -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()}`
|
||||
|
|
|
|||
|
|
@ -107,10 +107,10 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
|
|||
const dbTableName = config.dbTableName || "vehicles";
|
||||
const dbKeyField = config.dbKeyField || "user_id";
|
||||
|
||||
// 기본 옵션 (포항/광양)
|
||||
// 기본 옵션 (포항/광양) - 한글로 저장
|
||||
const DEFAULT_OPTIONS: LocationOption[] = [
|
||||
{ value: "pohang", label: "포항" },
|
||||
{ value: "gwangyang", label: "광양" },
|
||||
{ value: "포항", label: "포항" },
|
||||
{ value: "광양", label: "광양" },
|
||||
];
|
||||
|
||||
// 상태
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ export const LocationSwapSelectorDefinition = createComponentDefinition({
|
|||
labelField: "location_name", // 표시 필드
|
||||
codeCategory: "", // 코드 관리 카테고리 (type이 "code"일 때)
|
||||
staticOptions: [
|
||||
{ value: "pohang", label: "포항" },
|
||||
{ value: "gwangyang", label: "광양" },
|
||||
], // 정적 옵션 (type이 "static"일 때)
|
||||
{ value: "포항", label: "포항" },
|
||||
{ value: "광양", label: "광양" },
|
||||
], // 정적 옵션 (type이 "static"일 때) - 한글로 저장
|
||||
},
|
||||
// 필드 매핑
|
||||
departureField: "departure", // 출발지 저장 필드
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { RepeatScreenModalDefinition } from "./index";
|
|||
// 컴포넌트 자동 등록
|
||||
if (typeof window !== "undefined") {
|
||||
ComponentRegistry.registerComponent(RepeatScreenModalDefinition);
|
||||
console.log("✅ RepeatScreenModal 컴포넌트 등록 완료");
|
||||
// console.log("✅ RepeatScreenModal 컴포넌트 등록 완료");
|
||||
}
|
||||
|
||||
export {};
|
||||
|
|
|
|||
|
|
@ -432,10 +432,25 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
|||
const groups = componentConfig.fieldGroups || [];
|
||||
const additionalFields = componentConfig.additionalFields || [];
|
||||
|
||||
itemsList.forEach((item) => {
|
||||
itemsList.forEach((item, itemIndex) => {
|
||||
// 각 그룹의 엔트리 배열들을 준비
|
||||
const groupEntriesArrays: GroupEntry[][] = groups.map((group) => item.fieldGroups[group.id] || []);
|
||||
|
||||
// 🆕 모든 그룹이 비어있는지 확인
|
||||
const allGroupsEmpty = groupEntriesArrays.every((arr) => arr.length === 0);
|
||||
|
||||
if (allGroupsEmpty) {
|
||||
// 🆕 모든 그룹이 비어있으면 품목 기본 정보만으로 레코드 생성
|
||||
// (거래처 품번/품명, 기간별 단가 없이도 저장 가능)
|
||||
console.log("📝 [generateCartesianProduct] 모든 그룹이 비어있음 - 품목 기본 레코드 생성", {
|
||||
itemIndex,
|
||||
itemId: item.id,
|
||||
});
|
||||
// 빈 객체를 추가하면 parentKeys와 합쳐져서 기본 레코드가 됨
|
||||
allRecords.push({});
|
||||
return;
|
||||
}
|
||||
|
||||
// Cartesian Product 재귀 함수
|
||||
const cartesian = (arrays: GroupEntry[][], currentIndex: number, currentCombination: Record<string, any>) => {
|
||||
if (currentIndex === arrays.length) {
|
||||
|
|
@ -446,7 +461,8 @@ export const SelectedItemsDetailInputComponent: React.FC<SelectedItemsDetailInpu
|
|||
|
||||
const currentGroupEntries = arrays[currentIndex];
|
||||
if (currentGroupEntries.length === 0) {
|
||||
// 현재 그룹에 데이터가 없으면 빈 조합으로 다음 그룹 진행
|
||||
// 🆕 현재 그룹에 데이터가 없으면 빈 조합으로 다음 그룹 진행
|
||||
// (그룹이 비어있어도 다른 그룹의 데이터로 레코드 생성)
|
||||
cartesian(arrays, currentIndex + 1, currentCombination);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -2248,9 +2248,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
// 🆕 편집 모드 진입 placeholder (실제 구현은 visibleColumns 정의 후)
|
||||
const startEditingRef = useRef<() => void>(() => {});
|
||||
|
||||
// 🆕 각 컬럼의 고유값 목록 계산
|
||||
// 🆕 각 컬럼의 고유값 목록 계산 (라벨 포함)
|
||||
const columnUniqueValues = useMemo(() => {
|
||||
const result: Record<string, string[]> = {};
|
||||
const result: Record<string, Array<{ value: string; label: string }>> = {};
|
||||
|
||||
if (data.length === 0) return result;
|
||||
|
||||
|
|
@ -2258,16 +2258,34 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
if (column.columnName === "__checkbox__") return;
|
||||
|
||||
const mappedColumnName = joinColumnMapping[column.columnName] || column.columnName;
|
||||
const values = new Set<string>();
|
||||
// 라벨 컬럼 후보들 (백엔드에서 _name, _label, _value_label 등으로 반환할 수 있음)
|
||||
const labelColumnCandidates = [
|
||||
`${column.columnName}_name`, // 예: division_name
|
||||
`${column.columnName}_label`, // 예: division_label
|
||||
`${column.columnName}_value_label`, // 예: division_value_label
|
||||
];
|
||||
const valuesMap = new Map<string, string>(); // value -> label
|
||||
|
||||
data.forEach((row) => {
|
||||
const val = row[mappedColumnName];
|
||||
if (val !== null && val !== undefined && val !== "") {
|
||||
values.add(String(val));
|
||||
const valueStr = String(val);
|
||||
// 라벨 컬럼 후보들 중 값이 있는 것 사용, 없으면 원본 값 사용
|
||||
let label = valueStr;
|
||||
for (const labelCol of labelColumnCandidates) {
|
||||
if (row[labelCol] && row[labelCol] !== "") {
|
||||
label = String(row[labelCol]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
valuesMap.set(valueStr, label);
|
||||
}
|
||||
});
|
||||
|
||||
result[column.columnName] = Array.from(values).sort();
|
||||
// value-label 쌍으로 저장하고 라벨 기준 정렬
|
||||
result[column.columnName] = Array.from(valuesMap.entries())
|
||||
.map(([value, label]) => ({ value, label }))
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
});
|
||||
|
||||
return result;
|
||||
|
|
@ -5756,16 +5774,16 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
)}
|
||||
</div>
|
||||
<div className="max-h-48 space-y-1 overflow-y-auto">
|
||||
{columnUniqueValues[column.columnName]?.slice(0, 50).map((val) => {
|
||||
const isSelected = headerFilters[column.columnName]?.has(val);
|
||||
{columnUniqueValues[column.columnName]?.slice(0, 50).map((item) => {
|
||||
const isSelected = headerFilters[column.columnName]?.has(item.value);
|
||||
return (
|
||||
<div
|
||||
key={val}
|
||||
key={item.value}
|
||||
className={cn(
|
||||
"hover:bg-muted flex cursor-pointer items-center gap-2 rounded px-2 py-1 text-xs",
|
||||
isSelected && "bg-primary/10",
|
||||
)}
|
||||
onClick={() => toggleHeaderFilter(column.columnName, val)}
|
||||
onClick={() => toggleHeaderFilter(column.columnName, item.value)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -5775,7 +5793,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
>
|
||||
{isSelected && <Check className="text-primary-foreground h-3 w-3" />}
|
||||
</div>
|
||||
<span className="truncate">{val || "(빈 값)"}</span>
|
||||
<span className="truncate">{item.label || "(빈 값)"}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -385,6 +385,9 @@ export function TableSectionRenderer({
|
|||
// 소스 테이블의 카테고리 타입 컬럼 목록
|
||||
const [sourceCategoryColumns, setSourceCategoryColumns] = useState<string[]>([]);
|
||||
|
||||
// 소스 테이블의 컬럼 라벨 (API에서 동적 로드)
|
||||
const [sourceColumnLabels, setSourceColumnLabels] = useState<Record<string, string>>({});
|
||||
|
||||
// 소스 테이블의 카테고리 타입 컬럼 목록 로드
|
||||
useEffect(() => {
|
||||
const loadCategoryColumns = async () => {
|
||||
|
|
@ -410,6 +413,44 @@ export function TableSectionRenderer({
|
|||
loadCategoryColumns();
|
||||
}, [tableConfig.source.tableName]);
|
||||
|
||||
// 소스 테이블의 컬럼 라벨 로드 (source.columnLabels가 비어있을 때만)
|
||||
useEffect(() => {
|
||||
const loadColumnLabels = async () => {
|
||||
const sourceTableName = tableConfig.source.tableName;
|
||||
if (!sourceTableName) return;
|
||||
|
||||
// 이미 source.columnLabels가 설정되어 있으면 스킵
|
||||
if (tableConfig.source.columnLabels && Object.keys(tableConfig.source.columnLabels).length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiClient.get(`/table-management/tables/${sourceTableName}/columns`);
|
||||
|
||||
if (response.data?.success && response.data.data) {
|
||||
const columnsData = response.data.data.columns || response.data.data || [];
|
||||
const labels: Record<string, string> = {};
|
||||
|
||||
for (const col of columnsData) {
|
||||
const colName = col.column_name || col.columnName;
|
||||
// displayName: API에서 반환하는 라벨 (COALESCE(cl.column_label, c.column_name))
|
||||
const colLabel = col.displayName || col.column_label || col.columnLabel || col.comment;
|
||||
// 라벨이 컬럼명과 다를 때만 저장 (의미있는 라벨인 경우)
|
||||
if (colName && colLabel && colLabel !== colName) {
|
||||
labels[colName] = colLabel;
|
||||
}
|
||||
}
|
||||
|
||||
setSourceColumnLabels(labels);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("소스 테이블 컬럼 라벨 조회 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadColumnLabels();
|
||||
}, [tableConfig.source.tableName, tableConfig.source.columnLabels]);
|
||||
|
||||
// 조건부 테이블: 동적 옵션 로드 (optionSource 설정이 있는 경우)
|
||||
useEffect(() => {
|
||||
if (!isConditionalMode) return;
|
||||
|
|
@ -816,13 +857,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;
|
||||
|
|
@ -1296,7 +1346,12 @@ export function TableSectionRenderer({
|
|||
const sourceTable = source.tableName;
|
||||
const sourceColumns = source.displayColumns;
|
||||
const sourceSearchFields = source.searchColumns;
|
||||
const columnLabels = source.columnLabels || {};
|
||||
// 컬럼 라벨: source.columnLabels가 있으면 우선 사용, 없으면 동적 로드된 라벨 사용
|
||||
const columnLabels = useMemo(() => {
|
||||
const configLabels = source.columnLabels || {};
|
||||
// 설정된 라벨이 있으면 설정 우선, 없으면 API에서 로드한 라벨 사용
|
||||
return { ...sourceColumnLabels, ...configLabels };
|
||||
}, [source.columnLabels, sourceColumnLabels]);
|
||||
const modalTitle = uiConfig?.modalTitle || "항목 검색 및 선택";
|
||||
const multiSelect = uiConfig?.multiSelect ?? true;
|
||||
|
||||
|
|
@ -1327,7 +1382,7 @@ export function TableSectionRenderer({
|
|||
}
|
||||
}
|
||||
}
|
||||
console.log("[TableSectionRenderer] baseFilterCondition:", condition, "preFilters:", filters?.preFilters);
|
||||
// console.log("[TableSectionRenderer] baseFilterCondition:", condition, "preFilters:", filters?.preFilters);
|
||||
return condition;
|
||||
}, [filters?.preFilters]);
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -2106,7 +2106,20 @@ export class ButtonActionExecutor {
|
|||
|
||||
// 모든 그룹의 카티션 곱 생성
|
||||
const entryArrays = groupArrays.map((g) => g.entries);
|
||||
const combinations = cartesianProduct(entryArrays);
|
||||
|
||||
// 🆕 모든 그룹이 비어있는지 확인
|
||||
const allGroupsEmpty = entryArrays.every((arr) => arr.length === 0);
|
||||
|
||||
let combinations: any[][];
|
||||
if (allGroupsEmpty) {
|
||||
// 🆕 모든 그룹이 비어있으면 빈 조합 하나 생성 (품목 기본 정보만으로 저장)
|
||||
console.log("📝 [handleBatchSave] 모든 그룹이 비어있음 - 기본 레코드 생성");
|
||||
combinations = [[]];
|
||||
} else {
|
||||
// 빈 그룹을 필터링하여 카티션 곱 계산 (빈 그룹은 무시)
|
||||
const nonEmptyArrays = entryArrays.filter((arr) => arr.length > 0);
|
||||
combinations = nonEmptyArrays.length > 0 ? cartesianProduct(nonEmptyArrays) : [[]];
|
||||
}
|
||||
|
||||
// 각 조합을 개별 레코드로 저장
|
||||
for (let i = 0; i < combinations.length; i++) {
|
||||
|
|
@ -3016,11 +3029,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