501 lines
16 KiB
TypeScript
501 lines
16 KiB
TypeScript
/**
|
|
* 화면 그룹 관리 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<string, any>;
|
|
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<T> {
|
|
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<ApiResponse<ScreenGroup[]>> {
|
|
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<ApiResponse<ScreenGroup>> {
|
|
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<ScreenGroup>): Promise<ApiResponse<ScreenGroup>> {
|
|
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<ScreenGroup>): Promise<ApiResponse<ScreenGroup>> {
|
|
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<ApiResponse<void>> {
|
|
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<ApiResponse<ScreenGroupScreen>> {
|
|
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<ApiResponse<ScreenGroupScreen>> {
|
|
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<ApiResponse<void>> {
|
|
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<ApiResponse<FieldJoin[]>> {
|
|
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<FieldJoin>): Promise<ApiResponse<FieldJoin>> {
|
|
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<FieldJoin>): Promise<ApiResponse<FieldJoin>> {
|
|
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<ApiResponse<void>> {
|
|
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<ApiResponse<DataFlow[]>> {
|
|
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<DataFlow>): Promise<ApiResponse<DataFlow>> {
|
|
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<DataFlow>): Promise<ApiResponse<DataFlow>> {
|
|
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<ApiResponse<void>> {
|
|
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<ApiResponse<TableRelation[]>> {
|
|
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<TableRelation>): Promise<ApiResponse<TableRelation>> {
|
|
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<TableRelation>): Promise<ApiResponse<TableRelation>> {
|
|
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<ApiResponse<void>> {
|
|
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<string, number>;
|
|
totalComponents: number;
|
|
// 미니어처 렌더링용 레이아웃 데이터
|
|
layoutItems: LayoutItem[];
|
|
canvasWidth: number;
|
|
canvasHeight: number;
|
|
}
|
|
|
|
// 단일 화면 레이아웃 요약 조회
|
|
export async function getScreenLayoutSummary(screenId: number): Promise<ApiResponse<ScreenLayoutSummary>> {
|
|
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<ApiResponse<Record<number, ScreenLayoutSummary>>> {
|
|
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<ApiResponse<Record<number, ScreenSubTablesData>>> {
|
|
try {
|
|
const response = await apiClient.post("/screen-groups/sub-tables/batch", { screenIds });
|
|
return response.data;
|
|
} catch (error: any) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|