2025-09-19 18:43:55 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 🖥️ 화면관리 시스템 전용 타입 정의
|
|
|
|
|
|
*
|
|
|
|
|
|
* 화면 설계, 컴포넌트 관리, 레이아웃 등 화면관리 시스템에서만 사용하는 타입들
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
|
ComponentType,
|
|
|
|
|
|
WebType,
|
|
|
|
|
|
DynamicWebType,
|
|
|
|
|
|
Position,
|
|
|
|
|
|
Size,
|
|
|
|
|
|
CommonStyle,
|
|
|
|
|
|
ValidationRule,
|
|
|
|
|
|
TimestampFields,
|
|
|
|
|
|
CompanyCode,
|
|
|
|
|
|
ActiveStatus,
|
|
|
|
|
|
isWebType,
|
|
|
|
|
|
} from "./unified-core";
|
2025-10-13 18:28:03 +09:00
|
|
|
|
import { ColumnSpanPreset } from "@/lib/constants/columnSpans";
|
2025-10-16 18:16:57 +09:00
|
|
|
|
import { ResponsiveComponentConfig } from "./responsive";
|
2025-09-19 18:43:55 +09:00
|
|
|
|
|
|
|
|
|
|
// ===== 기본 컴포넌트 인터페이스 =====
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 모든 컴포넌트의 기본 인터페이스
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface BaseComponent {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
type: ComponentType;
|
2025-10-13 18:28:03 +09:00
|
|
|
|
|
|
|
|
|
|
// 🔄 레거시 위치/크기 (단계적 제거 예정)
|
|
|
|
|
|
position: Position; // y 좌표는 유지 (행 정렬용)
|
|
|
|
|
|
size: Size; // height만 사용
|
|
|
|
|
|
|
|
|
|
|
|
// 🆕 그리드 시스템 속성
|
|
|
|
|
|
gridColumnSpan?: ColumnSpanPreset; // 컬럼 너비
|
|
|
|
|
|
gridColumnStart?: number; // 시작 컬럼 (1-12)
|
|
|
|
|
|
gridRowIndex?: number; // 행 인덱스
|
|
|
|
|
|
|
2025-09-19 18:43:55 +09:00
|
|
|
|
parentId?: string;
|
|
|
|
|
|
label?: string;
|
|
|
|
|
|
required?: boolean;
|
|
|
|
|
|
readonly?: boolean;
|
|
|
|
|
|
style?: ComponentStyle;
|
|
|
|
|
|
className?: string;
|
2025-10-13 18:28:03 +09:00
|
|
|
|
|
2025-09-25 16:22:02 +09:00
|
|
|
|
// 새 컴포넌트 시스템에서 필요한 속성들
|
2025-10-13 18:28:03 +09:00
|
|
|
|
gridColumns?: number; // 🔄 deprecated - gridColumnSpan 사용
|
2025-09-25 16:22:02 +09:00
|
|
|
|
zoneId?: string; // 레이아웃 존 ID
|
|
|
|
|
|
componentConfig?: any; // 컴포넌트별 설정
|
|
|
|
|
|
componentType?: string; // 새 컴포넌트 시스템의 ID
|
|
|
|
|
|
webTypeConfig?: WebTypeConfig; // 웹타입별 설정
|
2025-10-16 18:16:57 +09:00
|
|
|
|
|
|
|
|
|
|
// 반응형 설정
|
|
|
|
|
|
responsiveConfig?: ResponsiveComponentConfig;
|
|
|
|
|
|
responsiveDisplay?: any; // 런타임에 추가되는 임시 필드
|
2025-09-19 18:43:55 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 화면관리용 확장 스타일 (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;
|
2025-10-16 15:05:24 +09:00
|
|
|
|
arrayConfig?: ArrayTypeConfig;
|
2025-11-04 14:33:39 +09:00
|
|
|
|
|
|
|
|
|
|
// 🆕 자동 입력 설정 (테이블 조회 기반)
|
|
|
|
|
|
autoFill?: {
|
|
|
|
|
|
enabled: boolean; // 자동 입력 활성화
|
|
|
|
|
|
sourceTable: string; // 조회할 테이블 (예: company_mng)
|
|
|
|
|
|
filterColumn: string; // 필터링할 컬럼 (예: company_code)
|
|
|
|
|
|
userField: 'companyCode' | 'userId' | 'deptCode'; // 사용자 정보 필드
|
|
|
|
|
|
displayColumn: string; // 표시할 컬럼 (예: company_name)
|
|
|
|
|
|
};
|
2025-09-19 18:43:55 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 컨테이너 컴포넌트 (레이아웃)
|
|
|
|
|
|
*/
|
|
|
|
|
|
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[];
|
2025-11-04 14:33:39 +09:00
|
|
|
|
|
|
|
|
|
|
// 🆕 현재 사용자 정보로 자동 필터링
|
|
|
|
|
|
autoFilter?: {
|
|
|
|
|
|
enabled: boolean; // 자동 필터 활성화 여부
|
|
|
|
|
|
filterColumn: string; // 필터링할 테이블 컬럼 (예: company_code, dept_code)
|
|
|
|
|
|
userField: 'companyCode' | 'userId' | 'deptCode'; // 사용자 정보에서 가져올 필드
|
|
|
|
|
|
};
|
2025-11-13 17:06:41 +09:00
|
|
|
|
|
|
|
|
|
|
// 🆕 컬럼 값 기반 데이터 필터링
|
|
|
|
|
|
dataFilter?: DataFilterConfig;
|
2025-09-19 18:43:55 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 파일 업로드 컴포넌트
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface FileComponent extends BaseComponent {
|
|
|
|
|
|
type: "file";
|
|
|
|
|
|
fileConfig: FileTypeConfig;
|
|
|
|
|
|
uploadedFiles?: UploadedFile[];
|
2025-09-29 13:29:03 +09:00
|
|
|
|
columnName?: string;
|
|
|
|
|
|
tableName?: string;
|
|
|
|
|
|
lastFileUpdate?: number;
|
2025-09-19 18:43:55 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-27 09:49:13 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 플로우 스텝별 컬럼 표시 설정
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface FlowStepColumnConfig {
|
|
|
|
|
|
selectedColumns: string[]; // 표시할 컬럼 목록
|
|
|
|
|
|
columnOrder?: string[]; // 컬럼 순서 (선택사항)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-20 10:55:33 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 플로우 컴포넌트
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface FlowComponent extends BaseComponent {
|
|
|
|
|
|
type: "flow";
|
|
|
|
|
|
flowId?: number; // 선택된 플로우 ID
|
|
|
|
|
|
flowName?: string; // 플로우 이름 (표시용)
|
|
|
|
|
|
showStepCount?: boolean; // 각 스텝의 데이터 건수 표시 여부
|
|
|
|
|
|
allowDataMove?: boolean; // 데이터 이동 허용 여부
|
|
|
|
|
|
displayMode?: "horizontal" | "vertical"; // 플로우 표시 방향
|
2025-10-27 09:49:13 +09:00
|
|
|
|
// 🆕 단계별 컬럼 오버라이드 설정 (화면관리에서 설정)
|
|
|
|
|
|
stepColumnConfig?: {
|
|
|
|
|
|
[stepId: number]: FlowStepColumnConfig;
|
|
|
|
|
|
};
|
2025-11-13 17:06:41 +09:00
|
|
|
|
// 🆕 컬럼 값 기반 데이터 필터링
|
|
|
|
|
|
dataFilter?: DataFilterConfig;
|
2025-10-20 10:55:33 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 16:22:02 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 새로운 컴포넌트 시스템 컴포넌트
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface ComponentComponent extends BaseComponent {
|
|
|
|
|
|
type: "component";
|
|
|
|
|
|
widgetType: WebType; // 웹타입 (기존 호환성)
|
|
|
|
|
|
componentType: string; // 새 컴포넌트 시스템의 ID
|
|
|
|
|
|
componentConfig: any; // 컴포넌트별 설정
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-24 17:24:47 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 탭 아이템 인터페이스
|
|
|
|
|
|
*/
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 18:43:55 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 통합 컴포넌트 데이터 타입
|
|
|
|
|
|
*/
|
2025-10-13 18:28:03 +09:00
|
|
|
|
export type ComponentData =
|
|
|
|
|
|
| WidgetComponent
|
|
|
|
|
|
| ContainerComponent
|
|
|
|
|
|
| GroupComponent
|
|
|
|
|
|
| DataTableComponent
|
|
|
|
|
|
| FileComponent
|
2025-10-20 10:55:33 +09:00
|
|
|
|
| FlowComponent
|
2025-11-24 17:24:47 +09:00
|
|
|
|
| ComponentComponent
|
|
|
|
|
|
| TabsComponent;
|
2025-09-19 18:43:55 +09:00
|
|
|
|
|
|
|
|
|
|
// ===== 웹타입별 설정 인터페이스 =====
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 기본 웹타입 설정
|
|
|
|
|
|
*/
|
|
|
|
|
|
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 {
|
2025-12-10 13:53:44 +09:00
|
|
|
|
options: Array<{ label: string; value: string; disabled?: boolean }>;
|
2025-09-19 18:43:55 +09:00
|
|
|
|
multiple?: boolean;
|
|
|
|
|
|
searchable?: boolean;
|
|
|
|
|
|
placeholder?: string;
|
|
|
|
|
|
allowCustomValue?: boolean;
|
2025-12-10 13:53:44 +09:00
|
|
|
|
defaultValue?: string;
|
|
|
|
|
|
required?: boolean;
|
|
|
|
|
|
readonly?: boolean;
|
|
|
|
|
|
emptyMessage?: string;
|
|
|
|
|
|
|
|
|
|
|
|
/** 🆕 연쇄 드롭다운 관계 코드 (관계 관리에서 정의한 코드) */
|
|
|
|
|
|
cascadingRelationCode?: string;
|
|
|
|
|
|
|
|
|
|
|
|
/** 🆕 연쇄 드롭다운 부모 필드명 (화면 내 다른 필드의 columnName) */
|
|
|
|
|
|
cascadingParentField?: string;
|
|
|
|
|
|
|
|
|
|
|
|
/** @deprecated 직접 설정 방식 - cascadingRelationCode 사용 권장 */
|
|
|
|
|
|
cascading?: CascadingDropdownConfig;
|
2025-09-19 18:43:55 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 텍스트 타입 설정
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface TextTypeConfig {
|
|
|
|
|
|
minLength?: number;
|
|
|
|
|
|
maxLength?: number;
|
|
|
|
|
|
pattern?: string;
|
|
|
|
|
|
format?: "none" | "email" | "phone" | "url" | "korean" | "english";
|
|
|
|
|
|
placeholder?: string;
|
2025-11-04 17:35:02 +09:00
|
|
|
|
defaultValue?: string;
|
2025-09-19 18:43:55 +09:00
|
|
|
|
multiline?: boolean;
|
|
|
|
|
|
rows?: number;
|
2025-11-04 17:35:02 +09:00
|
|
|
|
// 자동입력 관련 설정
|
|
|
|
|
|
autoInput?: boolean;
|
|
|
|
|
|
autoValueType?:
|
|
|
|
|
|
| "current_datetime"
|
|
|
|
|
|
| "current_date"
|
|
|
|
|
|
| "current_time"
|
|
|
|
|
|
| "current_user"
|
|
|
|
|
|
| "uuid"
|
|
|
|
|
|
| "sequence"
|
|
|
|
|
|
| "numbering_rule"
|
|
|
|
|
|
| "custom";
|
|
|
|
|
|
customValue?: string;
|
|
|
|
|
|
numberingRuleId?: string; // 채번 규칙 ID
|
2025-09-19 18:43:55 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 15:05:24 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 배열(다중 입력) 타입 설정
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface ArrayTypeConfig {
|
|
|
|
|
|
itemType?: "text" | "number" | "email" | "tel"; // 각 항목의 입력 타입
|
|
|
|
|
|
minItems?: number; // 최소 항목 수
|
|
|
|
|
|
maxItems?: number; // 최대 항목 수
|
|
|
|
|
|
placeholder?: string; // 입력 필드 placeholder
|
|
|
|
|
|
addButtonText?: string; // + 버튼 텍스트
|
|
|
|
|
|
removeButtonText?: string; // - 버튼 텍스트 (보통 아이콘)
|
|
|
|
|
|
allowReorder?: boolean; // 순서 변경 가능 여부
|
|
|
|
|
|
showIndex?: boolean; // 인덱스 번호 표시 여부
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 18:43:55 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 파일 타입 설정
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface FileTypeConfig {
|
2025-09-29 13:29:03 +09:00
|
|
|
|
accept?: string[];
|
2025-09-19 18:43:55 +09:00
|
|
|
|
multiple?: boolean;
|
2025-09-29 13:29:03 +09:00
|
|
|
|
maxSize?: number; // MB
|
2025-09-19 18:43:55 +09:00
|
|
|
|
maxFiles?: number;
|
2025-09-29 13:29:03 +09:00
|
|
|
|
showPreview?: boolean;
|
|
|
|
|
|
showProgress?: boolean;
|
2025-09-19 18:43:55 +09:00
|
|
|
|
docType?: string;
|
2025-09-29 13:29:03 +09:00
|
|
|
|
docTypeName?: string;
|
|
|
|
|
|
dragDropText?: string;
|
|
|
|
|
|
uploadButtonText?: string;
|
|
|
|
|
|
autoUpload?: boolean;
|
|
|
|
|
|
chunkedUpload?: boolean;
|
|
|
|
|
|
linkedTable?: string;
|
|
|
|
|
|
linkedField?: string;
|
|
|
|
|
|
autoLink?: boolean;
|
2025-09-19 18:43:55 +09:00
|
|
|
|
companyCode?: CompanyCode;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 엔티티 타입 설정
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface EntityTypeConfig {
|
|
|
|
|
|
referenceTable: string;
|
|
|
|
|
|
referenceColumn: string;
|
2025-09-23 15:58:54 +09:00
|
|
|
|
displayColumns: string[]; // 여러 표시 컬럼을 배열로 변경
|
|
|
|
|
|
displayColumn?: string; // 하위 호환성을 위해 유지 (deprecated)
|
2025-09-19 18:43:55 +09:00
|
|
|
|
searchColumns?: string[];
|
|
|
|
|
|
filters?: Record<string, unknown>;
|
|
|
|
|
|
placeholder?: string;
|
2025-09-23 16:23:36 +09:00
|
|
|
|
displayFormat?: "simple" | "detailed" | "custom"; // 표시 형식
|
2025-09-23 15:58:54 +09:00
|
|
|
|
separator?: string; // 여러 컬럼 표시 시 구분자 (기본: ' - ')
|
2025-12-16 14:38:03 +09:00
|
|
|
|
// UI 모드
|
|
|
|
|
|
uiMode?: "select" | "modal" | "combo" | "autocomplete"; // 기본: "combo"
|
2026-01-08 14:49:24 +09:00
|
|
|
|
// 다중 선택
|
|
|
|
|
|
multiple?: boolean; // 여러 항목 선택 가능 여부 (기본: false)
|
2025-09-19 18:43:55 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 13:53:44 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 🆕 연쇄 드롭다운(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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 18:43:55 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 버튼 타입 설정
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface ButtonTypeConfig {
|
|
|
|
|
|
text?: string;
|
|
|
|
|
|
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
|
|
|
|
|
|
size?: "sm" | "md" | "lg";
|
|
|
|
|
|
icon?: string;
|
|
|
|
|
|
// ButtonActionType과 관련된 설정은 control-management.ts에서 정의
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-16 14:38:03 +09:00
|
|
|
|
// ===== 즉시 저장(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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-23 18:23:01 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 플로우 단계별 버튼 표시 설정
|
|
|
|
|
|
*
|
|
|
|
|
|
* 플로우 위젯과 버튼을 함께 사용할 때, 특정 플로우 단계에서만 버튼을 표시하거나 숨길 수 있습니다.
|
|
|
|
|
|
*/
|
|
|
|
|
|
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, 빈 공간 유지)
|
2025-10-24 10:37:02 +09:00
|
|
|
|
* - auto-compact: 빈 공간 자동 제거 (Flexbox 그룹으로 자동 정렬)
|
2025-10-23 18:23:01 +09:00
|
|
|
|
*/
|
|
|
|
|
|
layoutBehavior: "preserve-position" | "auto-compact";
|
2025-10-24 10:37:02 +09:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 그룹 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";
|
2025-10-23 18:23:01 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 18:43:55 +09:00
|
|
|
|
// ===== 데이터 테이블 관련 =====
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 데이터 테이블 컬럼
|
|
|
|
|
|
*/
|
|
|
|
|
|
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";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-13 17:06:41 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 컬럼 필터 조건 (단일 필터)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface ColumnFilter {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
columnName: string; // 필터링할 컬럼명
|
2025-11-20 10:23:54 +09:00
|
|
|
|
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; // 종료일 컬럼명
|
|
|
|
|
|
};
|
2025-11-13 17:06:41 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 데이터 필터 설정 (여러 필터의 조합)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface DataFilterConfig {
|
|
|
|
|
|
enabled: boolean; // 필터 활성화 여부
|
|
|
|
|
|
filters: ColumnFilter[]; // 필터 조건 목록
|
|
|
|
|
|
matchType: "all" | "any"; // AND(모두 만족) / OR(하나 이상 만족)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 18:43:55 +09:00
|
|
|
|
// ===== 파일 업로드 관련 =====
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 업로드된 파일 정보
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface UploadedFile {
|
|
|
|
|
|
objid: string;
|
|
|
|
|
|
realFileName: string;
|
|
|
|
|
|
savedFileName: string;
|
|
|
|
|
|
fileSize: number;
|
|
|
|
|
|
fileExt: string;
|
|
|
|
|
|
filePath: string;
|
|
|
|
|
|
docType?: string;
|
|
|
|
|
|
docTypeName?: string;
|
2025-09-29 13:29:03 +09:00
|
|
|
|
targetObjid: string;
|
|
|
|
|
|
parentTargetObjid?: string;
|
2025-09-19 18:43:55 +09:00
|
|
|
|
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;
|
2025-10-21 13:42:57 +09:00
|
|
|
|
dbSourceType?: "internal" | "external";
|
|
|
|
|
|
dbConnectionId?: number;
|
2025-11-28 11:34:48 +09:00
|
|
|
|
// REST API 관련 필드
|
|
|
|
|
|
dataSourceType?: "database" | "restapi";
|
|
|
|
|
|
restApiConnectionId?: number;
|
|
|
|
|
|
restApiEndpoint?: string;
|
|
|
|
|
|
restApiJsonPath?: string;
|
2025-09-19 18:43:55 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 화면 생성 요청
|
|
|
|
|
|
*/
|
|
|
|
|
|
export interface CreateScreenRequest {
|
|
|
|
|
|
screenName: string;
|
|
|
|
|
|
screenCode?: string;
|
|
|
|
|
|
tableName: string;
|
|
|
|
|
|
tableLabel?: string;
|
|
|
|
|
|
companyCode: CompanyCode;
|
|
|
|
|
|
description?: string;
|
2025-10-21 13:42:57 +09:00
|
|
|
|
dbSourceType?: "internal" | "external";
|
|
|
|
|
|
dbConnectionId?: number;
|
2025-11-28 11:34:48 +09:00
|
|
|
|
// REST API 관련 필드
|
|
|
|
|
|
dataSourceType?: "database" | "restapi";
|
|
|
|
|
|
restApiConnectionId?: number;
|
|
|
|
|
|
restApiEndpoint?: string;
|
|
|
|
|
|
restApiJsonPath?: string;
|
2025-09-19 18:43:55 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 화면 수정 요청
|
|
|
|
|
|
*/
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-11-10 14:21:29 +09:00
|
|
|
|
* 격자 설정
|
2025-09-19 18:43:55 +09:00
|
|
|
|
*/
|
|
|
|
|
|
export interface GridSettings {
|
2025-11-10 14:21:29 +09:00
|
|
|
|
enabled: boolean;
|
|
|
|
|
|
size: number;
|
|
|
|
|
|
color: string;
|
|
|
|
|
|
opacity: number;
|
|
|
|
|
|
snapToGrid: boolean;
|
|
|
|
|
|
// gridUtils에서 필요한 속성들 추가
|
|
|
|
|
|
columns: number;
|
|
|
|
|
|
gap: number;
|
|
|
|
|
|
padding: number;
|
|
|
|
|
|
showGrid?: boolean;
|
|
|
|
|
|
gridColor?: string;
|
|
|
|
|
|
gridOpacity?: number;
|
2025-09-19 18:43:55 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 레이아웃 메타데이터
|
|
|
|
|
|
*/
|
|
|
|
|
|
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";
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-20 10:55:33 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* FlowComponent 타입 가드
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const isFlowComponent = (component: ComponentData): component is FlowComponent => {
|
|
|
|
|
|
return component.type === "flow";
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-24 17:24:47 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* TabsComponent 타입 가드
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const isTabsComponent = (component: ComponentData): component is TabsComponent => {
|
|
|
|
|
|
return component.type === "tabs";
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-19 18:43:55 +09:00
|
|
|
|
// ===== 안전한 타입 캐스팅 유틸리티 =====
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 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;
|
|
|
|
|
|
};
|
2025-10-20 10:55:33 +09:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ComponentData를 FlowComponent로 안전하게 캐스팅
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const asFlowComponent = (component: ComponentData): FlowComponent => {
|
|
|
|
|
|
if (!isFlowComponent(component)) {
|
|
|
|
|
|
throw new Error(`Expected FlowComponent, got ${component.type}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return component;
|
|
|
|
|
|
};
|
2025-11-24 17:24:47 +09:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ComponentData를 TabsComponent로 안전하게 캐스팅
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const asTabsComponent = (component: ComponentData): TabsComponent => {
|
|
|
|
|
|
if (!isTabsComponent(component)) {
|
|
|
|
|
|
throw new Error(`Expected TabsComponent, got ${component.type}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return component;
|
|
|
|
|
|
};
|