import { apiClient, ApiResponse } from "./client"; // 동적 폼 데이터 타입 export interface DynamicFormData { screenId: number; tableName: string; data: Record; } // 폼 데이터 저장 응답 타입 export interface SaveFormDataResponse { id: number; success: boolean; message: string; data?: Record; } // 폼 데이터 조회 응답 타입 export interface FormDataResponse { id: number; screenId: number; tableName: string; data: Record; createdAt: string; updatedAt: string; } // 동적 폼 API 클래스 export class DynamicFormApi { /** * 폼 데이터 저장 (기존 버전 - 레거시 지원) * @param formData 저장할 폼 데이터 * @returns 저장 결과 */ static async saveFormData(formData: DynamicFormData): Promise> { try { const response = await apiClient.post("/dynamic-form/save", formData); return { success: true, data: response.data, message: "데이터가 성공적으로 저장되었습니다.", }; } catch (error: any) { console.error("❌ 폼 데이터 저장 실패:", error); const errorMessage = error.response?.data?.message || error.message || "데이터 저장 중 오류가 발생했습니다."; return { success: false, message: errorMessage, errorCode: error.response?.data?.errorCode, }; } } /** * 폼 데이터 저장 (개선된 버전) * @param formData 저장할 폼 데이터 * @returns 저장 결과 (상세한 검증 정보 포함) */ static async saveData(formData: DynamicFormData): Promise> { try { const response = await apiClient.post("/dynamic-form/save-enhanced", formData); return response.data; } catch (error: any) { console.error("❌ 개선된 폼 데이터 저장 실패:", error); // 개선된 오류 처리 const errorResponse = error.response?.data; if (errorResponse && !errorResponse.success) { return errorResponse; // 서버에서 온 구조화된 오류 응답 그대로 반환 } const errorMessage = error.response?.data?.message || error.message || "데이터 저장 중 오류가 발생했습니다."; return { success: false, message: errorMessage, errorCode: error.response?.data?.errorCode, }; } } /** * 폼 데이터 업데이트 * @param id 레코드 ID * @param formData 업데이트할 폼 데이터 * @returns 업데이트 결과 */ static async updateFormData( id: string | number, formData: Partial, ): Promise> { try { console.log("🔄 폼 데이터 업데이트 요청:", { id, formData }); console.log("🌐 API URL:", `/dynamic-form/${id}`); console.log("📦 요청 본문:", JSON.stringify(formData, null, 2)); const response = await apiClient.put(`/dynamic-form/${id}`, formData); console.log("✅ 폼 데이터 업데이트 성공:", response.data); console.log("📊 응답 상태:", response.status); console.log("📋 응답 헤더:", response.headers); return { success: true, data: response.data, message: "데이터가 성공적으로 업데이트되었습니다.", }; } catch (error: any) { console.error("❌ 폼 데이터 업데이트 실패:", error); console.error("📊 에러 응답:", error.response?.data); console.error("📊 에러 상태:", error.response?.status); const errorMessage = error.response?.data?.message || error.message || "데이터 업데이트 중 오류가 발생했습니다."; return { success: false, message: errorMessage, errorCode: error.response?.data?.errorCode, }; } } /** * 폼 데이터 부분 업데이트 (변경된 필드만) * @param id 레코드 ID * @param originalData 원본 데이터 * @param newData 변경할 데이터 * @param tableName 테이블명 * @returns 업데이트 결과 */ static async updateFormDataPartial( id: string | number, // 🔧 UUID 문자열도 지원 originalData: Record, newData: Record, tableName: string, ): Promise> { try { console.log("🔄 폼 데이터 부분 업데이트 요청:", { id, originalData, newData, tableName, }); const response = await apiClient.patch(`/dynamic-form/${id}/partial`, { tableName, originalData, newData, }); console.log("✅ 폼 데이터 부분 업데이트 성공:", response.data); return { success: true, data: response.data, message: "데이터가 성공적으로 업데이트되었습니다.", }; } catch (error: any) { console.error("❌ 폼 데이터 부분 업데이트 실패:", error); const errorMessage = error.response?.data?.message || error.message || "부분 업데이트 중 오류가 발생했습니다."; return { success: false, message: errorMessage, errorCode: error.response?.data?.errorCode, }; } } /** * 폼 데이터 삭제 * @param id 레코드 ID * @returns 삭제 결과 */ static async deleteFormData(id: number): Promise> { try { console.log("🗑️ 폼 데이터 삭제 요청:", id); await apiClient.delete(`/dynamic-form/${id}`); console.log("✅ 폼 데이터 삭제 성공"); return { success: true, message: "데이터가 성공적으로 삭제되었습니다.", }; } catch (error: any) { console.error("❌ 폼 데이터 삭제 실패:", error); const errorMessage = error.response?.data?.message || error.message || "데이터 삭제 중 오류가 발생했습니다."; return { success: false, message: errorMessage, errorCode: error.response?.data?.errorCode, }; } } /** * 실제 테이블에서 폼 데이터 삭제 * @param id 레코드 ID * @param tableName 테이블명 * @returns 삭제 결과 */ static async deleteFormDataFromTable(id: string | number, tableName: string): Promise> { try { console.log("🗑️ 실제 테이블에서 폼 데이터 삭제 요청:", { id, tableName }); await apiClient.delete(`/dynamic-form/${id}`, { data: { tableName }, }); console.log("✅ 실제 테이블에서 폼 데이터 삭제 성공"); return { success: true, message: "데이터가 성공적으로 삭제되었습니다.", }; } catch (error: any) { console.error("❌ 실제 테이블에서 폼 데이터 삭제 실패:", error); const errorMessage = error.response?.data?.message || error.message || "데이터 삭제 중 오류가 발생했습니다."; return { success: false, message: errorMessage, errorCode: error.response?.data?.errorCode, }; } } /** * 폼 데이터 목록 조회 * @param screenId 화면 ID * @param params 검색 파라미터 * @returns 폼 데이터 목록 */ static async getFormDataList( screenId: number, params?: { page?: number; size?: number; search?: string; sortBy?: string; sortOrder?: "asc" | "desc"; }, ): Promise< ApiResponse<{ content: FormDataResponse[]; totalElements: number; totalPages: number; currentPage: number; size: number; }> > { try { console.log("📋 폼 데이터 목록 조회 요청:", { screenId, params }); const response = await apiClient.get(`/dynamic-form/screen/${screenId}`, { params }); console.log("✅ 폼 데이터 목록 조회 성공:", response.data); return { success: true, data: response.data, }; } catch (error: any) { console.error("❌ 폼 데이터 목록 조회 실패:", error); const errorMessage = error.response?.data?.message || error.message || "데이터 조회 중 오류가 발생했습니다."; return { success: false, message: errorMessage, errorCode: error.response?.data?.errorCode, }; } } /** * 특정 폼 데이터 조회 * @param id 레코드 ID * @returns 폼 데이터 */ static async getFormData(id: number): Promise> { try { console.log("📄 폼 데이터 단건 조회 요청:", id); const response = await apiClient.get(`/dynamic-form/${id}`); console.log("✅ 폼 데이터 단건 조회 성공:", response.data); return { success: true, data: response.data, }; } catch (error: any) { console.error("❌ 폼 데이터 단건 조회 실패:", error); const errorMessage = error.response?.data?.message || error.message || "데이터 조회 중 오류가 발생했습니다."; return { success: false, message: errorMessage, errorCode: error.response?.data?.errorCode, }; } } /** * 테이블의 컬럼 정보 조회 (폼 검증용) * @param tableName 테이블명 * @returns 컬럼 정보 */ static async getTableColumns(tableName: string): Promise< ApiResponse<{ tableName: string; columns: Array<{ columnName: string; dataType: string; nullable: boolean; primaryKey: boolean; maxLength?: number; defaultValue?: any; }>; }> > { try { console.log("📊 테이블 컬럼 정보 조회 요청:", tableName); const response = await apiClient.get(`/dynamic-form/table/${tableName}/columns`); console.log("✅ 테이블 컬럼 정보 조회 성공:", response.data); return { success: true, data: response.data, }; } catch (error: any) { console.error("❌ 테이블 컬럼 정보 조회 실패:", error); const errorMessage = error.response?.data?.message || error.message || "테이블 정보 조회 중 오류가 발생했습니다."; return { success: false, message: errorMessage, errorCode: error.response?.data?.errorCode, }; } } /** * 폼 데이터 검증 * @param tableName 테이블명 * @param data 검증할 데이터 * @returns 검증 결과 */ static async validateFormData( tableName: string, data: Record, ): Promise< ApiResponse<{ valid: boolean; errors: Array<{ field: string; message: string; code: string; }>; }> > { try { console.log("✅ 폼 데이터 검증 요청:", { tableName, data }); const response = await apiClient.post(`/dynamic-form/validate`, { tableName, data, }); console.log("✅ 폼 데이터 검증 성공:", response.data); return { success: true, data: response.data, }; } catch (error: any) { console.error("❌ 폼 데이터 검증 실패:", error); const errorMessage = error.response?.data?.message || error.message || "데이터 검증 중 오류가 발생했습니다."; return { success: false, message: errorMessage, errorCode: error.response?.data?.errorCode, }; } } /** * 테이블의 기본키 조회 * @param tableName 테이블명 * @returns 기본키 컬럼명 배열 */ static async getTablePrimaryKeys(tableName: string): Promise> { try { const response = await apiClient.get(`/dynamic-form/table/${tableName}/primary-keys`); return { success: true, data: response.data.data, message: "기본키 조회가 완료되었습니다.", }; } catch (error: any) { console.error("❌ 테이블 기본키 조회 실패:", error); const errorMessage = error.response?.data?.message || error.message || "기본키 조회 중 오류가 발생했습니다."; return { success: false, message: errorMessage, errorCode: error.response?.data?.errorCode, }; } } /** * 테이블 데이터 조회 (페이징 + 검색) * @param tableName 테이블명 * @param params 검색 파라미터 * @returns 테이블 데이터 */ static async getTableData( tableName: string, params?: { page?: number; pageSize?: number; search?: string; sortBy?: string; sortOrder?: "asc" | "desc"; filters?: Record; autoFilter?: { enabled: boolean; filterColumn?: string; userField?: string; }; }, ): Promise> { try { console.log("📊 테이블 데이터 조회 요청:", { tableName, params }); // autoFilter가 없으면 기본값으로 멀티테넌시 필터 적용 // pageSize를 size로 변환 (백엔드 파라미터명 호환) const requestParams = { ...params, size: params?.pageSize || params?.size || 100, // 기본값 100 autoFilter: params?.autoFilter ?? { enabled: true, filterColumn: "company_code", userField: "companyCode", }, }; const response = await apiClient.post(`/table-management/tables/${tableName}/data`, requestParams); console.log("✅ 테이블 데이터 조회 성공 (원본):", response.data); console.log("🔍 response.data 상세:", { type: typeof response.data, isArray: Array.isArray(response.data), keys: response.data ? Object.keys(response.data) : [], hasData: response.data?.data !== undefined, dataType: response.data?.data ? typeof response.data.data : "N/A", dataIsArray: response.data?.data ? Array.isArray(response.data.data) : false, dataLength: response.data?.data ? (Array.isArray(response.data.data) ? response.data.data.length : "not array") : "no data", // 중첩 구조 확인 dataDataExists: response.data?.data?.data !== undefined, dataDataIsArray: response.data?.data?.data ? Array.isArray(response.data.data.data) : false, dataDataLength: response.data?.data?.data ? (Array.isArray(response.data.data.data) ? response.data.data.data.length : "not array") : "no nested data", }); // API 응답 구조: { data: [...], total, page, size, totalPages } // 또는 중첩: { success: true, data: { data: [...], total, ... } } // data 배열만 추출 let tableData: any[] = []; if (Array.isArray(response.data)) { // 케이스 1: 응답이 배열이면 그대로 사용 console.log("✅ 케이스 1: 응답이 배열"); tableData = response.data; } else if (response.data && Array.isArray(response.data.data)) { // 케이스 2: 응답이 { data: [...] } 구조면 data 배열 추출 console.log("✅ 케이스 2: 응답이 { data: [...] } 구조"); tableData = response.data.data; } else if (response.data?.data?.data && Array.isArray(response.data.data.data)) { // 케이스 2-1: 중첩 구조 { success: true, data: { data: [...] } } console.log("✅ 케이스 2-1: 중첩 구조 { data: { data: [...] } }"); tableData = response.data.data.data; } else if (response.data && typeof response.data === "object") { // 케이스 3: 응답이 객체면 배열로 감싸기 (최후의 수단) console.log("⚠️ 케이스 3: 응답이 객체 (배열로 감싸기)"); tableData = [response.data]; } console.log("✅ 테이블 데이터 추출 완료:", { originalType: typeof response.data, isArray: Array.isArray(response.data), hasDataProperty: response.data?.data !== undefined, extractedCount: tableData.length, firstRow: tableData[0], allRows: tableData, }); return { success: true, data: tableData, message: "테이블 데이터 조회가 완료되었습니다.", }; } catch (error: any) { console.error("❌ 테이블 데이터 조회 실패:", error); const errorMessage = error.response?.data?.message || error.message || "테이블 데이터 조회 중 오류가 발생했습니다."; return { success: false, message: errorMessage, errorCode: error.response?.data?.errorCode, }; } } /** * 엑셀 업로드 (대량 데이터 삽입/업데이트) * @param payload 업로드 데이터 * @returns 업로드 결과 */ static async uploadExcelData(payload: { tableName: string; data: any[]; uploadMode: "insert" | "update" | "upsert"; keyColumn?: string; }): Promise> { try { console.log("📤 엑셀 업로드 요청:", payload); const response = await apiClient.post(`/dynamic-form/excel-upload`, payload); console.log("✅ 엑셀 업로드 성공:", response.data); return { success: true, data: response.data, message: "엑셀 파일이 성공적으로 업로드되었습니다.", }; } catch (error: any) { console.error("❌ 엑셀 업로드 실패:", error); const errorMessage = error.response?.data?.message || error.message || "엑셀 업로드 중 오류가 발생했습니다."; return { success: false, message: errorMessage, errorCode: error.response?.data?.errorCode, }; } } // ================================ // 마스터-디테일 엑셀 API // ================================ /** * 마스터-디테일 관계 정보 조회 * @param screenId 화면 ID * @returns 마스터-디테일 관계 정보 (null이면 마스터-디테일 구조 아님) */ static async getMasterDetailRelation(screenId: number): Promise> { try { console.log("🔍 마스터-디테일 관계 조회:", screenId); const response = await apiClient.get(`/data/master-detail/relation/${screenId}`); return { success: true, data: response.data?.data || null, message: response.data?.message || "조회 완료", }; } catch (error: any) { console.error("❌ 마스터-디테일 관계 조회 실패:", error); return { success: false, data: null, message: error.response?.data?.message || error.message, }; } } /** * 마스터-디테일 엑셀 다운로드 데이터 조회 * @param screenId 화면 ID * @param filters 필터 조건 * @returns JOIN된 플랫 데이터 */ static async getMasterDetailDownloadData( screenId: number, filters?: Record ): Promise> { try { console.log("📥 마스터-디테일 다운로드 데이터 조회:", { screenId, filters }); const response = await apiClient.post(`/data/master-detail/download`, { screenId, filters, }); return { success: true, data: response.data?.data, message: "데이터 조회 완료", }; } catch (error: any) { console.error("❌ 마스터-디테일 다운로드 실패:", error); return { success: false, message: error.response?.data?.message || error.message, }; } } /** * 마스터-디테일 엑셀 업로드 * @param screenId 화면 ID * @param data 엑셀에서 읽은 플랫 데이터 * @returns 업로드 결과 */ static async uploadMasterDetailData( screenId: number, data: Record[] ): Promise> { try { console.log("📤 마스터-디테일 업로드:", { screenId, rowCount: data.length }); const response = await apiClient.post(`/data/master-detail/upload`, { screenId, data, }); return { success: response.data?.success, data: response.data?.data, message: response.data?.message, }; } catch (error: any) { console.error("❌ 마스터-디테일 업로드 실패:", error); return { success: false, message: error.response?.data?.message || error.message, }; } } } // 마스터-디테일 관계 타입 export interface MasterDetailRelation { masterTable: string; detailTable: string; masterKeyColumn: string; detailFkColumn: string; masterColumns: Array<{ name: string; label: string; inputType: string; isFromMaster: boolean }>; detailColumns: Array<{ name: string; label: string; inputType: string; isFromMaster: boolean }>; } // 마스터-디테일 다운로드 데이터 타입 export interface MasterDetailDownloadData { headers: string[]; columns: string[]; data: Record[]; masterColumns: string[]; detailColumns: string[]; joinKey: string; } // 마스터-디테일 업로드 결과 타입 export interface MasterDetailUploadResult { success: boolean; masterInserted: number; masterUpdated: number; detailInserted: number; detailDeleted: number; errors: string[]; } // 편의를 위한 기본 export export const dynamicFormApi = DynamicFormApi;