Merge branch 'jskim-node' of http://39.117.244.52:3000/kjs/ERP-node into gbpark-node
; Please enter a commit message to explain why this merge is necessary, ; especially if it merges an updated upstream into a topic branch. ; ; Lines starting with ';' will be ignored, and an empty message aborts ; the commit.
This commit is contained in:
@@ -1221,38 +1221,35 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
||||
toast.warning("저장은 완료되었으나 연결된 제어 실행 중 오류가 발생했습니다.");
|
||||
}
|
||||
|
||||
// V2Repeater 디테일 데이터 저장 (모달 닫기 전에 실행)
|
||||
try {
|
||||
const repeaterSavePromise = new Promise<void>((resolve) => {
|
||||
const fallbackTimeout = setTimeout(resolve, 5000);
|
||||
const handler = () => {
|
||||
clearTimeout(fallbackTimeout);
|
||||
window.removeEventListener("repeaterSaveComplete", handler);
|
||||
resolve();
|
||||
};
|
||||
window.addEventListener("repeaterSaveComplete", handler);
|
||||
});
|
||||
// V2Repeater 디테일 데이터 저장 (등록된 인스턴스가 있을 때만)
|
||||
const hasRepeaterForInsert = window.__v2RepeaterInstances && window.__v2RepeaterInstances.size > 0;
|
||||
if (hasRepeaterForInsert) {
|
||||
try {
|
||||
const repeaterSavePromise = new Promise<void>((resolve) => {
|
||||
const fallbackTimeout = setTimeout(resolve, 5000);
|
||||
const handler = () => {
|
||||
clearTimeout(fallbackTimeout);
|
||||
window.removeEventListener("repeaterSaveComplete", handler);
|
||||
resolve();
|
||||
};
|
||||
window.addEventListener("repeaterSaveComplete", handler);
|
||||
});
|
||||
|
||||
console.log("🟢 [EditModal] INSERT 후 repeaterSave 이벤트 발행:", {
|
||||
parentId: masterRecordId,
|
||||
tableName: screenData.screenInfo.tableName,
|
||||
});
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("repeaterSave", {
|
||||
detail: {
|
||||
parentId: masterRecordId,
|
||||
tableName: screenData.screenInfo.tableName,
|
||||
mainFormData: formData,
|
||||
masterRecordId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("repeaterSave", {
|
||||
detail: {
|
||||
parentId: masterRecordId,
|
||||
tableName: screenData.screenInfo.tableName,
|
||||
mainFormData: formData,
|
||||
masterRecordId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await repeaterSavePromise;
|
||||
console.log("✅ [EditModal] INSERT 후 repeaterSave 완료");
|
||||
} catch (repeaterError) {
|
||||
console.error("❌ [EditModal] repeaterSave 오류:", repeaterError);
|
||||
await repeaterSavePromise;
|
||||
} catch (repeaterError) {
|
||||
console.error("❌ [EditModal] repeaterSave 오류:", repeaterError);
|
||||
}
|
||||
}
|
||||
|
||||
handleClose();
|
||||
@@ -1351,38 +1348,35 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
||||
toast.warning("저장은 완료되었으나 연결된 제어 실행 중 오류가 발생했습니다.");
|
||||
}
|
||||
|
||||
// V2Repeater 디테일 데이터 저장 (모달 닫기 전에 실행)
|
||||
try {
|
||||
const repeaterSavePromise = new Promise<void>((resolve) => {
|
||||
const fallbackTimeout = setTimeout(resolve, 5000);
|
||||
const handler = () => {
|
||||
clearTimeout(fallbackTimeout);
|
||||
window.removeEventListener("repeaterSaveComplete", handler);
|
||||
resolve();
|
||||
};
|
||||
window.addEventListener("repeaterSaveComplete", handler);
|
||||
});
|
||||
// V2Repeater 디테일 데이터 저장 (등록된 인스턴스가 있을 때만)
|
||||
const hasRepeaterForUpdate = window.__v2RepeaterInstances && window.__v2RepeaterInstances.size > 0;
|
||||
if (hasRepeaterForUpdate) {
|
||||
try {
|
||||
const repeaterSavePromise = new Promise<void>((resolve) => {
|
||||
const fallbackTimeout = setTimeout(resolve, 5000);
|
||||
const handler = () => {
|
||||
clearTimeout(fallbackTimeout);
|
||||
window.removeEventListener("repeaterSaveComplete", handler);
|
||||
resolve();
|
||||
};
|
||||
window.addEventListener("repeaterSaveComplete", handler);
|
||||
});
|
||||
|
||||
console.log("🟢 [EditModal] UPDATE 후 repeaterSave 이벤트 발행:", {
|
||||
parentId: recordId,
|
||||
tableName: screenData.screenInfo.tableName,
|
||||
});
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("repeaterSave", {
|
||||
detail: {
|
||||
parentId: recordId,
|
||||
tableName: screenData.screenInfo.tableName,
|
||||
mainFormData: formData,
|
||||
masterRecordId: recordId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("repeaterSave", {
|
||||
detail: {
|
||||
parentId: recordId,
|
||||
tableName: screenData.screenInfo.tableName,
|
||||
mainFormData: formData,
|
||||
masterRecordId: recordId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await repeaterSavePromise;
|
||||
console.log("✅ [EditModal] UPDATE 후 repeaterSave 완료");
|
||||
} catch (repeaterError) {
|
||||
console.error("❌ [EditModal] repeaterSave 오류:", repeaterError);
|
||||
await repeaterSavePromise;
|
||||
} catch (repeaterError) {
|
||||
console.error("❌ [EditModal] repeaterSave 오류:", repeaterError);
|
||||
}
|
||||
}
|
||||
|
||||
// 리피터 저장 완료 후 메인 테이블 새로고침
|
||||
|
||||
@@ -50,9 +50,6 @@ export const V2Repeater: React.FC<V2RepeaterProps> = ({
|
||||
formData: parentFormData,
|
||||
...restProps
|
||||
}) => {
|
||||
// ScreenModal에서 전달된 groupedData (모달 간 데이터 전달용)
|
||||
const groupedData = (restProps as any).groupedData || (restProps as any)._groupedData;
|
||||
|
||||
// componentId 결정: 직접 전달 또는 component 객체에서 추출
|
||||
const effectiveComponentId = componentId || (restProps as any).component?.id;
|
||||
|
||||
@@ -214,21 +211,20 @@ export const V2Repeater: React.FC<V2RepeaterProps> = ({
|
||||
const isModalMode = config.renderMode === "modal";
|
||||
|
||||
// 전역 리피터 등록
|
||||
// 🆕 useCustomTable이 설정된 경우 mainTableName 사용 (실제 저장될 테이블)
|
||||
// tableName이 비어있어도 반드시 등록 (repeaterSave 이벤트 발행 가드에 필요)
|
||||
useEffect(() => {
|
||||
const targetTableName =
|
||||
config.useCustomTable && config.mainTableName ? config.mainTableName : config.dataSource?.tableName;
|
||||
const registrationKey = targetTableName || "__v2_repeater_same_table__";
|
||||
|
||||
if (targetTableName) {
|
||||
if (!window.__v2RepeaterInstances) {
|
||||
window.__v2RepeaterInstances = new Set();
|
||||
}
|
||||
window.__v2RepeaterInstances.add(targetTableName);
|
||||
if (!window.__v2RepeaterInstances) {
|
||||
window.__v2RepeaterInstances = new Set();
|
||||
}
|
||||
window.__v2RepeaterInstances.add(registrationKey);
|
||||
|
||||
return () => {
|
||||
if (targetTableName && window.__v2RepeaterInstances) {
|
||||
window.__v2RepeaterInstances.delete(targetTableName);
|
||||
if (window.__v2RepeaterInstances) {
|
||||
window.__v2RepeaterInstances.delete(registrationKey);
|
||||
}
|
||||
};
|
||||
}, [config.useCustomTable, config.mainTableName, config.dataSource?.tableName]);
|
||||
@@ -968,90 +964,8 @@ export const V2Repeater: React.FC<V2RepeaterProps> = ({
|
||||
[],
|
||||
);
|
||||
|
||||
// 모달에서 전달된 groupedData를 초기 행 데이터로 변환 (컬럼 매핑 포함)
|
||||
const groupedDataProcessedRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!groupedData || !Array.isArray(groupedData) || groupedData.length === 0) return;
|
||||
if (groupedDataProcessedRef.current) return;
|
||||
|
||||
groupedDataProcessedRef.current = true;
|
||||
|
||||
const newRows = groupedData.map((item: any, index: number) => {
|
||||
const row: any = { _id: `grouped_${Date.now()}_${index}` };
|
||||
|
||||
for (const col of config.columns) {
|
||||
let sourceValue = item[(col as any).sourceKey || col.key];
|
||||
|
||||
// 카테고리 코드 → 라벨 변환 (접두사 무관, categoryLabelMap 기반)
|
||||
if (typeof sourceValue === "string" && categoryLabelMap[sourceValue]) {
|
||||
sourceValue = categoryLabelMap[sourceValue];
|
||||
}
|
||||
|
||||
if (col.isSourceDisplay) {
|
||||
row[col.key] = sourceValue ?? "";
|
||||
row[`_display_${col.key}`] = sourceValue ?? "";
|
||||
} else if (col.autoFill && col.autoFill.type !== "none") {
|
||||
const autoValue = generateAutoFillValueSync(col, index, parentFormData);
|
||||
if (autoValue !== undefined) {
|
||||
row[col.key] = autoValue;
|
||||
} else {
|
||||
row[col.key] = "";
|
||||
}
|
||||
} else if (sourceValue !== undefined) {
|
||||
row[col.key] = sourceValue;
|
||||
} else {
|
||||
row[col.key] = "";
|
||||
}
|
||||
}
|
||||
return row;
|
||||
});
|
||||
|
||||
// 카테고리 컬럼의 코드 → 라벨 변환 (접두사 무관)
|
||||
const categoryColSet = new Set(allCategoryColumns);
|
||||
const codesToResolve = new Set<string>();
|
||||
for (const row of newRows) {
|
||||
for (const col of config.columns) {
|
||||
const val = row[col.key] || row[`_display_${col.key}`];
|
||||
if (typeof val === "string" && val && (categoryColSet.has(col.key) || col.autoFill?.type === "fromMainForm")) {
|
||||
if (!categoryLabelMap[val]) {
|
||||
codesToResolve.add(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (codesToResolve.size > 0) {
|
||||
apiClient.post("/table-categories/labels-by-codes", {
|
||||
valueCodes: Array.from(codesToResolve),
|
||||
}).then((resp) => {
|
||||
if (resp.data?.success && resp.data.data) {
|
||||
const labelData = resp.data.data as Record<string, string>;
|
||||
setCategoryLabelMap((prev) => ({ ...prev, ...labelData }));
|
||||
const convertedRows = newRows.map((row) => {
|
||||
const updated = { ...row };
|
||||
for (const col of config.columns) {
|
||||
const val = updated[col.key];
|
||||
if (typeof val === "string" && labelData[val]) {
|
||||
updated[col.key] = labelData[val];
|
||||
}
|
||||
const dispKey = `_display_${col.key}`;
|
||||
const dispVal = updated[dispKey];
|
||||
if (typeof dispVal === "string" && labelData[dispVal]) {
|
||||
updated[dispKey] = labelData[dispVal];
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
setData(convertedRows);
|
||||
onDataChange?.(convertedRows);
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
setData(newRows);
|
||||
onDataChange?.(newRows);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [groupedData, config.columns, generateAutoFillValueSync]);
|
||||
// V2Repeater는 자체 데이터 관리 (아이템 선택 모달, useCustomTable 로딩, DataReceiver)를 사용.
|
||||
// EditModal의 groupedData는 메인 테이블 레코드이므로 V2Repeater에서는 사용하지 않음.
|
||||
|
||||
// parentSequence 컬럼의 부모 필드 값이 변경되면 행 데이터 갱신
|
||||
useEffect(() => {
|
||||
|
||||
@@ -546,10 +546,12 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||
let currentValue;
|
||||
if (componentType === "modal-repeater-table" ||
|
||||
componentType === "repeat-screen-modal" ||
|
||||
componentType === "selected-items-detail-input" ||
|
||||
componentType === "v2-repeater") {
|
||||
componentType === "selected-items-detail-input") {
|
||||
// EditModal/ScreenModal에서 전달된 groupedData가 있으면 우선 사용
|
||||
currentValue = props.groupedData || formData?.[fieldName] || [];
|
||||
} else if (componentType === "v2-repeater") {
|
||||
// V2Repeater는 자체 데이터 관리 (groupedData는 메인 테이블 레코드이므로 사용하지 않음)
|
||||
currentValue = formData?.[fieldName] || [];
|
||||
} else {
|
||||
currentValue = formData?.[fieldName] || "";
|
||||
}
|
||||
|
||||
@@ -1893,29 +1893,34 @@ export class ButtonActionExecutor {
|
||||
mainFormDataKeys: Object.keys(mainFormData),
|
||||
});
|
||||
|
||||
// V2Repeater 저장 완료를 기다리기 위한 Promise
|
||||
const repeaterSavePromise = new Promise<void>((resolve) => {
|
||||
const fallbackTimeout = setTimeout(resolve, 5000);
|
||||
const handler = () => {
|
||||
clearTimeout(fallbackTimeout);
|
||||
window.removeEventListener("repeaterSaveComplete", handler);
|
||||
resolve();
|
||||
};
|
||||
window.addEventListener("repeaterSaveComplete", handler);
|
||||
});
|
||||
// V2Repeater가 등록된 경우에만 저장 완료를 기다림
|
||||
// @ts-ignore
|
||||
const hasActiveRepeaters = window.__v2RepeaterInstances && window.__v2RepeaterInstances.size > 0;
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("repeaterSave", {
|
||||
detail: {
|
||||
parentId: savedId,
|
||||
tableName: context.tableName,
|
||||
mainFormData,
|
||||
masterRecordId: savedId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
if (hasActiveRepeaters) {
|
||||
const repeaterSavePromise = new Promise<void>((resolve) => {
|
||||
const fallbackTimeout = setTimeout(resolve, 5000);
|
||||
const handler = () => {
|
||||
clearTimeout(fallbackTimeout);
|
||||
window.removeEventListener("repeaterSaveComplete", handler);
|
||||
resolve();
|
||||
};
|
||||
window.addEventListener("repeaterSaveComplete", handler);
|
||||
});
|
||||
|
||||
await repeaterSavePromise;
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("repeaterSave", {
|
||||
detail: {
|
||||
parentId: savedId,
|
||||
tableName: context.tableName,
|
||||
mainFormData,
|
||||
masterRecordId: savedId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await repeaterSavePromise;
|
||||
}
|
||||
|
||||
// 테이블과 플로우 새로고침 (모달 닫기 전에 실행)
|
||||
context.onRefresh?.();
|
||||
@@ -1951,29 +1956,33 @@ export class ButtonActionExecutor {
|
||||
formDataKeys: Object.keys(formData),
|
||||
});
|
||||
|
||||
const repeaterSavePromise = new Promise<void>((resolve) => {
|
||||
const fallbackTimeout = setTimeout(resolve, 5000);
|
||||
const handler = () => {
|
||||
clearTimeout(fallbackTimeout);
|
||||
window.removeEventListener("repeaterSaveComplete", handler);
|
||||
resolve();
|
||||
};
|
||||
window.addEventListener("repeaterSaveComplete", handler);
|
||||
});
|
||||
// @ts-ignore
|
||||
const hasActiveRepeaters = window.__v2RepeaterInstances && window.__v2RepeaterInstances.size > 0;
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("repeaterSave", {
|
||||
detail: {
|
||||
parentId: savedId,
|
||||
tableName: context.tableName,
|
||||
mainFormData: formData,
|
||||
masterRecordId: savedId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
if (hasActiveRepeaters) {
|
||||
const repeaterSavePromise = new Promise<void>((resolve) => {
|
||||
const fallbackTimeout = setTimeout(resolve, 5000);
|
||||
const handler = () => {
|
||||
clearTimeout(fallbackTimeout);
|
||||
window.removeEventListener("repeaterSaveComplete", handler);
|
||||
resolve();
|
||||
};
|
||||
window.addEventListener("repeaterSaveComplete", handler);
|
||||
});
|
||||
|
||||
await repeaterSavePromise;
|
||||
console.log("✅ [dispatchRepeaterSave] repeaterSave 완료");
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("repeaterSave", {
|
||||
detail: {
|
||||
parentId: savedId,
|
||||
tableName: context.tableName,
|
||||
mainFormData: formData,
|
||||
masterRecordId: savedId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await repeaterSavePromise;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user