2025-10-15 17:25:38 +09:00
|
|
|
import { apiClient } from "./client";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 데이터 조회 API
|
|
|
|
|
*/
|
|
|
|
|
export const dataApi = {
|
|
|
|
|
/**
|
|
|
|
|
* 테이블 데이터 조회
|
|
|
|
|
* @param tableName 테이블명
|
|
|
|
|
* @param params 조회 파라미터 (검색, 페이징 등)
|
|
|
|
|
*/
|
|
|
|
|
getTableData: async (
|
|
|
|
|
tableName: string,
|
|
|
|
|
params?: {
|
|
|
|
|
page?: number;
|
|
|
|
|
size?: number;
|
|
|
|
|
searchTerm?: string;
|
|
|
|
|
sortBy?: string;
|
|
|
|
|
sortOrder?: "asc" | "desc";
|
|
|
|
|
filters?: Record<string, any>;
|
|
|
|
|
},
|
|
|
|
|
): Promise<{
|
|
|
|
|
data: any[];
|
|
|
|
|
total: number;
|
|
|
|
|
page: number;
|
|
|
|
|
size: number;
|
|
|
|
|
totalPages: number;
|
|
|
|
|
}> => {
|
|
|
|
|
const response = await apiClient.get(`/data/${tableName}`, { params });
|
|
|
|
|
const raw = response.data || {};
|
|
|
|
|
const items: any[] = (raw.data ?? raw.items ?? raw.rows ?? []) as any[];
|
|
|
|
|
|
|
|
|
|
const page = raw.page ?? params?.page ?? 1;
|
|
|
|
|
const size = raw.size ?? params?.size ?? items.length;
|
|
|
|
|
const total = raw.total ?? items.length;
|
|
|
|
|
const totalPages = raw.totalPages ?? Math.max(1, Math.ceil(total / (size || 1)));
|
|
|
|
|
|
|
|
|
|
return { data: items, total, page, size, totalPages };
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 특정 레코드 상세 조회
|
|
|
|
|
* @param tableName 테이블명
|
|
|
|
|
* @param id 레코드 ID
|
2025-11-20 10:23:54 +09:00
|
|
|
* @param enableEntityJoin Entity 조인 활성화 여부 (기본값: false)
|
|
|
|
|
* @param groupByColumns 그룹핑 기준 컬럼들 (배열)
|
2025-10-15 17:25:38 +09:00
|
|
|
*/
|
2025-11-20 10:23:54 +09:00
|
|
|
getRecordDetail: async (
|
|
|
|
|
tableName: string,
|
|
|
|
|
id: string | number,
|
|
|
|
|
enableEntityJoin: boolean = false,
|
|
|
|
|
groupByColumns: string[] = []
|
|
|
|
|
): Promise<{ success: boolean; data?: any; error?: string }> => {
|
|
|
|
|
try {
|
|
|
|
|
const params: any = {};
|
|
|
|
|
if (enableEntityJoin) {
|
|
|
|
|
params.enableEntityJoin = true;
|
|
|
|
|
}
|
|
|
|
|
if (groupByColumns.length > 0) {
|
|
|
|
|
params.groupByColumns = JSON.stringify(groupByColumns);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log("🌐 [dataApi.getRecordDetail] API 호출:", {
|
|
|
|
|
tableName,
|
|
|
|
|
id,
|
|
|
|
|
enableEntityJoin,
|
|
|
|
|
groupByColumns,
|
|
|
|
|
params,
|
|
|
|
|
url: `/data/${tableName}/${id}`,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const response = await apiClient.get(`/data/${tableName}/${id}`, { params });
|
|
|
|
|
|
|
|
|
|
console.log("📥 [dataApi.getRecordDetail] API 응답:", {
|
|
|
|
|
success: response.data?.success,
|
|
|
|
|
dataType: Array.isArray(response.data?.data) ? "배열" : "객체",
|
|
|
|
|
dataCount: Array.isArray(response.data?.data) ? response.data.data.length : 1,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return response.data; // { success: true, data: ... } 형식 그대로 반환
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error("❌ [dataApi.getRecordDetail] API 오류:", error);
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
error: error.response?.data?.message || error.message || "레코드 조회 실패",
|
|
|
|
|
};
|
|
|
|
|
}
|
2025-10-15 17:25:38 +09:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 조인된 데이터 조회
|
|
|
|
|
* @param leftTable 좌측 테이블명
|
|
|
|
|
* @param rightTable 우측 테이블명
|
|
|
|
|
* @param leftColumn 좌측 컬럼명
|
|
|
|
|
* @param rightColumn 우측 컬럼명 (외래키)
|
|
|
|
|
* @param leftValue 좌측 값 (필터링)
|
2025-11-20 10:23:54 +09:00
|
|
|
* @param dataFilter 데이터 필터
|
|
|
|
|
* @param enableEntityJoin Entity 조인 활성화
|
|
|
|
|
* @param displayColumns 표시할 컬럼 목록 (tableName.columnName 형식 포함)
|
2025-10-15 17:25:38 +09:00
|
|
|
*/
|
|
|
|
|
getJoinedData: async (
|
|
|
|
|
leftTable: string,
|
|
|
|
|
rightTable: string,
|
|
|
|
|
leftColumn: string,
|
|
|
|
|
rightColumn: string,
|
|
|
|
|
leftValue?: any,
|
2025-11-20 10:23:54 +09:00
|
|
|
dataFilter?: any,
|
|
|
|
|
enableEntityJoin?: boolean,
|
|
|
|
|
displayColumns?: Array<{ name: string; label?: string }>,
|
|
|
|
|
deduplication?: { // 🆕 중복 제거 설정
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
groupByColumn: string;
|
|
|
|
|
keepStrategy: "latest" | "earliest" | "base_price" | "current_date";
|
|
|
|
|
sortColumn?: string;
|
|
|
|
|
},
|
2025-10-15 17:25:38 +09:00
|
|
|
): Promise<any[]> => {
|
|
|
|
|
const response = await apiClient.get(`/data/join`, {
|
|
|
|
|
params: {
|
|
|
|
|
leftTable,
|
|
|
|
|
rightTable,
|
|
|
|
|
leftColumn,
|
|
|
|
|
rightColumn,
|
|
|
|
|
leftValue,
|
2025-11-20 10:23:54 +09:00
|
|
|
dataFilter: dataFilter ? JSON.stringify(dataFilter) : undefined,
|
|
|
|
|
enableEntityJoin: enableEntityJoin ?? true,
|
|
|
|
|
displayColumns: displayColumns ? JSON.stringify(displayColumns) : undefined, // 🆕 표시 컬럼 전달
|
|
|
|
|
deduplication: deduplication ? JSON.stringify(deduplication) : undefined, // 🆕 중복 제거 설정 전달
|
2025-10-15 17:25:38 +09:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
const raw = response.data || {};
|
|
|
|
|
return (raw.data ?? raw.items ?? raw.rows ?? []) as any[];
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 레코드 생성
|
|
|
|
|
* @param tableName 테이블명
|
|
|
|
|
* @param data 레코드 데이터
|
|
|
|
|
*/
|
|
|
|
|
createRecord: async (tableName: string, data: Record<string, any>): Promise<any> => {
|
|
|
|
|
const response = await apiClient.post(`/data/${tableName}`, data);
|
2025-11-07 14:22:23 +09:00
|
|
|
return response.data; // success, data, message 포함된 전체 응답 반환
|
2025-10-15 17:25:38 +09:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 레코드 수정
|
|
|
|
|
* @param tableName 테이블명
|
|
|
|
|
* @param id 레코드 ID
|
|
|
|
|
* @param data 수정할 데이터
|
|
|
|
|
*/
|
|
|
|
|
updateRecord: async (tableName: string, id: string | number, data: Record<string, any>): Promise<any> => {
|
|
|
|
|
const response = await apiClient.put(`/data/${tableName}/${id}`, data);
|
2025-11-07 16:02:01 +09:00
|
|
|
return response.data; // success, data, message 포함된 전체 응답 반환
|
2025-10-15 17:25:38 +09:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 레코드 삭제
|
|
|
|
|
* @param tableName 테이블명
|
2025-11-07 18:20:24 +09:00
|
|
|
* @param id 레코드 ID 또는 복합키 객체
|
2025-10-15 17:25:38 +09:00
|
|
|
*/
|
2025-11-07 18:20:24 +09:00
|
|
|
deleteRecord: async (tableName: string, id: string | number | Record<string, any>): Promise<any> => {
|
|
|
|
|
// 복합키 객체인 경우 POST로 전달
|
|
|
|
|
if (typeof id === 'object' && !Array.isArray(id)) {
|
|
|
|
|
const response = await apiClient.post(`/data/${tableName}/delete`, id);
|
|
|
|
|
return response.data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 단일 ID인 경우 기존 방식
|
2025-11-07 16:02:01 +09:00
|
|
|
const response = await apiClient.delete(`/data/${tableName}/${id}`);
|
|
|
|
|
return response.data; // success, message 포함된 전체 응답 반환
|
2025-10-15 17:25:38 +09:00
|
|
|
},
|
2025-11-20 10:23:54 +09:00
|
|
|
|
2025-11-20 11:58:43 +09:00
|
|
|
/**
|
|
|
|
|
* 조건에 맞는 모든 레코드 삭제 (그룹 삭제)
|
|
|
|
|
* @param tableName 테이블명
|
|
|
|
|
* @param filterConditions 삭제 조건 (예: { customer_id: "CUST-0002", item_id: "SLI-2025-0002" })
|
|
|
|
|
*/
|
|
|
|
|
deleteGroupRecords: async (
|
|
|
|
|
tableName: string,
|
|
|
|
|
filterConditions: Record<string, any>
|
|
|
|
|
): Promise<{ success: boolean; deleted?: number; message?: string; error?: string }> => {
|
|
|
|
|
try {
|
|
|
|
|
console.log(`🗑️ [dataApi] 그룹 삭제 요청:`, { tableName, filterConditions });
|
|
|
|
|
|
|
|
|
|
const response = await apiClient.post(`/data/${tableName}/delete-group`, filterConditions);
|
|
|
|
|
|
|
|
|
|
console.log(`✅ [dataApi] 그룹 삭제 성공:`, response.data);
|
|
|
|
|
return response.data;
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error(`❌ [dataApi] 그룹 삭제 실패:`, error);
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
error: error.response?.data?.message || error.message || "그룹 삭제 실패",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2025-11-20 10:23:54 +09:00
|
|
|
/**
|
|
|
|
|
* 특정 레코드 상세 조회
|
|
|
|
|
* @param tableName 테이블명
|
|
|
|
|
* @param id 레코드 ID
|
|
|
|
|
* @param enableEntityJoin Entity 조인 활성화 여부 (기본값: false)
|
|
|
|
|
*/
|
|
|
|
|
getRecordDetail: async (
|
|
|
|
|
tableName: string,
|
|
|
|
|
id: string | number,
|
|
|
|
|
enableEntityJoin: boolean = false
|
|
|
|
|
): Promise<{ success: boolean; data?: any; error?: string }> => {
|
|
|
|
|
try {
|
|
|
|
|
const params: any = {};
|
|
|
|
|
if (enableEntityJoin) {
|
|
|
|
|
params.enableEntityJoin = "true";
|
|
|
|
|
}
|
|
|
|
|
const response = await apiClient.get(`/data/${tableName}/${id}`, { params });
|
|
|
|
|
return response.data; // { success: true, data: ... } 형식 그대로 반환
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
error: error.response?.data?.message || error.message || "레코드 조회 실패",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 그룹화된 데이터 UPSERT
|
|
|
|
|
* @param tableName 테이블명
|
|
|
|
|
* @param parentKeys 부모 키 (예: { customer_id: "CUST-0002", item_id: "SLI-2025-0002" })
|
|
|
|
|
* @param records 레코드 배열
|
|
|
|
|
*/
|
|
|
|
|
upsertGroupedRecords: async (
|
|
|
|
|
tableName: string,
|
|
|
|
|
parentKeys: Record<string, any>,
|
|
|
|
|
records: Array<Record<string, any>>
|
|
|
|
|
): Promise<{ success: boolean; inserted?: number; updated?: number; deleted?: number; message?: string; error?: string }> => {
|
|
|
|
|
try {
|
2025-11-20 11:58:43 +09:00
|
|
|
console.log("📡 [dataApi.upsertGroupedRecords] 요청 데이터:", {
|
2025-11-20 10:23:54 +09:00
|
|
|
tableName,
|
2025-11-20 11:58:43 +09:00
|
|
|
tableNameType: typeof tableName,
|
|
|
|
|
tableNameValue: JSON.stringify(tableName),
|
2025-11-20 10:23:54 +09:00
|
|
|
parentKeys,
|
2025-11-20 11:58:43 +09:00
|
|
|
recordsCount: records.length,
|
2025-11-20 10:23:54 +09:00
|
|
|
});
|
2025-11-20 11:58:43 +09:00
|
|
|
|
|
|
|
|
const requestBody = {
|
|
|
|
|
tableName,
|
|
|
|
|
parentKeys,
|
|
|
|
|
records,
|
|
|
|
|
};
|
|
|
|
|
console.log("📦 [dataApi.upsertGroupedRecords] 요청 본문 (JSON):", JSON.stringify(requestBody, null, 2));
|
|
|
|
|
|
|
|
|
|
const response = await apiClient.post('/data/upsert-grouped', requestBody);
|
2025-11-20 10:23:54 +09:00
|
|
|
return response.data;
|
|
|
|
|
} catch (error: any) {
|
2025-11-20 11:58:43 +09:00
|
|
|
console.error("❌ [dataApi.upsertGroupedRecords] 에러:", {
|
|
|
|
|
status: error.response?.status,
|
|
|
|
|
statusText: error.response?.statusText,
|
|
|
|
|
data: error.response?.data,
|
|
|
|
|
message: error.message,
|
|
|
|
|
});
|
2025-11-20 10:23:54 +09:00
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
error: error.response?.data?.message || error.message || "데이터 저장 실패",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-10-15 17:25:38 +09:00
|
|
|
};
|