From 429f1ba6eedb7ecd1194c64ae95a47bc4f0e7f39 Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 13 Mar 2026 14:01:09 +0900 Subject: [PATCH 1/3] feat: add item list mode configuration and screen code handling - Introduced `itemListMode` to the process work standard configuration, allowing users to select between displaying all items or only registered items. - Added `screenCode` to automatically set the screen ID when in registered mode. - Updated the `ProcessWorkStandardComponent` to handle the new configuration and adjust item fetching logic accordingly. - Enhanced the `ProcessWorkStandardConfigPanel` to include a select input for item list mode, improving user experience and configurability. These changes aim to enhance the flexibility and usability of the process work standard component. Made-with: Cursor --- .../ProcessWorkStandardComponent.tsx | 35 ++++++++---- .../ProcessWorkStandardConfigPanel.tsx | 25 +++++++++ .../ProcessWorkStandardRenderer.tsx | 3 +- .../v2-process-work-standard/config.ts | 2 + .../hooks/useProcessWorkStandard.ts | 54 ++++++++++++++++++- .../v2-process-work-standard/types.ts | 5 ++ 6 files changed, 111 insertions(+), 13 deletions(-) diff --git a/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardComponent.tsx b/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardComponent.tsx index cf7b306f..ad169acd 100644 --- a/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardComponent.tsx +++ b/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardComponent.tsx @@ -17,25 +17,37 @@ interface ProcessWorkStandardComponentProps { formData?: Record; isPreview?: boolean; tableName?: string; + screenId?: number | string; } export function ProcessWorkStandardComponent({ config: configProp, isPreview, + screenId, }: ProcessWorkStandardComponentProps) { + const resolvedConfig = useMemo(() => { + const merged = { + ...configProp, + }; + if (merged.itemListMode === "registered" && !merged.screenCode && screenId) { + merged.screenCode = `screen_${screenId}`; + } + return merged; + }, [configProp, screenId]); + const config: ProcessWorkStandardConfig = useMemo( () => ({ ...defaultConfig, - ...configProp, - dataSource: { ...defaultConfig.dataSource, ...configProp?.dataSource }, - phases: configProp?.phases?.length - ? configProp.phases + ...resolvedConfig, + dataSource: { ...defaultConfig.dataSource, ...resolvedConfig?.dataSource }, + phases: resolvedConfig?.phases?.length + ? resolvedConfig.phases : defaultConfig.phases, - detailTypes: configProp?.detailTypes?.length - ? configProp.detailTypes + detailTypes: resolvedConfig?.detailTypes?.length + ? resolvedConfig.detailTypes : defaultConfig.detailTypes, }), - [configProp] + [resolvedConfig] ); const { @@ -46,7 +58,8 @@ export function ProcessWorkStandardComponent({ selectedDetailsByPhase, selection, loading, - fetchItems, + isRegisteredMode, + loadItems, selectItem, selectProcess, fetchWorkItemDetails, @@ -112,8 +125,8 @@ export function ProcessWorkStandardComponent({ ); const handleInit = useCallback(() => { - fetchItems(); - }, [fetchItems]); + loadItems(); + }, [loadItems]); const splitRatio = config.splitRatio || 30; @@ -144,7 +157,7 @@ export function ProcessWorkStandardComponent({ items={items} routings={routings} selection={selection} - onSearch={(keyword) => fetchItems(keyword)} + onSearch={(keyword) => loadItems(keyword)} onSelectItem={selectItem} onSelectProcess={selectProcess} onInit={handleInit} diff --git a/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardConfigPanel.tsx b/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardConfigPanel.tsx index 21a5d69f..27af18f4 100644 --- a/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardConfigPanel.tsx +++ b/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardConfigPanel.tsx @@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { ProcessWorkStandardConfig, WorkPhaseDefinition, DetailTypeDefinition } from "./types"; import { defaultConfig } from "./config"; @@ -81,6 +82,30 @@ export function ProcessWorkStandardConfigPanel({

공정 작업기준 설정

+ {/* 품목 목록 모드 */} +
+

품목 목록 모드

+
+ +

+ {config.itemListMode === "registered" + ? "품목별 라우팅 탭에서 등록한 품목만 표시됩니다. screenCode는 화면 ID 기준으로 자동 설정됩니다." + : "모든 품목을 표시합니다."} +

+
+
+ {/* 데이터 소스 설정 */}

데이터 소스 설정

diff --git a/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardRenderer.tsx b/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardRenderer.tsx index cb1e0e85..aedfae37 100644 --- a/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardRenderer.tsx +++ b/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardRenderer.tsx @@ -9,7 +9,7 @@ export class ProcessWorkStandardRenderer extends AutoRegisteringComponentRendere static componentDefinition = V2ProcessWorkStandardDefinition; render(): React.ReactElement { - const { formData, isPreview, config, tableName } = this.props as Record< + const { formData, isPreview, config, tableName, screenId } = this.props as Record< string, unknown >; @@ -20,6 +20,7 @@ export class ProcessWorkStandardRenderer extends AutoRegisteringComponentRendere formData={formData as Record} tableName={tableName as string} isPreview={isPreview as boolean} + screenId={screenId as number | string} /> ); } diff --git a/frontend/lib/registry/components/v2-process-work-standard/config.ts b/frontend/lib/registry/components/v2-process-work-standard/config.ts index 43ad60cd..22b36ea3 100644 --- a/frontend/lib/registry/components/v2-process-work-standard/config.ts +++ b/frontend/lib/registry/components/v2-process-work-standard/config.ts @@ -31,4 +31,6 @@ export const defaultConfig: ProcessWorkStandardConfig = { splitRatio: 30, leftPanelTitle: "품목 및 공정 선택", readonly: false, + itemListMode: "all", + screenCode: "", }; diff --git a/frontend/lib/registry/components/v2-process-work-standard/hooks/useProcessWorkStandard.ts b/frontend/lib/registry/components/v2-process-work-standard/hooks/useProcessWorkStandard.ts index e909d291..194a7d95 100644 --- a/frontend/lib/registry/components/v2-process-work-standard/hooks/useProcessWorkStandard.ts +++ b/frontend/lib/registry/components/v2-process-work-standard/hooks/useProcessWorkStandard.ts @@ -32,7 +32,9 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) { processName: null, }); - // 품목 목록 조회 + const isRegisteredMode = config.itemListMode === "registered"; + + // 품목 목록 조회 (전체 모드) const fetchItems = useCallback( async (search?: string) => { try { @@ -59,6 +61,53 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) { [config.dataSource] ); + // 등록 품목 조회 (등록 모드) + const fetchRegisteredItems = useCallback( + async (search?: string) => { + const screenCode = config.screenCode; + if (!screenCode) { + console.warn("screenCode가 설정되지 않았습니다"); + setItems([]); + return; + } + try { + setLoading(true); + const ds = config.dataSource; + const params = new URLSearchParams({ + tableName: ds.itemTable, + nameColumn: ds.itemNameColumn, + codeColumn: ds.itemCodeColumn, + routingTable: ds.routingVersionTable, + routingFkColumn: ds.routingFkColumn, + ...(search ? { search } : {}), + }); + const res = await apiClient.get( + `${API_BASE}/registered-items/${encodeURIComponent(screenCode)}?${params}` + ); + if (res.data?.success) { + setItems(res.data.data || []); + } + } catch (err) { + console.error("등록 품목 조회 실패", err); + } finally { + setLoading(false); + } + }, + [config.dataSource, config.screenCode] + ); + + // 모드에 따라 적절한 함수 호출 + const loadItems = useCallback( + async (search?: string) => { + if (isRegisteredMode) { + await fetchRegisteredItems(search); + } else { + await fetchItems(search); + } + }, + [isRegisteredMode, fetchItems, fetchRegisteredItems] + ); + // 라우팅 + 공정 조회 const fetchRoutings = useCallback( async (itemCode: string) => { @@ -340,7 +389,10 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) { selection, loading, saving, + isRegisteredMode, fetchItems, + fetchRegisteredItems, + loadItems, selectItem, selectProcess, fetchWorkItems, diff --git a/frontend/lib/registry/components/v2-process-work-standard/types.ts b/frontend/lib/registry/components/v2-process-work-standard/types.ts index 6d2b0bea..7185429b 100644 --- a/frontend/lib/registry/components/v2-process-work-standard/types.ts +++ b/frontend/lib/registry/components/v2-process-work-standard/types.ts @@ -37,6 +37,10 @@ export interface ProcessWorkStandardConfig { splitRatio?: number; leftPanelTitle?: string; readonly?: boolean; + /** 품목 목록 모드: all=전체, registered=등록된 품목만 */ + itemListMode?: "all" | "registered"; + /** 등록 모드 시 화면 코드 (자동 설정됨) */ + screenCode?: string; } // ============================================================ @@ -121,6 +125,7 @@ export interface ProcessWorkStandardComponentProps { formData?: Record; isPreview?: boolean; tableName?: string; + screenId?: number | string; } // 선택 상태 From 7a65ab0f853a0593c0b59866787940e7e6c6e6e4 Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 13 Mar 2026 15:02:06 +0900 Subject: [PATCH 2/3] docs: update full-screen analysis and V2 component usage guide - Revised the full-screen analysis document to reflect the latest updates, including the purpose and core rules for screen development. - Expanded the V2 component usage guide to include a comprehensive catalog of components, their configurations, and usage guidelines for LLM and chatbot applications. - Added a summary of the system architecture and clarified the implementation methods for user business screens and admin menus. - Enhanced the documentation to serve as a reference for AI agents and screen designers, ensuring adherence to the established guidelines. These updates aim to improve clarity and usability for developers and designers working with the WACE ERP screen composition system. Made-with: Cursor --- .../00_analysis/full-screen-analysis.md | 682 +++++---- .../00_analysis/v2-component-usage-guide.md | 1323 ++++++++++------- frontend/components/v2/V2Input.tsx | 120 +- frontend/lib/formatting/index.ts | 19 +- .../modal-repeater-table/RepeaterTable.tsx | 38 +- .../SelectedItemsDetailInputComponent.tsx | 12 +- .../SimpleRepeaterTableComponent.tsx | 40 +- .../SplitPanelLayoutComponent.tsx | 24 +- .../v2-table-list/TableListComponent.tsx | 33 +- 9 files changed, 1383 insertions(+), 908 deletions(-) diff --git a/docs/screen-implementation-guide/00_analysis/full-screen-analysis.md b/docs/screen-implementation-guide/00_analysis/full-screen-analysis.md index 9b4a9908..378b13b4 100644 --- a/docs/screen-implementation-guide/00_analysis/full-screen-analysis.md +++ b/docs/screen-implementation-guide/00_analysis/full-screen-analysis.md @@ -1,331 +1,485 @@ -# 화면 전체 분석 보고서 +# WACE ERP 화면 구성 시스템 전체 분석 -> **분석 대상**: `/Users/kimjuseok/Downloads/화면개발 8` 폴더 내 핵심 업무 화면 -> **분석 기준**: 메뉴별 분류, 3개 이상 재활용 가능한 컴포넌트 식별 -> **분석 일자**: 2026-01-30 +> **최종 업데이트**: 2026-03-13 +> **용도**: LLM 챗봇 / AI 에이전트가 화면 개발 요청을 받았을 때 참조하는 시스템 구조 레퍼런스 +> **핵심 규칙**: 사용자 업무 화면은 React 코드(.tsx)로 직접 만들지 않는다. DB 등록(screen_definitions + screen_layouts_v2 + menu_info)으로 구현한다. --- -## 1. 현재 사용 중인 V2 컴포넌트 목록 +## 1. 시스템 아키텍처 요약 -> **중요**: v2- 접두사가 붙은 컴포넌트만 사용합니다. +### 1.1 화면 렌더링 파이프라인 -### 입력 컴포넌트 -| ID | 이름 | 용도 | -|----|------|------| -| `v2-input` | V2 입력 | 텍스트, 숫자, 비밀번호, 이메일 등 입력 | -| `v2-select` | V2 선택 | 드롭다운, 콤보박스, 라디오, 체크박스 | -| `v2-date` | V2 날짜 | 날짜, 시간, 날짜범위 입력 | +``` +[DB] screen_definitions (화면 정의) + + screen_layouts_v2 (레이아웃 JSON) + + menu_info (메뉴 등록) + │ + ▼ +[Backend API] GET /api/screens/:screenId + │ + ▼ +[Frontend] /screens/[screenId]/page.tsx + │ + ▼ +[Converter] layoutV2Converter.ts → V2 JSON을 Legacy 포맷으로 변환 + │ + ▼ +[Renderer] ResponsiveGridRenderer → RealtimePreview → DynamicComponentRenderer + │ + ▼ +[Registry] ComponentRegistry.getComponent(componentType) → 실제 React 컴포넌트 렌더링 +``` -### 표시 컴포넌트 -| ID | 이름 | 용도 | -|----|------|------| -| `v2-text-display` | 텍스트 표시 | 라벨, 텍스트 표시 | -| `v2-card-display` | 카드 디스플레이 | 테이블 데이터를 카드 형태로 표시 | -| `v2-aggregation-widget` | 집계 위젯 | 합계, 평균, 개수 등 집계 표시 | +### 1.2 화면 유형 분류 -### 테이블/데이터 컴포넌트 -| ID | 이름 | 용도 | -|----|------|------| -| `v2-table-list` | 테이블 리스트 | 데이터 테이블 표시, 페이지네이션, 정렬, 필터 | -| `v2-table-search-widget` | 검색 필터 | 화면 내 테이블 검색/필터/그룹 기능 | -| `v2-pivot-grid` | 피벗 그리드 | 다차원 데이터 분석 (피벗 테이블) | +| 구분 | 구현 방식 | 코드 위치 | 예시 | +|------|----------|----------|------| +| **사용자 업무 화면** | DB 등록 (SQL INSERT) | screen_definitions + screen_layouts_v2 | 수주관리, 품목정보, BOM관리 | +| **관리자 메뉴** | React 코드 직접 작성 | frontend/app/(main)/admin/*/page.tsx | 사용자관리, 권한관리, 시스템설정 | -### 레이아웃 컴포넌트 -| ID | 이름 | 용도 | -|----|------|------| -| `v2-split-panel-layout` | 분할 패널 | 마스터-디테일 좌우 분할 레이아웃 | -| `v2-tabs-widget` | 탭 위젯 | 탭 전환, 탭 내 컴포넌트 배치 | -| `v2-section-card` | Section Card | 제목/테두리가 있는 그룹화 컨테이너 | -| `v2-section-paper` | Section Paper | 배경색 기반 미니멀 그룹화 컨테이너 | -| `v2-divider-line` | 구분선 | 영역 구분 | -| `v2-repeat-container` | 리피터 컨테이너 | 데이터 수만큼 내부 컴포넌트 반복 렌더링 | -| `v2-repeater` | 리피터 | 반복 컨트롤 | - -### 액션/기타 컴포넌트 -| ID | 이름 | 용도 | -|----|------|------| -| `v2-button-primary` | 기본 버튼 | 저장, 삭제 등 액션 버튼 | -| `v2-numbering-rule` | 채번규칙 | 자동 코드/번호 생성 | -| `v2-category-manager` | 카테고리 관리자 | 카테고리 관리 | -| `v2-location-swap-selector` | 위치 교환 선택기 | 위치 교환 기능 | -| `v2-rack-structure` | 랙 구조 | 창고 랙 시각화 | -| `v2-media` | 미디어 | 미디어 표시 | - -**총 23개 V2 컴포넌트** +**절대 규칙**: 사용자 업무 화면(생산, 영업, 구매, 물류, 품질 등)은 React 하드코딩 금지. 반드시 DB 등록 방식으로 구현. --- -## 2. 화면 분류 (메뉴별) +## 2. V2 컴포넌트 전체 목록 (32개) -### 01. 기준정보 (master-data) -| 화면명 | 파일명 | 패턴 | 구현 가능 | -|--------|--------|------|----------| -| 회사정보 | 회사정보.html | 검색+테이블 | ✅ 완전 | -| 부서정보 | 부서정보.html | 검색+테이블 | ✅ 완전 | -| 품목정보 | 품목정보.html | 검색+테이블+그룹화 | ⚠️ 그룹화 미지원 | -| BOM관리 | BOM관리.html | 분할패널+트리 | ⚠️ 트리뷰 미지원 | -| 공정정보관리 | 공정정보관리.html | 분할패널+테이블 | ✅ 완전 | -| 공정작업기준 | 공정작업기준관리.html | 검색+테이블 | ✅ 완전 | -| 품목라우팅 | 품목라우팅관리.html | 분할패널+테이블 | ✅ 완전 | +> 모든 컴포넌트는 `v2-` 접두사를 사용한다. 접두사 없는 컴포넌트는 레거시이므로 사용 금지. -### 02. 영업관리 (sales) -| 화면명 | 파일명 | 패턴 | 구현 가능 | -|--------|--------|------|----------| -| 수주관리 | 수주관리.html | 분할패널+테이블 | ✅ 완전 | -| 견적관리 | 견적관리.html | 분할패널+테이블 | ✅ 완전 | -| 거래처관리 | 거래처관리.html | 분할패널+탭+그룹화 | ⚠️ 그룹화 미지원 | -| 판매품목정보 | 판매품목정보.html | 검색+테이블 | ✅ 완전 | -| 출하계획관리 | 출하계획관리.html | 검색+테이블 | ✅ 완전 | +### 2.1 입력 컴포넌트 (9개) -### 03. 생산관리 (production) -| 화면명 | 파일명 | 패턴 | 구현 가능 | -|--------|--------|------|----------| -| 생산계획관리 | 생산계획관리.html | 분할패널+탭+타임라인 | ❌ 타임라인 미지원 | -| 생산관리 | 생산관리.html | 검색+테이블 | ✅ 완전 | -| 생산실적관리 | 생산실적관리.html | 검색+테이블 | ✅ 완전 | -| 작업지시 | 작업지시.html | 탭+그룹화테이블+분할패널 | ⚠️ 그룹화 미지원 | -| 공정관리 | 공정관리.html | 분할패널+테이블 | ✅ 완전 | +| ID | 용도 | 핵심 설정 | +|----|------|----------| +| `v2-input` | 텍스트, 숫자, 비밀번호, textarea, 슬라이더, 컬러, 버튼 | inputType, format(email/tel/url/currency/biz_no), required, readonly, maxLength | +| `v2-select` | 드롭다운, 콤보박스, 라디오, 체크박스, 태그, 토글, 스왑 | mode, source(static/code/db/api/entity/category/distinct/select), searchable, multiple, cascading | +| `v2-date` | 날짜, 시간, 날짜시간, 날짜범위, 월, 연도 | dateType(date/time/datetime), format, range, minDate, maxDate | +| `v2-file-upload` | 파일/이미지 업로드, 다중 업로드 | accept, maxSize, multiple | +| `v2-media` | 이미지, 비디오, 오디오 표시 | mediaType | +| `v2-location-swap-selector` | 출발지/도착지 선택 및 교환 | - | +| `v2-rack-structure` | 창고 랙 위치 일괄 생성 | columns, rows | +| `v2-process-work-standard` | 품목별 공정 작업기준 관리 (Pre/In/Post-Work) | - | +| `v2-item-routing` | 품목별 라우팅 버전 및 공정 순서 관리 (3단계 계층) | - | -### 04. 구매관리 (purchase) -| 화면명 | 파일명 | 패턴 | 구현 가능 | -|--------|--------|------|----------| -| 발주관리 | 발주관리.html | 검색+테이블 | ✅ 완전 | -| 공급업체관리 | 공급업체관리.html | 검색+테이블 | ✅ 완전 | -| 구매입고 | pages/구매입고.html | 검색+테이블 | ✅ 완전 | +### 2.2 표시/데이터 컴포넌트 (10개) -### 05. 설비관리 (equipment) -| 화면명 | 파일명 | 패턴 | 구현 가능 | -|--------|--------|------|----------| -| 설비정보 | 설비정보.html | 분할패널+카드+탭 | ✅ v2-card-display 활용 | +| ID | 용도 | 핵심 설정 | +|----|------|----------| +| `v2-table-list` | 데이터 테이블 (조회/편집, 페이지네이션, 정렬, 필터) | selectedTable, columns, pagination, displayMode(table/card), checkbox, linkedFilters | +| `v2-table-grouped` | 그룹화 테이블 (접기/펼치기, 그룹별 집계) | groupConfig(groupByColumn, summary), v2-table-list 기반 확장 | +| `v2-table-search-widget` | 테이블 검색/필터/그룹 바 | autoSelectFirstTable, showTableSelector | +| `v2-pivot-grid` | 다차원 피벗 분석 (행/열/데이터/필터 영역) | fields(area, summaryType, groupInterval), dataSource | +| `v2-text-display` | 라벨, 제목, 설명 텍스트 표시 | fontSize, fontWeight, color, textAlign | +| `v2-card-display` | 테이블 데이터를 카드 형태로 표시 | cardsPerRow, columnMapping(title/subtitle/image/status), cardStyle | +| `v2-aggregation-widget` | 합계, 평균, 개수, 최대, 최소 집계 카드 | items, filters, layout | +| `v2-status-count` | 상태별 건수 카드 표시 | statusField, countField | +| `v2-numbering-rule` | 자동 코드/번호 채번 (접두사+날짜+순번) | rule, prefix, format | +| `v2-category-manager` | 트리 기반 카테고리 관리 (3단계 계층) | - | -### 06. 물류관리 (logistics) -| 화면명 | 파일명 | 패턴 | 구현 가능 | -|--------|--------|------|----------| -| 창고관리 | 창고관리.html | 모바일앱스타일+iframe | ❌ 별도개발 필요 | -| 창고정보관리 | 창고정보관리.html | 검색+테이블 | ✅ 완전 | -| 입출고관리 | 입출고관리.html | 검색+테이블+그룹화 | ⚠️ 그룹화 미지원 | -| 재고현황 | 재고현황.html | 검색+테이블 | ✅ 완전 | +### 2.3 레이아웃 컴포넌트 (8개) -### 07. 품질관리 (quality) -| 화면명 | 파일명 | 패턴 | 구현 가능 | -|--------|--------|------|----------| -| 검사기준 | 검사기준.html | 검색+테이블 | ✅ 완전 | -| 검사정보관리 | 검사정보관리.html | 탭+테이블 | ✅ 완전 | -| 검사장비관리 | 검사장비관리.html | 검색+테이블 | ✅ 완전 | -| 불량관리 | 불량관리.html | 검색+테이블 | ✅ 완전 | -| 클레임관리 | 클레임관리.html | 검색+테이블 | ✅ 완전 | +| ID | 용도 | 핵심 설정 | +|----|------|----------| +| `v2-split-panel-layout` | 마스터-디테일 좌우 분할 | splitRatio, leftPanel(displayMode/tableName), rightPanel(relation/foreignKey), additionalTabs | +| `v2-tabs-widget` | 탭 전환, 탭 내 컴포넌트 배치 | tabs(id/label/components), defaultTab, orientation | +| `v2-section-card` | 제목+테두리가 있는 그룹화 컨테이너 | title, collapsible | +| `v2-section-paper` | 배경색 기반 미니멀 그룹화 컨테이너 | backgroundColor, padding | +| `v2-divider-line` | 영역 구분선 | orientation, thickness | +| `v2-split-line` | 캔버스 좌우 분할용 드래그 가능 세로선 | - | +| `v2-repeat-container` | 데이터 수만큼 내부 컴포넌트 반복 렌더링 | dataSourceType, layout, gridColumns | +| `v2-repeater` | 인라인/모달/버튼 모드 반복 데이터 관리 | mode(inline/modal/button) | + +### 2.4 특수/비즈니스 컴포넌트 (5개) + +| ID | 용도 | 핵심 설정 | +|----|------|----------| +| `v2-button-primary` | 저장/삭제/조회 등 액션 버튼 | text, actionType, variant, webTypeConfig.dataflowConfig | +| `v2-timeline-scheduler` | 간트차트형 일정/계획 시각화 (드래그/리사이즈) | selectedTable, resourceTable, fieldMapping, zoomLevel, editable | +| `v2-approval-step` | 결재 단계 스테퍼 시각화 | - | +| `v2-bom-tree` | BOM 계층 트리 표시 (정전개/역전개) | - | +| `v2-bom-item-editor` | BOM 하위품목 트리 편집 | - | --- -## 3. 화면 UI 패턴 분석 +## 3. 화면 UI 패턴 분류 (7가지) -### 패턴 A: 검색 + 테이블 (가장 기본) -**해당 화면**: 약 60% (15개 이상) +> AI가 화면 개발 요청을 받았을 때, 아래 패턴 중 해당하는 것을 선택하여 구현한다. -**사용 컴포넌트**: -- `v2-table-search-widget`: 검색 필터 -- `v2-table-list`: 데이터 테이블 +### 패턴 A: 기본 마스터 (검색 + 테이블) + +**적용 비율**: 약 50% (가장 흔함) +**적용 화면**: 코드관리, 부서정보, 창고정보, 검사기준, 불량관리, 공급업체관리 등 ``` -┌─────────────────────────────────────────┐ -│ [검색필드들...] [조회] [엑셀] │ ← v2-table-search-widget -├─────────────────────────────────────────┤ -│ 테이블 제목 [신규등록] [삭제] │ -│ ────────────────────────────────────── │ -│ □ | 코드 | 이름 | 상태 | 등록일 | │ ← v2-table-list -│ □ | A001 | 테스트| 사용 | 2026-01-30 | │ -└─────────────────────────────────────────┘ +┌─────────────────────────────────────────────────┐ +│ v2-table-search-widget │ +│ [검색필드1] [검색필드2] [조회] [엑셀] │ +├─────────────────────────────────────────────────┤ +│ v2-table-list │ +│ 제목 [신규] [삭제] │ +│ ─────────────────────────────────────────────── │ +│ □ | 코드 | 이름 | 상태 | 등록일 │ +└─────────────────────────────────────────────────┘ ``` -### 패턴 B: 분할 패널 (마스터-디테일) -**해당 화면**: 약 25% (8개) +**필수 컴포넌트**: `v2-table-search-widget` (1개) + `v2-table-list` (1개) -**사용 컴포넌트**: -- `v2-split-panel-layout`: 좌우 분할 -- `v2-table-list`: 마스터/디테일 테이블 -- `v2-tabs-widget`: 상세 탭 (선택) +### 패턴 B: 마스터-디테일 (좌우 분할) + +**적용 비율**: 약 25% +**적용 화면**: 공정관리, 수주관리, 견적관리, 품목라우팅 등 ``` -┌──────────────────┬──────────────────────┐ -│ 마스터 리스트 │ 상세 정보 / 탭 │ -│ ─────────────── │ ┌────┬────┬────┐ │ -│ □ A001 제품A │ │기본│이력│첨부│ │ -│ □ A002 제품B ← │ └────┴────┴────┘ │ -│ □ A003 제품C │ [테이블 or 폼] │ -└──────────────────┴──────────────────────┘ +┌──────────────────┬──────────────────────────────┐ +│ 마스터 테이블 │ 디테일 테이블/폼 │ +│ (좌측 패널) │ (우측 패널) │ +│ □ A001 항목1 │ [상세 정보 테이블] │ +│ □ A002 항목2 ← │ │ +└──────────────────┴──────────────────────────────┘ + v2-split-panel-layout ``` -### 패턴 C: 탭 + 테이블 -**해당 화면**: 약 10% (3개) +**필수 컴포넌트**: `v2-split-panel-layout` (1개) -**사용 컴포넌트**: -- `v2-tabs-widget`: 탭 전환 -- `v2-table-list`: 탭별 테이블 +### 패턴 C: 마스터-디테일 + 탭 + +**적용 비율**: 약 10% +**적용 화면**: 거래처관리, 설비정보, 품목정보 등 ``` -┌─────────────────────────────────────────┐ -│ [탭1] [탭2] [탭3] │ -├─────────────────────────────────────────┤ -│ [테이블 영역] │ -└─────────────────────────────────────────┘ +┌──────────────────┬──────────────────────────────┐ +│ 마스터 테이블 │ [기본] [이력] [첨부] │ +│ │ ┌────────────────────────┐ │ +│ □ A001 거래처1 │ │ 탭별 컨텐츠 │ │ +│ □ A002 거래처2 ← │ └────────────────────────┘ │ +└──────────────────┴──────────────────────────────┘ ``` -### 패턴 D: 특수 UI -**해당 화면**: 약 5% (2개) +**필수 컴포넌트**: `v2-split-panel-layout` (1개, rightPanel에 additionalTabs 설정) -- 생산계획관리: 타임라인/간트 차트 → **v2-timeline 미존재** -- 창고관리: 모바일 앱 스타일 → **별도 개발 필요** +### 패턴 D: 카드 뷰 + +**적용 비율**: 약 5% +**적용 화면**: 설비정보, 대시보드, 상품 카탈로그 등 + +``` +┌─────────────────────────────────────────────────┐ +│ v2-table-search-widget │ +├─────────────────────────────────────────────────┤ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ [이미지]│ │ [이미지]│ │ [이미지]│ │ +│ │ 제목 │ │ 제목 │ │ 제목 │ │ +│ │ 설명 │ │ 설명 │ │ 설명 │ │ +│ └─────────┘ └─────────┘ └─────────┘ │ +│ v2-card-display │ +└─────────────────────────────────────────────────┘ +``` + +**필수 컴포넌트**: `v2-card-display` (1개) + `v2-table-search-widget` (선택) + +### 패턴 E: 피벗 분석 + +**적용 비율**: 약 3% +**적용 화면**: 매출분석, 재고현황, 생산실적 분석 등 + +**필수 컴포넌트**: `v2-pivot-grid` (1개) + +### 패턴 F: 그룹화 테이블 + +**적용 비율**: 약 5% +**적용 화면**: 품목정보(카테고리별), 입출고관리(구분별), 작업지시(공정별) 등 + +``` +┌─────────────────────────────────────────────────┐ +│ [전체 펼치기] [전체 접기] │ +├─────────────────────────────────────────────────┤ +│ ▼ □ 그룹A 수량: 150 3건 │ +│ ├─ □ 항목1 50개 │ +│ ├─ □ 항목2 50개 │ +│ └─ □ 항목3 50개 │ +│ ► □ 그룹B 수량: 200 2건 │ +└─────────────────────────────────────────────────┘ +``` + +**필수 컴포넌트**: `v2-table-grouped` (1개) + +### 패턴 G: 타임라인/간트차트 + +**적용 비율**: 약 2% +**적용 화면**: 생산계획관리, 설비가동현황 등 + +``` +┌────────────┬─────────────────────────────────────┐ +│ │ 15(수) │ 16(목) │ 17(금) │ 18(토) │ +├────────────┼─────────────────────────────────────┤ +│ 설비A │ ████████████████ │ +│ 설비B │ █████████████████████ │ +│ 설비C │ ████████████████ │ +└────────────┴─────────────────────────────────────┘ +``` + +**필수 컴포넌트**: `v2-timeline-scheduler` (1개) --- -## 4. 신규 컴포넌트 분석 (3개 이상 재활용 기준) +## 4. 패턴 판단 의사결정 트리 -### 4.1 v2-grouped-table (그룹화 테이블) -**재활용 화면 수**: 5개 이상 ✅ +> AI가 화면 요청을 받았을 때 이 트리를 따라 패턴을 결정한다. -| 화면 | 그룹화 기준 | -|------|------------| -| 품목정보 | 품목구분, 카테고리 | -| 거래처관리 | 거래처유형, 지역 | -| 작업지시 | 작업일자, 공정 | -| 입출고관리 | 입출고구분, 창고 | -| 견적관리 | 상태, 거래처 | +``` +Q1. 시간축 기반 일정/간트차트가 필요한가? +├─ YES → 패턴 G (타임라인) +└─ NO ↓ -**기능 요구사항**: -- 특정 컬럼 기준 그룹핑 -- 그룹 접기/펼치기 -- 그룹 헤더에 집계 표시 -- 다중 그룹핑 지원 +Q2. 다차원 집계/피벗 분석이 필요한가? +├─ YES → 패턴 E (피벗) +└─ NO ↓ -**구현 복잡도**: 중 +Q3. 데이터를 그룹별로 묶어서 접기/펼치기가 필요한가? +├─ YES → 패턴 F (그룹화 테이블) +└─ NO ↓ -### 4.2 v2-tree-view (트리 뷰) -**재활용 화면 수**: 3개 ✅ +Q4. 이미지+정보를 카드 형태로 표시하는가? +├─ YES → 패턴 D (카드 뷰) +└─ NO ↓ -| 화면 | 트리 용도 | -|------|----------| -| BOM관리 | BOM 구조 (정전개/역전개) | -| 부서정보 | 조직도 | -| 메뉴관리 | 메뉴 계층 | +Q5. 마스터 테이블 선택 시 연관 디테일이 필요한가? +├─ YES → Q5-1. 디테일에 탭이 필요한가? +│ ├─ YES → 패턴 C (마스터-디테일+탭) +│ └─ NO → 패턴 B (마스터-디테일) +└─ NO → 패턴 A (기본 마스터) +``` -**기능 요구사항**: -- 노드 접기/펼치기 -- 드래그앤드롭 (선택) -- 정전개/역전개 전환 -- 노드 선택 이벤트 +--- -**구현 복잡도**: 중상 +## 5. 화면 구현 불가능/제한 사항 -### 4.3 v2-timeline-scheduler (타임라인) -**재활용 화면 수**: 1~2개 (기준 미달) +### 5.1 현재 불가능 (별도 React 개발 필요) -| 화면 | 용도 | +| 기능 | 상태 | 대안 | +|------|------|------| +| 칸반 보드 (드래그앤드롭) | 미지원 | 별도 React 컴포넌트 | +| 모바일 네이티브 앱 스타일 | 미지원 | 별도 개발 | +| 복잡한 차트 (line, bar, pie) | 미지원 | 외부 라이브러리 연동 | + +### 5.2 이전에 불가능했으나 현재 지원되는 기능 + +| 기능 | 지원 컴포넌트 | 추가 시점 | +|------|-------------|----------| +| 그룹화 테이블 | `v2-table-grouped` | 2026-01 | +| 타임라인/간트차트 | `v2-timeline-scheduler` | 2026-01 | +| BOM 트리 (정전개/역전개) | `v2-bom-tree` | 2026-02 | +| BOM 하위품목 편집 | `v2-bom-item-editor` | 2026-02 | +| 결재 스테퍼 | `v2-approval-step` | 2026-02 | + +### 5.3 권장하지 않는 조합 + +| 조합 | 이유 | |------|------| -| 생산계획관리 | 간트 차트 | -| 설비 가동 현황 | 타임라인 | - -**기능 요구사항**: -- 시간축 기반 배치 -- 드래그로 일정 변경 -- 공정별 색상 구분 -- 줌 인/아웃 - -**구현 복잡도**: 상 - -> **참고**: 3개 미만이므로 우선순위 하향 +| 3단계 이상 중첩 분할 | 화면 복잡도 증가, 성능 저하 | +| 탭 안에 탭 | 사용성 저하 | +| 한 화면에 3개 이상 테이블 | 데이터 로딩 성능 | +| 피벗 + 상세 테이블 동시 | 데이터 과부하 | --- -## 5. 컴포넌트 커버리지 +## 6. 화면별 구현 현황 (메뉴 분류) -### 현재 V2 컴포넌트로 구현 가능 -``` -┌─────────────────────────────────────────────────┐ -│ 17개 화면 (65%) │ -│ - 기본 검색 + 테이블 패턴 │ -│ - 분할 패널 │ -│ - 탭 전환 │ -│ - 카드 디스플레이 │ -└─────────────────────────────────────────────────┘ -``` +### 6.1 기준정보 -### v2-grouped-table 개발 후 -``` -┌─────────────────────────────────────────────────┐ -│ +5개 화면 (22개, 85%) │ -│ - 품목정보, 거래처관리, 작업지시 │ -│ - 입출고관리, 견적관리 │ -└─────────────────────────────────────────────────┘ -``` +| 화면명 | 패턴 | 구현 가능 | 비고 | +|--------|------|----------|------| +| 회사정보 | A | 완전 지원 | | +| 부서정보 | A | 완전 지원 | | +| 품목정보 | F 또는 A | 완전 지원 | 카테고리별 그룹화 시 F | +| BOM관리 | B + v2-bom-tree | 완전 지원 | v2-bom-tree로 트리 구현 | +| 공정정보관리 | B | 완전 지원 | | +| 공정작업기준 | A | 완전 지원 | v2-process-work-standard 활용 가능 | +| 품목라우팅 | B | 완전 지원 | v2-item-routing 활용 가능 | -### v2-tree-view 개발 후 -``` -┌─────────────────────────────────────────────────┐ -│ +2개 화면 (24개, 92%) │ -│ - BOM관리, 부서정보(계층) │ -└─────────────────────────────────────────────────┘ -``` +### 6.2 영업관리 -### 별도 개발 필요 -``` -┌─────────────────────────────────────────────────┐ -│ 2개 화면 (8%) │ -│ - 생산계획관리 (타임라인) │ -│ - 창고관리 (모바일 앱 스타일) │ -└─────────────────────────────────────────────────┘ -``` +| 화면명 | 패턴 | 구현 가능 | 비고 | +|--------|------|----------|------| +| 수주관리 | B | 완전 지원 | | +| 견적관리 | B | 완전 지원 | | +| 거래처관리 | C | 완전 지원 | 탭(기본/이력/첨부) | +| 판매품목정보 | A | 완전 지원 | | +| 출하계획관리 | A | 완전 지원 | | + +### 6.3 생산관리 + +| 화면명 | 패턴 | 구현 가능 | 비고 | +|--------|------|----------|------| +| 생산계획관리 | G | 완전 지원 | v2-timeline-scheduler | +| 생산관리 | A | 완전 지원 | | +| 생산실적관리 | A | 완전 지원 | | +| 작업지시 | F | 완전 지원 | 공정별 그룹화 | +| 공정관리 | B | 완전 지원 | | + +### 6.4 구매관리 + +| 화면명 | 패턴 | 구현 가능 | 비고 | +|--------|------|----------|------| +| 발주관리 | A | 완전 지원 | | +| 공급업체관리 | A | 완전 지원 | | +| 구매입고 | A | 완전 지원 | | + +### 6.5 설비관리 + +| 화면명 | 패턴 | 구현 가능 | 비고 | +|--------|------|----------|------| +| 설비정보 | C 또는 D | 완전 지원 | 카드뷰 또는 탭 | + +### 6.6 물류관리 + +| 화면명 | 패턴 | 구현 가능 | 비고 | +|--------|------|----------|------| +| 창고정보관리 | A | 완전 지원 | | +| 입출고관리 | F | 완전 지원 | 구분별 그룹화 | +| 재고현황 | A 또는 E | 완전 지원 | 피벗 분석도 가능 | + +### 6.7 품질관리 + +| 화면명 | 패턴 | 구현 가능 | 비고 | +|--------|------|----------|------| +| 검사기준 | A | 완전 지원 | | +| 검사정보관리 | C | 완전 지원 | 탭(수입검사/공정검사/출하검사) | +| 검사장비관리 | A | 완전 지원 | | +| 불량관리 | A | 완전 지원 | | +| 클레임관리 | A | 완전 지원 | | --- -## 6. 신규 컴포넌트 개발 우선순위 - -| 순위 | 컴포넌트 | 재활용 화면 수 | 복잡도 | ROI | -|------|----------|--------------|--------|-----| -| 1 | v2-grouped-table | 5+ | 중 | ⭐⭐⭐⭐⭐ | -| 2 | v2-tree-view | 3 | 중상 | ⭐⭐⭐⭐ | -| 3 | v2-timeline-scheduler | 1~2 | 상 | ⭐⭐ | - ---- - -## 7. 권장 구현 전략 - -### Phase 1: 즉시 구현 (현재 V2 컴포넌트) -- 회사정보, 부서정보 -- 발주관리, 공급업체관리 -- 검사기준, 검사장비관리, 불량관리 -- 창고정보관리, 재고현황 -- 공정작업기준관리 -- 수주관리, 견적관리, 공정관리 -- 설비정보 (v2-card-display 활용) -- 검사정보관리 - -### Phase 2: v2-grouped-table 개발 후 -- 품목정보, 거래처관리, 입출고관리 -- 작업지시 - -### Phase 3: v2-tree-view 개발 후 -- BOM관리 -- 부서정보 (계층 뷰) - -### Phase 4: 개별 개발 -- 생산계획관리 (타임라인) -- 창고관리 (모바일 스타일) - ---- - -## 8. 요약 +## 7. 컴포넌트 커버리지 요약 | 항목 | 수치 | |------|------| -| 전체 분석 화면 수 | 26개 | -| 현재 즉시 구현 가능 | 17개 (65%) | -| v2-grouped-table 추가 시 | 22개 (85%) | -| v2-tree-view 추가 시 | 24개 (92%) | -| 별도 개발 필요 | 2개 (8%) | +| 전체 분석 대상 화면 | 26개 | +| V2 컴포넌트로 구현 가능 | **26개 (100%)** | +| 등록된 V2 컴포넌트 수 | 32개 | +| 화면 UI 패턴 수 | 7가지 (A~G) | -**핵심 결론**: -1. **현재 V2 컴포넌트**로 65% 화면 구현 가능 -2. **v2-grouped-table** 1개 컴포넌트 개발로 85%까지 확대 -3. **v2-tree-view** 추가로 92% 도달 -4. 나머지 8%는 화면별 특수 UI (타임라인, 모바일 스타일)로 개별 개발 필요 +**이전 대비 변경 사항**: +- BOM 트리 지원 추가 (v2-bom-tree) → BOM관리 완전 지원 +- 그룹화 테이블 지원 추가 (v2-table-grouped) → 품목정보, 입출고 등 완전 지원 +- 타임라인 지원 추가 (v2-timeline-scheduler) → 생산계획관리 완전 지원 +- 결재 스테퍼 추가 (v2-approval-step) → 결재 프로세스 시각화 가능 +- 전체 커버리지: 65% → **100%** 달성 + +--- + +## 8. UI vs 비즈니스 로직 분리 구조 + +``` +┌───────────────────────────────┬───────────────────────────────────┐ +│ UI 레이아웃 │ 제어관리 (비즈니스 로직) │ +│ screen_layouts_v2 (JSON) │ dataflow_diagrams (JSONB) │ +├───────────────────────────────┼───────────────────────────────────┤ +│ - 컴포넌트 배치/크기/위치 │ - 버튼 클릭 시 액션 (INSERT 등) │ +│ - 검색 필드 구성 │ - 조건부 실행 │ +│ - 테이블 컬럼 표시/숨김 │ - 다중 행 일괄 처리 │ +│ - 카드/탭/분할 레이아웃 │ - 테이블 간 데이터 이동 │ +│ - 페이지네이션/정렬 설정 │ - 외부 시스템 호출 │ +└───────────────────────────────┴───────────────────────────────────┘ +``` + +**핵심**: V2 컴포넌트는 UI만 담당한다. 버튼 클릭 시 INSERT/UPDATE/DELETE 같은 비즈니스 로직은 `dataflow_diagrams` 테이블에서 별도 설정한다. + +### 비즈니스 로직 실행 흐름 + +``` +v2-button-primary 클릭 + → webTypeConfig.dataflowConfig 확인 + → ImprovedButtonActionExecutor 실행 + 1. Before 제어 실행 (조건 체크) + 2. Main 액션 실행 (save/delete/update) + 3. After 제어 실행 (후처리) +``` + +--- + +## 9. 화면 개발 순서 (AI 에이전트용) + +``` +Step 1: DB 테이블 생성 + └─ 모든 컬럼 VARCHAR(500), 기본 5개 컬럼 필수 (id, created_date, updated_date, writer, company_code) + └─ table_labels, table_type_columns, column_labels 메타데이터 등록 + +Step 2: screen_definitions INSERT + └─ screen_code = '{company_code}_{순번}' + └─ table_name = 메인 테이블명 + +Step 3: screen_layouts_v2 INSERT + └─ layout_data = V2 레이아웃 JSON (version: "2.0", components 배열) + └─ 패턴(A~G)에 맞는 컴포넌트 배치 + +Step 4: dataflow_diagrams INSERT (비즈니스 로직이 필요한 경우) + └─ 버튼별 액션 정의 (INSERT/UPDATE/DELETE) + └─ 조건, 필드 매핑 설정 + +Step 5: menu_info INSERT + └─ menu_url = '/screen/{screen_code}' + └─ 적절한 parent_id로 메뉴 트리에 배치 +``` + +--- + +## 10. DB 테이블 구조 레퍼런스 + +### screen_definitions + +| 컬럼 | 타입 | 설명 | +|------|------|------| +| screen_id | integer PK (시퀀스) | 화면 고유 ID | +| screen_name | varchar(100) | 화면명 | +| screen_code | varchar(50) UNIQUE | 화면 코드 (`{company_code}_{순번}`) | +| table_name | varchar(100) | 기본 테이블명 | +| company_code | varchar(50) | 회사 코드 | +| description | text | 설명 | +| is_active | char(1) | Y/N/D | + +### screen_layouts_v2 + +| 컬럼 | 타입 | 설명 | +|------|------|------| +| layout_id | integer PK | 레이아웃 ID | +| screen_id | integer FK | 화면 ID | +| company_code | varchar(20) | 회사 코드 | +| layer_id | integer | 레이어 ID (1=기본) | +| layout_data | jsonb | 전체 레이아웃 JSON | + +### layout_data JSON 구조 + +```json +{ + "version": "2.0", + "components": [ + { + "id": "comp_xxx", + "url": "@/lib/registry/components/v2-table-list", + "position": { "x": 0, "y": 150, "z": 1 }, + "size": { "width": 1920, "height": 600 }, + "displayOrder": 0, + "overrides": { + "label": "품목 목록", + "tableName": "item_info", + "columns": [ + { "columnName": "item_code", "displayName": "품목코드", "visible": true } + ], + "pagination": { "enabled": true, "pageSize": 20 } + } + } + ], + "gridSettings": { "columns": 12, "gap": 16, "padding": 16 }, + "screenResolution": { "width": 1920, "height": 1080 } +} +``` + +### menu_info (주요 컬럼) + +| 컬럼 | 설명 | +|------|------| +| objid / menu_id | 메뉴 PK | +| screen_id | 연결된 화면 ID | +| menu_url | 메뉴 URL (`/screen/{screen_code}`) | +| company_code | 회사 코드 | diff --git a/docs/screen-implementation-guide/00_analysis/v2-component-usage-guide.md b/docs/screen-implementation-guide/00_analysis/v2-component-usage-guide.md index b37abf5e..1966a3c7 100644 --- a/docs/screen-implementation-guide/00_analysis/v2-component-usage-guide.md +++ b/docs/screen-implementation-guide/00_analysis/v2-component-usage-guide.md @@ -1,631 +1,846 @@ -# V2 공통 컴포넌트 사용 가이드 +# V2 컴포넌트 사용 가이드 (LLM/챗봇용) -> **목적**: 다양한 회사에서 V2 컴포넌트를 활용하여 화면을 개발할 때 참고하는 범용 가이드 -> **대상**: 화면 설계자, 개발자 -> **버전**: 1.1.0 -> **작성일**: 2026-02-23 (최종 업데이트) +> **최종 업데이트**: 2026-03-13 +> **용도**: LLM 챗봇이 화면 개발 요청을 받았을 때, 어떤 컴포넌트를 어떤 설정으로 사용해야 하는지 판단하는 레퍼런스 +> **버전**: 2.0.0 +> **대상**: AI 에이전트, 챗봇, 화면 설계자 --- -## 1. V2 컴포넌트로 가능한 것 / 불가능한 것 +## 0. 핵심 규칙 (반드시 준수) -### 1.1 가능한 화면 유형 - -| 화면 유형 | 설명 | 대표 예시 | -|-----------|------|----------| -| 마스터 관리 | 단일 테이블 CRUD | 회사정보, 부서정보, 코드관리 | -| 마스터-디테일 | 좌측 선택 → 우측 상세 | 공정관리, 품목라우팅, 견적관리 | -| 탭 기반 화면 | 탭별 다른 테이블/뷰 | 검사정보관리, 거래처관리 | -| 카드 뷰 | 이미지+정보 카드 형태 | 설비정보, 대시보드 | -| 피벗 분석 | 다차원 집계 | 매출분석, 재고현황 | -| 반복 컨테이너 | 데이터 수만큼 UI 반복 | 주문 상세, 항목 리스트 | -| 그룹화 테이블 | 그룹핑 기능 포함 테이블 | 카테고리별 집계, 부서별 현황 | -| 타임라인/스케줄 | 시간축 기반 일정 관리 | 생산일정, 작업스케줄 | - -### 1.2 불가능한 화면 유형 (별도 개발 필요) - -| 화면 유형 | 이유 | 해결 방안 | -|-----------|------|----------| -| 트리 뷰 (계층 구조) | 트리 컴포넌트 미존재 | `v2-tree-view` 개발 필요 | -| 드래그앤드롭 보드 | 칸반 스타일 UI 없음 | 별도 개발 | -| 모바일 앱 스타일 | 네이티브 앱 UI | 별도 개발 | -| 복잡한 차트 | 기본 집계 외 시각화 | 차트 라이브러리 연동 | - -> **참고**: 그룹화 테이블(`v2-table-grouped`)과 타임라인 스케줄러(`v2-timeline-scheduler`)는 v1.1에서 추가되어 이제 지원됩니다. +1. **사용자 업무 화면은 React 코드로 직접 만들지 않는다** → DB에 `screen_definitions` + `screen_layouts_v2` + `menu_info` INSERT로 구현 +2. **모든 컴포넌트는 `v2-` 접두사** → `v2-` 없는 레거시 컴포넌트 사용 금지 +3. **UI와 로직 분리** → UI는 `screen_layouts_v2`, 비즈니스 로직은 `dataflow_diagrams` +4. **멀티테넌시 필수** → 모든 테이블에 `company_code` 컬럼, 모든 쿼리에 `company_code` 필터링 --- -## 2. V2 컴포넌트 전체 목록 (25개) +## 1. 컴포넌트 전체 카탈로그 (32개) -### 2.1 입력 컴포넌트 (4개) +### 1.1 입력 컴포넌트 -| ID | 이름 | 용도 | 주요 옵션 | -|----|------|------|----------| -| `v2-input` | 입력 | 텍스트, 숫자, 비밀번호, 슬라이더, 컬러 | inputType(text/number/password/slider/color/button), format(email/tel/url/currency/biz_no), required, readonly, maxLength, min, max, step | -| `v2-select` | 선택 | 드롭다운, 콤보박스, 라디오, 체크, 태그, 토글, 스왑 | mode(dropdown/combobox/radio/check/tag/tagbox/toggle/swap), source(static/code/db/api/entity/category/distinct/select), searchable, multiple, cascading | -| `v2-date` | 날짜 | 날짜, 시간, 날짜시간 | dateType(date/time/datetime), format, range, minDate, maxDate, showToday | -| `v2-file-upload` | 파일 업로드 | 파일/이미지 업로드 | - | +#### v2-input -### 2.2 표시 컴포넌트 (3개) +텍스트, 숫자, 비밀번호, textarea 등 모든 단일 값 입력을 처리하는 통합 입력 컴포넌트. -| ID | 이름 | 용도 | 주요 옵션 | -|----|------|------|----------| -| `v2-text-display` | 텍스트 표시 | 라벨, 제목, 설명 텍스트 | fontSize, fontWeight, color, textAlign | -| `v2-card-display` | 카드 디스플레이 | 테이블 데이터를 카드 형태로 표시 | cardsPerRow, cardSpacing, columnMapping(titleColumn/subtitleColumn/descriptionColumn/imageColumn), cardStyle(imagePosition/imageSize), dataSource(table/static/api) | -| `v2-aggregation-widget` | 집계 위젯 | 합계, 평균, 개수, 최대, 최소 | items, filters, layout | +| 설정 | 타입 | 설명 | 값 | +|------|------|------|----| +| inputType | string | 입력 유형 | `text`, `number`, `password`, `slider`, `color`, `button`, `textarea` | +| format | string | 포맷 검증 | `email`, `tel`, `url`, `currency`, `biz_no` | +| required | boolean | 필수 여부 | true/false | +| readonly | boolean | 읽기 전용 | true/false | +| maxLength | number | 최대 길이 | 숫자 | +| min / max | number | 숫자 범위 | 숫자 (inputType=number일 때) | +| step | number | 증감 단위 | 숫자 (inputType=number/slider일 때) | +| tableName | string | 바인딩 테이블 | DB 테이블명 | +| columnName | string | 바인딩 컬럼 | DB 컬럼명 | -### 2.3 테이블/데이터 컴포넌트 (4개) +#### v2-select -| ID | 이름 | 용도 | 주요 옵션 | -|----|------|------|----------| -| `v2-table-list` | 테이블 리스트 | 데이터 조회/편집 테이블 | selectedTable, columns, pagination, filter, displayMode(table/card), checkbox, horizontalScroll, linkedFilters, excludeFilter, toolbar, tableStyle, autoLoad | -| `v2-table-search-widget` | 검색 필터 | 테이블 검색/필터/그룹 | autoSelectFirstTable, showTableSelector, title | -| `v2-pivot-grid` | 피벗 그리드 | 다차원 분석 (행/열/데이터 영역) | fields(area: row/column/data/filter, summaryType: sum/avg/count/min/max/countDistinct, groupInterval: year/quarter/month/week/day), dataSource(type: table/api/static, joinConfigs, filterConditions) | -| `v2-table-grouped` | 그룹화 테이블 | 그룹핑 기능이 포함된 테이블 | - | +드롭다운, 콤보박스, 라디오, 체크박스, 태그 등 선택형 입력 통합 컴포넌트. -### 2.4 레이아웃 컴포넌트 (7개) +| 설정 | 타입 | 설명 | 값 | +|------|------|------|----| +| mode | string | 선택 모드 | `dropdown`, `combobox`, `radio`, `check`, `tag`, `tagbox`, `toggle`, `swap` | +| source | string | 데이터 소스 | `static`, `code`, `db`, `api`, `entity`, `category`, `distinct`, `select` | +| searchable | boolean | 검색 가능 | true/false (mode=combobox일 때 기본 true) | +| multiple | boolean | 다중 선택 | true/false | +| cascading | object | 연쇄 선택 | 상위 select 값에 따라 하위 옵션 변경 | -| ID | 이름 | 용도 | 주요 옵션 | -|----|------|------|----------| -| `v2-split-panel-layout` | 분할 패널 | 마스터-디테일 좌우 분할 | splitRatio, resizable, minLeftWidth, minRightWidth, syncSelection, panel별: displayMode(list/table/custom), relation(type/foreignKey), editButton, addButton, deleteButton, additionalTabs | -| `v2-tabs-widget` | 탭 위젯 | 탭 전환, 탭 내 컴포넌트 | tabs(id/label/order/disabled/components), defaultTab, orientation(horizontal/vertical), allowCloseable, persistSelection | -| `v2-section-card` | 섹션 카드 | 제목+테두리 그룹화 | title, collapsible, padding | -| `v2-section-paper` | 섹션 페이퍼 | 배경색 그룹화 | backgroundColor, padding, shadow | -| `v2-divider-line` | 구분선 | 영역 구분 | orientation, thickness | -| `v2-repeat-container` | 리피터 컨테이너 | 데이터 수만큼 반복 렌더링 | dataSourceType, layout, gridColumns | -| `v2-repeater` | 리피터 | 반복 컨트롤 (inline/modal) | - | +**source 설명**: +- `static`: 고정 옵션 목록 (options 배열 직접 지정) +- `code`: code_info 테이블의 공통 코드 +- `db`: 특정 테이블의 컬럼 값 +- `entity`: 엔티티 조인 (다른 테이블 참조) +- `category`: v2-category-manager의 카테고리 +- `distinct`: 테이블 컬럼의 DISTINCT 값 -### 2.5 액션/특수 컴포넌트 (7개) +#### v2-date -| ID | 이름 | 용도 | 주요 옵션 | -|----|------|------|----------| -| `v2-button-primary` | 기본 버튼 | 저장, 삭제 등 액션 | text, actionType, variant | -| `v2-numbering-rule` | 채번규칙 | 자동 코드/번호 생성 | rule, prefix, format | -| `v2-category-manager` | 카테고리 관리자 | 카테고리 관리 UI | - | -| `v2-location-swap-selector` | 위치 교환 | 위치 선택/교환 | - | -| `v2-rack-structure` | 랙 구조 | 창고 랙 시각화 | - | -| `v2-media` | 미디어 | 이미지/동영상 표시 | - | -| `v2-timeline-scheduler` | 타임라인 스케줄러 | 시간축 기반 일정/작업 관리 | - | +날짜, 시간, 날짜시간, 날짜범위, 월, 연도 입력. + +| 설정 | 타입 | 설명 | 값 | +|------|------|------|----| +| dateType | string | 날짜 유형 | `date`, `time`, `datetime`, `month`, `year` | +| format | string | 표시 형식 | `YYYY-MM-DD`, `HH:mm`, `YYYY-MM-DD HH:mm` 등 | +| range | boolean | 범위 선택 | true/false (시작~종료 날짜) | +| minDate / maxDate | string | 선택 가능 범위 | ISO 8601 날짜 | +| showToday | boolean | 오늘 버튼 | true/false | + +#### v2-file-upload + +파일/이미지 업로드. 다중 업로드, 미리보기 지원. + +#### v2-media + +이미지, 비디오, 오디오 등 미디어 표시/재생. + +#### v2-location-swap-selector + +출발지/도착지 선택 및 교환 (물류 화면용). + +#### v2-rack-structure + +창고 랙 위치 일괄 생성 (열 범위, 단 수 설정). + +#### v2-process-work-standard + +품목별 공정 작업기준(Pre-Work/In-Work/Post-Work) 관리. 라우팅과 연동. + +#### v2-item-routing + +품목별 라우팅 버전 및 공정 순서 관리 (3단계 계층: 품목 → 라우팅 버전 → 공정). --- -## 3. 화면 패턴별 컴포넌트 조합 +### 1.2 표시/데이터 컴포넌트 -### 3.1 패턴 A: 기본 마스터 화면 (가장 흔함) +#### v2-table-list -**적용 화면**: 코드관리, 사용자관리, 부서정보, 창고정보 등 +가장 핵심 컴포넌트. DB 테이블 데이터를 조회/편집하는 테이블. -``` -┌─────────────────────────────────────────────────┐ -│ v2-table-search-widget │ -│ [검색필드1] [검색필드2] [조회] [엑셀] │ -├─────────────────────────────────────────────────┤ -│ v2-table-list │ -│ 제목 [신규] [삭제] │ -│ ─────────────────────────────────────────────── │ -│ □ | 코드 | 이름 | 상태 | 등록일 | │ -└─────────────────────────────────────────────────┘ -``` +| 설정 | 타입 | 설명 | 값 | +|------|------|------|----| +| selectedTable | string | **필수**. DB 테이블명 | 예: `"item_info"` | +| columns | array | 표시할 컬럼 설정 | `[{ columnName, displayName, visible, sortable, width, editable }]` | +| pagination | object | 페이지네이션 | `{ enabled: true, pageSize: 20, showSizeSelector: true }` | +| displayMode | string | 표시 모드 | `"table"` (기본) 또는 `"card"` | +| checkbox | object | 체크박스 | `{ enabled: true, multiple: true, position: "left", selectAll: true }` | +| horizontalScroll | object | 가로 스크롤 | `{ enabled: true, maxVisibleColumns: 8 }` | +| linkedFilters | array | 다른 컴포넌트와 연동 필터 | - | +| excludeFilter | object | 제외 필터 | - | +| autoLoad | boolean | 자동 데이터 로드 | true/false | +| stickyHeader | boolean | 헤더 고정 | true/false | +| toolbar | object | 툴바 (신규/삭제/엑셀 등) | - | +| tableStyle | object | 테이블 스타일 | `{ compact: true, striped: true }` | -**필수 컴포넌트**: -- `v2-table-search-widget` (1개) -- `v2-table-list` (1개) +**columns 배열 항목**: -**설정 포인트**: -- 테이블명 지정 -- 검색 대상 컬럼 설정 -- 컬럼 표시/숨김 설정 - ---- - -### 3.2 패턴 B: 마스터-디테일 화면 - -**적용 화면**: 공정관리, 견적관리, 수주관리, 품목라우팅 등 - -``` -┌──────────────────┬──────────────────────────────┐ -│ v2-table-list │ v2-table-list 또는 폼 │ -│ (마스터) │ (디테일) │ -│ ─────────────── │ │ -│ □ A001 항목1 │ [상세 정보] │ -│ □ A002 항목2 ← │ │ -│ □ A003 항목3 │ │ -└──────────────────┴──────────────────────────────┘ - v2-split-panel-layout -``` - -**필수 컴포넌트**: -- `v2-split-panel-layout` (1개) -- `v2-table-list` (2개: 마스터, 디테일) - -**설정 포인트**: -- `splitRatio`: 좌우 비율 (기본 30:70) -- `relation.type`: join / detail / custom -- `relation.foreignKey`: 연결 키 컬럼 - ---- - -### 3.3 패턴 C: 마스터-디테일 + 탭 - -**적용 화면**: 거래처관리, 품목정보, 설비정보 등 - -``` -┌──────────────────┬──────────────────────────────┐ -│ v2-table-list │ v2-tabs-widget │ -│ (마스터) │ ┌────┬────┬────┐ │ -│ │ │기본│이력│첨부│ │ -│ □ A001 거래처1 │ └────┴────┴────┘ │ -│ □ A002 거래처2 ← │ [탭별 컨텐츠] │ -└──────────────────┴──────────────────────────────┘ -``` - -**필수 컴포넌트**: -- `v2-split-panel-layout` (1개) -- `v2-table-list` (1개: 마스터) -- `v2-tabs-widget` (1개) - -**설정 포인트**: -- 탭별 표시할 테이블/폼 설정 -- 마스터 선택 시 탭 컨텐츠 연동 - ---- - -### 3.4 패턴 D: 카드 뷰 - -**적용 화면**: 설비정보, 대시보드, 상품 카탈로그 등 - -``` -┌─────────────────────────────────────────────────┐ -│ v2-table-search-widget │ -├─────────────────────────────────────────────────┤ -│ v2-card-display │ -│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ -│ │ [이미지]│ │ [이미지]│ │ [이미지]│ │ -│ │ 제목 │ │ 제목 │ │ 제목 │ │ -│ │ 설명 │ │ 설명 │ │ 설명 │ │ -│ └─────────┘ └─────────┘ └─────────┘ │ -└─────────────────────────────────────────────────┘ -``` - -**필수 컴포넌트**: -- `v2-table-search-widget` (1개) -- `v2-card-display` (1개) - -**설정 포인트**: -- `cardsPerRow`: 한 행당 카드 수 -- `columnMapping`: 제목, 부제목, 이미지, 상태 필드 매핑 -- `cardStyle`: 이미지 위치, 크기 - ---- - -### 3.5 패턴 E: 피벗 분석 - -**적용 화면**: 매출분석, 재고현황, 생산실적 분석 등 - -``` -┌─────────────────────────────────────────────────┐ -│ v2-pivot-grid │ -│ │ 2024년 │ 2025년 │ 2026년 │ 합계 │ -│ ─────────────────────────────────────────────── │ -│ 지역A │ 1,000 │ 1,200 │ 1,500 │ 3,700 │ -│ 지역B │ 2,000 │ 2,500 │ 3,000 │ 7,500 │ -│ 합계 │ 3,000 │ 3,700 │ 4,500 │ 11,200 │ -└─────────────────────────────────────────────────┘ -``` - -**필수 컴포넌트**: -- `v2-pivot-grid` (1개) - -**설정 포인트**: -- `fields[].area`: row / column / data / filter -- `fields[].summaryType`: sum / avg / count / min / max -- `fields[].groupInterval`: 날짜 그룹화 (year/quarter/month) - ---- - -## 4. 회사별 개발 시 핵심 체크포인트 - -### 4.1 테이블 설계 확인 - -**가장 먼저 확인**: -1. 회사에서 사용할 테이블 목록 -2. 테이블 간 관계 (FK) -3. 조회 조건으로 쓸 컬럼 - -``` -✅ 체크리스트: -□ 테이블명이 DB에 존재하는가? -□ company_code 컬럼이 있는가? (멀티테넌시) -□ 마스터-디테일 관계의 FK가 정의되어 있는가? -□ 검색 대상 컬럼에 인덱스가 있는가? -``` - -### 4.2 화면 패턴 판단 - -**질문을 통한 판단**: - -| 질문 | 예 → 패턴 | -|------|----------| -| 단일 테이블만 조회/편집? | 패턴 A (기본 마스터) | -| 마스터 선택 시 디테일 표시? | 패턴 B (마스터-디테일) | -| 상세에 탭이 필요? | 패턴 C (마스터-디테일+탭) | -| 이미지+정보 카드 형태? | 패턴 D (카드 뷰) | -| 다차원 집계/분석? | 패턴 E (피벗) | - -### 4.3 컴포넌트 설정 필수 항목 - -#### v2-table-list 필수 설정 - -```typescript +```json { - selectedTable: "테이블명", // 필수 - columns: [ // 표시할 컬럼 - { columnName: "id", displayName: "ID", visible: true, sortable: true }, - // ... - ], - pagination: { - enabled: true, - pageSize: 20, - showSizeSelector: true, - showPageInfo: true - }, - displayMode: "table", // "table" | "card" - checkbox: { - enabled: true, - multiple: true, - position: "left", - selectAll: true - }, - horizontalScroll: { // 가로 스크롤 설정 - enabled: true, - maxVisibleColumns: 8 - }, - linkedFilters: [], // 연결 필터 (다른 컴포넌트와 연동) - excludeFilter: {}, // 제외 필터 - autoLoad: true, // 자동 데이터 로드 - stickyHeader: false, // 헤더 고정 - autoWidth: true // 자동 너비 조정 + "columnName": "item_code", + "displayName": "품목코드", + "visible": true, + "sortable": true, + "width": 120, + "editable": false, + "inputType": "text", + "format": null, + "align": "left" } ``` -#### v2-split-panel-layout 필수 설정 +#### v2-table-grouped -```typescript +v2-table-list 기반 확장. 특정 컬럼 기준으로 데이터를 그룹화하여 접기/펼치기 기능 제공. + +| 설정 | 타입 | 설명 | 값 | +|------|------|------|----| +| selectedTable | string | **필수**. DB 테이블명 | - | +| groupConfig | object | **필수**. 그룹화 설정 | 아래 참조 | +| columns | array | 컬럼 설정 (v2-table-list와 동일) | - | +| showCheckbox | boolean | 체크박스 표시 | true/false | + +**groupConfig 구조**: + +```json { - leftPanel: { - displayMode: "table", // "list" | "table" | "custom" - tableName: "마스터_테이블명", - columns: [], // 컬럼 설정 - editButton: { // 수정 버튼 설정 - enabled: true, - mode: "auto", // "auto" | "modal" - modalScreenId: "" // 모달 모드 시 화면 ID - }, - addButton: { // 추가 버튼 설정 - enabled: true, - mode: "auto", - modalScreenId: "" - }, - deleteButton: { // 삭제 버튼 설정 - enabled: true, - buttonLabel: "삭제", - confirmMessage: "삭제하시겠습니까?" - }, - addModalColumns: [], // 추가 모달 전용 컬럼 - additionalTabs: [] // 추가 탭 설정 + "groupByColumn": "category", + "groupLabelFormat": "{category_name} ({category_code})", + "summary": { + "sumColumns": ["quantity", "amount"], + "showCount": true }, - rightPanel: { - displayMode: "table", - tableName: "디테일_테이블명", - relation: { - type: "detail", // "join" | "detail" | "custom" - foreignKey: "master_id", // 연결 키 - leftColumn: "", // 좌측 연결 컬럼 - rightColumn: "", // 우측 연결 컬럼 - keys: [] // 복합 키 - } - }, - splitRatio: 30, // 좌측 비율 (0-100) - resizable: true, // 리사이즈 가능 - minLeftWidth: 200, // 좌측 최소 너비 - minRightWidth: 300, // 우측 최소 너비 - syncSelection: true, // 선택 동기화 - autoLoad: true // 자동 로드 + "defaultExpanded": true, + "nestedGroup": null } ``` -#### v2-split-panel-layout 커스텀 모드 (NEW) +#### v2-table-search-widget -패널 내부에 자유롭게 컴포넌트를 배치할 수 있습니다. (v2-tabs-widget과 동일 구조) +테이블 상단에 배치하는 검색/필터 바. 대상 테이블의 컬럼을 자동 감지하여 검색 필드 생성. -```typescript +| 설정 | 타입 | 설명 | 값 | +|------|------|------|----| +| autoSelectFirstTable | boolean | 첫 번째 테이블 자동 선택 | true/false | +| showTableSelector | boolean | 테이블 선택기 표시 | true/false | +| title | string | 검색 바 제목 | - | + +#### v2-pivot-grid + +다차원 피벗 테이블. 행/열/데이터/필터 영역으로 데이터 분석. + +| 설정 | 타입 | 설명 | 값 | +|------|------|------|----| +| fields | array | **필수**. 필드 정의 | 아래 참조 | +| dataSource | object | 데이터 소스 | `{ type: "table"/"api"/"static", joinConfigs, filterConditions }` | + +**fields 배열 항목**: + +```json { - leftPanel: { - displayMode: "custom", // 커스텀 모드 활성화 - components: [ // 내부 컴포넌트 배열 - { - id: "btn-save", - componentType: "v2-button-primary", - label: "저장", - position: { x: 10, y: 10 }, - size: { width: 100, height: 40 }, - componentConfig: { buttonAction: "save" } - }, - { - id: "tbl-list", - componentType: "v2-table-list", - label: "목록", - position: { x: 10, y: 60 }, - size: { width: 400, height: 300 }, - componentConfig: { selectedTable: "테이블명" } - } - ] + "dataField": "region", + "area": "row", + "caption": "지역" +} +``` + +area 값: `"row"`, `"column"`, `"data"`, `"filter"` +summaryType (area=data일 때): `"sum"`, `"avg"`, `"count"`, `"min"`, `"max"`, `"countDistinct"` +groupInterval (날짜 필드): `"year"`, `"quarter"`, `"month"`, `"week"`, `"day"` + +#### v2-text-display + +라벨, 제목, 설명 텍스트 표시. + +| 설정 | 타입 | 설명 | +|------|------|------| +| fontSize | string | 폰트 크기 | +| fontWeight | string | 폰트 두께 | +| color | string | 텍스트 색상 | +| textAlign | string | 정렬 | + +#### v2-card-display + +테이블 데이터를 카드 형태로 표시. + +| 설정 | 타입 | 설명 | 값 | +|------|------|------|----| +| dataSource | string | 데이터 소스 | `"table"`, `"static"`, `"api"` | +| cardsPerRow | number | 행당 카드 수 | 1~6 | +| columnMapping | object | 필드 매핑 | `{ title, subtitle, description, image, status }` | +| cardStyle | object | 카드 스타일 | `{ imagePosition, imageSize }` | + +#### v2-aggregation-widget + +합계, 평균, 개수, 최대, 최소 등 집계 카드 표시. + +| 설정 | 타입 | 설명 | +|------|------|------| +| items | array | 집계 항목 배열 (테이블, 컬럼, 집계함수) | +| filters | array | 필터 조건 | +| layout | string | 레이아웃 (horizontal/vertical) | + +#### v2-status-count + +상태별 건수를 카드 형태로 표시. 대시보드/현황 화면에 적합. + +#### v2-numbering-rule + +자동 코드/번호 채번. 접두사 + 날짜 + 순번 조합. + +| 설정 | 타입 | 설명 | +|------|------|------| +| rule | string | 채번 규칙 | +| prefix | string | 접두사 | +| format | string | 포맷 | + +#### v2-category-manager + +트리 기반 카테고리 관리 (3단계 계층). 카테고리 생성/수정/삭제/이동. + +--- + +### 1.3 레이아웃 컴포넌트 + +#### v2-split-panel-layout + +**가장 복잡하고 중요한 컴포넌트**. 마스터-디테일 좌우 분할 레이아웃. + +| 설정 | 타입 | 설명 | 값 | +|------|------|------|----| +| splitRatio | number | 좌측 비율 (0-100) | 기본 30 | +| resizable | boolean | 리사이즈 가능 | true/false | +| minLeftWidth | number | 좌측 최소 너비 | 기본 200 | +| minRightWidth | number | 우측 최소 너비 | 기본 300 | +| syncSelection | boolean | 선택 동기화 | true/false | +| autoLoad | boolean | 자동 로드 | true/false | + +**leftPanel / rightPanel 구조**: + +```json +{ + "leftPanel": { + "displayMode": "table", + "tableName": "master_table", + "columns": [], + "showSearch": true, + "showAdd": true, + "showEdit": true, + "showDelete": true, + "addButton": { + "enabled": true, + "mode": "auto", + "modalScreenId": "" + }, + "editButton": { + "enabled": true, + "mode": "auto", + "modalScreenId": "" + }, + "deleteButton": { + "enabled": true, + "buttonLabel": "삭제", + "confirmMessage": "삭제하시겠습니까?" + }, + "addModalColumns": [], + "additionalTabs": [], + "dataFilter": {} }, - rightPanel: { - displayMode: "table" // 기존 모드 유지 + "rightPanel": { + "displayMode": "table", + "tableName": "detail_table", + "relation": { + "type": "detail", + "foreignKey": "master_id", + "leftColumn": "id", + "rightColumn": "master_id", + "keys": [] + }, + "addButton": { "enabled": true }, + "editButton": { "enabled": true }, + "deleteButton": { "enabled": true }, + "additionalTabs": [] } } ``` -**디자인 모드 기능**: -- 컴포넌트 클릭 → 좌측 설정 패널에서 속성 편집 -- 드래그 핸들(상단)로 이동 -- 리사이즈 핸들(모서리)로 크기 조절 -- 실제 컴포넌트 미리보기 렌더링 +**displayMode 종류**: +- `"list"`: 리스트 형태 (제목, 부제목) +- `"table"`: 테이블 형태 (컬럼, 행) +- `"custom"`: 자유 배치 (components 배열로 내부 컴포넌트 배치) -#### v2-card-display 필수 설정 +**relation.type 종류**: +- `"join"`: 두 테이블 JOIN +- `"detail"`: 마스터 PK → 디테일 FK 관계 +- `"custom"`: 커스텀 관계 (leftColumn, rightColumn 직접 지정) -```typescript -{ - dataSource: "table", - columnMapping: { - title: "name", // 제목 필드 - subtitle: "code", // 부제목 필드 - image: "image_url", // 이미지 필드 (선택) - status: "status" // 상태 필드 (선택) - }, - cardsPerRow: 3 -} -``` +**additionalTabs** (우측 패널에 멀티 탭 배치): ---- - -## 5. 공통 컴포넌트 한계점 - -### 5.1 현재 불가능한 기능 - -| 기능 | 상태 | 대안 | -|------|------|------| -| 트리 뷰 (BOM, 조직도) | ❌ 미지원 | 테이블로 대체 or 별도 개발 | -| 드래그앤드롭 정렬 | ❌ 미지원 | 순서 컬럼으로 대체 | -| 인라인 편집 | ⚠️ 제한적 | 모달 편집으로 대체 | -| 복잡한 차트 | ❌ 미지원 | 외부 라이브러리 연동 | - -> **v1.1 업데이트**: 그룹화 테이블(`v2-table-grouped`)과 타임라인 스케줄러(`v2-timeline-scheduler`)가 추가되어 해당 기능은 이제 지원됩니다. - -### 5.2 권장하지 않는 조합 - -| 조합 | 이유 | -|------|------| -| 3단계 이상 중첩 분할 | 화면 복잡도 증가, 성능 저하 | -| 탭 안에 탭 | 사용성 저하 | -| 한 화면에 3개 이상 테이블 | 데이터 로딩 성능 | -| 피벗 + 상세 테이블 동시 | 데이터 과부하 | - ---- - -## 6. 제어관리 (비즈니스 로직) - 별도 설정 필수 - -> **핵심**: V2 컴포넌트는 **UI만 담당**합니다. 비즈니스 로직은 **제어관리**에서 별도 설정해야 합니다. - -### 6.1 UI vs 제어 분리 구조 - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ 화면 구성 │ -├─────────────────────────────┬───────────────────────────────────┤ -│ UI 레이아웃 │ 제어관리 │ -│ (screen_layouts_v2) │ (dataflow_diagrams) │ -├─────────────────────────────┼───────────────────────────────────┤ -│ • 컴포넌트 배치 │ • 버튼 클릭 시 액션 │ -│ • 검색 필드 구성 │ • INSERT/UPDATE/DELETE 설정 │ -│ • 테이블 컬럼 표시 │ • 조건부 실행 │ -│ • 카드/탭 레이아웃 │ • 다중 행 처리 │ -│ │ • 테이블 간 데이터 이동 │ -└─────────────────────────────┴───────────────────────────────────┘ -``` - -### 6.2 HTML에서 파악 가능/불가능 - -| 구분 | HTML에서 파악 | 이유 | -|------|--------------|------| -| 컴포넌트 배치 | ✅ 가능 | HTML 구조에서 보임 | -| 검색 필드 | ✅ 가능 | input 태그로 확인 | -| 테이블 컬럼 | ✅ 가능 | thead에서 확인 | -| **저장 테이블** | ❌ 불가능 | JS/백엔드에서 처리 | -| **버튼 액션** | ❌ 불가능 | 제어관리에서 설정 | -| **전/후 처리** | ❌ 불가능 | 제어관리에서 설정 | -| **다중 행 처리** | ❌ 불가능 | 제어관리에서 설정 | -| **테이블 간 관계** | ❌ 불가능 | DB/제어관리에서 설정 | - -### 6.3 제어관리 설정 항목 - -#### 트리거 타입 -- **버튼 클릭 전 (before)**: 클릭 직전 실행 -- **버튼 클릭 후 (after)**: 클릭 완료 후 실행 - -#### 액션 타입 -- **INSERT**: 새로운 데이터 삽입 -- **UPDATE**: 기존 데이터 수정 -- **DELETE**: 데이터 삭제 - -#### 조건 설정 -```typescript -// 예: 선택된 행의 상태가 '대기'인 경우에만 실행 -{ - field: "status", - operator: "=", - value: "대기", - dataType: "string" -} -``` - -#### 필드 매핑 -```typescript -// 예: 소스 테이블의 값을 타겟 테이블로 이동 -{ - sourceTable: "order_master", - sourceField: "order_no", - targetTable: "order_history", - targetField: "order_no" -} -``` - -### 6.4 제어관리 예시: 수주 확정 버튼 - -**시나리오**: 수주 목록에서 3건 선택 후 [확정] 버튼 클릭 - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ [확정] 버튼 클릭 │ -├─────────────────────────────────────────────────────────────────┤ -│ 1. 조건 체크: status = '대기' 인 행만 │ -│ 2. UPDATE order_master SET status = '확정' WHERE id IN (선택) │ -│ 3. INSERT order_history (수주이력 테이블에 기록) │ -│ 4. 외부 시스템 호출 (ERP 연동) │ -└─────────────────────────────────────────────────────────────────┘ -``` - -**제어관리 설정**: ```json { - "triggerType": "after", - "actions": [ + "additionalTabs": [ { - "actionType": "update", - "targetTable": "order_master", - "conditions": [{ "field": "status", "operator": "=", "value": "대기" }], - "fieldMappings": [{ "targetField": "status", "defaultValue": "확정" }] + "id": "tab1", + "label": "기본정보", + "tableName": "detail_basic", + "relation": { "type": "detail", "foreignKey": "master_id" } }, { - "actionType": "insert", - "targetTable": "order_history", - "fieldMappings": [ - { "sourceField": "order_no", "targetField": "order_no" }, - { "sourceField": "customer_name", "targetField": "customer_name" } + "id": "tab2", + "label": "이력", + "tableName": "detail_history", + "relation": { "type": "detail", "foreignKey": "master_id" } + } + ] +} +``` + +#### v2-tabs-widget + +탭 전환 레이아웃. 탭 내부에 컴포넌트를 배치할 수 있음. + +| 설정 | 타입 | 설명 | 값 | +|------|------|------|----| +| tabs | array | **필수**. 탭 배열 | `[{ id, label, order, disabled, components }]` | +| defaultTab | string | 기본 활성 탭 ID | - | +| orientation | string | 탭 방향 | `"horizontal"`, `"vertical"` | +| persistSelection | boolean | 선택 유지 | true/false | + +**tabs 항목 구조**: + +```json +{ + "id": "tab-basic", + "label": "기본정보", + "order": 0, + "disabled": false, + "components": [ + { + "id": "tbl-basic", + "componentType": "v2-table-list", + "label": "기본정보", + "position": { "x": 0, "y": 0 }, + "size": { "width": 800, "height": 400 }, + "componentConfig": { "selectedTable": "basic_info" } + } + ] +} +``` + +#### v2-section-card + +제목+테두리가 있는 그룹화 컨테이너. 폼 필드를 묶을 때 사용. + +| 설정 | 타입 | 설명 | +|------|------|------| +| title | string | 섹션 제목 | +| collapsible | boolean | 접기/펼치기 가능 | +| padding | string | 내부 여백 | + +#### v2-section-paper + +배경색 기반 미니멀 그룹화 컨테이너. + +#### v2-divider-line + +영역 구분선 (가로/세로). + +#### v2-split-line + +캔버스 화면에서 좌우 영역을 분할하는 드래그 가능한 세로선. 디자이너 모드에서 사용. + +#### v2-repeat-container + +데이터 수만큼 내부 컴포넌트를 반복 렌더링. + +| 설정 | 타입 | 설명 | 값 | +|------|------|------|----| +| dataSourceType | string | 데이터 소스 | `"table"`, `"api"`, `"static"` | +| layout | string | 레이아웃 | `"vertical"`, `"horizontal"`, `"grid"` | +| gridColumns | number | 그리드 열 수 | 1~12 | + +#### v2-repeater + +인라인/모달/버튼 모드로 반복 데이터를 관리하는 컴포넌트. 주문 상세, 항목 리스트 등. + +| 설정 | 타입 | 설명 | 값 | +|------|------|------|----| +| mode | string | 동작 모드 | `"inline"` (인라인 편집), `"modal"` (모달 팝업), `"button"` (버튼 트리거) | + +--- + +### 1.4 특수/비즈니스 컴포넌트 + +#### v2-button-primary + +저장, 삭제, 조회 등 액션 버튼. 제어관리(dataflow)와 연결 가능. + +| 설정 | 타입 | 설명 | 값 | +|------|------|------|----| +| text | string | 버튼 텍스트 | 예: `"저장"`, `"삭제"` | +| actionType | string | 액션 유형 | `"save"`, `"delete"`, `"search"`, `"custom"` | +| variant | string | 스타일 | `"default"`, `"outline"`, `"destructive"`, `"ghost"` | + +**제어관리 연결** (webTypeConfig.dataflowConfig): + +```json +{ + "webTypeConfig": { + "enableDataflowControl": true, + "dataflowConfig": { + "controlMode": "relationship", + "relationshipConfig": { + "relationshipId": "rel_001", + "relationshipName": "수주확정", + "executionTiming": "after" + } + } + } +} +``` + +executionTiming 값: +- `"before"`: 메인 액션 전에 제어 실행 (조건 체크용) +- `"after"`: 메인 액션 후에 제어 실행 (후처리용) +- `"replace"`: 메인 액션 대신 제어만 실행 + +#### v2-timeline-scheduler + +간트차트형 일정/계획 시각화. 드래그/리사이즈로 일정 변경 가능. + +| 설정 | 타입 | 설명 | 값 | +|------|------|------|----| +| selectedTable | string | 스케줄 테이블명 | - | +| resourceTable | string | 리소스 테이블명 (설비/작업자) | - | +| fieldMapping | object | 필드 매핑 | `{ id, resourceId, title, startDate, endDate, status, progress }` | +| resourceFieldMapping | object | 리소스 필드 매핑 | `{ id, name, group }` | +| defaultZoomLevel | string | 초기 줌 | `"day"`, `"week"`, `"month"` | +| editable | boolean | 편집 가능 | true/false | +| allowDrag | boolean | 드래그 이동 허용 | true/false | +| allowResize | boolean | 리사이즈 허용 | true/false | + +#### v2-approval-step + +결재 단계 스테퍼 시각화. 결재 프로세스의 진행 상태를 단계별로 표시. + +#### v2-bom-tree + +BOM(Bill of Materials) 계층 트리 표시. 정전개(상위→하위)/역전개(하위→상위) 전환 가능. + +#### v2-bom-item-editor + +BOM 하위품목 트리 편집. 하위 부품 추가/수정/삭제/수량 변경 등. + +--- + +## 2. 화면 패턴별 상세 구현 가이드 + +### 2.1 패턴 A: 기본 마스터 (검색 + 테이블) + +**적용 조건**: 단일 테이블 CRUD. 마스터-디테일 관계 없음. + +**layout_data 예시**: + +```json +{ + "version": "2.0", + "components": [ + { + "id": "search_widget", + "url": "@/lib/registry/components/v2-table-search-widget", + "position": { "x": 0, "y": 0, "z": 1 }, + "size": { "width": 1920, "height": 100 }, + "displayOrder": 0, + "overrides": { + "label": "검색", + "autoSelectFirstTable": true + } + }, + { + "id": "table_list", + "url": "@/lib/registry/components/v2-table-list", + "position": { "x": 0, "y": 120, "z": 1 }, + "size": { "width": 1920, "height": 700 }, + "displayOrder": 1, + "overrides": { + "label": "품목 목록", + "tableName": "item_info", + "columns": [ + { "columnName": "item_code", "displayName": "품목코드", "visible": true, "sortable": true, "width": 120 }, + { "columnName": "item_name", "displayName": "품목명", "visible": true, "sortable": true, "width": 200 }, + { "columnName": "item_type", "displayName": "품목유형", "visible": true, "width": 100 }, + { "columnName": "unit", "displayName": "단위", "visible": true, "width": 80 } + ], + "pagination": { "enabled": true, "pageSize": 20 }, + "checkbox": { "enabled": true, "multiple": true } + } + } + ], + "gridSettings": { "columns": 12, "gap": 16, "padding": 16 }, + "screenResolution": { "width": 1920, "height": 1080 } +} +``` + +### 2.2 패턴 B: 마스터-디테일 (좌우 분할) + +**적용 조건**: 마스터 테이블 선택 시 연관 디테일 테이블 표시. 두 테이블 간 FK 관계 존재. + +**layout_data 예시**: + +```json +{ + "version": "2.0", + "components": [ + { + "id": "split_panel", + "url": "@/lib/registry/components/v2-split-panel-layout", + "position": { "x": 0, "y": 0, "z": 1 }, + "size": { "width": 1920, "height": 800 }, + "displayOrder": 0, + "overrides": { + "label": "수주관리", + "splitRatio": 35, + "resizable": true, + "leftPanel": { + "displayMode": "table", + "tableName": "order_master", + "columns": [ + { "columnName": "order_no", "displayName": "수주번호", "visible": true, "width": 120 }, + { "columnName": "customer_name", "displayName": "거래처", "visible": true, "width": 150 }, + { "columnName": "order_date", "displayName": "수주일자", "visible": true, "width": 100 }, + { "columnName": "status", "displayName": "상태", "visible": true, "width": 80 } + ], + "showSearch": true, + "showAdd": true, + "showDelete": true, + "addButton": { "enabled": true, "mode": "auto" }, + "deleteButton": { "enabled": true, "confirmMessage": "삭제하시겠습니까?" } + }, + "rightPanel": { + "displayMode": "table", + "tableName": "order_detail", + "relation": { + "type": "detail", + "foreignKey": "order_master_id", + "leftColumn": "id", + "rightColumn": "order_master_id" + }, + "columns": [ + { "columnName": "item_code", "displayName": "품목코드", "visible": true, "width": 120 }, + { "columnName": "item_name", "displayName": "품목명", "visible": true, "width": 150 }, + { "columnName": "quantity", "displayName": "수량", "visible": true, "width": 80, "editable": true }, + { "columnName": "unit_price", "displayName": "단가", "visible": true, "width": 100, "editable": true } + ], + "addButton": { "enabled": true }, + "deleteButton": { "enabled": true } + } + } + } + ], + "gridSettings": { "columns": 12, "gap": 16, "padding": 16 }, + "screenResolution": { "width": 1920, "height": 1080 } +} +``` + +### 2.3 패턴 C: 마스터-디테일 + 탭 + +**적용 조건**: 마스터-디테일에서 우측에 여러 종류의 상세 정보를 탭으로 분리. + +**차이점**: rightPanel에 `additionalTabs` 배열 추가. + +```json +{ + "rightPanel": { + "displayMode": "table", + "tableName": "customer_basic", + "relation": { + "type": "detail", + "foreignKey": "customer_id" + }, + "additionalTabs": [ + { + "id": "tab-basic", + "label": "기본정보", + "tableName": "customer_basic", + "relation": { "type": "detail", "foreignKey": "customer_id" } + }, + { + "id": "tab-history", + "label": "거래이력", + "tableName": "customer_history", + "relation": { "type": "detail", "foreignKey": "customer_id" } + }, + { + "id": "tab-files", + "label": "첨부파일", + "tableName": "customer_files", + "relation": { "type": "detail", "foreignKey": "customer_id" } + } + ] + } +} +``` + +--- + +## 3. 제어관리 (비즈니스 로직) 설정 + +### 3.1 개요 + +V2 컴포넌트는 **UI만 담당**한다. 버튼 클릭 시 실행되는 비즈니스 로직(INSERT/UPDATE/DELETE, 조건 체크, 외부 호출 등)은 `dataflow_diagrams` 테이블에서 별도 설정한다. + +### 3.2 설정 위치 + +| 구분 | 저장 위치 | 용도 | +|------|----------|------| +| 버튼-제어 연결 | `screen_layouts_v2` → 컴포넌트 `webTypeConfig.dataflowConfig` | 어떤 버튼이 어떤 제어를 실행하는지 | +| 제어 정의 | `dataflow_diagrams` → `control`, `plan` JSONB | 조건, 액션, 필드 매핑 | +| 노드 플로우 | `node_flows` → `flow_data` JSONB | 복잡한 다단계 플로우 | + +### 3.3 실행 흐름 + +``` +[버튼 클릭] v2-button-primary + │ + ▼ +[설정 확인] webTypeConfig.dataflowConfig + │ + ├─ controlMode: "relationship" → dataflow_diagrams 실행 + ├─ controlMode: "flow" → node_flows 실행 + └─ controlMode: "none" → 기본 액션만 실행 + │ + ▼ +[실행기] ImprovedButtonActionExecutor + │ + ├─ 1. Before 제어 (executionTiming = "before") + │ └─ 조건 체크 → 실패 시 중단 + │ + ├─ 2. Main 액션 (executionTiming ≠ "replace"일 때) + │ └─ save / delete / update 실행 + │ + └─ 3. After 제어 (executionTiming = "after") + └─ 후처리 (이력 저장, 상태 변경, 외부 호출 등) +``` + +### 3.4 dataflow_diagrams 설정 예시 + +**시나리오**: 수주 목록에서 [확정] 버튼 클릭 → 상태를 '확정'으로 변경 + 이력 테이블에 기록 + +```json +{ + "control": [ + { + "conditions": [ + { "field": "status", "operator": "=", "value": "대기", "dataType": "string" } + ], + "triggerType": "update" + } + ], + "plan": [ + { + "actions": [ + { + "actionType": "update", + "targetTable": "order_master", + "conditions": [ + { "field": "status", "operator": "=", "value": "대기" } + ], + "fieldMappings": [ + { "targetField": "status", "defaultValue": "확정" } + ] + }, + { + "actionType": "insert", + "targetTable": "order_history", + "fieldMappings": [ + { "sourceField": "order_no", "targetField": "order_no" }, + { "sourceField": "customer_name", "targetField": "customer_name" }, + { "targetField": "action", "defaultValue": "확정" } + ] + } ] } ] } ``` -### 6.5 회사별 개발 시 제어관리 체크리스트 +--- + +## 4. 화면 개발 체크리스트 (AI 에이전트용) + +화면 개발 요청을 받았을 때 이 순서대로 확인한다. + +### Step 1: 요구사항 분석 + +``` +□ 어떤 테이블을 사용하는가? (메인 테이블, 디테일 테이블) +□ 테이블 간 관계는? (FK, 1:N, M:N) +□ 어떤 패턴인가? (A~G 중 선택, 의사결정 트리 참조) +□ 어떤 버튼이 필요한가? (저장, 삭제, 확정 등) +□ 비즈니스 로직이 있는가? (상태 변경, 이력 기록, 외부 호출 등) +``` + +### Step 2: DB 테이블 생성 + +``` +□ 모든 컬럼 VARCHAR(500) +□ 기본 5개 컬럼 포함 (id, created_date, updated_date, writer, company_code) +□ table_labels, table_type_columns, column_labels 메타데이터 등록 +□ company_code 인덱스 생성 +``` + +### Step 3: screen_definitions INSERT + +```sql +INSERT INTO screen_definitions (screen_name, screen_code, table_name, company_code, is_active) +VALUES ('화면명', '{company_code}_순번', '메인_테이블', '{company_code}', 'Y'); +``` + +### Step 4: screen_layouts_v2 INSERT + +```sql +INSERT INTO screen_layouts_v2 (screen_id, company_code, layer_id, layout_data) +VALUES ({screen_id}, '{company_code}', 1, '{패턴별 JSON}'); +``` + +### Step 5: dataflow_diagrams INSERT (비즈니스 로직 필요 시) ``` □ 버튼별 액션 정의 - - 어떤 버튼이 있는가? - - 각 버튼 클릭 시 무슨 동작? +□ 조건 설정 (어떤 상태일 때만 실행) +□ 필드 매핑 (소스→타겟) +□ 실행 타이밍 (before/after/replace) +``` -□ 저장/수정/삭제 대상 테이블 - - 메인 테이블은? - - 이력 테이블은? - - 연관 테이블은? +### Step 6: menu_info INSERT -□ 조건부 실행 - - 특정 상태일 때만 실행? - - 특정 값 체크 필요? +```sql +INSERT INTO menu_info (screen_id, menu_url, company_code, ...) +VALUES ({screen_id}, '/screen/{screen_code}', '{company_code}', ...); +``` -□ 다중 행 처리 - - 여러 행 선택 후 일괄 처리? - - 각 행별 개별 처리? +### Step 7: 멀티테넌시 검증 -□ 외부 연동 - - ERP/MES 등 외부 시스템 호출? - - API 연동 필요? +``` +□ 테이블에 company_code 컬럼 존재 +□ 모든 SELECT에 company_code 필터링 +□ INSERT에 company_code 포함 +□ UPDATE/DELETE WHERE절에 company_code 포함 +□ JOIN에 company_code 매칭 ``` --- -## 7. 회사별 커스터마이징 영역 +## 5. 자주 묻는 질문 (FAQ) -### 7.1 컴포넌트로 처리되는 영역 (표준화) +### Q: v2-table-list와 v2-table-grouped 중 어떤 걸 써야 하나? -| 영역 | 설명 | -|------|------| -| UI 레이아웃 | 컴포넌트 배치, 크기, 위치 | -| 검색 조건 | 화면 디자이너에서 설정 | -| 테이블 컬럼 | 표시/숨김, 순서, 너비 | -| 기본 CRUD | 조회, 저장, 삭제 자동 처리 | -| 페이지네이션 | 자동 처리 | -| 정렬/필터 | 자동 처리 | +**A**: 데이터를 특정 컬럼 기준으로 묶어서 접기/펼치기가 필요하면 `v2-table-grouped`, 아니면 `v2-table-list`. 예를 들어 "카테고리별 품목 목록"은 `v2-table-grouped`, "품목 전체 목록"은 `v2-table-list`. -### 7.2 회사별 개발 필요 영역 +### Q: 마스터-디테일에서 탭을 사용하고 싶은데? -| 영역 | 설명 | 개발 방법 | -|------|------|----------| -| 비즈니스 로직 | 저장 전/후 검증, 계산 | 데이터플로우 또는 백엔드 API | -| 특수 UI | 간트, 트리, 차트 등 | 별도 컴포넌트 개발 | -| 외부 연동 | ERP, MES 등 연계 | 외부 호출 설정 | -| 리포트/인쇄 | 전표, 라벨 출력 | 리포트 컴포넌트 | -| 결재 프로세스 | 승인/반려 흐름 | 워크플로우 설정 | +**A**: `v2-split-panel-layout`의 rightPanel에 `additionalTabs` 배열을 설정한다. 별도의 `v2-tabs-widget`을 배치할 필요 없다. + +### Q: BOM 화면은 어떻게 만드나? + +**A**: `v2-split-panel-layout`의 좌측에 `v2-bom-tree` (또는 `v2-table-list`), 우측에 `v2-bom-item-editor`를 배치한다. 트리 컴포넌트가 자체적으로 정전개/역전개를 지원한다. + +### Q: 버튼 클릭 시 특정 로직을 실행하고 싶은데? + +**A**: `v2-button-primary`의 `webTypeConfig.dataflowConfig`에 제어관리를 연결한다. `dataflow_diagrams` 테이블에 실행할 액션(INSERT/UPDATE/DELETE)과 조건을 정의한다. + +### Q: 검색 기능은 어떻게 추가하나? + +**A**: `v2-table-search-widget`을 `v2-table-list` 위에 배치한다. `autoSelectFirstTable: true`로 설정하면 자동으로 화면의 첫 번째 테이블과 연동된다. + +### Q: 파일 업로드는? + +**A**: `v2-file-upload` 컴포넌트를 사용한다. 폼 내 배치하여 파일/이미지 업로드를 처리한다. + +### Q: 결재 프로세스를 표시하고 싶은데? + +**A**: `v2-approval-step` 컴포넌트를 사용한다. 결재 단계를 스테퍼 형태로 시각화한다. + +### Q: 인라인 편집이 가능한가? + +**A**: `v2-table-list`의 columns에서 `editable: true`로 설정하면 해당 컬럼의 셀을 직접 편집할 수 있다. 단, 복잡한 편집은 모달 편집(`v2-split-panel-layout`의 editButton.mode = "modal")을 권장한다. + +### Q: 화면에 집계(합계, 평균 등)를 표시하고 싶은데? + +**A**: `v2-aggregation-widget`을 사용하여 테이블 데이터의 합계, 평균, 개수 등을 카드 형태로 표시한다. 또는 `v2-status-count`로 상태별 건수를 표시한다. --- -## 8. 빠른 개발 가이드 +## 6. 컴포넌트 선택 빠른 참조표 -### Step 1: 화면 분석 -1. 어떤 테이블을 사용하는가? -2. 테이블 간 관계는? -3. 어떤 패턴인가? (A/B/C/D/E) - -### Step 2: 컴포넌트 배치 -1. 화면 디자이너에서 패턴에 맞는 컴포넌트 배치 -2. 각 컴포넌트에 테이블/컬럼 설정 - -### Step 3: 연동 설정 -1. 마스터-디테일 관계 설정 (FK) -2. 검색 조건 설정 -3. 버튼 액션 설정 - -### Step 4: 테스트 -1. 데이터 조회 확인 -2. 마스터 선택 시 디테일 연동 확인 -3. 저장/삭제 동작 확인 - ---- - -## 9. 요약 - -### V2 컴포넌트 커버리지 - -| 화면 유형 | 지원 여부 | 주요 컴포넌트 | -|-----------|----------|--------------| -| 기본 CRUD | ✅ 완전 | v2-table-list, v2-table-search-widget | -| 마스터-디테일 | ✅ 완전 | v2-split-panel-layout | -| 탭 화면 | ✅ 완전 | v2-tabs-widget | -| 카드 뷰 | ✅ 완전 | v2-card-display | -| 피벗 분석 | ✅ 완전 | v2-pivot-grid | -| 그룹화 테이블 | ✅ 지원 | v2-table-grouped | -| 타임라인/스케줄 | ✅ 지원 | v2-timeline-scheduler | -| 파일 업로드 | ✅ 지원 | v2-file-upload | -| 트리 뷰 | ❌ 미지원 | 개발 필요 | - -### 개발 시 핵심 원칙 - -1. **테이블 먼저**: DB 테이블 구조 확인이 최우선 -2. **패턴 판단**: 5가지 패턴 중 어디에 해당하는지 판단 -3. **표준 조합**: 검증된 컴포넌트 조합 사용 -4. **한계 인식**: 불가능한 UI는 조기에 식별하여 별도 개발 계획 -5. **멀티테넌시**: 모든 테이블에 company_code 필터링 필수 -6. **제어관리 필수**: UI 완성 후 버튼별 비즈니스 로직 설정 필수 - -### UI vs 제어 구분 - -| 영역 | 담당 | 설정 위치 | -|------|------|----------| -| 화면 레이아웃 | V2 컴포넌트 | 화면 디자이너 | -| 비즈니스 로직 | 제어관리 | dataflow_diagrams | -| 외부 연동 | 외부호출 설정 | external_call_configs | - -**HTML에서 배낄 수 있는 것**: UI 구조만 -**별도 설정 필요한 것**: 저장 테이블, 버튼 액션, 조건 처리, 다중 행 처리 +| 요구사항 | 컴포넌트 | +|----------|----------| +| 데이터 목록 보기 | `v2-table-list` | +| 데이터 검색/필터 | `v2-table-search-widget` | +| 좌우 분할 (마스터-디테일) | `v2-split-panel-layout` | +| 그룹별 묶기 (접기/펼치기) | `v2-table-grouped` | +| 탭 전환 | `v2-tabs-widget` 또는 `v2-split-panel-layout`의 additionalTabs | +| 카드 형태 보기 | `v2-card-display` | +| 피벗/집계 분석 | `v2-pivot-grid` | +| 간트차트/일정 | `v2-timeline-scheduler` | +| BOM 트리 | `v2-bom-tree` + `v2-bom-item-editor` | +| 텍스트 입력 | `v2-input` | +| 선택 (드롭다운 등) | `v2-select` | +| 날짜 입력 | `v2-date` | +| 파일 업로드 | `v2-file-upload` | +| 액션 버튼 | `v2-button-primary` | +| 자동 채번 | `v2-numbering-rule` | +| 카테고리 관리 | `v2-category-manager` | +| 결재 단계 표시 | `v2-approval-step` | +| 상태별 건수 | `v2-status-count` | +| 합계/평균 카드 | `v2-aggregation-widget` | +| 구분선 | `v2-divider-line` | +| 그룹 컨테이너 | `v2-section-card` 또는 `v2-section-paper` | +| 데이터 반복 | `v2-repeat-container` 또는 `v2-repeater` | +| 공정 작업기준 | `v2-process-work-standard` | +| 품목 라우팅 | `v2-item-routing` | +| 미디어 표시 | `v2-media` | +| 랙 구조 | `v2-rack-structure` | +| 위치 교환 | `v2-location-swap-selector` | diff --git a/frontend/components/v2/V2Input.tsx b/frontend/components/v2/V2Input.tsx index 2d7c3246..c8204faf 100644 --- a/frontend/components/v2/V2Input.tsx +++ b/frontend/components/v2/V2Input.tsx @@ -18,6 +18,7 @@ import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } import { Input } from "@/components/ui/input"; import { Slider } from "@/components/ui/slider"; import { Label } from "@/components/ui/label"; +import { formatNumber as centralFormatNumber } from "@/lib/formatting"; import { cn } from "@/lib/utils"; import { V2InputProps, V2InputConfig, V2InputFormat } from "@/types/v2-components"; import { AutoGenerationUtils } from "@/lib/utils/autoGeneration"; @@ -61,11 +62,11 @@ export function validateInputFormat(value: string, format: V2InputFormat): { isV return { isValid, errorMessage: isValid ? "" : formatConfig.errorMessage }; } -// 통화 형식 변환 +// 통화 형식 변환 (공통 formatNumber 사용) function formatCurrency(value: string | number): string { const num = typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : value; if (isNaN(num)) return ""; - return num.toLocaleString("ko-KR"); + return centralFormatNumber(num); } // 사업자번호 형식 변환 @@ -234,7 +235,22 @@ const TextInput = forwardRef< TextInput.displayName = "TextInput"; /** - * 숫자 입력 컴포넌트 + * 숫자를 콤마 포맷 문자열로 변환 (입력 중 실시간 표시용) + * 소수점 입력 중인 경우(끝이 "."이거나 ".0" 등)를 보존 + */ +function toCommaDisplay(raw: string): string { + if (raw === "" || raw === "-") return raw; + const negative = raw.startsWith("-"); + const abs = negative ? raw.slice(1) : raw; + const dotIdx = abs.indexOf("."); + const intPart = dotIdx >= 0 ? abs.slice(0, dotIdx) : abs; + const decPart = dotIdx >= 0 ? abs.slice(dotIdx) : ""; + const formatted = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ","); + return (negative ? "-" : "") + formatted + decPart; +} + +/** + * 숫자 입력 컴포넌트 - 입력 중에도 실시간 천단위 콤마 표시 */ const NumberInput = forwardRef< HTMLInputElement, @@ -250,40 +266,112 @@ const NumberInput = forwardRef< className?: string; inputStyle?: React.CSSProperties; } ->(({ value, onChange, min, max, step = 1, placeholder, readonly, disabled, className, inputStyle }, ref) => { +>(({ value, onChange, min, max, placeholder, readonly, disabled, className, inputStyle }, ref) => { + const innerRef = useRef(null); + const combinedRef = (node: HTMLInputElement | null) => { + (innerRef as React.MutableRefObject).current = node; + if (typeof ref === "function") ref(node); + else if (ref) (ref as React.MutableRefObject).current = node; + }; + + // 콤마 포함된 표시 문자열을 내부 상태로 관리 + const [displayValue, setDisplayValue] = useState(() => { + if (value === undefined || value === null) return ""; + return centralFormatNumber(value); + }); + + // 외부 value가 변경되면 표시 값 동기화 (포커스 아닐 때만) + const isFocusedRef = useRef(false); + useEffect(() => { + if (isFocusedRef.current) return; + if (value === undefined || value === null) { + setDisplayValue(""); + } else { + setDisplayValue(centralFormatNumber(value)); + } + }, [value]); + const handleChange = useCallback( (e: React.ChangeEvent) => { - const val = e.target.value; - if (val === "") { + const input = e.target; + const cursorPos = input.selectionStart ?? 0; + const oldVal = displayValue; + const rawInput = e.target.value; + + // 콤마 제거하여 순수 숫자 문자열 추출 + const stripped = rawInput.replace(/,/g, ""); + + // 빈 값 처리 + if (stripped === "" || stripped === "-") { + setDisplayValue(stripped); onChange?.(undefined); return; } - let num = parseFloat(val); + // 숫자 + 소수점만 허용 (입력 중 "123." 같은 중간 상태도 허용) + if (!/^-?\d*\.?\d*$/.test(stripped)) return; + + // 새 콤마 포맷 생성 + const newDisplay = toCommaDisplay(stripped); + setDisplayValue(newDisplay); + + // 콤마 개수 차이로 커서 위치 보정 + const oldCommas = (oldVal.slice(0, cursorPos).match(/,/g) || []).length; + const newCommas = (newDisplay.slice(0, cursorPos).match(/,/g) || []).length; + const adjustedCursor = cursorPos + (newCommas - oldCommas); + + requestAnimationFrame(() => { + if (innerRef.current) { + innerRef.current.setSelectionRange(adjustedCursor, adjustedCursor); + } + }); + + // 실제 숫자 값 전달 (소수점 입력 중이면 아직 전달하지 않음) + if (stripped.endsWith(".") || stripped.endsWith("-")) return; + let num = parseFloat(stripped); + if (isNaN(num)) return; - // 범위 제한 if (min !== undefined && num < min) num = min; if (max !== undefined && num > max) num = max; onChange?.(num); }, - [min, max, onChange], + [min, max, onChange, displayValue], ); + const handleFocus = useCallback(() => { + isFocusedRef.current = true; + }, []); + + const handleBlur = useCallback(() => { + isFocusedRef.current = false; + // 블러 시 최종 포맷 정리 + const stripped = displayValue.replace(/,/g, ""); + if (stripped === "" || stripped === "-" || stripped === ".") { + setDisplayValue(""); + onChange?.(undefined); + return; + } + const num = parseFloat(stripped); + if (!isNaN(num)) { + setDisplayValue(centralFormatNumber(num)); + } + }, [displayValue, onChange]); + return ( ); }); diff --git a/frontend/lib/formatting/index.ts b/frontend/lib/formatting/index.ts index 53dff08d..573b3af1 100644 --- a/frontend/lib/formatting/index.ts +++ b/frontend/lib/formatting/index.ts @@ -63,10 +63,23 @@ function applyDateFormat(date: Date, pattern: string): string { // --- 숫자 포맷 --- +/** 최대 허용 소수점 자릿수 */ +const MAX_DECIMAL_PLACES = 5; + /** - * 숫자를 로케일 기반으로 포맷한다 (천단위 구분자 등). + * 실제 값에서 소수점 자릿수를 감지한다 (최대 MAX_DECIMAL_PLACES). + */ +function detectDecimals(num: number): number { + if (Number.isInteger(num)) return 0; + const parts = String(num).split("."); + if (parts.length < 2) return 0; + return Math.min(parts[1].length, MAX_DECIMAL_PLACES); +} + +/** + * 숫자를 로케일 기반으로 포맷한다 (천단위 구분자 + 소수점 자동감지). * @param value - 숫자 또는 숫자 문자열 - * @param decimals - 소수점 자릿수 (미지정 시 기본값 사용) + * @param decimals - 소수점 자릿수 (미지정 시 실제 값에서 자동감지, 최대 5자리) * @returns 포맷된 문자열 */ export function formatNumber(value: unknown, decimals?: number): string { @@ -76,7 +89,7 @@ export function formatNumber(value: unknown, decimals?: number): string { const num = typeof value === "number" ? value : parseFloat(String(value)); if (isNaN(num)) return String(value); - const dec = decimals ?? rules.number.decimals; + const dec = decimals ?? detectDecimals(num); return new Intl.NumberFormat(rules.number.locale, { minimumFractionDigits: dec, diff --git a/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx b/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx index 782c3ea9..85db9002 100644 --- a/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx +++ b/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx @@ -9,6 +9,7 @@ import { Checkbox } from "@/components/ui/checkbox"; import { RepeaterColumnConfig } from "./types"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; +import { formatNumber as centralFormatNumber } from "@/lib/formatting"; // @dnd-kit imports import { @@ -594,21 +595,10 @@ export function RepeaterTable({ // 계산 필드는 편집 불가 if (column.calculated || !column.editable) { - // 숫자 포맷팅 함수: 정수/소수점 자동 구분 - const formatNumber = (val: any): string => { - if (val === undefined || val === null || val === "") return "0"; - const num = typeof val === "number" ? val : parseFloat(val); - if (isNaN(num)) return "0"; - // 정수면 소수점 없이, 소수면 소수점 유지 - if (Number.isInteger(num)) { - return num.toLocaleString("ko-KR"); - } else { - return num.toLocaleString("ko-KR"); - } - }; - // 🆕 카테고리 타입이면 라벨로 변환하여 표시 - const displayValue = column.type === "number" ? formatNumber(value) : getCategoryDisplayValue(value); + const displayValue = column.type === "number" + ? (value === undefined || value === null || value === "" ? "0" : centralFormatNumber(value) || "0") + : getCategoryDisplayValue(value); // 🆕 40자 초과 시 ... 처리 및 툴팁 const { truncated, isTruncated } = truncateText(String(displayValue)); @@ -623,24 +613,28 @@ export function RepeaterTable({ // 편집 가능한 필드 switch (column.type) { case "number": - // 숫자 표시: 정수/소수점 자동 구분 + // 콤마 포함 숫자 표시 const displayValue = (() => { if (value === undefined || value === null || value === "") return ""; - const num = typeof value === "number" ? value : parseFloat(value); + const num = typeof value === "number" ? value : parseFloat(String(value)); if (isNaN(num)) return ""; - return num.toString(); + return centralFormatNumber(num); })(); return ( { - const val = e.target.value; - // 숫자와 소수점만 허용 - if (val === "" || /^-?\d*\.?\d*$/.test(val)) { - handleCellEdit(rowIndex, column.field, val === "" ? 0 : parseFloat(val) || 0); + const stripped = e.target.value.replace(/,/g, ""); + if (stripped === "" || stripped === "-") { + handleCellEdit(rowIndex, column.field, 0); + return; + } + if (!/^-?\d*\.?\d*$/.test(stripped)) return; + if (!stripped.endsWith(".")) { + handleCellEdit(rowIndex, column.field, parseFloat(stripped) || 0); } }} className="border-border focus:border-primary focus:ring-ring h-8 w-full min-w-0 rounded-none text-right text-xs focus:ring-1" diff --git a/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx b/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx index 6f4aa411..dba11885 100644 --- a/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx +++ b/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx @@ -13,6 +13,7 @@ import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { formatNumber as centralFormatNumber } from "@/lib/formatting"; import { X, Check, @@ -1286,7 +1287,7 @@ export const SelectedItemsDetailInputComponent: React.FC { const decimals = field.decimals ?? 0; - const formatted = value.toFixed(decimals); switch (field.format) { case "currency": - return Number(formatted).toLocaleString() + "원"; + return centralFormatNumber(value, decimals) + "원"; case "percent": - return formatted + "%"; + return value.toFixed(decimals) + "%"; default: - return Number(formatted).toLocaleString(); + return centralFormatNumber(value, decimals); } }; @@ -554,9 +554,9 @@ export function SimpleRepeaterTableComponent({ return (
{column.type === "number" - ? typeof cellValue === "number" - ? cellValue.toLocaleString() - : cellValue || "0" + ? (cellValue !== null && cellValue !== undefined && cellValue !== "" + ? centralFormatNumber(cellValue) + : "0") : cellValue || "-"}
); @@ -565,12 +565,28 @@ export function SimpleRepeaterTableComponent({ // 편집 가능한 필드 switch (column.type) { case "number": + const numDisplay = (() => { + if (cellValue === undefined || cellValue === null || cellValue === "") return ""; + const n = typeof cellValue === "number" ? cellValue : parseFloat(String(cellValue)); + return isNaN(n) ? "" : centralFormatNumber(n); + })(); return ( handleCellEdit(rowIndex, column.field, parseFloat(e.target.value) || 0)} - className="h-7 text-xs" + type="text" + inputMode="decimal" + value={numDisplay} + onChange={(e) => { + const stripped = e.target.value.replace(/,/g, ""); + if (stripped === "" || stripped === "-") { + handleCellEdit(rowIndex, column.field, 0); + return; + } + if (!/^-?\d*\.?\d*$/.test(stripped)) return; + if (!stripped.endsWith(".")) { + handleCellEdit(rowIndex, column.field, parseFloat(stripped) || 0); + } + }} + className="h-7 text-right text-xs" /> ); diff --git a/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutComponent.tsx index 9ed4c81f..0f54b4f2 100644 --- a/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutComponent.tsx @@ -24,6 +24,7 @@ import { } from "lucide-react"; import { dataApi } from "@/lib/api/data"; import { entityJoinApi } from "@/lib/api/entityJoin"; +import { formatNumber as centralFormatNumber } from "@/lib/formatting"; import { useToast } from "@/hooks/use-toast"; import { tableTypeApi } from "@/lib/api/screen"; import { apiClient, getFullImageUrl } from "@/lib/api/client"; @@ -1006,19 +1007,20 @@ export const SplitPanelLayoutComponent: React.FC .replace("ss", String(date.getSeconds()).padStart(2, "0")); }, []); - // 숫자 포맷팅 헬퍼 함수 + // 숫자 포맷팅 헬퍼 함수 (공통 formatNumber 기반) const formatNumberValue = useCallback((value: any, format: any): string => { if (value === null || value === undefined || value === "") return "-"; const num = typeof value === "number" ? value : parseFloat(String(value)); if (isNaN(num)) return String(value); - const options: Intl.NumberFormatOptions = { - minimumFractionDigits: format?.decimalPlaces ?? 0, - maximumFractionDigits: format?.decimalPlaces ?? 10, - useGrouping: format?.thousandSeparator ?? false, - }; + let result: string; + if (format?.thousandSeparator === false) { + const dec = format?.decimalPlaces ?? 0; + result = num.toFixed(dec); + } else { + result = centralFormatNumber(num, format?.decimalPlaces); + } - let result = num.toLocaleString("ko-KR", options); if (format?.prefix) result = format.prefix + result; if (format?.suffix) result = result + format.suffix; return result; @@ -1088,14 +1090,16 @@ export const SplitPanelLayoutComponent: React.FC return formatDateValue(value, format?.dateFormat || "YYYY-MM-DD"); } - // 🆕 숫자 포맷 적용 + // 숫자 포맷 적용 (format 설정이 있거나 input_type이 number/decimal이면 자동 적용) + const isNumericByInputType = colInputType === "number" || colInputType === "decimal"; if ( format?.type === "number" || format?.type === "currency" || format?.thousandSeparator || - format?.decimalPlaces !== undefined + format?.decimalPlaces !== undefined || + isNumericByInputType ) { - return formatNumberValue(value, format); + return formatNumberValue(value, format || { thousandSeparator: true }); } // 카테고리 매핑 찾기 (여러 키 형태 시도) diff --git a/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx b/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx index 6b8c674c..65833fa9 100644 --- a/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx @@ -13,6 +13,7 @@ import { Button } from "@/components/ui/button"; import { v2EventBus, V2_EVENTS, V2ErrorBoundary } from "@/lib/v2-core"; import { getAdaptiveLabelColor } from "@/lib/utils/darkModeColor"; import { useTabId } from "@/contexts/TabIdContext"; +import { formatNumber as centralFormatNumber, formatCurrency as centralFormatCurrency } from "@/lib/formatting"; // 🖼️ 테이블 셀 이미지 썸네일 컴포넌트 // objid인 경우 인증된 API로 blob URL 생성, 경로인 경우 직접 URL 사용 @@ -4445,17 +4446,14 @@ export const TableListComponent: React.FC = ({ return "-"; } - // 숫자 타입 포맷팅 (천단위 구분자 설정 확인) + // 숫자 타입 포맷팅 (공통 formatNumber 사용) if (inputType === "number" || inputType === "decimal") { if (value !== null && value !== undefined && value !== "") { - const numValue = typeof value === "string" ? parseFloat(value) : value; - if (!isNaN(numValue)) { - // thousandSeparator가 false가 아닌 경우(기본값 true) 천단위 구분자 적용 - if (column.thousandSeparator !== false) { - return numValue.toLocaleString("ko-KR"); - } - return String(numValue); + if (column.thousandSeparator !== false) { + return centralFormatNumber(value); } + const numValue = typeof value === "string" ? parseFloat(value) : value; + return isNaN(numValue) ? String(value) : String(numValue); } return String(value); } @@ -4463,14 +4461,11 @@ export const TableListComponent: React.FC = ({ switch (column.format) { case "number": if (value !== null && value !== undefined && value !== "") { - const numValue = typeof value === "string" ? parseFloat(value) : value; - if (!isNaN(numValue)) { - // thousandSeparator가 false가 아닌 경우(기본값 true) 천단위 구분자 적용 - if (column.thousandSeparator !== false) { - return numValue.toLocaleString("ko-KR"); - } - return String(numValue); + if (column.thousandSeparator !== false) { + return centralFormatNumber(value); } + const numValue = typeof value === "string" ? parseFloat(value) : value; + return isNaN(numValue) ? String(value) : String(numValue); } return String(value); case "date": @@ -4487,12 +4482,8 @@ export const TableListComponent: React.FC = ({ } return "-"; case "currency": - if (typeof value === "number") { - // thousandSeparator가 false가 아닌 경우(기본값 true) 천단위 구분자 적용 - if (column.thousandSeparator !== false) { - return `₩${value.toLocaleString()}`; - } - return `₩${value}`; + if (value !== null && value !== undefined && value !== "") { + return centralFormatCurrency(value); } return value; case "boolean": From a2040a228a614d8805adc37ba0026a8b3e6000e4 Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 13 Mar 2026 16:02:02 +0900 Subject: [PATCH 3/3] docs: add document sync rule for component and DB changes - Introduced a new document sync rule to ensure that related documentation is updated whenever components are added or modified, or when there are changes to the database structure. - Specified the documents that must be updated, including the full-screen analysis and V2 component usage guide, along with detailed instructions on how to update them. - This addition aims to enforce consistency and accuracy in documentation, facilitating better collaboration and adherence to development standards. Made-with: Cursor --- .cursor/rules/document-sync-rule.mdc | 38 + .../00_analysis/full-screen-analysis.md | 485 ------- .../00_analysis/v2-component-usage-guide.md | 846 ------------ .../full-screen-analysis.md | 952 ++++++++++++++ .../v2-component-usage-guide.md | 1146 +++++++++++++++++ 5 files changed, 2136 insertions(+), 1331 deletions(-) create mode 100644 .cursor/rules/document-sync-rule.mdc delete mode 100644 docs/screen-implementation-guide/00_analysis/full-screen-analysis.md delete mode 100644 docs/screen-implementation-guide/00_analysis/v2-component-usage-guide.md create mode 100644 docs/screen-implementation-guide/01_reference_guides/full-screen-analysis.md create mode 100644 docs/screen-implementation-guide/01_reference_guides/v2-component-usage-guide.md diff --git a/.cursor/rules/document-sync-rule.mdc b/.cursor/rules/document-sync-rule.mdc new file mode 100644 index 00000000..a2b7e13a --- /dev/null +++ b/.cursor/rules/document-sync-rule.mdc @@ -0,0 +1,38 @@ +--- +description: 컴포넌트 추가/수정 또는 DB 구조 변경 시 관련 문서를 항상 최신화하도록 강제하는 규칙 +globs: + - "frontend/lib/registry/components/**/*.tsx" + - "frontend/components/v2/**/*.tsx" + - "db/migrations/**/*.sql" + - "backend-node/src/types/ddl.ts" +--- + +# 컴포넌트 및 DB 구조 변경 시 문서 동기화 규칙 + +## 🚨 핵심 원칙 (절대 준수) + +새로운 V2 컴포넌트를 생성하거나 기존 컴포넌트의 설정(overrides)을 변경할 때, 또는 DB 테이블 구조나 화면 생성 파이프라인이 변경될 때는 **반드시** 아래 두 문서를 함께 업데이트해야 합니다. + +1. `docs/screen-implementation-guide/01_reference_guides/full-screen-analysis.md` (전체 레퍼런스) +2. `docs/screen-implementation-guide/01_reference_guides/v2-component-usage-guide.md` (실행 가이드) + +## 📌 업데이트 대상 및 방법 + +### 1. V2 컴포넌트 신규 추가 또는 속성(Props/Overrides) 변경 시 +- **`full-screen-analysis.md`**: `3. 컴포넌트 전체 설정 레퍼런스` 섹션에 해당 컴포넌트의 모든 설정값(타입, 기본값, 설명)을 표 형태로 추가/수정하세요. +- **`v2-component-usage-guide.md`**: + - `7. Step 6: screen_layouts_v2 INSERT`의 컴포넌트 url 매핑표에 추가하세요. + - `16. 컴포넌트 빠른 참조표`에 추가하세요. + - 필요한 경우 `8. 패턴별 layout_data 완전 예시`에 새로운 패턴을 추가하세요. + +### 2. DB 테이블 구조 또는 화면 생성 로직 변경 시 +- **`full-screen-analysis.md`**: `2. DB 테이블 스키마` 섹션의 테이블 구조(컬럼, 타입, 설명)를 최신화하세요. +- **`v2-component-usage-guide.md`**: + - `Step 1` ~ `Step 7`의 SQL 템플릿이 변경된 구조와 일치하는지 확인하고 수정하세요. + - 특히 `INSERT` 문의 컬럼 목록과 `VALUES` 형식이 정확한지 검증하세요. + +## ⚠️ AI 에이전트 행동 지침 + +1. 사용자가 컴포넌트 코드를 수정해달라고 요청하면, 수정 완료 후 **"관련 가이드 문서도 업데이트할까요?"** 라고 반드시 물어보세요. +2. 사용자가 DB 마이그레이션 스크립트를 작성해달라고 하거나 핵심 시스템 테이블을 건드리면, 가이드 문서의 SQL 템플릿도 수정해야 하는지 확인하세요. +3. 가이드 문서 업데이트 시 JSON 예제 안에 `//` 같은 주석을 넣지 않도록 주의하세요 (DB 파싱 에러 방지). diff --git a/docs/screen-implementation-guide/00_analysis/full-screen-analysis.md b/docs/screen-implementation-guide/00_analysis/full-screen-analysis.md deleted file mode 100644 index 378b13b4..00000000 --- a/docs/screen-implementation-guide/00_analysis/full-screen-analysis.md +++ /dev/null @@ -1,485 +0,0 @@ -# WACE ERP 화면 구성 시스템 전체 분석 - -> **최종 업데이트**: 2026-03-13 -> **용도**: LLM 챗봇 / AI 에이전트가 화면 개발 요청을 받았을 때 참조하는 시스템 구조 레퍼런스 -> **핵심 규칙**: 사용자 업무 화면은 React 코드(.tsx)로 직접 만들지 않는다. DB 등록(screen_definitions + screen_layouts_v2 + menu_info)으로 구현한다. - ---- - -## 1. 시스템 아키텍처 요약 - -### 1.1 화면 렌더링 파이프라인 - -``` -[DB] screen_definitions (화면 정의) - + screen_layouts_v2 (레이아웃 JSON) - + menu_info (메뉴 등록) - │ - ▼ -[Backend API] GET /api/screens/:screenId - │ - ▼ -[Frontend] /screens/[screenId]/page.tsx - │ - ▼ -[Converter] layoutV2Converter.ts → V2 JSON을 Legacy 포맷으로 변환 - │ - ▼ -[Renderer] ResponsiveGridRenderer → RealtimePreview → DynamicComponentRenderer - │ - ▼ -[Registry] ComponentRegistry.getComponent(componentType) → 실제 React 컴포넌트 렌더링 -``` - -### 1.2 화면 유형 분류 - -| 구분 | 구현 방식 | 코드 위치 | 예시 | -|------|----------|----------|------| -| **사용자 업무 화면** | DB 등록 (SQL INSERT) | screen_definitions + screen_layouts_v2 | 수주관리, 품목정보, BOM관리 | -| **관리자 메뉴** | React 코드 직접 작성 | frontend/app/(main)/admin/*/page.tsx | 사용자관리, 권한관리, 시스템설정 | - -**절대 규칙**: 사용자 업무 화면(생산, 영업, 구매, 물류, 품질 등)은 React 하드코딩 금지. 반드시 DB 등록 방식으로 구현. - ---- - -## 2. V2 컴포넌트 전체 목록 (32개) - -> 모든 컴포넌트는 `v2-` 접두사를 사용한다. 접두사 없는 컴포넌트는 레거시이므로 사용 금지. - -### 2.1 입력 컴포넌트 (9개) - -| ID | 용도 | 핵심 설정 | -|----|------|----------| -| `v2-input` | 텍스트, 숫자, 비밀번호, textarea, 슬라이더, 컬러, 버튼 | inputType, format(email/tel/url/currency/biz_no), required, readonly, maxLength | -| `v2-select` | 드롭다운, 콤보박스, 라디오, 체크박스, 태그, 토글, 스왑 | mode, source(static/code/db/api/entity/category/distinct/select), searchable, multiple, cascading | -| `v2-date` | 날짜, 시간, 날짜시간, 날짜범위, 월, 연도 | dateType(date/time/datetime), format, range, minDate, maxDate | -| `v2-file-upload` | 파일/이미지 업로드, 다중 업로드 | accept, maxSize, multiple | -| `v2-media` | 이미지, 비디오, 오디오 표시 | mediaType | -| `v2-location-swap-selector` | 출발지/도착지 선택 및 교환 | - | -| `v2-rack-structure` | 창고 랙 위치 일괄 생성 | columns, rows | -| `v2-process-work-standard` | 품목별 공정 작업기준 관리 (Pre/In/Post-Work) | - | -| `v2-item-routing` | 품목별 라우팅 버전 및 공정 순서 관리 (3단계 계층) | - | - -### 2.2 표시/데이터 컴포넌트 (10개) - -| ID | 용도 | 핵심 설정 | -|----|------|----------| -| `v2-table-list` | 데이터 테이블 (조회/편집, 페이지네이션, 정렬, 필터) | selectedTable, columns, pagination, displayMode(table/card), checkbox, linkedFilters | -| `v2-table-grouped` | 그룹화 테이블 (접기/펼치기, 그룹별 집계) | groupConfig(groupByColumn, summary), v2-table-list 기반 확장 | -| `v2-table-search-widget` | 테이블 검색/필터/그룹 바 | autoSelectFirstTable, showTableSelector | -| `v2-pivot-grid` | 다차원 피벗 분석 (행/열/데이터/필터 영역) | fields(area, summaryType, groupInterval), dataSource | -| `v2-text-display` | 라벨, 제목, 설명 텍스트 표시 | fontSize, fontWeight, color, textAlign | -| `v2-card-display` | 테이블 데이터를 카드 형태로 표시 | cardsPerRow, columnMapping(title/subtitle/image/status), cardStyle | -| `v2-aggregation-widget` | 합계, 평균, 개수, 최대, 최소 집계 카드 | items, filters, layout | -| `v2-status-count` | 상태별 건수 카드 표시 | statusField, countField | -| `v2-numbering-rule` | 자동 코드/번호 채번 (접두사+날짜+순번) | rule, prefix, format | -| `v2-category-manager` | 트리 기반 카테고리 관리 (3단계 계층) | - | - -### 2.3 레이아웃 컴포넌트 (8개) - -| ID | 용도 | 핵심 설정 | -|----|------|----------| -| `v2-split-panel-layout` | 마스터-디테일 좌우 분할 | splitRatio, leftPanel(displayMode/tableName), rightPanel(relation/foreignKey), additionalTabs | -| `v2-tabs-widget` | 탭 전환, 탭 내 컴포넌트 배치 | tabs(id/label/components), defaultTab, orientation | -| `v2-section-card` | 제목+테두리가 있는 그룹화 컨테이너 | title, collapsible | -| `v2-section-paper` | 배경색 기반 미니멀 그룹화 컨테이너 | backgroundColor, padding | -| `v2-divider-line` | 영역 구분선 | orientation, thickness | -| `v2-split-line` | 캔버스 좌우 분할용 드래그 가능 세로선 | - | -| `v2-repeat-container` | 데이터 수만큼 내부 컴포넌트 반복 렌더링 | dataSourceType, layout, gridColumns | -| `v2-repeater` | 인라인/모달/버튼 모드 반복 데이터 관리 | mode(inline/modal/button) | - -### 2.4 특수/비즈니스 컴포넌트 (5개) - -| ID | 용도 | 핵심 설정 | -|----|------|----------| -| `v2-button-primary` | 저장/삭제/조회 등 액션 버튼 | text, actionType, variant, webTypeConfig.dataflowConfig | -| `v2-timeline-scheduler` | 간트차트형 일정/계획 시각화 (드래그/리사이즈) | selectedTable, resourceTable, fieldMapping, zoomLevel, editable | -| `v2-approval-step` | 결재 단계 스테퍼 시각화 | - | -| `v2-bom-tree` | BOM 계층 트리 표시 (정전개/역전개) | - | -| `v2-bom-item-editor` | BOM 하위품목 트리 편집 | - | - ---- - -## 3. 화면 UI 패턴 분류 (7가지) - -> AI가 화면 개발 요청을 받았을 때, 아래 패턴 중 해당하는 것을 선택하여 구현한다. - -### 패턴 A: 기본 마스터 (검색 + 테이블) - -**적용 비율**: 약 50% (가장 흔함) -**적용 화면**: 코드관리, 부서정보, 창고정보, 검사기준, 불량관리, 공급업체관리 등 - -``` -┌─────────────────────────────────────────────────┐ -│ v2-table-search-widget │ -│ [검색필드1] [검색필드2] [조회] [엑셀] │ -├─────────────────────────────────────────────────┤ -│ v2-table-list │ -│ 제목 [신규] [삭제] │ -│ ─────────────────────────────────────────────── │ -│ □ | 코드 | 이름 | 상태 | 등록일 │ -└─────────────────────────────────────────────────┘ -``` - -**필수 컴포넌트**: `v2-table-search-widget` (1개) + `v2-table-list` (1개) - -### 패턴 B: 마스터-디테일 (좌우 분할) - -**적용 비율**: 약 25% -**적용 화면**: 공정관리, 수주관리, 견적관리, 품목라우팅 등 - -``` -┌──────────────────┬──────────────────────────────┐ -│ 마스터 테이블 │ 디테일 테이블/폼 │ -│ (좌측 패널) │ (우측 패널) │ -│ □ A001 항목1 │ [상세 정보 테이블] │ -│ □ A002 항목2 ← │ │ -└──────────────────┴──────────────────────────────┘ - v2-split-panel-layout -``` - -**필수 컴포넌트**: `v2-split-panel-layout` (1개) - -### 패턴 C: 마스터-디테일 + 탭 - -**적용 비율**: 약 10% -**적용 화면**: 거래처관리, 설비정보, 품목정보 등 - -``` -┌──────────────────┬──────────────────────────────┐ -│ 마스터 테이블 │ [기본] [이력] [첨부] │ -│ │ ┌────────────────────────┐ │ -│ □ A001 거래처1 │ │ 탭별 컨텐츠 │ │ -│ □ A002 거래처2 ← │ └────────────────────────┘ │ -└──────────────────┴──────────────────────────────┘ -``` - -**필수 컴포넌트**: `v2-split-panel-layout` (1개, rightPanel에 additionalTabs 설정) - -### 패턴 D: 카드 뷰 - -**적용 비율**: 약 5% -**적용 화면**: 설비정보, 대시보드, 상품 카탈로그 등 - -``` -┌─────────────────────────────────────────────────┐ -│ v2-table-search-widget │ -├─────────────────────────────────────────────────┤ -│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ -│ │ [이미지]│ │ [이미지]│ │ [이미지]│ │ -│ │ 제목 │ │ 제목 │ │ 제목 │ │ -│ │ 설명 │ │ 설명 │ │ 설명 │ │ -│ └─────────┘ └─────────┘ └─────────┘ │ -│ v2-card-display │ -└─────────────────────────────────────────────────┘ -``` - -**필수 컴포넌트**: `v2-card-display` (1개) + `v2-table-search-widget` (선택) - -### 패턴 E: 피벗 분석 - -**적용 비율**: 약 3% -**적용 화면**: 매출분석, 재고현황, 생산실적 분석 등 - -**필수 컴포넌트**: `v2-pivot-grid` (1개) - -### 패턴 F: 그룹화 테이블 - -**적용 비율**: 약 5% -**적용 화면**: 품목정보(카테고리별), 입출고관리(구분별), 작업지시(공정별) 등 - -``` -┌─────────────────────────────────────────────────┐ -│ [전체 펼치기] [전체 접기] │ -├─────────────────────────────────────────────────┤ -│ ▼ □ 그룹A 수량: 150 3건 │ -│ ├─ □ 항목1 50개 │ -│ ├─ □ 항목2 50개 │ -│ └─ □ 항목3 50개 │ -│ ► □ 그룹B 수량: 200 2건 │ -└─────────────────────────────────────────────────┘ -``` - -**필수 컴포넌트**: `v2-table-grouped` (1개) - -### 패턴 G: 타임라인/간트차트 - -**적용 비율**: 약 2% -**적용 화면**: 생산계획관리, 설비가동현황 등 - -``` -┌────────────┬─────────────────────────────────────┐ -│ │ 15(수) │ 16(목) │ 17(금) │ 18(토) │ -├────────────┼─────────────────────────────────────┤ -│ 설비A │ ████████████████ │ -│ 설비B │ █████████████████████ │ -│ 설비C │ ████████████████ │ -└────────────┴─────────────────────────────────────┘ -``` - -**필수 컴포넌트**: `v2-timeline-scheduler` (1개) - ---- - -## 4. 패턴 판단 의사결정 트리 - -> AI가 화면 요청을 받았을 때 이 트리를 따라 패턴을 결정한다. - -``` -Q1. 시간축 기반 일정/간트차트가 필요한가? -├─ YES → 패턴 G (타임라인) -└─ NO ↓ - -Q2. 다차원 집계/피벗 분석이 필요한가? -├─ YES → 패턴 E (피벗) -└─ NO ↓ - -Q3. 데이터를 그룹별로 묶어서 접기/펼치기가 필요한가? -├─ YES → 패턴 F (그룹화 테이블) -└─ NO ↓ - -Q4. 이미지+정보를 카드 형태로 표시하는가? -├─ YES → 패턴 D (카드 뷰) -└─ NO ↓ - -Q5. 마스터 테이블 선택 시 연관 디테일이 필요한가? -├─ YES → Q5-1. 디테일에 탭이 필요한가? -│ ├─ YES → 패턴 C (마스터-디테일+탭) -│ └─ NO → 패턴 B (마스터-디테일) -└─ NO → 패턴 A (기본 마스터) -``` - ---- - -## 5. 화면 구현 불가능/제한 사항 - -### 5.1 현재 불가능 (별도 React 개발 필요) - -| 기능 | 상태 | 대안 | -|------|------|------| -| 칸반 보드 (드래그앤드롭) | 미지원 | 별도 React 컴포넌트 | -| 모바일 네이티브 앱 스타일 | 미지원 | 별도 개발 | -| 복잡한 차트 (line, bar, pie) | 미지원 | 외부 라이브러리 연동 | - -### 5.2 이전에 불가능했으나 현재 지원되는 기능 - -| 기능 | 지원 컴포넌트 | 추가 시점 | -|------|-------------|----------| -| 그룹화 테이블 | `v2-table-grouped` | 2026-01 | -| 타임라인/간트차트 | `v2-timeline-scheduler` | 2026-01 | -| BOM 트리 (정전개/역전개) | `v2-bom-tree` | 2026-02 | -| BOM 하위품목 편집 | `v2-bom-item-editor` | 2026-02 | -| 결재 스테퍼 | `v2-approval-step` | 2026-02 | - -### 5.3 권장하지 않는 조합 - -| 조합 | 이유 | -|------|------| -| 3단계 이상 중첩 분할 | 화면 복잡도 증가, 성능 저하 | -| 탭 안에 탭 | 사용성 저하 | -| 한 화면에 3개 이상 테이블 | 데이터 로딩 성능 | -| 피벗 + 상세 테이블 동시 | 데이터 과부하 | - ---- - -## 6. 화면별 구현 현황 (메뉴 분류) - -### 6.1 기준정보 - -| 화면명 | 패턴 | 구현 가능 | 비고 | -|--------|------|----------|------| -| 회사정보 | A | 완전 지원 | | -| 부서정보 | A | 완전 지원 | | -| 품목정보 | F 또는 A | 완전 지원 | 카테고리별 그룹화 시 F | -| BOM관리 | B + v2-bom-tree | 완전 지원 | v2-bom-tree로 트리 구현 | -| 공정정보관리 | B | 완전 지원 | | -| 공정작업기준 | A | 완전 지원 | v2-process-work-standard 활용 가능 | -| 품목라우팅 | B | 완전 지원 | v2-item-routing 활용 가능 | - -### 6.2 영업관리 - -| 화면명 | 패턴 | 구현 가능 | 비고 | -|--------|------|----------|------| -| 수주관리 | B | 완전 지원 | | -| 견적관리 | B | 완전 지원 | | -| 거래처관리 | C | 완전 지원 | 탭(기본/이력/첨부) | -| 판매품목정보 | A | 완전 지원 | | -| 출하계획관리 | A | 완전 지원 | | - -### 6.3 생산관리 - -| 화면명 | 패턴 | 구현 가능 | 비고 | -|--------|------|----------|------| -| 생산계획관리 | G | 완전 지원 | v2-timeline-scheduler | -| 생산관리 | A | 완전 지원 | | -| 생산실적관리 | A | 완전 지원 | | -| 작업지시 | F | 완전 지원 | 공정별 그룹화 | -| 공정관리 | B | 완전 지원 | | - -### 6.4 구매관리 - -| 화면명 | 패턴 | 구현 가능 | 비고 | -|--------|------|----------|------| -| 발주관리 | A | 완전 지원 | | -| 공급업체관리 | A | 완전 지원 | | -| 구매입고 | A | 완전 지원 | | - -### 6.5 설비관리 - -| 화면명 | 패턴 | 구현 가능 | 비고 | -|--------|------|----------|------| -| 설비정보 | C 또는 D | 완전 지원 | 카드뷰 또는 탭 | - -### 6.6 물류관리 - -| 화면명 | 패턴 | 구현 가능 | 비고 | -|--------|------|----------|------| -| 창고정보관리 | A | 완전 지원 | | -| 입출고관리 | F | 완전 지원 | 구분별 그룹화 | -| 재고현황 | A 또는 E | 완전 지원 | 피벗 분석도 가능 | - -### 6.7 품질관리 - -| 화면명 | 패턴 | 구현 가능 | 비고 | -|--------|------|----------|------| -| 검사기준 | A | 완전 지원 | | -| 검사정보관리 | C | 완전 지원 | 탭(수입검사/공정검사/출하검사) | -| 검사장비관리 | A | 완전 지원 | | -| 불량관리 | A | 완전 지원 | | -| 클레임관리 | A | 완전 지원 | | - ---- - -## 7. 컴포넌트 커버리지 요약 - -| 항목 | 수치 | -|------|------| -| 전체 분석 대상 화면 | 26개 | -| V2 컴포넌트로 구현 가능 | **26개 (100%)** | -| 등록된 V2 컴포넌트 수 | 32개 | -| 화면 UI 패턴 수 | 7가지 (A~G) | - -**이전 대비 변경 사항**: -- BOM 트리 지원 추가 (v2-bom-tree) → BOM관리 완전 지원 -- 그룹화 테이블 지원 추가 (v2-table-grouped) → 품목정보, 입출고 등 완전 지원 -- 타임라인 지원 추가 (v2-timeline-scheduler) → 생산계획관리 완전 지원 -- 결재 스테퍼 추가 (v2-approval-step) → 결재 프로세스 시각화 가능 -- 전체 커버리지: 65% → **100%** 달성 - ---- - -## 8. UI vs 비즈니스 로직 분리 구조 - -``` -┌───────────────────────────────┬───────────────────────────────────┐ -│ UI 레이아웃 │ 제어관리 (비즈니스 로직) │ -│ screen_layouts_v2 (JSON) │ dataflow_diagrams (JSONB) │ -├───────────────────────────────┼───────────────────────────────────┤ -│ - 컴포넌트 배치/크기/위치 │ - 버튼 클릭 시 액션 (INSERT 등) │ -│ - 검색 필드 구성 │ - 조건부 실행 │ -│ - 테이블 컬럼 표시/숨김 │ - 다중 행 일괄 처리 │ -│ - 카드/탭/분할 레이아웃 │ - 테이블 간 데이터 이동 │ -│ - 페이지네이션/정렬 설정 │ - 외부 시스템 호출 │ -└───────────────────────────────┴───────────────────────────────────┘ -``` - -**핵심**: V2 컴포넌트는 UI만 담당한다. 버튼 클릭 시 INSERT/UPDATE/DELETE 같은 비즈니스 로직은 `dataflow_diagrams` 테이블에서 별도 설정한다. - -### 비즈니스 로직 실행 흐름 - -``` -v2-button-primary 클릭 - → webTypeConfig.dataflowConfig 확인 - → ImprovedButtonActionExecutor 실행 - 1. Before 제어 실행 (조건 체크) - 2. Main 액션 실행 (save/delete/update) - 3. After 제어 실행 (후처리) -``` - ---- - -## 9. 화면 개발 순서 (AI 에이전트용) - -``` -Step 1: DB 테이블 생성 - └─ 모든 컬럼 VARCHAR(500), 기본 5개 컬럼 필수 (id, created_date, updated_date, writer, company_code) - └─ table_labels, table_type_columns, column_labels 메타데이터 등록 - -Step 2: screen_definitions INSERT - └─ screen_code = '{company_code}_{순번}' - └─ table_name = 메인 테이블명 - -Step 3: screen_layouts_v2 INSERT - └─ layout_data = V2 레이아웃 JSON (version: "2.0", components 배열) - └─ 패턴(A~G)에 맞는 컴포넌트 배치 - -Step 4: dataflow_diagrams INSERT (비즈니스 로직이 필요한 경우) - └─ 버튼별 액션 정의 (INSERT/UPDATE/DELETE) - └─ 조건, 필드 매핑 설정 - -Step 5: menu_info INSERT - └─ menu_url = '/screen/{screen_code}' - └─ 적절한 parent_id로 메뉴 트리에 배치 -``` - ---- - -## 10. DB 테이블 구조 레퍼런스 - -### screen_definitions - -| 컬럼 | 타입 | 설명 | -|------|------|------| -| screen_id | integer PK (시퀀스) | 화면 고유 ID | -| screen_name | varchar(100) | 화면명 | -| screen_code | varchar(50) UNIQUE | 화면 코드 (`{company_code}_{순번}`) | -| table_name | varchar(100) | 기본 테이블명 | -| company_code | varchar(50) | 회사 코드 | -| description | text | 설명 | -| is_active | char(1) | Y/N/D | - -### screen_layouts_v2 - -| 컬럼 | 타입 | 설명 | -|------|------|------| -| layout_id | integer PK | 레이아웃 ID | -| screen_id | integer FK | 화면 ID | -| company_code | varchar(20) | 회사 코드 | -| layer_id | integer | 레이어 ID (1=기본) | -| layout_data | jsonb | 전체 레이아웃 JSON | - -### layout_data JSON 구조 - -```json -{ - "version": "2.0", - "components": [ - { - "id": "comp_xxx", - "url": "@/lib/registry/components/v2-table-list", - "position": { "x": 0, "y": 150, "z": 1 }, - "size": { "width": 1920, "height": 600 }, - "displayOrder": 0, - "overrides": { - "label": "품목 목록", - "tableName": "item_info", - "columns": [ - { "columnName": "item_code", "displayName": "품목코드", "visible": true } - ], - "pagination": { "enabled": true, "pageSize": 20 } - } - } - ], - "gridSettings": { "columns": 12, "gap": 16, "padding": 16 }, - "screenResolution": { "width": 1920, "height": 1080 } -} -``` - -### menu_info (주요 컬럼) - -| 컬럼 | 설명 | -|------|------| -| objid / menu_id | 메뉴 PK | -| screen_id | 연결된 화면 ID | -| menu_url | 메뉴 URL (`/screen/{screen_code}`) | -| company_code | 회사 코드 | diff --git a/docs/screen-implementation-guide/00_analysis/v2-component-usage-guide.md b/docs/screen-implementation-guide/00_analysis/v2-component-usage-guide.md deleted file mode 100644 index 1966a3c7..00000000 --- a/docs/screen-implementation-guide/00_analysis/v2-component-usage-guide.md +++ /dev/null @@ -1,846 +0,0 @@ -# V2 컴포넌트 사용 가이드 (LLM/챗봇용) - -> **최종 업데이트**: 2026-03-13 -> **용도**: LLM 챗봇이 화면 개발 요청을 받았을 때, 어떤 컴포넌트를 어떤 설정으로 사용해야 하는지 판단하는 레퍼런스 -> **버전**: 2.0.0 -> **대상**: AI 에이전트, 챗봇, 화면 설계자 - ---- - -## 0. 핵심 규칙 (반드시 준수) - -1. **사용자 업무 화면은 React 코드로 직접 만들지 않는다** → DB에 `screen_definitions` + `screen_layouts_v2` + `menu_info` INSERT로 구현 -2. **모든 컴포넌트는 `v2-` 접두사** → `v2-` 없는 레거시 컴포넌트 사용 금지 -3. **UI와 로직 분리** → UI는 `screen_layouts_v2`, 비즈니스 로직은 `dataflow_diagrams` -4. **멀티테넌시 필수** → 모든 테이블에 `company_code` 컬럼, 모든 쿼리에 `company_code` 필터링 - ---- - -## 1. 컴포넌트 전체 카탈로그 (32개) - -### 1.1 입력 컴포넌트 - -#### v2-input - -텍스트, 숫자, 비밀번호, textarea 등 모든 단일 값 입력을 처리하는 통합 입력 컴포넌트. - -| 설정 | 타입 | 설명 | 값 | -|------|------|------|----| -| inputType | string | 입력 유형 | `text`, `number`, `password`, `slider`, `color`, `button`, `textarea` | -| format | string | 포맷 검증 | `email`, `tel`, `url`, `currency`, `biz_no` | -| required | boolean | 필수 여부 | true/false | -| readonly | boolean | 읽기 전용 | true/false | -| maxLength | number | 최대 길이 | 숫자 | -| min / max | number | 숫자 범위 | 숫자 (inputType=number일 때) | -| step | number | 증감 단위 | 숫자 (inputType=number/slider일 때) | -| tableName | string | 바인딩 테이블 | DB 테이블명 | -| columnName | string | 바인딩 컬럼 | DB 컬럼명 | - -#### v2-select - -드롭다운, 콤보박스, 라디오, 체크박스, 태그 등 선택형 입력 통합 컴포넌트. - -| 설정 | 타입 | 설명 | 값 | -|------|------|------|----| -| mode | string | 선택 모드 | `dropdown`, `combobox`, `radio`, `check`, `tag`, `tagbox`, `toggle`, `swap` | -| source | string | 데이터 소스 | `static`, `code`, `db`, `api`, `entity`, `category`, `distinct`, `select` | -| searchable | boolean | 검색 가능 | true/false (mode=combobox일 때 기본 true) | -| multiple | boolean | 다중 선택 | true/false | -| cascading | object | 연쇄 선택 | 상위 select 값에 따라 하위 옵션 변경 | - -**source 설명**: -- `static`: 고정 옵션 목록 (options 배열 직접 지정) -- `code`: code_info 테이블의 공통 코드 -- `db`: 특정 테이블의 컬럼 값 -- `entity`: 엔티티 조인 (다른 테이블 참조) -- `category`: v2-category-manager의 카테고리 -- `distinct`: 테이블 컬럼의 DISTINCT 값 - -#### v2-date - -날짜, 시간, 날짜시간, 날짜범위, 월, 연도 입력. - -| 설정 | 타입 | 설명 | 값 | -|------|------|------|----| -| dateType | string | 날짜 유형 | `date`, `time`, `datetime`, `month`, `year` | -| format | string | 표시 형식 | `YYYY-MM-DD`, `HH:mm`, `YYYY-MM-DD HH:mm` 등 | -| range | boolean | 범위 선택 | true/false (시작~종료 날짜) | -| minDate / maxDate | string | 선택 가능 범위 | ISO 8601 날짜 | -| showToday | boolean | 오늘 버튼 | true/false | - -#### v2-file-upload - -파일/이미지 업로드. 다중 업로드, 미리보기 지원. - -#### v2-media - -이미지, 비디오, 오디오 등 미디어 표시/재생. - -#### v2-location-swap-selector - -출발지/도착지 선택 및 교환 (물류 화면용). - -#### v2-rack-structure - -창고 랙 위치 일괄 생성 (열 범위, 단 수 설정). - -#### v2-process-work-standard - -품목별 공정 작업기준(Pre-Work/In-Work/Post-Work) 관리. 라우팅과 연동. - -#### v2-item-routing - -품목별 라우팅 버전 및 공정 순서 관리 (3단계 계층: 품목 → 라우팅 버전 → 공정). - ---- - -### 1.2 표시/데이터 컴포넌트 - -#### v2-table-list - -가장 핵심 컴포넌트. DB 테이블 데이터를 조회/편집하는 테이블. - -| 설정 | 타입 | 설명 | 값 | -|------|------|------|----| -| selectedTable | string | **필수**. DB 테이블명 | 예: `"item_info"` | -| columns | array | 표시할 컬럼 설정 | `[{ columnName, displayName, visible, sortable, width, editable }]` | -| pagination | object | 페이지네이션 | `{ enabled: true, pageSize: 20, showSizeSelector: true }` | -| displayMode | string | 표시 모드 | `"table"` (기본) 또는 `"card"` | -| checkbox | object | 체크박스 | `{ enabled: true, multiple: true, position: "left", selectAll: true }` | -| horizontalScroll | object | 가로 스크롤 | `{ enabled: true, maxVisibleColumns: 8 }` | -| linkedFilters | array | 다른 컴포넌트와 연동 필터 | - | -| excludeFilter | object | 제외 필터 | - | -| autoLoad | boolean | 자동 데이터 로드 | true/false | -| stickyHeader | boolean | 헤더 고정 | true/false | -| toolbar | object | 툴바 (신규/삭제/엑셀 등) | - | -| tableStyle | object | 테이블 스타일 | `{ compact: true, striped: true }` | - -**columns 배열 항목**: - -```json -{ - "columnName": "item_code", - "displayName": "품목코드", - "visible": true, - "sortable": true, - "width": 120, - "editable": false, - "inputType": "text", - "format": null, - "align": "left" -} -``` - -#### v2-table-grouped - -v2-table-list 기반 확장. 특정 컬럼 기준으로 데이터를 그룹화하여 접기/펼치기 기능 제공. - -| 설정 | 타입 | 설명 | 값 | -|------|------|------|----| -| selectedTable | string | **필수**. DB 테이블명 | - | -| groupConfig | object | **필수**. 그룹화 설정 | 아래 참조 | -| columns | array | 컬럼 설정 (v2-table-list와 동일) | - | -| showCheckbox | boolean | 체크박스 표시 | true/false | - -**groupConfig 구조**: - -```json -{ - "groupByColumn": "category", - "groupLabelFormat": "{category_name} ({category_code})", - "summary": { - "sumColumns": ["quantity", "amount"], - "showCount": true - }, - "defaultExpanded": true, - "nestedGroup": null -} -``` - -#### v2-table-search-widget - -테이블 상단에 배치하는 검색/필터 바. 대상 테이블의 컬럼을 자동 감지하여 검색 필드 생성. - -| 설정 | 타입 | 설명 | 값 | -|------|------|------|----| -| autoSelectFirstTable | boolean | 첫 번째 테이블 자동 선택 | true/false | -| showTableSelector | boolean | 테이블 선택기 표시 | true/false | -| title | string | 검색 바 제목 | - | - -#### v2-pivot-grid - -다차원 피벗 테이블. 행/열/데이터/필터 영역으로 데이터 분석. - -| 설정 | 타입 | 설명 | 값 | -|------|------|------|----| -| fields | array | **필수**. 필드 정의 | 아래 참조 | -| dataSource | object | 데이터 소스 | `{ type: "table"/"api"/"static", joinConfigs, filterConditions }` | - -**fields 배열 항목**: - -```json -{ - "dataField": "region", - "area": "row", - "caption": "지역" -} -``` - -area 값: `"row"`, `"column"`, `"data"`, `"filter"` -summaryType (area=data일 때): `"sum"`, `"avg"`, `"count"`, `"min"`, `"max"`, `"countDistinct"` -groupInterval (날짜 필드): `"year"`, `"quarter"`, `"month"`, `"week"`, `"day"` - -#### v2-text-display - -라벨, 제목, 설명 텍스트 표시. - -| 설정 | 타입 | 설명 | -|------|------|------| -| fontSize | string | 폰트 크기 | -| fontWeight | string | 폰트 두께 | -| color | string | 텍스트 색상 | -| textAlign | string | 정렬 | - -#### v2-card-display - -테이블 데이터를 카드 형태로 표시. - -| 설정 | 타입 | 설명 | 값 | -|------|------|------|----| -| dataSource | string | 데이터 소스 | `"table"`, `"static"`, `"api"` | -| cardsPerRow | number | 행당 카드 수 | 1~6 | -| columnMapping | object | 필드 매핑 | `{ title, subtitle, description, image, status }` | -| cardStyle | object | 카드 스타일 | `{ imagePosition, imageSize }` | - -#### v2-aggregation-widget - -합계, 평균, 개수, 최대, 최소 등 집계 카드 표시. - -| 설정 | 타입 | 설명 | -|------|------|------| -| items | array | 집계 항목 배열 (테이블, 컬럼, 집계함수) | -| filters | array | 필터 조건 | -| layout | string | 레이아웃 (horizontal/vertical) | - -#### v2-status-count - -상태별 건수를 카드 형태로 표시. 대시보드/현황 화면에 적합. - -#### v2-numbering-rule - -자동 코드/번호 채번. 접두사 + 날짜 + 순번 조합. - -| 설정 | 타입 | 설명 | -|------|------|------| -| rule | string | 채번 규칙 | -| prefix | string | 접두사 | -| format | string | 포맷 | - -#### v2-category-manager - -트리 기반 카테고리 관리 (3단계 계층). 카테고리 생성/수정/삭제/이동. - ---- - -### 1.3 레이아웃 컴포넌트 - -#### v2-split-panel-layout - -**가장 복잡하고 중요한 컴포넌트**. 마스터-디테일 좌우 분할 레이아웃. - -| 설정 | 타입 | 설명 | 값 | -|------|------|------|----| -| splitRatio | number | 좌측 비율 (0-100) | 기본 30 | -| resizable | boolean | 리사이즈 가능 | true/false | -| minLeftWidth | number | 좌측 최소 너비 | 기본 200 | -| minRightWidth | number | 우측 최소 너비 | 기본 300 | -| syncSelection | boolean | 선택 동기화 | true/false | -| autoLoad | boolean | 자동 로드 | true/false | - -**leftPanel / rightPanel 구조**: - -```json -{ - "leftPanel": { - "displayMode": "table", - "tableName": "master_table", - "columns": [], - "showSearch": true, - "showAdd": true, - "showEdit": true, - "showDelete": true, - "addButton": { - "enabled": true, - "mode": "auto", - "modalScreenId": "" - }, - "editButton": { - "enabled": true, - "mode": "auto", - "modalScreenId": "" - }, - "deleteButton": { - "enabled": true, - "buttonLabel": "삭제", - "confirmMessage": "삭제하시겠습니까?" - }, - "addModalColumns": [], - "additionalTabs": [], - "dataFilter": {} - }, - "rightPanel": { - "displayMode": "table", - "tableName": "detail_table", - "relation": { - "type": "detail", - "foreignKey": "master_id", - "leftColumn": "id", - "rightColumn": "master_id", - "keys": [] - }, - "addButton": { "enabled": true }, - "editButton": { "enabled": true }, - "deleteButton": { "enabled": true }, - "additionalTabs": [] - } -} -``` - -**displayMode 종류**: -- `"list"`: 리스트 형태 (제목, 부제목) -- `"table"`: 테이블 형태 (컬럼, 행) -- `"custom"`: 자유 배치 (components 배열로 내부 컴포넌트 배치) - -**relation.type 종류**: -- `"join"`: 두 테이블 JOIN -- `"detail"`: 마스터 PK → 디테일 FK 관계 -- `"custom"`: 커스텀 관계 (leftColumn, rightColumn 직접 지정) - -**additionalTabs** (우측 패널에 멀티 탭 배치): - -```json -{ - "additionalTabs": [ - { - "id": "tab1", - "label": "기본정보", - "tableName": "detail_basic", - "relation": { "type": "detail", "foreignKey": "master_id" } - }, - { - "id": "tab2", - "label": "이력", - "tableName": "detail_history", - "relation": { "type": "detail", "foreignKey": "master_id" } - } - ] -} -``` - -#### v2-tabs-widget - -탭 전환 레이아웃. 탭 내부에 컴포넌트를 배치할 수 있음. - -| 설정 | 타입 | 설명 | 값 | -|------|------|------|----| -| tabs | array | **필수**. 탭 배열 | `[{ id, label, order, disabled, components }]` | -| defaultTab | string | 기본 활성 탭 ID | - | -| orientation | string | 탭 방향 | `"horizontal"`, `"vertical"` | -| persistSelection | boolean | 선택 유지 | true/false | - -**tabs 항목 구조**: - -```json -{ - "id": "tab-basic", - "label": "기본정보", - "order": 0, - "disabled": false, - "components": [ - { - "id": "tbl-basic", - "componentType": "v2-table-list", - "label": "기본정보", - "position": { "x": 0, "y": 0 }, - "size": { "width": 800, "height": 400 }, - "componentConfig": { "selectedTable": "basic_info" } - } - ] -} -``` - -#### v2-section-card - -제목+테두리가 있는 그룹화 컨테이너. 폼 필드를 묶을 때 사용. - -| 설정 | 타입 | 설명 | -|------|------|------| -| title | string | 섹션 제목 | -| collapsible | boolean | 접기/펼치기 가능 | -| padding | string | 내부 여백 | - -#### v2-section-paper - -배경색 기반 미니멀 그룹화 컨테이너. - -#### v2-divider-line - -영역 구분선 (가로/세로). - -#### v2-split-line - -캔버스 화면에서 좌우 영역을 분할하는 드래그 가능한 세로선. 디자이너 모드에서 사용. - -#### v2-repeat-container - -데이터 수만큼 내부 컴포넌트를 반복 렌더링. - -| 설정 | 타입 | 설명 | 값 | -|------|------|------|----| -| dataSourceType | string | 데이터 소스 | `"table"`, `"api"`, `"static"` | -| layout | string | 레이아웃 | `"vertical"`, `"horizontal"`, `"grid"` | -| gridColumns | number | 그리드 열 수 | 1~12 | - -#### v2-repeater - -인라인/모달/버튼 모드로 반복 데이터를 관리하는 컴포넌트. 주문 상세, 항목 리스트 등. - -| 설정 | 타입 | 설명 | 값 | -|------|------|------|----| -| mode | string | 동작 모드 | `"inline"` (인라인 편집), `"modal"` (모달 팝업), `"button"` (버튼 트리거) | - ---- - -### 1.4 특수/비즈니스 컴포넌트 - -#### v2-button-primary - -저장, 삭제, 조회 등 액션 버튼. 제어관리(dataflow)와 연결 가능. - -| 설정 | 타입 | 설명 | 값 | -|------|------|------|----| -| text | string | 버튼 텍스트 | 예: `"저장"`, `"삭제"` | -| actionType | string | 액션 유형 | `"save"`, `"delete"`, `"search"`, `"custom"` | -| variant | string | 스타일 | `"default"`, `"outline"`, `"destructive"`, `"ghost"` | - -**제어관리 연결** (webTypeConfig.dataflowConfig): - -```json -{ - "webTypeConfig": { - "enableDataflowControl": true, - "dataflowConfig": { - "controlMode": "relationship", - "relationshipConfig": { - "relationshipId": "rel_001", - "relationshipName": "수주확정", - "executionTiming": "after" - } - } - } -} -``` - -executionTiming 값: -- `"before"`: 메인 액션 전에 제어 실행 (조건 체크용) -- `"after"`: 메인 액션 후에 제어 실행 (후처리용) -- `"replace"`: 메인 액션 대신 제어만 실행 - -#### v2-timeline-scheduler - -간트차트형 일정/계획 시각화. 드래그/리사이즈로 일정 변경 가능. - -| 설정 | 타입 | 설명 | 값 | -|------|------|------|----| -| selectedTable | string | 스케줄 테이블명 | - | -| resourceTable | string | 리소스 테이블명 (설비/작업자) | - | -| fieldMapping | object | 필드 매핑 | `{ id, resourceId, title, startDate, endDate, status, progress }` | -| resourceFieldMapping | object | 리소스 필드 매핑 | `{ id, name, group }` | -| defaultZoomLevel | string | 초기 줌 | `"day"`, `"week"`, `"month"` | -| editable | boolean | 편집 가능 | true/false | -| allowDrag | boolean | 드래그 이동 허용 | true/false | -| allowResize | boolean | 리사이즈 허용 | true/false | - -#### v2-approval-step - -결재 단계 스테퍼 시각화. 결재 프로세스의 진행 상태를 단계별로 표시. - -#### v2-bom-tree - -BOM(Bill of Materials) 계층 트리 표시. 정전개(상위→하위)/역전개(하위→상위) 전환 가능. - -#### v2-bom-item-editor - -BOM 하위품목 트리 편집. 하위 부품 추가/수정/삭제/수량 변경 등. - ---- - -## 2. 화면 패턴별 상세 구현 가이드 - -### 2.1 패턴 A: 기본 마스터 (검색 + 테이블) - -**적용 조건**: 단일 테이블 CRUD. 마스터-디테일 관계 없음. - -**layout_data 예시**: - -```json -{ - "version": "2.0", - "components": [ - { - "id": "search_widget", - "url": "@/lib/registry/components/v2-table-search-widget", - "position": { "x": 0, "y": 0, "z": 1 }, - "size": { "width": 1920, "height": 100 }, - "displayOrder": 0, - "overrides": { - "label": "검색", - "autoSelectFirstTable": true - } - }, - { - "id": "table_list", - "url": "@/lib/registry/components/v2-table-list", - "position": { "x": 0, "y": 120, "z": 1 }, - "size": { "width": 1920, "height": 700 }, - "displayOrder": 1, - "overrides": { - "label": "품목 목록", - "tableName": "item_info", - "columns": [ - { "columnName": "item_code", "displayName": "품목코드", "visible": true, "sortable": true, "width": 120 }, - { "columnName": "item_name", "displayName": "품목명", "visible": true, "sortable": true, "width": 200 }, - { "columnName": "item_type", "displayName": "품목유형", "visible": true, "width": 100 }, - { "columnName": "unit", "displayName": "단위", "visible": true, "width": 80 } - ], - "pagination": { "enabled": true, "pageSize": 20 }, - "checkbox": { "enabled": true, "multiple": true } - } - } - ], - "gridSettings": { "columns": 12, "gap": 16, "padding": 16 }, - "screenResolution": { "width": 1920, "height": 1080 } -} -``` - -### 2.2 패턴 B: 마스터-디테일 (좌우 분할) - -**적용 조건**: 마스터 테이블 선택 시 연관 디테일 테이블 표시. 두 테이블 간 FK 관계 존재. - -**layout_data 예시**: - -```json -{ - "version": "2.0", - "components": [ - { - "id": "split_panel", - "url": "@/lib/registry/components/v2-split-panel-layout", - "position": { "x": 0, "y": 0, "z": 1 }, - "size": { "width": 1920, "height": 800 }, - "displayOrder": 0, - "overrides": { - "label": "수주관리", - "splitRatio": 35, - "resizable": true, - "leftPanel": { - "displayMode": "table", - "tableName": "order_master", - "columns": [ - { "columnName": "order_no", "displayName": "수주번호", "visible": true, "width": 120 }, - { "columnName": "customer_name", "displayName": "거래처", "visible": true, "width": 150 }, - { "columnName": "order_date", "displayName": "수주일자", "visible": true, "width": 100 }, - { "columnName": "status", "displayName": "상태", "visible": true, "width": 80 } - ], - "showSearch": true, - "showAdd": true, - "showDelete": true, - "addButton": { "enabled": true, "mode": "auto" }, - "deleteButton": { "enabled": true, "confirmMessage": "삭제하시겠습니까?" } - }, - "rightPanel": { - "displayMode": "table", - "tableName": "order_detail", - "relation": { - "type": "detail", - "foreignKey": "order_master_id", - "leftColumn": "id", - "rightColumn": "order_master_id" - }, - "columns": [ - { "columnName": "item_code", "displayName": "품목코드", "visible": true, "width": 120 }, - { "columnName": "item_name", "displayName": "품목명", "visible": true, "width": 150 }, - { "columnName": "quantity", "displayName": "수량", "visible": true, "width": 80, "editable": true }, - { "columnName": "unit_price", "displayName": "단가", "visible": true, "width": 100, "editable": true } - ], - "addButton": { "enabled": true }, - "deleteButton": { "enabled": true } - } - } - } - ], - "gridSettings": { "columns": 12, "gap": 16, "padding": 16 }, - "screenResolution": { "width": 1920, "height": 1080 } -} -``` - -### 2.3 패턴 C: 마스터-디테일 + 탭 - -**적용 조건**: 마스터-디테일에서 우측에 여러 종류의 상세 정보를 탭으로 분리. - -**차이점**: rightPanel에 `additionalTabs` 배열 추가. - -```json -{ - "rightPanel": { - "displayMode": "table", - "tableName": "customer_basic", - "relation": { - "type": "detail", - "foreignKey": "customer_id" - }, - "additionalTabs": [ - { - "id": "tab-basic", - "label": "기본정보", - "tableName": "customer_basic", - "relation": { "type": "detail", "foreignKey": "customer_id" } - }, - { - "id": "tab-history", - "label": "거래이력", - "tableName": "customer_history", - "relation": { "type": "detail", "foreignKey": "customer_id" } - }, - { - "id": "tab-files", - "label": "첨부파일", - "tableName": "customer_files", - "relation": { "type": "detail", "foreignKey": "customer_id" } - } - ] - } -} -``` - ---- - -## 3. 제어관리 (비즈니스 로직) 설정 - -### 3.1 개요 - -V2 컴포넌트는 **UI만 담당**한다. 버튼 클릭 시 실행되는 비즈니스 로직(INSERT/UPDATE/DELETE, 조건 체크, 외부 호출 등)은 `dataflow_diagrams` 테이블에서 별도 설정한다. - -### 3.2 설정 위치 - -| 구분 | 저장 위치 | 용도 | -|------|----------|------| -| 버튼-제어 연결 | `screen_layouts_v2` → 컴포넌트 `webTypeConfig.dataflowConfig` | 어떤 버튼이 어떤 제어를 실행하는지 | -| 제어 정의 | `dataflow_diagrams` → `control`, `plan` JSONB | 조건, 액션, 필드 매핑 | -| 노드 플로우 | `node_flows` → `flow_data` JSONB | 복잡한 다단계 플로우 | - -### 3.3 실행 흐름 - -``` -[버튼 클릭] v2-button-primary - │ - ▼ -[설정 확인] webTypeConfig.dataflowConfig - │ - ├─ controlMode: "relationship" → dataflow_diagrams 실행 - ├─ controlMode: "flow" → node_flows 실행 - └─ controlMode: "none" → 기본 액션만 실행 - │ - ▼ -[실행기] ImprovedButtonActionExecutor - │ - ├─ 1. Before 제어 (executionTiming = "before") - │ └─ 조건 체크 → 실패 시 중단 - │ - ├─ 2. Main 액션 (executionTiming ≠ "replace"일 때) - │ └─ save / delete / update 실행 - │ - └─ 3. After 제어 (executionTiming = "after") - └─ 후처리 (이력 저장, 상태 변경, 외부 호출 등) -``` - -### 3.4 dataflow_diagrams 설정 예시 - -**시나리오**: 수주 목록에서 [확정] 버튼 클릭 → 상태를 '확정'으로 변경 + 이력 테이블에 기록 - -```json -{ - "control": [ - { - "conditions": [ - { "field": "status", "operator": "=", "value": "대기", "dataType": "string" } - ], - "triggerType": "update" - } - ], - "plan": [ - { - "actions": [ - { - "actionType": "update", - "targetTable": "order_master", - "conditions": [ - { "field": "status", "operator": "=", "value": "대기" } - ], - "fieldMappings": [ - { "targetField": "status", "defaultValue": "확정" } - ] - }, - { - "actionType": "insert", - "targetTable": "order_history", - "fieldMappings": [ - { "sourceField": "order_no", "targetField": "order_no" }, - { "sourceField": "customer_name", "targetField": "customer_name" }, - { "targetField": "action", "defaultValue": "확정" } - ] - } - ] - } - ] -} -``` - ---- - -## 4. 화면 개발 체크리스트 (AI 에이전트용) - -화면 개발 요청을 받았을 때 이 순서대로 확인한다. - -### Step 1: 요구사항 분석 - -``` -□ 어떤 테이블을 사용하는가? (메인 테이블, 디테일 테이블) -□ 테이블 간 관계는? (FK, 1:N, M:N) -□ 어떤 패턴인가? (A~G 중 선택, 의사결정 트리 참조) -□ 어떤 버튼이 필요한가? (저장, 삭제, 확정 등) -□ 비즈니스 로직이 있는가? (상태 변경, 이력 기록, 외부 호출 등) -``` - -### Step 2: DB 테이블 생성 - -``` -□ 모든 컬럼 VARCHAR(500) -□ 기본 5개 컬럼 포함 (id, created_date, updated_date, writer, company_code) -□ table_labels, table_type_columns, column_labels 메타데이터 등록 -□ company_code 인덱스 생성 -``` - -### Step 3: screen_definitions INSERT - -```sql -INSERT INTO screen_definitions (screen_name, screen_code, table_name, company_code, is_active) -VALUES ('화면명', '{company_code}_순번', '메인_테이블', '{company_code}', 'Y'); -``` - -### Step 4: screen_layouts_v2 INSERT - -```sql -INSERT INTO screen_layouts_v2 (screen_id, company_code, layer_id, layout_data) -VALUES ({screen_id}, '{company_code}', 1, '{패턴별 JSON}'); -``` - -### Step 5: dataflow_diagrams INSERT (비즈니스 로직 필요 시) - -``` -□ 버튼별 액션 정의 -□ 조건 설정 (어떤 상태일 때만 실행) -□ 필드 매핑 (소스→타겟) -□ 실행 타이밍 (before/after/replace) -``` - -### Step 6: menu_info INSERT - -```sql -INSERT INTO menu_info (screen_id, menu_url, company_code, ...) -VALUES ({screen_id}, '/screen/{screen_code}', '{company_code}', ...); -``` - -### Step 7: 멀티테넌시 검증 - -``` -□ 테이블에 company_code 컬럼 존재 -□ 모든 SELECT에 company_code 필터링 -□ INSERT에 company_code 포함 -□ UPDATE/DELETE WHERE절에 company_code 포함 -□ JOIN에 company_code 매칭 -``` - ---- - -## 5. 자주 묻는 질문 (FAQ) - -### Q: v2-table-list와 v2-table-grouped 중 어떤 걸 써야 하나? - -**A**: 데이터를 특정 컬럼 기준으로 묶어서 접기/펼치기가 필요하면 `v2-table-grouped`, 아니면 `v2-table-list`. 예를 들어 "카테고리별 품목 목록"은 `v2-table-grouped`, "품목 전체 목록"은 `v2-table-list`. - -### Q: 마스터-디테일에서 탭을 사용하고 싶은데? - -**A**: `v2-split-panel-layout`의 rightPanel에 `additionalTabs` 배열을 설정한다. 별도의 `v2-tabs-widget`을 배치할 필요 없다. - -### Q: BOM 화면은 어떻게 만드나? - -**A**: `v2-split-panel-layout`의 좌측에 `v2-bom-tree` (또는 `v2-table-list`), 우측에 `v2-bom-item-editor`를 배치한다. 트리 컴포넌트가 자체적으로 정전개/역전개를 지원한다. - -### Q: 버튼 클릭 시 특정 로직을 실행하고 싶은데? - -**A**: `v2-button-primary`의 `webTypeConfig.dataflowConfig`에 제어관리를 연결한다. `dataflow_diagrams` 테이블에 실행할 액션(INSERT/UPDATE/DELETE)과 조건을 정의한다. - -### Q: 검색 기능은 어떻게 추가하나? - -**A**: `v2-table-search-widget`을 `v2-table-list` 위에 배치한다. `autoSelectFirstTable: true`로 설정하면 자동으로 화면의 첫 번째 테이블과 연동된다. - -### Q: 파일 업로드는? - -**A**: `v2-file-upload` 컴포넌트를 사용한다. 폼 내 배치하여 파일/이미지 업로드를 처리한다. - -### Q: 결재 프로세스를 표시하고 싶은데? - -**A**: `v2-approval-step` 컴포넌트를 사용한다. 결재 단계를 스테퍼 형태로 시각화한다. - -### Q: 인라인 편집이 가능한가? - -**A**: `v2-table-list`의 columns에서 `editable: true`로 설정하면 해당 컬럼의 셀을 직접 편집할 수 있다. 단, 복잡한 편집은 모달 편집(`v2-split-panel-layout`의 editButton.mode = "modal")을 권장한다. - -### Q: 화면에 집계(합계, 평균 등)를 표시하고 싶은데? - -**A**: `v2-aggregation-widget`을 사용하여 테이블 데이터의 합계, 평균, 개수 등을 카드 형태로 표시한다. 또는 `v2-status-count`로 상태별 건수를 표시한다. - ---- - -## 6. 컴포넌트 선택 빠른 참조표 - -| 요구사항 | 컴포넌트 | -|----------|----------| -| 데이터 목록 보기 | `v2-table-list` | -| 데이터 검색/필터 | `v2-table-search-widget` | -| 좌우 분할 (마스터-디테일) | `v2-split-panel-layout` | -| 그룹별 묶기 (접기/펼치기) | `v2-table-grouped` | -| 탭 전환 | `v2-tabs-widget` 또는 `v2-split-panel-layout`의 additionalTabs | -| 카드 형태 보기 | `v2-card-display` | -| 피벗/집계 분석 | `v2-pivot-grid` | -| 간트차트/일정 | `v2-timeline-scheduler` | -| BOM 트리 | `v2-bom-tree` + `v2-bom-item-editor` | -| 텍스트 입력 | `v2-input` | -| 선택 (드롭다운 등) | `v2-select` | -| 날짜 입력 | `v2-date` | -| 파일 업로드 | `v2-file-upload` | -| 액션 버튼 | `v2-button-primary` | -| 자동 채번 | `v2-numbering-rule` | -| 카테고리 관리 | `v2-category-manager` | -| 결재 단계 표시 | `v2-approval-step` | -| 상태별 건수 | `v2-status-count` | -| 합계/평균 카드 | `v2-aggregation-widget` | -| 구분선 | `v2-divider-line` | -| 그룹 컨테이너 | `v2-section-card` 또는 `v2-section-paper` | -| 데이터 반복 | `v2-repeat-container` 또는 `v2-repeater` | -| 공정 작업기준 | `v2-process-work-standard` | -| 품목 라우팅 | `v2-item-routing` | -| 미디어 표시 | `v2-media` | -| 랙 구조 | `v2-rack-structure` | -| 위치 교환 | `v2-location-swap-selector` | diff --git a/docs/screen-implementation-guide/01_reference_guides/full-screen-analysis.md b/docs/screen-implementation-guide/01_reference_guides/full-screen-analysis.md new file mode 100644 index 00000000..1ba0da01 --- /dev/null +++ b/docs/screen-implementation-guide/01_reference_guides/full-screen-analysis.md @@ -0,0 +1,952 @@ +# WACE 화면 시스템 - DB 스키마 & 컴포넌트 설정 전체 레퍼런스 + +> **최종 업데이트**: 2026-03-13 +> **용도**: AI 챗봇이 화면 생성 시 참조하는 DB 스키마, 컴포넌트 전체 설정 사전 +> **관련 문서**: `v2-component-usage-guide.md` (SQL 템플릿, 실행 예시) + +--- + +## 1. 시스템 아키텍처 + +### 렌더링 파이프라인 + +``` +[DB] screen_definitions + screen_layouts_v2 + → [Backend API] GET /api/screens/:screenId + → [layoutV2Converter] V2 JSON → Legacy 변환 (기본값 + overrides 병합) + → [ResponsiveGridRenderer] → DynamicComponentRenderer + → [ComponentRegistry] → 실제 React 컴포넌트 +``` + +### 테이블 관계도 + +``` +비즈니스 테이블 ←── table_labels (라벨) + ←── table_type_columns (컬럼 타입, company_code='*') + ←── column_labels (한글 라벨) + +screen_definitions ←── screen_layouts_v2 (layout_data JSON) +menu_info (메뉴 트리, menu_url → /screen/{screen_code}) + +[선택] dataflow_diagrams (비즈니스 로직) +[선택] numbering_rules + numbering_rule_parts (채번) +[선택] table_column_category_values (카테고리) +``` + +--- + +## 2. DB 테이블 스키마 + +### 2.1 비즈니스 테이블 필수 구조 + +> **[최우선 규칙] 비즈니스 테이블에 NOT NULL / UNIQUE 제약조건 절대 금지!** +> +> 멀티테넌시 환경에서 회사별로 필수값/유니크 규칙이 다를 수 있으므로, +> 제약조건은 DB 레벨이 아닌 **`table_type_columns`의 메타데이터(`is_nullable`, `is_unique`)로 논리적 제어**한다. +> DB에 직접 NOT NULL/UNIQUE/CHECK/FOREIGN KEY를 걸면 멀티테넌시가 깨진다. +> +> **허용**: `id` PRIMARY KEY, `DEFAULT` 값만 DB 레벨 설정 +> **금지**: 비즈니스 컬럼에 `NOT NULL`, `UNIQUE`, `CHECK`, `FOREIGN KEY` + +```sql +CREATE TABLE "{테이블명}" ( + "id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text, + "created_date" timestamp DEFAULT now(), + "updated_date" timestamp DEFAULT now(), + "writer" varchar(500) DEFAULT NULL, + "company_code" varchar(500), + -- 모든 비즈니스 컬럼은 varchar(500), NOT NULL/UNIQUE 제약조건 금지 +); +``` + +### 2.2 table_labels + +| 컬럼 | 타입 | 설명 | +|------|------|------| +| table_name | varchar PK | 테이블명 | +| table_label | varchar | 한글 라벨 | +| description | text | 설명 | +| use_log_table | varchar(1) | 'Y'/'N' | + +### 2.3 table_type_columns + +| 컬럼 | 타입 | 설명 | +|------|------|------| +| id | serial PK | 자동 증가 | +| table_name | varchar | UNIQUE(+column_name+company_code) | +| column_name | varchar | 컬럼명 | +| company_code | varchar | `'*'` = 전체 공통 | +| input_type | varchar | text/number/date/code/entity/select/checkbox/radio/textarea/category/numbering | +| detail_settings | text | JSON (code/entity/select 상세) | +| is_nullable | varchar | `'Y'`/`'N'` (논리적 필수값 제어) | +| display_order | integer | -5~-1: 기본, 0~: 비즈니스 | +| column_label | varchar | 컬럼 한글 라벨 | +| description | text | 컬럼 설명 | +| is_visible | boolean | 화면 표시 여부 (기본 true) | +| code_category | varchar | input_type=code일 때 코드 카테고리 | +| code_value | varchar | 코드 값 | +| reference_table | varchar | input_type=entity일 때 참조 테이블 | +| reference_column | varchar | 참조 컬럼 | +| display_column | varchar | 참조 표시 컬럼 | +| is_unique | varchar | `'Y'`/`'N'` (논리적 유니크 제어) | +| category_ref | varchar | 카테고리 참조 | + +### 2.4 screen_definitions + +| 컬럼 | 타입 | 설명 | +|------|------|------| +| screen_id | serial PK | 자동 증가 | +| screen_name | varchar NOT NULL | 화면명 | +| screen_code | varchar | **조건부 UNIQUE** (`WHERE is_active <> 'D'`) | +| table_name | varchar | 메인 테이블명 | +| company_code | varchar NOT NULL | 회사 코드 | +| description | text | 화면 설명 | +| is_active | char(1) | `'Y'`/`'N'`/`'D'` (D=삭제) | +| layout_metadata | jsonb | 레이아웃 메타데이터 | +| created_date | timestamp | 생성일시 | +| created_by | varchar | 생성자 | +| updated_date | timestamp | 수정일시 | +| updated_by | varchar | 수정자 | +| deleted_date | timestamp | 삭제일시 | +| deleted_by | varchar | 삭제자 | +| delete_reason | text | 삭제 사유 | +| db_source_type | varchar | `'internal'` (기본) / `'external'` | +| db_connection_id | integer | 외부 DB 연결 ID | +| data_source_type | varchar | `'database'` (기본) / `'rest_api'` | +| rest_api_connection_id | integer | REST API 연결 ID | +| rest_api_endpoint | varchar | REST API 엔드포인트 | +| rest_api_json_path | varchar | JSON 응답 경로 (기본 `'data'`) | +| source_screen_id | integer | 원본 화면 ID (복사본일 때) | + +> **screen_code UNIQUE 주의**: `is_active = 'D'`(삭제)인 화면은 UNIQUE 대상에서 제외된다. 삭제된 화면과 같은 코드로 새 화면을 만들 수 있지만, 활성 상태(`'Y'`/`'N'`)에서는 중복 불가. + +### 2.5 screen_layouts_v2 + +| 컬럼 | 타입 | 설명 | +|------|------|------| +| layout_id | serial PK | 자동 증가 | +| screen_id | integer FK | UNIQUE(+company_code+layer_id) | +| company_code | varchar NOT NULL | 회사 코드 | +| layout_data | jsonb NOT NULL | 전체 레이아웃 JSON (기본 `'{}'`) | +| created_at | timestamptz | 생성일시 | +| updated_at | timestamptz | 수정일시 | +| layer_id | integer | 1=기본 레이어 (기본값 1) | +| layer_name | varchar | 레이어명 (기본 `'기본 레이어'`) | +| condition_config | jsonb | 레이어 조건부 표시 설정 | + +### 2.6 menu_info + +| 컬럼 | 타입 | 설명 | +|------|------|------| +| objid | numeric PK | BIGINT 고유값 | +| menu_type | numeric | 0=화면, 1=폴더 | +| parent_obj_id | numeric | 부모 메뉴 objid | +| menu_name_kor | varchar | 메뉴명 (한글) | +| menu_name_eng | varchar | 메뉴명 (영문) | +| seq | numeric | 정렬 순서 | +| menu_url | varchar | `/screen/{screen_code}` | +| menu_desc | varchar | 메뉴 설명 | +| writer | varchar | 작성자 | +| regdate | timestamp | 등록일시 | +| status | varchar | 상태 (`'active'` 등) | +| company_code | varchar | 회사 코드 (기본 `'*'`) | +| screen_code | varchar | 연결 화면 코드 | +| system_name | varchar | 시스템명 | +| lang_key | varchar | 다국어 키 | +| lang_key_desc | varchar | 다국어 설명 키 | +| menu_code | varchar | 메뉴 코드 | +| source_menu_objid | bigint | 원본 메뉴 objid (복사본일 때) | +| screen_group_id | integer | 화면 그룹 ID | +| menu_icon | varchar | 메뉴 아이콘 | + +--- + +## 3. 컴포넌트 전체 설정 레퍼런스 (32개) + +> 아래 설정은 layout_data JSON의 각 컴포넌트 `overrides` 안에 들어가는 값이다. +> 기본값과 다른 부분만 overrides에 지정하면 된다. + +--- + +### 3.1 v2-table-list (데이터 테이블) + +**용도**: DB 테이블 데이터를 테이블/카드 형태로 조회/편집. 가장 핵심적인 컴포넌트. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| tableName | string | - | 조회할 DB 테이블명 | +| selectedTable | string | - | tableName 별칭 | +| displayMode | `"table"\|"card"` | `"table"` | 테이블 모드 또는 카드 모드 | +| autoLoad | boolean | `true` | 화면 로드 시 자동으로 데이터 조회 | +| isReadOnly | boolean | false | 읽기 전용 (편집 불가) | +| columns | ColumnConfig[] | `[]` | 표시할 컬럼 설정 배열 | +| title | string | - | 테이블 상단 제목 | +| showHeader | boolean | `true` | 테이블 헤더 행 표시 | +| showFooter | boolean | `true` | 테이블 푸터 표시 | +| height | string | `"auto"` | 높이 모드 (`"auto"`, `"fixed"`, `"viewport"`) | +| fixedHeight | number | - | height="fixed"일 때 고정 높이(px) | +| autoWidth | boolean | `true` | 컬럼 너비 자동 계산 | +| stickyHeader | boolean | `false` | 스크롤 시 헤더 고정 | + +**checkbox (체크박스 설정)**: + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| enabled | boolean | `true` | 체크박스 사용 여부 | +| multiple | boolean | `true` | 다중 선택 허용 | +| position | `"left"\|"right"` | `"left"` | 체크박스 위치 | +| selectAll | boolean | `true` | 전체 선택 버튼 표시 | + +**pagination (페이지네이션)**: + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| enabled | boolean | `true` | 페이지네이션 사용 | +| pageSize | number | `20` | 한 페이지당 행 수 | +| showSizeSelector | boolean | `true` | 페이지 크기 변경 드롭다운 | +| showPageInfo | boolean | `true` | "1-20 / 100건" 같은 정보 표시 | +| pageSizeOptions | number[] | `[10,20,50,100]` | 선택 가능한 페이지 크기 | + +**horizontalScroll (가로 스크롤)**: + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| enabled | boolean | `true` | 가로 스크롤 사용 | +| maxVisibleColumns | number | `8` | 스크롤 없이 보이는 최대 컬럼 수 | +| minColumnWidth | number | `100` | 컬럼 최소 너비(px) | +| maxColumnWidth | number | `300` | 컬럼 최대 너비(px) | + +**tableStyle (스타일)**: + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| theme | string | `"default"` | 테마 (`default`/`striped`/`bordered`/`minimal`) | +| headerStyle | string | `"default"` | 헤더 스타일 (`default`/`dark`/`light`) | +| rowHeight | string | `"normal"` | 행 높이 (`compact`/`normal`/`comfortable`) | +| alternateRows | boolean | `true` | 짝수/홀수 행 색상 교차 | +| hoverEffect | boolean | `true` | 마우스 호버 시 행 강조 | +| borderStyle | string | `"light"` | 테두리 (`none`/`light`/`heavy`) | + +**toolbar (툴바 버튼)**: + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| showEditMode | boolean | `false` | 즉시저장/배치저장 모드 전환 버튼 | +| showExcel | boolean | `false` | Excel 내보내기 버튼 | +| showPdf | boolean | `false` | PDF 내보내기 버튼 | +| showSearch | boolean | `false` | 테이블 내 검색 | +| showRefresh | boolean | `false` | 상단 새로고침 버튼 | +| showPaginationRefresh | boolean | `true` | 하단 새로고침 버튼 | + +**filter (필터)**: + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| enabled | boolean | `true` | 필터 기능 사용 | +| filters | array | `[]` | 사전 정의 필터 목록 | + +**ColumnConfig (columns 배열 요소)**: + +| 설정 | 타입 | 설명 | +|------|------|------| +| columnName | string | DB 컬럼명 | +| displayName | string | 화면 표시명 | +| visible | boolean | 표시 여부 | +| sortable | boolean | 정렬 가능 여부 | +| searchable | boolean | 검색 가능 여부 | +| editable | boolean | 인라인 편집 가능 여부 | +| width | number | 컬럼 너비(px) | +| align | `"left"\|"center"\|"right"` | 텍스트 정렬 | +| format | string | 포맷 (`text`/`number`/`date`/`currency`/`boolean`) | +| hidden | boolean | 숨김 (데이터는 로드하되 표시 안 함) | +| fixed | `"left"\|"right"\|false` | 컬럼 고정 위치 | +| thousandSeparator | boolean | 숫자 천 단위 콤마 | +| isEntityJoin | boolean | 엔티티 조인 사용 여부 | +| entityJoinInfo | object | 조인 정보 (`sourceTable`, `sourceColumn`, `referenceTable`, `joinAlias`) | + +**cardConfig (displayMode="card"일 때)**: + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| idColumn | string | `"id"` | ID 컬럼 | +| titleColumn | string | `"name"` | 카드 제목 컬럼 | +| subtitleColumn | string | - | 부제목 컬럼 | +| descriptionColumn | string | - | 설명 컬럼 | +| imageColumn | string | - | 이미지 URL 컬럼 | +| cardsPerRow | number | `3` | 행당 카드 수 | +| cardSpacing | number | `16` | 카드 간격(px) | +| showActions | boolean | `true` | 카드 액션 버튼 표시 | + +--- + +### 3.2 v2-split-panel-layout (마스터-디테일 분할) + +**용도**: 좌측 마스터 테이블 선택 → 우측 디테일 테이블 연동. 가장 복잡한 컴포넌트. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| splitRatio | number | `30` | 좌측 패널 비율(0~100) | +| resizable | boolean | `true` | 사용자가 분할선 드래그로 비율 변경 가능 | +| minLeftWidth | number | `200` | 좌측 최소 너비(px) | +| minRightWidth | number | `300` | 우측 최소 너비(px) | +| autoLoad | boolean | `true` | 화면 로드 시 자동 데이터 조회 | +| syncSelection | boolean | `true` | 좌측 선택 시 우측 자동 갱신 | + +**leftPanel / rightPanel 공통 설정**: + +| 설정 | 타입 | 설명 | +|------|------|------| +| title | string | 패널 제목 | +| tableName | string | DB 테이블명 | +| displayMode | `"list"\|"table"\|"custom"` | `list`: 리스트, `table`: 테이블, `custom`: 자유 배치 | +| columns | array | 컬럼 설정 (`name`, `label`, `width`, `sortable`, `align`, `isEntityJoin`, `joinInfo`) | +| showSearch | boolean | 패널 내 검색 바 표시 | +| showAdd | boolean | 추가 버튼 표시 | +| showEdit | boolean | 수정 버튼 표시 | +| showDelete | boolean | 삭제 버튼 표시 | +| addButton | object | `{ enabled, mode("auto"/"modal"), modalScreenId }` | +| editButton | object | `{ enabled, mode("auto"/"modal"), modalScreenId, buttonLabel }` | +| deleteButton | object | `{ enabled, buttonLabel, confirmMessage }` | +| addModalColumns | array | 추가 모달 전용 컬럼 (`name`, `label`, `required`) | +| dataFilter | object | `{ enabled, filters, matchType("all"/"any") }` | +| tableConfig | object | `{ showCheckbox, showRowNumber, rowHeight, headerHeight, striped, bordered, hoverable, stickyHeader }` | +| components | array | displayMode="custom"일 때 내부 컴포넌트 배열 | + +**rightPanel 전용 설정**: + +| 설정 | 타입 | 설명 | +|------|------|------| +| relation | object | 마스터-디테일 연결 관계 | +| relation.type | `"detail"\|"join"` | detail: FK 관계, join: 테이블 JOIN | +| relation.leftColumn | string | 좌측(마스터) 연결 컬럼 (보통 `"id"`) | +| relation.rightColumn | string | 우측(디테일) 연결 컬럼 (FK) | +| relation.foreignKey | string | FK 컬럼명 (rightColumn과 동일) | +| relation.keys | array | 복합키 `[{ leftColumn, rightColumn }]` | +| additionalTabs | array | 우측 패널에 탭 추가 (각 탭은 rightPanel과 동일 구조 + `tabId`, `label`) | +| addConfig | object | `{ targetTable, autoFillColumns, leftPanelColumn, targetColumn }` | +| deduplication | object | `{ enabled, groupByColumn, keepStrategy, sortColumn }` | +| summaryColumnCount | number | 요약 표시 컬럼 수 | + +--- + +### 3.3 v2-table-search-widget (검색 바) + +**용도**: 테이블 상단에 배치하여 검색/필터 기능 제공. 대상 테이블 컬럼을 자동 감지. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| autoSelectFirstTable | boolean | `true` | 화면 내 첫 번째 테이블 자동 연결 | +| showTableSelector | boolean | `true` | 테이블 선택 드롭다운 표시 | +| title | string | `"테이블 검색"` | 검색 바 제목 | +| filterMode | `"dynamic"\|"preset"` | `"dynamic"` | dynamic: 자동 필터, preset: 고정 필터 | +| presetFilters | array | `[]` | 고정 필터 목록 (`{ columnName, columnLabel, filterType, width }`) | +| targetPanelPosition | `"left"\|"right"\|"auto"` | `"left"` | split-panel에서 대상 패널 위치 | + +--- + +### 3.4 v2-input (텍스트/숫자 입력) + +**용도**: 텍스트, 숫자, 비밀번호, textarea, 슬라이더, 컬러, 버튼 등 단일 값 입력. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| inputType | string | `"text"` | 입력 유형: `text`/`number`/`password`/`slider`/`color`/`button`/`textarea` | +| format | string | `"none"` | 포맷 검증: `none`/`email`/`tel`/`url`/`currency`/`biz_no` | +| placeholder | string | `""` | 입력 힌트 텍스트 | +| required | boolean | `false` | 필수 입력 표시 | +| readonly | boolean | `false` | 읽기 전용 | +| disabled | boolean | `false` | 비활성화 | +| maxLength | number | - | 최대 입력 글자 수 | +| minLength | number | - | 최소 입력 글자 수 | +| pattern | string | - | 정규식 패턴 검증 | +| showCounter | boolean | `false` | 글자 수 카운터 표시 | +| min | number | - | 최소값 (number/slider) | +| max | number | - | 최대값 (number/slider) | +| step | number | - | 증감 단위 (number/slider) | +| buttonText | string | - | 버튼 텍스트 (inputType=button) | +| tableName | string | - | 바인딩 테이블명 | +| columnName | string | - | 바인딩 컬럼명 | + +--- + +### 3.5 v2-select (선택) + +**용도**: 드롭다운, 콤보박스, 라디오, 체크박스, 태그, 토글 등 선택형 입력. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| mode | string | `"dropdown"` | 선택 모드: `dropdown`/`combobox`/`radio`/`check`/`tag`/`tagbox`/`toggle`/`swap` | +| source | string | `"distinct"` | 데이터 소스: `static`/`code`/`db`/`api`/`entity`/`category`/`distinct`/`select` | +| options | array | `[]` | source=static일 때 옵션 목록 `[{ label, value }]` | +| codeGroup | string | - | source=code일 때 코드 그룹 | +| codeCategory | string | - | source=code일 때 코드 카테고리 | +| table | string | - | source=db일 때 테이블명 | +| valueColumn | string | - | source=db일 때 값 컬럼 | +| labelColumn | string | - | source=db일 때 표시 컬럼 | +| entityTable | string | - | source=entity일 때 엔티티 테이블 | +| entityValueField | string | - | source=entity일 때 값 필드 | +| entityLabelField | string | - | source=entity일 때 표시 필드 | +| searchable | boolean | `true` | 검색 가능 (combobox에서 기본 활성) | +| multiple | boolean | `false` | 다중 선택 허용 | +| maxSelect | number | - | 최대 선택 수 | +| allowClear | boolean | - | 선택 해제 허용 | +| placeholder | string | `"선택하세요"` | 힌트 텍스트 | +| required | boolean | `false` | 필수 선택 | +| readonly | boolean | `false` | 읽기 전용 | +| disabled | boolean | `false` | 비활성화 | +| cascading | object | - | 연쇄 선택 (상위 select 값에 따라 하위 옵션 변경) | +| hierarchical | boolean | - | 계층 구조 (부모-자식 관계) | +| parentField | string | - | 부모 필드명 | + +--- + +### 3.6 v2-date (날짜) + +**용도**: 날짜, 시간, 날짜시간, 날짜범위, 월, 연도 입력. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| dateType | string | `"date"` | 날짜 유형: `date`/`datetime`/`time`/`daterange`/`month`/`year` | +| format | string | `"YYYY-MM-DD"` | 표시/저장 형식 | +| placeholder | string | `"날짜 선택"` | 힌트 텍스트 | +| required | boolean | `false` | 필수 입력 | +| readonly | boolean | `false` | 읽기 전용 | +| disabled | boolean | `false` | 비활성화 | +| showTime | boolean | `false` | 시간 선택 표시 (datetime) | +| use24Hours | boolean | `true` | 24시간 형식 | +| range | boolean | - | 범위 선택 (시작~종료) | +| minDate | string | - | 선택 가능 최소 날짜 (ISO 8601) | +| maxDate | string | - | 선택 가능 최대 날짜 | +| showToday | boolean | - | 오늘 버튼 표시 | + +--- + +### 3.7 v2-button-primary (액션 버튼) + +**용도**: 저장, 삭제, 조회, 커스텀 등 액션 버튼. 제어관리(dataflow)와 연결 가능. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| text | string | `"저장"` | 버튼 텍스트 | +| actionType | string | `"button"` | 버튼 타입: `button`/`submit`/`reset` | +| variant | string | `"primary"` | 스타일: `primary`/`secondary`/`danger` | +| size | string | `"md"` | 크기: `sm`/`md`/`lg` | +| disabled | boolean | `false` | 비활성화 | +| action | object | - | 액션 설정 | +| action.type | string | `"save"` | 액션 유형: `save`/`delete`/`edit`/`copy`/`navigate`/`modal`/`control`/`custom` | +| action.successMessage | string | `"저장되었습니다."` | 성공 시 토스트 메시지 | +| action.errorMessage | string | `"오류가 발생했습니다."` | 실패 시 토스트 메시지 | +| webTypeConfig | object | - | 제어관리 연결 설정 | +| webTypeConfig.enableDataflowControl | boolean | - | 제어관리 활성화 | +| webTypeConfig.dataflowConfig | object | - | 제어관리 설정 | +| webTypeConfig.dataflowConfig.controlMode | string | - | `"relationship"`/`"flow"`/`"none"` | +| webTypeConfig.dataflowConfig.relationshipConfig | object | - | `{ relationshipId, executionTiming("before"/"after"/"replace") }` | +| webTypeConfig.dataflowConfig.flowConfig | object | - | `{ flowId, executionTiming }` | + +--- + +### 3.8 v2-table-grouped (그룹화 테이블) + +**용도**: 특정 컬럼 기준으로 데이터를 그룹화. 그룹별 접기/펼치기, 집계 표시. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| selectedTable | string | `""` | DB 테이블명 | +| columns | array | `[]` | 컬럼 설정 (v2-table-list와 동일) | +| showCheckbox | boolean | `false` | 체크박스 표시 | +| checkboxMode | `"single"\|"multi"` | `"multi"` | 체크박스 모드 | +| isReadOnly | boolean | `false` | 읽기 전용 | +| rowClickable | boolean | `true` | 행 클릭 가능 | +| showExpandAllButton | boolean | `true` | 전체 펼치기/접기 버튼 | +| groupHeaderStyle | string | `"default"` | 그룹 헤더 스타일 (`default`/`compact`/`card`) | +| emptyMessage | string | `"데이터가 없습니다."` | 빈 데이터 메시지 | +| height | string\|number | `"auto"` | 높이 | +| maxHeight | number | `600` | 최대 높이(px) | +| pagination.enabled | boolean | `false` | 페이지네이션 사용 | +| pagination.pageSize | number | `10` | 페이지 크기 | + +**groupConfig (그룹화 설정)**: + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| groupByColumn | string | `""` | **필수**. 그룹화 기준 컬럼 | +| groupLabelFormat | string | `"{value}"` | 그룹 라벨 포맷 | +| defaultExpanded | boolean | `true` | 초기 펼침 여부 | +| sortDirection | `"asc"\|"desc"` | `"asc"` | 그룹 정렬 방향 | +| summary.showCount | boolean | `true` | 그룹별 건수 표시 | +| summary.sumColumns | string[] | `[]` | 합계 표시할 컬럼 목록 | +| summary.avgColumns | string[] | - | 평균 표시 컬럼 | +| summary.maxColumns | string[] | - | 최대값 표시 컬럼 | +| summary.minColumns | string[] | - | 최소값 표시 컬럼 | + +--- + +### 3.9 v2-pivot-grid (피벗 분석) + +**용도**: 다차원 데이터 분석. 행/열/데이터/필터 영역에 필드를 배치하여 집계. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| fields | array | `[]` | **필수**. 피벗 필드 배열 | +| dataSource | object | - | 데이터 소스 (`type`, `tableName`, `joinConfigs`, `filterConditions`) | +| allowSortingBySummary | boolean | - | 집계값 기준 정렬 허용 | +| allowFiltering | boolean | - | 필터링 허용 | +| allowExpandAll | boolean | - | 전체 확장/축소 허용 | +| wordWrapEnabled | boolean | - | 텍스트 줄바꿈 | +| height | string\|number | - | 높이 | +| totals.showRowGrandTotals | boolean | - | 행 총합계 표시 | +| totals.showColumnGrandTotals | boolean | - | 열 총합계 표시 | +| chart.enabled | boolean | - | 차트 연동 표시 | +| chart.type | string | - | 차트 타입 (`bar`/`line`/`area`/`pie`/`stackedBar`) | + +**fields 배열 요소**: + +| 설정 | 타입 | 설명 | +|------|------|------| +| field | string | DB 컬럼명 | +| caption | string | 표시 라벨 | +| area | `"row"\|"column"\|"data"\|"filter"` | **필수**. 배치 영역 | +| summaryType | string | area=data일 때: `sum`/`count`/`avg`/`min`/`max`/`countDistinct` | +| groupInterval | string | 날짜 그룹화: `year`/`quarter`/`month`/`week`/`day` | +| sortBy | string | 정렬 기준: `value`/`caption` | +| sortOrder | string | 정렬 방향: `asc`/`desc`/`none` | + +--- + +### 3.10 v2-card-display (카드 뷰) + +**용도**: 테이블 데이터를 카드 형태로 표시. 이미지+제목+설명 구조. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| dataSource | string | `"table"` | 데이터 소스: `table`/`static` | +| tableName | string | - | DB 테이블명 | +| cardsPerRow | number | `3` | 행당 카드 수 (1~6) | +| cardSpacing | number | `16` | 카드 간격(px) | +| columnMapping | object | `{}` | 필드 매핑 (`title`, `subtitle`, `description`, `image`, `status`) | +| cardStyle.showTitle | boolean | `true` | 제목 표시 | +| cardStyle.showSubtitle | boolean | `true` | 부제목 표시 | +| cardStyle.showDescription | boolean | `true` | 설명 표시 | +| cardStyle.showImage | boolean | `false` | 이미지 표시 | +| cardStyle.showActions | boolean | `true` | 액션 버튼 표시 | + +--- + +### 3.11 v2-timeline-scheduler (간트차트) + +**용도**: 시간축 기반 일정/계획 시각화. 드래그/리사이즈로 일정 편집. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| selectedTable | string | - | 스케줄 데이터 테이블 | +| resourceTable | string | `"equipment_mng"` | 리소스(설비/작업자) 테이블 | +| scheduleType | string | `"PRODUCTION"` | 스케줄 유형: `PRODUCTION`/`MAINTENANCE`/`SHIPPING`/`WORK_ASSIGN` | +| defaultZoomLevel | string | `"day"` | 초기 줌: `day`/`week`/`month` | +| editable | boolean | `true` | 편집 가능 | +| draggable | boolean | `true` | 드래그 이동 허용 | +| resizable | boolean | `true` | 기간 리사이즈 허용 | +| rowHeight | number | `50` | 행 높이(px) | +| headerHeight | number | `60` | 헤더 높이(px) | +| resourceColumnWidth | number | `150` | 리소스 컬럼 너비(px) | +| cellWidth.day | number | `60` | 일 단위 셀 너비 | +| cellWidth.week | number | `120` | 주 단위 셀 너비 | +| cellWidth.month | number | `40` | 월 단위 셀 너비 | +| showConflicts | boolean | `true` | 시간 겹침 충돌 표시 | +| showProgress | boolean | `true` | 진행률 바 표시 | +| showTodayLine | boolean | `true` | 오늘 날짜 표시선 | +| showToolbar | boolean | `true` | 상단 툴바 표시 | +| showAddButton | boolean | `true` | 추가 버튼 | +| height | number | `500` | 높이(px) | + +**fieldMapping (필수)**: + +| 설정 | 기본값 | 설명 | +|------|--------|------| +| id | `"schedule_id"` | 스케줄 PK 필드 | +| resourceId | `"resource_id"` | 리소스 FK 필드 | +| title | `"schedule_name"` | 제목 필드 | +| startDate | `"start_date"` | 시작일 필드 | +| endDate | `"end_date"` | 종료일 필드 | +| status | - | 상태 필드 | +| progress | - | 진행률 필드 (0~100) | + +**resourceFieldMapping**: + +| 설정 | 기본값 | 설명 | +|------|--------|------| +| id | `"equipment_code"` | 리소스 PK | +| name | `"equipment_name"` | 리소스 표시명 | +| group | - | 리소스 그룹 | + +**statusColors (상태별 색상)**: + +| 상태 | 기본 색상 | +|------|----------| +| planned | `"#3b82f6"` (파랑) | +| in_progress | `"#f59e0b"` (주황) | +| completed | `"#10b981"` (초록) | +| delayed | `"#ef4444"` (빨강) | +| cancelled | `"#6b7280"` (회색) | + +--- + +### 3.12 v2-tabs-widget (탭) + +**용도**: 탭 전환. 각 탭 내부에 컴포넌트 배치 가능. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| tabs | array | `[{id:"tab-1",label:"탭1",...}]` | 탭 배열 | +| defaultTab | string | `"tab-1"` | 기본 활성 탭 ID | +| orientation | string | `"horizontal"` | 탭 방향: `horizontal`/`vertical` | +| variant | string | `"default"` | 스타일: `default`/`pills`/`underline` | +| allowCloseable | boolean | `false` | 탭 닫기 버튼 표시 | +| persistSelection | boolean | `false` | 탭 선택 상태 localStorage 저장 | + +**tabs 배열 요소**: `{ id, label, order, disabled, icon, components[] }` +**components 요소**: `{ id, componentType, label, position, size, componentConfig }` + +--- + +### 3.13 v2-aggregation-widget (집계 카드) + +**용도**: 합계, 평균, 개수 등 집계값을 카드 형태로 표시. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| dataSourceType | string | `"table"` | 데이터 소스: `table`/`component`/`selection` | +| tableName | string | - | 테이블명 | +| items | array | `[]` | 집계 항목 배열 | +| layout | string | `"horizontal"` | 배치: `horizontal`/`vertical` | +| showLabels | boolean | `true` | 라벨 표시 | +| showIcons | boolean | `true` | 아이콘 표시 | +| gap | string | `"16px"` | 항목 간격 | +| autoRefresh | boolean | `false` | 자동 새로고침 | +| refreshOnFormChange | boolean | `true` | 폼 변경 시 새로고침 | + +**items 요소**: `{ id, columnName, columnLabel, type("sum"/"avg"/"count"/"max"/"min"), format, decimalPlaces, prefix, suffix }` + +--- + +### 3.14 v2-status-count (상태별 건수) + +**용도**: 상태별 건수를 카드 형태로 표시. 대시보드/현황 화면용. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| title | string | `"상태 현황"` | 제목 | +| tableName | string | `""` | 대상 테이블 | +| statusColumn | string | `"status"` | 상태 컬럼명 | +| relationColumn | string | `""` | 관계 컬럼 (필터용) | +| items | array | - | 상태 항목 `[{ value, label, color }]` | +| showTotal | boolean | - | 합계 표시 | +| cardSize | string | `"md"` | 카드 크기: `sm`/`md`/`lg` | + +--- + +### 3.15 v2-text-display (텍스트 표시) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| text | string | `"텍스트를 입력하세요"` | 표시 텍스트 | +| fontSize | string | `"14px"` | 폰트 크기 | +| fontWeight | string | `"normal"` | 폰트 굵기 | +| color | string | `"#212121"` | 텍스트 색상 | +| textAlign | string | `"left"` | 정렬: `left`/`center`/`right` | +| backgroundColor | string | - | 배경색 | +| padding | string | - | 패딩 | + +--- + +### 3.16 v2-numbering-rule (자동 채번) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| ruleConfig | object | - | 채번 규칙 설정 | +| maxRules | number | `6` | 최대 파트 수 | +| readonly | boolean | `false` | 읽기 전용 | +| showPreview | boolean | `true` | 미리보기 표시 | +| showRuleList | boolean | `true` | 규칙 목록 표시 | +| cardLayout | string | `"vertical"` | 레이아웃: `vertical`/`horizontal` | + +--- + +### 3.17 v2-file-upload (파일 업로드) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| placeholder | string | `"파일을 선택하세요"` | 힌트 텍스트 | +| multiple | boolean | `true` | 다중 업로드 | +| accept | string | `"*/*"` | 허용 파일 형식 (예: `"image/*"`, `".pdf,.xlsx"`) | +| maxSize | number | `10485760` | 최대 파일 크기(bytes, 기본 10MB) | +| maxFiles | number | - | 최대 파일 수 | +| showPreview | boolean | - | 미리보기 표시 | +| showFileList | boolean | - | 파일 목록 표시 | +| allowDelete | boolean | - | 삭제 허용 | +| allowDownload | boolean | - | 다운로드 허용 | + +--- + +### 3.18 v2-section-card (그룹 컨테이너 - 테두리) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| title | string | `"섹션 제목"` | 제목 | +| description | string | `""` | 설명 | +| showHeader | boolean | `true` | 헤더 표시 | +| padding | string | `"md"` | 패딩: `none`/`sm`/`md`/`lg` | +| backgroundColor | string | `"default"` | 배경: `default`/`muted`/`transparent` | +| borderStyle | string | `"solid"` | 테두리: `solid`/`dashed`/`none` | +| collapsible | boolean | `false` | 접기/펼치기 가능 | +| defaultOpen | boolean | `true` | 기본 펼침 | + +--- + +### 3.19 v2-section-paper (그룹 컨테이너 - 배경색) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| backgroundColor | string | `"default"` | 배경: `default`/`muted`/`accent`/`primary`/`custom` | +| customColor | string | - | custom일 때 색상 | +| showBorder | boolean | `false` | 테두리 표시 | +| padding | string | `"md"` | 패딩: `none`/`sm`/`md`/`lg` | +| roundedCorners | string | `"md"` | 모서리: `none`/`sm`/`md`/`lg` | +| shadow | string | `"none"` | 그림자: `none`/`sm`/`md` | + +--- + +### 3.20 v2-divider-line (구분선) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| orientation | string | - | 방향 (가로/세로) | +| thickness | number | - | 두께 | + +--- + +### 3.21 v2-split-line (캔버스 분할선) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| resizable | boolean | `true` | 드래그 리사이즈 허용 | +| lineColor | string | `"#e2e8f0"` | 분할선 색상 | +| lineWidth | number | `4` | 분할선 두께(px) | + +--- + +### 3.22 v2-repeat-container (반복 렌더링) + +**용도**: 데이터 수만큼 내부 컴포넌트를 반복 렌더링. 카드 리스트 등에 사용. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| dataSourceType | string | `"manual"` | 소스: `table-list`/`v2-repeater`/`externalData`/`manual` | +| dataSourceComponentId | string | - | 연결할 컴포넌트 ID | +| tableName | string | - | 테이블명 | +| layout | string | `"vertical"` | 배치: `vertical`/`horizontal`/`grid` | +| gridColumns | number | `2` | grid일 때 컬럼 수 | +| gap | string | `"16px"` | 아이템 간격 | +| showBorder | boolean | `true` | 카드 테두리 | +| showShadow | boolean | `false` | 카드 그림자 | +| borderRadius | string | `"8px"` | 모서리 둥글기 | +| backgroundColor | string | `"#ffffff"` | 배경색 | +| padding | string | `"16px"` | 패딩 | +| showItemTitle | boolean | `false` | 아이템 제목 표시 | +| itemTitleTemplate | string | `""` | 제목 템플릿 (예: `"{order_no} - {item}"`) | +| emptyMessage | string | `"데이터가 없습니다"` | 빈 상태 메시지 | +| clickable | boolean | `false` | 클릭 가능 | +| selectionMode | string | `"single"` | 선택 모드: `single`/`multiple` | +| usePaging | boolean | `false` | 페이징 사용 | +| pageSize | number | `10` | 페이지 크기 | + +--- + +### 3.23 v2-repeater (반복 데이터 관리) + +**용도**: 인라인/모달 모드로 반복 데이터(주문 상세 등) 관리. 행 추가/삭제/편집. + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| renderMode | string | `"inline"` | 모드: `inline` (인라인 편집) / `modal` (모달로 선택 추가) | +| mainTableName | string | - | 저장 대상 테이블 | +| foreignKeyColumn | string | - | 마스터 연결 FK 컬럼 | +| foreignKeySourceColumn | string | - | 마스터 PK 컬럼 | +| columns | array | `[]` | 컬럼 설정 | +| dataSource.tableName | string | - | 데이터 테이블 | +| dataSource.foreignKey | string | - | FK 컬럼 | +| dataSource.sourceTable | string | - | 모달용 소스 테이블 | +| modal.size | string | `"md"` | 모달 크기: `sm`/`md`/`lg`/`xl`/`full` | +| modal.title | string | - | 모달 제목 | +| modal.searchFields | string[] | - | 검색 필드 | +| features.showAddButton | boolean | `true` | 추가 버튼 | +| features.showDeleteButton | boolean | `true` | 삭제 버튼 | +| features.inlineEdit | boolean | `false` | 인라인 편집 | +| features.showRowNumber | boolean | `false` | 행 번호 표시 | +| calculationRules | array | - | 자동 계산 규칙 (예: 수량*단가=금액) | + +--- + +### 3.24 v2-approval-step (결재 스테퍼) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| targetTable | string | `""` | 결재 대상 테이블 | +| targetRecordIdField | string | `""` | 레코드 ID 필드 | +| displayMode | string | `"horizontal"` | 표시 방향: `horizontal`/`vertical` | +| showComment | boolean | `true` | 결재 코멘트 표시 | +| showTimestamp | boolean | `true` | 결재 시간 표시 | +| showDept | boolean | `true` | 부서 표시 | +| compact | boolean | `false` | 컴팩트 모드 | + +--- + +### 3.25 v2-bom-tree (BOM 트리) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| detailTable | string | `"bom_detail"` | BOM 디테일 테이블 | +| foreignKey | string | `"bom_id"` | BOM 마스터 FK | +| parentKey | string | `"parent_detail_id"` | 트리 부모 키 (자기참조) | + +--- + +### 3.26 v2-bom-item-editor (BOM 편집) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| detailTable | string | `"bom_detail"` | BOM 디테일 테이블 | +| sourceTable | string | `"item_info"` | 품목 소스 테이블 | +| foreignKey | string | `"bom_id"` | BOM 마스터 FK | +| parentKey | string | `"parent_detail_id"` | 트리 부모 키 | +| itemCodeField | string | `"item_number"` | 품목 코드 필드 | +| itemNameField | string | `"item_name"` | 품목명 필드 | +| itemTypeField | string | `"type"` | 품목 유형 필드 | +| itemUnitField | string | `"unit"` | 품목 단위 필드 | + +--- + +### 3.27 v2-category-manager (카테고리 관리) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| tableName | string | - | 대상 테이블 | +| columnName | string | - | 카테고리 컬럼 | +| menuObjid | number | - | 연결 메뉴 OBJID | +| viewMode | string | `"tree"` | 뷰 모드: `tree`/`list` | +| showViewModeToggle | boolean | `true` | 뷰 모드 토글 표시 | +| defaultExpandLevel | number | `1` | 기본 트리 펼침 레벨 | +| showInactiveItems | boolean | `false` | 비활성 항목 표시 | +| leftPanelWidth | number | `15` | 좌측 패널 너비 | + +--- + +### 3.28 v2-media (미디어) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| mediaType | string | `"file"` | 미디어 타입: `file`/`image`/`video`/`audio` | +| multiple | boolean | `false` | 다중 업로드 | +| preview | boolean | `true` | 미리보기 | +| maxSize | number | `10` | 최대 크기(MB) | +| accept | string | `"*/*"` | 허용 형식 | +| showFileList | boolean | `true` | 파일 목록 | +| dragDrop | boolean | `true` | 드래그앤드롭 | + +--- + +### 3.29 v2-location-swap-selector (위치 교환) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| dataSource.type | string | `"static"` | 소스: `static`/`table`/`code` | +| dataSource.tableName | string | - | 장소 테이블 | +| dataSource.valueField | string | `"location_code"` | 값 필드 | +| dataSource.labelField | string | `"location_name"` | 표시 필드 | +| dataSource.staticOptions | array | - | 정적 옵션 `[{value, label}]` | +| departureField | string | `"departure"` | 출발지 저장 필드 | +| destinationField | string | `"destination"` | 도착지 저장 필드 | +| departureLabel | string | `"출발지"` | 출발지 라벨 | +| destinationLabel | string | `"도착지"` | 도착지 라벨 | +| showSwapButton | boolean | `true` | 교환 버튼 표시 | +| variant | string | `"card"` | UI: `card`/`inline`/`minimal` | + +--- + +### 3.30 v2-rack-structure (창고 랙) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| maxConditions | number | `10` | 최대 조건 수 | +| maxRows | number | `99` | 최대 열 수 | +| maxLevels | number | `20` | 최대 단 수 | +| codePattern | string | `"{warehouseCode}-{floor}{zone}-{row:02d}-{level}"` | 위치 코드 패턴 | +| namePattern | string | `"{zone}구역-{row:02d}열-{level}단"` | 위치 이름 패턴 | +| showTemplates | boolean | `true` | 템플릿 표시 | +| showPreview | boolean | `true` | 미리보기 | +| showStatistics | boolean | `true` | 통계 카드 | +| readonly | boolean | `false` | 읽기 전용 | + +--- + +### 3.31 v2-process-work-standard (공정 작업기준) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| dataSource.itemTable | string | `"item_info"` | 품목 테이블 | +| dataSource.routingVersionTable | string | `"item_routing_version"` | 라우팅 버전 테이블 | +| dataSource.routingDetailTable | string | `"item_routing_detail"` | 라우팅 디테일 테이블 | +| dataSource.processTable | string | `"process_mng"` | 공정 테이블 | +| splitRatio | number | `30` | 좌우 분할 비율 | +| leftPanelTitle | string | `"품목 및 공정 선택"` | 좌측 패널 제목 | +| readonly | boolean | `false` | 읽기 전용 | +| itemListMode | string | `"all"` | 품목 모드: `all`/`registered` | + +--- + +### 3.32 v2-item-routing (품목 라우팅) + +| 설정 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| dataSource.itemTable | string | `"item_info"` | 품목 테이블 | +| dataSource.routingVersionTable | string | `"item_routing_version"` | 라우팅 버전 테이블 | +| dataSource.routingDetailTable | string | `"item_routing_detail"` | 라우팅 디테일 테이블 | +| dataSource.processTable | string | `"process_mng"` | 공정 테이블 | +| splitRatio | number | `40` | 좌우 분할 비율 | +| leftPanelTitle | string | `"품목 목록"` | 좌측 제목 | +| rightPanelTitle | string | `"공정 순서"` | 우측 제목 | +| readonly | boolean | `false` | 읽기 전용 | +| autoSelectFirstVersion | boolean | `true` | 첫 버전 자동 선택 | +| itemListMode | string | `"all"` | 품목 모드: `all`/`registered` | + +--- + +## 4. 패턴 의사결정 트리 + +``` +Q1. 시간축 기반 일정/간트차트? → v2-timeline-scheduler +Q2. 다차원 피벗 분석? → v2-pivot-grid +Q3. 그룹별 접기/펼치기? → v2-table-grouped +Q4. 카드 형태 표시? → v2-card-display +Q5. 마스터-디테일? + ├ 우측 멀티 탭? → v2-split-panel-layout + additionalTabs + └ 단일 디테일? → v2-split-panel-layout +Q6. 단일 테이블? → v2-table-search-widget + v2-table-list +``` + +--- + +## 5. 관계(relation) 레퍼런스 + +| 관계 유형 | 설정 | +|----------|------| +| 단순 FK | `{ type:"detail", leftColumn:"id", rightColumn:"{FK}", foreignKey:"{FK}" }` | +| 복합 키 | `{ type:"detail", keys:[{ leftColumn:"a", rightColumn:"b" }] }` | +| JOIN | `{ type:"join", leftColumn:"{col}", rightColumn:"{col}" }` | + +## 6. 엔티티 조인 + +FK 컬럼에 참조 테이블의 이름을 표시: + +**table_type_columns**: `input_type='entity'`, `detail_settings='{"referenceTable":"X","referenceColumn":"id","displayColumn":"name"}'` + +**layout_data columns**: `{ name:"fk_col", isEntityJoin:true, joinInfo:{ sourceTable:"A", sourceColumn:"fk_col", referenceTable:"X", joinAlias:"name" } }` diff --git a/docs/screen-implementation-guide/01_reference_guides/v2-component-usage-guide.md b/docs/screen-implementation-guide/01_reference_guides/v2-component-usage-guide.md new file mode 100644 index 00000000..14182a91 --- /dev/null +++ b/docs/screen-implementation-guide/01_reference_guides/v2-component-usage-guide.md @@ -0,0 +1,1146 @@ +# WACE 화면 구현 실행 가이드 (챗봇/AI 에이전트 전용) + +> **최종 업데이트**: 2026-03-13 +> **용도**: 사용자가 "수주관리 화면 만들어줘"라고 요청하면, 이 문서를 참조하여 SQL을 직접 생성하고 화면을 구현하는 AI 챗봇용 실행 가이드 +> **핵심**: 이 문서의 SQL 템플릿을 따라 INSERT하면 화면이 자동으로 생성된다 + +--- + +## 0. 절대 규칙 + +1. 사용자 업무 화면(수주, 생산, 품질 등)은 **React 코드(.tsx) 작성 금지** → DB INSERT로만 구현 +2. 모든 DB 컬럼은 **VARCHAR(500)** (날짜 컬럼만 TIMESTAMP) +3. 모든 테이블에 **기본 5개 컬럼** 필수: id, created_date, updated_date, writer, company_code +4. 모든 INSERT에 **ON CONFLICT** 절 필수 (중복 방지) +5. 컴포넌트는 반드시 **v2-** 접두사 사용 +6. **[최우선] 비즈니스 테이블 CREATE TABLE 시 NOT NULL / UNIQUE 제약조건 절대 금지!** + +> **왜 DB 레벨 제약조건을 걸면 안 되는가?** +> +> 이 시스템은 **멀티테넌시(Multi-Tenancy)** 환경이다. +> 각 회사(tenant)마다 같은 테이블을 공유하되, **필수값/유니크 규칙이 회사별로 다를 수 있다.** +> +> 따라서 제약조건은 DB에 직접 거는 것이 아니라, **관리자 메뉴에서 회사별 메타데이터**로 논리적으로 제어한다: +> - **필수값**: `table_type_columns.is_nullable = 'N'` → 애플리케이션 레벨에서 검증 +> - **유니크**: `table_type_columns.is_unique = 'Y'` → 애플리케이션 레벨에서 검증 +> +> DB 레벨에서 NOT NULL이나 UNIQUE를 걸면, **특정 회사에만 적용해야 할 규칙이 모든 회사에 강제되어** 멀티테넌시가 깨진다. +> +> **허용**: 기본 5개 컬럼의 `id` PRIMARY KEY, `DEFAULT` 값만 DB 레벨에서 설정 +> **금지**: 비즈니스 컬럼에 `NOT NULL`, `UNIQUE`, `CHECK`, `FOREIGN KEY` 등 DB 제약조건 직접 적용 + +--- + +## 1. 화면 생성 전체 파이프라인 + +사용자가 화면을 요청하면 아래 7단계를 순서대로 실행한다. + +``` +Step 1: 비즈니스 테이블 CREATE TABLE +Step 2: table_labels INSERT (테이블 라벨) +Step 3: table_type_columns INSERT (컬럼 타입 정의, company_code='*') +Step 4: column_labels INSERT (컬럼 한글 라벨) +Step 5: screen_definitions INSERT → screen_id 획득 +Step 6: screen_layouts_v2 INSERT (레이아웃 JSON) +Step 7: menu_info INSERT (메뉴 등록) +``` + +**선택적 추가 단계**: +- 채번 규칙이 필요하면: numbering_rules + numbering_rule_parts INSERT +- 카테고리가 필요하면: table_column_category_values INSERT +- 비즈니스 로직(버튼 액션)이 필요하면: dataflow_diagrams INSERT + +--- + +## 2. Step 1: 비즈니스 테이블 생성 (CREATE TABLE) + +### 템플릿 + +> **[최우선] 비즈니스 컬럼에 NOT NULL / UNIQUE / CHECK / FOREIGN KEY 제약조건 절대 금지!** +> 멀티테넌시 환경에서 회사별로 규칙이 다르므로, `table_type_columns`의 `is_nullable`, `is_unique` 메타데이터로 논리적 제어한다. + +```sql +CREATE TABLE "{테이블명}" ( + "id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text, + "created_date" timestamp DEFAULT now(), + "updated_date" timestamp DEFAULT now(), + "writer" varchar(500) DEFAULT NULL, + "company_code" varchar(500), + + "{비즈니스_컬럼1}" varchar(500), + "{비즈니스_컬럼2}" varchar(500), + "{비즈니스_컬럼3}" varchar(500) + -- NOT NULL, UNIQUE, CHECK, FOREIGN KEY 금지! +); +``` + +### 마스터-디테일인 경우 (2개 테이블) + +```sql +-- 마스터 테이블 +CREATE TABLE "{마스터_테이블}" ( + "id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text, + "created_date" timestamp DEFAULT now(), + "updated_date" timestamp DEFAULT now(), + "writer" varchar(500) DEFAULT NULL, + "company_code" varchar(500), + "{컬럼1}" varchar(500), + "{컬럼2}" varchar(500) + -- NOT NULL, UNIQUE, FOREIGN KEY 금지! +); + +-- 디테일 테이블 +CREATE TABLE "{디테일_테이블}" ( + "id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text, + "created_date" timestamp DEFAULT now(), + "updated_date" timestamp DEFAULT now(), + "writer" varchar(500) DEFAULT NULL, + "company_code" varchar(500), + "{마스터_FK}" varchar(500), -- 마스터 테이블 id 참조 (FOREIGN KEY 제약조건은 걸지 않는다!) + "{컬럼1}" varchar(500), + "{컬럼2}" varchar(500) + -- NOT NULL, UNIQUE, FOREIGN KEY 금지! +); +``` + +**금지 사항**: +- INTEGER, NUMERIC, BOOLEAN, TEXT, DATE 등 DB 타입 직접 사용 금지. 반드시 VARCHAR(500). +- 비즈니스 컬럼에 NOT NULL, UNIQUE, CHECK, FOREIGN KEY 등 DB 레벨 제약조건 금지. + +--- + +## 3. Step 2: table_labels INSERT + +```sql +INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date) +VALUES ('{테이블명}', '{한글_라벨}', '{설명}', now(), now()) +ON CONFLICT (table_name) +DO UPDATE SET table_label = EXCLUDED.table_label, description = EXCLUDED.description, updated_date = now(); +``` + +**예시**: +```sql +INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date) +VALUES ('order_master', '수주 마스터', '수주 헤더 정보 관리', now(), now()) +ON CONFLICT (table_name) +DO UPDATE SET table_label = EXCLUDED.table_label, description = EXCLUDED.description, updated_date = now(); + +INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date) +VALUES ('order_detail', '수주 상세', '수주 품목별 상세 정보', now(), now()) +ON CONFLICT (table_name) +DO UPDATE SET table_label = EXCLUDED.table_label, description = EXCLUDED.description, updated_date = now(); +``` + +--- + +## 4. Step 3: table_type_columns INSERT + +> `company_code = '*'` 로 등록한다 (전체 공통 설정). + +### 기본 5개 컬럼 (모든 테이블 공통) + +```sql +INSERT INTO table_type_columns ( + table_name, column_name, company_code, input_type, detail_settings, + is_nullable, is_unique, display_order, column_label, description, is_visible, + created_date, updated_date +) +VALUES + ('{테이블명}', 'id', '*', 'text', '{}', 'N', 'Y', -5, 'ID', '기본키 (자동생성)', false, now(), now()), + ('{테이블명}', 'created_date', '*', 'date', '{}', 'Y', 'N', -4, '생성일시', '레코드 생성일시', false, now(), now()), + ('{테이블명}', 'updated_date', '*', 'date', '{}', 'Y', 'N', -3, '수정일시', '레코드 수정일시', false, now(), now()), + ('{테이블명}', 'writer', '*', 'text', '{}', 'Y', 'N', -2, '작성자', '레코드 작성자', false, now(), now()), + ('{테이블명}', 'company_code', '*', 'text', '{}', 'Y', 'N', -1, '회사코드', '회사 구분 코드', false, now(), now()) +ON CONFLICT (table_name, column_name, company_code) +DO UPDATE SET input_type = EXCLUDED.input_type, detail_settings = EXCLUDED.detail_settings, + is_nullable = EXCLUDED.is_nullable, is_unique = EXCLUDED.is_unique, + display_order = EXCLUDED.display_order, column_label = EXCLUDED.column_label, + description = EXCLUDED.description, is_visible = EXCLUDED.is_visible, updated_date = now(); +``` + +### 비즈니스 컬럼 (display_order 0부터) + +```sql +INSERT INTO table_type_columns ( + table_name, column_name, company_code, input_type, detail_settings, + is_nullable, is_unique, display_order, column_label, description, is_visible, + created_date, updated_date +) +VALUES + ('{테이블명}', '{컬럼명}', '*', '{input_type}', '{detail_settings_json}', 'Y', 'N', {순서}, '{한글라벨}', '{설명}', true, now(), now()) +ON CONFLICT (table_name, column_name, company_code) +DO UPDATE SET input_type = EXCLUDED.input_type, detail_settings = EXCLUDED.detail_settings, + is_nullable = EXCLUDED.is_nullable, is_unique = EXCLUDED.is_unique, + display_order = EXCLUDED.display_order, column_label = EXCLUDED.column_label, + description = EXCLUDED.description, is_visible = EXCLUDED.is_visible, updated_date = now(); +``` + +### input_type 선택 기준 + +| 데이터 성격 | input_type | detail_settings 예시 | +|------------|-----------|---------------------| +| 일반 텍스트 | `text` | `'{}'` | +| 숫자 (수량, 금액) | `number` | `'{}'` | +| 날짜 | `date` | `'{}'` | +| 여러 줄 텍스트 (비고) | `textarea` | `'{}'` | +| 공통코드 선택 (상태 등) | `code` | `'{"codeCategory":"STATUS_CODE"}'` | +| 다른 테이블 참조 (거래처 등) | `entity` | `'{"referenceTable":"customer_info","referenceColumn":"id","displayColumn":"customer_name"}'` | +| 정적 옵션 선택 | `select` | `'{"options":[{"label":"옵션1","value":"v1"},{"label":"옵션2","value":"v2"}]}'` | +| 체크박스 | `checkbox` | `'{}'` | +| 라디오 | `radio` | `'{}'` | +| 카테고리 | `category` | `'{"categoryRef":"CAT_ID"}'` | +| 자동 채번 | `numbering` | `'{"numberingRuleId":"rule_id"}'` | + +--- + +## 5. Step 4: column_labels INSERT + +> 레거시 호환용이지만 **필수 등록**이다. table_type_columns와 동일한 값을 넣되, `column_label`(한글명)을 추가. +> +> **주의**: `column_labels` 테이블의 UNIQUE 제약조건은 `(table_name, column_name, company_code)` 3개 컬럼이다. 반드시 `company_code`를 포함해야 한다. + +```sql +-- 기본 5개 컬럼 +INSERT INTO column_labels (table_name, column_name, column_label, input_type, detail_settings, description, display_order, is_visible, company_code, created_date, updated_date) +VALUES + ('{테이블명}', 'id', 'ID', 'text', '{}', '기본키 (자동생성)', -5, true, '*', now(), now()), + ('{테이블명}', 'created_date', '생성일시', 'date', '{}', '레코드 생성일시', -4, true, '*', now(), now()), + ('{테이블명}', 'updated_date', '수정일시', 'date', '{}', '레코드 수정일시', -3, true, '*', now(), now()), + ('{테이블명}', 'writer', '작성자', 'text', '{}', '레코드 작성자', -2, true, '*', now(), now()), + ('{테이블명}', 'company_code', '회사코드', 'text', '{}', '회사 구분 코드', -1, true, '*', now(), now()) +ON CONFLICT (table_name, column_name, company_code) +DO UPDATE SET column_label = EXCLUDED.column_label, input_type = EXCLUDED.input_type, + detail_settings = EXCLUDED.detail_settings, description = EXCLUDED.description, + display_order = EXCLUDED.display_order, is_visible = EXCLUDED.is_visible, updated_date = now(); + +-- 비즈니스 컬럼 +INSERT INTO column_labels (table_name, column_name, column_label, input_type, detail_settings, description, display_order, is_visible, company_code, created_date, updated_date) +VALUES + ('{테이블명}', '{컬럼명}', '{한글라벨}', '{input_type}', '{detail_settings}', '{설명}', {순서}, true, '*', now(), now()) +ON CONFLICT (table_name, column_name, company_code) +DO UPDATE SET column_label = EXCLUDED.column_label, input_type = EXCLUDED.input_type, + detail_settings = EXCLUDED.detail_settings, description = EXCLUDED.description, + display_order = EXCLUDED.display_order, is_visible = EXCLUDED.is_visible, updated_date = now(); +``` + +--- + +## 6. Step 5: screen_definitions INSERT + +```sql +INSERT INTO screen_definitions ( + screen_name, screen_code, table_name, company_code, description, is_active, + db_source_type, data_source_type, created_date +) +VALUES ( + '{화면명}', -- 예: '수주관리' + '{screen_code}', -- 예: 'COMPANY_A_ORDER_MNG' (회사코드_식별자) + '{메인_테이블명}', -- 예: 'order_master' + '{company_code}', -- 예: 'COMPANY_A' + '{설명}', + 'Y', + 'internal', + 'database', + now() +) +RETURNING screen_id; +``` + +**screen_code 규칙**: `{company_code}_{영문식별자}` (예: `ILSHIN_ORDER_MNG`, `COMPANY_19_ITEM_INFO`) + +**중요**: Step 6, 7에서 `screen_id`가 필요하다. 서브쿼리로 참조하면 하드코딩 실수를 방지할 수 있다: +```sql +(SELECT screen_id FROM screen_definitions WHERE screen_code = '{screen_code}') +``` + +> **screen_code 조건부 UNIQUE 규칙**: +> `screen_code`는 단순 UNIQUE가 아니라 **`WHERE is_active <> 'D'`** 조건부 UNIQUE이다. +> - 삭제된 화면(`is_active = 'D'`)과 동일한 코드로 새 화면을 만들 수 있다. +> - 활성 상태(`'Y'` 또는 `'N'`)에서는 같은 `screen_code`가 중복되면 에러가 발생한다. +> - 화면 삭제 시 `DELETE`가 아닌 `UPDATE SET is_active = 'D'`로 소프트 삭제하므로, 이전 코드의 재사용이 가능하다. + +--- + +## 7. Step 6: screen_layouts_v2 INSERT (핵심) + +### 기본 구조 + +```sql +INSERT INTO screen_layouts_v2 (screen_id, company_code, layer_id, layer_name, layout_data, created_at, updated_at) +VALUES ( + (SELECT screen_id FROM screen_definitions WHERE screen_code = '{screen_code}'), + '{company_code}', + 1, -- 기본 레이어 + '기본 레이어', + '{layout_data_json}'::jsonb, + now(), + now() +) +ON CONFLICT (screen_id, company_code, layer_id) +DO UPDATE SET layout_data = EXCLUDED.layout_data, updated_at = now(); +``` + +### layout_data JSON 뼈대 + +```json +{ + "version": "2.0", + "components": [ + { + "id": "{고유ID}", + "url": "@/lib/registry/components/{컴포넌트타입}", + "position": { "x": 0, "y": 0 }, + "size": { "width": 1920, "height": 800 }, + "displayOrder": 0, + "overrides": { /* 컴포넌트별 설정 */ } + } + ], + "gridSettings": { "columns": 12, "gap": 16, "padding": 16 }, + "screenResolution": { "width": 1920, "height": 1080 } +} +``` + +### 컴포넌트 url 매핑표 + +| 컴포넌트 | url 값 | +|----------|--------| +| v2-table-list | `@/lib/registry/components/v2-table-list` | +| v2-table-search-widget | `@/lib/registry/components/v2-table-search-widget` | +| v2-split-panel-layout | `@/lib/registry/components/v2-split-panel-layout` | +| v2-table-grouped | `@/lib/registry/components/v2-table-grouped` | +| v2-tabs-widget | `@/lib/registry/components/v2-tabs-widget` | +| v2-button-primary | `@/lib/registry/components/v2-button-primary` | +| v2-input | `@/lib/registry/components/v2-input` | +| v2-select | `@/lib/registry/components/v2-select` | +| v2-date | `@/lib/registry/components/v2-date` | +| v2-card-display | `@/lib/registry/components/v2-card-display` | +| v2-pivot-grid | `@/lib/registry/components/v2-pivot-grid` | +| v2-timeline-scheduler | `@/lib/registry/components/v2-timeline-scheduler` | +| v2-text-display | `@/lib/registry/components/v2-text-display` | +| v2-aggregation-widget | `@/lib/registry/components/v2-aggregation-widget` | +| v2-numbering-rule | `@/lib/registry/components/v2-numbering-rule` | +| v2-file-upload | `@/lib/registry/components/v2-file-upload` | +| v2-section-card | `@/lib/registry/components/v2-section-card` | +| v2-divider-line | `@/lib/registry/components/v2-divider-line` | +| v2-bom-tree | `@/lib/registry/components/v2-bom-tree` | +| v2-approval-step | `@/lib/registry/components/v2-approval-step` | +| v2-status-count | `@/lib/registry/components/v2-status-count` | +| v2-section-paper | `@/lib/registry/components/v2-section-paper` | +| v2-split-line | `@/lib/registry/components/v2-split-line` | +| v2-repeat-container | `@/lib/registry/components/v2-repeat-container` | +| v2-repeater | `@/lib/registry/components/v2-repeater` | +| v2-category-manager | `@/lib/registry/components/v2-category-manager` | +| v2-media | `@/lib/registry/components/v2-media` | +| v2-location-swap-selector | `@/lib/registry/components/v2-location-swap-selector` | +| v2-rack-structure | `@/lib/registry/components/v2-rack-structure` | +| v2-process-work-standard | `@/lib/registry/components/v2-process-work-standard` | +| v2-item-routing | `@/lib/registry/components/v2-item-routing` | +| v2-bom-item-editor | `@/lib/registry/components/v2-bom-item-editor` | + +--- + +## 8. 패턴별 layout_data 완전 예시 + +### 8.1 패턴 A: 기본 마스터 (검색 + 테이블) + +**사용 조건**: 단일 테이블 CRUD, 마스터-디테일 관계 없음 + +```json +{ + "version": "2.0", + "components": [ + { + "id": "search_1", + "url": "@/lib/registry/components/v2-table-search-widget", + "position": { "x": 0, "y": 0 }, + "size": { "width": 1920, "height": 100 }, + "displayOrder": 0, + "overrides": { + "label": "검색", + "autoSelectFirstTable": true, + "showTableSelector": false + } + }, + { + "id": "table_1", + "url": "@/lib/registry/components/v2-table-list", + "position": { "x": 0, "y": 120 }, + "size": { "width": 1920, "height": 700 }, + "displayOrder": 1, + "overrides": { + "label": "{화면제목}", + "tableName": "{테이블명}", + "autoLoad": true, + "displayMode": "table", + "checkbox": { "enabled": true, "multiple": true, "position": "left", "selectAll": true }, + "pagination": { "enabled": true, "pageSize": 20, "showSizeSelector": true, "showPageInfo": true }, + "horizontalScroll": { "enabled": true, "maxVisibleColumns": 8 }, + "toolbar": { "showEditMode": true, "showExcel": true, "showRefresh": true } + } + } + ], + "gridSettings": { "columns": 12, "gap": 16, "padding": 16 }, + "screenResolution": { "width": 1920, "height": 1080 } +} +``` + +### 8.2 패턴 B: 마스터-디테일 (좌우 분할) + +**사용 조건**: 좌측 마스터 테이블 선택 → 우측 디테일 테이블 연동. 두 테이블 간 FK 관계. + +```json +{ + "version": "2.0", + "components": [ + { + "id": "split_1", + "url": "@/lib/registry/components/v2-split-panel-layout", + "position": { "x": 0, "y": 0 }, + "size": { "width": 1920, "height": 850 }, + "displayOrder": 0, + "overrides": { + "label": "{화면제목}", + "splitRatio": 35, + "resizable": true, + "autoLoad": true, + "syncSelection": true, + "leftPanel": { + "title": "{마스터_제목}", + "displayMode": "table", + "tableName": "{마스터_테이블명}", + "showSearch": true, + "showAdd": true, + "showEdit": false, + "showDelete": true, + "columns": [ + { "name": "{컬럼1}", "label": "{라벨1}", "width": 120, "sortable": true }, + { "name": "{컬럼2}", "label": "{라벨2}", "width": 150 }, + { "name": "{컬럼3}", "label": "{라벨3}", "width": 100 } + ], + "addButton": { "enabled": true, "mode": "auto" }, + "deleteButton": { "enabled": true, "confirmMessage": "선택한 항목을 삭제하시겠습니까?" } + }, + "rightPanel": { + "title": "{디테일_제목}", + "displayMode": "table", + "tableName": "{디테일_테이블명}", + "relation": { + "type": "detail", + "leftColumn": "id", + "rightColumn": "{마스터FK_컬럼}", + "foreignKey": "{마스터FK_컬럼}" + }, + "columns": [ + { "name": "{컬럼1}", "label": "{라벨1}", "width": 120 }, + { "name": "{컬럼2}", "label": "{라벨2}", "width": 150 }, + { "name": "{컬럼3}", "label": "{라벨3}", "width": 100, "editable": true } + ], + "addButton": { "enabled": true, "mode": "auto" }, + "editButton": { "enabled": true, "mode": "auto" }, + "deleteButton": { "enabled": true, "confirmMessage": "삭제하시겠습니까?" } + } + } + } + ], + "gridSettings": { "columns": 12, "gap": 16, "padding": 16 }, + "screenResolution": { "width": 1920, "height": 1080 } +} +``` + +### 8.3 패턴 C: 마스터-디테일 + 탭 + +**사용 조건**: 패턴 B에서 우측에 여러 종류의 상세를 탭으로 구분 + +패턴 B의 rightPanel에 **additionalTabs** 추가: + +```json +{ + "rightPanel": { + "title": "{디테일_제목}", + "displayMode": "table", + "tableName": "{기본탭_테이블}", + "relation": { + "type": "detail", + "leftColumn": "id", + "rightColumn": "{FK_컬럼}", + "foreignKey": "{FK_컬럼}" + }, + "additionalTabs": [ + { + "tabId": "tab_basic", + "label": "기본정보", + "tableName": "{기본정보_테이블}", + "displayMode": "table", + "relation": { "type": "detail", "leftColumn": "id", "rightColumn": "{FK}", "foreignKey": "{FK}" }, + "columns": [ /* 컬럼 배열 */ ], + "addButton": { "enabled": true }, + "deleteButton": { "enabled": true } + }, + { + "tabId": "tab_history", + "label": "이력", + "tableName": "{이력_테이블}", + "displayMode": "table", + "relation": { "type": "detail", "leftColumn": "id", "rightColumn": "{FK}", "foreignKey": "{FK}" }, + "columns": [ /* 컬럼 배열 */ ] + }, + { + "tabId": "tab_files", + "label": "첨부파일", + "tableName": "{파일_테이블}", + "displayMode": "table", + "relation": { "type": "detail", "leftColumn": "id", "rightColumn": "{FK}", "foreignKey": "{FK}" }, + "columns": [ /* 컬럼 배열 */ ] + } + ] + } +} +``` + +### 8.4 패턴 D: 그룹화 테이블 + +```json +{ + "version": "2.0", + "components": [ + { + "id": "grouped_1", + "url": "@/lib/registry/components/v2-table-grouped", + "position": { "x": 0, "y": 0 }, + "size": { "width": 1920, "height": 800 }, + "displayOrder": 0, + "overrides": { + "label": "{화면제목}", + "selectedTable": "{테이블명}", + "groupConfig": { + "groupByColumn": "{그룹기준_컬럼}", + "groupLabelFormat": "{value}", + "defaultExpanded": true, + "sortDirection": "asc", + "summary": { "showCount": true, "sumColumns": ["{합계컬럼1}", "{합계컬럼2}"] } + }, + "columns": [ + { "columnName": "{컬럼1}", "displayName": "{라벨1}", "visible": true, "width": 120 }, + { "columnName": "{컬럼2}", "displayName": "{라벨2}", "visible": true, "width": 150 } + ], + "showCheckbox": true, + "showExpandAllButton": true + } + } + ], + "gridSettings": { "columns": 12, "gap": 16, "padding": 16 }, + "screenResolution": { "width": 1920, "height": 1080 } +} +``` + +### 8.5 패턴 E: 타임라인/간트차트 + +```json +{ + "version": "2.0", + "components": [ + { + "id": "timeline_1", + "url": "@/lib/registry/components/v2-timeline-scheduler", + "position": { "x": 0, "y": 0 }, + "size": { "width": 1920, "height": 800 }, + "displayOrder": 0, + "overrides": { + "label": "{화면제목}", + "selectedTable": "{스케줄_테이블}", + "resourceTable": "{리소스_테이블}", + "fieldMapping": { + "id": "id", + "resourceId": "{리소스FK_컬럼}", + "title": "{제목_컬럼}", + "startDate": "{시작일_컬럼}", + "endDate": "{종료일_컬럼}", + "status": "{상태_컬럼}", + "progress": "{진행률_컬럼}" + }, + "resourceFieldMapping": { + "id": "id", + "name": "{리소스명_컬럼}", + "group": "{그룹_컬럼}" + }, + "defaultZoomLevel": "day", + "editable": true, + "allowDrag": true, + "allowResize": true + } + } + ], + "gridSettings": { "columns": 12, "gap": 16, "padding": 16 }, + "screenResolution": { "width": 1920, "height": 1080 } +} +``` + +--- + +## 9. Step 7: menu_info INSERT + +```sql +INSERT INTO menu_info ( + objid, menu_type, parent_obj_id, + menu_name_kor, menu_name_eng, seq, + menu_url, menu_desc, writer, regdate, status, + company_code, screen_code +) +VALUES ( + {고유_objid}, + 0, + {부모_메뉴_objid}, + '{메뉴명_한글}', + '{메뉴명_영문}', + {정렬순서}, + '/screen/{screen_code}', + '{메뉴_설명}', + 'admin', + now(), + 'active', + '{company_code}', + '{screen_code}' +); +``` + +- `objid`: BIGINT 고유값. `extract(epoch from now())::bigint * 1000` 으로 생성 +- `menu_type`: `0` = 말단 메뉴(화면), `1` = 폴더 +- `parent_obj_id`: 상위 폴더 메뉴의 objid + +**objid 생성 규칙 및 주의사항**: + +기본 생성: `extract(epoch from now())::bigint * 1000` + +> **여러 메뉴를 한 트랜잭션에서 동시에 INSERT할 때 PK 중복 위험!** +> `now()`는 같은 트랜잭션 안에서 동일한 값을 반환하므로, 복수 INSERT 시 objid가 충돌한다. +> 반드시 순서값을 더해서 고유성을 보장할 것: +> +> ```sql +> -- 폴더 메뉴 +> extract(epoch from now())::bigint * 1000 + 1 +> -- 화면 메뉴 1 +> extract(epoch from now())::bigint * 1000 + 2 +> -- 화면 메뉴 2 +> extract(epoch from now())::bigint * 1000 + 3 +> ``` + +--- + +## 10. 선택적 단계: 채번 규칙 설정 + +자동으로 코드/번호를 생성해야 하는 컬럼이 있을 때 사용. + +### numbering_rules INSERT + +```sql +INSERT INTO numbering_rules ( + rule_id, rule_name, description, separator, reset_period, + current_sequence, table_name, column_name, company_code, + created_at, updated_at, created_by +) +VALUES ( + '{rule_id}', -- 예: 'ORDER_NO_RULE' + '{규칙명}', -- 예: '수주번호 채번' + '{설명}', + '-', -- 구분자 + 'year', -- 'none', 'year', 'month', 'day' + 1, -- 시작 순번 + '{테이블명}', -- 예: 'order_master' + '{컬럼명}', -- 예: 'order_no' + '{company_code}', + now(), now(), 'admin' +); +``` + +### numbering_rule_parts INSERT (채번 구성 파트) + +```sql +-- 파트 1: 접두사 +INSERT INTO numbering_rule_parts (rule_id, part_order, part_type, generation_method, auto_config, manual_config, company_code, created_at) +VALUES ('{rule_id}', 1, 'prefix', 'auto', '{"prefix": "SO", "separatorAfter": "-"}'::jsonb, '{}'::jsonb, '{company_code}', now()); + +-- 파트 2: 날짜 +INSERT INTO numbering_rule_parts (rule_id, part_order, part_type, generation_method, auto_config, manual_config, company_code, created_at) +VALUES ('{rule_id}', 2, 'date', 'auto', '{"format": "YYYYMM", "separatorAfter": "-"}'::jsonb, '{}'::jsonb, '{company_code}', now()); + +-- 파트 3: 순번 +INSERT INTO numbering_rule_parts (rule_id, part_order, part_type, generation_method, auto_config, manual_config, company_code, created_at) +VALUES ('{rule_id}', 3, 'sequence', 'auto', '{"digits": 4, "startFrom": 1}'::jsonb, '{}'::jsonb, '{company_code}', now()); +``` + +**결과**: `SO-202603-0001`, `SO-202603-0002`, ... + +--- + +## 11. 선택적 단계: 카테고리 값 설정 + +상태, 유형 등을 카테고리로 관리할 때 사용. + +### table_column_category_values INSERT + +```sql +INSERT INTO table_column_category_values ( + table_name, column_name, value_code, value_label, value_order, + parent_value_id, depth, description, color, company_code, created_by +) +VALUES + ('{테이블명}', '{컬럼명}', 'ACTIVE', '활성', 1, NULL, 1, '활성 상태', '#22c55e', '{company_code}', 'admin'), + ('{테이블명}', '{컬럼명}', 'INACTIVE', '비활성', 2, NULL, 1, '비활성 상태', '#ef4444', '{company_code}', 'admin'), + ('{테이블명}', '{컬럼명}', 'PENDING', '대기', 3, NULL, 1, '승인 대기', '#f59e0b', '{company_code}', 'admin'); +``` + +--- + +## 12. 패턴 판단 의사결정 트리 + +사용자가 화면을 요청하면 이 트리로 패턴을 결정한다. + +``` +Q1. 시간축 기반 일정/간트차트가 필요한가? +├─ YES → 패턴 E (타임라인) → v2-timeline-scheduler +└─ NO ↓ + +Q2. 다차원 집계/피벗 분석이 필요한가? +├─ YES → 피벗 → v2-pivot-grid +└─ NO ↓ + +Q3. 데이터를 그룹별로 접기/펼치기가 필요한가? +├─ YES → 패턴 D (그룹화) → v2-table-grouped +└─ NO ↓ + +Q4. 이미지+정보를 카드 형태로 표시하는가? +├─ YES → 카드뷰 → v2-card-display +└─ NO ↓ + +Q5. 마스터 테이블 선택 시 연관 디테일이 필요한가? +├─ YES → Q5-1. 디테일에 여러 탭이 필요한가? +│ ├─ YES → 패턴 C (마스터-디테일+탭) → v2-split-panel-layout + additionalTabs +│ └─ NO → 패턴 B (마스터-디테일) → v2-split-panel-layout +└─ NO → 패턴 A (기본 마스터) → v2-table-search-widget + v2-table-list +``` + +--- + +## 13. 화면 간 연결 관계 정의 + +### 13.1 마스터-디테일 관계 (v2-split-panel-layout) + +좌측 마스터 테이블의 행을 선택하면, 우측 디테일 테이블이 해당 FK로 필터링된다. + +**relation 설정**: + +> **JSON 안에 주석(`//`, `/* */`) 절대 금지!** PostgreSQL `::jsonb` 캐스팅 시 파싱 에러 발생. 설명은 반드시 JSON 바깥에 작성한다. + +- `type`: `"detail"` (FK 관계) +- `leftColumn`: 마스터 테이블의 PK 컬럼 (보통 `"id"`) +- `rightColumn`: 디테일 테이블의 FK 컬럼 +- `foreignKey`: `rightColumn`과 동일한 값 + +```json +{ + "relation": { + "type": "detail", + "leftColumn": "id", + "rightColumn": "master_id", + "foreignKey": "master_id" + } +} +``` + +**복합 키인 경우**: + +```json +{ + "relation": { + "type": "detail", + "keys": [ + { "leftColumn": "order_no", "rightColumn": "order_no" }, + { "leftColumn": "company_code", "rightColumn": "company_code" } + ] + } +} +``` + +### 13.2 엔티티 조인 (테이블 참조 표시) + +디테일 테이블의 FK 컬럼에 다른 테이블의 이름을 표시하고 싶을 때. + +**table_type_columns에서 설정**: + +```sql +INSERT INTO table_type_columns (table_name, column_name, company_code, input_type, detail_settings, ...) +VALUES ('order_detail', 'item_id', '*', 'entity', + '{"referenceTable":"item_info","referenceColumn":"id","displayColumn":"item_name"}', ...); +``` + +**v2-table-list columns에서 설정**: + +```json +{ + "columns": [ + { + "name": "item_id", + "label": "품목", + "isEntityJoin": true, + "joinInfo": { + "sourceTable": "order_detail", + "sourceColumn": "item_id", + "referenceTable": "item_info", + "joinAlias": "item_name" + } + } + ] +} +``` + +### 13.3 모달 화면 연결 + +추가/편집 버튼 클릭 시 별도 모달 화면을 띄우는 경우. + +1. **모달용 screen_definitions INSERT** (별도 화면 생성) +2. split-panel의 addButton/editButton에서 연결: + +```json +{ + "addButton": { + "enabled": true, + "mode": "modal", + "modalScreenId": "{모달_screen_id}" + }, + "editButton": { + "enabled": true, + "mode": "modal", + "modalScreenId": "{모달_screen_id}" + } +} +``` + +--- + +## 14. 비즈니스 로직 설정 (제어관리) + +버튼 클릭 시 INSERT/UPDATE/DELETE, 상태 변경, 이력 기록 등이 필요한 경우. + +### 14.1 v2-button-primary overrides + +```json +{ + "id": "btn_confirm", + "url": "@/lib/registry/components/v2-button-primary", + "position": { "x": 1700, "y": 10 }, + "size": { "width": 100, "height": 40 }, + "overrides": { + "text": "확정", + "variant": "primary", + "actionType": "button", + "action": { "type": "custom" }, + "webTypeConfig": { + "enableDataflowControl": true, + "dataflowConfig": { + "controlMode": "relationship", + "relationshipConfig": { + "relationshipId": "{관계_ID}", + "relationshipName": "{관계명}", + "executionTiming": "after" + } + } + } + } +} +``` + +### 14.2 dataflow_diagrams INSERT + +```sql +INSERT INTO dataflow_diagrams ( + diagram_name, company_code, + relationships, control, plan, node_positions +) +VALUES ( + '{관계도명}', + '{company_code}', + '[{"fromTable":"{소스_테이블}","toTable":"{타겟_테이블}","relationType":"data_save"}]'::jsonb, + '[{ + "conditions": [{"field":"status","operator":"=","value":"대기","dataType":"string"}], + "triggerType": "update" + }]'::jsonb, + '[{ + "actions": [ + { + "actionType": "update", + "targetTable": "{타겟_테이블}", + "conditions": [{"field":"status","operator":"=","value":"대기"}], + "fieldMappings": [{"targetField":"status","defaultValue":"확정"}] + }, + { + "actionType": "insert", + "targetTable": "{이력_테이블}", + "fieldMappings": [ + {"sourceField":"order_no","targetField":"order_no"}, + {"targetField":"action","defaultValue":"확정"} + ] + } + ] + }]'::jsonb, + '[]'::jsonb +) +RETURNING diagram_id; +``` + +**executionTiming 선택**: +- `before`: 메인 액션 전 → 조건 체크 (조건 불충족 시 메인 액션 중단) +- `after`: 메인 액션 후 → 후처리 (이력 기록, 상태 변경 등) +- `replace`: 메인 액션 대체 → 제어만 실행 + +--- + +## 15. 전체 예시: "수주관리 화면 만들어줘" + +### 요구사항 해석 +- 마스터: order_master (수주번호, 거래처, 수주일자, 상태) +- 디테일: order_detail (품목, 수량, 단가, 금액) +- 패턴: B (마스터-디테일) + +### 실행 SQL + +```sql +-- ===== Step 1: 테이블 생성 ===== +CREATE TABLE "order_master" ( + "id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text, + "created_date" timestamp DEFAULT now(), + "updated_date" timestamp DEFAULT now(), + "writer" varchar(500) DEFAULT NULL, + "company_code" varchar(500), + "order_no" varchar(500), + "customer_id" varchar(500), + "order_date" varchar(500), + "delivery_date" varchar(500), + "status" varchar(500), + "total_amount" varchar(500), + "notes" varchar(500) +); + +CREATE TABLE "order_detail" ( + "id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text, + "created_date" timestamp DEFAULT now(), + "updated_date" timestamp DEFAULT now(), + "writer" varchar(500) DEFAULT NULL, + "company_code" varchar(500), + "order_master_id" varchar(500), + "item_id" varchar(500), + "quantity" varchar(500), + "unit_price" varchar(500), + "amount" varchar(500), + "notes" varchar(500) +); + +-- ===== Step 2: table_labels ===== +INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date) VALUES + ('order_master', '수주 마스터', '수주 헤더 정보', now(), now()), + ('order_detail', '수주 상세', '수주 품목별 상세', now(), now()) +ON CONFLICT (table_name) DO UPDATE SET table_label = EXCLUDED.table_label, description = EXCLUDED.description, updated_date = now(); + +-- ===== Step 3: table_type_columns (확장 컬럼 포함) ===== +-- order_master 기본 + 비즈니스 컬럼 +INSERT INTO table_type_columns ( + table_name, column_name, company_code, input_type, detail_settings, + is_nullable, is_unique, display_order, column_label, description, is_visible, + created_date, updated_date +) VALUES + ('order_master', 'id', '*', 'text', '{}', 'N', 'Y', -5, 'ID', '기본키', false, now(), now()), + ('order_master', 'created_date', '*', 'date', '{}', 'Y', 'N', -4, '생성일시', '레코드 생성일시', false, now(), now()), + ('order_master', 'updated_date', '*', 'date', '{}', 'Y', 'N', -3, '수정일시', '레코드 수정일시', false, now(), now()), + ('order_master', 'writer', '*', 'text', '{}', 'Y', 'N', -2, '작성자', '레코드 작성자', false, now(), now()), + ('order_master', 'company_code', '*', 'text', '{}', 'Y', 'N', -1, '회사코드', '회사 구분 코드', false, now(), now()), + ('order_master', 'order_no', '*', 'text', '{}', 'N', 'Y', 0, '수주번호', '수주 식별번호', true, now(), now()), + ('order_master', 'customer_id', '*', 'entity', '{"referenceTable":"customer_info","referenceColumn":"id","displayColumn":"customer_name"}', 'N', 'N', 1, '거래처', '거래처 참조', true, now(), now()), + ('order_master', 'order_date', '*', 'date', '{}', 'N', 'N', 2, '수주일자', '', true, now(), now()), + ('order_master', 'delivery_date', '*', 'date', '{}', 'Y', 'N', 3, '납기일', '', true, now(), now()), + ('order_master', 'status', '*', 'code', '{"codeCategory":"ORDER_STATUS"}', 'Y', 'N', 4, '상태', '수주 상태', true, now(), now()), + ('order_master', 'total_amount', '*', 'number', '{}', 'Y', 'N', 5, '총금액', '', true, now(), now()), + ('order_master', 'notes', '*', 'textarea', '{}', 'Y', 'N', 6, '비고', '', true, now(), now()) +ON CONFLICT (table_name, column_name, company_code) DO UPDATE SET + input_type = EXCLUDED.input_type, detail_settings = EXCLUDED.detail_settings, + is_nullable = EXCLUDED.is_nullable, is_unique = EXCLUDED.is_unique, + display_order = EXCLUDED.display_order, column_label = EXCLUDED.column_label, + description = EXCLUDED.description, is_visible = EXCLUDED.is_visible, updated_date = now(); + +-- order_detail 기본 + 비즈니스 컬럼 +INSERT INTO table_type_columns ( + table_name, column_name, company_code, input_type, detail_settings, + is_nullable, is_unique, display_order, column_label, description, is_visible, + created_date, updated_date +) VALUES + ('order_detail', 'id', '*', 'text', '{}', 'N', 'Y', -5, 'ID', '기본키', false, now(), now()), + ('order_detail', 'created_date', '*', 'date', '{}', 'Y', 'N', -4, '생성일시', '레코드 생성일시', false, now(), now()), + ('order_detail', 'updated_date', '*', 'date', '{}', 'Y', 'N', -3, '수정일시', '레코드 수정일시', false, now(), now()), + ('order_detail', 'writer', '*', 'text', '{}', 'Y', 'N', -2, '작성자', '레코드 작성자', false, now(), now()), + ('order_detail', 'company_code', '*', 'text', '{}', 'Y', 'N', -1, '회사코드', '회사 구분 코드', false, now(), now()), + ('order_detail', 'order_master_id', '*', 'text', '{}', 'N', 'N', 0, '수주마스터ID', 'FK', false, now(), now()), + ('order_detail', 'item_id', '*', 'entity', '{"referenceTable":"item_info","referenceColumn":"id","displayColumn":"item_name"}', 'N', 'N', 1, '품목', '품목 참조', true, now(), now()), + ('order_detail', 'quantity', '*', 'number', '{}', 'N', 'N', 2, '수량', '', true, now(), now()), + ('order_detail', 'unit_price', '*', 'number', '{}', 'Y', 'N', 3, '단가', '', true, now(), now()), + ('order_detail', 'amount', '*', 'number', '{}', 'Y', 'N', 4, '금액', '', true, now(), now()), + ('order_detail', 'notes', '*', 'textarea', '{}', 'Y', 'N', 5, '비고', '', true, now(), now()) +ON CONFLICT (table_name, column_name, company_code) DO UPDATE SET + input_type = EXCLUDED.input_type, detail_settings = EXCLUDED.detail_settings, + is_nullable = EXCLUDED.is_nullable, is_unique = EXCLUDED.is_unique, + display_order = EXCLUDED.display_order, column_label = EXCLUDED.column_label, + description = EXCLUDED.description, is_visible = EXCLUDED.is_visible, updated_date = now(); + +-- ===== Step 4: column_labels (company_code 필수!) ===== +INSERT INTO column_labels (table_name, column_name, column_label, input_type, detail_settings, description, display_order, is_visible, company_code, created_date, updated_date) VALUES + ('order_master', 'id', 'ID', 'text', '{}', '기본키', -5, true, '*', now(), now()), + ('order_master', 'created_date', '생성일시', 'date', '{}', '', -4, true, '*', now(), now()), + ('order_master', 'updated_date', '수정일시', 'date', '{}', '', -3, true, '*', now(), now()), + ('order_master', 'writer', '작성자', 'text', '{}', '', -2, true, '*', now(), now()), + ('order_master', 'company_code', '회사코드', 'text', '{}', '', -1, true, '*', now(), now()), + ('order_master', 'order_no', '수주번호', 'text', '{}', '수주 식별번호', 0, true, '*', now(), now()), + ('order_master', 'customer_id', '거래처', 'entity', '{"referenceTable":"customer_info","referenceColumn":"id","displayColumn":"customer_name"}', '거래처 참조', 1, true, '*', now(), now()), + ('order_master', 'order_date', '수주일자', 'date', '{}', '', 2, true, '*', now(), now()), + ('order_master', 'delivery_date', '납기일', 'date', '{}', '', 3, true, '*', now(), now()), + ('order_master', 'status', '상태', 'code', '{"codeCategory":"ORDER_STATUS"}', '수주 상태', 4, true, '*', now(), now()), + ('order_master', 'total_amount', '총금액', 'number', '{}', '', 5, true, '*', now(), now()), + ('order_master', 'notes', '비고', 'textarea', '{}', '', 6, true, '*', now(), now()) +ON CONFLICT (table_name, column_name, company_code) DO UPDATE SET + column_label = EXCLUDED.column_label, input_type = EXCLUDED.input_type, + detail_settings = EXCLUDED.detail_settings, description = EXCLUDED.description, + display_order = EXCLUDED.display_order, is_visible = EXCLUDED.is_visible, updated_date = now(); + +INSERT INTO column_labels (table_name, column_name, column_label, input_type, detail_settings, description, display_order, is_visible, company_code, created_date, updated_date) VALUES + ('order_detail', 'id', 'ID', 'text', '{}', '기본키', -5, true, '*', now(), now()), + ('order_detail', 'created_date', '생성일시', 'date', '{}', '', -4, true, '*', now(), now()), + ('order_detail', 'updated_date', '수정일시', 'date', '{}', '', -3, true, '*', now(), now()), + ('order_detail', 'writer', '작성자', 'text', '{}', '', -2, true, '*', now(), now()), + ('order_detail', 'company_code', '회사코드', 'text', '{}', '', -1, true, '*', now(), now()), + ('order_detail', 'order_master_id', '수주마스터ID', 'text', '{}', 'FK', 0, true, '*', now(), now()), + ('order_detail', 'item_id', '품목', 'entity', '{"referenceTable":"item_info","referenceColumn":"id","displayColumn":"item_name"}', '품목 참조', 1, true, '*', now(), now()), + ('order_detail', 'quantity', '수량', 'number', '{}', '', 2, true, '*', now(), now()), + ('order_detail', 'unit_price', '단가', 'number', '{}', '', 3, true, '*', now(), now()), + ('order_detail', 'amount', '금액', 'number', '{}', '', 4, true, '*', now(), now()), + ('order_detail', 'notes', '비고', 'textarea', '{}', '', 5, true, '*', now(), now()) +ON CONFLICT (table_name, column_name, company_code) DO UPDATE SET + column_label = EXCLUDED.column_label, input_type = EXCLUDED.input_type, + detail_settings = EXCLUDED.detail_settings, description = EXCLUDED.description, + display_order = EXCLUDED.display_order, is_visible = EXCLUDED.is_visible, updated_date = now(); + +-- ===== Step 5: screen_definitions ===== +INSERT INTO screen_definitions (screen_name, screen_code, table_name, company_code, description, is_active, db_source_type, data_source_type, created_date) +VALUES ('수주관리', 'ILSHIN_ORDER_MNG', 'order_master', 'ILSHIN', '수주 마스터-디테일 관리', 'Y', 'internal', 'database', now()); + +-- ===== Step 6: screen_layouts_v2 (서브쿼리로 screen_id 참조) ===== +INSERT INTO screen_layouts_v2 (screen_id, company_code, layer_id, layer_name, layout_data, created_at, updated_at) +VALUES ( + (SELECT screen_id FROM screen_definitions WHERE screen_code = 'ILSHIN_ORDER_MNG'), + 'ILSHIN', 1, '기본 레이어', + '{ + "version": "2.0", + "components": [ + { + "id": "split_order", + "url": "@/lib/registry/components/v2-split-panel-layout", + "position": {"x": 0, "y": 0}, + "size": {"width": 1920, "height": 850}, + "displayOrder": 0, + "overrides": { + "label": "수주관리", + "splitRatio": 35, + "resizable": true, + "autoLoad": true, + "syncSelection": true, + "leftPanel": { + "title": "수주 목록", + "displayMode": "table", + "tableName": "order_master", + "showSearch": true, + "showAdd": true, + "showDelete": true, + "columns": [ + {"name": "order_no", "label": "수주번호", "width": 120, "sortable": true}, + {"name": "customer_id", "label": "거래처", "width": 150, "isEntityJoin": true, "joinInfo": {"sourceTable": "order_master", "sourceColumn": "customer_id", "referenceTable": "customer_info", "joinAlias": "customer_name"}}, + {"name": "order_date", "label": "수주일자", "width": 100}, + {"name": "status", "label": "상태", "width": 80}, + {"name": "total_amount", "label": "총금액", "width": 120} + ], + "addButton": {"enabled": true, "mode": "auto"}, + "deleteButton": {"enabled": true, "confirmMessage": "선택한 수주를 삭제하시겠습니까?"} + }, + "rightPanel": { + "title": "수주 상세", + "displayMode": "table", + "tableName": "order_detail", + "relation": { + "type": "detail", + "leftColumn": "id", + "rightColumn": "order_master_id", + "foreignKey": "order_master_id" + }, + "columns": [ + {"name": "item_id", "label": "품목", "width": 150, "isEntityJoin": true, "joinInfo": {"sourceTable": "order_detail", "sourceColumn": "item_id", "referenceTable": "item_info", "joinAlias": "item_name"}}, + {"name": "quantity", "label": "수량", "width": 80, "editable": true}, + {"name": "unit_price", "label": "단가", "width": 100, "editable": true}, + {"name": "amount", "label": "금액", "width": 100}, + {"name": "notes", "label": "비고", "width": 200, "editable": true} + ], + "addButton": {"enabled": true, "mode": "auto"}, + "editButton": {"enabled": true, "mode": "auto"}, + "deleteButton": {"enabled": true, "confirmMessage": "삭제하시겠습니까?"} + } + } + } + ], + "gridSettings": {"columns": 12, "gap": 16, "padding": 16}, + "screenResolution": {"width": 1920, "height": 1080} + }'::jsonb, + now(), now() +) +ON CONFLICT (screen_id, company_code, layer_id) DO UPDATE SET layout_data = EXCLUDED.layout_data, updated_at = now(); + +-- ===== Step 7: menu_info (objid에 순서값 더해서 PK 충돌 방지) ===== +INSERT INTO menu_info ( + objid, menu_type, parent_obj_id, menu_name_kor, menu_name_eng, + seq, menu_url, menu_desc, writer, regdate, status, company_code, screen_code +) +VALUES ( + extract(epoch from now())::bigint * 1000 + 1, 0, {부모_메뉴_objid}, + '수주관리', 'Order Management', + 1, '/screen/ILSHIN_ORDER_MNG', '수주 마스터-디테일 관리', + 'admin', now(), 'active', 'ILSHIN', 'ILSHIN_ORDER_MNG' +); +``` + +--- + +## 16. 컴포넌트 빠른 참조표 + +| 요구사항 | 컴포넌트 url | 핵심 overrides | +|----------|-------------|---------------| +| 데이터 테이블 | v2-table-list | `tableName`, `columns`, `pagination` | +| 검색 바 | v2-table-search-widget | `autoSelectFirstTable` | +| 좌우 분할 | v2-split-panel-layout | `leftPanel`, `rightPanel`, `relation`, `splitRatio` | +| 그룹화 테이블 | v2-table-grouped | `groupConfig.groupByColumn`, `summary` | +| 간트차트 | v2-timeline-scheduler | `fieldMapping`, `resourceTable` | +| 피벗 분석 | v2-pivot-grid | `fields(area, summaryType)` | +| 카드 뷰 | v2-card-display | `columnMapping`, `cardsPerRow` | +| 액션 버튼 | v2-button-primary | `text`, `actionType`, `webTypeConfig.dataflowConfig` | +| 텍스트 입력 | v2-input | `inputType`, `tableName`, `columnName` | +| 선택 | v2-select | `mode`, `source` | +| 날짜 | v2-date | `dateType` | +| 자동 채번 | v2-numbering-rule | `rule` | +| BOM 트리 | v2-bom-tree | `detailTable`, `foreignKey`, `parentKey` | +| BOM 편집 | v2-bom-item-editor | `detailTable`, `sourceTable`, `itemCodeField` | +| 결재 스테퍼 | v2-approval-step | `targetTable`, `displayMode` | +| 파일 업로드 | v2-file-upload | `multiple`, `accept`, `maxSize` | +| 상태별 건수 | v2-status-count | `tableName`, `statusColumn`, `items` | +| 집계 카드 | v2-aggregation-widget | `tableName`, `items` | +| 반복 데이터 관리 | v2-repeater | `renderMode`, `mainTableName`, `foreignKeyColumn` | +| 반복 렌더링 | v2-repeat-container | `dataSourceType`, `layout`, `gridColumns` | +| 그룹 컨테이너 (테두리) | v2-section-card | `title`, `collapsible`, `borderStyle` | +| 그룹 컨테이너 (배경색) | v2-section-paper | `backgroundColor`, `shadow`, `padding` | +| 캔버스 분할선 | v2-split-line | `resizable`, `lineColor`, `lineWidth` | +| 카테고리 관리 | v2-category-manager | `tableName`, `columnName`, `menuObjid` | +| 미디어 | v2-media | `mediaType`, `multiple`, `maxSize` | +| 위치 교환 | v2-location-swap-selector | `dataSource`, `departureField`, `destinationField` | +| 창고 랙 | v2-rack-structure | `codePattern`, `namePattern`, `maxRows` | +| 공정 작업기준 | v2-process-work-standard | `dataSource.itemTable`, `dataSource.routingDetailTable` | +| 품목 라우팅 | v2-item-routing | `dataSource.itemTable`, `dataSource.routingDetailTable` |