feat: 다중 선택 및 일괄 삭제 기능 추가
- 카테고리 값 관리 컴포넌트에 체크박스를 통한 다중 선택 기능을 추가하였습니다. - 선택된 카테고리를 일괄 삭제할 수 있는 다이얼로그를 구현하였습니다. - 테이블 관리 서비스에서 다중 선택 처리 로직을 추가하여, 파이프(|)로 구분된 값을 처리하도록 개선하였습니다. - 관련된 로그 메시지를 추가하여 다중 선택 및 삭제 과정에서의 정보를 기록하도록 하였습니다.
This commit is contained in:
@@ -261,9 +261,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;
|
||||
@@ -741,7 +739,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
});
|
||||
|
||||
if (hasChanges) {
|
||||
console.log("🔗 [TableList] 연결된 필터 값 변경:", newFilterValues);
|
||||
setLinkedFilterValues(newFilterValues);
|
||||
|
||||
// searchValues에 연결된 필터 값 병합
|
||||
@@ -797,13 +794,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[] = [];
|
||||
|
||||
@@ -811,13 +801,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":
|
||||
@@ -833,7 +821,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
}
|
||||
});
|
||||
newData = Array.from(existingMap.values());
|
||||
console.log("✅ Merge 모드: 데이터 병합", { newDataCount: newData.length });
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -842,10 +829,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
|
||||
// 총 아이템 수 업데이트
|
||||
setTotalItems(newData.length);
|
||||
|
||||
console.log("✅ 데이터 수신 완료:", { finalDataCount: newData.length });
|
||||
} catch (error) {
|
||||
console.error("❌ 데이터 수신 실패:", error);
|
||||
console.error("데이터 수신 실패:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
@@ -879,12 +864,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
componentId: component.id,
|
||||
componentType: "table-list",
|
||||
receiveData: async (incomingData: any[], mode: "append" | "replace" | "merge") => {
|
||||
console.log("📥 [TableListComponent] 분할 패널에서 데이터 수신:", {
|
||||
count: incomingData.length,
|
||||
mode,
|
||||
position: currentSplitPosition,
|
||||
});
|
||||
|
||||
await dataReceiver.receiveData(incomingData, {
|
||||
targetComponentId: component.id,
|
||||
targetComponentType: "table-list",
|
||||
@@ -917,24 +896,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`);
|
||||
@@ -945,24 +912,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,
|
||||
});
|
||||
// 에러 시 현재 데이터 기반으로 fallback
|
||||
}
|
||||
}
|
||||
@@ -971,15 +923,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
|
||||
|
||||
@@ -1000,15 +943,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;
|
||||
};
|
||||
|
||||
@@ -1085,10 +1019,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
setSortColumn(column);
|
||||
setSortDirection(direction);
|
||||
hasInitializedSort.current = true;
|
||||
console.log("📂 localStorage에서 정렬 상태 복원:", { column, direction });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ 정렬 상태 복원 실패:", error);
|
||||
// 정렬 상태 복원 실패
|
||||
}
|
||||
}
|
||||
}, [tableConfig.selectedTable, userId]);
|
||||
@@ -1104,12 +1037,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) => {
|
||||
@@ -1598,48 +1529,36 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
// 자동 컬럼 매칭도 equals 연산자 사용
|
||||
linkedFilterValues[colName] = { value: colValue, operator: "equals" };
|
||||
hasLinkedFiltersConfigured = true;
|
||||
console.log(`🔗 [TableList] 자동 컬럼 매칭: ${colName} = ${colValue}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(linkedFilterValues).length > 0) {
|
||||
console.log("🔗 [TableList] 자동 컬럼 매칭 필터 적용:", linkedFilterValues);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(linkedFilterValues).length > 0) {
|
||||
console.log("🔗 [TableList] 연결 필터 적용:", linkedFilterValues);
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 연결 필터가 설정되어 있지만 좌측에서 데이터가 선택되지 않은 경우
|
||||
// 연결 필터가 설정되어 있지만 좌측에서 데이터가 선택되지 않은 경우
|
||||
// → 빈 데이터 표시 (모든 데이터를 보여주지 않음)
|
||||
if (hasLinkedFiltersConfigured && !hasSelectedLeftData) {
|
||||
console.log("⚠️ [TableList] 연결 필터 설정됨 but 좌측 데이터 미선택 → 빈 데이터 표시");
|
||||
setData([]);
|
||||
setTotalItems(0);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 🆕 RelatedDataButtons 대상이지만 아직 버튼이 선택되지 않은 경우
|
||||
// RelatedDataButtons 대상이지만 아직 버튼이 선택되지 않은 경우
|
||||
// → 빈 데이터 표시 (모든 데이터를 보여주지 않음)
|
||||
if (isRelatedButtonTarget && !relatedButtonFilter) {
|
||||
console.log("⚠️ [TableList] RelatedDataButtons 대상이지만 버튼 미선택 → 빈 데이터 표시");
|
||||
setData([]);
|
||||
setTotalItems(0);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 🆕 RelatedDataButtons 필터 값 준비
|
||||
// RelatedDataButtons 필터 값 준비
|
||||
const relatedButtonFilterValues: Record<string, any> = {};
|
||||
if (relatedButtonFilter) {
|
||||
relatedButtonFilterValues[relatedButtonFilter.filterColumn] = {
|
||||
value: relatedButtonFilter.filterValue,
|
||||
operator: "equals",
|
||||
};
|
||||
console.log("🔗 [TableList] RelatedDataButtons 필터 적용:", relatedButtonFilterValues);
|
||||
}
|
||||
|
||||
// 검색 필터, 연결 필터, RelatedDataButtons 필터 병합
|
||||
@@ -1662,8 +1581,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
const connectionId = connectionIdMatch ? parseInt(connectionIdMatch[1]) : null;
|
||||
|
||||
if (connectionId) {
|
||||
console.log("🌐 [TableList] REST API 데이터 소스 호출", { connectionId });
|
||||
|
||||
// REST API 연결 정보 가져오기 및 데이터 조회
|
||||
const { ExternalRestApiConnectionAPI } = await import("@/lib/api/externalRestApiConnection");
|
||||
const restApiData = await ExternalRestApiConnectionAPI.fetchData(
|
||||
@@ -1677,11 +1594,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
total: restApiData.total || restApiData.rows?.length || 0,
|
||||
totalPages: Math.ceil((restApiData.total || restApiData.rows?.length || 0) / pageSize),
|
||||
};
|
||||
|
||||
console.log("✅ [TableList] REST API 응답:", {
|
||||
dataLength: response.data.length,
|
||||
total: response.total,
|
||||
});
|
||||
} else {
|
||||
throw new Error("REST API 연결 ID를 찾을 수 없습니다.");
|
||||
}
|
||||
@@ -1722,31 +1634,15 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
// 1순위: props로 전달받은 formData에서 값 가져오기 (모달에서 사용)
|
||||
if (propFormData && propFormData[fieldName]) {
|
||||
filterValue = propFormData[fieldName];
|
||||
console.log("🔗 [TableList] formData에서 excludeFilter 값 가져오기:", {
|
||||
field: fieldName,
|
||||
value: filterValue,
|
||||
});
|
||||
}
|
||||
// 2순위: URL 파라미터에서 값 가져오기
|
||||
else if (typeof window !== "undefined") {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
filterValue = urlParams.get(fieldName);
|
||||
if (filterValue) {
|
||||
console.log("🔗 [TableList] URL에서 excludeFilter 값 가져오기:", {
|
||||
field: fieldName,
|
||||
value: filterValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
// 3순위: 분할 패널 부모 데이터에서 값 가져오기
|
||||
if (!filterValue && splitPanelContext?.selectedLeftData) {
|
||||
filterValue = splitPanelContext.selectedLeftData[fieldName];
|
||||
if (filterValue) {
|
||||
console.log("🔗 [TableList] 분할패널에서 excludeFilter 값 가져오기:", {
|
||||
field: fieldName,
|
||||
value: filterValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1759,7 +1655,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
filterColumn: excludeConfig.filterColumn,
|
||||
filterValue: filterValue,
|
||||
};
|
||||
console.log("🚫 [TableList] 제외 필터 적용:", excludeFilterParam);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1874,8 +1769,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
};
|
||||
|
||||
const handleSort = (column: string) => {
|
||||
console.log("🔄 정렬 클릭:", { column, currentSortColumn: sortColumn, currentSortDirection: sortDirection });
|
||||
|
||||
let newSortColumn = column;
|
||||
let newSortDirection: "asc" | "desc" = "asc";
|
||||
|
||||
@@ -1889,7 +1782,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
newSortDirection = "asc";
|
||||
}
|
||||
|
||||
// 🎯 정렬 상태를 localStorage에 저장 (사용자별)
|
||||
// 정렬 상태를 localStorage에 저장 (사용자별)
|
||||
if (tableConfig.selectedTable && userId) {
|
||||
const storageKey = `table_sort_state_${tableConfig.selectedTable}_${userId}`;
|
||||
try {
|
||||
@@ -1900,15 +1793,11 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
direction: newSortDirection,
|
||||
}),
|
||||
);
|
||||
console.log("💾 정렬 상태 저장:", { column: newSortColumn, direction: newSortDirection });
|
||||
} catch (error) {
|
||||
console.error("❌ 정렬 상태 저장 실패:", error);
|
||||
// 정렬 상태 저장 실패
|
||||
}
|
||||
}
|
||||
|
||||
console.log("📊 새로운 정렬 정보:", { newSortColumn, newSortDirection });
|
||||
console.log("🔍 onSelectedRowsChange 존재 여부:", !!onSelectedRowsChange);
|
||||
|
||||
// 정렬 변경 시 선택 정보와 함께 정렬 정보도 전달
|
||||
if (onSelectedRowsChange) {
|
||||
const selectedRowsData = data.filter((row, index) => selectedRows.has(getRowKey(row, index)));
|
||||
@@ -1960,16 +1849,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
return reordered;
|
||||
});
|
||||
|
||||
console.log("✅ 정렬 정보 전달:", {
|
||||
selectedRowsCount: selectedRows.size,
|
||||
selectedRowsDataCount: selectedRowsData.length,
|
||||
sortBy: newSortColumn,
|
||||
sortOrder: newSortDirection,
|
||||
columnOrder: columnOrder.length > 0 ? columnOrder : undefined,
|
||||
tableDisplayDataCount: reorderedData.length,
|
||||
firstRowAfterSort: reorderedData[0]?.[newSortColumn],
|
||||
lastRowAfterSort: reorderedData[reorderedData.length - 1]?.[newSortColumn],
|
||||
});
|
||||
onSelectedRowsChange(
|
||||
Array.from(selectedRows),
|
||||
selectedRowsData,
|
||||
@@ -2023,8 +1902,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
};
|
||||
|
||||
const handleClearAdvancedFilters = useCallback(() => {
|
||||
console.log("🔄 필터 초기화 시작", { 이전searchValues: searchValues });
|
||||
|
||||
// 상태를 초기화하고 useEffect로 데이터 새로고침
|
||||
setSearchValues({});
|
||||
setCurrentPage(1);
|
||||
@@ -2173,30 +2050,15 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
// currentSplitPosition을 사용하여 정확한 위치 확인 (splitPanelPosition이 없을 수 있음)
|
||||
const effectiveSplitPosition = splitPanelPosition || currentSplitPosition;
|
||||
|
||||
console.log("🔗 [TableList] 행 클릭 - 분할 패널 위치 확인:", {
|
||||
splitPanelPosition,
|
||||
currentSplitPosition,
|
||||
effectiveSplitPosition,
|
||||
hasSplitPanelContext: !!splitPanelContext,
|
||||
disableAutoDataTransfer: splitPanelContext?.disableAutoDataTransfer,
|
||||
});
|
||||
|
||||
if (splitPanelContext && effectiveSplitPosition === "left" && !splitPanelContext.disableAutoDataTransfer) {
|
||||
if (!isCurrentlySelected) {
|
||||
// 선택된 경우: 데이터 저장
|
||||
splitPanelContext.setSelectedLeftData(row);
|
||||
console.log("🔗 [TableList] 분할 패널 좌측 데이터 저장:", {
|
||||
row,
|
||||
parentDataMapping: splitPanelContext.parentDataMapping,
|
||||
});
|
||||
} else {
|
||||
// 선택 해제된 경우: 데이터 초기화
|
||||
splitPanelContext.setSelectedLeftData(null);
|
||||
console.log("🔗 [TableList] 분할 패널 좌측 데이터 초기화");
|
||||
}
|
||||
}
|
||||
|
||||
console.log("행 클릭:", { row, index, isSelected: !isCurrentlySelected });
|
||||
};
|
||||
|
||||
// 🆕 셀 클릭 핸들러 (포커스 설정 + 행 선택)
|
||||
@@ -2457,12 +2319,11 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
},
|
||||
}));
|
||||
|
||||
console.log("📝 배치 편집 추가:", { columnName, newValue, pendingCount: pendingChanges.size + 1 });
|
||||
cancelEditing();
|
||||
return;
|
||||
}
|
||||
|
||||
// 🆕 즉시 모드: 바로 저장
|
||||
// 즉시 모드: 바로 저장
|
||||
try {
|
||||
const { apiClient } = await import("@/lib/api/client");
|
||||
|
||||
@@ -2476,10 +2337,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
|
||||
// 데이터 새로고침 트리거
|
||||
setRefreshTrigger((prev) => prev + 1);
|
||||
|
||||
console.log("✅ 셀 편집 저장 완료:", { columnName, newValue });
|
||||
} catch (error) {
|
||||
console.error("❌ 셀 편집 저장 실패:", error);
|
||||
// 셀 편집 저장 실패
|
||||
}
|
||||
|
||||
cancelEditing();
|
||||
@@ -2524,21 +2383,18 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
setRefreshTrigger((prev) => prev + 1);
|
||||
|
||||
toast.success(`${pendingChanges.size}개의 변경사항이 저장되었습니다.`);
|
||||
console.log("✅ 배치 저장 완료:", pendingChanges.size, "개");
|
||||
} catch (error) {
|
||||
console.error("❌ 배치 저장 실패:", error);
|
||||
toast.error("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
}, [pendingChanges, tableConfig.selectedTable, tableConfig.primaryKey]);
|
||||
|
||||
// 🆕 배치 취소: 모든 변경사항 롤백
|
||||
// 배치 취소: 모든 변경사항 롤백
|
||||
const cancelBatchChanges = useCallback(() => {
|
||||
if (pendingChanges.size === 0) return;
|
||||
|
||||
setPendingChanges(new Map());
|
||||
setLocalEditedData({});
|
||||
toast.info("변경사항이 취소되었습니다.");
|
||||
console.log("🔄 배치 편집 취소");
|
||||
}, [pendingChanges.size]);
|
||||
|
||||
// 🆕 특정 셀이 수정되었는지 확인
|
||||
@@ -2715,9 +2571,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
XLSX.writeFile(wb, fileName);
|
||||
|
||||
toast.success(`${exportData.length}개 행이 Excel로 내보내기 되었습니다.`);
|
||||
console.log("✅ Excel 내보내기 완료:", fileName);
|
||||
} catch (error) {
|
||||
console.error("❌ Excel 내보내기 실패:", error);
|
||||
toast.error("Excel 내보내기 중 오류가 발생했습니다.");
|
||||
}
|
||||
},
|
||||
@@ -2783,10 +2637,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
...prev,
|
||||
[rowKey]: details,
|
||||
}));
|
||||
|
||||
console.log("✅ 상세 데이터 로딩 완료:", { rowKey, count: details.length });
|
||||
} catch (error) {
|
||||
console.error("❌ 상세 데이터 로딩 실패:", error);
|
||||
setDetailData((prev) => ({
|
||||
...prev,
|
||||
[rowKey]: [],
|
||||
@@ -2883,10 +2734,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
...prev,
|
||||
[cacheKey]: options,
|
||||
}));
|
||||
|
||||
console.log("✅ Cascading options 로딩 완료:", { columnName, parentValue, count: options.length });
|
||||
} catch (error) {
|
||||
console.error("❌ Cascading options 로딩 실패:", error);
|
||||
setCascadingOptions((prev) => ({
|
||||
...prev,
|
||||
[cacheKey]: [],
|
||||
@@ -3040,13 +2888,11 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
|
||||
wsRef.current.onopen = () => {
|
||||
setWsConnectionStatus("connected");
|
||||
console.log("✅ WebSocket 연결됨:", tableConfig.selectedTable);
|
||||
};
|
||||
|
||||
wsRef.current.onmessage = (event) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data);
|
||||
console.log("📨 WebSocket 메시지 수신:", message);
|
||||
|
||||
switch (message.type) {
|
||||
case "insert":
|
||||
@@ -3069,32 +2915,29 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
setRefreshTrigger((prev) => prev + 1);
|
||||
break;
|
||||
default:
|
||||
console.log("알 수 없는 메시지 타입:", message.type);
|
||||
// 알 수 없는 메시지 타입
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("WebSocket 메시지 파싱 오류:", error);
|
||||
// WebSocket 메시지 파싱 오류
|
||||
}
|
||||
};
|
||||
|
||||
wsRef.current.onclose = () => {
|
||||
setWsConnectionStatus("disconnected");
|
||||
console.log("🔌 WebSocket 연결 종료");
|
||||
|
||||
// 자동 재연결 (5초 후)
|
||||
if (isRealTimeEnabled) {
|
||||
reconnectTimeoutRef.current = setTimeout(() => {
|
||||
console.log("🔄 WebSocket 재연결 시도...");
|
||||
connectWebSocket();
|
||||
}, 5000);
|
||||
}
|
||||
};
|
||||
|
||||
wsRef.current.onerror = (error) => {
|
||||
console.error("❌ WebSocket 오류:", error);
|
||||
wsRef.current.onerror = () => {
|
||||
setWsConnectionStatus("disconnected");
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("WebSocket 연결 실패:", error);
|
||||
setWsConnectionStatus("disconnected");
|
||||
}
|
||||
}, [isRealTimeEnabled, tableConfig.selectedTable]);
|
||||
@@ -3179,9 +3022,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
await navigator.clipboard.writeText(tsvContent);
|
||||
|
||||
toast.success(`${copyData.length}행 복사됨`);
|
||||
console.log("✅ 클립보드 복사:", copyData.length, "행");
|
||||
} catch (error) {
|
||||
console.error("❌ 클립보드 복사 실패:", error);
|
||||
toast.error("복사 실패");
|
||||
}
|
||||
}, [selectedRows, filteredData, focusedCell, visibleColumns, columnLabels, getRowKey]);
|
||||
@@ -3532,7 +3373,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
|
||||
setColumnOrder(newOrder);
|
||||
toast.info("컬럼 순서가 변경되었습니다.");
|
||||
console.log("✅ 컬럼 순서 변경:", { from: draggedColumnIndex, to: targetIndex });
|
||||
|
||||
handleColumnDragEnd();
|
||||
},
|
||||
@@ -3623,10 +3463,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
// 로컬에서만 순서 변경 (저장 안함)
|
||||
toast.info("순서가 변경되었습니다. (로컬만)");
|
||||
}
|
||||
|
||||
console.log("✅ 행 순서 변경:", { from: draggedRowIndex, to: targetIndex });
|
||||
} catch (error) {
|
||||
console.error("❌ 행 순서 변경 실패:", error);
|
||||
toast.error("순서 변경 중 오류가 발생했습니다.");
|
||||
}
|
||||
|
||||
@@ -4712,8 +4549,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
return filteredData;
|
||||
}
|
||||
|
||||
console.log("🔍 [테이블리스트] 그룹합산 적용:", groupSumConfig);
|
||||
|
||||
const groupByColumn = groupSumConfig.groupByColumn;
|
||||
const groupMap = new Map<string, any>();
|
||||
|
||||
@@ -4766,11 +4601,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
});
|
||||
|
||||
const result = Array.from(groupMap.values());
|
||||
console.log("🔗 [테이블리스트] 그룹별 합산 결과:", {
|
||||
원본개수: filteredData.length,
|
||||
그룹개수: result.length,
|
||||
그룹기준: groupByColumn,
|
||||
});
|
||||
|
||||
return result;
|
||||
}, [filteredData, groupSumConfig]);
|
||||
@@ -4878,7 +4708,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
useEffect(() => {
|
||||
const handleRefreshTable = () => {
|
||||
if (tableConfig.selectedTable && !isDesignMode) {
|
||||
console.log("🔄 [TableList] refreshTable 이벤트 수신 - 데이터 새로고침");
|
||||
setRefreshTrigger((prev) => prev + 1);
|
||||
}
|
||||
};
|
||||
@@ -4904,23 +4733,21 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
};
|
||||
}, [tableConfig.selectedTable, isDesignMode, component.id]);
|
||||
|
||||
// 🆕 테이블명 변경 시 전역 레지스트리에서 확인
|
||||
// 테이블명 변경 시 전역 레지스트리에서 확인
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined" && window.__relatedButtonsTargetTables && tableConfig.selectedTable) {
|
||||
const isTarget = window.__relatedButtonsTargetTables.has(tableConfig.selectedTable);
|
||||
if (isTarget) {
|
||||
console.log("📝 [TableList] 전역 레지스트리에서 RelatedDataButtons 대상 확인:", tableConfig.selectedTable);
|
||||
setIsRelatedButtonTarget(true);
|
||||
}
|
||||
}
|
||||
}, [tableConfig.selectedTable]);
|
||||
|
||||
// 🆕 RelatedDataButtons 등록/해제 이벤트 리스너
|
||||
// RelatedDataButtons 등록/해제 이벤트 리스너
|
||||
useEffect(() => {
|
||||
const handleRelatedButtonRegister = (event: CustomEvent) => {
|
||||
const { targetTable } = event.detail || {};
|
||||
if (targetTable === tableConfig.selectedTable) {
|
||||
console.log("📝 [TableList] RelatedDataButtons 대상으로 등록됨:", tableConfig.selectedTable);
|
||||
setIsRelatedButtonTarget(true);
|
||||
}
|
||||
};
|
||||
@@ -4928,7 +4755,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
const handleRelatedButtonUnregister = (event: CustomEvent) => {
|
||||
const { targetTable } = event.detail || {};
|
||||
if (targetTable === tableConfig.selectedTable) {
|
||||
console.log("📝 [TableList] RelatedDataButtons 대상에서 해제됨:", tableConfig.selectedTable);
|
||||
setIsRelatedButtonTarget(false);
|
||||
setRelatedButtonFilter(null);
|
||||
}
|
||||
@@ -4939,7 +4765,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
V2_EVENTS.RELATED_BUTTON_REGISTER,
|
||||
(payload) => {
|
||||
if (payload.targetTables.includes(tableConfig.selectedTable || "")) {
|
||||
console.log("📝 [TableList] RelatedDataButtons 대상으로 등록됨:", tableConfig.selectedTable);
|
||||
setIsRelatedButtonTarget(true);
|
||||
}
|
||||
},
|
||||
@@ -4950,7 +4775,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
V2_EVENTS.RELATED_BUTTON_UNREGISTER,
|
||||
(payload) => {
|
||||
if (payload.buttonId) {
|
||||
console.log("📝 [TableList] RelatedDataButtons 대상에서 해제됨:", tableConfig.selectedTable);
|
||||
setIsRelatedButtonTarget(false);
|
||||
setRelatedButtonFilter(null);
|
||||
}
|
||||
@@ -4970,7 +4794,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
};
|
||||
}, [tableConfig.selectedTable, component.id]);
|
||||
|
||||
// 🆕 RelatedDataButtons 선택 이벤트 리스너 (버튼 선택 시 테이블 필터링)
|
||||
// RelatedDataButtons 선택 이벤트 리스너 (버튼 선택 시 테이블 필터링)
|
||||
useEffect(() => {
|
||||
const handleRelatedButtonSelect = (event: CustomEvent) => {
|
||||
const { targetTable, filterColumn, filterValue } = event.detail || {};
|
||||
@@ -4979,15 +4803,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
if (targetTable === tableConfig.selectedTable) {
|
||||
// filterValue가 null이면 선택 해제 (빈 상태)
|
||||
if (filterValue === null || filterValue === undefined) {
|
||||
console.log("📌 [TableList] RelatedDataButtons 선택 해제 (빈 상태):", tableConfig.selectedTable);
|
||||
setRelatedButtonFilter(null);
|
||||
setIsRelatedButtonTarget(true); // 대상으로 등록은 유지
|
||||
} else {
|
||||
console.log("📌 [TableList] RelatedDataButtons 필터 적용:", {
|
||||
tableName: tableConfig.selectedTable,
|
||||
filterColumn,
|
||||
filterValue,
|
||||
});
|
||||
setRelatedButtonFilter({ filterColumn, filterValue });
|
||||
setIsRelatedButtonTarget(true);
|
||||
}
|
||||
@@ -5000,14 +4818,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
(payload) => {
|
||||
if (payload.tableName === tableConfig.selectedTable) {
|
||||
if (!payload.selectedData || payload.selectedData.length === 0) {
|
||||
console.log("📌 [TableList] RelatedDataButtons 선택 해제 (빈 상태):", tableConfig.selectedTable);
|
||||
setRelatedButtonFilter(null);
|
||||
setIsRelatedButtonTarget(true);
|
||||
} else {
|
||||
console.log("📌 [TableList] RelatedDataButtons 필터 적용:", {
|
||||
tableName: tableConfig.selectedTable,
|
||||
selectedData: payload.selectedData,
|
||||
});
|
||||
// 첫 번째 선택된 데이터의 ID를 필터로 사용
|
||||
const firstItem = payload.selectedData[0];
|
||||
if (firstItem?.id) {
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
import React, { useState, useEffect, useRef, useMemo } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Settings, Filter, Layers, X, Check, ChevronsUpDown } from "lucide-react";
|
||||
import { Settings, X, ChevronsUpDown } from "lucide-react";
|
||||
import { useTableOptions } from "@/contexts/TableOptionsContext";
|
||||
import { useTableSearchWidgetHeight } from "@/contexts/TableSearchWidgetHeightContext";
|
||||
import { useActiveTab } from "@/contexts/ActiveTabContext";
|
||||
import { ColumnVisibilityPanel } from "@/components/screen/table-options/ColumnVisibilityPanel";
|
||||
import { FilterPanel } from "@/components/screen/table-options/FilterPanel";
|
||||
import { GroupingPanel } from "@/components/screen/table-options/GroupingPanel";
|
||||
import { TableSettingsModal } from "@/components/screen/table-options/TableSettingsModal";
|
||||
import { TableFilter } from "@/types/table-options";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { ModernDatePicker } from "@/components/screen/filters/ModernDatePicker";
|
||||
@@ -50,24 +48,8 @@ interface TableSearchWidgetProps {
|
||||
}
|
||||
|
||||
export function TableSearchWidget({ component, screenId, onHeightChange }: TableSearchWidgetProps) {
|
||||
console.log("🎯🎯🎯 [TableSearchWidget] 함수 시작!", { componentId: component?.id, screenId });
|
||||
|
||||
// 🔧 직접 useTableOptions 호출 (에러 발생 시 catch하지 않고 그대로 throw)
|
||||
const tableOptionsContext = useTableOptions();
|
||||
console.log("✅ [TableSearchWidget] useTableOptions 성공", { hasContext: !!tableOptionsContext });
|
||||
|
||||
const { registeredTables, selectedTableId, setSelectedTableId, getTable, getActiveTabTables } = tableOptionsContext;
|
||||
|
||||
// 등록된 테이블 확인 로그
|
||||
console.log("🔍 [TableSearchWidget] 등록된 테이블:", {
|
||||
count: registeredTables.size,
|
||||
tables: Array.from(registeredTables.entries()).map(([id, t]) => ({
|
||||
id,
|
||||
tableName: t.tableName,
|
||||
hasOnFilterChange: typeof t.onFilterChange === "function",
|
||||
})),
|
||||
selectedTableId,
|
||||
});
|
||||
const { isPreviewMode } = useScreenPreview(); // 미리보기 모드 확인
|
||||
const { getAllActiveTabIds, activeTabs } = useActiveTab(); // 활성 탭 정보
|
||||
|
||||
@@ -86,9 +68,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
// 탭별 필터 값 저장 (탭 ID -> 필터 값)
|
||||
const [tabFilterValues, setTabFilterValues] = useState<Record<string, Record<string, any>>>({});
|
||||
|
||||
const [columnVisibilityOpen, setColumnVisibilityOpen] = useState(false);
|
||||
const [filterOpen, setFilterOpen] = useState(false);
|
||||
const [groupingOpen, setGroupingOpen] = useState(false);
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
|
||||
// 활성화된 필터 목록
|
||||
const [activeFilters, setActiveFilters] = useState<TableFilter[]>([]);
|
||||
@@ -153,24 +133,16 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
|
||||
// currentTable은 tableList(필터링된 목록)에서 가져와야 함
|
||||
const currentTable = useMemo(() => {
|
||||
console.log("🔍 [TableSearchWidget] currentTable 계산:", {
|
||||
selectedTableId,
|
||||
tableListLength: tableList.length,
|
||||
tableList: tableList.map((t) => ({ id: t.tableId, name: t.tableName, parentTabId: t.parentTabId })),
|
||||
});
|
||||
|
||||
if (!selectedTableId) return undefined;
|
||||
|
||||
// 먼저 tableList(필터링된 목록)에서 찾기
|
||||
const tableFromList = tableList.find((t) => t.tableId === selectedTableId);
|
||||
if (tableFromList) {
|
||||
console.log("✅ [TableSearchWidget] 테이블 찾음 (tableList):", tableFromList.tableName);
|
||||
return tableFromList;
|
||||
}
|
||||
|
||||
// tableList에 없으면 전체에서 찾기 (폴백)
|
||||
const tableFromAll = getTable(selectedTableId);
|
||||
console.log("🔄 [TableSearchWidget] 테이블 찾음 (전체):", tableFromAll?.tableName);
|
||||
return tableFromAll;
|
||||
}, [selectedTableId, tableList, getTable]);
|
||||
|
||||
@@ -186,28 +158,16 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
return;
|
||||
}
|
||||
|
||||
// 🆕 탭 전환 감지: 활성 탭이 변경되었는지 확인
|
||||
// 탭 전환 감지: 활성 탭이 변경되었는지 확인
|
||||
const tabChanged = prevActiveTabIdsRef.current !== activeTabIdsStr;
|
||||
if (tabChanged) {
|
||||
console.log("🔄 [TableSearchWidget] 탭 전환 감지:", {
|
||||
이전탭: prevActiveTabIdsRef.current,
|
||||
현재탭: activeTabIdsStr,
|
||||
가용테이블: tableList.map((t) => ({ id: t.tableId, tableName: t.tableName, parentTabId: t.parentTabId })),
|
||||
현재선택테이블: selectedTableId,
|
||||
});
|
||||
prevActiveTabIdsRef.current = activeTabIdsStr;
|
||||
|
||||
// 🆕 탭 전환 시: 해당 탭에 속한 테이블 중 첫 번째 강제 선택
|
||||
// 탭 전환 시: 해당 탭에 속한 테이블 중 첫 번째 강제 선택
|
||||
const activeTabTable = tableList.find((t) => t.parentTabId && activeTabIds.includes(t.parentTabId));
|
||||
const targetTable = activeTabTable || tableList[0];
|
||||
|
||||
if (targetTable) {
|
||||
console.log("✅ [TableSearchWidget] 탭 전환으로 테이블 강제 선택:", {
|
||||
테이블ID: targetTable.tableId,
|
||||
테이블명: targetTable.tableName,
|
||||
탭ID: targetTable.parentTabId,
|
||||
이전테이블: selectedTableId,
|
||||
});
|
||||
setSelectedTableId(targetTable.tableId);
|
||||
}
|
||||
return; // 탭 전환 시에는 여기서 종료
|
||||
@@ -222,11 +182,6 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
const targetTable = activeTabTable || tableList[0];
|
||||
|
||||
if (targetTable && targetTable.tableId !== selectedTableId) {
|
||||
console.log("✅ [TableSearchWidget] 테이블 자동 선택 (초기):", {
|
||||
테이블ID: targetTable.tableId,
|
||||
테이블명: targetTable.tableName,
|
||||
탭ID: targetTable.parentTabId,
|
||||
});
|
||||
setSelectedTableId(targetTable.tableId);
|
||||
}
|
||||
}
|
||||
@@ -270,13 +225,6 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
|
||||
// 현재 테이블의 저장된 필터 불러오기 (동적 모드) 또는 고정 필터 적용 (고정 모드)
|
||||
useEffect(() => {
|
||||
console.log("📋 [TableSearchWidget] 필터 설정 useEffect 실행:", {
|
||||
currentTable: currentTable?.tableName,
|
||||
currentTableTabId,
|
||||
filterMode,
|
||||
selectedTableId,
|
||||
컬럼수: currentTable?.columns?.length,
|
||||
});
|
||||
if (!currentTable?.tableName) return;
|
||||
|
||||
// 고정 모드: presetFilters를 activeFilters로 설정
|
||||
@@ -317,13 +265,6 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
: `table_filters_${currentTable.tableName}`;
|
||||
const savedFilters = localStorage.getItem(filterConfigKey);
|
||||
|
||||
console.log("🔑 [TableSearchWidget] 필터 설정 키 확인:", {
|
||||
filterConfigKey,
|
||||
savedFilters: savedFilters ? `${savedFilters.substring(0, 100)}...` : null,
|
||||
screenId,
|
||||
tableName: currentTable.tableName,
|
||||
});
|
||||
|
||||
if (savedFilters) {
|
||||
try {
|
||||
const parsed = JSON.parse(savedFilters) as Array<{
|
||||
@@ -346,13 +287,6 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
width: f.width || 200,
|
||||
}));
|
||||
|
||||
console.log("📌 [TableSearchWidget] 필터 설정 로드:", {
|
||||
filterConfigKey,
|
||||
총필터수: parsed.length,
|
||||
활성화필터수: activeFiltersList.length,
|
||||
활성화필터: activeFiltersList.map((f) => f.columnName),
|
||||
});
|
||||
|
||||
setActiveFilters(activeFiltersList);
|
||||
|
||||
// 탭별 저장된 필터 값 복원
|
||||
@@ -382,10 +316,6 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
}
|
||||
} else {
|
||||
// 필터 설정이 없으면 activeFilters와 filterValues 모두 초기화
|
||||
console.log("⚠️ [TableSearchWidget] 저장된 필터 설정 없음 - 필터 초기화:", {
|
||||
tableName: currentTable.tableName,
|
||||
filterConfigKey,
|
||||
});
|
||||
setActiveFilters([]);
|
||||
setFilterValues({});
|
||||
setSelectOptions({});
|
||||
@@ -540,7 +470,8 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
}
|
||||
|
||||
// 다중선택 배열을 처리 (파이프로 연결된 문자열로 변환)
|
||||
if (filter.filterType === "select" && Array.isArray(filterValue)) {
|
||||
// filterType에 관계없이 배열이면 파이프로 연결
|
||||
if (Array.isArray(filterValue)) {
|
||||
filterValue = filterValue.join("|");
|
||||
}
|
||||
|
||||
@@ -553,26 +484,11 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
// 빈 값 체크
|
||||
if (!f.value) return false;
|
||||
if (typeof f.value === "string" && f.value === "") return false;
|
||||
if (Array.isArray(f.value) && f.value.length === 0) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
console.log("🔍 [TableSearchWidget] applyFilters 호출:", {
|
||||
currentTableId: currentTable?.tableId,
|
||||
currentTableName: currentTable?.tableName,
|
||||
hasOnFilterChange: !!currentTable?.onFilterChange,
|
||||
filtersCount: filtersWithValues.length,
|
||||
filters: filtersWithValues.map((f) => ({
|
||||
col: f.columnName,
|
||||
op: f.operator,
|
||||
val: f.value,
|
||||
})),
|
||||
});
|
||||
|
||||
if (currentTable?.onFilterChange) {
|
||||
currentTable.onFilterChange(filtersWithValues);
|
||||
} else {
|
||||
console.warn("⚠️ [TableSearchWidget] onFilterChange가 없음!", { currentTable });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -771,54 +687,28 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 동적 모드일 때만 설정 버튼들 표시 (미리보기에서는 비활성화) */}
|
||||
{/* 동적 모드일 때만 설정 버튼 표시 (미리보기에서는 비활성화) */}
|
||||
{filterMode === "dynamic" && (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => !isPreviewMode && setColumnVisibilityOpen(true)}
|
||||
disabled={!selectedTableId || isPreviewMode}
|
||||
className="h-8 text-xs sm:h-9 sm:text-sm"
|
||||
>
|
||||
<Settings className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
|
||||
테이블 옵션
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => !isPreviewMode && setFilterOpen(true)}
|
||||
disabled={!selectedTableId || isPreviewMode}
|
||||
className="h-8 text-xs sm:h-9 sm:text-sm"
|
||||
>
|
||||
<Filter className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
|
||||
필터 설정
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => !isPreviewMode && setGroupingOpen(true)}
|
||||
disabled={!selectedTableId || isPreviewMode}
|
||||
className="h-8 text-xs sm:h-9 sm:text-sm"
|
||||
>
|
||||
<Layers className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
|
||||
그룹 설정
|
||||
</Button>
|
||||
</>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => !isPreviewMode && setSettingsOpen(true)}
|
||||
disabled={!selectedTableId || isPreviewMode}
|
||||
className="h-8 text-xs sm:h-9 sm:text-sm"
|
||||
>
|
||||
<Settings className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
|
||||
테이블 설정
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 패널들 */}
|
||||
<ColumnVisibilityPanel isOpen={columnVisibilityOpen} onClose={() => setColumnVisibilityOpen(false)} />
|
||||
<FilterPanel
|
||||
isOpen={filterOpen}
|
||||
onClose={() => setFilterOpen(false)}
|
||||
{/* 통합 설정 모달 */}
|
||||
<TableSettingsModal
|
||||
isOpen={settingsOpen}
|
||||
onClose={() => setSettingsOpen(false)}
|
||||
onFiltersApplied={(filters) => setActiveFilters(filters)}
|
||||
screenId={screenId}
|
||||
/>
|
||||
<GroupingPanel isOpen={groupingOpen} onClose={() => setGroupingOpen(false)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -529,8 +529,6 @@ export class ButtonActionExecutor {
|
||||
// 약간의 대기 시간을 주어 이벤트 핸들러가 formData를 업데이트할 수 있도록 함
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
console.log("📦 [handleSave] beforeFormSave 이벤트 후 formData keys:", Object.keys(context.formData || {}));
|
||||
|
||||
// 검증 실패 시 저장 중단
|
||||
if (beforeSaveEventDetail.validationFailed) {
|
||||
console.log("❌ [handleSave] 검증 실패로 저장 중단:", beforeSaveEventDetail.validationErrors);
|
||||
@@ -549,13 +547,13 @@ export class ButtonActionExecutor {
|
||||
);
|
||||
|
||||
if (hasTableSectionData) {
|
||||
console.log("📋 [handleSave] _tableSection_ 데이터 감지 - onSave 콜백 건너뛰고 테이블 섹션 저장 로직 사용");
|
||||
|
||||
}
|
||||
|
||||
// 🆕 EditModal 등에서 전달된 onSave 콜백이 있으면 우선 사용
|
||||
// 단, _tableSection_ 데이터가 있으면 건너뛰기 (handleUniversalFormModalTableSectionSave가 처리)
|
||||
if (onSave && !hasTableSectionData) {
|
||||
console.log("✅ [handleSave] onSave 콜백 발견 - 콜백 실행 (테이블 섹션 데이터 없음)");
|
||||
|
||||
try {
|
||||
await onSave();
|
||||
return true;
|
||||
@@ -2214,14 +2212,6 @@ export class ButtonActionExecutor {
|
||||
// 섹션별 원본 데이터가 있으면 사용, 없으면 전역 originalGroupedData 사용
|
||||
const originalDataForDelete = sectionOriginalData.length > 0 ? sectionOriginalData : originalGroupedData;
|
||||
|
||||
console.log(`🔍 [DELETE 비교] 섹션 ${sectionId}:`, {
|
||||
sectionOriginalKey,
|
||||
sectionOriginalCount: sectionOriginalData.length,
|
||||
globalOriginalCount: originalGroupedData.length,
|
||||
usingData: sectionOriginalData.length > 0 ? "섹션별 원본" : "전역 원본",
|
||||
currentCount: currentItems.length,
|
||||
});
|
||||
|
||||
// ⚠️ id 타입 통일: 문자열로 변환하여 비교 (숫자 vs 문자열 불일치 방지)
|
||||
const currentIds = new Set(currentItems.map((item) => String(item.id)).filter(Boolean));
|
||||
const deletedItems = originalDataForDelete.filter((orig) => orig.id && !currentIds.has(String(orig.id)));
|
||||
|
||||
@@ -319,8 +319,6 @@ class LegacyEventAdapter {
|
||||
this.config = { ...this.config, ...options };
|
||||
}
|
||||
|
||||
console.log("[LegacyEventAdapter] 초기화 시작", this.config);
|
||||
|
||||
EVENT_MAPPINGS.forEach((mapping) => {
|
||||
// 레거시 → V2 브릿지
|
||||
if (this.config.legacyToV2) {
|
||||
@@ -334,9 +332,6 @@ class LegacyEventAdapter {
|
||||
});
|
||||
|
||||
this.isActive = true;
|
||||
console.log(
|
||||
`[LegacyEventAdapter] 초기화 완료 (${EVENT_MAPPINGS.length}개 매핑)`
|
||||
);
|
||||
}
|
||||
|
||||
private setupLegacyToV2Bridge(mapping: EventMapping): void {
|
||||
@@ -411,8 +406,6 @@ class LegacyEventAdapter {
|
||||
|
||||
this.bridgedEvents.clear();
|
||||
this.isActive = false;
|
||||
|
||||
console.log("[LegacyEventAdapter] 정리 완료");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,8 +55,6 @@ export function initV2Core(options?: V2CoreOptions): void {
|
||||
legacyBridge = { legacyToV2: true, v2ToLegacy: true },
|
||||
} = options ?? {};
|
||||
|
||||
console.log("[V2Core] 초기화 시작...");
|
||||
|
||||
// 디버그 모드 설정
|
||||
v2EventBus.debug = debug;
|
||||
|
||||
@@ -64,11 +62,6 @@ export function initV2Core(options?: V2CoreOptions): void {
|
||||
legacyEventAdapter.init(legacyBridge);
|
||||
|
||||
isInitialized = true;
|
||||
|
||||
console.log("[V2Core] 초기화 완료", {
|
||||
debug,
|
||||
legacyBridge: legacyEventAdapter.active,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user