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

587 lines
14 KiB
TypeScript
Raw Normal View History

/**
* 🖥
*
* , ,
*/
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;
displayColumns: string[]; // 여러 표시 컬럼을 배열로 변경
displayColumn?: string; // 하위 호환성을 위해 유지 (deprecated)
searchColumns?: string[];
filters?: Record<string, unknown>;
placeholder?: string;
displayFormat?: "simple" | "detailed" | "custom"; // 표시 형식
separator?: 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;
};