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

584 lines
13 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";
// ===== 기본 컴포넌트 인터페이스 =====
/**
* 모든 컴포넌트의 기본 인터페이스
*/
export interface BaseComponent {
id: string;
type: ComponentType;
position: Position;
size: Size;
parentId?: string;
label?: string;
required?: boolean;
readonly?: boolean;
style?: ComponentStyle;
className?: 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;
}
/**
* 컨테이너 컴포넌트 (레이아웃)
*/
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[];
}
/**
* 파일 업로드 컴포넌트
*/
export interface FileComponent extends BaseComponent {
type: "file";
fileConfig: FileTypeConfig;
uploadedFiles?: UploadedFile[];
}
/**
* 통합 컴포넌트 데이터 타입
*/
export type ComponentData = WidgetComponent | ContainerComponent | GroupComponent | DataTableComponent | FileComponent;
// ===== 웹타입별 설정 인터페이스 =====
/**
* 기본 웹타입 설정
*/
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 }>;
multiple?: boolean;
searchable?: boolean;
placeholder?: string;
allowCustomValue?: boolean;
}
/**
* 텍스트 타입 설정
*/
export interface TextTypeConfig {
minLength?: number;
maxLength?: number;
pattern?: string;
format?: "none" | "email" | "phone" | "url" | "korean" | "english";
placeholder?: string;
multiline?: boolean;
rows?: number;
}
/**
* 파일 타입 설정
*/
export interface FileTypeConfig {
accept?: string;
multiple?: boolean;
maxSize?: number; // bytes
maxFiles?: number;
preview?: boolean;
docType?: string;
companyCode?: CompanyCode;
}
/**
* 엔티티 타입 설정
*/
export interface EntityTypeConfig {
referenceTable: string;
referenceColumn: string;
displayColumn: string;
searchColumns?: string[];
filters?: Record<string, unknown>;
placeholder?: string;
}
/**
* 버튼 타입 설정
*/
export interface ButtonTypeConfig {
text?: string;
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
size?: "sm" | "md" | "lg";
icon?: string;
// ButtonActionType과 관련된 설정은 control-management.ts에서 정의
}
// ===== 데이터 테이블 관련 =====
/**
* 데이터 테이블 컬럼
*/
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 UploadedFile {
objid: string;
realFileName: string;
savedFileName: string;
fileSize: number;
fileExt: string;
filePath: string;
docType?: string;
docTypeName?: 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;
}
/**
* 화면 생성 요청
*/
export interface CreateScreenRequest {
screenName: string;
screenCode?: string;
tableName: string;
tableLabel?: string;
companyCode: CompanyCode;
description?: 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;
}
/**
* 레이아웃 메타데이터
*/
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";
};
// ===== 안전한 타입 캐스팅 유틸리티 =====
/**
* 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;
};