ERP-node/frontend/types/screen-management.ts

1099 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 🖥️ 화면관리 시스템 전용 타입 정의
*
* 화면 설계, 컴포넌트 관리, 레이아웃 등 화면관리 시스템에서만 사용하는 타입들
*/
import {
ComponentType,
WebType,
DynamicWebType,
Position,
Size,
CommonStyle,
ValidationRule,
TimestampFields,
CompanyCode,
ActiveStatus,
isWebType,
} from "./unified-core";
import { ColumnSpanPreset } from "@/lib/constants/columnSpans";
import { ResponsiveComponentConfig } from "./responsive";
// ===== 기본 컴포넌트 인터페이스 =====
/**
* 모든 컴포넌트의 기본 인터페이스
*/
export interface BaseComponent {
id: string;
type: ComponentType;
// 🔄 레거시 위치/크기 (단계적 제거 예정)
position: Position; // y 좌표는 유지 (행 정렬용)
size: Size; // height만 사용
// 🆕 그리드 시스템 속성
gridColumnSpan?: ColumnSpanPreset; // 컬럼 너비
gridColumnStart?: number; // 시작 컬럼 (1-12)
gridRowIndex?: number; // 행 인덱스
parentId?: string;
label?: string;
required?: boolean;
readonly?: boolean;
style?: ComponentStyle;
className?: string;
// 새 컴포넌트 시스템에서 필요한 속성들
gridColumns?: number; // 🔄 deprecated - gridColumnSpan 사용
zoneId?: string; // 레이아웃 존 ID
componentConfig?: any; // 컴포넌트별 설정
componentType?: string; // 새 컴포넌트 시스템의 ID
webTypeConfig?: WebTypeConfig; // 웹타입별 설정
// 반응형 설정
responsiveConfig?: ResponsiveComponentConfig;
responsiveDisplay?: any; // 런타임에 추가되는 임시 필드
// 조건부 표시 설정
conditional?: {
enabled: boolean;
field: string;
operator: "=" | "!=" | ">" | "<" | "in" | "notIn" | "isEmpty" | "isNotEmpty";
value: unknown;
action: "show" | "hide" | "enable" | "disable";
};
// 자동 입력 설정
autoFill?: {
enabled: boolean;
sourceTable: string;
filterColumn: string;
userField: "companyCode" | "userId" | "deptCode";
displayColumn: string;
};
}
/**
* 화면관리용 확장 스타일 (CommonStyle 기반)
*/
export interface ComponentStyle extends CommonStyle {
// 화면관리 전용 스타일 확장 가능
}
/**
* 위젯 컴포넌트 (입력 요소)
*/
export interface WidgetComponent extends BaseComponent {
type: "widget";
widgetType: DynamicWebType;
placeholder?: string;
columnName?: string;
webTypeConfig?: WebTypeConfig;
validationRules?: ValidationRule[];
// 웹타입별 추가 설정
dateConfig?: DateTypeConfig;
numberConfig?: NumberTypeConfig;
selectConfig?: SelectTypeConfig;
textConfig?: TextTypeConfig;
fileConfig?: FileTypeConfig;
entityConfig?: EntityTypeConfig;
buttonConfig?: ButtonTypeConfig;
arrayConfig?: ArrayTypeConfig;
// 🆕 자동 입력 설정 (테이블 조회 기반)
autoFill?: {
enabled: boolean; // 자동 입력 활성화
sourceTable: string; // 조회할 테이블 (예: company_mng)
filterColumn: string; // 필터링할 컬럼 (예: company_code)
userField: 'companyCode' | 'userId' | 'deptCode'; // 사용자 정보 필드
displayColumn: string; // 표시할 컬럼 (예: company_name)
};
}
/**
* 컨테이너 컴포넌트 (레이아웃)
*/
export interface ContainerComponent extends BaseComponent {
type: "container" | "row" | "column" | "area";
children?: string[]; // 자식 컴포넌트 ID 배열
layoutDirection?: "horizontal" | "vertical";
justifyContent?: "start" | "center" | "end" | "space-between" | "space-around";
alignItems?: "start" | "center" | "end" | "stretch";
gap?: number;
}
/**
* 그룹 컴포넌트 (논리적 그룹핑)
*/
export interface GroupComponent extends BaseComponent {
type: "group";
groupName: string;
children: string[]; // 그룹에 속한 컴포넌트 ID 배열
isCollapsible?: boolean;
isCollapsed?: boolean;
}
/**
* 데이터 테이블 컴포넌트
*/
export interface DataTableComponent extends BaseComponent {
type: "datatable";
tableName?: string;
columns: DataTableColumn[];
pagination?: boolean;
pageSize?: number;
searchable?: boolean;
sortable?: boolean;
filters?: DataTableFilter[];
// 🆕 현재 사용자 정보로 자동 필터링
autoFilter?: {
enabled: boolean; // 자동 필터 활성화 여부
filterColumn: string; // 필터링할 테이블 컬럼 (예: company_code, dept_code)
userField: 'companyCode' | 'userId' | 'deptCode'; // 사용자 정보에서 가져올 필드
};
// 🆕 컬럼 값 기반 데이터 필터링
dataFilter?: DataFilterConfig;
}
/**
* 파일 업로드 컴포넌트
*/
export interface FileComponent extends BaseComponent {
type: "file";
fileConfig: FileTypeConfig;
uploadedFiles?: UploadedFile[];
columnName?: string;
tableName?: string;
lastFileUpdate?: number;
}
/**
* 플로우 스텝별 컬럼 표시 설정
*/
export interface FlowStepColumnConfig {
selectedColumns: string[]; // 표시할 컬럼 목록
columnOrder?: string[]; // 컬럼 순서 (선택사항)
}
/**
* 플로우 컴포넌트
*/
export interface FlowComponent extends BaseComponent {
type: "flow";
flowId?: number; // 선택된 플로우 ID
flowName?: string; // 플로우 이름 (표시용)
showStepCount?: boolean; // 각 스텝의 데이터 건수 표시 여부
allowDataMove?: boolean; // 데이터 이동 허용 여부
displayMode?: "horizontal" | "vertical"; // 플로우 표시 방향
// 🆕 단계별 컬럼 오버라이드 설정 (화면관리에서 설정)
stepColumnConfig?: {
[stepId: number]: FlowStepColumnConfig;
};
// 🆕 컬럼 값 기반 데이터 필터링
dataFilter?: DataFilterConfig;
}
/**
* 새로운 컴포넌트 시스템 컴포넌트
*/
export interface ComponentComponent extends BaseComponent {
type: "component";
widgetType: WebType; // 웹타입 (기존 호환성)
componentType: string; // 새 컴포넌트 시스템의 ID
componentConfig: any; // 컴포넌트별 설정
}
/**
* 탭 아이템 인터페이스
*/
export interface TabItem {
id: string;
label: string;
screenId?: number; // 연결된 화면 ID
screenName?: string; // 화면 이름 (표시용)
icon?: string; // 아이콘 (선택사항)
disabled?: boolean; // 비활성화 여부
order: number; // 탭 순서
}
/**
* 탭 컴포넌트
*/
export interface TabsComponent extends BaseComponent {
type: "tabs";
tabs: TabItem[]; // 탭 목록
defaultTab?: string; // 기본 선택 탭 ID
orientation?: "horizontal" | "vertical"; // 탭 방향
variant?: "default" | "pills" | "underline"; // 탭 스타일
allowCloseable?: boolean; // 탭 닫기 버튼 표시 여부
persistSelection?: boolean; // 선택 상태 유지 (localStorage)
}
/**
* 통합 컴포넌트 데이터 타입
*/
export type ComponentData =
| WidgetComponent
| ContainerComponent
| GroupComponent
| DataTableComponent
| FileComponent
| FlowComponent
| ComponentComponent
| TabsComponent;
// ===== 웹타입별 설정 인터페이스 =====
/**
* 기본 웹타입 설정
*/
export interface WebTypeConfig {
[key: string]: unknown;
}
/**
* 날짜/시간 타입 설정
*/
export interface DateTypeConfig {
format: "YYYY-MM-DD" | "YYYY-MM-DD HH:mm" | "YYYY-MM-DD HH:mm:ss";
showTime: boolean;
minDate?: string;
maxDate?: string;
defaultValue?: string;
placeholder?: string;
}
/**
* 숫자 타입 설정
*/
export interface NumberTypeConfig {
min?: number;
max?: number;
step?: number;
format?: "integer" | "decimal" | "currency" | "percentage";
decimalPlaces?: number;
thousandSeparator?: boolean;
placeholder?: string;
}
/**
* 선택박스 타입 설정
*/
export interface SelectTypeConfig {
options: Array<{ label: string; value: string; disabled?: boolean }>;
multiple?: boolean;
searchable?: boolean;
placeholder?: string;
allowCustomValue?: boolean;
defaultValue?: string;
required?: boolean;
readonly?: boolean;
emptyMessage?: string;
/** 🆕 연쇄 드롭다운 관계 코드 (관계 관리에서 정의한 코드) */
cascadingRelationCode?: string;
/** 🆕 연쇄 드롭다운 부모 필드명 (화면 내 다른 필드의 columnName) */
cascadingParentField?: string;
/** @deprecated 직접 설정 방식 - cascadingRelationCode 사용 권장 */
cascading?: CascadingDropdownConfig;
}
/**
* 텍스트 타입 설정
*/
export interface TextTypeConfig {
minLength?: number;
maxLength?: number;
pattern?: string;
format?: "none" | "email" | "phone" | "url" | "korean" | "english";
placeholder?: string;
defaultValue?: string;
multiline?: boolean;
rows?: number;
// 자동입력 관련 설정
autoInput?: boolean;
autoValueType?:
| "current_datetime"
| "current_date"
| "current_time"
| "current_user"
| "uuid"
| "sequence"
| "numbering_rule"
| "custom";
customValue?: string;
numberingRuleId?: string; // 채번 규칙 ID
}
/**
* 배열(다중 입력) 타입 설정
*/
export interface ArrayTypeConfig {
itemType?: "text" | "number" | "email" | "tel"; // 각 항목의 입력 타입
minItems?: number; // 최소 항목 수
maxItems?: number; // 최대 항목 수
placeholder?: string; // 입력 필드 placeholder
addButtonText?: string; // + 버튼 텍스트
removeButtonText?: string; // - 버튼 텍스트 (보통 아이콘)
allowReorder?: boolean; // 순서 변경 가능 여부
showIndex?: boolean; // 인덱스 번호 표시 여부
}
/**
* 파일 타입 설정
*/
export interface FileTypeConfig {
accept?: string[];
multiple?: boolean;
maxSize?: number; // MB
maxFiles?: number;
showPreview?: boolean;
showProgress?: boolean;
docType?: string;
docTypeName?: string;
dragDropText?: string;
uploadButtonText?: string;
autoUpload?: boolean;
chunkedUpload?: boolean;
linkedTable?: string;
linkedField?: string;
autoLink?: boolean;
companyCode?: CompanyCode;
}
/**
* 엔티티 타입 설정
*/
export interface EntityTypeConfig {
referenceTable: string;
referenceColumn: string;
displayColumns: string[]; // 여러 표시 컬럼을 배열로 변경
displayColumn?: string; // 하위 호환성을 위해 유지 (deprecated)
searchColumns?: string[];
filters?: Record<string, unknown>;
placeholder?: string;
displayFormat?: "simple" | "detailed" | "custom"; // 표시 형식
separator?: string; // 여러 컬럼 표시 시 구분자 (기본: ' - ')
// UI 모드
uiMode?: "select" | "modal" | "combo" | "autocomplete"; // 기본: "combo"
}
/**
* 🆕 연쇄 드롭다운(Cascading Dropdown) 설정
*
* 부모 필드의 값에 따라 자식 드롭다운의 옵션이 동적으로 변경됩니다.
* 예: 창고 선택 → 해당 창고의 위치만 표시
*
* @example
* // 창고 → 위치 연쇄 드롭다운
* {
* enabled: true,
* parentField: "warehouse_code",
* sourceTable: "warehouse_location",
* parentKeyColumn: "warehouse_id",
* valueColumn: "location_code",
* labelColumn: "location_name",
* }
*/
export interface CascadingDropdownConfig {
/** 연쇄 드롭다운 활성화 여부 */
enabled: boolean;
/** 부모 필드명 (이 필드의 값에 따라 옵션이 필터링됨) */
parentField: string;
/** 옵션을 조회할 테이블명 */
sourceTable: string;
/** 부모 값과 매칭할 컬럼명 (sourceTable의 컬럼) */
parentKeyColumn: string;
/** 드롭다운 value로 사용할 컬럼명 */
valueColumn: string;
/** 드롭다운 label로 표시할 컬럼명 */
labelColumn: string;
/** 추가 필터 조건 (선택사항) */
additionalFilters?: Record<string, unknown>;
/** 부모 값이 없을 때 표시할 메시지 */
emptyParentMessage?: string;
/** 옵션이 없을 때 표시할 메시지 */
noOptionsMessage?: string;
/** 로딩 중 표시할 메시지 */
loadingMessage?: string;
/** 부모 값 변경 시 자동으로 값 초기화 */
clearOnParentChange?: boolean;
}
/**
* 버튼 타입 설정
*/
export interface ButtonTypeConfig {
text?: string;
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
size?: "sm" | "md" | "lg";
icon?: string;
// ButtonActionType과 관련된 설정은 control-management.ts에서 정의
}
// ===== 즉시 저장(quickInsert) 설정 =====
/**
* 즉시 저장 컬럼 매핑 설정
* 저장할 테이블의 각 컬럼에 대해 값을 어디서 가져올지 정의
*/
export interface QuickInsertColumnMapping {
/** 저장할 테이블의 대상 컬럼명 */
targetColumn: string;
/** 값 소스 타입 */
sourceType: "component" | "leftPanel" | "fixed" | "currentUser";
// sourceType별 추가 설정
/** component: 값을 가져올 컴포넌트 ID */
sourceComponentId?: string;
/** component: 컴포넌트의 columnName (formData 접근용) */
sourceColumnName?: string;
/** leftPanel: 좌측 선택 데이터의 컬럼명 */
sourceColumn?: string;
/** fixed: 고정값 */
fixedValue?: any;
/** currentUser: 사용자 정보 필드 */
userField?: "userId" | "userName" | "companyCode" | "deptCode";
}
/**
* 즉시 저장 후 동작 설정
*/
export interface QuickInsertAfterAction {
/** 데이터 새로고침 (테이블리스트, 카드 디스플레이 컴포넌트) */
refreshData?: boolean;
/** 초기화할 컴포넌트 ID 목록 */
clearComponents?: string[];
/** 성공 메시지 표시 여부 */
showSuccessMessage?: boolean;
/** 커스텀 성공 메시지 */
successMessage?: string;
}
/**
* 중복 체크 설정
*/
export interface QuickInsertDuplicateCheck {
/** 중복 체크 활성화 */
enabled: boolean;
/** 중복 체크할 컬럼들 */
columns: string[];
/** 중복 시 에러 메시지 */
errorMessage?: string;
}
/**
* 즉시 저장(quickInsert) 버튼 액션 설정
*
* 화면에서 entity 타입 선택박스로 데이터를 선택한 후,
* 버튼 클릭 시 특정 테이블에 즉시 INSERT하는 기능
*
* @example
* ```typescript
* const config: QuickInsertConfig = {
* targetTable: "process_equipment",
* columnMappings: [
* {
* targetColumn: "equipment_code",
* sourceType: "component",
* sourceComponentId: "equipment-select"
* },
* {
* targetColumn: "process_code",
* sourceType: "leftPanel",
* sourceColumn: "process_code"
* }
* ],
* afterInsert: {
* refreshData: true,
* clearComponents: ["equipment-select"],
* showSuccessMessage: true
* }
* };
* ```
*/
export interface QuickInsertConfig {
/** 저장할 대상 테이블명 */
targetTable: string;
/** 컬럼 매핑 설정 */
columnMappings: QuickInsertColumnMapping[];
/** 저장 후 동작 설정 */
afterInsert?: QuickInsertAfterAction;
/** 중복 체크 설정 (선택사항) */
duplicateCheck?: QuickInsertDuplicateCheck;
}
/**
* 플로우 단계별 버튼 표시 설정
*
* 플로우 위젯과 버튼을 함께 사용할 때, 특정 플로우 단계에서만 버튼을 표시하거나 숨길 수 있습니다.
*/
export interface FlowVisibilityConfig {
/**
* 플로우 단계별 표시 제어 활성화 여부
*/
enabled: boolean;
/**
* 대상 플로우 컴포넌트 ID
* 화면에 여러 플로우 위젯이 있을 경우, 어떤 플로우에 반응할지 지정
*/
targetFlowComponentId: string;
/**
* 대상 플로우 정의 ID (선택사항, 검증용)
*/
targetFlowId?: number;
/**
* 대상 플로우 이름 (표시용)
*/
targetFlowName?: string;
/**
* 표시 조건 모드
* - whitelist: visibleSteps에 포함된 단계에서만 표시
* - blacklist: hiddenSteps에 포함된 단계에서 숨김
* - all: 모든 단계에서 표시 (기본값)
*/
mode: "whitelist" | "blacklist" | "all";
/**
* 표시할 단계 ID 목록 (mode="whitelist"일 때 사용)
*/
visibleSteps?: number[];
/**
* 숨길 단계 ID 목록 (mode="blacklist"일 때 사용)
*/
hiddenSteps?: number[];
/**
* 레이아웃 동작 방식
* - preserve-position: 원래 위치 유지 (display: none, 빈 공간 유지)
* - auto-compact: 빈 공간 자동 제거 (Flexbox 그룹으로 자동 정렬)
*/
layoutBehavior: "preserve-position" | "auto-compact";
/**
* 그룹 ID (auto-compact 모드일 때 사용)
* 같은 그룹 ID를 가진 버튼들이 하나의 FlowButtonGroup으로 묶임
*/
groupId?: string;
/**
* 그룹 정렬 방향 (auto-compact 모드일 때 사용)
*/
groupDirection?: "horizontal" | "vertical";
/**
* 그룹 내 버튼 간격 (px, auto-compact 모드일 때 사용)
*/
groupGap?: number;
/**
* 그룹 정렬 방식 (auto-compact 모드일 때 사용)
*/
groupAlign?: "start" | "center" | "end" | "space-between" | "space-around";
}
// ===== 데이터 테이블 관련 =====
/**
* 데이터 테이블 컬럼
*/
export interface DataTableColumn {
id: string;
columnName: string;
label: string;
dataType?: string;
widgetType?: DynamicWebType;
width?: number;
sortable?: boolean;
searchable?: boolean;
visible: boolean;
frozen?: boolean;
align?: "left" | "center" | "right";
}
/**
* 데이터 테이블 필터
*/
export interface DataTableFilter {
id: string;
columnName: string;
operator: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE" | "IN";
value: unknown;
logicalOperator?: "AND" | "OR";
}
/**
* 컬럼 필터 조건 (단일 필터)
*/
export interface ColumnFilter {
id: string;
columnName: string; // 필터링할 컬럼명
operator:
| "equals"
| "not_equals"
| "in"
| "not_in"
| "contains"
| "starts_with"
| "ends_with"
| "is_null"
| "is_not_null"
| "greater_than"
| "less_than"
| "greater_than_or_equal"
| "less_than_or_equal"
| "between"
| "date_range_contains"; // 날짜 범위 포함 (start_date <= value <= end_date)
value: string | string[]; // 필터 값 (in/not_in은 배열, date_range_contains는 비교할 날짜)
valueType: "static" | "category" | "code" | "dynamic"; // 값 타입 (dynamic: 현재 날짜 등)
// date_range_contains 전용 설정
rangeConfig?: {
startColumn: string; // 시작일 컬럼명
endColumn: string; // 종료일 컬럼명
};
}
/**
* 데이터 필터 설정 (여러 필터의 조합)
*/
export interface DataFilterConfig {
enabled: boolean; // 필터 활성화 여부
filters: ColumnFilter[]; // 필터 조건 목록
matchType: "all" | "any"; // AND(모두 만족) / OR(하나 이상 만족)
}
// ===== 파일 업로드 관련 =====
/**
* 업로드된 파일 정보
*/
export interface UploadedFile {
objid: string;
realFileName: string;
savedFileName: string;
fileSize: number;
fileExt: string;
filePath: string;
docType?: string;
docTypeName?: string;
targetObjid: string;
parentTargetObjid?: string;
writer?: string;
regdate?: string;
status?: "uploading" | "completed" | "error";
companyCode?: CompanyCode;
}
// ===== 화면 정의 관련 =====
/**
* 화면 정의
*/
export interface ScreenDefinition {
screenId: number;
screenName: string;
screenCode: string;
tableName: string;
tableLabel?: string;
companyCode: CompanyCode;
description?: string;
isActive: ActiveStatus;
createdDate: Date;
updatedDate: Date;
createdBy?: string;
updatedBy?: string;
dbSourceType?: "internal" | "external";
dbConnectionId?: number;
// REST API 관련 필드
dataSourceType?: "database" | "restapi";
restApiConnectionId?: number;
restApiEndpoint?: string;
restApiJsonPath?: string;
}
/**
* 화면 생성 요청
*/
export interface CreateScreenRequest {
screenName: string;
screenCode?: string;
tableName: string;
tableLabel?: string;
companyCode: CompanyCode;
description?: string;
dbSourceType?: "internal" | "external";
dbConnectionId?: number;
// REST API 관련 필드
dataSourceType?: "database" | "restapi";
restApiConnectionId?: number;
restApiEndpoint?: string;
restApiJsonPath?: string;
}
/**
* 화면 수정 요청
*/
export interface UpdateScreenRequest {
screenName?: string;
screenCode?: string;
tableName?: string;
tableLabel?: string;
description?: string;
isActive?: ActiveStatus;
}
/**
* 화면 해상도 설정
*/
export interface ScreenResolution {
width: number;
height: number;
name: string;
category: "desktop" | "tablet" | "mobile" | "custom";
}
/**
* 미리 정의된 해상도 프리셋
*/
export const SCREEN_RESOLUTIONS: ScreenResolution[] = [
// Desktop
{ width: 1920, height: 1080, name: "Full HD (1920×1080)", category: "desktop" },
{ width: 1366, height: 768, name: "HD (1366×768)", category: "desktop" },
{ width: 1440, height: 900, name: "WXGA+ (1440×900)", category: "desktop" },
{ width: 1280, height: 1024, name: "SXGA (1280×1024)", category: "desktop" },
// Tablet
{ width: 1024, height: 768, name: "iPad Landscape (1024×768)", category: "tablet" },
{ width: 768, height: 1024, name: "iPad Portrait (768×1024)", category: "tablet" },
{ width: 1112, height: 834, name: 'iPad Pro 10.5" Landscape', category: "tablet" },
{ width: 834, height: 1112, name: 'iPad Pro 10.5" Portrait', category: "tablet" },
// Mobile
{ width: 375, height: 667, name: "iPhone 8 (375×667)", category: "mobile" },
{ width: 414, height: 896, name: "iPhone 11 (414×896)", category: "mobile" },
{ width: 390, height: 844, name: "iPhone 12/13 (390×844)", category: "mobile" },
{ width: 360, height: 640, name: "Android Medium (360×640)", category: "mobile" },
];
/**
* 그룹화 상태
*/
export interface GroupState {
isGrouping: boolean;
selectedComponents: string[];
groupTarget?: string | null;
groupMode?: "create" | "add" | "remove" | "ungroup";
groupTitle?: string;
}
/**
* 레이아웃 데이터
*/
export interface LayoutData {
screenId: number;
components: ComponentData[];
gridSettings?: GridSettings;
metadata?: LayoutMetadata;
screenResolution?: ScreenResolution;
}
/**
* 격자 설정
*/
export interface GridSettings {
enabled: boolean;
size: number;
color: string;
opacity: number;
snapToGrid: boolean;
// gridUtils에서 필요한 속성들 추가
columns: number;
gap: number;
padding: number;
showGrid?: boolean;
gridColor?: string;
gridOpacity?: number;
}
/**
* 레이아웃 메타데이터
*/
export interface LayoutMetadata {
version: string;
lastModified: Date;
modifiedBy: string;
description?: string;
tags?: string[];
}
// ===== 템플릿 관련 =====
/**
* 화면 템플릿
*/
export interface ScreenTemplate {
id: string;
name: string;
description?: string;
category: string;
components: ComponentData[];
previewImage?: string;
isActive: boolean;
}
/**
* 템플릿 컴포넌트 (템플릿 패널에서 사용)
*/
export interface TemplateComponent {
id: string;
name: string;
description?: string;
icon?: string;
category: string;
defaultProps: Partial<ComponentData>;
children?: Array<{
id: string;
name: string;
defaultProps: Partial<ComponentData>;
}>;
}
// ===== 타입 가드 함수들 =====
/**
* WidgetComponent 타입 가드 (강화된 검증)
*/
export const isWidgetComponent = (component: ComponentData): component is WidgetComponent => {
if (!component || typeof component !== "object") {
return false;
}
// 기본 타입 체크
if (component.type !== "widget") {
return false;
}
// 필수 필드 존재 여부 체크
if (!component.id || typeof component.id !== "string") {
return false;
}
// widgetType이 유효한 WebType인지 체크
if (!component.widgetType || !isWebType(component.widgetType)) {
return false;
}
// position 검증
if (
!component.position ||
typeof component.position.x !== "number" ||
typeof component.position.y !== "number" ||
!Number.isFinite(component.position.x) ||
!Number.isFinite(component.position.y)
) {
return false;
}
// size 검증
if (
!component.size ||
typeof component.size.width !== "number" ||
typeof component.size.height !== "number" ||
!Number.isFinite(component.size.width) ||
!Number.isFinite(component.size.height) ||
component.size.width <= 0 ||
component.size.height <= 0
) {
return false;
}
return true;
};
/**
* ContainerComponent 타입 가드 (강화된 검증)
*/
export const isContainerComponent = (component: ComponentData): component is ContainerComponent => {
if (!component || typeof component !== "object") {
return false;
}
// 기본 타입 체크
if (!["container", "row", "column", "area"].includes(component.type)) {
return false;
}
// 필수 필드 존재 여부 체크
if (!component.id || typeof component.id !== "string") {
return false;
}
// position 검증
if (
!component.position ||
typeof component.position.x !== "number" ||
typeof component.position.y !== "number" ||
!Number.isFinite(component.position.x) ||
!Number.isFinite(component.position.y)
) {
return false;
}
// size 검증
if (
!component.size ||
typeof component.size.width !== "number" ||
typeof component.size.height !== "number" ||
!Number.isFinite(component.size.width) ||
!Number.isFinite(component.size.height) ||
component.size.width <= 0 ||
component.size.height <= 0
) {
return false;
}
return true;
};
/**
* GroupComponent 타입 가드
*/
export const isGroupComponent = (component: ComponentData): component is GroupComponent => {
return component.type === "group";
};
/**
* DataTableComponent 타입 가드
*/
export const isDataTableComponent = (component: ComponentData): component is DataTableComponent => {
return component.type === "datatable";
};
/**
* FileComponent 타입 가드
*/
export const isFileComponent = (component: ComponentData): component is FileComponent => {
return component.type === "file";
};
/**
* FlowComponent 타입 가드
*/
export const isFlowComponent = (component: ComponentData): component is FlowComponent => {
return component.type === "flow";
};
/**
* TabsComponent 타입 가드
*/
export const isTabsComponent = (component: ComponentData): component is TabsComponent => {
return component.type === "tabs";
};
// ===== 안전한 타입 캐스팅 유틸리티 =====
/**
* ComponentData를 WidgetComponent로 안전하게 캐스팅
*/
export const asWidgetComponent = (component: ComponentData): WidgetComponent => {
if (!isWidgetComponent(component)) {
throw new Error(`Expected WidgetComponent, got ${component.type}`);
}
return component;
};
/**
* ComponentData를 ContainerComponent로 안전하게 캐스팅
*/
export const asContainerComponent = (component: ComponentData): ContainerComponent => {
if (!isContainerComponent(component)) {
throw new Error(`Expected ContainerComponent, got ${component.type}`);
}
return component;
};
/**
* ComponentData를 GroupComponent로 안전하게 캐스팅
*/
export const asGroupComponent = (component: ComponentData): GroupComponent => {
if (!isGroupComponent(component)) {
throw new Error(`Expected GroupComponent, got ${component.type}`);
}
return component;
};
/**
* ComponentData를 DataTableComponent로 안전하게 캐스팅
*/
export const asDataTableComponent = (component: ComponentData): DataTableComponent => {
if (!isDataTableComponent(component)) {
throw new Error(`Expected DataTableComponent, got ${component.type}`);
}
return component;
};
/**
* ComponentData를 FileComponent로 안전하게 캐스팅
*/
export const asFileComponent = (component: ComponentData): FileComponent => {
if (!isFileComponent(component)) {
throw new Error(`Expected FileComponent, got ${component.type}`);
}
return component;
};
/**
* ComponentData를 FlowComponent로 안전하게 캐스팅
*/
export const asFlowComponent = (component: ComponentData): FlowComponent => {
if (!isFlowComponent(component)) {
throw new Error(`Expected FlowComponent, got ${component.type}`);
}
return component;
};
/**
* ComponentData를 TabsComponent로 안전하게 캐스팅
*/
export const asTabsComponent = (component: ComponentData): TabsComponent => {
if (!isTabsComponent(component)) {
throw new Error(`Expected TabsComponent, got ${component.type}`);
}
return component;
};