feat: 수정 모드 UPSERT 기능 구현

- SelectedItemsDetailInput 컴포넌트 수정 모드 지원
- 그룹화된 데이터 UPSERT API 추가 (/api/data/upsert-grouped)
- 부모 키 기준으로 기존 레코드 조회 후 INSERT/UPDATE/DELETE
- 각 레코드의 모든 필드 조합을 고유 키로 사용
- created_date 보존 (UPDATE 시)
- 수정 모드에서 groupByColumns 기준으로 관련 레코드 조회
- 날짜 타입 ISO 형식 자동 감지 및 포맷팅 (YYYY.MM.DD)

주요 변경사항:
- backend: dataService.upsertGroupedRecords() 메서드 구현
- backend: dataRoutes POST /api/data/upsert-grouped 엔드포인트 추가
- frontend: ScreenModal에서 groupByColumns 파라미터 전달
- frontend: SelectedItemsDetailInput 수정 모드 로직 추가
- frontend: 날짜 필드 타임존 제거 및 포맷팅 개선
This commit is contained in:
kjs
2025-11-20 10:23:54 +09:00
parent d4895c363c
commit 34cd7ba9e3
13 changed files with 2704 additions and 345 deletions

View File

@@ -221,6 +221,97 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
console.log("API 응답:", { screenInfo, layoutData });
// 🆕 URL 파라미터 확인 (수정 모드)
if (typeof window !== "undefined") {
const urlParams = new URLSearchParams(window.location.search);
const mode = urlParams.get("mode");
const editId = urlParams.get("editId");
const tableName = urlParams.get("tableName") || screenInfo.tableName;
const groupByColumnsParam = urlParams.get("groupByColumns");
console.log("📋 URL 파라미터 확인:", { mode, editId, tableName, groupByColumnsParam });
// 수정 모드이고 editId가 있으면 해당 레코드 조회
if (mode === "edit" && editId && tableName) {
try {
console.log("🔍 수정 데이터 조회 시작:", { tableName, editId, groupByColumnsParam });
const { dataApi } = await import("@/lib/api/data");
// groupByColumns 파싱
let groupByColumns: string[] = [];
if (groupByColumnsParam) {
try {
groupByColumns = JSON.parse(groupByColumnsParam);
console.log("✅ [ScreenModal] groupByColumns 파싱 성공:", groupByColumns);
} catch (e) {
console.warn("groupByColumns 파싱 실패:", e);
}
} else {
console.warn("⚠️ [ScreenModal] groupByColumnsParam이 없습니다!");
}
console.log("🚀 [ScreenModal] API 호출 직전:", {
tableName,
editId,
enableEntityJoin: true,
groupByColumns,
groupByColumnsLength: groupByColumns.length,
});
// 🆕 apiClient를 named import로 가져오기
const { apiClient } = await import("@/lib/api/client");
const params: any = {
enableEntityJoin: true,
};
if (groupByColumns.length > 0) {
params.groupByColumns = JSON.stringify(groupByColumns);
console.log("✅ [ScreenModal] groupByColumns를 params에 추가:", params.groupByColumns);
}
console.log("📡 [ScreenModal] 실제 API 요청:", {
url: `/data/${tableName}/${editId}`,
params,
});
const apiResponse = await apiClient.get(`/data/${tableName}/${editId}`, { params });
const response = apiResponse.data;
console.log("📩 [ScreenModal] API 응답 받음:", {
success: response.success,
hasData: !!response.data,
dataType: response.data ? (Array.isArray(response.data) ? "배열" : "객체") : "없음",
dataLength: Array.isArray(response.data) ? response.data.length : 1,
});
if (response.success && response.data) {
// 배열인 경우 (그룹핑) vs 단일 객체
const isArray = Array.isArray(response.data);
if (isArray) {
console.log(`✅ 수정 데이터 로드 완료 (그룹 레코드: ${response.data.length}개)`);
console.log("📦 전체 데이터 (JSON):", JSON.stringify(response.data, null, 2));
} else {
console.log("✅ 수정 데이터 로드 완료 (필드 수:", Object.keys(response.data).length, ")");
console.log("📊 모든 필드 키:", Object.keys(response.data));
console.log("📦 전체 데이터 (JSON):", JSON.stringify(response.data, null, 2));
}
setFormData(response.data);
// setFormData 직후 확인
console.log("🔄 setFormData 호출 완료");
} else {
console.error("❌ 수정 데이터 로드 실패:", response.error);
toast.error("데이터를 불러올 수 없습니다.");
}
} catch (error) {
console.error("❌ 수정 데이터 조회 오류:", error);
toast.error("데이터를 불러오는 중 오류가 발생했습니다.");
}
}
}
// screenApi는 직접 데이터를 반환하므로 .success 체크 불필요
if (screenInfo && layoutData) {
const components = layoutData.components || [];