/** * 화면 그룹 관리 API 클라이언트 * - 화면 그룹 (screen_groups) * - 화면-그룹 연결 (screen_group_screens) * - 필드 조인 (screen_field_joins) * - 데이터 흐름 (screen_data_flows) * - 화면-테이블 관계 (screen_table_relations) */ import { apiClient } from "./client"; // ============================================================ // 타입 정의 // ============================================================ export interface ScreenGroup { id: number; group_name: string; group_code: string; main_table_name?: string; description?: string; icon?: string; display_order: number; is_active: string; company_code: string; created_date?: string; updated_date?: string; writer?: string; screen_count?: number; screens?: ScreenGroupScreen[]; parent_group_id?: number | null; // 상위 그룹 ID group_level?: number; // 그룹 레벨 (0: 대분류, 1: 중분류, 2: 소분류 ...) hierarchy_path?: string; // 계층 경로 } export interface ScreenGroupScreen { id: number; group_id: number; screen_id: number; screen_name?: string; screen_role: string; display_order: number; is_default: string; company_code: string; } export interface FieldJoin { id: number; screen_id: number; layout_id?: number; component_id?: string; field_name?: string; save_table: string; save_column: string; join_table: string; join_column: string; display_column: string; join_type: string; filter_condition?: string; sort_column?: string; sort_direction?: string; is_active: string; save_table_label?: string; join_table_label?: string; } export interface DataFlow { id: number; group_id?: number; source_screen_id: number; source_action?: string; target_screen_id: number; target_action?: string; data_mapping?: Record; flow_type: string; flow_label?: string; condition_expression?: string; is_active: string; source_screen_name?: string; target_screen_name?: string; group_name?: string; } export interface TableRelation { id: number; group_id?: number; screen_id: number; table_name: string; relation_type: string; crud_operations: string; description?: string; is_active: string; screen_name?: string; group_name?: string; table_label?: string; } export interface ApiResponse { success: boolean; data?: T; message?: string; error?: string; total?: number; page?: number; size?: number; totalPages?: number; } // ============================================================ // 화면 그룹 (screen_groups) API // ============================================================ export async function getScreenGroups(params?: { page?: number; size?: number; searchTerm?: string; }): Promise> { try { const queryParams = new URLSearchParams(); if (params?.page) queryParams.append("page", params.page.toString()); if (params?.size) queryParams.append("size", params.size.toString()); if (params?.searchTerm) queryParams.append("searchTerm", params.searchTerm); const response = await apiClient.get(`/screen-groups/groups?${queryParams.toString()}`); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } export async function getScreenGroup(id: number): Promise> { try { const response = await apiClient.get(`/screen-groups/groups/${id}`); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } export async function createScreenGroup(data: Partial): Promise> { try { const response = await apiClient.post("/screen-groups/groups", data); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } export async function updateScreenGroup(id: number, data: Partial): Promise> { try { const response = await apiClient.put(`/screen-groups/groups/${id}`, data); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } export async function deleteScreenGroup(id: number): Promise> { try { const response = await apiClient.delete(`/screen-groups/groups/${id}`); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } // ============================================================ // 화면-그룹 연결 (screen_group_screens) API // ============================================================ export async function addScreenToGroup(data: { group_id: number; screen_id: number; screen_role?: string; display_order?: number; is_default?: string; }): Promise> { try { const response = await apiClient.post("/screen-groups/group-screens", data); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } export async function updateScreenInGroup(id: number, data: { screen_role?: string; display_order?: number; is_default?: string; }): Promise> { try { const response = await apiClient.put(`/screen-groups/group-screens/${id}`, data); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } export async function removeScreenFromGroup(id: number): Promise> { try { const response = await apiClient.delete(`/screen-groups/group-screens/${id}`); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } // ============================================================ // 필드 조인 (screen_field_joins) API // ============================================================ export async function getFieldJoins(screenId?: number): Promise> { try { const queryParams = screenId ? `?screen_id=${screenId}` : ""; const response = await apiClient.get(`/screen-groups/field-joins${queryParams}`); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } export async function createFieldJoin(data: Partial): Promise> { try { const response = await apiClient.post("/screen-groups/field-joins", data); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } export async function updateFieldJoin(id: number, data: Partial): Promise> { try { const response = await apiClient.put(`/screen-groups/field-joins/${id}`, data); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } export async function deleteFieldJoin(id: number): Promise> { try { const response = await apiClient.delete(`/screen-groups/field-joins/${id}`); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } // ============================================================ // 데이터 흐름 (screen_data_flows) API // ============================================================ export async function getDataFlows(params?: { groupId?: number; sourceScreenId?: number }): Promise> { try { const queryParts: string[] = []; if (params?.groupId) { queryParts.push(`group_id=${params.groupId}`); } if (params?.sourceScreenId) { queryParts.push(`source_screen_id=${params.sourceScreenId}`); } const queryString = queryParts.length > 0 ? `?${queryParts.join("&")}` : ""; const response = await apiClient.get(`/screen-groups/data-flows${queryString}`); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } export async function createDataFlow(data: Partial): Promise> { try { const response = await apiClient.post("/screen-groups/data-flows", data); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } export async function updateDataFlow(id: number, data: Partial): Promise> { try { const response = await apiClient.put(`/screen-groups/data-flows/${id}`, data); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } export async function deleteDataFlow(id: number): Promise> { try { const response = await apiClient.delete(`/screen-groups/data-flows/${id}`); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } // ============================================================ // 화면-테이블 관계 (screen_table_relations) API // ============================================================ export async function getTableRelations(params?: { screen_id?: number; group_id?: number; }): Promise> { try { const queryParams = new URLSearchParams(); if (params?.screen_id) queryParams.append("screen_id", params.screen_id.toString()); if (params?.group_id) queryParams.append("group_id", params.group_id.toString()); const response = await apiClient.get(`/screen-groups/table-relations?${queryParams.toString()}`); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } export async function createTableRelation(data: Partial): Promise> { try { const response = await apiClient.post("/screen-groups/table-relations", data); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } export async function updateTableRelation(id: number, data: Partial): Promise> { try { const response = await apiClient.put(`/screen-groups/table-relations/${id}`, data); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } export async function deleteTableRelation(id: number): Promise> { try { const response = await apiClient.delete(`/screen-groups/table-relations/${id}`); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } // ============================================================ // 화면 레이아웃 요약 (미리보기용) API // ============================================================ // 레이아웃 아이템 (미니어처 렌더링용) export interface LayoutItem { x: number; y: number; width: number; height: number; componentKind: string; // 정확한 컴포넌트 종류 (table-list, button-primary 등) widgetType: string; // 일반적인 위젯 타입 (button, text 등) label?: string; bindField?: string; // 바인딩된 필드명 (컬럼명) usedColumns?: string[]; // 이 컴포넌트에서 사용하는 컬럼 목록 joinColumns?: string[]; // 이 컴포넌트에서 조인 컬럼 목록 (isEntityJoin=true) } export interface ScreenLayoutSummary { screenId: number; screenType: 'form' | 'grid' | 'dashboard' | 'action'; widgetCounts: Record; totalComponents: number; // 미니어처 렌더링용 레이아웃 데이터 layoutItems: LayoutItem[]; canvasWidth: number; canvasHeight: number; } // 단일 화면 레이아웃 요약 조회 export async function getScreenLayoutSummary(screenId: number): Promise> { try { const response = await apiClient.get(`/screen-groups/layout-summary/${screenId}`); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } // 여러 화면 레이아웃 요약 일괄 조회 export async function getMultipleScreenLayoutSummary( screenIds: number[] ): Promise>> { try { const response = await apiClient.post("/screen-groups/layout-summary/batch", { screenIds }); return response.data; } catch (error: any) { return { success: false, error: error.message }; } } // 필드 매핑 정보 타입 export interface FieldMappingInfo { sourceTable?: string; // 연관 테이블명 (parentDataMapping에서 사용) sourceField: string; targetField: string; sourceDisplayName?: string; // 메인 테이블 한글 컬럼명 targetDisplayName?: string; // 서브 테이블 한글 컬럼명 } // 서브 테이블 정보 타입 export interface SubTableInfo { tableName: string; tableLabel?: string; // 테이블 한글명 componentType: string; relationType: 'lookup' | 'source' | 'join' | 'reference' | 'parentMapping' | 'rightPanelRelation'; fieldMappings?: FieldMappingInfo[]; filterColumns?: string[]; // 필터링에 사용되는 컬럼 목록 // rightPanelRelation에서 추가 정보 (관계 유형 추론용) originalRelationType?: 'join' | 'detail'; // 원본 relation.type foreignKey?: string; // 디테일 테이블의 FK 컬럼 leftColumn?: string; // 마스터 테이블의 선택 기준 컬럼 // rightPanel.columns에서 외부 테이블 참조 정보 joinedTables?: string[]; // 참조하는 외부 테이블들 (예: ['customer_mng']) joinColumns?: string[]; // 외부 테이블과 조인하는 FK 컬럼들 (예: ['customer_id']) joinColumnRefs?: Array<{ // FK 컬럼 참조 정보 (어떤 테이블.컬럼에서 오는지) column: string; // FK 컬럼명 (예: 'customer_id') columnLabel: string; // FK 컬럼 한글명 (예: '거래처 ID') refTable: string; // 참조 테이블 (예: 'customer_mng') refTableLabel: string; // 참조 테이블 한글명 (예: '거래처 관리') refColumn: string; // 참조 컬럼 (예: 'customer_code') }>; } // 시각적 관계 유형 (시각화에서 사용) export type VisualRelationType = 'filter' | 'hierarchy' | 'lookup' | 'mapping' | 'join'; // 관계 유형 추론 함수 export function inferVisualRelationType(subTable: SubTableInfo): VisualRelationType { // 1. split-panel-layout의 rightPanel.relation if (subTable.relationType === 'rightPanelRelation') { // 원본 relation.type 기반 구분 if (subTable.originalRelationType === 'detail') { return 'hierarchy'; // 부모-자식 계층 구조 (같은 테이블 자기 참조) } return 'filter'; // 마스터-디테일 필터링 } // 2. selected-items-detail-input의 parentDataMapping // parentDataMapping은 FK 관계를 정의하므로 조인으로 분류 if (subTable.relationType === 'parentMapping') { return 'join'; // FK 조인 (sourceTable.sourceField → targetTable.targetField) } // 3. column_labels.reference_table if (subTable.relationType === 'reference') { return 'join'; // 실제 엔티티 조인 (LEFT JOIN 등) } // 4. autocomplete, entity-search if (subTable.relationType === 'lookup') { return 'lookup'; // 코드→명칭 변환 } // 5. 기타 (source, join 등) return 'join'; } // 저장 테이블 정보 타입 export interface SaveTableInfo { tableName: string; saveType: 'save' | 'edit' | 'delete' | 'transferData'; componentType: string; isMainTable: boolean; mappingRules?: Array<{ sourceField: string; targetField: string; transform?: string; }>; } export interface ScreenSubTablesData { screenId: number; screenName: string; mainTable: string; subTables: SubTableInfo[]; saveTables?: SaveTableInfo[]; // 저장 대상 테이블 목록 } // 여러 화면의 서브 테이블 정보 조회 (메인 테이블 → 서브 테이블 관계) export async function getScreenSubTables( screenIds: number[] ): Promise>> { try { const response = await apiClient.post("/screen-groups/sub-tables/batch", { screenIds }); return response.data; } catch (error: any) { return { success: false, error: error.message }; } }