4a8413000b
Build & Deploy to K8s / build-and-deploy (push) Failing after 11m17s
Remove legacy v2 input/select and file/media runtimes, add canonical option/file loaders, and document Codex handoff.
1120 lines
36 KiB
TypeScript
1120 lines
36 KiB
TypeScript
// ============================================================================
|
|
// INVYONE 컴포넌트 규격 v1.0 — TypeScript 타입 정의
|
|
// ============================================================================
|
|
//
|
|
// 설계 원칙:
|
|
// 1. 하나의 FieldConfig가 모든 곳(테이블/폼/검색)에서 쓰인다
|
|
// 2. 컴포넌트는 DataPort로 통신한다
|
|
// 3. 설정은 BaseConfig → TypeConfig → InstanceConfig 3단계
|
|
//
|
|
// 기존 vex 타입(types/component.ts, types/screen.ts)과 의존성 없음 — 완전 독립
|
|
// ============================================================================
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 1. FieldConfig — 유일한 필드 규격
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* 필드 렌더링 방식을 결정하는 타입.
|
|
*
|
|
* - 테이블에서는 셀 포맷, 폼에서는 입력 위젯, 검색에서는 필터 UI로 매핑된다.
|
|
* - 새 타입 추가 시 이 union에 리터럴만 추가하면 된다.
|
|
*/
|
|
export type FieldType =
|
|
| 'text' // 일반 문자열
|
|
| 'number' // 숫자 (포맷팅)
|
|
| 'date' // 날짜
|
|
| 'datetime' // 날짜+시간
|
|
| 'time' // 시간만
|
|
| 'daterange' // 기간 (시작 ~ 끝)
|
|
| 'select' // 드롭다운 (options 배열)
|
|
| 'entity' // 참조 테이블 code-name 선택
|
|
| 'checkbox' // 체크박스
|
|
| 'textarea' // 장문
|
|
| 'file' // 파일 첨부
|
|
| 'code'; // 자동채번 (readonly)
|
|
|
|
/**
|
|
* entity 타입 필드의 참조 정보.
|
|
* DB FK 를 직접 걸 수 없는 경우 화면 설정에서 대상 테이블·저장 컬럼·표시 컬럼을 지정한다.
|
|
*/
|
|
export interface FieldRef {
|
|
/** 참조 대상 테이블 */
|
|
table: string;
|
|
/** 값으로 사용할 컬럼 (PK 등) */
|
|
valueColumn: string;
|
|
/** 화면에 표시할 컬럼 */
|
|
displayColumn: string;
|
|
/** 검색 가능한 표시 컬럼 목록 (명시 검색 UI가 사용할 수 있음) */
|
|
searchColumns?: string[];
|
|
/**
|
|
* 선택 시 다른 필드 자동 채움 매핑.
|
|
* key = 참조 테이블의 컬럼명, value = 폼의 target 필드 키.
|
|
* 예: `{ "customer_name": "buyer_name", "phone": "buyer_phone" }`
|
|
* → entity 선택 결과 row 의 customer_name → 폼의 buyer_name 필드에 set.
|
|
*/
|
|
autoFillMap?: Record<string, string>;
|
|
/**
|
|
* 옵션 결과 사전 필터. 예: `{ "is_active": "Y" }` → 활성 거래처만 노출.
|
|
*/
|
|
filter?: Record<string, any>;
|
|
/**
|
|
* 명시 검색 UI 안에 표시할 컬럼 목록. 미지정 시 displayColumn 하나만.
|
|
* 예: `["customer_code", "customer_name", "ceo_name", "phone"]`
|
|
*/
|
|
modalColumns?: string[];
|
|
}
|
|
|
|
/**
|
|
* select 타입의 선택지.
|
|
* - string이면 value=label로 해석 (단순 목록)
|
|
* - { value, label } 객체면 value를 저장, label을 표시 (코드값/표시값 분리)
|
|
*/
|
|
export type FieldOption = string | { value: string; label: string };
|
|
|
|
/**
|
|
* 모든 컴포넌트가 공유하는 유일한 필드 정의.
|
|
*
|
|
* 테이블 컬럼, 폼 입력, 검색 조건 전부 이 하나의 규격으로 표현한다.
|
|
* 렌더러가 `type`을 보고 적절한 UI를 그린다.
|
|
*
|
|
* vex의 ColumnConfig(354줄) + FilterConfig + FormField → 이 ~30줄로 통합.
|
|
*/
|
|
export interface FieldConfigCore {
|
|
/** DB 컬럼명 (필드의 유일 키) */
|
|
column: string;
|
|
/** 화면에 표시되는 라벨 */
|
|
label: string;
|
|
/** 렌더링 방식을 결정하는 필드 타입 */
|
|
type: FieldType;
|
|
/** 화면에 보이는지 여부 */
|
|
visible: boolean;
|
|
/** 표시 순서 (작을수록 먼저) */
|
|
order: number;
|
|
/** 필수 입력 여부 */
|
|
required: boolean;
|
|
/** 편집 가능 여부 */
|
|
editable: boolean;
|
|
}
|
|
|
|
/**
|
|
* 공통 표시/입력 속성.
|
|
*
|
|
* v0.1 기준:
|
|
* - `width`, `align` 은 table 쪽 표시 힌트
|
|
* - `defaultValue`, `placeholder` 는 form/search 입력 힌트
|
|
* - 컴포넌트 전용 레이아웃/동작 옵션은 FieldConfig 가 아니라 각 config 로 분리
|
|
*/
|
|
export interface FieldConfigCommonOptions {
|
|
/** 컬럼 너비 (px, 테이블에서 사용) */
|
|
width?: number;
|
|
/** 텍스트 정렬 */
|
|
align?: 'left' | 'center' | 'right';
|
|
/** 기본값 */
|
|
defaultValue?: unknown;
|
|
/** 입력 힌트 텍스트 */
|
|
placeholder?: string;
|
|
}
|
|
|
|
/**
|
|
* 타입별 확장 속성.
|
|
*
|
|
* 원칙:
|
|
* - select 전용은 `options`
|
|
* - entity 전용은 `ref`
|
|
* - number/date/datetime 표시 힌트는 `format`
|
|
* - 계산 규칙은 `computed`
|
|
*/
|
|
export interface FieldConfigTypeOptions {
|
|
/** select 타입: 선택지 목록 */
|
|
options?: FieldOption[];
|
|
/** entity 타입: 참조 테이블 code-name 정보 */
|
|
ref?: FieldRef;
|
|
/** 포맷 문자열 (number: '#,##0', date: 'YYYY-MM-DD' 등) */
|
|
format?: string;
|
|
/** 자동 계산 수식 (예: 'quantity * unit_price') */
|
|
computed?: string;
|
|
}
|
|
|
|
/**
|
|
* 메타 속성.
|
|
*
|
|
* 화면 공통 렌더링 계약보다는 저장/동작 힌트에 가깝다.
|
|
*/
|
|
export interface FieldConfigMeta {
|
|
/** PK 여부 */
|
|
pk?: boolean;
|
|
/** 시스템 필드 여부 (company_code 등, 폼에서 숨김) */
|
|
system?: boolean;
|
|
/** 검색 대상 여부 */
|
|
searchable?: boolean;
|
|
/** 정렬 가능 여부 */
|
|
sortable?: boolean;
|
|
}
|
|
|
|
export interface FieldConfig
|
|
extends FieldConfigCore,
|
|
FieldConfigCommonOptions,
|
|
FieldConfigTypeOptions,
|
|
FieldConfigMeta {}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 2. Component — 유일한 컴포넌트 규격
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* 지원하는 컴포넌트 종류.
|
|
*
|
|
* 새 컴포넌트 추가 시 이 union에 리터럴을 추가하고,
|
|
* ComponentTypeConfigMap에 대응하는 설정 타입을 등록한다.
|
|
*/
|
|
export type ComponentType =
|
|
| 'table' // 데이터 테이블 (목록)
|
|
| 'form' // 입력 폼
|
|
| 'search' // 검색 필터
|
|
| 'button' // 버튼 (단일)
|
|
| 'button-bar' // 버튼 그룹
|
|
| 'tabs' // 탭
|
|
| 'split-panel' // 분할 패널
|
|
| 'title' // 텍스트/제목
|
|
| 'stats' // 통계 카드
|
|
| 'divider' // 구분선
|
|
| 'pagination'; // 페이지네이션
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Position — 단일 자유배치 모델은 §7 FreePosition 참고
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* 컴포넌트의 데이터 소스 바인딩.
|
|
*/
|
|
export interface DataSource {
|
|
/** 바인딩 대상 테이블명 */
|
|
table: string;
|
|
/** 읽기/쓰기 모드 */
|
|
mode: 'read' | 'write' | 'readwrite';
|
|
}
|
|
|
|
/**
|
|
* 모든 컴포넌트의 기본 구조.
|
|
*
|
|
* 타입이 무엇이든 이 구조를 따른다.
|
|
* 타입별 고유 설정은 `config` 필드에 ComponentTypeConfig로 들어간다.
|
|
*/
|
|
export interface Component {
|
|
// ─── 식별 ───
|
|
|
|
/** 컴포넌트 고유 ID */
|
|
id: string;
|
|
/** 컴포넌트 종류 */
|
|
type: ComponentType;
|
|
/** 빌더에서 표시되는 이름 */
|
|
label: string;
|
|
|
|
// ─── 위치 (빌더가 관리) ───
|
|
//
|
|
// Component 인터페이스는 INVYONE 규격 v1.0 의 초기 드래프트였고, 자유배치
|
|
// 단일 모델 확정 이후 TemplateComponent (§7) 가 실질 대체이다. 이 인터페이스는
|
|
// 아직 참조 코드가 없어 즉시 삭제 가능한 상태이며, position 필드는 FreePosition
|
|
// 단일 모델로 정리되었다. 자세한 내용은 §7 참조.
|
|
|
|
/** 자유배치 위치 (px) */
|
|
position?: FreePosition;
|
|
|
|
// ─── 데이터 바인딩 ───
|
|
|
|
/** 데이터 소스 설정 */
|
|
dataSource?: DataSource;
|
|
/** 이 컴포넌트가 사용하는 필드 목록 (유일한 필드 규격) */
|
|
fields?: FieldConfig[];
|
|
|
|
// ─── 데이터 포트 (컴포넌트 간 통신) ───
|
|
|
|
/** 받는 데이터 포트 */
|
|
inputs?: DataPort[];
|
|
/** 내보내는 데이터 포트 */
|
|
outputs?: DataPort[];
|
|
|
|
// ─── 타입별 설정 ───
|
|
|
|
/** 컴포넌트 타입에 따른 고유 설정 */
|
|
config: ComponentTypeConfig;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 3. DataPort — 컴포넌트 간 통신 규격
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* DataPort가 전달하는 데이터의 형태.
|
|
*
|
|
* | 타입 | 실제 데이터 |
|
|
* |------|------------|
|
|
* | row | Record<string, any> (단일 행) |
|
|
* | rows | Record<string, any>[] (복수 행) |
|
|
* | value | any (단일 값) |
|
|
* | params | Record<string, any> (검색 파라미터) |
|
|
*/
|
|
export type DataPortType =
|
|
| 'row' // 단일 행: Record<string, any>
|
|
| 'rows' // 복수 행: Record<string, any>[]
|
|
| 'value' // 단일 값: any
|
|
| 'params'; // 검색 파라미터: Record<string, any>
|
|
|
|
/**
|
|
* v0.1 예약 포트 이름.
|
|
*
|
|
* 아직 `name` 자체를 이 union으로 강제하지는 않는다.
|
|
* 이유:
|
|
* - 기존 코드와 저장 JSON 호환성 유지
|
|
* - 사용자 정의 포트 확장 여지 보존
|
|
*
|
|
* 대신 아래 이름들은 기본 컴포넌트가 우선적으로 사용한다.
|
|
*/
|
|
export type ReservedDataPortName =
|
|
| 'searchParams'
|
|
| 'refreshTrigger'
|
|
| 'selectedRow'
|
|
| 'selectedRows'
|
|
| 'loadRow'
|
|
| 'formData'
|
|
| 'savedRow'
|
|
| 'clicked';
|
|
|
|
/**
|
|
* 컴포넌트가 데이터를 주고받는 포트.
|
|
*
|
|
* v0.1 원칙:
|
|
* - 포트는 "데이터 계약 이름 + 타입"만 가진다
|
|
* - 값 변환/매핑/가공은 포트가 아니라 컴포넌트 내부 또는 별도 액션에서 처리한다
|
|
* - 화면 수준 연결은 Connection 이 담당하고, 런타임은 단순 publish/subscribe 브리지다
|
|
*/
|
|
export interface DataPort {
|
|
/** 포트 이름 (예: 'selectedRow', 'searchParams') */
|
|
name: string;
|
|
/** 데이터 형태 */
|
|
type: DataPortType;
|
|
/** 연결된 컴포넌트 ID (빌더에서 설정) */
|
|
connectedTo?: string;
|
|
}
|
|
|
|
/**
|
|
* 두 컴포넌트 간 DataPort 연결을 나타낸다.
|
|
*
|
|
* v0.1 에서는 단순 연결만 담당한다.
|
|
* 즉 `from.port` 값을 `to.port` 로 그대로 전달하며, 중간 매핑/변환 규칙은 없다.
|
|
*/
|
|
export interface Connection {
|
|
/** 연결 고유 ID */
|
|
id: string;
|
|
/** 출발점 (데이터를 보내는 쪽) */
|
|
from: {
|
|
/** 출발 컴포넌트 ID */
|
|
componentId: string;
|
|
/** 출발 output 포트 이름 */
|
|
port: string;
|
|
};
|
|
/** 도착점 (데이터를 받는 쪽) */
|
|
to: {
|
|
/** 도착 컴포넌트 ID */
|
|
componentId: string;
|
|
/** 도착 input 포트 이름 */
|
|
port: string;
|
|
};
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 4. ComponentTypeConfig — 타입별 설정
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
// --- 4.1 table ---
|
|
|
|
/** 테이블 기본 정렬 설정 */
|
|
export interface DefaultSort {
|
|
/** 정렬 대상 컬럼명 */
|
|
column: string;
|
|
/** 정렬 방향 */
|
|
direction: 'asc' | 'desc';
|
|
}
|
|
|
|
/** 테이블 상단 툴바 표시 설정 */
|
|
export interface TableToolbar {
|
|
/** 엑셀 내보내기 버튼 표시 */
|
|
showExcel: boolean;
|
|
/** 새로고침 버튼 표시 */
|
|
showRefresh: boolean;
|
|
/** 필터 버튼 표시 */
|
|
showFilter: boolean;
|
|
}
|
|
|
|
/** 테이블 스타일 종류 */
|
|
export type TableStyle = 'default' | 'striped' | 'bordered' | 'compact';
|
|
|
|
/**
|
|
* 테이블(목록) 컴포넌트 설정.
|
|
*
|
|
* 기본 포트:
|
|
* - outputs: selectedRow(row), selectedRows(rows)
|
|
* - inputs: searchParams(params), refreshTrigger(value)
|
|
*/
|
|
export interface TableConfig {
|
|
/** 페이지당 행 수 (기본: 20) */
|
|
pageSize: number;
|
|
/** 행 선택 모드 */
|
|
selectionMode: 'none' | 'single' | 'multiple';
|
|
/** 체크박스 컬럼 표시 여부 */
|
|
showCheckbox: boolean;
|
|
/** 인라인 편집 활성화 여부 */
|
|
inlineEdit: boolean;
|
|
/** 화면 로드 시 자동으로 데이터 조회 */
|
|
autoLoad: boolean;
|
|
/** 기본 정렬 (미지정 시 서버 기본) */
|
|
defaultSort?: DefaultSort;
|
|
/** 상단 툴바 설정 */
|
|
toolbar: TableToolbar;
|
|
/** 테이블 스타일 */
|
|
style: TableStyle;
|
|
}
|
|
|
|
// --- 4.2 form ---
|
|
|
|
/** 폼 섹션 구분 (선택) */
|
|
export interface FormSection {
|
|
/** 섹션 라벨 */
|
|
label: string;
|
|
/** 이 섹션에 포함되는 필드의 column 목록 (FieldConfig.column 참조) */
|
|
fields: string[];
|
|
}
|
|
|
|
/** 폼 저장 액션 설정 */
|
|
export interface FormSaveAction {
|
|
/** 저장 방식 */
|
|
method: 'INSERT' | 'UPDATE' | 'UPSERT';
|
|
/** 저장 성공 후 표시 메시지 */
|
|
successMessage?: string;
|
|
/** 저장 후 목록 자동 새로고침 여부 */
|
|
refreshAfterSave: boolean;
|
|
}
|
|
|
|
/**
|
|
* 입력 폼 컴포넌트 설정.
|
|
*
|
|
* 기본 포트:
|
|
* - outputs: formData(row), savedRow(row)
|
|
* - inputs: loadRow(row)
|
|
*/
|
|
export interface FormConfig {
|
|
/** 폼 레이아웃 컬럼 수 */
|
|
columns: 1 | 2 | 3;
|
|
/** 섹션 구분 (미지정 시 단일 섹션) */
|
|
sections?: FormSection[];
|
|
/** 저장 액션 설정 */
|
|
saveAction: FormSaveAction;
|
|
}
|
|
|
|
// --- 4.3 search ---
|
|
|
|
/**
|
|
* 검색 필터 컴포넌트 설정.
|
|
*
|
|
* 기본 포트:
|
|
* - outputs: searchParams(params)
|
|
* - inputs: 없음
|
|
*/
|
|
export interface SearchConfig {
|
|
/** 날짜 범위 검색 활성화 (date 타입 필드를 자동으로 범위 입력으로) */
|
|
dateRangeEnabled: boolean;
|
|
/** 초기화 버튼 표시 */
|
|
showResetButton: boolean;
|
|
/** 입력 시 자동 검색 (디바운스) */
|
|
autoSearch: boolean;
|
|
/** 검색 필드 배치 방식 */
|
|
layout: 'inline' | 'stacked';
|
|
}
|
|
|
|
// --- 4.4 button / button-bar ---
|
|
|
|
/**
|
|
* 버튼 액션 종류 (12종).
|
|
*
|
|
* 새 액션 추가 시 이 union에 리터럴만 추가하고 핸들러를 구현하면 된다.
|
|
*/
|
|
export type ActionType =
|
|
| 'save' // 저장
|
|
| 'edit' // 수정 모드 전환
|
|
| 'delete' // 삭제
|
|
| 'add' // 신규 추가
|
|
| 'cancel' // 취소
|
|
| 'close' // 닫기
|
|
| 'navigate' // 페이지 이동
|
|
| 'popup' // 팝업 열기
|
|
| 'search' // 검색 실행
|
|
| 'reset' // 초기화
|
|
| 'submit' // 제출 (폼)
|
|
| 'approval'; // 승인
|
|
|
|
/** 버튼 스타일 종류 */
|
|
export type ButtonVariant = 'primary' | 'default' | 'destructive' | 'outline' | 'ghost';
|
|
|
|
/** 버튼의 제어 플로우 연결 설정 */
|
|
export interface ButtonFlow {
|
|
/** 연결할 플로우 ID */
|
|
flowId: string;
|
|
/** 플로우 실행 타이밍 (액션 전/후) */
|
|
timing: 'before' | 'after';
|
|
}
|
|
|
|
/**
|
|
* 단일 버튼 컴포넌트 설정.
|
|
*
|
|
* 기본 포트:
|
|
* - outputs: clicked(value)
|
|
* - inputs: 없음
|
|
*/
|
|
export interface ButtonConfig {
|
|
/** 버튼 텍스트 */
|
|
text: string;
|
|
/** 실행할 액션 종류 */
|
|
actionType: ActionType;
|
|
/** 버튼 스타일 */
|
|
variant: ButtonVariant;
|
|
/** 확인 메시지 (있으면 실행 전 확인 팝업 표시) */
|
|
confirm?: string;
|
|
/** 제어 플로우 연결 (선택) */
|
|
flow?: ButtonFlow;
|
|
}
|
|
|
|
/**
|
|
* 버튼 그룹 컴포넌트 설정.
|
|
* 여러 버튼을 한 줄로 묶어 배치한다.
|
|
*/
|
|
export interface ButtonBarConfig {
|
|
/** 포함된 버튼 목록 */
|
|
buttons: ButtonConfig[];
|
|
}
|
|
|
|
// --- 4.5 tabs ---
|
|
|
|
/** 개별 탭 항목 */
|
|
export interface TabItem {
|
|
/** 탭에 표시되는 라벨 */
|
|
label: string;
|
|
/** 탭 고유 ID */
|
|
id: string;
|
|
}
|
|
|
|
/**
|
|
* 탭 컴포넌트 설정.
|
|
*/
|
|
export interface TabsConfig {
|
|
/** 탭 목록 */
|
|
tabs: TabItem[];
|
|
/** 기본 선택 탭 ID */
|
|
defaultTab: string;
|
|
}
|
|
|
|
// --- 4.6 기타 (title, stats, divider, pagination, split-panel) ---
|
|
|
|
/**
|
|
* 제목/텍스트 컴포넌트 설정.
|
|
*/
|
|
export interface TitleConfig {
|
|
/** 표시 텍스트 */
|
|
text: string;
|
|
/** 글꼴 크기 (CSS 값, 예: '1.2rem') */
|
|
fontSize: string;
|
|
/** 글꼴 두께 (CSS 값, 예: '600') */
|
|
fontWeight: string;
|
|
/** 텍스트 정렬 */
|
|
align: 'left' | 'center' | 'right';
|
|
}
|
|
|
|
/** 통계 항목 하나 */
|
|
export interface StatsItem {
|
|
/** 항목 라벨 */
|
|
label: string;
|
|
/** 집계 대상 컬럼명 */
|
|
column: string;
|
|
/** 집계 방식 */
|
|
aggregation: 'count' | 'sum' | 'avg';
|
|
}
|
|
|
|
/**
|
|
* 통계 카드 컴포넌트 설정.
|
|
*
|
|
* 기본 포트:
|
|
* - outputs: 없음
|
|
* - inputs: data(rows)
|
|
*/
|
|
export interface StatsConfig {
|
|
/** 통계 항목 목록 */
|
|
items: StatsItem[];
|
|
}
|
|
|
|
/**
|
|
* 구분선 컴포넌트 설정.
|
|
*/
|
|
export interface DividerConfig {
|
|
/** 선 스타일 */
|
|
style: 'solid' | 'dashed' | 'dotted';
|
|
}
|
|
|
|
/**
|
|
* 페이지네이션 컴포넌트 설정.
|
|
*
|
|
* 기본 포트:
|
|
* - outputs: pageChange(params)
|
|
* - inputs: totalCount(value)
|
|
*/
|
|
export interface PaginationConfig {
|
|
/** 페이지당 행 수 */
|
|
pageSize: number;
|
|
/** 페이지 크기 선택기 표시 여부 */
|
|
showSizeSelector: boolean;
|
|
/** 선택 가능한 페이지 크기 목록 */
|
|
sizeOptions: number[];
|
|
}
|
|
|
|
/**
|
|
* 분할 패널 컴포넌트 설정.
|
|
* 추후 확장 예정 — 현재는 빈 구조로 예약.
|
|
*/
|
|
export interface SplitPanelConfig {
|
|
/** 분할 방향 */
|
|
direction?: 'horizontal' | 'vertical';
|
|
/** 초기 분할 비율 (0~1, 왼쪽/위쪽 비율) */
|
|
ratio?: number;
|
|
}
|
|
|
|
// --- ComponentTypeConfig 유니온 ---
|
|
|
|
/**
|
|
* 모든 컴포넌트 타입별 설정의 유니온 타입.
|
|
*
|
|
* Component.config 필드에 사용되며,
|
|
* Component.type에 따라 실제로 들어가는 타입이 결정된다.
|
|
*/
|
|
export type ComponentTypeConfig =
|
|
| TableConfig
|
|
| FormConfig
|
|
| SearchConfig
|
|
| ButtonConfig
|
|
| ButtonBarConfig
|
|
| TabsConfig
|
|
| TitleConfig
|
|
| StatsConfig
|
|
| DividerConfig
|
|
| PaginationConfig
|
|
| SplitPanelConfig;
|
|
|
|
/**
|
|
* ComponentType → ComponentTypeConfig 매핑.
|
|
*
|
|
* 타입 안전한 config 접근이 필요할 때 사용한다.
|
|
* 예: ComponentTypeConfigMap['table'] → TableConfig
|
|
*/
|
|
export interface ComponentTypeConfigMap {
|
|
table: TableConfig;
|
|
form: FormConfig;
|
|
search: SearchConfig;
|
|
button: ButtonConfig;
|
|
'button-bar': ButtonBarConfig;
|
|
tabs: TabsConfig;
|
|
'split-panel': SplitPanelConfig;
|
|
title: TitleConfig;
|
|
stats: StatsConfig;
|
|
divider: DividerConfig;
|
|
pagination: PaginationConfig;
|
|
}
|
|
|
|
/**
|
|
* 타입 안전한 컴포넌트.
|
|
*
|
|
* Component.type과 Component.config의 타입을 연동시킨다.
|
|
* 예: TypedComponent<'table'>이면 config는 반드시 TableConfig.
|
|
*/
|
|
export interface TypedComponent<T extends ComponentType> extends Omit<Component, 'type' | 'config'> {
|
|
type: T;
|
|
config: ComponentTypeConfigMap[T];
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 5. Template — 화면 단위
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* 뷰 하나의 설정 (목록, 등록 팝업, 수정 팝업).
|
|
*
|
|
* edit 뷰가 create를 상속하면 extends: 'create'로 지정하고,
|
|
* 다른 부분만 오버라이드한다.
|
|
*/
|
|
export interface ViewConfig {
|
|
/** 이 뷰에 배치된 컴포넌트 목록 */
|
|
components: Component[];
|
|
/** 상속 대상 뷰 (edit가 create를 상속할 때) */
|
|
extends?: 'create';
|
|
/** 팝업 크기 (create/edit 뷰용) */
|
|
size?: {
|
|
/** 팝업 너비 (px) */
|
|
w: number;
|
|
/** 팝업 높이 (px) */
|
|
h: number;
|
|
};
|
|
}
|
|
|
|
/** 템플릿 상태 */
|
|
export type TemplateStatus = 'draft' | 'published';
|
|
|
|
/**
|
|
* 한 화면 = 한 템플릿.
|
|
*
|
|
* 목록 + 등록 팝업 + 수정 팝업이 하나의 Template에 포함된다.
|
|
* Template.fields가 유일한 진실의 원천이며, 모든 뷰가 이를 공유한다.
|
|
*/
|
|
export interface Template {
|
|
// ─── 식별 ───
|
|
|
|
/** 템플릿 고유 ID */
|
|
templateId: string;
|
|
/** 화면 이름 */
|
|
name: string;
|
|
/** 카드 헤더 아이콘 */
|
|
icon?: string;
|
|
/** 카드 헤더 배지 (예: 'ERP · 영업') */
|
|
badge?: string;
|
|
/** 분류 (예: sales, production, purchase) */
|
|
category: string;
|
|
/** 화면 설명 */
|
|
description?: string;
|
|
/** 카드로 배치될 때 기본 사이즈 */
|
|
defaultSize?: { w: number; h: number };
|
|
|
|
// ─── 데이터 ───
|
|
|
|
/** 메인 바인딩 테이블 */
|
|
primaryTable: string;
|
|
/** 필드 정의 목록 — 모든 뷰가 이 하나를 공유한다 */
|
|
fields: FieldConfig[];
|
|
|
|
// ─── 3뷰 (자유배치 단일 모델) ───
|
|
|
|
/** 목록 / 등록 / 수정 세 가지 뷰 */
|
|
views: TemplateViews;
|
|
|
|
// ─── 연결 ───
|
|
|
|
/** 컴포넌트 간 DataPort 연결 목록 */
|
|
connections: Connection[];
|
|
|
|
/** 카드 간 통신 포트 정의 (선택) */
|
|
inputs?: DataPortDef[];
|
|
outputs?: DataPortDef[];
|
|
|
|
// ─── 메타 ───
|
|
|
|
/** 회사 코드 */
|
|
companyCode: string;
|
|
/** 템플릿 버전 */
|
|
version: number;
|
|
/** 상태 (초안 / 게시됨) */
|
|
status: TemplateStatus;
|
|
/** 생성 일시 (ISO 8601) */
|
|
createdAt: string;
|
|
/** 수정 일시 (ISO 8601) */
|
|
updatedAt: string;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 6. 카드 엔진 v2 — 자유배치 단일 모델 (2026-04-10 확정)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
//
|
|
// 진실의 원천: notes/gbpark/2026-04-10-card-engine-final-spec.md
|
|
//
|
|
// 이 섹션의 타입들이 Phase 1 이후 INVYONE 의 유일한 템플릿/대시보드 모델이다.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* 자유배치 위치 — 단일 위치 모델.
|
|
*
|
|
* 빌더 캔버스(Template 안의 컴포넌트) 와 대시보드 캔버스(Card) 가
|
|
* 모두 이 하나의 타입으로 배치된다. 12-grid / 48-col 분기 없음.
|
|
*/
|
|
export interface FreePosition {
|
|
/** 좌상단 X (px) */
|
|
left: number;
|
|
/** 좌상단 Y (px) */
|
|
top: number;
|
|
/** 너비 (px) */
|
|
width: number;
|
|
/** 높이 (px) */
|
|
height: number;
|
|
}
|
|
|
|
/**
|
|
* 뷰가 다른 뷰를 여는 방식.
|
|
*
|
|
* 빌더가 "등록" 버튼을 감지하면 viewTrigger 를 자동 세팅하고
|
|
* 대응되는 create 뷰 placeholder 를 만든다.
|
|
*/
|
|
export interface ViewTrigger {
|
|
/** 어느 뷰를 여는가 */
|
|
targetView: 'create' | 'edit' | 'detail';
|
|
/** 뷰 표시 방식 */
|
|
action: 'open-modal' | 'navigate';
|
|
}
|
|
|
|
/**
|
|
* 반응 정책 모드. 카드/대시보드 영역 축소 시 컴포넌트가 어떻게 반응할지 결정.
|
|
* 자세한 정의: notes/gbpark/2026-04-19-template-responsive-policy.md
|
|
*
|
|
* - fixed : 크기 유지, 위치만 조정 (단일 버튼/입력/타이틀/구분선)
|
|
* - scroll : 내용 크기 유지, 넘치면 가로 스크롤 (테이블/그리드/검색/컨테이너)
|
|
* - reflow : 폭 부족 시 열 수 재배치 (stats 카드 그룹 5→3→2→1열)
|
|
* - wrap : 요소 크기 유지, 행 단위 줄바꿈 (버튼 그룹/태그 그룹)
|
|
*/
|
|
export type ResponsiveMode = 'fixed' | 'scroll' | 'reflow' | 'wrap';
|
|
|
|
/** fixed 모드에서 축소 시 붙일 기준점. */
|
|
export type ResponsiveAnchor =
|
|
| 'top-left'
|
|
| 'top-right'
|
|
| 'bottom-left'
|
|
| 'bottom-right'
|
|
| 'center';
|
|
|
|
/**
|
|
* 컴포넌트의 반응 정책.
|
|
*
|
|
* 명시하지 않으면 componentId 기반 기본값이 런타임에 주입된다.
|
|
* (예: componentId === 'table' → { mode: 'scroll' })
|
|
*
|
|
* 인접한 동종 단일 컴포넌트들이 같은 row 에 있으면 렌더러가 가상 그룹으로 묶어
|
|
* 그룹 정책을 적용한다 — 버튼 여러 개는 wrap, stats 여러 개는 reflow.
|
|
*/
|
|
export interface ResponsiveConfig {
|
|
/** 반응 모드 (미지정 시 componentId 기반 기본값) */
|
|
mode?: ResponsiveMode;
|
|
/** 최소 폭 (px) — 이 이하로는 줄지 않음 */
|
|
minWidth?: number;
|
|
/** 최소 높이 (px) */
|
|
minHeight?: number;
|
|
/** 컨테이너 overflow 정책 */
|
|
overflowX?: 'hidden' | 'auto' | 'scroll';
|
|
overflowY?: 'hidden' | 'auto' | 'scroll';
|
|
/** fixed 모드: 축소 시 붙일 기준점 (미지정 시 디자인 시점 위치로 자동 결정) */
|
|
anchor?: ResponsiveAnchor;
|
|
/** reflow 모드: grid auto-fit 의 minmax 최소값 (px) */
|
|
minItemWidth?: number;
|
|
/** reflow 모드: 최대 컬럼 수 */
|
|
maxColumns?: number;
|
|
/** reflow/wrap 모드: 아이템 간격 (px) */
|
|
gap?: number;
|
|
}
|
|
|
|
/**
|
|
* Template 안에 배치된 컴포넌트 하나.
|
|
*
|
|
* 카드 내부는 flex-column 자동 레이아웃이므로 px 좌표 대신 `order` 와
|
|
* 선택적 `row` 로만 위치가 결정된다. 카드 폭이 변하면 runtime 이 자동
|
|
* 재배치(줄바꿈/세로 스택)한다. FreePosition 은 오직 대시보드에 카드를
|
|
* 배치하는 상위 수준(Card.position)에서만 사용된다.
|
|
*/
|
|
export interface TemplateComponent {
|
|
/** 인스턴스 ID */
|
|
id: string;
|
|
|
|
/**
|
|
* 컴포넌트 종류 — ComponentRegistry 의 ID 참조.
|
|
* 예: 'v2-table-list', 'v2-button-primary', 'v2-bom-tree'
|
|
*/
|
|
componentId: string;
|
|
|
|
/** 빌더에서 표시되는 라벨 (선택) */
|
|
label?: string;
|
|
|
|
/**
|
|
* 세로 스택 내 순서. 작을수록 위. 0 부터 시작.
|
|
* 같은 `row` 값을 가진 연속 블록끼리는 가로 나열되고, 이때 `order`
|
|
* 는 그 행 안에서 왼쪽에서 오른쪽으로의 순서도 함께 결정한다.
|
|
*/
|
|
order: number;
|
|
|
|
/**
|
|
* 같은 값을 공유하는 연속된 블록들을 flex-row 로 묶는 키.
|
|
* undefined 면 단독 행(기본). 숫자값 자체는 의미 없고 키 역할만 한다.
|
|
*/
|
|
row?: number;
|
|
|
|
/**
|
|
* 컴포넌트별 설정 — 모드/옵션/컬럼 정의 등.
|
|
* ComponentRegistry 의 default_config 를 인스턴스마다 오버라이드한다.
|
|
* 로우코드 플랫폼 원칙상 느슨한 Record 형태.
|
|
*/
|
|
config: Record<string, any>;
|
|
|
|
/** 컨테이너 컴포넌트의 자식 (탭/아코디언 등) */
|
|
children?: TemplateComponent[];
|
|
|
|
/** 받는 데이터 포트 */
|
|
inputs?: DataPort[];
|
|
/** 내보내는 데이터 포트 */
|
|
outputs?: DataPort[];
|
|
|
|
/** 이 컴포넌트가 다른 뷰를 여는지 여부 (예: 등록 버튼 → create 뷰) */
|
|
viewTrigger?: ViewTrigger;
|
|
|
|
/** 반응 정책 (미지정 시 componentId 기반 기본값 주입) */
|
|
responsive?: ResponsiveConfig;
|
|
|
|
/** 그룹핑용 — 여러 컴포넌트를 하나의 group 으로 묶을 때 부모 id */
|
|
parentId?: string;
|
|
|
|
/**
|
|
* group 전용 — 이 컴포넌트가 type='group' 일 때 자식 id 목록.
|
|
* 빌더의 그룹 박스 UI 용이며, 런타임 렌더러는 parentId 로 부모를 찾는다.
|
|
*/
|
|
groupChildren?: string[];
|
|
}
|
|
|
|
/**
|
|
* 한 뷰의 자유배치 캔버스 설정.
|
|
*
|
|
* Template 의 views.list / create / edit 각각이 이 타입이다.
|
|
* 기존 §5 의 ViewConfig 와 이름이 겹치므로 Phase 1 Step 5 에서
|
|
* 기존 ViewConfig 를 이 타입으로 교체한다(그 사이에는 별칭으로 사용).
|
|
*/
|
|
export interface TemplateViewConfig {
|
|
/** 이 뷰에 배치된 컴포넌트들 (자유배치) */
|
|
components: TemplateComponent[];
|
|
|
|
/** 뷰 표시 방식 (list 는 카드 본체, create/edit 는 모달) */
|
|
layout?: 'card' | 'modal';
|
|
|
|
/** 모달 사이즈 (layout === 'modal' 일 때) */
|
|
modalSize?: { w: number; h: number };
|
|
|
|
/** 뷰 캔버스의 기본 사이즈 (디자인 시의 작업 영역 크기) */
|
|
designSize?: { w: number; h: number };
|
|
}
|
|
|
|
/** Template 의 3뷰 묶음. list 는 필수, create/edit 는 선택(자동 생성 가능). */
|
|
export interface TemplateViews {
|
|
/** 목록 뷰 — 카드 본체 (필수) */
|
|
list: TemplateViewConfig;
|
|
/** 등록 뷰 — 모달 (자동 생성 가능) */
|
|
create?: TemplateViewConfig;
|
|
/** 수정 뷰 — 모달 (자동 생성 가능) */
|
|
edit?: TemplateViewConfig;
|
|
}
|
|
|
|
/**
|
|
* Template 단위 데이터 포트 정의 (카드 간 통신용).
|
|
*
|
|
* 개별 TemplateComponent 의 DataPort 와 달리, 이건 Template 전체가
|
|
* 대시보드에서 어떤 입출력을 외부에 노출하는지를 기술한다.
|
|
*/
|
|
export interface DataPortDef {
|
|
/** 포트 이름 */
|
|
name: string;
|
|
/** 포트 데이터 형태 */
|
|
type: DataPortType;
|
|
/** 설명 (대시보드 연결 UI 에서 표시) */
|
|
description?: string;
|
|
}
|
|
|
|
/**
|
|
* 대시보드에 배치된 카드 한 개 — Template 의 인스턴스.
|
|
*
|
|
* 1 Template : N Card 관계. 한 Template 을 여러 대시보드에 배치할 수 있고,
|
|
* 각 인스턴스마다 위치/크기/설정 오버라이드를 가진다.
|
|
*/
|
|
export interface Card {
|
|
/** 인스턴스 ID */
|
|
id: string;
|
|
|
|
/** 참조하는 Template ID */
|
|
templateId: string;
|
|
|
|
/** 대시보드 안에서의 위치 (px 자유배치) */
|
|
position: FreePosition;
|
|
|
|
/** 접힘 상태 (mini 모드) */
|
|
collapsed: boolean;
|
|
|
|
/** Template 의 config 를 이 인스턴스에서만 덮어쓸 값 */
|
|
configOverride?: Record<string, any>;
|
|
|
|
/** 인스턴스별 DataPort 연결 상태 */
|
|
inputs?: DataPort[];
|
|
outputs?: DataPort[];
|
|
}
|
|
|
|
/**
|
|
* 대시보드 레벨 카드 간 연결.
|
|
*
|
|
* 예: 수주 카드의 selectedRow 를 BOM 카드의 masterRow 로 연결.
|
|
*/
|
|
export interface CardConnection {
|
|
/** 연결 ID */
|
|
id: string;
|
|
/** 출발 카드 + 포트 */
|
|
from: { cardId: string; port: string };
|
|
/** 도착 카드 + 포트 */
|
|
to: { cardId: string; port: string };
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 7. 템플릿 저장 모델 v2 — % 좌표 + semantic role (2026-04-20)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
//
|
|
// 설계: notes/gbpark/2026-04-20-template-storage-model-v2.md
|
|
//
|
|
// 목표: 런타임 추측 제거. 디자이너가 의도(role, responsivePolicy)를 명시하고
|
|
// 런타임은 그대로 렌더한다. v1 의 FIXED_SMALL_IDS/FULL_WIDTH_IDS 하드코딩,
|
|
// centerY overlap 판정, top tolerance clustering, action line clamp 등
|
|
// 좌표 기반 추론 전부 폐기 대상.
|
|
//
|
|
// DB 저장 경로는 기존과 동일 (TEMPLATES.VIEWS jsonb). views 최상위에
|
|
// `version: 2` 가 있으면 v2 포맷, 없으면 v1 로 해석해서 런타임에 migrate.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* 블록의 의미상 역할 — 런타임 렌더 전략을 결정한다.
|
|
* 좌표나 크기로 추측하지 않고 디자이너가 명시.
|
|
*/
|
|
export type BlockRole =
|
|
| 'main' // 페이지의 주 콘텐츠 (카드/테이블/폼)
|
|
| 'action' // main band 앞/뒤 별도 액션 라인 (저장/수정 바 등)
|
|
| 'companion' // main 과 같은 라인에 가로로 공존 (인디케이터, 뱃지 등)
|
|
| 'overlay'; // main 위에 absolute 로 띄우는 floating 요소
|
|
|
|
/**
|
|
* 카드(컨테이너) 폭이 변할 때 블록이 어떻게 반응할지 — 디자이너가 명시.
|
|
* v1 의 `ResponsiveConfig.mode` 와 동의어지만, v2 에서는 enum 단일 필드.
|
|
*/
|
|
export type ResponsivePolicy =
|
|
| 'fixed' // 원본 크기 유지 (단일 버튼/입력/타이틀/구분선)
|
|
| 'scroll' // 내부 overflow (테이블/컨테이너)
|
|
| 'reflow' // auto-fit grid (stats 카드 그룹)
|
|
| 'wrap'; // flex-wrap (버튼 그룹)
|
|
|
|
/**
|
|
* fixed / overlay 모드에서 축소 시 앵커 지점.
|
|
* 9분할 기준 (좌/중/우 × 상/중/하).
|
|
*/
|
|
export type BlockAnchor =
|
|
| 'top-left' | 'top-center' | 'top-right'
|
|
| 'center-left' | 'center' | 'center-right'
|
|
| 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
|
|
/**
|
|
* v2 블록 — 한 뷰 안에 배치된 컴포넌트 인스턴스.
|
|
*
|
|
* 좌표는 정규화된 % 값 (0~1, 기준 캔버스 대비). 디자이너는 px 로 편집하고
|
|
* 저장/로드 경계에서만 정규화/역정규화한다.
|
|
*/
|
|
export interface BlockV2 {
|
|
/** 인스턴스 ID */
|
|
id: string;
|
|
/** 컴포넌트 종류 — 통합 ID (table, stats, button, …) */
|
|
componentId: string;
|
|
|
|
// ─── 좌표 (기준 캔버스 대비 0.0 ~ 1.0) ───
|
|
xPct: number;
|
|
yPct: number;
|
|
wPct: number;
|
|
hPct: number;
|
|
|
|
// ─── 디자이너 의도 ───
|
|
/** 의미상 역할 — 런타임 렌더 전략 결정 */
|
|
role: BlockRole;
|
|
/** 반응 정책 */
|
|
responsivePolicy: ResponsivePolicy;
|
|
/** 앵커 (선택 — 미지정 시 fixed 모드 기본값) */
|
|
anchor?: BlockAnchor;
|
|
/** 소속 band ID (선택 — companion/action 이 어느 main band 에 붙는지 명시) */
|
|
bandId?: string;
|
|
|
|
/** 컴포넌트 타입별 설정 */
|
|
config: Record<string, any>;
|
|
}
|
|
|
|
/**
|
|
* v2 캔버스 메타 — 정규화 기준 + 축소 정책.
|
|
*/
|
|
export interface CanvasV2 {
|
|
/** 정규화 기준 폭 (저장 시의 디자이너 캔버스 폭) */
|
|
baseWidth: number;
|
|
/** 정규화 기준 높이 */
|
|
baseHeight: number;
|
|
/**
|
|
* 카드 폭이 canvas 폭보다 좁아질 때 세로 축소 정책.
|
|
* - 'preserve': 세로도 비례 축소 (aspect ratio 유지)
|
|
* - 'free' : 세로는 content 기반, 가로만 비례 축소
|
|
*/
|
|
aspectPolicy: 'preserve' | 'free';
|
|
}
|
|
|
|
/**
|
|
* v2 뷰 — 한 뷰(list/create/edit) 의 블록 모음.
|
|
*/
|
|
export interface ViewV2 {
|
|
blocks: BlockV2[];
|
|
}
|
|
|
|
/**
|
|
* v2 템플릿 views 최상위 — DB 의 TEMPLATES.VIEWS jsonb 포맷.
|
|
* version 필드로 v1 과 구분한다.
|
|
*/
|
|
export interface TemplateViewsV2 {
|
|
version: 2;
|
|
/** 기본/폴백 canvas (legacy 공통 screenResolution). view 별 override 없을 때 사용. */
|
|
canvas: CanvasV2;
|
|
list: ViewV2;
|
|
create?: ViewV2;
|
|
edit?: ViewV2;
|
|
/**
|
|
* 뷰별 canvas — 등록/수정 팝업이 목록과 다른 해상도로 편집되는 경우.
|
|
* 빌더의 viewScreenResolutions 를 반영. 컴포넌트 좌표는 해당 뷰의 canvas 기준으로 정규화.
|
|
*/
|
|
viewCanvases?: {
|
|
list?: CanvasV2;
|
|
create?: CanvasV2;
|
|
edit?: CanvasV2;
|
|
};
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 7 (cont.) — 기존 Dashboard 타입 (v1)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* 사이드바 메뉴 항목 = 카드 컬렉션.
|
|
*
|
|
* INVYONE 의 "메뉴 = 대시보드" 원칙에 따라, 사이드바 엔트리와 대시보드가
|
|
* 1:1 대응한다. 별도 대시보드 UI 없음.
|
|
*/
|
|
export interface Dashboard {
|
|
/** 대시보드 ID */
|
|
id: string;
|
|
/** 메뉴 표시 이름 */
|
|
name: string;
|
|
/** 사이드바 아이콘 */
|
|
icon: string;
|
|
|
|
/** 카드 자유배치 목록 */
|
|
cards: Card[];
|
|
|
|
/** 카드 간 데이터 연결 */
|
|
connections: CardConnection[];
|
|
|
|
// ─── 메타 ───
|
|
companyCode: string;
|
|
/** 사용자별 대시보드일 때 소유자 ID (공유 대시보드는 비움) */
|
|
ownerId?: string;
|
|
/** 공유 대시보드 여부 */
|
|
isShared: boolean;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|