리피터 데이터 저장 로직 개선 및 이벤트 처리 추가

- EditModal, InteractiveScreenViewer, SaveModal 컴포넌트에서 리피터 데이터(배열)를 마스터 저장에서 제외하고, 별도로 저장하는 로직을 추가하였습니다.
- 리피터 데이터 저장 이벤트를 발생시켜 UnifiedRepeater 컴포넌트가 이를 리스닝하도록 개선하였습니다.
- 각 컴포넌트에서 최종 저장 데이터 로그를 업데이트하여, 저장 과정에서의 데이터 흐름을 명확히 하였습니다.

이로 인해 데이터 저장의 효율성과 리피터 관리의 일관성이 향상되었습니다.
This commit is contained in:
kjs
2026-01-22 14:23:38 +09:00
parent d429e237ee
commit 1d068e0a20
15 changed files with 441 additions and 957 deletions

View File

@@ -260,9 +260,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
// 객체인 경우 tableName 속성 추출 시도
if (typeof finalSelectedTable === "object" && finalSelectedTable !== null) {
console.warn("⚠️ selectedTable이 객체입니다:", finalSelectedTable);
finalSelectedTable = (finalSelectedTable as any).tableName || (finalSelectedTable as any).name || tableName;
console.log("✅ 객체에서 추출한 테이블명:", finalSelectedTable);
}
tableConfig.selectedTable = finalSelectedTable;
@@ -353,12 +351,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
}
});
console.log("🔍 [TableListComponent] filters → searchValues:", {
filtersCount: filters.length,
filters: filters.map((f) => ({ col: f.columnName, op: f.operator, val: f.value })),
searchValues: newSearchValues,
});
setSearchValues(newSearchValues);
setCurrentPage(1); // 필터 변경 시 첫 페이지로
}, [filters]);
@@ -761,7 +753,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
});
if (hasChanges) {
console.log("🔗 [TableList] 연결된 필터 값 변경:", newFilterValues);
setLinkedFilterValues(newFilterValues);
// searchValues에 연결된 필터 값 병합
@@ -817,13 +808,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
componentType: "table",
receiveData: async (receivedData: any[], config: DataReceiverConfig) => {
console.log("📥 TableList 데이터 수신:", {
componentId: component.id,
receivedDataCount: receivedData.length,
mode: config.mode,
currentDataCount: data.length,
});
try {
let newData: any[] = [];
@@ -831,13 +815,11 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
case "append":
// 기존 데이터에 추가
newData = [...data, ...receivedData];
console.log("✅ Append 모드: 기존 데이터에 추가", { newDataCount: newData.length });
break;
case "replace":
// 기존 데이터를 완전히 교체
newData = receivedData;
console.log("✅ Replace 모드: 데이터 교체", { newDataCount: newData.length });
break;
case "merge":
@@ -853,7 +835,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
}
});
newData = Array.from(existingMap.values());
console.log("✅ Merge 모드: 데이터 병합", { newDataCount: newData.length });
break;
}
@@ -862,10 +843,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
// 총 아이템 수 업데이트
setTotalItems(newData.length);
console.log("✅ 데이터 수신 완료:", { finalDataCount: newData.length });
} catch (error) {
console.error("❌ 데이터 수신 실패:", error);
throw error;
}
},
@@ -899,7 +877,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
componentId: component.id,
componentType: "table-list",
receiveData: async (incomingData: any[], mode: "append" | "replace" | "merge") => {
console.log("📥 [TableListComponent] 분할 패널에서 데이터 수신:", {
// 분할 패널에서 데이터 수신 처리
const receiveInfo = {
count: incomingData.length,
mode,
position: currentSplitPosition,
@@ -937,24 +916,12 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
// 컬럼의 고유 값 조회 함수
const getColumnUniqueValues = async (columnName: string) => {
console.log("🔍 [getColumnUniqueValues] 호출됨:", {
columnName,
dataLength: data.length,
columnMeta: columnMeta[columnName],
sampleData: data[0],
});
const meta = columnMeta[columnName];
const inputType = meta?.inputType || "text";
// 카테고리 타입인 경우 전체 정의된 값 조회 (백엔드 API)
if (inputType === "category") {
try {
console.log("🔍 [getColumnUniqueValues] 카테고리 전체 값 조회:", {
tableName: tableConfig.selectedTable,
columnName,
});
// API 클라이언트 사용 (쿠키 인증 자동 처리)
const { apiClient } = await import("@/lib/api/client");
const response = await apiClient.get(`/table-categories/${tableConfig.selectedTable}/${columnName}/values`);
@@ -965,24 +932,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
label: item.valueLabel, // 카멜케이스
}));
console.log("✅ [getColumnUniqueValues] 카테고리 전체 값:", {
columnName,
count: categoryOptions.length,
options: categoryOptions,
});
return categoryOptions;
} else {
console.warn("⚠️ [getColumnUniqueValues] 응답 형식 오류:", response.data);
}
} catch (error: any) {
console.error("❌ [getColumnUniqueValues] 카테고리 조회 실패:", {
error: error.message,
response: error.response?.data,
status: error.response?.status,
columnName,
tableName: tableConfig.selectedTable,
});
} catch {
// 에러 시 현재 데이터 기반으로 fallback
}
}
@@ -991,15 +943,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
const isLabelType = ["category", "entity", "code"].includes(inputType);
const labelField = isLabelType ? `${columnName}_name` : columnName;
console.log("🔍 [getColumnUniqueValues] 데이터 기반 조회:", {
columnName,
inputType,
isLabelType,
labelField,
hasLabelField: data[0] && labelField in data[0],
sampleLabelValue: data[0] ? data[0][labelField] : undefined,
});
// 현재 로드된 데이터에서 고유 값 추출
const uniqueValuesMap = new Map<string, string>(); // value -> label
@@ -1020,15 +963,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
}))
.sort((a, b) => a.label.localeCompare(b.label));
console.log("✅ [getColumnUniqueValues] 데이터 기반 결과:", {
columnName,
inputType,
isLabelType,
labelField,
uniqueCount: result.length,
values: result,
});
return result;
};
@@ -1105,10 +1039,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
setSortColumn(column);
setSortDirection(direction);
hasInitializedSort.current = true;
console.log("📂 localStorage에서 정렬 상태 복원:", { column, direction });
}
} catch (error) {
console.error("❌ 정렬 상태 복원 실패:", error);
} catch {
// 정렬 상태 복원 실패 - 무시
}
}
}, [tableConfig.selectedTable, userId]);
@@ -1124,12 +1057,10 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder);
console.log("📂 localStorage에서 컬럼 순서 불러오기:", { storageKey, columnOrder: parsedOrder });
setColumnOrder(parsedOrder);
// 부모 컴포넌트에 초기 컬럼 순서 전달
if (onSelectedRowsChange && parsedOrder.length > 0) {
console.log("✅ 초기 컬럼 순서 전달:", parsedOrder);
// 초기 데이터도 함께 전달 (컬럼 순서대로 재정렬)
const initialData = data.map((row: any) => {
@@ -1177,8 +1108,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
onSelectedRowsChange([], [], sortColumn, sortDirection, parsedOrder, initialData);
}
} catch (error) {
console.error("❌ 컬럼 순서 파싱 실패:", error);
} catch {
// 컬럼 순서 파싱 실패 - 무시
}
}
}, [tableConfig.selectedTable, userId, data.length]); // data.length 추가 (데이터 로드 후 실행)
@@ -1336,11 +1267,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
const parts = columnName.split(".");
targetTable = parts[0]; // 조인된 테이블명 (예: item_info)
targetColumn = parts[1]; // 실제 컬럼명 (예: material)
console.log("🔗 [TableList] 엔티티 조인 컬럼 감지:", {
originalColumn: columnName,
targetTable,
targetColumn,
});
}
const response = await apiClient.get(`/table-categories/${targetTable}/${targetColumn}/values`);
@@ -1438,8 +1364,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
// 조인 테이블의 컬럼 inputType 정보 가져오기 (이미 import된 tableTypeApi 사용)
const inputTypes = await tableTypeApi.getColumnInputTypes(joinedTable);
console.log(`📡 [TableList] 조인 테이블 inputType 로드 [${joinedTable}]:`, inputTypes);
for (const col of columns) {
const inputTypeInfo = inputTypes.find((it: any) => it.columnName === col.actualColumn);
@@ -1448,17 +1372,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
inputType: inputTypeInfo?.inputType,
};
console.log(
` 🔗 [${col.columnName}] (실제: ${col.actualColumn}) inputType: ${inputTypeInfo?.inputType || "unknown"}`,
);
// inputType이 category인 경우 카테고리 매핑 로드
if (inputTypeInfo?.inputType === "category" && !mappings[col.columnName]) {
try {
console.log(`📡 [TableList] 조인 테이블 카테고리 로드 시도 [${col.columnName}]:`, {
url: `/table-categories/${joinedTable}/${col.actualColumn}/values`,
});
const response = await apiClient.get(`/table-categories/${joinedTable}/${col.actualColumn}/values`);
if (response.data.success && response.data.data && Array.isArray(response.data.data)) {
@@ -1476,20 +1392,19 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
mappings[col.columnName] = mapping;
}
}
} catch (error) {
console.log(` [TableList] 조인 테이블 카테고리 없음 (${col.columnName})`);
} catch {
// 조인 테이블 카테고리 없음 - 무시
}
}
}
} catch (error) {
console.error(`❌ [TableList] 조인 테이블 inputType 로드 실패 [${joinedTable}]:`, error);
console.error(`조인 테이블 inputType 로드 실패 [${joinedTable}]:`, error);
}
}
// 조인 컬럼 메타데이터 상태 업데이트
if (Object.keys(newJoinedColumnMeta).length > 0) {
setJoinedColumnMeta(newJoinedColumnMeta);
console.log("✅ [TableList] 조인 컬럼 메타데이터 설정:", newJoinedColumnMeta);
}
// 🆕 카테고리 연쇄관계 매핑 로드 (category_value_cascading_mapping)
@@ -1515,26 +1430,17 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
};
}
}
console.log("✅ [TableList] 카테고리 연쇄관계 매핑 로드 완료:", {
tableName: tableConfig.selectedTable,
cascadingColumns: Object.keys(cascadingMappings),
});
}
} catch (cascadingError: any) {
// 연쇄관계 매핑이 없는 경우 무시 (404 등)
if (cascadingError?.response?.status !== 404) {
console.warn("⚠️ [TableList] 카테고리 연쇄관계 매핑 로드 실패:", cascadingError?.message);
}
}
if (Object.keys(mappings).length > 0) {
setCategoryMappings(mappings);
setCategoryMappingsKey((prev) => prev + 1);
} else {
console.warn("⚠️ [TableList] 매핑이 비어있어 상태 업데이트 스킵");
}
} catch (error) {
console.error("❌ [TableList] 카테고리 매핑 로드 실패:", error);
console.error("카테고리 매핑 로드 실패:", error);
}
};