5f945363b2
V2DateConfigPanel(262줄) + V2Date 본체(1046줄) + V2DateConfig 타입 + lib/registry/components/v2-date/ + DynamicComponentRenderer 의 LEGACY alias 모두 삭제. 솔루션 정의 단계 정확한 1안. 데이터 모델 - FieldType / InputFieldType union 에 time, daterange 2종 추가하여 12종 확장 - canonical 키: dateFormat / minDate / maxDate / showToday / weekStart / dateDefault / rangePresets / maxRangeDays - 옛 v2-date 의 snake_case (min_date / max_date / show_today) 와 dateType / type / range / format 키 충돌 종결 런타임 (InputComponent + pickers.tsx) - V2Date 본체에 있던 SingleDatePicker / DateTimePicker / TimePicker / RangeDatePicker 4 picker 를 input/pickers.tsx 로 통합 - InputComponent 의 case "date"/"datetime" 의 native input 분기를 datepicker 로 교체하고 case "time"/"daterange" 신규 추가 - type 결정 로직에 inputType prop 인식 (DB input_type 매핑) → date/time 입력이 text 로 fallback 되던 silent breakage 해결 FC 계층 - FieldRenderer / CellRenderer / FcSearch 에 time / daterange 분기 추가 - TimeField, DateRangeField 신규 컴포넌트 - adapters.normalizeType allowed 배열 확장 ConfigPanel - InvFieldConfigPanel.DateOptions 에 showToday CPRow + CPSwitch 신규 - 옛 호환 코드 (showSeconds:ss 보정 / dateType-format 격상 등) 모두 제거 - InvInputConfigPanel.TYPE_OPTIONS / 날짜 옵션 분기에 time/daterange 추가 dead code 삭제 14곳 + 잔존 정리 - V2Date / V2DateConfigPanel / V2DateConfig / lib/registry/v2-date/ 폴더 - LEGACY_TO_UNIFIED / CONFIG_PANEL_MAP / CONFIG_PANEL_ALIAS / register / V2PropertiesPanel hardcoded require / config-panels barrel / hidden 목록 - componentConfig 스키마, templateMigrate, webTypeMapping, DynamicConfigPanel - withContainerQuery.css 의 v2-date 컨테이너 룰 - db/migrations/so_modal_layout(_kr).json 의 v2-date → input + type=date 37 files, +287 / -854. Codex GO 판정 기준 (2회 NO-GO 후 FC 계층 / inputType prop / FieldType union / 잔존 v2-date / console.log 모두 처리 후 GO). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
567 lines
14 KiB
TypeScript
567 lines
14 KiB
TypeScript
/**
|
|
* V2 컴포넌트 타입 정의
|
|
*
|
|
* 9개의 통합 컴포넌트 시스템을 위한 타입 정의
|
|
* - V2Input
|
|
* - V2Select
|
|
* - V2Text
|
|
* - V2Media
|
|
* - V2List
|
|
* - V2Layout
|
|
* - V2Group
|
|
* - V2Biz
|
|
* - V2Hierarchy
|
|
*
|
|
* V2Date 는 폐기됨 — InvField triple type=date 의 4 format(date/datetime/time/range) 으로 통일.
|
|
* 런타임은 InputComponent + lib/registry/components/input/pickers.tsx.
|
|
*/
|
|
|
|
import { Position, Size, CommonStyle, ValidationRule } from "./v2-core";
|
|
|
|
// ===== 공통 타입 =====
|
|
|
|
/**
|
|
* V2 컴포넌트 타입
|
|
*/
|
|
export type V2ComponentType =
|
|
| "V2Input"
|
|
| "V2Select"
|
|
| "V2Text"
|
|
| "V2Media"
|
|
| "V2List"
|
|
| "V2Layout"
|
|
| "V2Group"
|
|
| "V2Biz"
|
|
| "V2Hierarchy";
|
|
|
|
/**
|
|
* 조건부 렌더링 설정
|
|
*/
|
|
export interface ConditionalConfig {
|
|
enabled: boolean;
|
|
field: string; // 참조 필드
|
|
operator: "=" | "!=" | ">" | "<" | "in" | "notIn" | "isEmpty" | "isNotEmpty";
|
|
value: unknown;
|
|
action: "show" | "hide" | "disable" | "enable";
|
|
}
|
|
|
|
/**
|
|
* 자동 입력 설정
|
|
*/
|
|
export interface AutoFillConfig {
|
|
enabled: boolean;
|
|
source_table: string;
|
|
filter_column: string;
|
|
user_field: "companyCode" | "userId" | "deptCode";
|
|
display_column: string;
|
|
}
|
|
|
|
/**
|
|
* 연쇄 관계 설정
|
|
*/
|
|
export interface CascadingConfig {
|
|
parent_field: string;
|
|
filter_column: string;
|
|
clear_on_change?: boolean;
|
|
}
|
|
|
|
/**
|
|
* 상호 배제 설정
|
|
*/
|
|
export interface MutualExclusionConfig {
|
|
enabled: boolean;
|
|
target_field: string;
|
|
type: "exclusive" | "inclusive";
|
|
}
|
|
|
|
/**
|
|
* 공통 V2 컴포넌트 속성
|
|
*/
|
|
export interface V2BaseProps {
|
|
id: string;
|
|
label?: string;
|
|
required?: boolean;
|
|
readonly?: boolean;
|
|
disabled?: boolean;
|
|
// 데이터 바인딩
|
|
table_name?: string;
|
|
column_name?: string;
|
|
// 위치 및 크기
|
|
position?: Position;
|
|
size?: Size;
|
|
// 스타일
|
|
style?: CommonStyle;
|
|
// 조건부 및 자동화
|
|
conditional?: ConditionalConfig;
|
|
auto_fill?: AutoFillConfig;
|
|
// 유효성 검사
|
|
validation?: ValidationRule[];
|
|
// 디자인 모드 (클릭 방지)
|
|
isDesignMode?: boolean;
|
|
}
|
|
|
|
// ===== V2Input =====
|
|
|
|
export type V2InputType = "text" | "number" | "password" | "slider" | "color" | "button";
|
|
export type V2InputFormat = "none" | "email" | "tel" | "url" | "currency" | "biz_no";
|
|
|
|
export interface V2InputConfig {
|
|
type: V2InputType;
|
|
input_type?: V2InputType; // type 별칭
|
|
format?: V2InputFormat;
|
|
mask?: string;
|
|
placeholder?: string;
|
|
// 숫자 전용
|
|
min?: number;
|
|
max?: number;
|
|
step?: number;
|
|
// 버튼 전용
|
|
button_text?: string;
|
|
button_variant?: "default" | "destructive" | "outline" | "secondary" | "ghost";
|
|
onClick?: () => void;
|
|
// 테이블명 (채번용)
|
|
table_name?: string;
|
|
}
|
|
|
|
export interface V2InputProps extends V2BaseProps {
|
|
v2Type: "V2Input";
|
|
config: V2InputConfig;
|
|
value?: string | number;
|
|
onChange?: (value: string | number) => void;
|
|
}
|
|
|
|
// ===== V2Select =====
|
|
|
|
export type V2SelectMode = "dropdown" | "combobox" | "radio" | "check" | "tag" | "tagbox" | "toggle" | "swap";
|
|
export type V2SelectSource = "static" | "code" | "db" | "api" | "entity" | "category";
|
|
|
|
export interface SelectOption {
|
|
value: string;
|
|
label: string;
|
|
}
|
|
|
|
/**
|
|
* V2Select 필터 조건
|
|
* 옵션 데이터를 조회할 때 적용할 WHERE 조건
|
|
*/
|
|
export interface V2SelectFilter {
|
|
column: string;
|
|
operator: "=" | "!=" | ">" | "<" | ">=" | "<=" | "in" | "notIn" | "like" | "isNull" | "isNotNull";
|
|
/** 값 유형: static=고정값, field=다른 폼 필드 참조, user=로그인 사용자 정보 */
|
|
value_type?: "static" | "field" | "user";
|
|
/** static일 때 고정값 */
|
|
value?: unknown;
|
|
/** field일 때 참조할 폼 필드명 (column_name) */
|
|
field_ref?: string;
|
|
/** user일 때 참조할 사용자 필드 */
|
|
user_field?: "companyCode" | "userId" | "deptCode" | "userName";
|
|
}
|
|
|
|
export interface V2SelectConfig {
|
|
mode: V2SelectMode;
|
|
source: V2SelectSource | "distinct" | "select"; // distinct/select 추가 (테이블 컬럼에서 자동 로드)
|
|
// 정적 옵션 (source: static)
|
|
options?: SelectOption[];
|
|
// 코드 그룹 (source: code)
|
|
code_group?: string;
|
|
code_category?: string; // code_group 별칭
|
|
// DB 연결 (source: db)
|
|
table?: string;
|
|
value_column?: string;
|
|
label_column?: string;
|
|
// 옵션 필터 조건 (모든 source에서 사용 가능)
|
|
filters?: V2SelectFilter[];
|
|
// 엔티티 연결 (source: entity)
|
|
entity_table?: string;
|
|
entity_value_field?: string;
|
|
entity_label_field?: string;
|
|
entity_value_column?: string; // alias for entity_value_field
|
|
entity_label_column?: string; // alias for entity_label_field
|
|
// API 연결 (source: api)
|
|
api_endpoint?: string;
|
|
// 카테고리 연결 (source: category) - 레거시, code로 자동 변환됨
|
|
category_table?: string;
|
|
category_column?: string;
|
|
// 공통 옵션
|
|
searchable?: boolean;
|
|
multiple?: boolean;
|
|
max_select?: number;
|
|
allow_clear?: boolean;
|
|
// 연쇄 관계
|
|
cascading?: CascadingConfig;
|
|
// 상호 배제
|
|
mutual_exclusion?: MutualExclusionConfig;
|
|
// 계층 코드 연쇄 선택 (source: code일 때 계층 구조 사용)
|
|
hierarchical?: boolean; // 계층 구조 사용 여부
|
|
parent_field?: string; // 부모 값을 참조할 필드 (다른 컴포넌트의 column_name)
|
|
}
|
|
|
|
export interface V2SelectProps extends V2BaseProps {
|
|
v2Type: "V2Select";
|
|
config: V2SelectConfig;
|
|
value?: string | string[];
|
|
onChange?: (value: string | string[]) => void;
|
|
onFormDataChange?: (fieldName: string, value: any) => void;
|
|
form_data?: Record<string, any>;
|
|
}
|
|
|
|
// ===== V2Text =====
|
|
|
|
export type V2TextMode = "simple" | "rich" | "code" | "markdown";
|
|
|
|
export interface V2TextConfig {
|
|
mode: V2TextMode;
|
|
rows?: number;
|
|
max_length?: number;
|
|
placeholder?: string;
|
|
resize?: "none" | "vertical" | "horizontal" | "both";
|
|
}
|
|
|
|
export interface V2TextProps extends V2BaseProps {
|
|
v2Type: "V2Text";
|
|
config: V2TextConfig;
|
|
value?: string;
|
|
onChange?: (value: string) => void;
|
|
}
|
|
|
|
// ===== V2Media =====
|
|
|
|
export type V2MediaType = "file" | "image" | "video" | "audio";
|
|
|
|
export interface V2MediaConfig {
|
|
type: V2MediaType;
|
|
multiple?: boolean;
|
|
accept?: string;
|
|
max_size?: number;
|
|
preview?: boolean;
|
|
upload_endpoint?: string;
|
|
// 레거시 FileUpload 호환 설정
|
|
doc_type?: string;
|
|
doc_type_name?: string;
|
|
show_file_list?: boolean;
|
|
drag_drop?: boolean;
|
|
}
|
|
|
|
export interface V2MediaProps extends V2BaseProps {
|
|
v2Type?: "V2Media";
|
|
config?: V2MediaConfig;
|
|
value?: string | string[]; // 파일 URL 또는 배열
|
|
onChange?: (value: string | string[]) => void;
|
|
// 레거시 FileUpload 호환 props
|
|
form_data?: Record<string, any>;
|
|
column_name?: string;
|
|
table_name?: string;
|
|
// 부모 컴포넌트 시그니처: (fieldName, value) 형식
|
|
onFormDataChange?: (fieldName: string, value: any) => void;
|
|
isDesignMode?: boolean;
|
|
isInteractive?: boolean;
|
|
onUpdate?: (updates: Partial<any>) => void;
|
|
}
|
|
|
|
// ===== V2List =====
|
|
|
|
export type V2ListViewMode = "table" | "card" | "kanban" | "list";
|
|
|
|
export interface ListColumn {
|
|
field: string;
|
|
header: string;
|
|
width?: number;
|
|
sortable?: boolean;
|
|
filterable?: boolean;
|
|
editable?: boolean;
|
|
format?: string;
|
|
}
|
|
|
|
export interface V2ListCardConfig {
|
|
title_column?: string;
|
|
subtitle_column?: string;
|
|
description_column?: string;
|
|
image_column?: string;
|
|
cards_per_row?: number;
|
|
card_spacing?: number;
|
|
show_actions?: boolean;
|
|
}
|
|
|
|
export interface V2ListConfig {
|
|
view_mode: V2ListViewMode;
|
|
editable?: boolean;
|
|
searchable?: boolean;
|
|
pageable?: boolean;
|
|
page_size?: number;
|
|
sortable?: boolean;
|
|
pagination?: boolean;
|
|
source?: "static" | "db" | "api"; // 데이터 소스 타입
|
|
columns?: ListColumn[];
|
|
modal?: boolean;
|
|
card_config?: V2ListCardConfig;
|
|
// 데이터 소스
|
|
data_source?: {
|
|
table?: string;
|
|
api?: string;
|
|
filters?: Array<{ column: string; operator: string; value: unknown }>;
|
|
};
|
|
}
|
|
|
|
export interface V2ListProps extends V2BaseProps {
|
|
v2Type: "V2List";
|
|
config: V2ListConfig;
|
|
data?: Record<string, unknown>[];
|
|
selected_rows?: Record<string, unknown>[];
|
|
onRowSelect?: (rows: Record<string, unknown>[]) => void;
|
|
onRowClick?: (row: Record<string, unknown>) => void;
|
|
}
|
|
|
|
// ===== V2Layout =====
|
|
|
|
export type V2LayoutType = "grid" | "split" | "flex" | "divider" | "screen-embed";
|
|
|
|
export interface V2LayoutConfig {
|
|
type: V2LayoutType;
|
|
columns?: number; // 12컬럼 시스템에서 실제 표시할 컬럼 수 (1-12)
|
|
gap?: string;
|
|
split_ratio?: number[];
|
|
direction?: "horizontal" | "vertical";
|
|
use_12_column?: boolean; // 12컬럼 시스템 사용 여부 (기본 true)
|
|
// screen-embed 전용
|
|
screen_id?: number;
|
|
}
|
|
|
|
export interface V2LayoutProps extends V2BaseProps {
|
|
v2Type: "V2Layout";
|
|
config: V2LayoutConfig;
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
// ===== V2Group =====
|
|
|
|
export type V2GroupType = "tabs" | "accordion" | "section" | "card-section" | "modal" | "form-modal";
|
|
|
|
export interface TabItem {
|
|
id: string;
|
|
title: string;
|
|
content?: React.ReactNode;
|
|
}
|
|
|
|
export interface V2GroupConfig {
|
|
type: V2GroupType;
|
|
title?: string;
|
|
collapsible?: boolean;
|
|
default_expanded?: boolean;
|
|
default_open?: boolean; // default_expanded 별칭
|
|
// 탭 전용
|
|
tabs?: TabItem[];
|
|
active_tab?: string;
|
|
// 모달 전용
|
|
modal_size?: "sm" | "md" | "lg" | "xl";
|
|
}
|
|
|
|
export interface V2GroupProps extends V2BaseProps {
|
|
v2Type: "V2Group";
|
|
config: V2GroupConfig;
|
|
children?: React.ReactNode;
|
|
open?: boolean;
|
|
onOpenChange?: (open: boolean) => void;
|
|
}
|
|
|
|
// ===== V2Biz =====
|
|
|
|
export type V2BizType = "flow" | "rack" | "map" | "numbering" | "category" | "mapping" | "related-buttons";
|
|
|
|
export interface V2BizConfig {
|
|
type: V2BizType;
|
|
// 각 타입별 설정은 제네릭하게 처리
|
|
config?: Record<string, unknown>;
|
|
// 플로우 전용
|
|
flow_config?: {
|
|
flow_id?: number;
|
|
show_progress?: boolean;
|
|
};
|
|
}
|
|
|
|
export interface V2BizProps extends V2BaseProps {
|
|
v2Type: "V2Biz";
|
|
config: V2BizConfig;
|
|
}
|
|
|
|
// ===== V2Hierarchy =====
|
|
|
|
export type V2HierarchyType = "tree" | "org" | "bom" | "cascading";
|
|
export type V2HierarchyViewMode = "tree" | "table" | "indent" | "dropdown";
|
|
|
|
export interface HierarchyNode {
|
|
id: string;
|
|
parent_id?: string;
|
|
label: string;
|
|
children?: HierarchyNode[];
|
|
data?: Record<string, unknown>;
|
|
}
|
|
|
|
export interface V2HierarchyConfig {
|
|
type: V2HierarchyType;
|
|
view_mode: V2HierarchyViewMode;
|
|
source?: string; // 계층 그룹 코드
|
|
editable?: boolean;
|
|
draggable?: boolean;
|
|
show_qty?: boolean; // BOM 전용
|
|
max_level?: number;
|
|
// 데이터 소스
|
|
data_source?: {
|
|
table?: string;
|
|
id_column?: string;
|
|
parent_column?: string;
|
|
label_column?: string;
|
|
};
|
|
}
|
|
|
|
export interface V2HierarchyProps extends V2BaseProps {
|
|
v2Type: "V2Hierarchy";
|
|
config: V2HierarchyConfig;
|
|
data?: HierarchyNode[];
|
|
selected_node?: HierarchyNode;
|
|
onNodeSelect?: (node: HierarchyNode) => void;
|
|
onNodeMove?: (nodeId: string, newParentId: string) => void;
|
|
}
|
|
|
|
// ===== 통합 Props 유니온 타입 =====
|
|
|
|
export type V2ComponentProps =
|
|
| V2InputProps
|
|
| V2SelectProps
|
|
| V2TextProps
|
|
| V2MediaProps
|
|
| V2ListProps
|
|
| V2LayoutProps
|
|
| V2GroupProps
|
|
| V2BizProps
|
|
| V2HierarchyProps;
|
|
|
|
// ===== 타입 가드 =====
|
|
|
|
export function isV2Input(props: V2ComponentProps): props is V2InputProps {
|
|
return props.v2Type === "V2Input";
|
|
}
|
|
|
|
export function isV2Select(props: V2ComponentProps): props is V2SelectProps {
|
|
return props.v2Type === "V2Select";
|
|
}
|
|
|
|
export function isV2Text(props: V2ComponentProps): props is V2TextProps {
|
|
return props.v2Type === "V2Text";
|
|
}
|
|
|
|
export function isV2Media(props: V2ComponentProps): props is V2MediaProps {
|
|
return props.v2Type === "V2Media";
|
|
}
|
|
|
|
export function isV2List(props: V2ComponentProps): props is V2ListProps {
|
|
return props.v2Type === "V2List";
|
|
}
|
|
|
|
export function isV2Layout(props: V2ComponentProps): props is V2LayoutProps {
|
|
return props.v2Type === "V2Layout";
|
|
}
|
|
|
|
export function isV2Group(props: V2ComponentProps): props is V2GroupProps {
|
|
return props.v2Type === "V2Group";
|
|
}
|
|
|
|
export function isV2Biz(props: V2ComponentProps): props is V2BizProps {
|
|
return props.v2Type === "V2Biz";
|
|
}
|
|
|
|
export function isV2Hierarchy(props: V2ComponentProps): props is V2HierarchyProps {
|
|
return props.v2Type === "V2Hierarchy";
|
|
}
|
|
|
|
// ===== JSON Schema 타입 =====
|
|
|
|
export interface JSONSchemaProperty {
|
|
type: "string" | "number" | "boolean" | "array" | "object";
|
|
title?: string;
|
|
description?: string;
|
|
enum?: string[];
|
|
default?: unknown;
|
|
items?: JSONSchemaProperty;
|
|
properties?: Record<string, JSONSchemaProperty>;
|
|
required?: string[];
|
|
}
|
|
|
|
export interface V2ConfigSchema {
|
|
type: "object";
|
|
properties: Record<string, JSONSchemaProperty>;
|
|
required?: string[];
|
|
}
|
|
|
|
// ===== 레거시 컴포넌트 → V2 컴포넌트 매핑 =====
|
|
|
|
export const LEGACY_TO_V2_MAP: Record<string, V2ComponentType> = {
|
|
// Input 계열
|
|
"text-input": "V2Input",
|
|
"number-input": "V2Input",
|
|
"password-input": "V2Input",
|
|
|
|
// Select 계열
|
|
"select-basic": "V2Select",
|
|
"radio-basic": "V2Select",
|
|
"checkbox-basic": "V2Select",
|
|
"entity-search-input": "V2Select",
|
|
"autocomplete-search-input": "V2Select",
|
|
|
|
// Text 계열
|
|
"textarea-basic": "V2Text",
|
|
|
|
// Media 계열
|
|
"file-upload": "V2Media",
|
|
"image-widget": "V2Media",
|
|
|
|
// List 계열
|
|
"table-list": "V2List",
|
|
"table-search-widget": "V2List",
|
|
"modal-repeater-table": "V2List",
|
|
"repeater-field-group": "V2List",
|
|
"card-display": "V2List",
|
|
|
|
// Layout 계열
|
|
"split-panel-layout": "V2Layout",
|
|
"screen-split-panel": "V2Layout",
|
|
|
|
// Group 계열
|
|
"tabs-widget": "V2Group",
|
|
"section-paper": "V2Group",
|
|
"section-card": "V2Group",
|
|
"universal-form-modal": "V2Group",
|
|
|
|
// Biz 계열
|
|
"category-manager": "V2Biz",
|
|
"numbering-rule": "V2Biz",
|
|
"flow-widget": "V2Biz",
|
|
|
|
// Button (Input의 버튼 모드)
|
|
"button-primary": "V2Input",
|
|
};
|
|
|
|
// ===== 조건부 레이어 시스템 =====
|
|
|
|
/**
|
|
* 레이어 조건 설정
|
|
* 특정 필드값에 따라 레이어 활성화 여부를 결정
|
|
*/
|
|
export interface LayerCondition {
|
|
field: string; // 트리거 필드 (column_name 또는 탭ID)
|
|
operator: "=" | "!=" | "in" | "notIn" | "isEmpty" | "isNotEmpty";
|
|
value: string | string[]; // 비교값
|
|
}
|
|
|
|
/**
|
|
* 레이어 설정
|
|
* 특정 조건이 충족될 때 표시되는 컴포넌트들의 그룹
|
|
*/
|
|
export interface LayerConfig {
|
|
layer_id: string; // 고유 ID
|
|
layer_name: string; // 표시명 (설정용)
|
|
conditions: LayerCondition[]; // 조건 목록
|
|
condition_logic?: "AND" | "OR"; // 조건 조합 방식 (기본: AND)
|
|
target_components: string[]; // 표시할 컴포넌트 ID 목록
|
|
always_visible?: boolean; // 항상 표시 (조건 무시)
|
|
}
|