Files
invyone/frontend/types/invyone-component.ts
T
gbpark 2c0a97f2ba Phase 1: INVYONE 카드 엔진 토대 정리
- components/builder/* 폐기 (12-grid 미완성 빌더 14개 파일)
- components/template-builder/TemplateBuilder.tsx 신규
  (자유배치 + 3뷰 + Zustand 스토어 + 드래그/리사이즈/히스토리/격자)
- admin/builder/page.tsx 진입점 전환 (BuilderLayout → TemplateBuilder)
- 타입 정리: FreePosition / TemplateComponent / ViewConfig / Card /
  Dashboard / CardConnection 추가, 레거시(GridPosition/TemplateKind/
  DEFAULT_COMPONENT_LAYOUTS/CANVAS_KEYWORDS) @deprecated 표기
- v2-* 마이그레이션 1차:
  · 완전: v2-table-list (ResizeObserver), v2-table-search-widget (@container)
  · 경량: button/input/select/date/text-display/card-display/aggregation-widget
    (withContainerQuery HOC)
- 다크 모드 대응: Tailwind dark: variant 21패턴 71곳 치환
- /test-card-responsive PoC 검증 페이지

세션 후반 버그 픽스 (phase1-log §7):
- test-card-responsive (main) 그룹 밖 이동 (AppLayout 탭 시스템 회피)
- useRegistryPalette default_size {width,height}/{w,h} 포맷 정규화
- dark: variant 중복 체인 정리

검증: (A) 반응형 메커니즘, (B) TemplateBuilder UI 통과
(C) 기존 VEX 화면은 마이그레이션 미완 상태라 Phase 2 이후 개별 진행

스펙: notes/gbpark/2026-04-10-card-engine-final-spec.md
로그: notes/gbpark/2026-04-10-card-engine-phase1-log.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 03:08:06 +09:00

1034 lines
32 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' // 날짜+시간
| 'select' // 드롭다운 (options 배열)
| 'entity' // FK 참조 (팝업 검색)
| 'checkbox' // 체크박스
| 'textarea' // 장문
| 'file' // 파일 첨부
| 'code'; // 자동채번 (readonly)
/**
* entity 타입 필드의 FK 참조 정보.
* 팝업 검색 시 대상 테이블·표시 컬럼·검색 컬럼을 지정한다.
*/
export interface FieldRef {
/** 참조 대상 테이블 */
table: string;
/** 값으로 사용할 컬럼 (PK 등) */
valueColumn: string;
/** 화면에 표시할 컬럼 */
displayColumn: string;
/** 팝업 검색 대상 컬럼 목록 */
searchColumns?: 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 FieldConfig {
// ─── 식별 ───
/** DB 컬럼명 (필드의 유일 키) */
column: string;
/** 화면에 표시되는 라벨 */
label: string;
// ─── 타입 ───
/** 렌더링 방식을 결정하는 필드 타입 */
type: FieldType;
// ─── 표시 ───
/** 화면에 보이는지 여부 */
visible: boolean;
/** 표시 순서 (작을수록 먼저) */
order: number;
/** 컬럼 너비 (px, 테이블에서 사용) */
width?: number;
/** 텍스트 정렬 */
align?: 'left' | 'center' | 'right';
// ─── 입력 ───
/** 필수 입력 여부 */
required: boolean;
/** 편집 가능 여부 */
editable: boolean;
/** 기본값 */
defaultValue?: unknown;
/** 입력 힌트 텍스트 */
placeholder?: string;
// ─── 타입별 확장 ───
/** select 타입: 선택지 목록
* - string이면 value=label로 해석
* - { value, label } 객체면 value를 저장, label을 표시
* - select 렌더러/검색 파라미터는 항상 value를 저장/전송
* - 테이블 셀은 label을 표시
*/
options?: FieldOption[];
/** entity 타입: FK 참조 정보 */
ref?: FieldRef;
/** 포맷 문자열 (number: '#,##0', date: 'YYYY-MM-DD' 등) */
format?: string;
/** 자동 계산 수식 (예: 'quantity * unit_price') */
computed?: string;
// ─── 메타 ───
/** PK 여부 */
pk?: boolean;
/** 시스템 필드 여부 (company_code 등, 폼에서 숨김) */
system?: boolean;
/** 검색 대상 여부 */
searchable?: boolean;
/** 정렬 가능 여부 */
sortable?: boolean;
}
// ─────────────────────────────────────────────────────────────────────────────
// 2. Component — 유일한 컴포넌트 규격
// ─────────────────────────────────────────────────────────────────────────────
/**
* 지원하는 컴포넌트 종류.
*
* 새 컴포넌트 추가 시 이 union에 리터럴을 추가하고,
* ComponentTypeConfigMap에 대응하는 설정 타입을 등록한다.
*/
export type ComponentType =
| 'table' // 데이터 테이블 (목록)
| 'form' // 입력 폼
| 'search' // 검색 필터
| 'button' // 버튼 (단일)
| 'button-bar' // 버튼 그룹
| 'tabs' // 탭
| 'split-panel' // 분할 패널
| 'title' // 텍스트/제목
| 'stats' // 통계 카드
| 'divider' // 구분선
| 'pagination'; // 페이지네이션
// ─────────────────────────────────────────────────────────────────────────────
// Position — ⚠️ DEPRECATED 블록 (2026-04-10 폐기 결정)
// ─────────────────────────────────────────────────────────────────────────────
//
// 아래 타입/함수/상수는 12-grid + business/canvas 분기 모델 잔재이다.
// 진실의 원천: notes/gbpark/2026-04-10-card-engine-final-spec.md
// 대체 타입: §7 의 FreePosition / TemplateComponent / TemplateViewConfig.
//
// 현재 DashboardCard.tsx 가 유일한 내부 사용처이며, Phase 2 에서 해당 파일을
// Card = Template 인스턴스 모델로 재작성할 때 일괄 제거 예정이다.
// 새 코드는 이 블록의 타입을 사용하지 말 것.
// ─────────────────────────────────────────────────────────────────────────────
/**
* @deprecated Phase 2 DashboardCard 재작성 시 제거 예정.
* FreePosition { left, top, width, height } 를 사용할 것.
* 현재 DashboardCard.tsx 만 이 타입을 사용 중.
*/
export interface GridPosition {
/** 시작 컬럼 (1~12) */
col: number;
/** 차지 컬럼 수 (1~12) */
colSpan: number;
/** 명시적 행 번호 (생략 시 auto placement) */
row?: number;
/** 행 높이 배수 (기본 1) */
rowSpan?: number;
/** 카드 너비별 반응형 오버라이드 */
responsive?: ResponsiveGridOverride;
}
/**
* @deprecated Phase 2 DashboardCard 재작성 시 제거 예정.
* FreePosition { left, top, width, height } 로 통합됐다.
* 현재 DashboardCard.tsx 만 이 타입을 사용 중.
*/
export interface AbsolutePosition {
/** 가로 위치 (px) */
x: number;
/** 세로 위치 (px) */
y: number;
/** 너비 (px) */
w: number;
/** 높이 (px) */
h: number;
}
/**
* @deprecated Phase 2 DashboardCard 재작성 시 제거 예정.
* 단일 자유배치 모델로 union 분기가 사라졌다. FreePosition 을 사용할 것.
*/
export type ComponentPosition = GridPosition | AbsolutePosition;
/**
* @deprecated Phase 2 DashboardCard 재작성 시 제거 예정.
* 반응형은 컴포넌트 내부 @container 쿼리가 담당한다(스펙 §2).
* 외부에서 그리드 오버라이드를 주입하는 모델은 폐기됐다.
*/
export interface ResponsiveGridOverride {
narrow?: Partial<Omit<GridPosition, 'responsive'>>;
normal?: Partial<Omit<GridPosition, 'responsive'>>;
wide?: Partial<Omit<GridPosition, 'responsive'>>;
}
/**
* @deprecated Phase 2 DashboardCard 재작성 시 제거 예정.
* FreePosition 이 단일 위치 모델이므로 판정 가드가 필요 없다.
*/
export function isGridPosition(pos: ComponentPosition): pos is GridPosition {
return pos != null && typeof pos === 'object' && 'col' in pos && 'colSpan' in pos;
}
/**
* @deprecated Phase 2 DashboardCard 재작성 시 제거 예정.
* FreePosition 이 단일 위치 모델이므로 판정 가드가 필요 없다.
*/
export function isAbsolutePosition(pos: ComponentPosition): pos is AbsolutePosition {
return pos != null && typeof pos === 'object' && 'x' in pos && 'y' in pos && 'w' in pos && 'h' in pos;
}
/**
* @deprecated Phase 2 DashboardCard 재작성 시 제거 예정.
* business/canvas 분기는 폐기됐다. 모든 Template 은 단일 자유배치 모델이다.
* 현재 DashboardCard.tsx 만 이 타입을 사용 중.
*/
export type TemplateKind = 'business' | 'canvas';
/**
* 컴포넌트의 데이터 소스 바인딩.
*/
export interface DataSource {
/** 바인딩 대상 테이블명 */
table: string;
/** 읽기/쓰기 모드 */
mode: 'read' | 'write' | 'readwrite';
}
/**
* 모든 컴포넌트의 기본 구조.
*
* 타입이 무엇이든 이 구조를 따른다.
* 타입별 고유 설정은 `config` 필드에 ComponentTypeConfig로 들어간다.
*/
export interface Component {
// ─── 식별 ───
/** 컴포넌트 고유 ID */
id: string;
/** 컴포넌트 종류 */
type: ComponentType;
/** 빌더에서 표시되는 이름 */
label: string;
// ─── 위치 (빌더가 관리) ───
/**
* Template.kind에 따라 해석이 달라짐.
* - business → GridPosition (col / colSpan / row / responsive)
* - canvas → AbsolutePosition (x / y / w / h)
*/
position: ComponentPosition;
// ─── 데이터 바인딩 ───
/** 데이터 소스 설정 */
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>
/**
* 컴포넌트가 데이터를 주고받는 포트.
*
* output.name → input.name 매칭으로 연결된다.
* 빌더에서 시각적으로 연결을 설정하고, 실행 시 이벤트 버스가 자동 전달한다.
*/
export interface DataPort {
/** 포트 이름 (예: 'selectedRow', 'searchParams') */
name: string;
/** 데이터 형태 */
type: DataPortType;
/** 연결된 컴포넌트 ID (빌더에서 설정) */
connectedTo?: string;
}
/**
* 두 컴포넌트 간 DataPort 연결을 나타낸다.
*
* Template.connections 배열에 저장되어 화면 전체의 데이터 흐름을 정의한다.
*/
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;
/**
* @deprecated Phase 2 에서 제거. 단일 자유배치 모델로 kind 분기 없음.
* 기존 데이터 호환용으로만 optional 유지. 새 Template 은 이 필드를 쓰지 않는다.
*/
kind?: TemplateKind;
/** 분류 (예: sales, production, purchase) */
category: string;
/** 화면 설명 */
description?: string;
// ─── 데이터 ───
/** 메인 바인딩 테이블 */
primaryTable: string;
/** 필드 정의 목록 — 모든 뷰가 이 하나를 공유한다 */
fields: FieldConfig[];
// ─── 3뷰 ───
/** 목록 / 등록 / 수정 세 가지 뷰 */
views: {
/** 목록 화면 */
list: ViewConfig;
/** 등록 팝업 */
create: ViewConfig;
/** 수정 팝업 (create 상속 가능) */
edit: ViewConfig;
};
// ─── 연결 ───
/** 컴포넌트 간 DataPort 연결 목록 */
connections: Connection[];
// ─── 메타 ───
/** 회사 코드 */
companyCode: string;
/** 템플릿 버전 */
version: number;
/** 상태 (초안 / 게시됨) */
status: TemplateStatus;
/** 생성 일시 (ISO 8601) */
createdAt: string;
/** 수정 일시 (ISO 8601) */
updatedAt: string;
}
// ─────────────────────────────────────────────────────────────────────────────
// 6. 컴포넌트 기본 grid 배치 (섹션 10)
// ─────────────────────────────────────────────────────────────────────────────
/**
* @deprecated Phase 2 DashboardCard 재작성 시 제거 예정.
* 12-grid 기본 배치는 폐기됐다. 팔레트 드롭 시 기본 크기는
* ComponentRegistry 의 default_size 를 사용하고, 위치는 FreePosition 으로
* 마우스 좌표에서 계산한다(components/template-builder/TemplateBuilder.tsx).
*/
export const DEFAULT_COMPONENT_LAYOUTS: Record<ComponentType, GridPosition> = {
search: { col: 1, colSpan: 12 },
table: {
col: 1,
colSpan: 8,
responsive: {
narrow: { colSpan: 12 },
normal: { colSpan: 12 },
wide: { colSpan: 8 },
},
},
form: {
col: 1,
colSpan: 4,
responsive: {
narrow: { col: 1, colSpan: 12 },
normal: { col: 1, colSpan: 12 },
wide: { col: 9, colSpan: 4 },
},
},
'button-bar': { col: 1, colSpan: 12 },
button: {
col: 1,
colSpan: 2,
responsive: {
narrow: { colSpan: 12 },
normal: { colSpan: 3 },
wide: { colSpan: 2 },
},
},
stats: {
col: 1,
colSpan: 4,
responsive: {
narrow: { colSpan: 12 },
normal: { colSpan: 6 },
wide: { colSpan: 4 },
},
},
title: { col: 1, colSpan: 12 },
divider: { col: 1, colSpan: 12 },
pagination: { col: 1, colSpan: 12 },
tabs: { col: 1, colSpan: 12 },
'split-panel': { col: 1, colSpan: 12 },
};
/**
* @deprecated Phase 2 DashboardCard 재작성 시 제거 예정.
* 모든 Template 이 단일 자유배치 모델이므로 kind 휴리스틱이 필요 없다.
* 현재 DashboardCard.tsx 만 이 상수를 사용 중.
*/
export const CANVAS_KEYWORDS = [
'control',
'flow',
'workflow',
'bpm',
'canvas',
'node',
'diagram',
'graph',
] as const;
// ─────────────────────────────────────────────────────────────────────────────
// 7. 카드 엔진 v2 — 자유배치 단일 모델 (2026-04-10 확정)
// ─────────────────────────────────────────────────────────────────────────────
//
// 진실의 원천: notes/gbpark/2026-04-10-card-engine-final-spec.md
//
// 이 섹션의 타입들이 Phase 1 이후 INVYONE 의 유일한 템플릿/대시보드 모델이다.
// 위쪽 §6 까지의 Component/GridPosition/TemplateKind/DEFAULT_COMPONENT_LAYOUTS
// 등은 Phase 1 Step 5 에서 제거 예정(현재는 폐기 예정 코드와 호환용으로만 남음).
// ─────────────────────────────────────────────────────────────────────────────
/**
* 자유배치 위치 — 단일 위치 모델.
*
* 빌더 캔버스(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';
}
/**
* Template 안에 자유배치된 컴포넌트 하나.
*
* 컴포넌트 종류(componentId) 는 ComponentRegistry 의 v2-* ID 를 참조한다.
* 위치는 FreePosition 단일 모델. 타입별 옵션은 config 에 자유 형태로 들어간다.
*/
export interface TemplateComponent {
/** 인스턴스 ID */
id: string;
/**
* 컴포넌트 종류 — ComponentRegistry 의 ID 참조.
* 예: 'v2-table-list', 'v2-button-primary', 'v2-bom-tree'
*/
componentId: string;
/** 빌더에서 표시되는 라벨 (선택) */
label?: string;
/** 캔버스 안에서의 위치 (px 자유배치) */
position: FreePosition;
/**
* 컴포넌트별 설정 — 모드/옵션/컬럼 정의 등.
* ComponentRegistry 의 default_config 를 인스턴스마다 오버라이드한다.
* 로우코드 플랫폼 원칙상 느슨한 Record 형태.
*/
config: Record<string, any>;
/** 컨테이너 컴포넌트의 자식 (탭/아코디언 등) */
children?: TemplateComponent[];
/** 받는 데이터 포트 */
inputs?: DataPort[];
/** 내보내는 데이터 포트 */
outputs?: DataPort[];
/** 이 컴포넌트가 다른 뷰를 여는지 여부 (예: 등록 버튼 → create 뷰) */
viewTrigger?: ViewTrigger;
/** 그룹핑용 — 여러 컴포넌트를 하나의 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 };
}
/**
* 사이드바 메뉴 항목 = 카드 컬렉션.
*
* 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;
}