docs: 다양한 문서 및 가이드 업데이트

- 여러 문서의 내용을 업데이트하여 최신 정보를 반영하였습니다.
- 컴포넌트 개발 가이드와 관련된 문서의 목차를 재구성하고, V2 및 Zod 레이아웃 시스템에 대한 내용을 추가하였습니다.
- 화면 컴포넌트 개발 가이드를 개선하여 핵심 원칙과 패턴을 명확히 설명하였습니다.
- 불필요한 문서 및 가이드를 삭제하고, 통합된 가이드를 통해 개발자들이 쉽게 참고할 수 있도록 하였습니다.
This commit is contained in:
kjs 2026-01-28 17:36:19 +09:00
parent e0ee375f01
commit 95bef976a5
276 changed files with 2544 additions and 2495 deletions

View File

@ -51,7 +51,7 @@ alwaysApply: false
| `v2-split-panel-layout` | 분할 패널 | 좌우/상하 분할 | | `v2-split-panel-layout` | 분할 패널 | 좌우/상하 분할 |
| `v2-numbering-rule` | 채번 규칙 | 자동 채번 생성 | | `v2-numbering-rule` | 채번 규칙 | 자동 채번 생성 |
| `v2-tabs-widget` | 탭 위젯 | 탭 레이아웃 | | `v2-tabs-widget` | 탭 위젯 | 탭 레이아웃 |
| `v2-unified-repeater` | 통합 리피터 | 행 단위 입력/저장 | | `v2-repeater` | 통합 리피터 | 행 단위 입력/저장 |
| `v2-rack-structure` | 렉 구조 | 창고 렉 위치 생성 | | `v2-rack-structure` | 렉 구조 | 창고 렉 위치 생성 |
| `v2-section-paper` | 섹션 페이퍼 | 섹션 컨테이너 | | `v2-section-paper` | 섹션 페이퍼 | 섹션 컨테이너 |
| `v2-section-card` | 섹션 카드 | 카드 컨테이너 | | `v2-section-card` | 섹션 카드 | 카드 컨테이너 |
@ -118,7 +118,7 @@ export const V2TableListDefinition = createComponentDefinition({
"components": [ "components": [
{ {
"id": "comp_xxx", "id": "comp_xxx",
"url": "@/lib/registry/components/unified-select", "url": "@/lib/registry/components/v2-select",
"position": { "x": 100, "y": 50 }, "position": { "x": 100, "y": 50 },
"size": { "width": 180, "height": 30 }, "size": { "width": 180, "height": 30 },
"displayOrder": 0, "displayOrder": 0,
@ -226,7 +226,7 @@ export function convertV2ToLegacy(v2Layout: LayoutV2): LegacyLayoutData {
// frontend/lib/schemas/componentConfig.ts // frontend/lib/schemas/componentConfig.ts
// 컴포넌트별 overrides 스키마 // 컴포넌트별 overrides 스키마
export const unifiedSelectOverridesSchema = z.object({ export const v2SelectOverridesSchema = z.object({
mode: z.enum(["dropdown", "combobox", "radio", "checkbox"]).default("dropdown"), mode: z.enum(["dropdown", "combobox", "radio", "checkbox"]).default("dropdown"),
source: z.enum(["static", "code", "entity", "db", "distinct"]).default("distinct"), source: z.enum(["static", "code", "entity", "db", "distinct"]).default("distinct"),
multiple: z.boolean().default(false), multiple: z.boolean().default(false),
@ -236,15 +236,15 @@ export const unifiedSelectOverridesSchema = z.object({
// 스키마 레지스트리 // 스키마 레지스트리
export const componentOverridesSchemaRegistry: Record<string, z.ZodType<any>> = { export const componentOverridesSchemaRegistry: Record<string, z.ZodType<any>> = {
"unified-select": unifiedSelectOverridesSchema, "v2-select": v2SelectOverridesSchema,
"unified-input": unifiedInputOverridesSchema, "v2-input": v2InputOverridesSchema,
"v2-table-list": v2TableListOverridesSchema, "v2-table-list": v2TableListOverridesSchema,
// ... // ...
}; };
// 기본값 레지스트리 // 기본값 레지스트리
export const componentDefaultsRegistry: Record<string, any> = { export const componentDefaultsRegistry: Record<string, any> = {
"unified-select": { "v2-select": {
mode: "dropdown", mode: "dropdown",
source: "distinct", // 기본: 테이블 컬럼에서 자동 로드 source: "distinct", // 기본: 테이블 컬럼에서 자동 로드
multiple: false, multiple: false,
@ -254,7 +254,7 @@ export const componentDefaultsRegistry: Record<string, any> = {
}; };
``` ```
### unified-select 자동 옵션 로드 ### v2-select 자동 옵션 로드
`webType`이 `"select"`인 컬럼을 드래그하면: `webType`이 `"select"`인 컬럼을 드래그하면:
@ -265,9 +265,9 @@ export const componentDefaultsRegistry: Record<string, any> = {
```typescript ```typescript
// DynamicComponentRenderer.tsx // DynamicComponentRenderer.tsx
case "unified-select": case "v2-select":
return ( return (
<UnifiedSelect <V2Select
{...commonProps} {...commonProps}
config={{ config={{
mode: config.mode || "dropdown", mode: config.mode || "dropdown",
@ -604,26 +604,26 @@ if (!silentActions.includes(actionType)) {
| inputType | 생성 위젯 | 설명 | | inputType | 생성 위젯 | 설명 |
|-----------|----------|------| |-----------|----------|------|
| `text`, `textarea` | UnifiedInput | 텍스트 입력 | | `text`, `textarea` | V2Input | 텍스트 입력 |
| `number` | UnifiedInput | 숫자 입력 | | `number` | V2Input | 숫자 입력 |
| `date`, `datetime` | UnifiedDate | 날짜/시간 선택 | | `date`, `datetime` | V2Date | 날짜/시간 선택 |
| `code`, `category`, `entity` | UnifiedSelect | 선택박스 | | `code`, `category`, `entity` | V2Select | 선택박스 |
| `checkbox`, `radio` | 체크박스/라디오 | 선택 | | `checkbox`, `radio` | 체크박스/라디오 | 선택 |
| `image`, `file` | UnifiedMedia | 파일 업로드 | | `image`, `file` | V2Media | 파일 업로드 |
### 핵심 Unified 컴포넌트 (3개) ### 핵심 V2 컴포넌트 (3개)
| 컴포넌트 | 담당 inputType | 파일 경로 | | 컴포넌트 | 담당 inputType | 파일 경로 |
|----------|---------------|-----------| |----------|---------------|-----------|
| `UnifiedInput` | text, textarea, number, password | `components/unified/UnifiedInput.tsx` | | `V2Input` | text, textarea, number, password | `components/v2/V2Input.tsx` |
| `UnifiedSelect` | code, category, entity, select | `components/unified/UnifiedSelect.tsx` | | `V2Select` | code, category, entity, select | `components/v2/V2Select.tsx` |
| `UnifiedDate` | date, datetime, time, daterange | `components/unified/UnifiedDate.tsx` | | `V2Date` | date, datetime, time, daterange | `components/v2/V2Date.tsx` |
### 컴포넌트 패널에서 직접 드래그 가능한 컴포넌트 ### 컴포넌트 패널에서 직접 드래그 가능한 컴포넌트
| 컴포넌트 ID | 이름 | 설명 | | 컴포넌트 ID | 이름 | 설명 |
|-------------|------|------| |-------------|------|------|
| `v2-unified-repeater` | 리피터 그리드 | 행 단위 데이터 추가/수정/삭제 | | `v2-repeater` | 리피터 그리드 | 행 단위 데이터 추가/수정/삭제 |
| `v2-table-list` | 테이블 리스트 | 데이터 목록 조회/필터/정렬 | | `v2-table-list` | 테이블 리스트 | 데이터 목록 조회/필터/정렬 |
| `v2-table-search-widget` | 검색 필터 | 테이블 검색 조건 입력 | | `v2-table-search-widget` | 검색 필터 | 테이블 검색 조건 입력 |
| `v2-button-primary` | 버튼 | 저장, 삭제, 조회 등 액션 | | `v2-button-primary` | 버튼 | 저장, 삭제, 조회 등 액션 |
@ -725,7 +725,7 @@ interface TableListConfig {
#### 저장용 (리피터) #### 저장용 (리피터)
```typescript ```typescript
interface UnifiedRepeaterConfig { interface V2RepeaterConfig {
mainTableName?: string; // 저장할 테이블명 mainTableName?: string; // 저장할 테이블명
useCustomTable?: boolean; // true: mainTableName 사용 useCustomTable?: boolean; // true: mainTableName 사용
foreignKeyColumn?: string; // FK 컬럼 (예: receiving_id) foreignKeyColumn?: string; // FK 컬럼 (예: receiving_id)
@ -920,7 +920,7 @@ const getEntityJoinValue = (item: any, columnName: string): any => {
## 10. 폼 데이터 관리 ## 10. 폼 데이터 관리
### 통합 폼 시스템 (UnifiedFormContext) ### 통합 폼 시스템 (V2FormContext)
```typescript ```typescript
import { useFormCompatibility } from "@/hooks/useFormCompatibility"; import { useFormCompatibility } from "@/hooks/useFormCompatibility";
@ -949,15 +949,15 @@ const MyComponent = ({ onFormDataChange, formData }) => {
```typescript ```typescript
const handleChange = useCallback((value: any) => { const handleChange = useCallback((value: any) => {
// 1. UnifiedFormContext // 1. V2FormContext
unifiedContext?.setValue(fieldName, value); v2Context?.setValue(fieldName, value);
// 2. ScreenContext // 2. ScreenContext
screenContext?.updateFormData?.(fieldName, value); screenContext?.updateFormData?.(fieldName, value);
// 3. 레거시 콜백 // 3. 레거시 콜백
onFormDataChange?.(fieldName, value); onFormDataChange?.(fieldName, value);
}, [fieldName, unifiedContext, screenContext, onFormDataChange]); }, [fieldName, v2Context, screenContext, onFormDataChange]);
``` ```
--- ---
@ -1120,7 +1120,7 @@ const MyFormComponent = ({ formData, onFormDataChange }) => {
## 13. 표준 코드 스타일 가이드 ## 13. 표준 코드 스타일 가이드
**`v2-unified-repeater`** 컴포넌트를 표준으로 삼아 동일한 구조로 작성합니다. **`v2-repeater`** 컴포넌트를 표준으로 삼아 동일한 구조로 작성합니다.
### 핵심 원칙: 느슨한 결합도 (Loose Coupling) ### 핵심 원칙: 느슨한 결합도 (Loose Coupling)
@ -1675,7 +1675,7 @@ const derivedValue = useMemo(() => data.map(x => x.value), [data]);
- [ ] 테이블 컬럼 드래그 시 `tableName`, `columnName` 저장 확인 - [ ] 테이블 컬럼 드래그 시 `tableName`, `columnName` 저장 확인
- [ ] `convertLegacyToV2`에서 상위 레벨 속성 포함 확인 - [ ] `convertLegacyToV2`에서 상위 레벨 속성 포함 확인
- [ ] `convertV2ToLegacy`에서 상위 레벨 속성 복원 확인 - [ ] `convertV2ToLegacy`에서 상위 레벨 속성 복원 확인
- [ ] unified-select는 `source: "distinct"` 기본값 확인 - [ ] v2-select는 `source: "distinct"` 기본값 확인
### 표준 Props ### 표준 Props
@ -1749,7 +1749,7 @@ const derivedValue = useMemo(() => data.map(x => x.value), [data]);
### 코드 스타일 ### 코드 스타일
- [ ] `v2-unified-repeater` 구조 참고 - [ ] `v2-repeater` 구조 참고
- [ ] 느슨한 결합도 유지 (이벤트 기반 통신) - [ ] 느슨한 결합도 유지 (이벤트 기반 통신)
### 성능 최적화 ### 성능 최적화
@ -1769,9 +1769,9 @@ const derivedValue = useMemo(() => data.map(x => x.value), [data]);
| 파일 | 역할 | | 파일 | 역할 |
|------|------| |------|------|
| `components/unified/UnifiedInput.tsx` | text, number 입력 | | `components/v2/V2Input.tsx` | text, number 입력 |
| `components/unified/UnifiedSelect.tsx` | code, entity 선택 | | `components/v2/V2Select.tsx` | code, entity 선택 |
| `components/unified/UnifiedDate.tsx` | date, datetime 선택 | | `components/v2/V2Date.tsx` | date, datetime 선택 |
| `lib/registry/components/v2-*/` | V2 컴포넌트 폴더 | | `lib/registry/components/v2-*/` | V2 컴포넌트 폴더 |
| `lib/api/entityJoin.ts` | 엔티티 조인 API | | `lib/api/entityJoin.ts` | 엔티티 조인 API |
| `hooks/useFormCompatibility.ts` | 폼 호환성 브릿지 | | `hooks/useFormCompatibility.ts` | 폼 호환성 브릿지 |
@ -1783,6 +1783,6 @@ const derivedValue = useMemo(() => data.map(x => x.value), [data]);
| 컴포넌트 | 경로 | 참고 사항 | | 컴포넌트 | 경로 | 참고 사항 |
|----------|------|-----------| |----------|------|-----------|
| `v2-unified-repeater` | `lib/registry/components/v2-unified-repeater/` | **표준 참조 컴포넌트** | | `v2-repeater` | `lib/registry/components/v2-repeater/` | **표준 참조 컴포넌트** |
| `v2-table-list` | `lib/registry/components/v2-table-list/` | 조회 컴포넌트 참조 | | `v2-table-list` | `lib/registry/components/v2-table-list/` | 조회 컴포넌트 참조 |
| `v2-table-search-widget` | `lib/registry/components/v2-table-search-widget/` | 검색 필터 참조 | | `v2-table-search-widget` | `lib/registry/components/v2-table-search-widget/` | 검색 필터 참조 |

20
PLAN.MD
View File

@ -1,12 +1,12 @@
# 프로젝트: V2/Unified 컴포넌트 설정 스키마 정비 # 프로젝트: V2/V2 컴포넌트 설정 스키마 정비
## 개요 ## 개요
레거시 컴포넌트를 제거하고, V2/Unified 컴포넌트 전용 Zod 스키마와 기본값 레지스트리를 한 곳에서 관리한다. 레거시 컴포넌트를 제거하고, V2/V2 컴포넌트 전용 Zod 스키마와 기본값 레지스트리를 한 곳에서 관리한다.
## 핵심 기능 ## 핵심 기능
1. [x] 레거시 컴포넌트 스키마 제거 1. [x] 레거시 컴포넌트 스키마 제거
2. [x] V2 컴포넌트 overrides 스키마 정의 (16개) 2. [x] V2 컴포넌트 overrides 스키마 정의 (16개)
3. [x] Unified 컴포넌트 overrides 스키마 정의 (9개) 3. [x] V2 컴포넌트 overrides 스키마 정의 (9개)
4. [x] componentConfig.ts 한 파일에서 통합 관리 4. [x] componentConfig.ts 한 파일에서 통합 관리
## 정의된 V2 컴포넌트 (18개) ## 정의된 V2 컴포넌트 (18개)
@ -16,17 +16,17 @@
- v2-numbering-rule, v2-category-manager, v2-pivot-grid - v2-numbering-rule, v2-category-manager, v2-pivot-grid
- v2-location-swap-selector, v2-aggregation-widget - v2-location-swap-selector, v2-aggregation-widget
- v2-card-display, v2-table-search-widget, v2-tabs-widget - v2-card-display, v2-table-search-widget, v2-tabs-widget
- v2-unified-repeater - v2-v2-repeater
## 정의된 Unified 컴포넌트 (9개) ## 정의된 V2 컴포넌트 (9개)
- unified-input, unified-select, unified-date - v2-input, v2-select, v2-date
- unified-list, unified-layout, unified-group - v2-list, v2-layout, v2-group
- unified-media, unified-biz, unified-hierarchy - v2-media, v2-biz, v2-hierarchy
## 테스트 계획 ## 테스트 계획
### 1단계: 기본 기능 ### 1단계: 기본 기능
- [x] V2 레이아웃 저장 시 컴포넌트별 overrides 스키마 검증 통과 - [x] V2 레이아웃 저장 시 컴포넌트별 overrides 스키마 검증 통과
- [x] Unified 컴포넌트 기본값과 스키마가 매칭됨 - [x] V2 컴포넌트 기본값과 스키마가 매칭됨
### 2단계: 에러 케이스 ### 2단계: 에러 케이스
- [x] 잘못된 overrides 입력 시 Zod 검증 실패 처리 (safeParse + console.warn + graceful fallback) - [x] 잘못된 overrides 입력 시 Zod 검증 실패 처리 (safeParse + console.warn + graceful fallback)
@ -38,7 +38,7 @@
## 진행 상태 ## 진행 상태
- [x] 레거시 컴포넌트 제거 완료 - [x] 레거시 컴포넌트 제거 완료
- [x] V2/Unified 스키마 정의 완료 - [x] V2/V2 스키마 정의 완료
- [x] 한 파일 통합 관리 완료 - [x] 한 파일 통합 관리 완료
# 프로젝트: 화면 복제 기능 개선 (DB 구조 개편 후) # 프로젝트: 화면 복제 기능 개선 (DB 구조 개편 후)

View File

@ -254,7 +254,7 @@ app.use("/api/table-categories", tableCategoryValueRoutes); // 카테고리 값
app.use("/api/code-merge", codeMergeRoutes); // 코드 병합 app.use("/api/code-merge", codeMergeRoutes); // 코드 병합
app.use("/api/numbering-rules", numberingRuleRoutes); // 채번 규칙 관리 app.use("/api/numbering-rules", numberingRuleRoutes); // 채번 규칙 관리
app.use("/api/entity-search", entitySearchRoutes); // 엔티티 검색 app.use("/api/entity-search", entitySearchRoutes); // 엔티티 검색
app.use("/api/entity", entityOptionsRouter); // 엔티티 옵션 (UnifiedSelect용) app.use("/api/entity", entityOptionsRouter); // 엔티티 옵션 (V2Select용)
app.use("/api/driver", driverRoutes); // 공차중계 운전자 관리 app.use("/api/driver", driverRoutes); // 공차중계 운전자 관리
app.use("/api/tax-invoice", taxInvoiceRoutes); // 세금계산서 관리 app.use("/api/tax-invoice", taxInvoiceRoutes); // 세금계산서 관리
app.use("/api/cascading-relations", cascadingRelationRoutes); // 연쇄 드롭다운 관계 관리 app.use("/api/cascading-relations", cascadingRelationRoutes); // 연쇄 드롭다운 관계 관리

View File

@ -244,7 +244,7 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => {
// 검색 조건 처리 // 검색 조건 처리
if (search && typeof search === "string" && search.trim()) { if (search && typeof search === "string" && search.trim()) {
// 통합 검색 // 통합 검색
searchType = "unified"; searchType = "v2";
const searchTerm = search.trim(); const searchTerm = search.trim();
whereConditions.push(`( whereConditions.push(`(

View File

@ -105,7 +105,7 @@ export async function getDistinctColumnValues(req: AuthenticatedRequest, res: Re
} }
/** /**
* API (UnifiedSelect용) * API (V2Select용)
* GET /api/entity/:tableName/options * GET /api/entity/:tableName/options
* *
* Query Params: * Query Params:

View File

@ -12,7 +12,7 @@ router.get("/:tableName", authenticateToken, searchEntity);
export default router; export default router;
// 엔티티 옵션 라우터 (UnifiedSelect용) // 엔티티 옵션 라우터 (V2Select용)
export const entityOptionsRouter = Router(); export const entityOptionsRouter = Router();
/** /**

View File

@ -11,7 +11,7 @@ import {
isValidWebType, isValidWebType,
WEB_TYPE_TO_POSTGRES_CONVERTER, WEB_TYPE_TO_POSTGRES_CONVERTER,
WEB_TYPE_VALIDATION_PATTERNS, WEB_TYPE_VALIDATION_PATTERNS,
} from "../types/unified-web-types"; } from "../types/v2-web-types";
import { DataflowControlService } from "./dataflowControlService"; import { DataflowControlService } from "./dataflowControlService";
// 테이블 컬럼 정보 // 테이블 컬럼 정보

View File

@ -987,7 +987,7 @@ class NumberingRuleService {
} }
// 카테고리 매핑에서 해당 값에 대한 형식 찾기 // 카테고리 매핑에서 해당 값에 대한 형식 찾기
// selectedValue는 valueCode일 수 있음 (UnifiedSelect에서 valueCode를 value로 사용) // selectedValue는 valueCode일 수 있음 (V2Select에서 valueCode를 value로 사용)
const selectedValueStr = String(selectedValue); const selectedValueStr = String(selectedValue);
const mapping = categoryMappings.find( const mapping = categoryMappings.find(
(m: any) => { (m: any) => {

View File

@ -1406,7 +1406,7 @@ export class ScreenManagementService {
*/ */
private inferWebType(dataType: string): WebType { private inferWebType(dataType: string): WebType {
// 통합 타입 매핑에서 import // 통합 타입 매핑에서 import
const { DB_TYPE_TO_WEB_TYPE } = require("../types/unified-web-types"); const { DB_TYPE_TO_WEB_TYPE } = require("../types/v2-web-types");
const lowerType = dataType.toLowerCase(); const lowerType = dataType.toLowerCase();
@ -1741,15 +1741,15 @@ export class ScreenManagementService {
? inputTypeMap.get(`${tableName}.${columnName}`) ? inputTypeMap.get(`${tableName}.${columnName}`)
: null; : null;
// 🆕 Unified 컴포넌트는 덮어쓰지 않음 (새로운 컴포넌트 시스템 보호) // 🆕 V2 컴포넌트는 덮어쓰지 않음 (새로운 컴포넌트 시스템 보호)
const savedComponentType = properties?.componentType; const savedComponentType = properties?.componentType;
const isUnifiedComponent = savedComponentType?.startsWith("unified-"); const isV2Component = savedComponentType?.startsWith("v2-");
const component = { const component = {
id: layout.component_id, id: layout.component_id,
// 🔥 최신 componentType이 있으면 type 덮어쓰기 (단, Unified 컴포넌트는 제외) // 🔥 최신 componentType이 있으면 type 덮어쓰기 (단, V2 컴포넌트는 제외)
type: isUnifiedComponent type: isV2Component
? layout.component_type as any // Unified는 저장된 값 유지 ? layout.component_type as any // V2는 저장된 값 유지
: (latestTypeInfo?.componentType || layout.component_type as any), : (latestTypeInfo?.componentType || layout.component_type as any),
position: { position: {
x: layout.position_x, x: layout.position_x,
@ -1759,8 +1759,8 @@ export class ScreenManagementService {
size: { width: layout.width, height: layout.height }, size: { width: layout.width, height: layout.height },
parentId: layout.parent_id, parentId: layout.parent_id,
...properties, ...properties,
// 🔥 최신 inputType이 있으면 widgetType, componentType 덮어쓰기 (단, Unified 컴포넌트는 제외) // 🔥 최신 inputType이 있으면 widgetType, componentType 덮어쓰기 (단, V2 컴포넌트는 제외)
...(!isUnifiedComponent && latestTypeInfo && { ...(!isV2Component && latestTypeInfo && {
widgetType: latestTypeInfo.inputType, widgetType: latestTypeInfo.inputType,
inputType: latestTypeInfo.inputType, inputType: latestTypeInfo.inputType,
componentType: latestTypeInfo.componentType, componentType: latestTypeInfo.componentType,

View File

@ -10,7 +10,7 @@ import {
EntityJoinResponse, EntityJoinResponse,
EntityJoinConfig, EntityJoinConfig,
} from "../types/tableManagement"; } from "../types/tableManagement";
import { WebType } from "../types/unified-web-types"; import { WebType } from "../types/v2-web-types";
import { entityJoinService } from "./entityJoinService"; import { entityJoinService } from "./entityJoinService";
import { referenceCacheService } from "./referenceCacheService"; import { referenceCacheService } from "./referenceCacheService";
@ -4301,7 +4301,7 @@ export class TableManagementService {
*/ */
private inferWebType(dataType: string): WebType { private inferWebType(dataType: string): WebType {
// 통합 타입 매핑에서 import // 통합 타입 매핑에서 import
const { DB_TYPE_TO_WEB_TYPE } = require("../types/unified-web-types"); const { DB_TYPE_TO_WEB_TYPE } = require("../types/v2-web-types");
const lowerType = dataType.toLowerCase(); const lowerType = dataType.toLowerCase();

View File

@ -5,7 +5,7 @@ export type ComponentType = "container" | "row" | "column" | "widget" | "group";
// 웹 타입 정의 // 웹 타입 정의
// WebType은 통합 타입에서 import (중복 정의 제거) // WebType은 통합 타입에서 import (중복 정의 제거)
import { WebType } from "./unified-web-types"; import { WebType } from "./v2-web-types";
export { WebType }; export { WebType };
// 위치 정보 // 위치 정보

View File

@ -264,7 +264,7 @@ export const WEB_TYPE_VALIDATION_PATTERNS: Record<WebType, RegExp | null> = {
}; };
// 업데이트된 웹 타입 옵션 (기존 WEB_TYPE_OPTIONS 대체) // 업데이트된 웹 타입 옵션 (기존 WEB_TYPE_OPTIONS 대체)
export const UNIFIED_WEB_TYPE_OPTIONS = [ export const V2_WEB_TYPE_OPTIONS = [
{ {
value: "text", value: "text",
label: "text", label: "text",

View File

@ -134,7 +134,7 @@ export const componentDefaults: Record<string, any> = {
"flow-widget": { type: "flow-widget", webType: "text", displayMode: "horizontal", allowDataMove: false, showStepCount: true }, "flow-widget": { type: "flow-widget", webType: "text", displayMode: "horizontal", allowDataMove: false, showStepCount: true },
"entity-search-input": { type: "entity-search-input", webType: "entity" }, "entity-search-input": { type: "entity-search-input", webType: "entity" },
"autocomplete-search-input": { type: "autocomplete-search-input", webType: "entity" }, "autocomplete-search-input": { type: "autocomplete-search-input", webType: "entity" },
"unified-list": { type: "unified-list", webType: "table" }, "v2-list": { type: "v2-list", webType: "table" },
"modal-repeater-table": { type: "modal-repeater-table", webType: "table", columns: [], multiSelect: true }, "modal-repeater-table": { type: "modal-repeater-table", webType: "table", columns: [], multiSelect: true },
"category-manager": { type: "category-manager", webType: "custom" }, "category-manager": { type: "category-manager", webType: "custom" },
"numbering-rule": { type: "numbering-rule", webType: "text" }, "numbering-rule": { type: "numbering-rule", webType: "text" },
@ -159,10 +159,10 @@ export const componentDefaults: Record<string, any> = {
"repeat-screen-modal": { type: "repeat-screen-modal", webType: "custom" }, "repeat-screen-modal": { type: "repeat-screen-modal", webType: "custom" },
"related-data-buttons": { type: "related-data-buttons", webType: "custom" }, "related-data-buttons": { type: "related-data-buttons", webType: "custom" },
"split-panel-layout2": { type: "split-panel-layout2", webType: "custom" }, "split-panel-layout2": { type: "split-panel-layout2", webType: "custom" },
"unified-input": { type: "unified-input", webType: "text" }, "v2-input": { type: "v2-input", webType: "text" },
"unified-select": { type: "unified-select", webType: "select" }, "v2-select": { type: "v2-select", webType: "select" },
"unified-date": { type: "unified-date", webType: "date" }, "v2-date": { type: "v2-date", webType: "date" },
"unified-repeater": { type: "unified-repeater", webType: "custom" }, "v2-repeater": { type: "v2-repeater", webType: "custom" },
"v2-repeat-container": { type: "v2-repeat-container", webType: "custom" }, "v2-repeat-container": { type: "v2-repeat-container", webType: "custom" },
}; };

View File

@ -527,7 +527,7 @@ flowchart TB
subgraph Usage["사용처"] subgraph Usage["사용처"]
U1[NumberingRuleDesigner.tsx] U1[NumberingRuleDesigner.tsx]
U2[UnifiedSelect.tsx] U2[V2Select.tsx]
U3[screenManagementService.ts] U3[screenManagementService.ts]
end end

View File

@ -185,7 +185,7 @@ POST /api/screen-management/screens/:screenId/layout-v2
| `@/lib/registry/components/flow-widget` | 플로우 위젯 | | `@/lib/registry/components/flow-widget` | 플로우 위젯 |
| `@/lib/registry/components/category-management` | 카테고리 관리 | | `@/lib/registry/components/category-management` | 카테고리 관리 |
| `@/lib/registry/components/pivot-table` | 피벗 테이블 | | `@/lib/registry/components/pivot-table` | 피벗 테이블 |
| `@/lib/registry/components/unified-grid` | 통합 그리드 | | `@/lib/registry/components/v2-grid` | 통합 그리드 |
--- ---

View File

@ -192,7 +192,7 @@ async function verifyRenderingEquality(layoutId: number) {
| 6 | select-basic | 129 | 100% | 낮음 | | 6 | select-basic | 129 | 100% | 낮음 |
| 7 | split-panel-layout | 129 | 100% | 높음 | | 7 | split-panel-layout | 129 | 100% | 높음 |
| 8 | date-input | 116 | 100% | 낮음 | | 8 | date-input | 116 | 100% | 낮음 |
| 9 | unified-list | 97 | 100% | 높음 | | 9 | v2-list | 97 | 100% | 높음 |
| 10 | number-input | 87 | 100% | 낮음 | | 10 | number-input | 87 | 100% | 낮음 |
### 4.2 발견된 문제점 ### 4.2 발견된 문제점
@ -433,7 +433,7 @@ DROP TABLE screen_layouts_v2;
- [ ] select-basic - [ ] select-basic
- [ ] split-panel-layout - [ ] split-panel-layout
- [ ] date-input - [ ] date-input
- [ ] unified-list - [ ] v2-list
- [ ] number-input - [ ] number-input
### Step 3: 마이그레이션 스크립트 ### Step 3: 마이그레이션 스크립트

View File

@ -0,0 +1,192 @@
# V2 Components 구현 완료 보고서
## 구현 일시
2024-12-19
## 구현된 컴포넌트 목록 (10개)
### Phase 1: 핵심 입력 컴포넌트
| 컴포넌트 | 파일 | 모드/타입 | 설명 |
| :---------------- | :------------------ | :-------------------------------------------- | :---------------------- |
| **V2Input** | `V2Input.tsx` | text, number, password, slider, color, button | 통합 입력 컴포넌트 |
| **V2Select** | `V2Select.tsx` | dropdown, radio, check, tag, toggle, swap | 통합 선택 컴포넌트 |
| **V2Date** | `V2Date.tsx` | date, time, datetime + range | 통합 날짜/시간 컴포넌트 |
### Phase 2: 레이아웃 및 그룹 컴포넌트
| 컴포넌트 | 파일 | 모드/타입 | 설명 |
| :---------------- | :------------------ | :-------------------------------------------------------- | :--------------------- |
| **V2List** | `V2List.tsx` | table, card, kanban, list | 통합 리스트 컴포넌트 |
| **V2Layout** | `V2Layout.tsx` | grid, split, flex, divider, screen-embed | 통합 레이아웃 컴포넌트 |
| **V2Group** | `V2Group.tsx` | tabs, accordion, section, card-section, modal, form-modal | 통합 그룹 컴포넌트 |
### Phase 3: 미디어 및 비즈니스 컴포넌트
| 컴포넌트 | 파일 | 모드/타입 | 설명 |
| :------------------- | :--------------------- | :------------------------------------------------------------- | :---------------------- |
| **V2Media** | `V2Media.tsx` | file, image, video, audio | 통합 미디어 컴포넌트 |
| **V2Biz** | `V2Biz.tsx` | flow, rack, map, numbering, category, mapping, related-buttons | 통합 비즈니스 컴포넌트 |
| **V2Hierarchy** | `V2Hierarchy.tsx` | tree, org, bom, cascading | 통합 계층 구조 컴포넌트 |
---
## 공통 인프라
### 설정 패널
- **DynamicConfigPanel**: JSON Schema 기반 동적 설정 UI 생성
### 렌더러
- **V2ComponentRenderer**: v2Type에 따른 동적 컴포넌트 렌더링
---
## 파일 구조
```
frontend/components/v2/
├── index.ts # 모듈 인덱스
├── V2ComponentRenderer.tsx # 동적 렌더러
├── DynamicConfigPanel.tsx # JSON Schema 설정 패널
├── V2Input.tsx # 통합 입력
├── V2Select.tsx # 통합 선택
├── V2Date.tsx # 통합 날짜
├── V2List.tsx # 통합 리스트
├── V2Layout.tsx # 통합 레이아웃
├── V2Group.tsx # 통합 그룹
├── V2Media.tsx # 통합 미디어
├── V2Biz.tsx # 통합 비즈니스
└── V2Hierarchy.tsx # 통합 계층
frontend/types/
└── v2-components.ts # 타입 정의
db/migrations/
└── v2_component_schema.sql # DB 스키마 (미실행)
```
---
## 사용 예시
### 기본 사용법
```tsx
import {
V2Input,
V2Select,
V2Date,
V2List,
V2ComponentRenderer
} from "@/components/v2";
// V2Input 사용
<V2Input
id="name"
label="이름"
required
config={{ type: "text", placeholder: "이름을 입력하세요" }}
value={name}
onChange={setName}
/>
// V2Select 사용
<V2Select
id="status"
label="상태"
config={{
mode: "dropdown",
source: "code",
codeGroup: "ORDER_STATUS",
searchable: true
}}
value={status}
onChange={setStatus}
/>
// V2Date 사용
<V2Date
id="orderDate"
label="주문일"
config={{ type: "date", format: "YYYY-MM-DD" }}
value={orderDate}
onChange={setOrderDate}
/>
// V2List 사용
<V2List
id="orderList"
label="주문 목록"
config={{
viewMode: "table",
searchable: true,
pageable: true,
pageSize: 10,
columns: [
{ field: "orderId", header: "주문번호", sortable: true },
{ field: "customerName", header: "고객명" },
{ field: "orderDate", header: "주문일", format: "date" },
]
}}
data={orders}
onRowClick={handleRowClick}
/>
```
### 동적 렌더링
```tsx
import { V2ComponentRenderer } from "@/components/v2";
// v2Type에 따라 자동으로 적절한 컴포넌트 렌더링
<V2ComponentRenderer
props={{
v2Type: "V2Input",
id: "dynamicField",
label: "동적 필드",
config: { type: "text" },
value: fieldValue,
onChange: setFieldValue,
}}
/>;
```
---
## 주의사항
### 기존 컴포넌트와의 공존
1. **기존 컴포넌트는 그대로 유지**: 모든 레거시 컴포넌트는 정상 동작
2. **신규 화면에서만 V2 컴포넌트 사용**: 기존 화면에 영향 없음
3. **마이그레이션 없음**: 자동 마이그레이션 진행하지 않음
### 데이터베이스 마이그레이션
`db/migrations/v2_component_schema.sql` 파일은 아직 실행되지 않았습니다.
필요시 수동으로 실행해야 합니다:
```bash
psql -h localhost -U postgres -d plm_db -f db/migrations/v2_component_schema.sql
```
---
## 다음 단계 (선택)
1. **화면 관리 에디터 통합**: V2 컴포넌트를 화면 에디터의 컴포넌트 팔레트에 추가
2. **기존 비즈니스 컴포넌트 연동**: V2Biz의 플레이스홀더를 실제 구현으로 교체
3. **테스트 페이지 작성**: 모든 V2 컴포넌트 데모 페이지
4. **문서화**: 각 컴포넌트별 상세 사용 가이드
---
## 관련 문서
- `PLAN_RENEWAL.md`: 리뉴얼 계획서
- `docs/phase0-component-usage-analysis.md`: 컴포넌트 사용 현황 분석
- `docs/phase0-migration-strategy.md`: 마이그레이션 전략 (참고용)

View File

@ -95,7 +95,7 @@
| 파일 | 참조 횟수 | 영향도 | 용도 | | 파일 | 참조 횟수 | 영향도 | 용도 |
|------|----------|--------|------| |------|----------|--------|------|
| `UnifiedRepeater.tsx` | 3회 | 🟢 낮음 | 타입 주석 | | `V2Repeater.tsx` | 3회 | 🟢 낮음 | 타입 주석 |
| `ScreenDesigner.tsx` | 2회 | 🟢 낮음 | 타입 주석 | | `ScreenDesigner.tsx` | 2회 | 🟢 낮음 | 타입 주석 |
| `ButtonConfigPanel.tsx` | 2회 | 🟢 낮음 | 타입 주석 | | `ButtonConfigPanel.tsx` | 2회 | 🟢 낮음 | 타입 주석 |
| `ScreenRelationFlow.tsx` | 2회 | 🟢 낮음 | 타입 주석 | | `ScreenRelationFlow.tsx` | 2회 | 🟢 낮음 | 타입 주석 |

View File

@ -2,7 +2,7 @@
## 1. 개요 ## 1. 개요
현재 **68개 이상**으로 파편화된 화면 관리 컴포넌트들을 **9개의 핵심 통합 컴포넌트(Unified Components)**로 재편합니다. 현재 **68개 이상**으로 파편화된 화면 관리 컴포넌트들을 **9개의 핵심 통합 컴포넌트(V2 Components)**로 재편합니다.
각 컴포넌트는 **속성(Config)** 설정을 통해 다양한 형태(View Mode)와 기능(Behavior)을 수행하도록 설계되어, 유지보수성과 확장성을 극대화합니다. 각 컴포넌트는 **속성(Config)** 설정을 통해 다양한 형태(View Mode)와 기능(Behavior)을 수행하도록 설계되어, 유지보수성과 확장성을 극대화합니다.
### 현재 컴포넌트 현황 (AS-IS) ### 현재 컴포넌트 현황 (AS-IS)
@ -24,11 +24,11 @@
| 통합 컴포넌트 (TO-BE) | 포함되는 기존 컴포넌트 (AS-IS) | 핵심 속성 (Configuration) | | 통합 컴포넌트 (TO-BE) | 포함되는 기존 컴포넌트 (AS-IS) | 핵심 속성 (Configuration) |
| :-------------------- | :------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------- | | :-------------------- | :------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------- |
| **1. Unified Select** | Select, Radio, Checkbox, Boolean, Code, Entity, Combobox, Toggle | **`mode`**: "dropdown" / "radio" / "check" / "tag"<br>**`source`**: "static" / "code" / "db" / "api"<br>**`dependency`**: { parentField: "..." } | | **1. V2 Select** | Select, Radio, Checkbox, Boolean, Code, Entity, Combobox, Toggle | **`mode`**: "dropdown" / "radio" / "check" / "tag"<br>**`source`**: "static" / "code" / "db" / "api"<br>**`dependency`**: { parentField: "..." } |
| **2. Unified Input** | Text, Number, Email, Tel, Password, Color, Search, Integer, Decimal | **`type`**: "text" / "number" / "password"<br>**`format`**: "email", "currency", "biz_no"<br>**`mask`**: "000-0000-0000" | | **2. V2 Input** | Text, Number, Email, Tel, Password, Color, Search, Integer, Decimal | **`type`**: "text" / "number" / "password"<br>**`format`**: "email", "currency", "biz_no"<br>**`mask`**: "000-0000-0000" |
| **3. Unified Date** | Date, Time, DateTime, DateRange, Month, Year | **`type`**: "date" / "time" / "datetime"<br>**`range`**: true/false | | **3. V2 Date** | Date, Time, DateTime, DateRange, Month, Year | **`type`**: "date" / "time" / "datetime"<br>**`range`**: true/false |
| **4. Unified Text** | Textarea, RichEditor, Markdown, HTML | **`mode`**: "simple" / "rich" / "code"<br>**`rows`**: number | | **4. V2 Text** | Textarea, RichEditor, Markdown, HTML | **`mode`**: "simple" / "rich" / "code"<br>**`rows`**: number |
| **5. Unified Media** | File, Image, Video, Audio, Attachment | **`type`**: "file" / "image"<br>**`multiple`**: true/false<br>**`preview`**: true/false | | **5. V2 Media** | File, Image, Video, Audio, Attachment | **`type`**: "file" / "image"<br>**`multiple`**: true/false<br>**`preview`**: true/false |
### B. 구조/데이터 위젯 (Structure & Data Widgets) - 4종 ### B. 구조/데이터 위젯 (Structure & Data Widgets) - 4종
@ -36,10 +36,10 @@
| 통합 컴포넌트 (TO-BE) | 포함되는 기존 컴포넌트 (AS-IS) | 핵심 속성 (Configuration) | 활용 예시 | | 통합 컴포넌트 (TO-BE) | 포함되는 기존 컴포넌트 (AS-IS) | 핵심 속성 (Configuration) | 활용 예시 |
| :-------------------- | :-------------------------------------------------- | :------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------- | | :-------------------- | :-------------------------------------------------- | :------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------- |
| **6. Unified List** | **Table, Card List, Repeater, DataGrid, List View** | **`viewMode`**: "table" / "card" / "kanban"<br>**`editable`**: true/false | - `viewMode='table'`: 엑셀형 리스트<br>- `viewMode='card'`: **카드 디스플레이**<br>- `editable=true`: **반복 필드 그룹** | | **6. V2 List** | **Table, Card List, Repeater, DataGrid, List View** | **`viewMode`**: "table" / "card" / "kanban"<br>**`editable`**: true/false | - `viewMode='table'`: 엑셀형 리스트<br>- `viewMode='card'`: **카드 디스플레이**<br>- `editable=true`: **반복 필드 그룹** |
| **7. Unified Layout** | **Row, Col, Split Panel, Grid, Spacer** | **`type`**: "grid" / "split" / "flex"<br>**`columns`**: number | - `type='split'`: **화면 분할 패널**<br>- `type='grid'`: 격자 레이아웃 | | **7. V2 Layout** | **Row, Col, Split Panel, Grid, Spacer** | **`type`**: "grid" / "split" / "flex"<br>**`columns`**: number | - `type='split'`: **화면 분할 패널**<br>- `type='grid'`: 격자 레이아웃 |
| **8. Unified Group** | Tab, Accordion, FieldSet, Modal, Section | **`type`**: "tab" / "accordion" / "modal" | - 탭이나 아코디언으로 내용 그룹화 | | **8. V2 Group** | Tab, Accordion, FieldSet, Modal, Section | **`type`**: "tab" / "accordion" / "modal" | - 탭이나 아코디언으로 내용 그룹화 |
| **9. Unified Biz** | **Rack Structure**, Calendar, Gantt | **`type`**: "rack" / "calendar" / "gantt" | - `type='rack'`: **랙 구조 설정**<br>- 특수 비즈니스 로직 플러그인 탑재 | | **9. V2 Biz** | **Rack Structure**, Calendar, Gantt | **`type`**: "rack" / "calendar" / "gantt" | - `type='rack'`: **랙 구조 설정**<br>- 특수 비즈니스 로직 플러그인 탑재 |
### C. Config Panel 통합 전략 (핵심) ### C. Config Panel 통합 전략 (핵심)
@ -60,16 +60,16 @@
### Case 1: "테이블을 카드 리스트로 변경" ### Case 1: "테이블을 카드 리스트로 변경"
- **AS-IS**: `DataTable` 컴포넌트를 삭제하고 `CardList` 컴포넌트를 새로 추가해야 함. - **AS-IS**: `DataTable` 컴포넌트를 삭제하고 `CardList` 컴포넌트를 새로 추가해야 함.
- **TO-BE**: `UnifiedList`의 속성창에서 **[View Mode]**를 `Table``Card`로 변경하면 즉시 반영. - **TO-BE**: `V2List`의 속성창에서 **[View Mode]**를 `Table``Card`로 변경하면 즉시 반영.
### Case 2: "단일 선택을 라디오 버튼으로 변경" ### Case 2: "단일 선택을 라디오 버튼으로 변경"
- **AS-IS**: `SelectWidget`을 삭제하고 `RadioWidget` 추가. - **AS-IS**: `SelectWidget`을 삭제하고 `RadioWidget` 추가.
- **TO-BE**: `UnifiedSelect` 속성창에서 **[Display Mode]**를 `Dropdown``Radio`로 변경. - **TO-BE**: `V2Select` 속성창에서 **[Display Mode]**를 `Dropdown``Radio`로 변경.
### Case 3: "입력 폼에 반복 필드(Repeater) 추가" ### Case 3: "입력 폼에 반복 필드(Repeater) 추가"
- **TO-BE**: `UnifiedList` 컴포넌트 배치 후 `editable: true`, `viewMode: "table"` 설정. - **TO-BE**: `V2List` 컴포넌트 배치 후 `editable: true`, `viewMode: "table"` 설정.
--- ---
@ -80,7 +80,7 @@
통합 작업 전 필수 분석 및 설계를 진행합니다. 통합 작업 전 필수 분석 및 설계를 진행합니다.
- [ ] 기존 컴포넌트 사용 현황 분석 (화면별 위젯 사용 빈도 조사) - [ ] 기존 컴포넌트 사용 현황 분석 (화면별 위젯 사용 빈도 조사)
- [ ] 데이터 마이그레이션 전략 설계 (`widgetType` → `UnifiedWidget.type` 매핑 정의) - [ ] 데이터 마이그레이션 전략 설계 (`widgetType` → `V2Widget.type` 매핑 정의)
- [ ] `sys_input_type` 테이블 JSON Schema 설계 - [ ] `sys_input_type` 테이블 JSON Schema 설계
- [ ] DynamicConfigPanel 프로토타입 설계 - [ ] DynamicConfigPanel 프로토타입 설계
@ -88,9 +88,9 @@
가장 중복이 많고 효과가 즉각적인 입력 필드부터 통합합니다. 가장 중복이 많고 효과가 즉각적인 입력 필드부터 통합합니다.
- [ ] **UnifiedInput 구현**: Text, Number, Email, Tel, Password 통합 - [ ] **V2Input 구현**: Text, Number, Email, Tel, Password 통합
- [ ] **UnifiedSelect 구현**: Select, Radio, Checkbox, Boolean 통합 - [ ] **V2Select 구현**: Select, Radio, Checkbox, Boolean 통합
- [ ] **UnifiedDate 구현**: Date, DateTime, Time 통합 - [ ] **V2Date 구현**: Date, DateTime, Time 통합
- [ ] 기존 위젯과 **병행 운영** (deprecated 마킹, 삭제하지 않음) - [ ] 기존 위젯과 **병행 운영** (deprecated 마킹, 삭제하지 않음)
### Phase 2: Config Panel 통합 (2주) ### Phase 2: Config Panel 통합 (2주)
@ -105,15 +105,15 @@
프로젝트의 데이터를 보여주는 핵심 뷰를 통합합니다. 프로젝트의 데이터를 보여주는 핵심 뷰를 통합합니다.
- [ ] **UnifiedList 구현**: Table, Card, Repeater 통합 렌더러 개발 - [ ] **V2List 구현**: Table, Card, Repeater 통합 렌더러 개발
- [ ] **UnifiedLayout 구현**: Split Panel, Grid, Flex 통합 - [ ] **V2Layout 구현**: Split Panel, Grid, Flex 통합
- [ ] **UnifiedGroup 구현**: Tab, Accordion, Modal 통합 - [ ] **V2Group 구현**: Tab, Accordion, Modal 통합
### Phase 4: 안정화 및 마이그레이션 (2주) ### Phase 4: 안정화 및 마이그레이션 (2주)
신규 컴포넌트 안정화 후 점진적 전환을 진행합니다. 신규 컴포넌트 안정화 후 점진적 전환을 진행합니다.
- [ ] 신규 화면은 Unified 컴포넌트만 사용하도록 가이드 - [ ] 신규 화면은 V2 컴포넌트만 사용하도록 가이드
- [ ] 기존 화면 데이터 마이그레이션 스크립트 개발 - [ ] 기존 화면 데이터 마이그레이션 스크립트 개발
- [ ] 마이그레이션 테스트 (스테이징 환경) - [ ] 마이그레이션 테스트 (스테이징 환경)
- [ ] 문서화 및 개발 가이드 작성 - [ ] 문서화 및 개발 가이드 작성
@ -122,7 +122,7 @@
충분한 안정화 기간 후 레거시 컴포넌트 정리를 검토합니다. 충분한 안정화 기간 후 레거시 컴포넌트 정리를 검토합니다.
- [ ] 사용 현황 재분석 (Unified 전환율 확인) - [ ] 사용 현황 재분석 (V2 전환율 확인)
- [ ] 미전환 화면 목록 정리 - [ ] 미전환 화면 목록 정리
- [ ] 레거시 컴포넌트 삭제 여부 결정 (별도 회의) - [ ] 레거시 컴포넌트 삭제 여부 결정 (별도 회의)
@ -132,27 +132,27 @@
### 5.1 위젯 타입 매핑 테이블 ### 5.1 위젯 타입 매핑 테이블
기존 `widgetType`을 신규 Unified 컴포넌트로 매핑합니다. 기존 `widgetType`을 신규 V2 컴포넌트로 매핑합니다.
| 기존 widgetType | 신규 컴포넌트 | 속성 설정 | | 기존 widgetType | 신규 컴포넌트 | 속성 설정 |
| :-------------- | :------------ | :------------------------------ | | :-------------- | :------------ | :------------------------------ |
| `text` | UnifiedInput | `type: "text"` | | `text` | V2Input | `type: "text"` |
| `number` | UnifiedInput | `type: "number"` | | `number` | V2Input | `type: "number"` |
| `email` | UnifiedInput | `type: "text", format: "email"` | | `email` | V2Input | `type: "text", format: "email"` |
| `tel` | UnifiedInput | `type: "text", format: "tel"` | | `tel` | V2Input | `type: "text", format: "tel"` |
| `select` | UnifiedSelect | `mode: "dropdown"` | | `select` | V2Select | `mode: "dropdown"` |
| `radio` | UnifiedSelect | `mode: "radio"` | | `radio` | V2Select | `mode: "radio"` |
| `checkbox` | UnifiedSelect | `mode: "check"` | | `checkbox` | V2Select | `mode: "check"` |
| `date` | UnifiedDate | `type: "date"` | | `date` | V2Date | `type: "date"` |
| `datetime` | UnifiedDate | `type: "datetime"` | | `datetime` | V2Date | `type: "datetime"` |
| `textarea` | UnifiedText | `mode: "simple"` | | `textarea` | V2Text | `mode: "simple"` |
| `file` | UnifiedMedia | `type: "file"` | | `file` | V2Media | `type: "file"` |
| `image` | UnifiedMedia | `type: "image"` | | `image` | V2Media | `type: "image"` |
### 5.2 마이그레이션 원칙 ### 5.2 마이그레이션 원칙
1. **비파괴적 전환**: 기존 데이터 구조 유지, 신규 필드 추가 방식 1. **비파괴적 전환**: 기존 데이터 구조 유지, 신규 필드 추가 방식
2. **하위 호환성**: 기존 `widgetType` 필드는 유지, `unifiedType` 필드 추가 2. **하위 호환성**: 기존 `widgetType` 필드는 유지, `v2Type` 필드 추가
3. **점진적 전환**: 화면 수정 시점에 자동 또는 수동 전환 3. **점진적 전환**: 화면 수정 시점에 자동 또는 수동 전환
--- ---
@ -183,7 +183,7 @@
현재 `frontend/lib/registry/components/`에 등록된 모든 컴포넌트의 통합 가능 여부를 분석했습니다. 현재 `frontend/lib/registry/components/`에 등록된 모든 컴포넌트의 통합 가능 여부를 분석했습니다.
#### UnifiedInput으로 통합 (4개) #### V2Input으로 통합 (4개)
| 현재 컴포넌트 | 매핑 속성 | 비고 | | 현재 컴포넌트 | 매핑 속성 | 비고 |
| :------------- | :--------------- | :------------- | | :------------- | :--------------- | :------------- |
@ -192,7 +192,7 @@
| slider-basic | `type: "slider"` | 속성 추가 필요 | | slider-basic | `type: "slider"` | 속성 추가 필요 |
| button-primary | `type: "button"` | 별도 검토 | | button-primary | `type: "button"` | 별도 검토 |
#### UnifiedSelect로 통합 (8개) #### V2Select로 통합 (8개)
| 현재 컴포넌트 | 매핑 속성 | 비고 | | 현재 컴포넌트 | 매핑 속성 | 비고 |
| :------------------------ | :----------------------------------- | :------------- | | :------------------------ | :----------------------------------- | :------------- |
@ -205,19 +205,19 @@
| mail-recipient-selector | `mode: "multi", type: "email"` | 복합 컴포넌트 | | mail-recipient-selector | `mode: "multi", type: "email"` | 복합 컴포넌트 |
| location-swap-selector | `mode: "swap"` | 특수 UI | | location-swap-selector | `mode: "swap"` | 특수 UI |
#### UnifiedDate로 통합 (1개) #### V2Date로 통합 (1개)
| 현재 컴포넌트 | 매핑 속성 | 비고 | | 현재 컴포넌트 | 매핑 속성 | 비고 |
| :------------ | :------------- | :--- | | :------------ | :------------- | :--- |
| date-input | `type: "date"` | | | date-input | `type: "date"` | |
#### UnifiedText로 통합 (1개) #### V2Text로 통합 (1개)
| 현재 컴포넌트 | 매핑 속성 | 비고 | | 현재 컴포넌트 | 매핑 속성 | 비고 |
| :------------- | :--------------- | :--- | | :------------- | :--------------- | :--- |
| textarea-basic | `mode: "simple"` | | | textarea-basic | `mode: "simple"` | |
#### UnifiedMedia로 통합 (3개) #### V2Media로 통합 (3개)
| 현재 컴포넌트 | 매핑 속성 | 비고 | | 현재 컴포넌트 | 매핑 속성 | 비고 |
| :------------ | :------------------------------ | :--- | | :------------ | :------------------------------ | :--- |
@ -225,7 +225,7 @@
| image-widget | `type: "image"` | | | image-widget | `type: "image"` | |
| image-display | `type: "image", readonly: true` | | | image-display | `type: "image", readonly: true` | |
#### UnifiedList로 통합 (8개) #### V2List로 통합 (8개)
| 현재 컴포넌트 | 매핑 속성 | 비고 | | 현재 컴포넌트 | 매핑 속성 | 비고 |
| :-------------------- | :------------------------------------ | :------------ | | :-------------------- | :------------------------------------ | :------------ |
@ -238,7 +238,7 @@
| table-search-widget | `viewMode: "table", searchable: true` | | | table-search-widget | `viewMode: "table", searchable: true` | |
| tax-invoice-list | `viewMode: "table", bizType: "tax"` | 특수 비즈니스 | | tax-invoice-list | `viewMode: "table", bizType: "tax"` | 특수 비즈니스 |
#### UnifiedLayout으로 통합 (4개) #### V2Layout으로 통합 (4개)
| 현재 컴포넌트 | 매핑 속성 | 비고 | | 현재 컴포넌트 | 매핑 속성 | 비고 |
| :------------------ | :-------------------------- | :------------- | | :------------------ | :-------------------------- | :------------- |
@ -247,7 +247,7 @@
| divider-line | `type: "divider"` | 속성 추가 필요 | | divider-line | `type: "divider"` | 속성 추가 필요 |
| screen-split-panel | `type: "screen-embed"` | 화면 임베딩 | | screen-split-panel | `type: "screen-embed"` | 화면 임베딩 |
#### UnifiedGroup으로 통합 (5개) #### V2Group으로 통합 (5개)
| 현재 컴포넌트 | 매핑 속성 | 비고 | | 현재 컴포넌트 | 매핑 속성 | 비고 |
| :------------------- | :--------------------- | :------------ | | :------------------- | :--------------------- | :------------ |
@ -257,7 +257,7 @@
| section-card | `type: "card-section"` | | | section-card | `type: "card-section"` | |
| universal-form-modal | `type: "form-modal"` | 복합 컴포넌트 | | universal-form-modal | `type: "form-modal"` | 복합 컴포넌트 |
#### UnifiedBiz로 통합 (7개) #### V2Biz로 통합 (7개)
| 현재 컴포넌트 | 매핑 속성 | 비고 | | 현재 컴포넌트 | 매핑 속성 | 비고 |
| :-------------------- | :------------------------ | :--------------- | | :-------------------- | :------------------------ | :--------------- |
@ -274,8 +274,8 @@
| 현재 컴포넌트 | 문제점 | 제안 | | 현재 컴포넌트 | 문제점 | 제안 |
| :-------------------------- | :------------------- | :------------------------------ | | :-------------------------- | :------------------- | :------------------------------ |
| conditional-container | 조건부 렌더링 로직 | 공통 속성으로 분리 | | conditional-container | 조건부 렌더링 로직 | 공통 속성으로 분리 |
| selected-items-detail-input | 복합 (선택+상세입력) | UnifiedList + UnifiedGroup 조합 | | selected-items-detail-input | 복합 (선택+상세입력) | V2List + V2Group 조합 |
| text-display | 읽기 전용 텍스트 | UnifiedInput (readonly: true) | | text-display | 읽기 전용 텍스트 | V2Input (readonly: true) |
### 8.2 매핑 분석 결과 ### 8.2 매핑 분석 결과
@ -291,7 +291,7 @@
### 8.3 속성 확장 필요 사항 ### 8.3 속성 확장 필요 사항
#### UnifiedInput 속성 확장 #### V2Input 속성 확장
```typescript ```typescript
// 기존 // 기존
@ -301,7 +301,7 @@ type: "text" | "number" | "password";
type: "text" | "number" | "password" | "slider" | "color" | "button"; type: "text" | "number" | "password" | "slider" | "color" | "button";
``` ```
#### UnifiedSelect 속성 확장 #### V2Select 속성 확장
```typescript ```typescript
// 기존 // 기존
@ -311,7 +311,7 @@ mode: "dropdown" | "radio" | "check" | "tag";
mode: "dropdown" | "radio" | "check" | "tag" | "toggle" | "swap"; mode: "dropdown" | "radio" | "check" | "tag" | "toggle" | "swap";
``` ```
#### UnifiedLayout 속성 확장 #### V2Layout 속성 확장
```typescript ```typescript
// 기존 // 기존
@ -326,8 +326,8 @@ type: "grid" | "split" | "flex" | "divider" | "screen-embed";
`conditional-container`의 기능을 모든 컴포넌트에서 사용 가능한 공통 속성으로 분리합니다. `conditional-container`의 기능을 모든 컴포넌트에서 사용 가능한 공통 속성으로 분리합니다.
```typescript ```typescript
// 모든 Unified 컴포넌트에 적용 가능한 공통 속성 // 모든 V2 컴포넌트에 적용 가능한 공통 속성
interface BaseUnifiedProps { interface BaseV2Props {
// ... 기존 속성 // ... 기존 속성
/** 조건부 렌더링 설정 */ /** 조건부 렌더링 설정 */
@ -356,12 +356,12 @@ DB 테이블 `cascading_hierarchy_group`에서 4가지 계층 타입을 지원
| **BOM** | 자재명세서 구조 | 부품 > 하위부품 | | **BOM** | 자재명세서 구조 | 부품 > 하위부품 |
| **TREE** | 일반 트리 | 카테고리 | | **TREE** | 일반 트리 | 카테고리 |
### 9.2 통합 방안: UnifiedHierarchy 신설 (10번째 컴포넌트) ### 9.2 통합 방안: V2Hierarchy 신설 (10번째 컴포넌트)
계층 구조는 일반 입력/표시 위젯과 성격이 다르므로 **별도 컴포넌트로 분리**합니다. 계층 구조는 일반 입력/표시 위젯과 성격이 다르므로 **별도 컴포넌트로 분리**합니다.
```typescript ```typescript
interface UnifiedHierarchyProps { interface V2HierarchyProps {
/** 계층 유형 */ /** 계층 유형 */
type: "tree" | "org" | "bom" | "cascading"; type: "tree" | "org" | "bom" | "cascading";
@ -400,16 +400,16 @@ interface UnifiedHierarchyProps {
| # | 컴포넌트 | 역할 | 커버 범위 | | # | 컴포넌트 | 역할 | 커버 범위 |
| :-: | :------------------- | :------------- | :----------------------------------- | | :-: | :------------------- | :------------- | :----------------------------------- |
| 1 | **UnifiedInput** | 단일 값 입력 | text, number, slider, button 등 | | 1 | **V2Input** | 단일 값 입력 | text, number, slider, button 등 |
| 2 | **UnifiedSelect** | 선택 입력 | dropdown, radio, checkbox, toggle 등 | | 2 | **V2Select** | 선택 입력 | dropdown, radio, checkbox, toggle 등 |
| 3 | **UnifiedDate** | 날짜/시간 입력 | date, datetime, time, range | | 3 | **V2Date** | 날짜/시간 입력 | date, datetime, time, range |
| 4 | **UnifiedText** | 다중 행 텍스트 | textarea, rich editor, markdown | | 4 | **V2Text** | 다중 행 텍스트 | textarea, rich editor, markdown |
| 5 | **UnifiedMedia** | 파일/미디어 | file, image, video, audio | | 5 | **V2Media** | 파일/미디어 | file, image, video, audio |
| 6 | **UnifiedList** | 데이터 목록 | table, card, repeater, kanban | | 6 | **V2List** | 데이터 목록 | table, card, repeater, kanban |
| 7 | **UnifiedLayout** | 레이아웃 배치 | grid, split, flex, divider | | 7 | **V2Layout** | 레이아웃 배치 | grid, split, flex, divider |
| 8 | **UnifiedGroup** | 콘텐츠 그룹화 | tabs, accordion, section, modal | | 8 | **V2Group** | 콘텐츠 그룹화 | tabs, accordion, section, modal |
| 9 | **UnifiedBiz** | 비즈니스 특화 | flow, rack, map, numbering 등 | | 9 | **V2Biz** | 비즈니스 특화 | flow, rack, map, numbering 등 |
| 10 | **UnifiedHierarchy** | 계층 구조 | tree, org, bom, cascading | | 10 | **V2Hierarchy** | 계층 구조 | tree, org, bom, cascading |
--- ---
@ -443,14 +443,14 @@ interface UnifiedHierarchyProps {
### 11.3 속성 통합 설계 ### 11.3 속성 통합 설계
#### 2단계 연쇄 → UnifiedSelect 속성 #### 2단계 연쇄 → V2Select 속성
```typescript ```typescript
// AS-IS: 별도 관리 메뉴에서 정의 후 참조 // AS-IS: 별도 관리 메뉴에서 정의 후 참조
<SelectWidget cascadingRelation="WAREHOUSE_LOCATION" /> <SelectWidget cascadingRelation="WAREHOUSE_LOCATION" />
// TO-BE: 컴포넌트 속성에서 직접 정의 // TO-BE: 컴포넌트 속성에서 직접 정의
<UnifiedSelect <V2Select
source="db" source="db"
table="warehouse_location" table="warehouse_location"
valueColumn="location_code" valueColumn="location_code"
@ -470,7 +470,7 @@ interface UnifiedHierarchyProps {
// cascading_condition 테이블에 저장 // cascading_condition 테이블에 저장
// TO-BE: 모든 컴포넌트에 공통 속성으로 적용 // TO-BE: 모든 컴포넌트에 공통 속성으로 적용
<UnifiedInput <V2Input
conditional={{ conditional={{
enabled: true, enabled: true,
field: "order_type", // 참조할 필드 field: "order_type", // 참조할 필드
@ -487,7 +487,7 @@ interface UnifiedHierarchyProps {
// AS-IS: cascading_auto_fill_group 테이블에 정의 // AS-IS: cascading_auto_fill_group 테이블에 정의
// TO-BE: 컴포넌트 속성에서 직접 정의 // TO-BE: 컴포넌트 속성에서 직접 정의
<UnifiedInput <V2Input
autoFill={{ autoFill={{
enabled: true, enabled: true,
sourceTable: "company_mng", // 조회할 테이블 sourceTable: "company_mng", // 조회할 테이블
@ -504,7 +504,7 @@ interface UnifiedHierarchyProps {
// AS-IS: cascading_mutual_exclusion 테이블에 정의 // AS-IS: cascading_mutual_exclusion 테이블에 정의
// TO-BE: 컴포넌트 속성에서 직접 정의 // TO-BE: 컴포넌트 속성에서 직접 정의
<UnifiedSelect <V2Select
mutualExclusion={{ mutualExclusion={{
enabled: true, enabled: true,
targetField: "sub_category", // 상호 배제 대상 필드 targetField: "sub_category", // 상호 배제 대상 필드
@ -518,7 +518,7 @@ interface UnifiedHierarchyProps {
| 현재 메뉴 | TO-BE | 비고 | | 현재 메뉴 | TO-BE | 비고 |
| :-------------------------- | :----------------------- | :-------------------- | | :-------------------------- | :----------------------- | :-------------------- |
| **연쇄 드롭다운 통합 관리** | **삭제** | 6개 탭 전체 제거 | | **연쇄 드롭다운 통합 관리** | **삭제** | 6개 탭 전체 제거 |
| ├─ 2단계 연쇄관계 | UnifiedSelect 속성 | inline 정의 | | ├─ 2단계 연쇄관계 | V2Select 속성 | inline 정의 |
| ├─ 다단계 계층 | **테이블관리로 이동** | 복잡한 구조 유지 필요 | | ├─ 다단계 계층 | **테이블관리로 이동** | 복잡한 구조 유지 필요 |
| ├─ 조건부 필터 | 공통 conditional 속성 | 모든 컴포넌트에 적용 | | ├─ 조건부 필터 | 공통 conditional 속성 | 모든 컴포넌트에 적용 |
| ├─ 자동 입력 | autoFill 속성 | 컴포넌트별 정의 | | ├─ 자동 입력 | autoFill 속성 | 컴포넌트별 정의 |
@ -557,21 +557,21 @@ interface UnifiedHierarchyProps {
| # | 컴포넌트 | 역할 | | # | 컴포넌트 | 역할 |
| :-: | :------------------- | :--------------------------------------- | | :-: | :------------------- | :--------------------------------------- |
| 1 | **UnifiedInput** | 단일 값 입력 (text, number, slider 등) | | 1 | **V2Input** | 단일 값 입력 (text, number, slider 등) |
| 2 | **UnifiedSelect** | 선택 입력 (dropdown, radio, checkbox 등) | | 2 | **V2Select** | 선택 입력 (dropdown, radio, checkbox 등) |
| 3 | **UnifiedDate** | 날짜/시간 입력 | | 3 | **V2Date** | 날짜/시간 입력 |
| 4 | **UnifiedText** | 다중 행 텍스트 (textarea, rich editor) | | 4 | **V2Text** | 다중 행 텍스트 (textarea, rich editor) |
| 5 | **UnifiedMedia** | 파일/미디어 (file, image) | | 5 | **V2Media** | 파일/미디어 (file, image) |
| 6 | **UnifiedList** | 데이터 목록 (table, card, repeater) | | 6 | **V2List** | 데이터 목록 (table, card, repeater) |
| 7 | **UnifiedLayout** | 레이아웃 배치 (grid, split, flex) | | 7 | **V2Layout** | 레이아웃 배치 (grid, split, flex) |
| 8 | **UnifiedGroup** | 콘텐츠 그룹화 (tabs, accordion, section) | | 8 | **V2Group** | 콘텐츠 그룹화 (tabs, accordion, section) |
| 9 | **UnifiedBiz** | 비즈니스 특화 (flow, rack, map 등) | | 9 | **V2Biz** | 비즈니스 특화 (flow, rack, map 등) |
| 10 | **UnifiedHierarchy** | 계층 구조 (tree, org, bom, cascading) | | 10 | **V2Hierarchy** | 계층 구조 (tree, org, bom, cascading) |
### 12.2 공통 속성 (모든 컴포넌트에 적용) ### 12.2 공통 속성 (모든 컴포넌트에 적용)
```typescript ```typescript
interface BaseUnifiedProps { interface BaseV2Props {
// 기본 속성 // 기본 속성
id: string; id: string;
label?: string; label?: string;
@ -614,10 +614,10 @@ interface BaseUnifiedProps {
} }
``` ```
### 12.3 UnifiedSelect 전용 속성 ### 12.3 V2Select 전용 속성
```typescript ```typescript
interface UnifiedSelectProps extends BaseUnifiedProps { interface V2SelectProps extends BaseV2Props {
// 표시 모드 // 표시 모드
mode: "dropdown" | "radio" | "check" | "tag" | "toggle" | "swap"; mode: "dropdown" | "radio" | "check" | "tag" | "toggle" | "swap";
@ -660,11 +660,11 @@ interface UnifiedSelectProps extends BaseUnifiedProps {
| AS-IS | TO-BE | | AS-IS | TO-BE |
| :---------------------------- | :----------------------------------- | | :---------------------------- | :----------------------------------- |
| 연쇄 드롭다운 통합 관리 (6탭) | **삭제** | | 연쇄 드롭다운 통합 관리 (6탭) | **삭제** |
| - 2단계 연쇄관계 | → UnifiedSelect.cascading 속성 | | - 2단계 연쇄관계 | → V2Select.cascading 속성 |
| - 다단계 계층 | → 테이블관리 > 계층 구조 설정 | | - 다단계 계층 | → 테이블관리 > 계층 구조 설정 |
| - 조건부 필터 | → 공통 conditional 속성 | | - 조건부 필터 | → 공통 conditional 속성 |
| - 자동 입력 | → 공통 autoFill 속성 | | - 자동 입력 | → 공통 autoFill 속성 |
| - 상호 배제 | → UnifiedSelect.mutualExclusion 속성 | | - 상호 배제 | → V2Select.mutualExclusion 속성 |
| - 카테고리 값 연쇄 | → 카테고리 관리와 통합 | | - 카테고리 값 연쇄 | → 카테고리 관리와 통합 |
--- ---

View File

@ -1,4 +1,4 @@
# V2 컴포넌트 및 Unified 폼 컴포넌트 결합도 분석 보고서 # V2 컴포넌트 및 V2 폼 컴포넌트 결합도 분석 보고서
> 작성일: 2026-01-26 > 작성일: 2026-01-26
> 목적: 컴포넌트 간 결합도 분석 및 느슨한 결합 전환 가능성 평가 > 목적: 컴포넌트 간 결합도 분석 및 느슨한 결합 전환 가능성 평가
@ -29,23 +29,23 @@
| 16 | v2-table-search-widget | `v2-table-search-widget/` | 테이블 검색 위젯 | | 16 | v2-table-search-widget | `v2-table-search-widget/` | 테이블 검색 위젯 |
| 17 | v2-tabs-widget | `v2-tabs-widget/` | 탭 위젯 | | 17 | v2-tabs-widget | `v2-tabs-widget/` | 탭 위젯 |
| 18 | v2-text-display | `v2-text-display/` | 텍스트 표시 | | 18 | v2-text-display | `v2-text-display/` | 텍스트 표시 |
| 19 | v2-unified-repeater | `v2-unified-repeater/` | 통합 리피터 | | 19 | v2-repeater | `v2-repeater/` | 통합 리피터 |
### 1.2 Unified 폼 컴포넌트 (11개) ### 1.2 V2 폼 컴포넌트 (11개)
| # | 컴포넌트 | 파일 | 주요 용도 | | # | 컴포넌트 | 파일 | 주요 용도 |
|---|---------|------|----------| |---|---------|------|----------|
| 1 | UnifiedInput | `UnifiedInput.tsx` | 텍스트/숫자/이메일 등 입력 | | 1 | V2Input | `V2Input.tsx` | 텍스트/숫자/이메일 등 입력 |
| 2 | UnifiedSelect | `UnifiedSelect.tsx` | 선택박스/라디오/체크박스 | | 2 | V2Select | `V2Select.tsx` | 선택박스/라디오/체크박스 |
| 3 | UnifiedDate | `UnifiedDate.tsx` | 날짜/시간 입력 | | 3 | V2Date | `V2Date.tsx` | 날짜/시간 입력 |
| 4 | UnifiedRepeater | `UnifiedRepeater.tsx` | 리피터 (테이블 형태) | | 4 | V2Repeater | `V2Repeater.tsx` | 리피터 (테이블 형태) |
| 5 | UnifiedLayout | `UnifiedLayout.tsx` | 레이아웃 컨테이너 | | 5 | V2Layout | `V2Layout.tsx` | 레이아웃 컨테이너 |
| 6 | UnifiedGroup | `UnifiedGroup.tsx` | 그룹 컨테이너 (카드/탭/접기) | | 6 | V2Group | `V2Group.tsx` | 그룹 컨테이너 (카드/탭/접기) |
| 7 | UnifiedHierarchy | `UnifiedHierarchy.tsx` | 계층 구조 표시 | | 7 | V2Hierarchy | `V2Hierarchy.tsx` | 계층 구조 표시 |
| 8 | UnifiedList | `UnifiedList.tsx` | 리스트 표시 | | 8 | V2List | `V2List.tsx` | 리스트 표시 |
| 9 | UnifiedMedia | `UnifiedMedia.tsx` | 파일/이미지/비디오 업로드 | | 9 | V2Media | `V2Media.tsx` | 파일/이미지/비디오 업로드 |
| 10 | UnifiedBiz | `UnifiedBiz.tsx` | 비즈니스 컴포넌트 | | 10 | V2Biz | `V2Biz.tsx` | 비즈니스 컴포넌트 |
| 11 | UnifiedFormContext | `UnifiedFormContext.tsx` | 폼 상태 관리 컨텍스트 | | 11 | V2FormContext | `V2FormContext.tsx` | 폼 상태 관리 컨텍스트 |
--- ---
@ -140,29 +140,29 @@ window.addEventListener("checkboxSelectionChange", handleSelectionChange);
| v2-table-search-widget | ❌ | 0개 | ❌ | 🟢 1/10 | | v2-table-search-widget | ❌ | 0개 | ❌ | 🟢 1/10 |
| v2-text-display | ❌ | 0개 | ❌ | 🟢 1/10 | | v2-text-display | ❌ | 0개 | ❌ | 🟢 1/10 |
| v2-repeat-screen-modal | ❌ | 0개 | ❌ | 🟢 1/10 | | v2-repeat-screen-modal | ❌ | 0개 | ❌ | 🟢 1/10 |
| v2-unified-repeater | ❌ | 0개 | ❌ | 🟢 1/10 | | v2-repeater | ❌ | 0개 | ❌ | 🟢 1/10 |
### 2.3 Unified 폼 컴포넌트 결합도 상세 ### 2.3 V2 폼 컴포넌트 결합도 상세
| 컴포넌트 | buttonActions Import | CustomEvent 사용 | window.__ 사용 | 결합도 점수 | | 컴포넌트 | buttonActions Import | CustomEvent 사용 | window.__ 사용 | 결합도 점수 |
|---------|---------------------|------------------|----------------|------------| |---------|---------------------|------------------|----------------|------------|
| **UnifiedRepeater** | ❌ | 7개 수신/발생 | 2개 사용 | 🔴 8/10 | | **V2Repeater** | ❌ | 7개 수신/발생 | 2개 사용 | 🔴 8/10 |
| **UnifiedFormContext** | ❌ | 3개 발생 | ❌ | 🟠 4/10 | | **V2FormContext** | ❌ | 3개 발생 | ❌ | 🟠 4/10 |
| UnifiedInput | ❌ | 0개 | ❌ | 🟢 1/10 | | V2Input | ❌ | 0개 | ❌ | 🟢 1/10 |
| UnifiedSelect | ❌ | 0개 | ❌ | 🟢 1/10 | | V2Select | ❌ | 0개 | ❌ | 🟢 1/10 |
| UnifiedDate | ❌ | 0개 | ❌ | 🟢 1/10 | | V2Date | ❌ | 0개 | ❌ | 🟢 1/10 |
| UnifiedLayout | ❌ | 0개 | ❌ | 🟢 1/10 | | V2Layout | ❌ | 0개 | ❌ | 🟢 1/10 |
| UnifiedGroup | ❌ | 0개 | ❌ | 🟢 1/10 | | V2Group | ❌ | 0개 | ❌ | 🟢 1/10 |
| UnifiedHierarchy | ❌ | 0개 | ❌ | 🟢 1/10 | | V2Hierarchy | ❌ | 0개 | ❌ | 🟢 1/10 |
| UnifiedList | ❌ | 0개 (TableList 래핑) | ❌ | 🟢 2/10 | | V2List | ❌ | 0개 (TableList 래핑) | ❌ | 🟢 2/10 |
| UnifiedMedia | ❌ | 0개 | ❌ | 🟢 1/10 | | V2Media | ❌ | 0개 | ❌ | 🟢 1/10 |
| UnifiedBiz | ❌ | 0개 | ❌ | 🟢 1/10 | | V2Biz | ❌ | 0개 | ❌ | 🟢 1/10 |
**UnifiedRepeater 상세:** **V2Repeater 상세:**
```typescript ```typescript
// 전역 상태 사용 // 전역 상태 사용
window.__unifiedRepeaterInstances = new Set(); window.__v2RepeaterInstances = new Set();
window.__unifiedRepeaterInstances.add(targetTableName); window.__v2RepeaterInstances.add(targetTableName);
// CustomEvent 수신 // CustomEvent 수신
window.addEventListener("repeaterSave", handleSaveEvent); window.addEventListener("repeaterSave", handleSaveEvent);
@ -171,7 +171,7 @@ window.addEventListener("componentDataTransfer", handleComponentDataTransfer);
window.addEventListener("splitPanelDataTransfer", handleSplitPanelDataTransfer); window.addEventListener("splitPanelDataTransfer", handleSplitPanelDataTransfer);
``` ```
**UnifiedFormContext 상세:** **V2FormContext 상세:**
```typescript ```typescript
// CustomEvent 발생 (레거시 호환) // CustomEvent 발생 (레거시 호환)
window.dispatchEvent(new CustomEvent("beforeFormSave", { detail: eventDetail })); window.dispatchEvent(new CustomEvent("beforeFormSave", { detail: eventDetail }));
@ -211,7 +211,7 @@ window.dispatchEvent(new CustomEvent("afterFormSave", { detail: { ... } }));
│ │ │ │
▼ ▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
Unified │ │Unified V2 │ │V2
│Repeater │ │FormContext│ │Repeater │ │FormContext│
└───────────┘ └───────────┘ └───────────┘ └───────────┘
``` ```
@ -227,10 +227,10 @@ window.dispatchEvent(new CustomEvent("afterFormSave", { detail: { ... } }));
| `refreshTable` | v2-button-primary, buttonActions | 테이블 데이터 새로고침 | | `refreshTable` | v2-button-primary, buttonActions | 테이블 데이터 새로고침 |
| `closeEditModal` | v2-button-primary, buttonActions | 수정 모달 닫기 | | `closeEditModal` | v2-button-primary, buttonActions | 수정 모달 닫기 |
| `saveSuccessInModal` | v2-button-primary, buttonActions | 저장 성공 알림 (연속 등록) | | `saveSuccessInModal` | v2-button-primary, buttonActions | 저장 성공 알림 (연속 등록) |
| `beforeFormSave` | UnifiedFormContext, buttonActions | 저장 전 데이터 수집 | | `beforeFormSave` | V2FormContext, buttonActions | 저장 전 데이터 수집 |
| `afterFormSave` | UnifiedFormContext | 저장 완료 알림 | | `afterFormSave` | V2FormContext | 저장 완료 알림 |
| `tableListDataChange` | v2-table-list | 테이블 데이터 변경 알림 | | `tableListDataChange` | v2-table-list | 테이블 데이터 변경 알림 |
| `repeaterDataChange` | UnifiedRepeater | 리피터 데이터 변경 알림 | | `repeaterDataChange` | V2Repeater | 리피터 데이터 변경 알림 |
| `repeaterSave` | buttonActions | 리피터 저장 요청 | | `repeaterSave` | buttonActions | 리피터 저장 요청 |
| `openScreenModal` | v2-split-panel-layout | 화면 모달 열기 | | `openScreenModal` | v2-split-panel-layout | 화면 모달 열기 |
| `refreshCardDisplay` | buttonActions | 카드 디스플레이 새로고침 | | `refreshCardDisplay` | buttonActions | 카드 디스플레이 새로고침 |
@ -240,13 +240,13 @@ window.dispatchEvent(new CustomEvent("afterFormSave", { detail: { ... } }));
| 이벤트명 | 수신 컴포넌트 | 처리 내용 | | 이벤트명 | 수신 컴포넌트 | 처리 내용 |
|---------|-------------|----------| |---------|-------------|----------|
| `refreshTable` | v2-table-list, v2-split-panel-layout | 데이터 재조회 | | `refreshTable` | v2-table-list, v2-split-panel-layout | 데이터 재조회 |
| `beforeFormSave` | v2-repeat-container, UnifiedRepeater | formData에 섹션 데이터 추가 | | `beforeFormSave` | v2-repeat-container, V2Repeater | formData에 섹션 데이터 추가 |
| `tableListDataChange` | v2-aggregation-widget, v2-repeat-container | 집계 재계산, 데이터 동기화 | | `tableListDataChange` | v2-aggregation-widget, v2-repeat-container | 집계 재계산, 데이터 동기화 |
| `repeaterDataChange` | v2-aggregation-widget, v2-repeat-container | 집계 재계산, 데이터 동기화 | | `repeaterDataChange` | v2-aggregation-widget, v2-repeat-container | 집계 재계산, 데이터 동기화 |
| `repeaterSave` | UnifiedRepeater | 리피터 데이터 저장 실행 | | `repeaterSave` | V2Repeater | 리피터 데이터 저장 실행 |
| `selectionChange` | v2-aggregation-widget | 선택 기반 집계 | | `selectionChange` | v2-aggregation-widget | 선택 기반 집계 |
| `componentDataTransfer` | UnifiedRepeater | 컴포넌트 간 데이터 전달 | | `componentDataTransfer` | V2Repeater | 컴포넌트 간 데이터 전달 |
| `splitPanelDataTransfer` | UnifiedRepeater | 분할 패널 데이터 전달 | | `splitPanelDataTransfer` | V2Repeater | 분할 패널 데이터 전달 |
| `refreshCardDisplay` | v2-card-display | 카드 데이터 재조회 | | `refreshCardDisplay` | v2-card-display | 카드 데이터 재조회 |
--- ---
@ -255,7 +255,7 @@ window.dispatchEvent(new CustomEvent("afterFormSave", { detail: { ... } }));
| 전역 변수 | 사용 컴포넌트 | 용도 | 위험도 | | 전역 변수 | 사용 컴포넌트 | 용도 | 위험도 |
|----------|-------------|------|--------| |----------|-------------|------|--------|
| `window.__unifiedRepeaterInstances` | UnifiedRepeater, buttonActions | 리피터 인스턴스 추적 | 🟠 중간 | | `window.__v2RepeaterInstances` | V2Repeater, buttonActions | 리피터 인스턴스 추적 | 🟠 중간 |
| `window.__relatedButtonsTargetTables` | v2-table-list | 관련 버튼 대상 테이블 | 🟠 중간 | | `window.__relatedButtonsTargetTables` | v2-table-list | 관련 버튼 대상 테이블 | 🟠 중간 |
| `window.__relatedButtonsSelectedData` | v2-table-list, buttonActions | 관련 버튼 선택 데이터 | 🟠 중간 | | `window.__relatedButtonsSelectedData` | v2-table-list, buttonActions | 관련 버튼 선택 데이터 | 🟠 중간 |
| `window.__dataRegistry` | v2-table-list (v1/v2) | 테이블 데이터 레지스트리 | 🟠 중간 | | `window.__dataRegistry` | v2-table-list (v1/v2) | 테이블 데이터 레지스트리 | 🟠 중간 |
@ -272,12 +272,12 @@ window.dispatchEvent(new CustomEvent("afterFormSave", { detail: { ... } }));
| 🟠 중간 (4-6점) | 4개 | v2-repeat-container, v2-split-panel-layout, v2-aggregation-widget, v2-tabs-widget | | 🟠 중간 (4-6점) | 4개 | v2-repeat-container, v2-split-panel-layout, v2-aggregation-widget, v2-tabs-widget |
| 🟢 낮음 (1-3점) | 12개 | 나머지 | | 🟢 낮음 (1-3점) | 12개 | 나머지 |
### 6.2 Unified 컴포넌트 (11개) ### 6.2 V2 컴포넌트 (11개)
| 결합도 수준 | 개수 | 컴포넌트 | | 결합도 수준 | 개수 | 컴포넌트 |
|------------|------|---------| |------------|------|---------|
| 🔴 높음 (7-10점) | 1개 | UnifiedRepeater | | 🔴 높음 (7-10점) | 1개 | V2Repeater |
| 🟠 중간 (4-6점) | 1개 | UnifiedFormContext | | 🟠 중간 (4-6점) | 1개 | V2FormContext |
| 🟢 낮음 (1-3점) | 9개 | 나머지 | | 🟢 낮음 (1-3점) | 9개 | 나머지 |
### 6.3 전체 결합도 분포 ### 6.3 전체 결합도 분포
@ -288,14 +288,14 @@ window.dispatchEvent(new CustomEvent("afterFormSave", { detail: { ... } }));
높은 결합도 (🔴): 3개 (10.3%) 높은 결합도 (🔴): 3개 (10.3%)
├── v2-button-primary ├── v2-button-primary
├── v2-table-list ├── v2-table-list
└── UnifiedRepeater └── V2Repeater
중간 결합도 (🟠): 5개 (17.2%) 중간 결합도 (🟠): 5개 (17.2%)
├── v2-repeat-container ├── v2-repeat-container
├── v2-split-panel-layout ├── v2-split-panel-layout
├── v2-aggregation-widget ├── v2-aggregation-widget
├── v2-tabs-widget ├── v2-tabs-widget
└── UnifiedFormContext └── V2FormContext
낮은 결합도 (🟢): 21개 (72.5%) 낮은 결합도 (🟢): 21개 (72.5%)
└── 나머지 모든 컴포넌트 └── 나머지 모든 컴포넌트
@ -318,7 +318,7 @@ v2-table-list 오류 발생 시:
├── related-button 이벤트 미발생 → 관련 버튼 비활성화 ├── related-button 이벤트 미발생 → 관련 버튼 비활성화
└── 전역 상태 오염 가능성 └── 전역 상태 오염 가능성
UnifiedRepeater 오류 발생 시: V2Repeater 오류 발생 시:
├── beforeFormSave 처리 실패 → 리피터 데이터 저장 누락 ├── beforeFormSave 처리 실패 → 리피터 데이터 저장 누락
├── repeaterSave 수신 실패 → 저장 요청 무시 ├── repeaterSave 수신 실패 → 저장 요청 무시
└── 전역 인스턴스 레지스트리 오류 └── 전역 인스턴스 레지스트리 오류
@ -330,7 +330,7 @@ UnifiedRepeater 오류 발생 시:
|---------|-----------------|----------| |---------|-----------------|----------|
| v2-button-primary | 저장/삭제 전체 | ❌ 격리 안됨 | | v2-button-primary | 저장/삭제 전체 | ❌ 격리 안됨 |
| v2-table-list | 집계/관련버튼 | ❌ 격리 안됨 | | v2-table-list | 집계/관련버튼 | ❌ 격리 안됨 |
| UnifiedRepeater | 리피터 저장 | ❌ 격리 안됨 | | V2Repeater | 리피터 저장 | ❌ 격리 안됨 |
| v2-aggregation-widget | 자신만 | ✅ 부분 격리 | | v2-aggregation-widget | 자신만 | ✅ 부분 격리 |
| v2-repeat-container | 자신만 | ✅ 부분 격리 | | v2-repeat-container | 자신만 | ✅ 부분 격리 |
| 나머지 21개 | 자신만 | ✅ 완전 격리 | | 나머지 21개 | 자신만 | ✅ 완전 격리 |
@ -357,7 +357,7 @@ UnifiedRepeater 오류 발생 시:
|---------|---------|----------| |---------|---------|----------|
| 1 | v2-button-primary | buttonActions 의존성 제거, 독립 저장 서비스 | | 1 | v2-button-primary | buttonActions 의존성 제거, 독립 저장 서비스 |
| 2 | v2-table-list | 전역 상태 제거, EventBus 전환 | | 2 | v2-table-list | 전역 상태 제거, EventBus 전환 |
| 3 | UnifiedRepeater | 전역 상태 제거, EventBus 전환 | | 3 | V2Repeater | 전역 상태 제거, EventBus 전환 |
### 8.3 3단계: 이벤트 통합 (2-3일) ### 8.3 3단계: 이벤트 통합 (2-3일)
@ -422,7 +422,7 @@ UnifiedRepeater 오류 발생 시:
|---------|-----------------|-------------------|-------------|------| |---------|-----------------|-------------------|-------------|------|
| **v2-button-primary** | ✅ | ✅ | ✅ | 완료 | | **v2-button-primary** | ✅ | ✅ | ✅ | 완료 |
| **v2-table-list** | ✅ | - | ✅ | 완료 | | **v2-table-list** | ✅ | - | ✅ | 완료 |
| **UnifiedRepeater** | ✅ | - | ✅ | 완료 | | **V2Repeater** | ✅ | - | ✅ | 완료 |
### 10.3 아키텍처 특징 ### 10.3 아키텍처 특징
@ -469,7 +469,7 @@ useEffect(() => {
### 11.1 현재 상태 요약 ### 11.1 현재 상태 요약
- **전체 29개 컴포넌트 중 72.5%(21개)는 이미 낮은 결합도**를 가지고 있어 독립적으로 동작 - **전체 29개 컴포넌트 중 72.5%(21개)는 이미 낮은 결합도**를 가지고 있어 독립적으로 동작
- **핵심 문제 컴포넌트 3개 (v2-button-primary, v2-table-list, UnifiedRepeater) 마이그레이션 완료** - **핵심 문제 컴포넌트 3개 (v2-button-primary, v2-table-list, V2Repeater) 마이그레이션 완료**
- **buttonActions.ts (7,145줄)**는 추후 분할 예정 (현재는 동작 유지) - **buttonActions.ts (7,145줄)**는 추후 분할 예정 (현재는 동작 유지)
### 11.2 달성 목표 ### 11.2 달성 목표
@ -514,22 +514,22 @@ frontend/
│ │ ├── v2-table-search-widget/ │ │ ├── v2-table-search-widget/
│ │ ├── v2-tabs-widget/ │ │ ├── v2-tabs-widget/
│ │ ├── v2-text-display/ │ │ ├── v2-text-display/
│ │ └── v2-unified-repeater/ │ │ └── v2-repeater/
│ └── utils/ │ └── utils/
│ └── buttonActions.ts (7,145줄) │ └── buttonActions.ts (7,145줄)
└── components/ └── components/
└── unified/ └── v2/
├── UnifiedInput.tsx ├── V2Input.tsx
├── UnifiedSelect.tsx ├── V2Select.tsx
├── UnifiedDate.tsx ├── V2Date.tsx
├── UnifiedRepeater.tsx ├── V2Repeater.tsx
├── UnifiedLayout.tsx ├── V2Layout.tsx
├── UnifiedGroup.tsx ├── V2Group.tsx
├── UnifiedHierarchy.tsx ├── V2Hierarchy.tsx
├── UnifiedList.tsx ├── V2List.tsx
├── UnifiedMedia.tsx ├── V2Media.tsx
├── UnifiedBiz.tsx ├── V2Biz.tsx
└── UnifiedFormContext.tsx └── V2FormContext.tsx
``` ```
## 부록 B: V2 Core 파일 구조 (구현됨) ## 부록 B: V2 Core 파일 구조 (구현됨)

View File

@ -67,7 +67,7 @@ V2(Version 2) 컴포넌트는 기존 레거시 컴포넌트의 문제점을 해
|------------|------|------| |------------|------|------|
| `v2-rack-structure` | 렉 구조 | 창고 렉 구조 표시 | | `v2-rack-structure` | 렉 구조 | 창고 렉 구조 표시 |
| `v2-repeat-screen-modal` | 반복 화면 모달 | 반복 가능한 화면 모달 | | `v2-repeat-screen-modal` | 반복 화면 모달 | 반복 가능한 화면 모달 |
| `v2-unified-repeater` | 통합 리피터 | 통합 리피터 테이블 | | `v2-repeater` | 통합 리피터 | 통합 리피터 테이블 |
--- ---
@ -172,10 +172,10 @@ import { V2ErrorBoundary } from "@/lib/v2-core";
|--------|------|--------|--------| |--------|------|--------|--------|
| `v2:table:refresh` | 테이블 새로고침 | v2-button-primary | v2-table-list | | `v2:table:refresh` | 테이블 새로고침 | v2-button-primary | v2-table-list |
| `v2:table:data:change` | 테이블 데이터 변경 | v2-table-list | v2-aggregation-widget | | `v2:table:data:change` | 테이블 데이터 변경 | v2-table-list | v2-aggregation-widget |
| `v2:form:save:collect` | 폼 저장 전 데이터 수집 | buttonActions | v2-repeat-container, UnifiedRepeater | | `v2:form:save:collect` | 폼 저장 전 데이터 수집 | buttonActions | v2-repeat-container, V2Repeater |
| `v2:modal:close` | 모달 닫기 | v2-button-primary | EditModal | | `v2:modal:close` | 모달 닫기 | v2-button-primary | EditModal |
| `v2:modal:save:success` | 모달 저장 성공 | v2-button-primary | EditModal | | `v2:modal:save:success` | 모달 저장 성공 | v2-button-primary | EditModal |
| `v2:repeater:save` | 리피터 저장 | buttonActions | UnifiedRepeater | | `v2:repeater:save` | 리피터 저장 | buttonActions | V2Repeater |
| `v2:component:error` | 컴포넌트 에러 | V2ErrorBoundary | 로깅/모니터링 | | `v2:component:error` | 컴포넌트 에러 | V2ErrorBoundary | 로깅/모니터링 |
### 4.2 이벤트 흐름 다이어그램 ### 4.2 이벤트 흐름 다이어그램
@ -333,7 +333,7 @@ export const BadConfigPanel: React.FC<Props> = ({ config, onChange }) => {
|---------|-------------|---------------|-------------| |---------|-------------|---------------|-------------|
| v2-button-primary | ✅ | ✅ | ✅ | | v2-button-primary | ✅ | ✅ | ✅ |
| v2-table-list | ✅ | ✅ | ✅ | | v2-table-list | ✅ | ✅ | ✅ |
| UnifiedRepeater | ✅ | ✅ | ✅ | | V2Repeater | ✅ | ✅ | ✅ |
### 7.3 장애 격리 검증 ### 7.3 장애 격리 검증
@ -341,7 +341,7 @@ export const BadConfigPanel: React.FC<Props> = ({ config, onChange }) => {
v2-button-primary 에러 발생 시: v2-button-primary 에러 발생 시:
├── V2ErrorBoundary 캐치 → 버튼만 에러 UI 표시 ├── V2ErrorBoundary 캐치 → 버튼만 에러 UI 표시
├── v2-table-list: 정상 동작 ✅ ├── v2-table-list: 정상 동작 ✅
└── UnifiedRepeater: 정상 동작 ✅ └── V2Repeater: 정상 동작 ✅
v2-table-list 에러 발생 시: v2-table-list 에러 발생 시:
├── V2ErrorBoundary 캐치 → 테이블만 에러 UI 표시 ├── V2ErrorBoundary 캐치 → 테이블만 에러 UI 표시
@ -351,30 +351,30 @@ v2-table-list 에러 발생 시:
--- ---
## 8. Unified 폼 컴포넌트 ## 8. V2 폼 컴포넌트
### 8.1 목록 (11개) ### 8.1 목록 (11개)
| 컴포넌트 | 파일 | 용도 | | 컴포넌트 | 파일 | 용도 |
|---------|------|------| |---------|------|------|
| UnifiedInput | UnifiedInput.tsx | 텍스트/숫자/이메일/채번 입력 | | V2Input | V2Input.tsx | 텍스트/숫자/이메일/채번 입력 |
| UnifiedSelect | UnifiedSelect.tsx | 선택박스/라디오/체크박스/카테고리 | | V2Select | V2Select.tsx | 선택박스/라디오/체크박스/카테고리 |
| UnifiedDate | UnifiedDate.tsx | 날짜/시간 입력 | | V2Date | V2Date.tsx | 날짜/시간 입력 |
| UnifiedRepeater | UnifiedRepeater.tsx | 리피터 테이블 | | V2Repeater | V2Repeater.tsx | 리피터 테이블 |
| UnifiedLayout | UnifiedLayout.tsx | 레이아웃 컨테이너 | | V2Layout | V2Layout.tsx | 레이아웃 컨테이너 |
| UnifiedGroup | UnifiedGroup.tsx | 그룹 컨테이너 | | V2Group | V2Group.tsx | 그룹 컨테이너 |
| UnifiedHierarchy | UnifiedHierarchy.tsx | 계층 구조 표시 | | V2Hierarchy | V2Hierarchy.tsx | 계층 구조 표시 |
| UnifiedList | UnifiedList.tsx | 리스트 표시 | | V2List | V2List.tsx | 리스트 표시 |
| UnifiedMedia | UnifiedMedia.tsx | 파일/이미지/비디오 | | V2Media | V2Media.tsx | 파일/이미지/비디오 |
| UnifiedBiz | UnifiedBiz.tsx | 비즈니스 컴포넌트 | | V2Biz | V2Biz.tsx | 비즈니스 컴포넌트 |
| UnifiedFormContext | UnifiedFormContext.tsx | 폼 상태 관리 | | V2FormContext | V2FormContext.tsx | 폼 상태 관리 |
### 8.2 inputType 자동 처리 ### 8.2 inputType 자동 처리
Unified 컴포넌트는 `inputType`에 따라 자동으로 적절한 UI를 렌더링합니다: V2 컴포넌트는 `inputType`에 따라 자동으로 적절한 UI를 렌더링합니다:
```typescript ```typescript
// UnifiedInput.tsx // V2Input.tsx
switch (inputType) { switch (inputType) {
case "numbering": case "numbering":
// 채번 규칙 자동 조회 및 코드 생성 // 채번 규칙 자동 조회 및 코드 생성
@ -386,7 +386,7 @@ switch (inputType) {
break; break;
} }
// UnifiedSelect.tsx // V2Select.tsx
switch (inputType) { switch (inputType) {
case "category": case "category":
// 카테고리 값 자동 조회 및 드롭다운 표시 // 카테고리 값 자동 조회 및 드롭다운 표시
@ -468,8 +468,8 @@ V2 Core:
V2 컴포넌트: V2 컴포넌트:
- frontend/lib/registry/components/v2-*/ - frontend/lib/registry/components/v2-*/
Unified 폼 컴포넌트: V2 폼 컴포넌트:
- frontend/components/unified/ - frontend/components/v2/
채번/카테고리 테스트 테이블: 채번/카테고리 테스트 테이블:
- db/migrations/040_create_numbering_rules_test.sql - db/migrations/040_create_numbering_rules_test.sql
@ -534,6 +534,6 @@ frontend/lib/registry/components/
├── v2-table-search-widget/ ├── v2-table-search-widget/
├── v2-tabs-widget/ ├── v2-tabs-widget/
├── v2-text-display/ ├── v2-text-display/
└── v2-unified-repeater/ └── v2-repeater/
``` ```

View File

@ -15,29 +15,29 @@
### 상위 15개 컴포넌트 ### 상위 15개 컴포넌트
| 순위 | 컴포넌트 | 사용 횟수 | 사용 화면 수 | Unified 매핑 | | 순위 | 컴포넌트 | 사용 횟수 | 사용 화면 수 | V2 매핑 |
| :--: | :-------------------------- | :-------: | :----------: | :------------------------------ | | :--: | :-------------------------- | :-------: | :----------: | :------------------------------ |
| 1 | button-primary | 571 | 364 | UnifiedInput (type: button) | | 1 | button-primary | 571 | 364 | V2Input (type: button) |
| 2 | text-input | 805 | 166 | **UnifiedInput (type: text)** | | 2 | text-input | 805 | 166 | **V2Input (type: text)** |
| 3 | table-list | 130 | 130 | UnifiedList (viewMode: table) | | 3 | table-list | 130 | 130 | V2List (viewMode: table) |
| 4 | table-search-widget | 127 | 127 | UnifiedList (searchable: true) | | 4 | table-search-widget | 127 | 127 | V2List (searchable: true) |
| 5 | select-basic | 121 | 76 | **UnifiedSelect** | | 5 | select-basic | 121 | 76 | **V2Select** |
| 6 | number-input | 86 | 34 | **UnifiedInput (type: number)** | | 6 | number-input | 86 | 34 | **V2Input (type: number)** |
| 7 | date-input | 83 | 51 | **UnifiedDate** | | 7 | date-input | 83 | 51 | **V2Date** |
| 8 | file-upload | 41 | 18 | UnifiedMedia (type: file) | | 8 | file-upload | 41 | 18 | V2Media (type: file) |
| 9 | tabs-widget | 39 | 39 | UnifiedGroup (type: tabs) | | 9 | tabs-widget | 39 | 39 | V2Group (type: tabs) |
| 10 | split-panel-layout | 39 | 39 | UnifiedLayout (type: split) | | 10 | split-panel-layout | 39 | 39 | V2Layout (type: split) |
| 11 | category-manager | 38 | 38 | UnifiedBiz (type: category) | | 11 | category-manager | 38 | 38 | V2Biz (type: category) |
| 12 | numbering-rule | 31 | 31 | UnifiedBiz (type: numbering) | | 12 | numbering-rule | 31 | 31 | V2Biz (type: numbering) |
| 13 | selected-items-detail-input | 29 | 29 | 복합 컴포넌트 | | 13 | selected-items-detail-input | 29 | 29 | 복합 컴포넌트 |
| 14 | modal-repeater-table | 25 | 25 | UnifiedList (modal: true) | | 14 | modal-repeater-table | 25 | 25 | V2List (modal: true) |
| 15 | image-widget | 29 | 29 | UnifiedMedia (type: image) | | 15 | image-widget | 29 | 29 | V2Media (type: image) |
--- ---
## 2. Unified 컴포넌트별 통합 대상 분석 ## 2. V2 컴포넌트별 통합 대상 분석
### UnifiedInput (예상 통합 대상: 891개) ### V2Input (예상 통합 대상: 891개)
| 기존 컴포넌트 | 사용 횟수 | 비율 | | 기존 컴포넌트 | 사용 횟수 | 비율 |
| :------------ | :-------: | :---: | | :------------ | :-------: | :---: |
@ -46,7 +46,7 @@
**우선순위: 1위** - 가장 많이 사용되는 컴포넌트 **우선순위: 1위** - 가장 많이 사용되는 컴포넌트
### UnifiedSelect (예상 통합 대상: 140개) ### V2Select (예상 통합 대상: 140개)
| 기존 컴포넌트 | 사용 횟수 | widgetType | | 기존 컴포넌트 | 사용 횟수 | widgetType |
| :------------------------ | :-------: | :--------- | | :------------------------ | :-------: | :--------- |
@ -59,7 +59,7 @@
**우선순위: 2위** - 다양한 모드 지원 필요 **우선순위: 2위** - 다양한 모드 지원 필요
### UnifiedDate (예상 통합 대상: 83개) ### V2Date (예상 통합 대상: 83개)
| 기존 컴포넌트 | 사용 횟수 | | 기존 컴포넌트 | 사용 횟수 |
| :---------------- | :-------: | | :---------------- | :-------: |
@ -69,7 +69,7 @@
**우선순위: 3위** **우선순위: 3위**
### UnifiedList (예상 통합 대상: 283개) ### V2List (예상 통합 대상: 283개)
| 기존 컴포넌트 | 사용 횟수 | 비고 | | 기존 컴포넌트 | 사용 횟수 | 비고 |
| :-------------------- | :-------: | :---------- | | :-------------------- | :-------: | :---------- |
@ -82,14 +82,14 @@
**우선순위: 4위** - 핵심 데이터 표시 컴포넌트 **우선순위: 4위** - 핵심 데이터 표시 컴포넌트
### UnifiedMedia (예상 통합 대상: 70개) ### V2Media (예상 통합 대상: 70개)
| 기존 컴포넌트 | 사용 횟수 | | 기존 컴포넌트 | 사용 횟수 |
| :------------ | :-------: | | :------------ | :-------: |
| file-upload | 41 | | file-upload | 41 |
| image-widget | 29 | | image-widget | 29 |
### UnifiedLayout (예상 통합 대상: 62개) ### V2Layout (예상 통합 대상: 62개)
| 기존 컴포넌트 | 사용 횟수 | | 기존 컴포넌트 | 사용 횟수 |
| :------------------ | :-------: | | :------------------ | :-------: |
@ -97,7 +97,7 @@
| screen-split-panel | 21 | | screen-split-panel | 21 |
| split-panel-layout2 | 2 | | split-panel-layout2 | 2 |
### UnifiedGroup (예상 통합 대상: 99개) ### V2Group (예상 통합 대상: 99개)
| 기존 컴포넌트 | 사용 횟수 | | 기존 컴포넌트 | 사용 횟수 |
| :-------------------- | :-------: | | :-------------------- | :-------: |
@ -109,7 +109,7 @@
| universal-form-modal | 7 | | universal-form-modal | 7 |
| repeat-screen-modal | 5 | | repeat-screen-modal | 5 |
### UnifiedBiz (예상 통합 대상: 79개) ### V2Biz (예상 통합 대상: 79개)
| 기존 컴포넌트 | 사용 횟수 | | 기존 컴포넌트 | 사용 횟수 |
| :--------------------- | :-------: | | :--------------------- | :-------: |
@ -127,27 +127,27 @@
### Phase 1 우선순위 (즉시 효과가 큰 컴포넌트) ### Phase 1 우선순위 (즉시 효과가 큰 컴포넌트)
| 순위 | Unified 컴포넌트 | 통합 대상 수 | 영향 화면 수 | 이유 | | 순위 | V2 컴포넌트 | 통합 대상 수 | 영향 화면 수 | 이유 |
| :---: | :---------------- | :----------: | :----------: | :--------------- | | :---: | :---------------- | :----------: | :----------: | :--------------- |
| **1** | **UnifiedInput** | 891개 | 200+ | 가장 많이 사용 | | **1** | **V2Input** | 891개 | 200+ | 가장 많이 사용 |
| **2** | **UnifiedSelect** | 140개 | 100+ | 다양한 모드 필요 | | **2** | **V2Select** | 140개 | 100+ | 다양한 모드 필요 |
| **3** | **UnifiedDate** | 83개 | 51 | 비교적 단순 | | **3** | **V2Date** | 83개 | 51 | 비교적 단순 |
### Phase 2 우선순위 (데이터 표시 컴포넌트) ### Phase 2 우선순위 (데이터 표시 컴포넌트)
| 순위 | Unified 컴포넌트 | 통합 대상 수 | 이유 | | 순위 | V2 컴포넌트 | 통합 대상 수 | 이유 |
| :---: | :---------------- | :----------: | :--------------- | | :---: | :---------------- | :----------: | :--------------- |
| **4** | **UnifiedList** | 283개 | 핵심 데이터 표시 | | **4** | **V2List** | 283개 | 핵심 데이터 표시 |
| **5** | **UnifiedLayout** | 62개 | 레이아웃 구조 | | **5** | **V2Layout** | 62개 | 레이아웃 구조 |
| **6** | **UnifiedGroup** | 99개 | 콘텐츠 그룹화 | | **6** | **V2Group** | 99개 | 콘텐츠 그룹화 |
### Phase 3 우선순위 (특수 컴포넌트) ### Phase 3 우선순위 (특수 컴포넌트)
| 순위 | Unified 컴포넌트 | 통합 대상 수 | 이유 | | 순위 | V2 컴포넌트 | 통합 대상 수 | 이유 |
| :---: | :------------------- | :----------: | :------------ | | :---: | :------------------- | :----------: | :------------ |
| **7** | **UnifiedMedia** | 70개 | 파일/이미지 | | **7** | **V2Media** | 70개 | 파일/이미지 |
| **8** | **UnifiedBiz** | 79개 | 비즈니스 특화 | | **8** | **V2Biz** | 79개 | 비즈니스 특화 |
| **9** | **UnifiedHierarchy** | 0개 | 신규 기능 | | **9** | **V2Hierarchy** | 0개 | 신규 기능 |
--- ---
@ -156,8 +156,8 @@
### 4.1 button-primary 분리 검토 ### 4.1 button-primary 분리 검토
- 사용량: 571개 (1위) - 사용량: 571개 (1위)
- 현재 계획: UnifiedInput에 포함 - 현재 계획: V2Input에 포함
- **제안**: 별도 `UnifiedButton` 컴포넌트로 분리 검토 - **제안**: 별도 `V2Button` 컴포넌트로 분리 검토
- 버튼은 입력과 성격이 다름 - 버튼은 입력과 성격이 다름
- 액션 타입, 스타일, 권한 등 복잡한 설정 필요 - 액션 타입, 스타일, 권한 등 복잡한 설정 필요
@ -181,5 +181,5 @@
1. [ ] 데이터 마이그레이션 전략 설계 (Phase 0-2) 1. [ ] 데이터 마이그레이션 전략 설계 (Phase 0-2)
2. [ ] sys_input_type JSON Schema 설계 (Phase 0-3) 2. [ ] sys_input_type JSON Schema 설계 (Phase 0-3)
3. [ ] DynamicConfigPanel 프로토타입 (Phase 0-4) 3. [ ] DynamicConfigPanel 프로토타입 (Phase 0-4)
4. [ ] UnifiedInput 구현 시작 (Phase 1-1) 4. [ ] V2Input 구현 시작 (Phase 1-1)

View File

@ -67,8 +67,8 @@
"componentConfig": { ... }, "componentConfig": { ... },
// 신규 필드 추가 // 신규 필드 추가
"unifiedType": "UnifiedInput", // 새로운 통합 컴포넌트 타입 "v2Type": "V2Input", // 새로운 통합 컴포넌트 타입
"unifiedConfig": { // 새로운 설정 구조 "v2Config": { // 새로운 설정 구조
"type": "text", "type": "text",
"format": "none", "format": "none",
"placeholder": "텍스트를 입력하세요" "placeholder": "텍스트를 입력하세요"
@ -87,13 +87,13 @@
### 2.2 렌더링 로직 수정 ### 2.2 렌더링 로직 수정
```typescript ```typescript
// 렌더러에서 unifiedType 우선 사용 // 렌더러에서 v2Type 우선 사용
function renderComponent(props: ComponentProps) { function renderComponent(props: ComponentProps) {
// 신규 타입이 있으면 Unified 컴포넌트 사용 // 신규 타입이 있으면 V2 컴포넌트 사용
if (props.unifiedType) { if (props.v2Type) {
return <UnifiedComponentRenderer return <V2ComponentRenderer
type={props.unifiedType} type={props.v2Type}
config={props.unifiedConfig} config={props.v2Config}
/>; />;
} }
@ -109,7 +109,7 @@ function renderComponent(props: ComponentProps) {
## 3. 컴포넌트별 매핑 규칙 ## 3. 컴포넌트별 매핑 규칙
### 3.1 text-input → UnifiedInput ### 3.1 text-input → V2Input
```typescript ```typescript
// AS-IS // AS-IS
@ -126,8 +126,8 @@ function renderComponent(props: ComponentProps) {
// TO-BE // TO-BE
{ {
"unifiedType": "UnifiedInput", "v2Type": "V2Input",
"unifiedConfig": { "v2Config": {
"type": "text", // componentConfig.webType 또는 "text" "type": "text", // componentConfig.webType 또는 "text"
"format": "none", // componentConfig.format "format": "none", // componentConfig.format
"placeholder": "..." // componentConfig.placeholder "placeholder": "..." // componentConfig.placeholder
@ -135,7 +135,7 @@ function renderComponent(props: ComponentProps) {
} }
``` ```
### 3.2 number-input → UnifiedInput ### 3.2 number-input → V2Input
```typescript ```typescript
// AS-IS // AS-IS
@ -152,8 +152,8 @@ function renderComponent(props: ComponentProps) {
// TO-BE // TO-BE
{ {
"unifiedType": "UnifiedInput", "v2Type": "V2Input",
"unifiedConfig": { "v2Config": {
"type": "number", "type": "number",
"min": 0, "min": 0,
"max": 100, "max": 100,
@ -162,7 +162,7 @@ function renderComponent(props: ComponentProps) {
} }
``` ```
### 3.3 select-basic → UnifiedSelect ### 3.3 select-basic → V2Select
```typescript ```typescript
// AS-IS (code 타입) // AS-IS (code 타입)
@ -178,8 +178,8 @@ function renderComponent(props: ComponentProps) {
// TO-BE // TO-BE
{ {
"unifiedType": "UnifiedSelect", "v2Type": "V2Select",
"unifiedConfig": { "v2Config": {
"mode": "dropdown", "mode": "dropdown",
"source": "code", "source": "code",
"codeGroup": "ORDER_STATUS" "codeGroup": "ORDER_STATUS"
@ -200,8 +200,8 @@ function renderComponent(props: ComponentProps) {
// TO-BE // TO-BE
{ {
"unifiedType": "UnifiedSelect", "v2Type": "V2Select",
"unifiedConfig": { "v2Config": {
"mode": "dropdown", "mode": "dropdown",
"source": "entity", "source": "entity",
"searchable": true, "searchable": true,
@ -211,7 +211,7 @@ function renderComponent(props: ComponentProps) {
} }
``` ```
### 3.4 date-input → UnifiedDate ### 3.4 date-input → V2Date
```typescript ```typescript
// AS-IS // AS-IS
@ -226,8 +226,8 @@ function renderComponent(props: ComponentProps) {
// TO-BE // TO-BE
{ {
"unifiedType": "UnifiedDate", "v2Type": "V2Date",
"unifiedConfig": { "v2Config": {
"type": "date", "type": "date",
"format": "YYYY-MM-DD" "format": "YYYY-MM-DD"
} }
@ -245,11 +245,11 @@ function renderComponent(props: ComponentProps) {
interface MigrationResult { interface MigrationResult {
success: boolean; success: boolean;
unifiedType: string; v2Type: string;
unifiedConfig: Record<string, any>; v2Config: Record<string, any>;
} }
export function migrateToUnified( export function migrateToV2(
componentType: string, componentType: string,
componentConfig: Record<string, any> componentConfig: Record<string, any>
): MigrationResult { ): MigrationResult {
@ -258,8 +258,8 @@ export function migrateToUnified(
case 'text-input': case 'text-input':
return { return {
success: true, success: true,
unifiedType: 'UnifiedInput', v2Type: 'V2Input',
unifiedConfig: { v2Config: {
type: componentConfig.webType || 'text', type: componentConfig.webType || 'text',
format: componentConfig.format || 'none', format: componentConfig.format || 'none',
placeholder: componentConfig.placeholder placeholder: componentConfig.placeholder
@ -269,8 +269,8 @@ export function migrateToUnified(
case 'number-input': case 'number-input':
return { return {
success: true, success: true,
unifiedType: 'UnifiedInput', v2Type: 'V2Input',
unifiedConfig: { v2Config: {
type: 'number', type: 'number',
min: componentConfig.min, min: componentConfig.min,
max: componentConfig.max, max: componentConfig.max,
@ -281,8 +281,8 @@ export function migrateToUnified(
case 'select-basic': case 'select-basic':
return { return {
success: true, success: true,
unifiedType: 'UnifiedSelect', v2Type: 'V2Select',
unifiedConfig: { v2Config: {
mode: 'dropdown', mode: 'dropdown',
source: componentConfig.webType || 'static', source: componentConfig.webType || 'static',
codeGroup: componentConfig.codeCategory, codeGroup: componentConfig.codeCategory,
@ -295,8 +295,8 @@ export function migrateToUnified(
case 'date-input': case 'date-input':
return { return {
success: true, success: true,
unifiedType: 'UnifiedDate', v2Type: 'V2Date',
unifiedConfig: { v2Config: {
type: componentConfig.webType || 'date', type: componentConfig.webType || 'date',
format: componentConfig.format format: componentConfig.format
} }
@ -305,8 +305,8 @@ export function migrateToUnified(
default: default:
return { return {
success: false, success: false,
unifiedType: '', v2Type: '',
unifiedConfig: {} v2Config: {}
}; };
} }
} }
@ -322,8 +322,8 @@ SELECT * FROM screen_layouts;
-- 마이그레이션 실행 (text-input 예시) -- 마이그레이션 실행 (text-input 예시)
UPDATE screen_layouts UPDATE screen_layouts
SET properties = properties || jsonb_build_object( SET properties = properties || jsonb_build_object(
'unifiedType', 'UnifiedInput', 'v2Type', 'V2Input',
'unifiedConfig', jsonb_build_object( 'v2Config', jsonb_build_object(
'type', COALESCE(properties->'componentConfig'->>'webType', 'text'), 'type', COALESCE(properties->'componentConfig'->>'webType', 'text'),
'format', COALESCE(properties->'componentConfig'->>'format', 'none'), 'format', COALESCE(properties->'componentConfig'->>'format', 'none'),
'placeholder', properties->'componentConfig'->>'placeholder' 'placeholder', properties->'componentConfig'->>'placeholder'
@ -352,7 +352,7 @@ WHERE sl.layout_id = slb.layout_id;
-- 또는 신규 필드만 제거 -- 또는 신규 필드만 제거
UPDATE screen_layouts UPDATE screen_layouts
SET properties = properties - 'unifiedType' - 'unifiedConfig' - '_migration'; SET properties = properties - 'v2Type' - 'v2Config' - '_migration';
``` ```
### 5.2 단계적 롤백 ### 5.2 단계적 롤백
@ -362,7 +362,7 @@ SET properties = properties - 'unifiedType' - 'unifiedConfig' - '_migration';
async function rollbackScreen(screenId: number) { async function rollbackScreen(screenId: number) {
await db.query(` await db.query(`
UPDATE screen_layouts sl UPDATE screen_layouts sl
SET properties = properties - 'unifiedType' - 'unifiedConfig' - '_migration' SET properties = properties - 'v2Type' - 'v2Config' - '_migration'
WHERE screen_id = $1 WHERE screen_id = $1
`, [screenId]); `, [screenId]);
} }
@ -375,9 +375,9 @@ async function rollbackScreen(screenId: number) {
| 단계 | 작업 | 대상 | 시점 | | 단계 | 작업 | 대상 | 시점 |
|:---:|:---|:---|:---| |:---:|:---|:---|:---|
| 1 | 백업 테이블 생성 | 전체 | Phase 1 시작 전 | | 1 | 백업 테이블 생성 | 전체 | Phase 1 시작 전 |
| 2 | UnifiedInput 마이그레이션 | text-input, number-input | Phase 1 중 | | 2 | V2Input 마이그레이션 | text-input, number-input | Phase 1 중 |
| 3 | UnifiedSelect 마이그레이션 | select-basic | Phase 1 중 | | 3 | V2Select 마이그레이션 | select-basic | Phase 1 중 |
| 4 | UnifiedDate 마이그레이션 | date-input | Phase 1 중 | | 4 | V2Date 마이그레이션 | date-input | Phase 1 중 |
| 5 | 검증 및 테스트 | 전체 | Phase 1 완료 후 | | 5 | 검증 및 테스트 | 전체 | Phase 1 완료 후 |
| 6 | 레거시 필드 제거 | 전체 | Phase 5 (추후) | | 6 | 레거시 필드 제거 | 전체 | Phase 5 (추후) |

View File

@ -477,7 +477,7 @@ className={cn(
- ✅ `FileComponentConfigPanel.tsx`: `text-gray-900``text-foreground`, `text-blue-*``text-primary` - ✅ `FileComponentConfigPanel.tsx`: `text-gray-900``text-foreground`, `text-blue-*``text-primary`
- ✅ `ButtonConfigPanel.tsx`: 모든 `text-gray-*`, `bg-gray-*`, `hover:bg-gray-*` 교체 - ✅ `ButtonConfigPanel.tsx`: 모든 `text-gray-*`, `bg-gray-*`, `hover:bg-gray-*` 교체
- ✅ `UnifiedPropertiesPanel.tsx`: 모든 `text-gray-*`, `border-gray-*` 교체 - ✅ `V2PropertiesPanel.tsx`: 모든 `text-gray-*`, `border-gray-*` 교체
- ✅ `app/(main)/admin/page.tsx`: 전체 페이지 하드코딩 색상 교체 - ✅ `app/(main)/admin/page.tsx`: 전체 페이지 하드코딩 색상 교체
- ✅ `CardDisplayComponent.tsx`: 모든 `text-gray-*`, `bg-gray-*`, 인라인 색상 교체 - ✅ `CardDisplayComponent.tsx`: 모든 `text-gray-*`, `bg-gray-*`, 인라인 색상 교체
- ✅ `getComponentConfigPanel.tsx`: 로딩 상태 하드코딩 색상 교체 - ✅ `getComponentConfigPanel.tsx`: 로딩 상태 하드코딩 색상 교체

Some files were not shown because too many files have changed in this diff Show More