타입 관리 개선 및 화면 비율조정 중간커밋

This commit is contained in:
kjs
2025-09-19 18:43:55 +09:00
parent baa656dee5
commit 4b28530fec
47 changed files with 13149 additions and 1230 deletions

View File

@@ -0,0 +1,514 @@
/**
* 🎮 제어관리 시스템 전용 타입 정의
*
* 버튼 액션, 데이터플로우, 조건부 실행, 트랜잭션 처리 등 제어관리에서만 사용하는 타입들
*/
import {
ButtonActionType,
ConditionOperator,
CompanyCode,
ActiveStatus,
TimestampFields,
BaseApiResponse,
} from "./unified-core";
// ===== 버튼 제어 관련 =====
/**
* 확장된 버튼 설정 (화면관리의 ButtonTypeConfig 확장)
*/
export interface ExtendedButtonTypeConfig {
// 기본 버튼 설정
actionType: ButtonActionType;
text?: string;
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
size?: "sm" | "md" | "lg";
icon?: string;
// 확인 및 검증
confirmMessage?: string;
requiresConfirmation?: boolean;
// 모달 관련 설정
popupTitle?: string;
popupContent?: string;
popupScreenId?: number;
// 네비게이션 관련 설정
navigateType?: "url" | "screen";
navigateUrl?: string;
navigateScreenId?: number;
navigateTarget?: "_self" | "_blank";
// 커스텀 액션 설정
customAction?: string;
// 🎯 제어관리 기능
enableDataflowControl?: boolean;
dataflowConfig?: ButtonDataflowConfig;
dataflowTiming?: "before" | "after" | "replace";
// 스타일 설정
backgroundColor?: string;
textColor?: string;
borderColor?: string;
}
/**
* 버튼 데이터플로우 설정
*/
export interface ButtonDataflowConfig {
// 제어 방식 선택
controlMode: "simple" | "advanced";
// 관계도 방식 (diagram 기반)
selectedDiagramId?: number;
selectedRelationshipId?: number;
// 직접 설정 방식
directControl?: DirectControlConfig;
// 제어 데이터 소스
controlDataSource?: ControlDataSource;
// 실행 옵션
executionOptions?: ExecutionOptions;
}
/**
* 제어 데이터 소스 타입
*/
export type ControlDataSource = "form" | "table-selection" | "both";
/**
* 직접 제어 설정
*/
export interface DirectControlConfig {
conditions: DataflowCondition[];
actions: DataflowAction[];
logic?: "AND" | "OR" | "CUSTOM";
customLogic?: string; // "(A AND B) OR (C AND D)"
}
/**
* 실행 옵션
*/
export interface ExecutionOptions {
timeout?: number; // ms
retryCount?: number;
parallelExecution?: boolean;
continueOnError?: boolean;
}
// ===== 데이터플로우 조건 및 액션 =====
/**
* 데이터플로우 조건
*/
export interface DataflowCondition {
id: string;
type: "condition" | "group";
// 단일 조건
field?: string;
operator?: ConditionOperator;
value?: unknown;
dataSource?: ControlDataSource;
// 그룹 조건
conditions?: DataflowCondition[];
logic?: "AND" | "OR";
// 메타데이터
name?: string;
description?: string;
}
/**
* 데이터플로우 액션
*/
export interface DataflowAction {
id: string;
name: string;
type: ActionType;
// 데이터베이스 액션
tableName?: string;
operation?: DatabaseOperation;
fields?: ActionField[];
conditions?: DataflowCondition[];
// API 액션
endpoint?: string;
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
headers?: Record<string, string>;
body?: unknown;
// 알림 액션
notificationType?: NotificationType;
message?: string;
recipients?: string[];
// 리다이렉트 액션
redirectUrl?: string;
redirectTarget?: "_self" | "_blank";
// 실행 옵션
timeout?: number;
retryCount?: number;
rollbackable?: boolean;
// 메타데이터
description?: string;
order?: number;
}
/**
* 액션 타입
*/
export type ActionType = "database" | "api" | "notification" | "redirect" | "custom";
/**
* 데이터베이스 작업 타입
*/
export type DatabaseOperation = "INSERT" | "UPDATE" | "DELETE" | "SELECT";
/**
* 액션 필드
*/
export interface ActionField {
name: string;
value: unknown;
type?: "static" | "dynamic" | "computed";
source?: string; // 동적 값의 소스 (form field, selected row 등)
}
/**
* 알림 타입
*/
export type NotificationType = "success" | "error" | "warning" | "info" | "toast" | "modal" | "email";
// ===== 트랜잭션 관리 =====
/**
* 트랜잭션 그룹
*/
export interface TransactionGroup {
id: string;
name: string;
description?: string;
actions: DataflowAction[];
rollbackStrategy: RollbackStrategy;
executionMode: "sequential" | "parallel";
onFailure: FailureHandling;
}
/**
* 롤백 전략
*/
export type RollbackStrategy =
| "none" // 롤백 안함
| "partial" // 실패한 액션만 롤백
| "complete"; // 전체 트랜잭션 롤백
/**
* 실패 처리 방식
*/
export type FailureHandling =
| "stop" // 실패 시 중단
| "continue" // 실패해도 계속 진행
| "alternative"; // 대안 액션 실행
/**
* 조건부 실행 계획
*/
export interface ConditionalExecutionPlan {
id: string;
name: string;
conditions: ExecutionCondition[];
logic: "AND" | "OR" | "CUSTOM";
customLogic?: string;
}
/**
* 실행 조건
*/
export interface ExecutionCondition {
id: string;
type: "action_group" | "validation" | "data_check";
// 액션 그룹 조건
actionGroup?: TransactionGroup;
// 검증 조건
validation?: {
field: string;
operator: ConditionOperator;
value: unknown;
};
// 성공/실패 조건
expectedResult: "success" | "failure" | "any";
}
/**
* 조건부 액션 그룹
*/
export interface ConditionalActionGroup {
id: string;
name: string;
description?: string;
// 실행 조건
executionCondition: {
type: "always" | "conditional" | "fallback";
conditions?: DataflowCondition[];
logic?: "AND" | "OR";
};
// 액션들
actions: DataflowAction[];
// 성공/실패 조건 정의
successCriteria: {
type: "all_success" | "any_success" | "custom";
customLogic?: string;
};
// 다음 단계 정의
onSuccess?: {
nextGroup?: string;
completeTransaction?: boolean;
};
onFailure?: {
retryCount?: number;
fallbackGroup?: string;
rollbackStrategy?: RollbackStrategy;
};
}
// ===== 실행 결과 및 상태 =====
/**
* 액션 실행 결과
*/
export interface ActionExecutionResult {
actionId: string;
transactionId?: string;
status: "pending" | "running" | "success" | "failed" | "rolled_back";
startTime: Date;
endTime?: Date;
result?: unknown;
error?: {
code: string;
message: string;
details?: unknown;
};
rollbackData?: unknown;
}
/**
* 트랜잭션 실행 상태
*/
export interface TransactionExecutionState {
transactionId: string;
status: "pending" | "running" | "success" | "failed" | "rolling_back" | "rolled_back";
actions: ActionExecutionResult[];
rollbackActions?: ActionExecutionResult[];
startTime: Date;
endTime?: Date;
}
/**
* 트랜잭션 실행 결과
*/
export interface TransactionExecutionResult {
success: boolean;
message: string;
requiresRollback: boolean;
results: [string, boolean][];
transactionId?: string;
}
/**
* 데이터플로우 실행 결과
*/
export interface DataflowExecutionResult {
success: boolean;
message: string;
data?: unknown;
executedActions?: ActionExecutionResult[];
failedActions?: ActionExecutionResult[];
totalActions?: number;
executionTime?: number;
}
// ===== 제어 컨텍스트 =====
/**
* 확장된 제어 컨텍스트
*/
export interface ExtendedControlContext {
// 기존 폼 데이터
formData: Record<string, unknown>;
// 테이블 선택 데이터
selectedRows?: unknown[];
selectedRowsData?: Record<string, unknown>[];
// 제어 데이터 소스 타입
controlDataSource: ControlDataSource;
// 기타 컨텍스트
buttonId: string;
componentData?: unknown;
timestamp: string;
clickCount?: number;
// 사용자 정보
userId?: string;
companyCode?: CompanyCode;
// 화면 정보
screenId?: number;
screenCode?: string;
}
/**
* 빠른 검증 결과
*/
export interface QuickValidationResult {
success: boolean;
message?: string;
canExecuteImmediately: boolean;
actions?: DataflowAction[];
}
// ===== 버튼 액션 표준 (DB 기반) =====
/**
* 버튼 액션 표준 정의 (DB의 button_action_standards 테이블)
*/
export interface ButtonActionStandard extends TimestampFields {
action_type: string;
action_name: string;
action_name_eng?: string;
description?: string;
category: string;
default_text?: string;
default_text_eng?: string;
default_icon?: string;
default_color?: string;
default_variant?: string;
confirmation_required: boolean;
confirmation_message?: string;
validation_rules?: unknown;
action_config?: unknown;
sort_order?: number;
is_active: ActiveStatus;
}
/**
* 버튼 액션 생성/수정 요청
*/
export interface ButtonActionFormData {
action_type: string;
action_name: string;
action_name_eng?: string;
description?: string;
category: string;
default_text?: string;
default_text_eng?: string;
default_icon?: string;
default_color?: string;
default_variant?: string;
confirmation_required: boolean;
confirmation_message?: string;
validation_rules?: unknown;
action_config?: unknown;
sort_order?: number;
is_active: ActiveStatus;
}
// ===== API 응답 타입들 =====
/**
* 버튼 액션 목록 응답
*/
export interface ButtonActionListResponse extends BaseApiResponse<ButtonActionStandard[]> {}
/**
* 데이터플로우 실행 응답
*/
export interface DataflowExecutionResponse extends BaseApiResponse<DataflowExecutionResult> {}
/**
* 트랜잭션 실행 응답
*/
export interface TransactionExecutionResponse extends BaseApiResponse<TransactionExecutionResult> {}
// ===== 유틸리티 타입들 =====
/**
* 롤백 핸들러
*/
export interface RollbackHandler {
actionId: string;
rollbackFn: () => Promise<void>;
}
/**
* 최적화된 실행 결과
*/
export interface OptimizedExecutionResult {
jobId: string;
immediateResult?: unknown;
isBackground?: boolean;
timing?: "before" | "after" | "replace";
}
// ===== 타입 가드 및 유틸리티 함수들 =====
/**
* DataflowCondition이 단일 조건인지 확인
*/
export const isSingleCondition = (condition: DataflowCondition): boolean => {
return condition.type === "condition" && !!condition.field;
};
/**
* DataflowCondition이 그룹 조건인지 확인
*/
export const isGroupCondition = (condition: DataflowCondition): boolean => {
return condition.type === "group" && !!condition.conditions?.length;
};
/**
* DataflowAction이 데이터베이스 액션인지 확인
*/
export const isDatabaseAction = (action: DataflowAction): boolean => {
return action.type === "database" && !!action.tableName;
};
/**
* DataflowAction이 API 액션인지 확인
*/
export const isApiAction = (action: DataflowAction): boolean => {
return action.type === "api" && !!action.endpoint;
};
/**
* 액션 실행 결과가 성공인지 확인
*/
export const isActionSuccess = (result: ActionExecutionResult): boolean => {
return result.status === "success";
};
/**
* 트랜잭션이 완료되었는지 확인 (성공 또는 실패)
*/
export const isTransactionCompleted = (state: TransactionExecutionState): boolean => {
return ["success", "failed", "rolled_back"].includes(state.status);
};

343
frontend/types/index.ts Normal file
View File

@@ -0,0 +1,343 @@
/**
* 🎯 통합 타입 시스템 Index
*
* 모든 타입 정의를 중앙에서 관리하고 re-export합니다.
* 이 파일을 통해 모든 타입에 일관성 있게 접근할 수 있습니다.
*/
// ===== 핵심 공통 타입들 =====
export * from "./unified-core";
// ===== 시스템별 전용 타입들 =====
export * from "./screen-management";
export * from "./control-management";
export * from "./table-management";
// ===== 기존 호환성을 위한 re-export =====
// unified-core에서 제공하는 주요 타입들을 직접 export
export type {
// 핵심 타입들
WebType,
DynamicWebType,
ButtonActionType,
ComponentType,
Position,
Size,
CommonStyle,
ValidationRule,
ConditionOperator,
// API 관련
BaseApiResponse,
PaginatedResponse,
// 공통 필드들
CompanyCode,
ActiveStatus,
TimestampFields,
AuditFields,
// 이벤트 타입들
WebTypeEvent,
ComponentEvent,
} from "./unified-core";
// screen-management에서 제공하는 주요 타입들
export type {
// 컴포넌트 타입들
ComponentData,
BaseComponent,
WidgetComponent,
ContainerComponent,
GroupComponent,
DataTableComponent,
FileComponent,
// 웹타입 설정들
WebTypeConfig,
DateTypeConfig,
NumberTypeConfig,
SelectTypeConfig,
TextTypeConfig,
FileTypeConfig,
EntityTypeConfig,
ButtonTypeConfig,
// 화면 관련
ScreenDefinition,
CreateScreenRequest,
UpdateScreenRequest,
LayoutData,
GridSettings,
ScreenTemplate,
ScreenResolution,
GroupState,
// 화면 해상도 상수
SCREEN_RESOLUTIONS,
// 데이터 테이블
DataTableColumn,
DataTableFilter,
// 파일 업로드
UploadedFile,
} from "./screen-management";
// control-management에서 제공하는 주요 타입들
export type {
// 버튼 제어
ExtendedButtonTypeConfig,
ButtonDataflowConfig,
ControlDataSource,
// 데이터플로우
DataflowCondition,
DataflowAction,
ActionType,
DatabaseOperation,
NotificationType,
// 트랜잭션 관리
TransactionGroup,
RollbackStrategy,
FailureHandling,
ConditionalExecutionPlan,
ExecutionCondition,
// 실행 결과
ActionExecutionResult,
TransactionExecutionState,
DataflowExecutionResult,
// 컨텍스트
ExtendedControlContext,
QuickValidationResult,
// 버튼 액션 표준
ButtonActionStandard,
ButtonActionFormData,
} from "./control-management";
// table-management에서 제공하는 주요 타입들
export type {
// 테이블 정보
TableInfo,
UnifiedColumnInfo,
ColumnTypeInfo,
ColumnSettings,
// 웹타입 표준
WebTypeStandard,
WebTypeDefinition,
// 라벨 관리
TableLabels,
ColumnLabels,
// 엔티티 조인
EntityJoinConfig,
EntityJoinResponse,
BatchLookupRequest,
BatchLookupResponse,
// 테이블 관계
TableRelationship,
DataRelationshipBridge,
// 컬럼 웹타입 설정
ColumnWebTypeSetting,
// API 응답들
TableListResponse,
ColumnListResponse,
ColumnTypeInfoResponse,
WebTypeStandardListResponse,
TableDataResponse,
} from "./table-management";
// ===== 타입 가드 함수들 통합 export =====
// unified-core 타입 가드들
export { isWebType, isButtonActionType, isComponentType, ynToBoolean, booleanToYN } from "./unified-core";
// screen-management 타입 가드들
export {
isWidgetComponent,
isContainerComponent,
isGroupComponent,
isDataTableComponent,
isFileComponent,
asWidgetComponent,
asContainerComponent,
asGroupComponent,
asDataTableComponent,
asFileComponent,
} from "./screen-management";
// control-management 타입 가드들
export {
isSingleCondition,
isGroupCondition,
isDatabaseAction,
isApiAction,
isActionSuccess,
isTransactionCompleted,
} from "./control-management";
// table-management 타입 가드들
export {
isReferenceWebType,
isNumericWebType,
isDateWebType,
isSelectWebType,
isRequiredColumn,
isSystemColumn,
mapWebTypeStandardToDefinition,
mapColumnTypeInfoToUnified,
mapUnifiedToColumnTypeInfo,
} from "./table-management";
// ===== 상수들 통합 export =====
// table-management 상수들
export { WEB_TYPE_OPTIONS } from "./table-management";
// ===== 타입 별칭 (기존 호환성) =====
/**
* @deprecated screen.ts에서 이전하세요. unified-core.ts의 WebType을 사용하세요.
*/
export type LegacyWebType = WebType;
/**
* @deprecated screen.ts에서 이전하세요. unified-core.ts의 ButtonActionType을 사용하세요.
*/
export type LegacyButtonActionType = ButtonActionType;
/**
* @deprecated screen.ts에서 이전하세요. screen-management.ts의 ComponentData를 사용하세요.
*/
export type LegacyComponentData = ComponentData;
// ===== 유틸리티 타입들 =====
/**
* 컴포넌트 업데이트를 위한 부분 타입
*/
export type ComponentUpdate<T extends ComponentData> = Partial<Omit<T, "id" | "type">> & {
id: string;
type: T["type"];
};
/**
* API 요청을 위한 기본 파라미터
*/
export interface BaseRequestParams {
companyCode?: CompanyCode;
page?: number;
size?: number;
sortBy?: string;
sortDirection?: "asc" | "desc";
searchTerm?: string;
}
/**
* 폼 데이터 타입 (모든 시스템에서 공통 사용)
*/
export type FormData = Record<string, unknown>;
/**
* 선택된 행 데이터 타입
*/
export type SelectedRowData = Record<string, unknown>;
/**
* 테이블 데이터 타입
*/
export type TableData = Record<string, unknown>[];
// ===== 마이그레이션 도우미 =====
/**
* 기존 screen.ts 타입을 새로운 통합 타입으로 마이그레이션하는 도우미
*/
export namespace Migration {
/**
* 기존 screen.ts의 WebType을 새로운 WebType으로 변환
*/
export const migrateWebType = (oldWebType: string): WebType => {
// 기존 타입이 새로운 WebType에 포함되어 있는지 확인
if (isWebType(oldWebType)) {
return oldWebType as WebType;
}
// 호환되지 않는 타입의 경우 기본값 반환
console.warn(`Unknown WebType: ${oldWebType}, defaulting to 'text'`);
return "text";
};
/**
* 기존 ButtonActionType을 새로운 ButtonActionType으로 변환
*/
export const migrateButtonActionType = (oldActionType: string): ButtonActionType => {
if (isButtonActionType(oldActionType)) {
return oldActionType as ButtonActionType;
}
console.warn(`Unknown ButtonActionType: ${oldActionType}, defaulting to 'submit'`);
return "submit";
};
/**
* Y/N 문자열을 boolean으로 변환 (DB 호환성)
*/
export const migrateYNToBoolean = (value: string | undefined): boolean => {
return value === "Y";
};
/**
* boolean을 Y/N 문자열로 변환 (DB 호환성)
*/
export const migrateBooleanToYN = (value: boolean): string => {
return value ? "Y" : "N";
};
}
// ===== 타입 검증 도우미 =====
/**
* 런타임에서 타입 안전성을 보장하는 검증 함수들
*/
export namespace TypeValidation {
/**
* 객체가 BaseComponent 인터페이스를 만족하는지 검증
*/
export const validateBaseComponent = (obj: unknown): obj is BaseComponent => {
if (typeof obj !== "object" || obj === null) return false;
const component = obj as Record<string, unknown>;
return (
typeof component.id === "string" &&
typeof component.type === "string" &&
isComponentType(component.type as string) &&
typeof component.position === "object" &&
typeof component.size === "object"
);
};
/**
* 객체가 WebTypeConfig를 만족하는지 검증
*/
export const validateWebTypeConfig = (obj: unknown): obj is WebTypeConfig => {
return typeof obj === "object" && obj !== null;
};
/**
* 문자열이 유효한 CompanyCode인지 검증
*/
export const validateCompanyCode = (code: unknown): code is CompanyCode => {
return typeof code === "string" && code.length > 0;
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,583 @@
/**
* 🖥️ 화면관리 시스템 전용 타입 정의
*
* 화면 설계, 컴포넌트 관리, 레이아웃 등 화면관리 시스템에서만 사용하는 타입들
*/
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;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,505 @@
/**
* 🗄️ 테이블 타입관리 시스템 전용 타입 정의
*
* 데이터베이스 테이블 스키마, 컬럼 타입, 웹타입 매핑 등 테이블 관리에서만 사용하는 타입들
*/
import {
DynamicWebType,
CompanyCode,
ActiveStatus,
TimestampFields,
BaseApiResponse,
PaginatedResponse,
ConditionOperator,
} from "./unified-core";
// ===== 기본 테이블 정보 =====
/**
* 테이블 정보
*/
export interface TableInfo {
tableName: string;
displayName: string;
description: string;
columnCount: number;
companyCode?: CompanyCode;
isActive?: ActiveStatus;
createdDate?: Date;
updatedDate?: Date;
}
/**
* 통합된 컬럼 정보 (프론트엔드/백엔드 호환)
*/
export interface UnifiedColumnInfo {
// 기본 정보
tableName: string;
columnName: string;
displayName: string;
// 데이터 타입
dataType: string; // DB 데이터 타입 (varchar, integer, timestamp 등)
dbType: string; // DB 내부 타입
webType: DynamicWebType; // 웹 입력 타입 (text, number, date 등)
// 입력 설정
inputType: "direct" | "auto";
detailSettings?: Record<string, unknown>; // JSON 파싱된 객체
description?: string;
// 제약 조건
isNullable: boolean; // Y/N → boolean 변환
isPrimaryKey: boolean;
defaultValue?: string;
// 크기 제한
maxLength?: number;
numericPrecision?: number;
numericScale?: number;
// 표시 옵션
isVisible?: boolean;
displayOrder?: number;
// 참조 관계
codeCategory?: string;
codeValue?: string;
referenceTable?: string;
referenceColumn?: string;
displayColumn?: string;
// 메타데이터
companyCode?: CompanyCode;
createdDate?: Date;
updatedDate?: Date;
}
/**
* 백엔드 호환용 컬럼 타입 정보 (기존 ColumnTypeInfo)
*/
export interface ColumnTypeInfo {
columnName: string;
displayName: string;
dataType: string;
dbType: string;
webType: string; // string 타입 (백엔드 호환)
inputType?: "direct" | "auto";
detailSettings: string; // JSON 문자열
description: string; // 필수 필드
isNullable: string; // Y/N 문자열
isPrimaryKey: boolean;
defaultValue?: string;
maxLength?: number;
numericPrecision?: number;
numericScale?: number;
codeCategory?: string;
codeValue?: string;
referenceTable?: string;
referenceColumn?: string;
displayColumn?: string;
displayOrder?: number;
isVisible?: boolean;
}
/**
* 컬럼 설정 (업데이트용)
*/
export interface ColumnSettings {
columnName?: string; // 컬럼명 (업데이트 시 필요)
columnLabel: string; // 컬럼 표시명
webType: string; // 웹 입력 타입
detailSettings: string; // 상세 설정 (JSON 문자열)
codeCategory: string; // 코드 카테고리
codeValue: string; // 코드 값
referenceTable: string; // 참조 테이블
referenceColumn: string; // 참조 컬럼
displayColumn?: string; // 표시할 컬럼명
displayOrder?: number; // 표시 순서
isVisible?: boolean; // 표시 여부
}
// ===== 웹타입 표준 정의 =====
/**
* 웹타입 표준 정보 (DB의 web_type_standards 테이블)
*/
export interface WebTypeStandard extends TimestampFields {
web_type: string;
type_name: string;
type_name_eng?: string;
description?: string;
category: string;
default_config?: unknown; // JSON
validation_rules?: unknown; // JSON
default_style?: unknown; // JSON
input_properties?: unknown; // JSON
sort_order?: number;
is_active: ActiveStatus;
component_name?: string;
config_panel?: string;
}
/**
* 프론트엔드용 웹타입 정의 (WebTypeStandard 변환)
*/
export interface WebTypeDefinition {
webType: string; // web_type 필드
typeName: string; // type_name 필드
typeNameEng?: string; // type_name_eng 필드
description?: string;
category: string;
defaultConfig: Record<string, unknown>; // JSON 타입 매핑
validationRules?: Record<string, unknown>; // JSON 타입 매핑
defaultStyle?: Record<string, unknown>; // JSON 타입 매핑
inputProperties?: Record<string, unknown>; // JSON 타입 매핑
componentName?: string; // component_name 필드
configPanel?: string; // config_panel 필드
sortOrder?: number; // sort_order 필드
isActive: boolean; // is_active Y/N → boolean 변환
}
// ===== 테이블 라벨 관리 =====
/**
* 테이블 라벨
*/
export interface TableLabels extends TimestampFields {
tableName: string;
tableLabel?: string;
description?: string;
companyCode?: CompanyCode;
}
/**
* 컬럼 라벨
*/
export interface ColumnLabels extends TimestampFields {
id?: number;
tableName: string;
columnName: string;
columnLabel?: string;
webType?: string;
detailSettings?: string;
description?: string;
displayOrder?: number;
isVisible?: boolean;
codeCategory?: string;
codeValue?: string;
referenceTable?: string;
referenceColumn?: string;
displayColumn?: string;
companyCode?: CompanyCode;
}
// ===== 엔티티 조인 관리 =====
/**
* 엔티티 조인 설정
*/
export interface EntityJoinConfig {
sourceTable: string; // 원본 테이블 (예: companies)
sourceColumn: string; // 원본 컬럼 (예: writer)
referenceTable: string; // 참조 테이블 (예: user_info)
referenceColumn: string; // 조인 키 (예: user_id)
displayColumn: string; // 표시할 값 (예: user_name)
aliasColumn: string; // 결과 컬럼명 (예: writer_name)
companyCode?: CompanyCode;
}
/**
* 엔티티 조인 응답
*/
export interface EntityJoinResponse {
data: Record<string, unknown>[];
total: number;
page: number;
size: number;
totalPages: number;
entityJoinInfo?: {
joinConfigs: EntityJoinConfig[];
strategy: "full_join" | "cache_lookup" | "hybrid";
performance: {
queryTime: number;
cacheHitRate?: number;
hybridBreakdown?: {
dbJoins: number;
cacheJoins: number;
};
};
};
}
/**
* 배치 조회 요청
*/
export interface BatchLookupRequest {
table: string;
key: string;
displayColumn: string;
companyCode?: CompanyCode;
}
/**
* 배치 조회 응답
*/
export interface BatchLookupResponse {
key: string;
value: unknown;
}
// ===== 테이블 관계 관리 =====
/**
* 테이블 관계 정의
*/
export interface TableRelationship extends TimestampFields {
relationship_id?: number;
relationship_name?: string;
from_table_name?: string;
from_column_name?: string;
to_table_name?: string;
to_column_name?: string;
relationship_type?: string;
connection_type?: string;
company_code?: CompanyCode;
settings?: unknown; // JSON
is_active?: ActiveStatus;
diagram_id?: number;
}
/**
* 데이터 관계 브릿지
*/
export interface DataRelationshipBridge extends TimestampFields {
bridge_id?: number;
relationship_id?: number;
from_table_name: string;
from_column_name: string;
to_table_name: string;
to_column_name: string;
connection_type: string;
company_code: CompanyCode;
is_active?: ActiveStatus;
bridge_data?: unknown; // JSON
from_key_value?: string;
from_record_id?: string;
to_key_value?: string;
to_record_id?: string;
}
// ===== 컬럼 웹타입 설정 =====
/**
* 컬럼 웹타입 설정
*/
export interface ColumnWebTypeSetting {
tableName: string;
columnName: string;
webType: DynamicWebType;
detailSettings?: Record<string, unknown>;
codeCategory?: string;
referenceTable?: string;
referenceColumn?: string;
displayColumn?: string;
companyCode?: CompanyCode;
}
// ===== API 응답 타입들 =====
/**
* 테이블 목록 응답
*/
export interface TableListResponse extends BaseApiResponse<TableInfo[]> {}
/**
* 컬럼 목록 응답
*/
export interface ColumnListResponse extends BaseApiResponse<UnifiedColumnInfo[]> {}
/**
* 컬럼 타입 정보 응답 (백엔드 호환)
*/
export interface ColumnTypeInfoResponse extends BaseApiResponse<ColumnTypeInfo[]> {}
/**
* 컬럼 설정 응답
*/
export interface ColumnSettingsResponse extends BaseApiResponse<void> {}
/**
* 웹타입 표준 목록 응답
*/
export interface WebTypeStandardListResponse extends BaseApiResponse<WebTypeStandard[]> {}
/**
* 웹타입 정의 목록 응답
*/
export interface WebTypeDefinitionListResponse extends BaseApiResponse<WebTypeDefinition[]> {}
/**
* 테이블 데이터 조회 응답
*/
export interface TableDataResponse extends PaginatedResponse<Record<string, unknown>> {}
// ===== 웹타입 옵션 상수 =====
/**
* 웹타입 옵션 (기존 호환성 유지)
*/
export const WEB_TYPE_OPTIONS = [
{ value: "text", label: "text", description: "일반 텍스트 입력" },
{ value: "number", label: "number", description: "숫자 입력" },
{ value: "decimal", label: "decimal", description: "소수 입력" },
{ value: "date", label: "date", description: "날짜 선택기" },
{ value: "datetime", label: "datetime", description: "날짜시간 선택기" },
{ value: "code", label: "code", description: "코드 선택 (공통코드 지정)" },
{ value: "entity", label: "entity", description: "엔티티 참조 (참조테이블 지정)" },
{ value: "textarea", label: "textarea", description: "여러 줄 텍스트" },
{ value: "select", label: "select", description: "드롭다운 선택" },
{ value: "dropdown", label: "dropdown", description: "드롭다운 선택" },
{ value: "checkbox", label: "checkbox", description: "체크박스" },
{ value: "boolean", label: "boolean", description: "참/거짓" },
{ value: "radio", label: "radio", description: "라디오 버튼" },
{ value: "file", label: "file", description: "파일 업로드" },
{ value: "email", label: "email", description: "이메일 입력" },
{ value: "tel", label: "tel", description: "전화번호 입력" },
{ value: "url", label: "url", description: "URL 입력" },
] as const;
/**
* 웹타입 (기존 호환성)
*/
export type WebType = (typeof WEB_TYPE_OPTIONS)[number]["value"];
// ===== 변환 유틸리티 함수들 =====
/**
* WebTypeStandard를 WebTypeDefinition으로 변환
*/
export const mapWebTypeStandardToDefinition = (standard: WebTypeStandard): WebTypeDefinition => ({
webType: standard.web_type,
typeName: standard.type_name,
typeNameEng: standard.type_name_eng || undefined,
description: standard.description || undefined,
category: standard.category || "input",
defaultConfig: (standard.default_config as Record<string, unknown>) || {},
validationRules: (standard.validation_rules as Record<string, unknown>) || undefined,
defaultStyle: (standard.default_style as Record<string, unknown>) || undefined,
inputProperties: (standard.input_properties as Record<string, unknown>) || undefined,
componentName: standard.component_name || undefined,
configPanel: standard.config_panel || undefined,
sortOrder: standard.sort_order || 0,
isActive: standard.is_active === "Y",
});
/**
* ColumnTypeInfo를 UnifiedColumnInfo로 변환
*/
export const mapColumnTypeInfoToUnified = (columnInfo: ColumnTypeInfo): UnifiedColumnInfo => ({
tableName: columnInfo.tableName || "",
columnName: columnInfo.columnName,
displayName: columnInfo.displayName,
dataType: columnInfo.dataType,
dbType: columnInfo.dbType,
webType: columnInfo.webType,
inputType: columnInfo.inputType || "direct",
detailSettings: columnInfo.detailSettings ? JSON.parse(columnInfo.detailSettings) : undefined,
description: columnInfo.description,
isNullable: columnInfo.isNullable === "Y",
isPrimaryKey: columnInfo.isPrimaryKey,
defaultValue: columnInfo.defaultValue,
maxLength: columnInfo.maxLength,
numericPrecision: columnInfo.numericPrecision,
numericScale: columnInfo.numericScale,
isVisible: columnInfo.isVisible,
displayOrder: columnInfo.displayOrder,
codeCategory: columnInfo.codeCategory,
codeValue: columnInfo.codeValue,
referenceTable: columnInfo.referenceTable,
referenceColumn: columnInfo.referenceColumn,
displayColumn: columnInfo.displayColumn,
});
/**
* UnifiedColumnInfo를 ColumnTypeInfo로 변환
*/
export const mapUnifiedToColumnTypeInfo = (unified: UnifiedColumnInfo): ColumnTypeInfo => ({
tableName: unified.tableName,
columnName: unified.columnName,
displayName: unified.displayName,
dataType: unified.dataType,
dbType: unified.dbType,
webType: unified.webType,
inputType: unified.inputType,
detailSettings: unified.detailSettings ? JSON.stringify(unified.detailSettings) : "{}",
description: unified.description || "",
isNullable: unified.isNullable ? "Y" : "N",
isPrimaryKey: unified.isPrimaryKey,
defaultValue: unified.defaultValue,
maxLength: unified.maxLength,
numericPrecision: unified.numericPrecision,
numericScale: unified.numericScale,
isVisible: unified.isVisible,
displayOrder: unified.displayOrder,
codeCategory: unified.codeCategory,
codeValue: unified.codeValue,
referenceTable: unified.referenceTable,
referenceColumn: unified.referenceColumn,
displayColumn: unified.displayColumn,
});
// ===== 타입 가드 함수들 =====
/**
* 웹타입이 참조 타입인지 확인
*/
export const isReferenceWebType = (webType: string): boolean => {
return ["code", "entity"].includes(webType);
};
/**
* 웹타입이 숫자 타입인지 확인
*/
export const isNumericWebType = (webType: string): boolean => {
return ["number", "decimal"].includes(webType);
};
/**
* 웹타입이 날짜 타입인지 확인
*/
export const isDateWebType = (webType: string): boolean => {
return ["date", "datetime"].includes(webType);
};
/**
* 웹타입이 선택 타입인지 확인
*/
export const isSelectWebType = (webType: string): boolean => {
return ["select", "dropdown", "radio", "checkbox", "boolean"].includes(webType);
};
/**
* 컬럼이 필수 필드인지 확인
*/
export const isRequiredColumn = (column: UnifiedColumnInfo): boolean => {
return !column.isNullable || column.isPrimaryKey;
};
/**
* 컬럼이 시스템 컬럼인지 확인
*/
export const isSystemColumn = (columnName: string): boolean => {
const systemColumns = [
"created_date",
"updated_date",
"created_by",
"updated_by",
"is_active",
"company_code",
"version",
"id",
];
return systemColumns.includes(columnName.toLowerCase());
};

View File

@@ -0,0 +1,355 @@
/**
* 🎯 통합 핵심 타입 정의
*
* 모든 시스템에서 공통으로 사용하는 핵심 타입들을 중앙집중식으로 관리합니다.
* - 화면관리 시스템
* - 제어관리 시스템
* - 테이블 타입관리 시스템
*/
// ===== 핵심 공통 타입들 =====
/**
* 통합 WebType 정의
* 모든 시스템에서 사용하는 웹 입력 타입의 표준 정의
*/
export type WebType =
// 기본 텍스트 입력
| "text"
| "textarea"
| "email"
| "tel"
| "url"
// 숫자 입력
| "number"
| "decimal"
// 날짜/시간 입력
| "date"
| "datetime"
// 선택 입력
| "select"
| "dropdown"
| "radio"
| "checkbox"
| "boolean"
// 특수 입력
| "code" // 공통코드 참조
| "entity" // 엔티티 참조
| "file" // 파일 업로드
| "button"; // 버튼 컴포넌트
/**
* 동적 WebType 지원
* DB에서 동적으로 로드되는 웹타입도 지원
*/
export type DynamicWebType = WebType | string;
/**
* 통합 ButtonActionType 정의
* 모든 버튼 액션 타입의 표준 정의
*/
export type ButtonActionType =
// 데이터 조작
| "save"
| "cancel"
| "delete"
| "edit"
| "add"
// 검색 및 초기화
| "search"
| "reset"
| "submit"
// UI 제어
| "close"
| "popup"
| "modal"
// 네비게이션
| "navigate"
| "newWindow"
// 제어관리 전용
| "control";
/**
* 컴포넌트 타입 정의
*/
export type ComponentType =
| "container"
| "row"
| "column"
| "widget"
| "group"
| "datatable"
| "file"
| "area"
| "layout";
/**
* 기본 위치 정보
*/
export interface Position {
x: number;
y: number;
z?: number;
}
/**
* 기본 크기 정보
*/
export interface Size {
width: number;
height: number;
}
/**
* 공통 스타일 속성
*/
export interface CommonStyle {
// 여백
margin?: string;
marginTop?: string;
marginRight?: string;
marginBottom?: string;
marginLeft?: string;
padding?: string;
paddingTop?: string;
paddingRight?: string;
paddingBottom?: string;
paddingLeft?: string;
// 테두리
border?: string;
borderWidth?: string;
borderStyle?: string;
borderColor?: string;
borderRadius?: string;
// 배경
backgroundColor?: string;
backgroundImage?: string;
// 텍스트
color?: string;
fontSize?: string;
fontWeight?: string;
fontFamily?: string;
textAlign?: "left" | "center" | "right" | "justify";
lineHeight?: string;
// 라벨 스타일
labelFontSize?: string;
labelColor?: string;
labelFontWeight?: string;
labelMarginBottom?: string;
// 레이아웃
display?: string;
width?: string;
height?: string;
minWidth?: string;
minHeight?: string;
maxWidth?: string;
maxHeight?: string;
// 기타
opacity?: string;
zIndex?: string;
overflow?: "visible" | "hidden" | "scroll" | "auto";
}
/**
* 검증 규칙
*/
export interface ValidationRule {
type: "required" | "minLength" | "maxLength" | "pattern" | "min" | "max" | "email" | "url";
value?: unknown;
message: string;
}
/**
* 조건 연산자
*/
export type ConditionOperator =
| "="
| "!="
| ">"
| "<"
| ">="
| "<="
| "LIKE"
| "IN"
| "NOT IN"
| "IS NULL"
| "IS NOT NULL";
/**
* 기본 API 응답 형태
*/
export interface BaseApiResponse<T = unknown> {
success: boolean;
data?: T;
message?: string;
error?: {
code: string;
message: string;
details?: unknown;
};
}
/**
* 페이지네이션 응답
*/
export interface PaginatedResponse<T> {
data: T[];
total: number;
page: number;
size: number;
totalPages: number;
}
/**
* 회사 코드 (모든 데이터에 공통)
*/
export type CompanyCode = string;
/**
* 활성 상태 (DB의 Y/N을 boolean으로 변환)
*/
export type ActiveStatus = "Y" | "N";
/**
* boolean으로 변환하는 유틸리티 타입
*/
export type BooleanFromYN<T extends ActiveStatus> = T extends "Y" ? true : false;
// ===== 공통 유틸리티 타입들 =====
/**
* 선택적 ID (신규 생성 시에는 없고, 기존 데이터는 있음)
*/
export type OptionalId<T> = Omit<T, "id"> & { id?: string | number };
/**
* 타임스탬프 필드
*/
export interface TimestampFields {
createdDate?: Date;
updatedDate?: Date;
createdBy?: string;
updatedBy?: string;
}
/**
* 감사 필드 (DB 표준)
*/
export interface AuditFields {
created_date?: Date;
updated_date?: Date;
created_by?: string;
updated_by?: string;
is_active?: ActiveStatus;
}
// ===== 이벤트 타입들 =====
/**
* 웹타입 이벤트
*/
export interface WebTypeEvent {
type: "change" | "blur" | "focus" | "click" | "submit";
value: unknown;
field?: string;
component?: unknown;
}
/**
* 컴포넌트 이벤트
*/
export interface ComponentEvent {
type: "select" | "drag" | "drop" | "resize" | "delete" | "update";
componentId: string;
data?: unknown;
}
// ===== 타입 가드용 유틸리티 =====
/**
* 문자열이 WebType인지 확인
*/
export const isWebType = (value: string): value is WebType => {
const webTypes: WebType[] = [
"text",
"textarea",
"email",
"tel",
"url",
"number",
"decimal",
"date",
"datetime",
"select",
"dropdown",
"radio",
"checkbox",
"boolean",
"code",
"entity",
"file",
"button",
];
return webTypes.includes(value as WebType);
};
/**
* 문자열이 ButtonActionType인지 확인
*/
export const isButtonActionType = (value: string): value is ButtonActionType => {
const actionTypes: ButtonActionType[] = [
"save",
"cancel",
"delete",
"edit",
"add",
"search",
"reset",
"submit",
"close",
"popup",
"modal",
"navigate",
"newWindow",
"control",
];
return actionTypes.includes(value as ButtonActionType);
};
/**
* 문자열이 ComponentType인지 확인
*/
export const isComponentType = (value: string): value is ComponentType => {
const componentTypes: ComponentType[] = [
"container",
"row",
"column",
"widget",
"group",
"datatable",
"file",
"area",
"layout",
];
return componentTypes.includes(value as ComponentType);
};
/**
* Y/N 문자열을 boolean으로 변환
*/
export const ynToBoolean = (value: ActiveStatus | string | undefined): boolean => {
return value === "Y";
};
/**
* boolean을 Y/N 문자열로 변환
*/
export const booleanToYN = (value: boolean): ActiveStatus => {
return value ? "Y" : "N";
};

View File

@@ -0,0 +1,195 @@
/**
* 통합 웹 타입 정의
* 프론트엔드와 백엔드에서 공통으로 사용하는 웹 타입 정의
*
* 주의: 이 파일을 수정할 때는 반드시 백엔드 타입도 함께 업데이트 해야 합니다.
*/
// 기본 웹 타입 (DB web_type_standards와 동기화)
export type BaseWebType =
| "text" // 일반 텍스트
| "number" // 숫자 (정수)
| "decimal" // 소수점 숫자
| "date" // 날짜
| "datetime" // 날짜시간
| "time" // 시간
| "textarea" // 여러줄 텍스트
| "select" // 선택박스
| "dropdown" // 드롭다운 (select와 동일)
| "checkbox" // 체크박스
| "radio" // 라디오버튼
| "boolean" // 불린값
| "file" // 파일 업로드
| "email" // 이메일
| "tel" // 전화번호
| "url" // URL
| "password" // 패스워드
| "code" // 공통코드 참조
| "entity" // 엔티티 참조
| "button"; // 버튼
// 레거시 지원용 (기존 시스템과의 호환성)
export type LegacyWebType = "text_area"; // textarea와 동일
// 전체 웹 타입 (DB 동적 로딩 지원)
export type WebType = BaseWebType | LegacyWebType;
// 동적 웹 타입 (런타임에 DB에서 로드되는 타입 포함)
export type DynamicWebType = WebType | string;
// 웹 타입 카테고리
export type WebTypeCategory =
| "input" // 입력 컴포넌트
| "selection" // 선택 컴포넌트
| "display" // 표시 컴포넌트
| "action" // 액션 컴포넌트
| "upload" // 업로드 컴포넌트
| "reference"; // 참조 컴포넌트
// 웹 타입 정보
export interface WebTypeInfo {
webType: WebType;
typeName: string;
typeNameEng?: string;
description?: string;
category: WebTypeCategory;
defaultConfig?: Record<string, any>;
validationRules?: Record<string, any>;
componentName?: string;
configPanel?: string;
isActive: boolean;
}
// 웹 타입 매핑 (레거시 지원)
export const WEB_TYPE_MAPPINGS: Record<LegacyWebType, BaseWebType> = {
text_area: "textarea",
};
// 웹 타입 정규화 함수
export const normalizeWebType = (webType: DynamicWebType): WebType => {
if (webType in WEB_TYPE_MAPPINGS) {
return WEB_TYPE_MAPPINGS[webType as LegacyWebType];
}
return webType as WebType;
};
// 웹 타입 검증 함수
export const isValidWebType = (webType: string): webType is WebType => {
return (
[
"text",
"number",
"decimal",
"date",
"datetime",
"time",
"textarea",
"select",
"dropdown",
"checkbox",
"radio",
"boolean",
"file",
"email",
"tel",
"url",
"password",
"code",
"entity",
"button",
"text_area", // 레거시 지원
] as string[]
).includes(webType);
};
// DB 타입과 웹 타입 매핑
export const DB_TYPE_TO_WEB_TYPE: Record<string, WebType> = {
// 텍스트 타입
"character varying": "text",
varchar: "text",
text: "textarea",
char: "text",
// 숫자 타입
integer: "number",
bigint: "number",
smallint: "number",
serial: "number",
bigserial: "number",
numeric: "decimal",
decimal: "decimal",
real: "decimal",
"double precision": "decimal",
// 날짜/시간 타입
date: "date",
timestamp: "datetime",
"timestamp with time zone": "datetime",
"timestamp without time zone": "datetime",
time: "time",
"time with time zone": "time",
"time without time zone": "time",
// 불린 타입
boolean: "boolean",
// JSON 타입 (텍스트로 처리)
json: "textarea",
jsonb: "textarea",
// 배열 타입 (텍스트로 처리)
ARRAY: "textarea",
// UUID 타입
uuid: "text",
};
// 웹 타입별 기본 설정
export const WEB_TYPE_DEFAULT_CONFIGS: Record<WebType, Record<string, any>> = {
text: { maxLength: 255, placeholder: "텍스트를 입력하세요" },
number: { min: 0, max: 2147483647, step: 1 },
decimal: { min: 0, step: 0.01, decimalPlaces: 2 },
date: { format: "YYYY-MM-DD" },
datetime: { format: "YYYY-MM-DD HH:mm:ss", showTime: true },
time: { format: "HH:mm:ss" },
textarea: { rows: 4, cols: 50, maxLength: 1000 },
select: { placeholder: "선택하세요", searchable: false },
dropdown: { placeholder: "선택하세요", searchable: true },
checkbox: { defaultChecked: false },
radio: { inline: false },
boolean: { trueValue: true, falseValue: false },
file: { multiple: false, preview: true },
email: { placeholder: "이메일을 입력하세요" },
tel: { placeholder: "전화번호를 입력하세요" },
url: { placeholder: "URL을 입력하세요" },
password: { placeholder: "비밀번호를 입력하세요" },
code: { placeholder: "코드를 선택하세요", searchable: true },
entity: { placeholder: "항목을 선택하세요", searchable: true },
button: { variant: "default" },
text_area: { rows: 4, cols: 50, maxLength: 1000 }, // 레거시 지원
};
// 웹 타입별 검증 규칙
export const WEB_TYPE_VALIDATION_RULES: Record<WebType, Record<string, any>> = {
text: { type: "string", trim: true },
number: { type: "number", integer: true },
decimal: { type: "number", float: true },
date: { type: "date", format: "YYYY-MM-DD" },
datetime: { type: "datetime" },
time: { type: "time" },
textarea: { type: "string", multiline: true },
select: { type: "string", options: true },
dropdown: { type: "string", options: true },
checkbox: { type: "boolean" },
radio: { type: "string", options: true },
boolean: { type: "boolean" },
file: { type: "file" },
email: { type: "email" },
tel: { type: "tel" },
url: { type: "url" },
password: { type: "string", password: true },
code: { type: "string", code: true },
entity: { type: "string", entity: true },
button: { type: "action" },
text_area: { type: "string", multiline: true }, // 레거시 지원
};