ERP-node/frontend/lib/api/metaComponent.ts

349 lines
9.0 KiB
TypeScript
Raw Normal View History

2026-03-01 03:39:00 +09:00
/**
* V3 API
* - fetch() ! apiClient
*/
import { apiClient, ApiResponse } from "./client";
// ============================================
// TypeScript 인터페이스 (백엔드 응답과 동일)
// ============================================
export interface FieldConfig {
webType: string;
label: string;
placeholder?: string;
defaultValue?: any;
required: boolean;
maxLength?: number;
validation?: {
pattern?: string;
min?: number;
max?: number;
message?: string;
};
options?: { value: string; label: string }[];
join?: {
referenceTable: string;
referenceColumn: string;
displayColumn: string;
};
}
export interface TableColumn {
columnName: string;
webType: string;
label: string;
required: boolean;
maxLength?: number;
join?: {
referenceTable: string;
referenceColumn: string;
displayColumn: string;
};
}
export interface LayoutData {
version: "3.0";
screenId: number;
layerId: number;
components: MetaComponent[];
layers?: Layer[];
metadata?: {
lastModified?: string;
description?: string;
};
}
export interface MetaComponent {
id: string;
type: "meta-field" | "meta-dataview" | "meta-action" | "meta-layout" | "meta-display" | "meta-search" | "meta-modal";
position?: { x: number; y: number; width: number; height: number };
config:
| FieldComponentConfig
| DataViewComponentConfig
| ActionComponentConfig
| LayoutComponentConfig
| DisplayComponentConfig
| SearchComponentConfig
| ModalComponentConfig;
}
export interface FieldComponentConfig {
webType: string;
label: string;
binding: string; // 바인딩될 컬럼명
tableName?: string;
placeholder?: string;
required?: boolean;
defaultValue?: any;
validation?: FieldConfig["validation"];
join?: FieldConfig["join"];
options?: FieldConfig["options"];
}
export interface DataViewComponentConfig {
viewMode: "table" | "card" | "list" | "tree";
tableName: string;
columns: string[];
defaultSort?: { column: string; direction: "asc" | "desc" };
pageSize?: number;
actions?: {
create?: boolean;
read?: boolean;
update?: boolean;
delete?: boolean;
};
}
export interface ActionComponentConfig {
label: string;
buttonType: "primary" | "secondary" | "destructive" | "ghost";
icon?: string;
steps: ActionStep[];
confirmDialog?: {
title: string;
message: string;
};
}
export type ActionStep =
| { type: "save"; target: string }
| { type: "delete"; target: string }
| { type: "refresh"; target: string }
| { type: "navigate"; screenId: number }
| { type: "openModal"; modalId: string }
| { type: "api"; method: string; endpoint: string; body?: any };
// ============================================
// 새로운 메타 컴포넌트 타입 (Phase B)
// ============================================
export interface LayoutComponentConfig {
mode: "columns" | "rows" | "tabs" | "accordion" | "card";
areas?: { id: string; label?: string; size?: string; collapsible?: boolean }[];
tabs?: { id: string; label: string; icon?: string }[];
gap?: number;
padding?: number;
bordered?: boolean;
title?: string;
}
export interface DisplayComponentConfig {
displayType: "text" | "heading" | "divider" | "badge" | "alert" | "stat" | "spacer" | "progress";
text?: {
content: string;
size?: "xs" | "sm" | "md" | "lg" | "xl";
weight?: "normal" | "medium" | "semibold" | "bold";
align?: "left" | "center" | "right";
};
heading?: {
content: string;
level: 1 | 2 | 3 | 4 | 5 | 6;
};
badge?: {
text: string;
variant?: "default" | "secondary" | "destructive" | "outline";
};
alert?: {
title?: string;
message: string;
variant?: "default" | "destructive";
};
stat?: {
value: string;
label: string;
change?: string;
changeType?: "increase" | "decrease" | "neutral";
};
progress?: {
value: number;
max?: number;
label?: string;
};
spacer?: {
height: number;
};
dataBinding?: {
tableName?: string;
columnName?: string;
expression?: string;
};
}
export interface SearchComponentConfig {
targetDataView: string;
mode: "simple" | "advanced" | "combined";
fields?: {
columnName: string;
label: string;
searchType: "text" | "exact" | "range" | "select" | "date_range";
options?: { value: string; label: string }[];
}[];
quickFilters?: {
label: string;
filter: { column: string; operator: string; value: any };
}[];
}
export interface ModalComponentConfig {
trigger: "button" | "row_click" | "row_double_click" | "action";
triggerLabel?: string;
content: {
type: "form" | "screen" | "custom";
formConfig?: {
tableName: string;
mode: "create" | "edit" | "view";
columns: string[];
layout?: "single" | "two_column";
};
screenId?: number;
};
size?: "sm" | "md" | "lg" | "xl" | "full";
onClose?: ActionStep[];
}
export interface Layer {
id: number;
name: string;
visible: boolean;
order: number;
}
export interface ReactiveBinding {
id?: number;
sourceComponentId: string;
sourceEvent: "change" | "select" | "click" | "load";
sourceField?: string;
targetComponentId: string;
targetAction: "filter" | "setValue" | "show" | "hide" | "enable" | "disable" | "refresh";
targetField?: string;
transformConfig?: any;
conditionConfig?: any;
priority?: number;
}
// ============================================
// API 함수들
// ============================================
/**
* GET /api/meta/field-config/:tableName/:columnName
* - webType Field
*/
export async function getFieldConfig(
tableName: string,
columnName: string
): Promise<ApiResponse<FieldConfig>> {
try {
const response = await apiClient.get(`/meta/field-config/${tableName}/${columnName}`);
return response.data;
} catch (error: any) {
return {
success: false,
message: error.response?.data?.message || error.message || "Field 설정을 가져올 수 없습니다",
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* GET /api/meta/table-columns/:tableName
* - + webType + FK
*/
export async function getTableColumns(tableName: string): Promise<ApiResponse<TableColumn[]>> {
try {
const response = await apiClient.get(`/meta/table-columns/${tableName}`);
return response.data;
} catch (error: any) {
return {
success: false,
message: error.response?.data?.message || error.message || "테이블 컬럼 목록을 가져올 수 없습니다",
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* POST /api/meta/layout/save
* - (version "3.0", screen_layouts_v3 )
*/
export async function saveLayout(layoutData: LayoutData): Promise<ApiResponse<void>> {
try {
const response = await apiClient.post("/meta/layout/save", layoutData);
return response.data;
} catch (error: any) {
return {
success: false,
message: error.response?.data?.message || error.message || "레이아웃 저장에 실패했습니다",
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* GET /api/meta/layout/:screenId
* - (company_code )
*/
export async function getLayout(screenId: number, layerId: number = 1): Promise<ApiResponse<LayoutData>> {
try {
const response = await apiClient.get(`/meta/layout/${screenId}`, {
params: { layerId },
});
return response.data;
} catch (error: any) {
return {
success: false,
message: error.response?.data?.message || error.message || "레이아웃을 가져올 수 없습니다",
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* POST /api/meta/bindings/save
* - Reactive Bindings
*/
export async function saveBindings(
screenId: number,
layoutV3Id: number | null,
bindings: ReactiveBinding[]
): Promise<ApiResponse<void>> {
try {
const response = await apiClient.post("/meta/bindings/save", {
screenId,
layoutV3Id,
bindings,
});
return response.data;
} catch (error: any) {
return {
success: false,
message: error.response?.data?.message || error.message || "바인딩 저장에 실패했습니다",
errorCode: error.response?.data?.errorCode,
};
}
}
/**
* GET /api/meta/bindings/:screenId
* - Reactive Bindings (priority ASC )
*/
export async function getBindings(
screenId: number,
layoutV3Id: number | null = null
): Promise<ApiResponse<ReactiveBinding[]>> {
try {
const response = await apiClient.get(`/meta/bindings/${screenId}`, {
params: { layoutV3Id },
});
return response.data;
} catch (error: any) {
return {
success: false,
message: error.response?.data?.message || error.message || "바인딩을 가져올 수 없습니다",
errorCode: error.response?.data?.errorCode,
};
}
}