/** * 메타 컴포넌트 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> { 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> { 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> { 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> { 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> { 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> { 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, }; } }