ERP-node/docs/frontend-architecture-analy...

1921 lines
65 KiB
Markdown

# WACE ERP 프론트엔드 아키텍처 상세 분석
> 작성일: 2026-02-06
> 작성자: AI Assistant
> 프로젝트: WACE ERP-node
> 목적: 시스템 전체 워크플로우 문서화를 위한 프론트엔드 구조 분석
---
## 목차
1. [전체 디렉토리 구조](#1-전체-디렉토리-구조)
2. [Next.js App Router 구조](#2-nextjs-app-router-구조)
3. [컴포넌트 시스템](#3-컴포넌트-시스템)
4. [V2 컴포넌트 시스템](#4-v2-컴포넌트-시스템)
5. [화면 디자이너 워크플로우](#5-화면-디자이너-워크플로우)
6. [API 클라이언트 시스템](#6-api-클라이언트-시스템)
7. [상태 관리](#7-상태-관리)
8. [레지스트리 시스템](#8-레지스트리-시스템)
9. [대시보드 시스템](#9-대시보드-시스템)
10. [다국어 지원](#10-다국어-지원)
11. [인증 플로우](#11-인증-플로우)
12. [사용자 워크플로우](#12-사용자-워크플로우)
---
## 1. 전체 디렉토리 구조
```
frontend/
├── app/ # Next.js 14 App Router
│ ├── (main)/ # 메인 레이아웃 그룹
│ ├── (auth)/ # 인증 레이아웃 그룹
│ ├── (admin)/ # 관리자 레이아웃 그룹
│ ├── (pop)/ # 팝업 레이아웃 그룹
│ ├── layout.tsx # 루트 레이아웃
│ └── registry-provider.tsx # 레지스트리 초기화 프로바이더
├── components/ # React 컴포넌트
│ ├── admin/ # 관리자 컴포넌트 (137개)
│ ├── screen/ # 화면 디자이너/뷰어 (139개)
│ ├── dashboard/ # 대시보드 컴포넌트 (32개)
│ ├── dataflow/ # 데이터플로우 관련 (90개)
│ ├── v2/ # V2 컴포넌트 시스템 (28개)
│ ├── common/ # 공통 컴포넌트 (19개)
│ ├── layout/ # 레이아웃 컴포넌트 (10개)
│ ├── ui/ # shadcn/ui 기반 UI (31개)
│ └── ... # 기타 도메인별 컴포넌트
├── lib/ # 유틸리티 & 라이브러리
│ ├── api/ # API 클라이언트 (57개 파일)
│ │ ├── client.ts # Axios 기본 설정
│ │ ├── screen.ts # 화면 관리 API
│ │ ├── user.ts # 사용자 관리 API
│ │ └── ... # 도메인별 API
│ │
│ ├── registry/ # 컴포넌트 레지스트리 시스템 (540개)
│ │ ├── ComponentRegistry.ts # 컴포넌트 등록 관리
│ │ ├── WebTypeRegistry.ts # 웹타입 등록 관리
│ │ ├── LayoutRegistry.ts # 레이아웃 등록 관리
│ │ ├── init.ts # 레지스트리 초기화
│ │ ├── DynamicComponentRenderer.tsx # 동적 렌더링
│ │ ├── components/ # 등록 가능한 컴포넌트들 (288 tsx, 205 ts)
│ │ │ ├── v2-input/
│ │ │ ├── v2-select/
│ │ │ ├── text-input/
│ │ │ ├── entity-search-input/
│ │ │ ├── modal-repeater-table/
│ │ │ └── ...
│ │ └── layouts/ # 레이아웃 컴포넌트
│ │ ├── accordion/
│ │ ├── grid/
│ │ ├── flexbox/
│ │ ├── split/
│ │ └── tabs/
│ │
│ ├── v2-core/ # V2 코어 시스템
│ │ ├── events/ # 이벤트 버스
│ │ ├── adapters/ # 레거시 어댑터
│ │ ├── components/ # V2 공통 컴포넌트
│ │ ├── services/ # V2 서비스
│ │ └── init.ts # V2 초기화
│ │
│ ├── utils/ # 유틸리티 함수들 (40개+)
│ ├── services/ # 비즈니스 로직 서비스
│ ├── stores/ # Zustand 스토어
│ └── constants/ # 상수 정의
├── contexts/ # React Context (12개)
│ ├── AuthContext.tsx # 인증 컨텍스트
│ ├── MenuContext.tsx # 메뉴 컨텍스트
│ ├── ScreenContext.tsx # 화면 컨텍스트
│ ├── DashboardContext.tsx # 대시보드 컨텍스트
│ └── ...
├── hooks/ # Custom Hooks (32개)
│ ├── useAuth.ts # 인증 훅
│ ├── useMenu.ts # 메뉴 관리 훅
│ ├── useFormValidation.ts # 폼 검증 훅
│ └── ...
├── types/ # TypeScript 타입 정의 (44개)
│ ├── screen.ts # 화면 관련 타입
│ ├── component.ts # 컴포넌트 타입
│ ├── user.ts # 사용자 타입
│ └── ...
├── providers/ # React Provider
│ └── QueryProvider.tsx # React Query 설정
├── stores/ # 전역 상태 관리
│ ├── flowStepStore.ts # 플로우 단계 상태
│ ├── modalDataStore.ts # 모달 데이터 상태
│ └── tableDisplayStore.ts # 테이블 표시 상태
└── public/ # 정적 파일
├── favicon.ico
├── logo.png
└── locales/ # 다국어 파일
```
---
## 2. Next.js App Router 구조
### 2.1 라우팅 구조
WACE ERP는 **Next.js 14 App Router**를 사용하며, 레이아웃 그룹을 활용한 구조화된 라우팅을 제공합니다.
#### 라우트 그룹 구조
```
app/
├── layout.tsx # 글로벌 레이아웃
│ ├── QueryProvider # React Query 초기화
│ ├── RegistryProvider # 컴포넌트 레지스트리 초기화
│ ├── Toaster # 토스트 알림
│ └── ScreenModal # 전역 화면 모달
├── (main)/ # 메인 애플리케이션
│ ├── layout.tsx
│ │ ├── AuthProvider # 인증 컨텍스트
│ │ ├── MenuProvider # 메뉴 컨텍스트
│ │ └── AppLayout # 사이드바, 헤더
│ │
│ ├── main/page.tsx # 메인 페이지
│ ├── dashboard/ # 대시보드
│ │ ├── page.tsx # 대시보드 목록
│ │ └── [dashboardId]/page.tsx # 대시보드 상세
│ │
│ ├── screens/[screenId]/page.tsx # 동적 화면 뷰어
│ │
│ └── admin/ # 관리자 페이지
│ ├── page.tsx # 관리자 홈
│ ├── menu/page.tsx # 메뉴 관리
│ │
│ ├── userMng/ # 사용자 관리
│ │ ├── userMngList/page.tsx
│ │ ├── rolesList/page.tsx
│ │ ├── userAuthList/page.tsx
│ │ └── companyList/page.tsx
│ │
│ ├── screenMng/ # 화면 관리
│ │ ├── screenMngList/page.tsx
│ │ ├── dashboardList/page.tsx
│ │ └── reportList/page.tsx
│ │
│ ├── systemMng/ # 시스템 관리
│ │ ├── tableMngList/page.tsx
│ │ ├── commonCodeList/page.tsx
│ │ ├── i18nList/page.tsx
│ │ ├── dataflow/page.tsx
│ │ └── collection-managementList/page.tsx
│ │
│ ├── automaticMng/ # 자동화 관리
│ │ ├── flowMgmtList/page.tsx
│ │ ├── batchmngList/page.tsx
│ │ ├── exCallConfList/page.tsx
│ │ └── mail/ # 메일 관리
│ │ ├── accounts/page.tsx
│ │ ├── send/page.tsx
│ │ ├── receive/page.tsx
│ │ └── templates/page.tsx
│ │
│ └── [...slug]/page.tsx # 동적 관리자 페이지
├── (auth)/ # 인증 페이지
│ ├── layout.tsx # 최소 레이아웃
│ └── login/page.tsx # 로그인 페이지
├── (admin)/ # 관리자 전용 (별도 레이아웃)
│ └── admin/
│ ├── vehicle-trips/page.tsx
│ └── vehicle-reports/page.tsx
└── (pop)/ # 팝업 페이지
├── layout.tsx # 팝업 레이아웃
├── pop/page.tsx
└── work/page.tsx
```
### 2.2 페이지 목록 (총 76개)
**메인 애플리케이션 (50개)**
- `/main` - 메인 페이지
- `/dashboard` - 대시보드 목록
- `/dashboard/[dashboardId]` - 대시보드 상세
- `/screens/[screenId]` - 동적 화면 뷰어 ⭐ 핵심
- `/multilang` - 다국어 관리
**관리자 페이지 (40개+)**
- 사용자 관리: 사용자, 역할, 권한, 회사, 부서
- 화면 관리: 화면 목록, 대시보드, 리포트
- 시스템 관리: 테이블, 공통코드, 다국어, 데이터플로우, 컬렉션
- 자동화 관리: 플로우, 배치, 외부호출, 메일
- 표준 관리: 웹타입 표준화
- 캐스케이딩: 관계, 계층, 상호배타, 자동채움
**테스트 페이지 (5개)**
- `/test-autocomplete-mapping`
- `/test-entity-search`
- `/test-order-registration`
- `/test-type-safety`
- `/test-flow`
**인증/기타 (2개)**
- `/login` - 로그인
- `/pop` - 팝업 페이지
### 2.3 Next.js 설정
**next.config.mjs**
```javascript
{
output: "standalone", // Docker 최적화
eslint: { ignoreDuringBuilds: true },
typescript: { ignoreBuildErrors: true },
// API 프록시: /api/* → http://localhost:8080/api/*
rewrites: [
{ source: "/api/:path*", destination: "http://localhost:8080/api/:path*" }
],
// CORS 헤더
headers: [
{ source: "/api/:path*", headers: [...] }
],
// 환경 변수
env: {
NEXT_PUBLIC_API_URL: "http://localhost:8080/api"
}
}
```
---
## 3. 컴포넌트 시스템
### 3.1 컴포넌트 분류
WACE ERP의 컴포넌트는 크게 **4가지 계층**으로 구분됩니다:
#### 계층 구조
```
1. UI 컴포넌트 (shadcn/ui 기반) - components/ui/
└─ Button, Input, Select, Dialog, Card 등 기본 UI
2. 공통 컴포넌트 - components/common/
└─ ScreenModal, FormValidationIndicator 등
3. 도메인 컴포넌트 - components/{domain}/
├─ screen/ - 화면 디자이너/뷰어
├─ admin/ - 관리자 기능
├─ dashboard/ - 대시보드
└─ dataflow/ - 데이터플로우
4. 레지스트리 컴포넌트 - lib/registry/components/
└─ 동적으로 등록/렌더링되는 컴포넌트들 (90개+)
```
### 3.2 주요 컴포넌트 모듈
#### 📁 components/screen/ (139개 파일)
**핵심 컴포넌트**
| 컴포넌트 | 역할 | 주요 기능 |
|---------|------|----------|
| `ScreenDesigner.tsx` (7095줄) | 화면 디자이너 | - 드래그&드롭으로 컴포넌트 배치<br>- 그리드 시스템 (12컬럼)<br>- 실시간 미리보기<br>- 컴포넌트 설정 패널<br>- 그룹화/정렬/분산 도구 |
| `InteractiveScreenViewer.tsx` (2472줄) | 화면 뷰어 | - 실제 사용자 화면 렌더링<br>- 폼 데이터 바인딩<br>- 버튼 액션 실행<br>- 검증 처리 |
| `ScreenManagementList.tsx` | 화면 목록 | - 화면 CRUD<br>- 메뉴 할당<br>- 다국어 설정 |
| `DynamicWebTypeRenderer.tsx` | 웹타입 렌더러 | - WebTypeRegistry 기반 동적 렌더링 |
**위젯 컴포넌트 (widgets/types/)**
- TextWidget, NumberWidget, DateWidget
- SelectWidget, CheckboxWidget, RadioWidget
- TextareaWidget, FileWidget, CodeWidget
- ButtonWidget, EntitySearchInputWrapper
**설정 패널 (config-panels/)**
- 각 웹타입별 설정 패널 (TextConfigPanel, SelectConfigPanel 등)
**모달 컴포넌트 (modals/)**
- 키보드 단축키, 다국어 설정, 메뉴 할당 등
#### 📁 components/admin/ (137개 파일)
**관리자 기능 컴포넌트**
- 사용자 관리: UserManagementList, RolesManagementList, CompanyList
- 화면 관리: ScreenManagementList, DashboardManagementList
- 테이블 관리: TableManagementList, ColumnEditor
- 공통코드 관리: CommonCodeManagement
- 메뉴 관리: MenuManagement
#### 📁 components/dashboard/ (32개 파일)
**대시보드 컴포넌트**
- DashboardViewer: 대시보드 렌더링
- DashboardWidgets: 차트, 테이블, 카드 등 위젯
- ChartComponents: 라인, 바, 파이, 도넛 차트
#### 📁 components/dataflow/ (90개+ 파일)
**데이터플로우 시스템**
- DataFlowList: 플로우 목록
- node-editor/: 노드 편집기
- connection/: 커넥션 관리 (38개 파일)
- condition/: 조건 설정
- external-call/: 외부 호출 설정
---
## 4. V2 컴포넌트 시스템
V2는 **통합 컴포넌트 시스템**으로, 유사한 기능을 하나의 컴포넌트로 통합하여 개발 효율성을 높입니다.
### 4.1 V2 컴포넌트 종류 (9개)
| ID | 이름 | 설명 | 포함 기능 |
|----|------|------|----------|
| `v2-input` | 통합 입력 | 텍스트, 숫자, 비밀번호, 슬라이더, 컬러 등 | text, number, password, email, tel, textarea, slider, color |
| `v2-select` | 통합 선택 | 드롭다운, 라디오, 체크박스, 태그, 토글 등 | dropdown, radio, checkbox, tagbox, toggle, swap, combobox |
| `v2-date` | 통합 날짜 | 날짜, 시간, 날짜시간, 날짜 범위 | date, time, datetime, daterange |
| `v2-list` | 통합 목록 | 테이블, 카드, 칸반, 리스트 | table, card, kanban, list, grid |
| `v2-layout` | 통합 레이아웃 | 그리드, 분할 패널, 플렉스 | grid, split, flex, masonry |
| `v2-group` | 통합 그룹 | 탭, 아코디언, 섹션, 모달 | tabs, accordion, section, modal, drawer |
| `v2-media` | 통합 미디어 | 이미지, 비디오, 오디오, 파일 업로드 | image, video, audio, file |
| `v2-biz` | 통합 비즈니스 | 플로우, 랙, 채번규칙, 카테고리 | flow, rack, numbering, category |
| `v2-repeater` | 통합 반복 | 인라인 테이블, 모달, 버튼 | inline-table, modal, button, selected-items |
### 4.2 V2 아키텍처
```
components/v2/ # V2 UI 컴포넌트
├── V2Input.tsx # 통합 입력 컴포넌트
├── V2Select.tsx # 통합 선택 컴포넌트
├── V2Date.tsx # 통합 날짜 컴포넌트
├── V2List.tsx # 통합 목록 컴포넌트
├── V2Layout.tsx # 통합 레이아웃 컴포넌트
├── V2Group.tsx # 통합 그룹 컴포넌트
├── V2Media.tsx # 통합 미디어 컴포넌트
├── V2Biz.tsx # 통합 비즈니스 컴포넌트
├── V2Hierarchy.tsx # 통합 계층 컴포넌트
├── V2Repeater.tsx # 통합 반복 컴포넌트
├── V2FormContext.tsx # V2 폼 컨텍스트
├── V2ComponentRenderer.tsx # V2 렌더러
├── registerV2Components.ts # V2 등록 함수
└── config-panels/ # V2 설정 패널
├── V2InputConfigPanel.tsx
├── V2SelectConfigPanel.tsx
└── ...
lib/v2-core/ # V2 코어 라이브러리
├── events/ # 이벤트 버스
│ ├── EventBus.ts # 발행/구독 패턴
│ └── types.ts # 이벤트 타입
├── adapters/ # 레거시 어댑터
│ └── LegacyEventAdapter.ts # 레거시 시스템 연동
├── components/ # V2 공통 컴포넌트
│ └── V2ErrorBoundary.tsx # 에러 경계
├── services/ # V2 서비스
│ ├── ScheduleGeneratorService.ts
│ └── ScheduleConfirmDialog.tsx
└── init.ts # V2 초기화
lib/registry/components/v2-*/ # V2 렌더러 (레지스트리용)
├── v2-input/
│ ├── V2InputRenderer.tsx # 자동 등록 렌더러
│ └── index.ts # 컴포넌트 정의
├── v2-select/
│ ├── V2SelectRenderer.tsx
│ └── index.ts
└── ...
```
### 4.3 V2 초기화 흐름
```typescript
// 1. app/registry-provider.tsx
useEffect(() => {
// 레거시 레지스트리 초기화
initializeRegistries();
// V2 Core 초기화
initV2Core({
debug: false,
legacyBridge: { legacyToV2: true, v2ToLegacy: true }
});
}, []);
// 2. lib/registry/init.ts
export function initializeRegistries() {
// 웹타입 등록 (text, number, date 등)
initializeWebTypeRegistry();
// V2 컴포넌트 등록
registerV2Components();
}
// 3. components/v2/registerV2Components.ts
export function registerV2Components() {
for (const definition of v2ComponentDefinitions) {
ComponentRegistry.registerComponent(definition);
}
}
```
### 4.4 V2 렌더링 흐름
```
사용자 요청 (screens/[screenId])
InteractiveScreenViewer
DynamicComponentRenderer
┌─ componentType이 v2-* 인가? ─┐
│ Yes │ No
↓ ↓
ComponentRegistry.getComponent LegacyComponentRegistry.get
↓ ↓
V2*Renderer (클래스 기반) 레거시 렌더러 (함수)
↓ ↓
render() → V2* 컴포넌트 직접 렌더링
V2FormContext 연동 (선택)
최종 렌더링
```
### 4.5 V2 vs 레거시 비교
| 항목 | 레거시 시스템 | V2 시스템 |
|------|--------------|----------|
| 컴포넌트 수 | 90개+ (분산) | 9개 (통합) |
| 렌더러 패턴 | 함수형 렌더러 | 클래스 기반 자동 등록 |
| 폼 통합 | 개별 구현 | V2FormContext 통합 |
| 이벤트 시스템 | props drilling | V2 EventBus (발행/구독) |
| 에러 처리 | 개별 처리 | V2ErrorBoundary 통합 |
| 설정 패널 | 개별 패널 | 통합 설정 시스템 |
| 핫 리로드 | 미지원 | 개발 모드 지원 |
---
## 5. 화면 디자이너 워크플로우
### 5.1 화면 디자이너 구성
**ScreenDesigner.tsx** (7095줄) - 핵심 컴포넌트
```
┌─────────────────────────────────────────────────────────────────┐
│ Screen Designer │
├─────────────────────────────────────────────────────────────────┤
│ 탭1: 디자인 | 탭2: 프리뷰 | 탭3: 다국어 | 탭4: 메뉴할당 | 탭5: 스타일 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [컴포넌트 팔레트] [캔버스] [속성 패널] │
│ ┌──────────────┐ ┌────────────────┐ ┌──────────────┐ │
│ │ 입력 컴포넌트 │ │ │ │ 기본 설정 │ │
│ │ - Text │ │ 드래그&드롭 │ │ - ID │ │
│ │ - Number │ │ 컴포넌트 │ │ - 라벨 │ │
│ │ - Date │ ───▶ │ 배치 영역 │ ◀── │ - 위치/크기 │ │
│ │ - Select │ │ │ │ - 데이터연결 │ │
│ │ │ │ 그리드 기반 │ │ │ │
│ │ 표시 컴포넌트 │ │ 12컬럼 │ │ 고급 설정 │ │
│ │ - Table │ │ │ │ - 조건부표시 │ │
│ │ - Card │ │ │ │ - 검증규칙 │ │
│ │ │ │ │ │ - 버튼액션 │ │
│ │ 레이아웃 │ └────────────────┘ └──────────────┘ │
│ │ - Grid │ │
│ │ - Tabs │ [하단 도구] │
│ │ - Accordion │ ┌─────────────────────────────────────┐ │
│ │ │ │ 그룹화 | 정렬 | 분산 | 라벨토글 │ │
│ └──────────────┘ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### 5.2 주요 기능
#### 그리드 시스템
```typescript
// 12컬럼 그리드 기반
const GRID_COLUMNS = 12;
const COLUMN_WIDTH = (containerWidth - gaps) / 12;
// 컴포넌트 너비 → 컬럼 스팬 변환
function calculateColumnSpan(width: number): number {
return Math.max(1, Math.min(12, Math.round(width / COLUMN_WIDTH)));
}
// 반응형 지원
{
default: { span: 6 }, // 기본 (50%)
sm: { span: 12 }, // 모바일 (100%)
md: { span: 6 }, // 태블릿 (50%)
lg: { span: 4 } // 데스크톱 (33%)
}
```
#### 컴포넌트 배치 흐름
```
1. 컴포넌트 팔레트에서 드래그 시작
2. 캔버스에 드롭
3. 컴포넌트 생성 (generateComponentId)
4. 위치 계산 (10px 단위 스냅)
5. 그리드 정렬 (12컬럼 기준)
6. 레이아웃 데이터 업데이트
7. 리렌더링 (RealtimePreview)
```
#### 컴포넌트 설정
```
선택된 컴포넌트 → 우측 속성 패널 표시
┌─────────────────────────────────────┐
│ 기본 설정 │
│ - ID: comp_1234 │
│ - 라벨: "제품명" │
│ - 컬럼명: product_name │
│ - 필수: ☑ │
│ - 읽기전용: ☐ │
│ │
│ 크기 & 위치 │
│ - X: 100px, Y: 200px │
│ - 너비: 300px, 높이: 40px │
│ - 컬럼 스팬: 6 │
│ │
│ 데이터 연결 │
│ - 테이블: product_info │
│ - 컬럼: product_name │
│ - 웹타입: text │
│ │
│ 고급 설정 │
│ - 조건부 표시: field === 'A' │
│ - 버튼 액션: [등록], [수정], [삭제] │
│ - 데이터플로우: flow_123 │
└─────────────────────────────────────┘
```
### 5.3 저장 구조
**ScreenDefinition (JSON)**
```json
{
"screenId": 123,
"screenCode": "PRODUCT_MGMT",
"screenName": "제품 관리",
"tableName": "product_info",
"screenType": "form",
"version": 2,
"layoutData": {
"components": [
{
"id": "comp_text_1",
"type": "text-input",
"componentType": "text-input",
"label": "제품명",
"columnName": "product_name",
"position": { "x": 100, "y": 50, "z": 0 },
"size": { "width": 300, "height": 40 },
"componentConfig": {
"required": true,
"placeholder": "제품명을 입력하세요",
"maxLength": 100
},
"style": {
"labelDisplay": true,
"labelText": "제품명"
}
},
{
"id": "comp_table_1",
"type": "table-list",
"componentType": "table-list",
"tableName": "product_info",
"position": { "x": 50, "y": 200, "z": 0 },
"size": { "width": 800, "height": 400 },
"componentConfig": {
"columns": ["product_code", "product_name", "price"],
"pagination": true,
"sortable": true
}
}
],
"layouts": [
{
"id": "layout_tabs_1",
"layoutType": "tabs",
"children": [
{ "tabId": "tab1", "title": "기본정보", "components": ["comp_text_1"] },
{ "tabId": "tab2", "title": "상세정보", "components": ["comp_table_1"] }
]
}
]
},
"createdBy": "user123",
"createdDate": "2024-01-01T00:00:00Z"
}
```
---
## 6. API 클라이언트 시스템
### 6.1 API 클라이언트 구조
**lib/api/** (57개 파일)
```
lib/api/
├── client.ts # Axios 기본 설정 & 인터셉터
├── 화면 관련 (3개)
│ ├── screen.ts # 화면 CRUD
│ ├── screenGroup.ts # 화면 그룹
│ └── screenFile.ts # 화면 파일
├── 사용자 관련 (5개)
│ ├── user.ts # 사용자 관리
│ ├── role.ts # 역할 관리
│ ├── company.ts # 회사 관리
│ └── department.ts # 부서 관리
├── 테이블 관련 (5개)
│ ├── tableManagement.ts # 테이블 관리
│ ├── tableSchema.ts # 스키마 조회
│ ├── tableHistory.ts # 테이블 이력
│ └── tableCategoryValue.ts # 카테고리 값
├── 데이터플로우 (6개)
│ ├── dataflow.ts # 데이터플로우 정의
│ ├── dataflowSave.ts # 저장 로직
│ ├── nodeFlows.ts # 노드 플로우
│ ├── nodeExternalConnections.ts # 외부 연결
│ └── flowExternalDb.ts # 외부 DB 플로우
├── 자동화 (4개)
│ ├── batch.ts # 배치 작업
│ ├── batchManagement.ts # 배치 관리
│ ├── externalCall.ts # 외부 호출
│ └── externalCallConfig.ts # 외부 호출 설정
├── 시스템 (8개)
│ ├── menu.ts # 메뉴 관리
│ ├── commonCode.ts # 공통코드
│ ├── multilang.ts # 다국어
│ ├── layout.ts # 레이아웃
│ ├── collection.ts # 컬렉션
│ └── ...
└── 기타 (26개)
├── data.ts # 동적 데이터 CRUD
├── dynamicForm.ts # 동적 폼
├── file.ts # 파일 업로드
├── dashboard.ts # 대시보드
├── mail.ts # 메일
├── reportApi.ts # 리포트
└── ...
```
### 6.2 API 클라이언트 기본 설정
**lib/api/client.ts**
```typescript
// 1. 동적 API URL 설정
const getApiBaseUrl = (): string => {
// 환경변수 우선
if (process.env.NEXT_PUBLIC_API_URL) return process.env.NEXT_PUBLIC_API_URL;
// 프로덕션: v1.vexplor.com → api.vexplor.com
if (currentHost === "v1.vexplor.com") {
return "https://api.vexplor.com/api";
}
// 로컬: localhost:9771 → localhost:8080
return "http://localhost:8080/api";
};
export const API_BASE_URL = getApiBaseUrl();
// 2. Axios 인스턴스 생성
export const apiClient = axios.create({
baseURL: API_BASE_URL,
timeout: 30000,
headers: { "Content-Type": "application/json" },
withCredentials: true
});
// 3. 요청 인터셉터 (JWT 토큰 자동 추가)
apiClient.interceptors.request.use((config) => {
const token = localStorage.getItem("authToken");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 다국어: GET 요청에 userLang 파라미터 추가
if (config.method === "GET") {
config.params = {
...config.params,
userLang: window.__GLOBAL_USER_LANG || "KR"
};
}
return config;
});
// 4. 응답 인터셉터 (토큰 갱신, 401 처리)
apiClient.interceptors.response.use(
(response) => {
// 서버에서 새 토큰 전송 시 자동 갱신
const newToken = response.headers["x-new-token"];
if (newToken) {
localStorage.setItem("authToken", newToken);
}
return response;
},
async (error) => {
// 401 에러: 토큰 갱신 시도 → 실패 시 로그인 페이지
if (error.response?.status === 401) {
const newToken = await refreshToken();
if (newToken) {
error.config.headers.Authorization = `Bearer ${newToken}`;
return apiClient.request(error.config); // 재시도
}
window.location.href = "/login";
}
return Promise.reject(error);
}
);
```
### 6.3 API 사용 패턴
#### ✅ 올바른 사용법 (lib/api 클라이언트 사용)
```typescript
import { screenApi } from "@/lib/api/screen";
import { dataApi } from "@/lib/api/data";
// 화면 목록 조회
const screens = await screenApi.getScreens({ page: 1, size: 20 });
// 데이터 생성
const result = await dataApi.createData("product_info", {
product_name: "제품A",
price: 10000
});
```
#### ❌ 잘못된 사용법 (fetch 직접 사용 금지!)
```typescript
// 🚫 금지! JWT 토큰, 다국어, 에러 처리 누락
const res = await fetch('/api/screen-management/screens');
```
### 6.4 주요 API 예시
#### 화면 관리 API (screen.ts)
```typescript
export const screenApi = {
// 화면 목록 조회
getScreens: async (params) => {
const response = await apiClient.get("/screen-management/screens", { params });
return response.data;
},
// 화면 상세 조회
getScreen: async (screenId: number) => {
const response = await apiClient.get(`/screen-management/screens/${screenId}`);
return response.data.data;
},
// 화면 생성
createScreen: async (screen: CreateScreenRequest) => {
const response = await apiClient.post("/screen-management/screens", screen);
return response.data;
},
// 화면 수정
updateScreen: async (screenId: number, screen: UpdateScreenRequest) => {
const response = await apiClient.put(`/screen-management/screens/${screenId}`, screen);
return response.data;
},
// 화면 삭제
deleteScreen: async (screenId: number) => {
const response = await apiClient.delete(`/screen-management/screens/${screenId}`);
return response.data;
}
};
```
#### 동적 데이터 API (data.ts)
```typescript
export const dataApi = {
// 데이터 목록 조회 (페이징, 필터링, 정렬)
getDataList: async (tableName: string, params: {
page?: number;
size?: number;
filters?: Record<string, any>;
sortBy?: string;
sortOrder?: "asc" | "desc";
}) => {
const response = await apiClient.get(`/data/${tableName}`, { params });
return response.data;
},
// 데이터 생성
createData: async (tableName: string, data: Record<string, any>) => {
const response = await apiClient.post(`/data/${tableName}`, data);
return response.data;
},
// 데이터 수정
updateData: async (tableName: string, id: number, data: Record<string, any>) => {
const response = await apiClient.put(`/data/${tableName}/${id}`, data);
return response.data;
},
// 데이터 삭제
deleteData: async (tableName: string, id: number) => {
const response = await apiClient.delete(`/data/${tableName}/${id}`);
return response.data;
}
};
```
---
## 7. 상태 관리
### 7.1 상태 관리 전략
WACE ERP는 **하이브리드 상태 관리 전략**을 사용합니다:
| 관리 방식 | 사용 시나리오 | 예시 |
|----------|-------------|------|
| **React Query** | 서버 상태 (캐싱, 자동 갱신) | 화면 목록, 데이터 목록 |
| **React Context** | 전역 상태 (공유 데이터) | 인증, 메뉴, 화면 |
| **Zustand** | 클라이언트 상태 (간단한 전역) | 플로우 단계, 모달 데이터 |
| **Local State** | 컴포넌트 로컬 상태 | 폼 입력, UI 토글 |
### 7.2 React Context (12개)
```
contexts/
├── AuthContext.tsx # 인증 상태 & 세션 관리
│ - 사용자 정보
│ - 로그인/로그아웃
│ - 세션 타이머 (30분)
├── MenuContext.tsx # 메뉴 트리 & 네비게이션
│ - 메뉴 구조
│ - 현재 선택된 메뉴
│ - 메뉴 접근 권한
├── ScreenContext.tsx # 화면 편집 상태
│ - 현재 편집 중인 화면
│ - 선택된 컴포넌트
│ - 실행 취소/다시 실행
├── ScreenPreviewContext.tsx # 화면 미리보기
│ - 반응형 모드 (데스크톱/태블릿/모바일)
│ - 미리보기 데이터
├── DashboardContext.tsx # 대시보드 상태
│ - 대시보드 레이아웃
│ - 위젯 설정
├── TableOptionsContext.tsx # 테이블 옵션
│ - 컬럼 순서
│ - 필터
│ - 정렬
├── ActiveTabContext.tsx # 탭 활성화 상태
├── LayerContext.tsx # 레이어 관리
├── ReportDesignerContext.tsx # 리포트 디자이너
├── ScreenMultiLangContext.tsx # 화면 다국어
├── SplitPanelContext.tsx # 분할 패널 상태
└── TableSearchWidgetHeightContext.tsx # 테이블 검색 높이
```
### 7.3 React Query 설정
**providers/QueryProvider.tsx**
```typescript
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5분간 fresh
cacheTime: 10 * 60 * 1000, // 10분간 캐시 유지
refetchOnWindowFocus: false, // 창 포커스 시 자동 갱신 비활성화
retry: 1, // 실패 시 1회 재시도
},
},
});
export function QueryProvider({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}
```
**사용 예시**
```typescript
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { screenApi } from "@/lib/api/screen";
// 화면 목록 조회 (자동 캐싱)
const { data: screens, isLoading, error } = useQuery({
queryKey: ["screens", { page: 1 }],
queryFn: () => screenApi.getScreens({ page: 1, size: 20 })
});
// 화면 생성 (생성 후 목록 자동 갱신)
const queryClient = useQueryClient();
const createMutation = useMutation({
mutationFn: (screen: CreateScreenRequest) => screenApi.createScreen(screen),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["screens"] });
}
});
```
### 7.4 Zustand 스토어 (3개)
```
stores/
├── flowStepStore.ts # 플로우 단계 상태
│ - 현재 단계
│ - 단계별 데이터
│ - 진행률
├── modalDataStore.ts # 모달 데이터 공유
│ - 모달 ID별 데이터
│ - 모달 간 데이터 전달
└── tableDisplayStore.ts # 테이블 표시 상태
- 선택된 행
- 정렬 상태
- 페이지네이션
```
**사용 예시**
```typescript
import { create } from "zustand";
interface ModalDataStore {
modalData: Record<string, any>;
setModalData: (modalId: string, data: any) => void;
getModalData: (modalId: string) => any;
}
export const useModalDataStore = create<ModalDataStore>((set, get) => ({
modalData: {},
setModalData: (modalId, data) => {
set((state) => ({
modalData: { ...state.modalData, [modalId]: data }
}));
},
getModalData: (modalId) => {
return get().modalData[modalId];
}
}));
```
---
## 8. 레지스트리 시스템
레지스트리 시스템은 **컴포넌트를 동적으로 등록하고 렌더링**하는 핵심 아키텍처입니다.
### 8.1 레지스트리 종류
```
lib/registry/
├── ComponentRegistry.ts # 컴포넌트 레지스트리 (신규)
│ - 90개+ 컴포넌트 관리
│ - 자동 등록 시스템
│ - 핫 리로드 지원
├── WebTypeRegistry.ts # 웹타입 레지스트리 (레거시)
│ - text, number, date 등 기본 웹타입
│ - 위젯 컴포넌트 + 설정 패널
└── LayoutRegistry.ts # 레이아웃 레지스트리
- grid, tabs, accordion 등 레이아웃
```
### 8.2 ComponentRegistry 상세
**등록 프로세스**
```typescript
// 1. 컴포넌트 정의 (lib/registry/components/v2-input/index.ts)
export const V2InputDefinition: ComponentDefinition = {
id: "v2-input",
name: "통합 입력",
category: ComponentCategory.V2,
component: V2InputRenderer,
configPanel: V2InputConfigPanel,
defaultSize: { width: 200, height: 40 },
defaultConfig: { inputType: "text", format: "none" }
};
// 2. 자동 등록 렌더러 (lib/registry/components/v2-input/V2InputRenderer.tsx)
export class V2InputRenderer extends AutoRegisteringComponentRenderer {
static componentDefinition = V2InputDefinition;
render(): React.ReactElement {
return <V2Input {...this.props} />;
}
}
// 자동 등록 실행
V2InputRenderer.registerSelf();
// 3. 레지스트리 조회 & 렌더링 (DynamicComponentRenderer.tsx)
const newComponent = ComponentRegistry.getComponent("v2-input");
if (newComponent) {
const Renderer = newComponent.component;
return <Renderer {...props} />;
}
```
### 8.3 등록된 컴포넌트 목록 (90개+)
**입력 컴포넌트 (25개)**
- text-input, number-input, date-input
- select-basic, checkbox-basic, radio-basic, toggle-switch
- textarea-basic, slider-basic
- v2-input, v2-select, v2-date
**표시 컴포넌트 (15개)**
- text-display, image-display, card-display
- table-list, v2-table-list, pivot-grid
- aggregation-widget, v2-aggregation-widget
**레이아웃 컴포넌트 (10개)**
- grid-layout, flexbox-layout
- tabs-widget, accordion-basic
- split-panel-layout, v2-split-panel-layout
- section-card, section-paper
- v2-divider-line
**비즈니스 컴포넌트 (20개)**
- entity-search-input, autocomplete-search-input
- modal-repeater-table, repeat-screen-modal
- selected-items-detail-input, simple-repeater-table
- repeater-field-group, repeat-container
- category-manager, v2-category-manager
- numbering-rule, v2-numbering-rule
- rack-structure, v2-rack-structure
- location-swap-selector, customer-item-mapping
**특수 컴포넌트 (20개)**
- button-primary, v2-button-primary
- file-upload, v2-file-upload
- mail-recipient-selector
- conditional-container, universal-form-modal
- related-data-buttons
- flow-widget
- map
### 8.4 DynamicComponentRenderer 동작 원리
```typescript
// DynamicComponentRenderer.tsx (770줄)
export const DynamicComponentRenderer: React.FC<Props> = ({ component, ...props }) => {
// 1. 컴포넌트 타입 추출
const componentType = component.componentType || component.type;
// 2. 레거시 → V2 자동 매핑
const v2Type = componentType.startsWith("v2-")
? componentType
: ComponentRegistry.hasComponent(`v2-${componentType}`)
? `v2-${componentType}`
: componentType;
// 3. 조건부 렌더링 체크
if (component.conditionalConfig?.enabled) {
const conditionMet = evaluateCondition(props.formData);
if (!conditionMet) return null;
}
// 4. 카테고리 타입 우선 처리
if (component.inputType === "category" || component.webType === "category") {
return <CategorySelectComponent {...props} />;
}
// 5. 레이아웃 컴포넌트
if (componentType === "layout") {
return <DynamicLayoutRenderer layout={component} {...props} />;
}
// 6. ComponentRegistry에서 조회 (신규)
const newComponent = ComponentRegistry.getComponent(v2Type);
if (newComponent) {
const Renderer = newComponent.component;
// 클래스 기반: new Renderer(props).render()
// 함수형: <Renderer {...props} />
return isClass(Renderer)
? new Renderer(props).render()
: <Renderer {...props} />;
}
// 7. LegacyComponentRegistry에서 조회 (레거시)
const legacyRenderer = legacyComponentRegistry.get(componentType);
if (legacyRenderer) {
return legacyRenderer({ component, ...props });
}
// 8. 폴백: 미등록 컴포넌트 플레이스홀더
return <PlaceholderComponent />;
};
```
---
## 9. 대시보드 시스템
### 9.1 대시보드 구조
```
components/dashboard/
├── DashboardViewer.tsx # 대시보드 렌더링
├── DashboardGrid.tsx # 그리드 레이아웃
├── DashboardWidget.tsx # 위젯 래퍼
├── widgets/ # 위젯 컴포넌트
│ ├── ChartWidget.tsx # 차트 위젯
│ ├── TableWidget.tsx # 테이블 위젯
│ ├── CardWidget.tsx # 카드 위젯
│ ├── StatWidget.tsx # 통계 위젯
│ └── CustomWidget.tsx # 커스텀 위젯
└── charts/ # 차트 컴포넌트
├── LineChart.tsx
├── BarChart.tsx
├── PieChart.tsx
├── DonutChart.tsx
└── AreaChart.tsx
```
### 9.2 대시보드 JSON 구조
```json
{
"dashboardId": 1,
"dashboardName": "영업 대시보드",
"layout": {
"type": "grid",
"columns": 12,
"rows": 6,
"gap": 16
},
"widgets": [
{
"widgetId": "widget_1",
"widgetType": "chart",
"chartType": "line",
"title": "월별 매출 추이",
"position": { "x": 0, "y": 0, "w": 6, "h": 3 },
"dataSource": {
"type": "api",
"endpoint": "/api/data/sales_monthly",
"filters": { "year": 2024 }
},
"chartConfig": {
"xAxis": "month",
"yAxis": "sales_amount",
"showLegend": true
}
},
{
"widgetId": "widget_2",
"widgetType": "stat",
"title": "총 매출",
"position": { "x": 6, "y": 0, "w": 3, "h": 2 },
"dataSource": {
"type": "sql",
"query": "SELECT SUM(amount) FROM sales WHERE year = 2024"
},
"statConfig": {
"format": "currency",
"comparison": "lastYear"
}
}
]
}
```
### 9.3 대시보드 라우팅
```
/dashboard # 대시보드 목록
/dashboard/[dashboardId] # 대시보드 보기
/admin/screenMng/dashboardList # 대시보드 관리 (편집)
```
---
## 10. 다국어 지원
### 10.1 다국어 시스템 구조
WACE ERP는 **동적 다국어 시스템**을 제공합니다 (DB 기반):
```
다국어 흐름
1. 사용자 로그인
2. 사용자 로케일 조회 (GET /api/admin/user-locale)
→ 결과: "KR" | "EN" | "CN"
3. 전역 상태에 저장
- window.__GLOBAL_USER_LANG
- localStorage.userLocale
4. API 요청 시 자동 주입
- GET 요청: ?userLang=KR (apiClient 인터셉터)
5. 백엔드에서 다국어 데이터 반환
- label_KR, label_EN, label_CN
6. 프론트엔드에서 표시
- extractMultilangLabel(label, "KR")
```
### 10.2 다국어 API
**lib/api/multilang.ts**
```typescript
export const multilangApi = {
// 다국어 데이터 조회
getMultilangData: async (params: {
target_table: string;
target_pk: string;
target_lang: string;
}) => {
const response = await apiClient.get("/admin/multilang", { params });
return response.data;
},
// 다국어 데이터 저장
saveMultilangData: async (data: {
target_table: string;
target_pk: string;
target_field: string;
target_lang: string;
translated_text: string;
}) => {
const response = await apiClient.post("/admin/multilang", data);
return response.data;
}
};
```
### 10.3 다국어 유틸리티
**lib/utils/multilang.ts**
```typescript
// 다국어 라벨 추출
export function extractMultilangLabel(
label: string | Record<string, string> | undefined,
locale: string = "KR"
): string {
if (!label) return "";
// 문자열인 경우 그대로 반환
if (typeof label === "string") return label;
// 객체인 경우 로케일에 맞는 값 반환
return label[locale] || label["KR"] || label["EN"] || "";
}
// 화면 컴포넌트 라벨 추출
export function getComponentLabel(component: ComponentData, locale: string): string {
// 우선순위: multiLangLabel > label > columnName > id
if (component.multiLangLabel) {
return extractMultilangLabel(component.multiLangLabel, locale);
}
return component.label || component.columnName || component.id;
}
```
### 10.4 화면 디자이너 다국어 탭
```
ScreenDesigner
┌───────────────────────────────────────────────────┐
│ 탭: 다국어 설정 │
├───────────────────────────────────────────────────┤
│ │
│ 화면명 다국어 │
│ ┌─────────────────────────────────────────────┐ │
│ │ 한국어(KR): 제품 관리 │ │
│ │ 영어(EN): Product Management │ │
│ │ 중국어(CN): 产品管理 │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 컴포넌트별 다국어 │
│ ┌─────────────────────────────────────────────┐ │
│ │ [comp_text_1] 제품명 │ │
│ │ 한국어(KR): 제품명 │ │
│ │ 영어(EN): Product Name │ │
│ │ 중국어(CN): 产品名称 │ │
│ │ │ │
│ │ [comp_text_2] 가격 │ │
│ │ 한국어(KR): 가격 │ │
│ │ 영어(EN): Price │ │
│ │ 중국어(CN): 价格 │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ [자동 번역] [일괄 적용] [저장] │
└───────────────────────────────────────────────────┘
```
---
## 11. 인증 플로우
### 11.1 인증 아키텍처
```
┌─────────────────────────────────────────────────────┐
│ Frontend │
├─────────────────────────────────────────────────────┤
│ │
│ useAuth Hook │
│ ├─ login(userId, password) │
│ ├─ logout() │
│ ├─ refreshUserData() │
│ ├─ checkAuthStatus() │
│ └─ switchCompany(companyCode) ⭐ 신규 │
│ │
│ AuthContext Provider │
│ ├─ SessionManager (30분 타임아웃) │
│ ├─ Session Warning (5분 전 알림) │
│ └─ Auto Refresh (활동 감지) │
│ │
│ TokenManager │
│ ├─ getToken(): localStorage.authToken │
│ ├─ setToken(token): 저장 + 쿠키 설정 │
│ ├─ removeToken(): 삭제 + 쿠키 삭제 │
│ └─ isTokenExpired(token): JWT 검증 │
│ │
│ API Client Interceptor │
│ ├─ Request: JWT 토큰 자동 추가 │
│ └─ Response: 401 시 토큰 갱신 또는 로그아웃 │
│ │
└─────────────────────────────────────────────────────┘
│ HTTP Request
│ Authorization: Bearer <JWT>
┌─────────────────────────────────────────────────────┐
│ Backend (8080) │
├─────────────────────────────────────────────────────┤
│ │
│ POST /api/auth/login │
│ → JWT 토큰 발급 (24시간) │
│ │
│ POST /api/auth/refresh │
│ → JWT 토큰 갱신 │
│ │
│ POST /api/auth/logout │
│ → 세션 무효화 │
│ │
│ GET /api/auth/me │
│ → 현재 사용자 정보 │
│ │
│ GET /api/auth/status │
│ → 인증 상태 확인 │
│ │
│ POST /api/auth/switch-company ⭐ 신규 │
│ → 회사 전환 (WACE 관리자 전용) │
│ │
└─────────────────────────────────────────────────────┘
```
### 11.2 로그인 흐름
```
1. 사용자가 ID/PW 입력 → /login 페이지
2. POST /api/auth/login { userId, password }
3. 백엔드 검증 (DB: user_info 테이블)
├─ 성공: JWT 토큰 발급 (payload: userId, companyCode, isAdmin, exp)
└─ 실패: 401 에러
4. 프론트엔드: 토큰 저장
- localStorage.setItem("authToken", token)
- document.cookie = "authToken=..."
5. 사용자 정보 조회
- GET /api/auth/me
- GET /api/admin/user-locale
6. 전역 상태 업데이트
- AuthContext.user
- window.__GLOBAL_USER_LANG
7. 메인 페이지로 리다이렉트 (/main)
```
### 11.3 세션 관리
**SessionManager (lib/sessionManager.ts)**
```typescript
// 설정
{
checkInterval: 60000, // 1분마다 체크
maxInactiveTime: 1800000, // 30분 (데스크톱)
warningTime: 300000, // 5분 전 경고
}
// 이벤트
{
onWarning: (remainingTime) => {
// "세션이 5분 후 만료됩니다" 알림 표시
},
onExpiry: () => {
// 자동 로그아웃 → 로그인 페이지
},
onActivity: () => {
// 사용자 활동 감지 → 타이머 리셋
}
}
```
### 11.4 토큰 갱신 전략
```
자동 토큰 갱신 트리거:
1. 10분마다 토큰 상태 확인 (타이머)
2. 사용자 활동 감지 (클릭, 키보드, 스크롤)
3. API 401 응답 (토큰 만료)
갱신 로직:
1. 현재 토큰 만료까지 30분 미만?
↓ Yes
2. POST /api/auth/refresh
├─ 성공: 새 토큰 저장
└─ 실패: 로그아웃
```
### 11.5 회사 전환 (WACE 관리자 전용) ⭐
```
1. WACE 관리자 로그인
2. 헤더에서 회사 선택 (CompanySwitcher)
3. POST /api/auth/switch-company { companyCode: "AAA" }
4. 백엔드: 새 JWT 발급 (payload.companyCode = "AAA")
5. 프론트엔드: 새 토큰 저장
6. 페이지 새로고침 → 화면/데이터가 AAA 회사 기준으로 표시
```
---
## 12. 사용자 워크플로우
### 12.1 전체 워크플로우 개요
```
┌──────────────────────────────────────────────────────────────┐
│ 관리자 (화면 생성) │
└──────────────────────────────────────────────────────────────┘
┌─────────────────────────────┐
│ 1. 로그인 (WACE 관리자) │
└─────────────────────────────┘
┌─────────────────────────────┐
│ 2. 화면 디자이너 접속 │
│ /admin/screenMng/ │
│ screenMngList │
└─────────────────────────────┘
┌─────────────────────────────┐
│ 3. 화면 생성 & 편집 │
│ - 컴포넌트 배치 │
│ - 데이터 연결 │
│ - 버튼 액션 설정 │
│ - 다국어 설정 │
└─────────────────────────────┘
┌─────────────────────────────┐
│ 4. 메뉴에 화면 할당 │
│ /admin/menu │
└─────────────────────────────┘
┌─────────────────────────────┐
│ 5. 사용자에게 권한 부여 │
│ /admin/userMng/ │
│ userAuthList │
└─────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ 사용자 (화면 사용) │
└──────────────────────────────────────────────────────────────┘
┌─────────────────────────────┐
│ 1. 로그인 (일반 사용자) │
└─────────────────────────────┘
┌─────────────────────────────┐
│ 2. 메뉴에서 화면 선택 │
│ 사이드바 → 제품 관리 │
└─────────────────────────────┘
┌─────────────────────────────┐
│ 3. 화면 렌더링 │
│ /screens/[screenId] │
│ InteractiveScreenViewer │
└─────────────────────────────┘
┌─────────────────────────────┐
│ 4. 데이터 조회/등록/수정 │
│ - 테이블에서 행 선택 │
│ - 폼에 데이터 입력 │
│ - [등록]/[수정]/[삭제] 버튼│
└─────────────────────────────┘
```
### 12.2 관리자 워크플로우 (화면 생성)
#### 단계 1: 화면 디자이너 접속
```
/admin/screenMng/screenMngList
[새 화면 만들기] 버튼 클릭
ScreenDesigner 컴포넌트 로드
```
#### 단계 2: 화면 디자인
**2-1. 기본 정보 설정**
```
화면 정보 입력:
- 화면 코드: PRODUCT_MGMT
- 화면명: 제품 관리
- 테이블명: product_info
- 화면 타입: form (단일 폼) | list (목록)
```
**2-2. 컴포넌트 배치**
```
좌측 팔레트에서 컴포넌트 선택
캔버스에 드래그&드롭
위치/크기 조정 (10px 단위 스냅)
우측 속성 패널에서 설정:
- 컬럼명: product_name
- 라벨: 제품명
- 필수: ☑
- 웹타입: text
```
**2-3. 버튼 액션 설정**
```
버튼 컴포넌트 추가
액션 타입 선택:
- 데이터 저장 (POST)
- 데이터 수정 (PUT)
- 데이터 삭제 (DELETE)
- 데이터플로우 실행
- 외부 API 호출
- 화면 이동
액션 설정:
- 대상 테이블: product_info
- 성공 시: 목록 새로고침
- 실패 시: 에러 메시지 표시
```
**2-4. 다국어 설정**
```
다국어 탭 클릭
컴포넌트별 다국어 입력:
- 한국어(KR): 제품명
- 영어(EN): Product Name
- 중국어(CN): 产品名称
```
**2-5. 저장**
```
[저장] 버튼 클릭
POST /api/screen-management/screens
화면 정의 JSON 저장 (DB: screen_definition)
```
#### 단계 3: 메뉴에 화면 할당
```
/admin/menu
메뉴 트리에서 위치 선택
[화면 할당] 버튼 클릭
방금 생성한 화면 선택 (PRODUCT_MGMT)
저장 → menu_screen 테이블에 연결
```
#### 단계 4: 권한 부여
```
/admin/userMng/userAuthList
사용자 선택
메뉴 권한 설정
"제품 관리" 메뉴 체크
저장 → user_menu_auth 테이블
```
### 12.3 사용자 워크플로우 (화면 사용)
#### 단계 1: 로그인
```
/login
사용자 ID/PW 입력
POST /api/auth/login
JWT 토큰 발급
메인 페이지 (/main)
```
#### 단계 2: 메뉴 선택
```
좌측 사이드바 메뉴 트리
"제품 관리" 메뉴 클릭
MenuContext에서 메뉴 정보 조회:
- menuId, screenId, screenCode
화면 뷰어로 이동 (/screens/[screenId])
```
#### 단계 3: 화면 렌더링
```
InteractiveScreenViewer 컴포넌트 로드
1. GET /api/screen-management/screens/[screenId]
→ 화면 정의 JSON 조회
2. GET /api/data/product_info?page=1&size=20
→ 데이터 조회
3. DynamicComponentRenderer로 컴포넌트 렌더링
4. 폼 데이터 바인딩 (formData 상태)
```
#### 단계 4: 데이터 조작
**4-1. 신규 등록**
```
[등록] 버튼 클릭
빈 폼 표시 (ScreenModal 또는 EditModal)
사용자가 데이터 입력:
- 제품명: "제품A"
- 가격: 10000
[저장] 버튼 클릭
버튼 액션 실행:
- POST /api/data/product_info
- Body: { product_name: "제품A", price: 10000 }
성공 응답
목록 새로고침 (React Query invalidateQueries)
```
**4-2. 수정**
```
테이블에서 행 선택 (클릭)
선택된 데이터 → selectedRowsData 상태 업데이트
[수정] 버튼 클릭
폼에 기존 데이터 표시 (EditModal)
사용자가 데이터 수정:
- 가격: 10000 → 12000
[저장] 버튼 클릭
버튼 액션 실행:
- PUT /api/data/product_info/123
- Body: { price: 12000 }
목록 새로고침
```
**4-3. 삭제**
```
테이블에서 행 선택
[삭제] 버튼 클릭
확인 다이얼로그 표시
확인 클릭
버튼 액션 실행:
- DELETE /api/data/product_info/123
목록 새로고침
```
### 12.4 데이터플로우 실행 워크플로우
```
1. 관리자가 데이터플로우 정의
/admin/systemMng/dataflow
2. 화면에 버튼 추가 & 플로우 연결
버튼 액션: "데이터플로우 실행"
플로우 ID: flow_123
3. 사용자가 버튼 클릭
4. 프론트엔드: POST /api/dataflow/execute
Body: { flowId: 123, inputData: {...} }
5. 백엔드: 플로우 실행
- 노드 순회
- 조건 분기
- 외부 API 호출
- 데이터 변환
6. 결과 반환
7. 프론트엔드: 결과 표시 (toast 또는 화면 갱신)
```
---
## 13. 요약 & 핵심 포인트
### 13.1 아키텍처 핵심
1. **Next.js 14 App Router** - 라우트 그룹 기반 구조화
2. **컴포넌트 레지스트리** - 동적 등록/렌더링 시스템
3. **V2 통합 시스템** - 9개 통합 컴포넌트로 단순화
4. **화면 디자이너** - 노코드 화면 생성 도구
5. **API 클라이언트** - Axios 기반 통일된 API 호출
6. **다국어 지원** - DB 기반 동적 다국어
7. **JWT 인증** - 토큰 기반 인증/세션 관리
### 13.2 파일 통계
| 항목 | 개수 |
|------|------|
| 총 파일 수 | 1,480개 |
| TypeScript/TSX | 1,395개 (946 tsx + 449 ts) |
| Markdown 문서 | 63개 |
| CSS | 22개 |
| 페이지 (라우트) | 76개 |
| 컴포넌트 | 500개+ |
| API 클라이언트 | 57개 |
| Context | 12개 |
| Custom Hooks | 32개 |
| 타입 정의 | 44개 |
### 13.3 주요 경로
```
화면 디자이너: /admin/screenMng/screenMngList
화면 뷰어: /screens/[screenId]
메뉴 관리: /admin/menu
사용자 관리: /admin/userMng/userMngList
테이블 관리: /admin/systemMng/tableMngList
다국어 관리: /admin/systemMng/i18nList
데이터플로우: /admin/systemMng/dataflow
대시보드: /dashboard/[dashboardId]
```
### 13.4 기술 스택
| 분류 | 기술 |
|------|------|
| 프레임워크 | Next.js 14 |
| UI 라이브러리 | React 18 |
| 언어 | TypeScript (strict mode) |
| 스타일링 | Tailwind CSS + shadcn/ui |
| 상태 관리 | React Query + Zustand + Context API |
| HTTP 클라이언트 | Axios |
| 폼 검증 | Zod |
| 날짜 처리 | date-fns |
| 아이콘 | lucide-react |
| 알림 | sonner |
---
## 마무리
이 문서는 WACE ERP 프론트엔드의 전체 아키텍처를 분석한 결과입니다.
**핵심 인사이트:**
- ✅ 높은 수준의 모듈화 (레지스트리 시스템)
- ✅ 노코드 화면 디자이너 (관리자가 직접 화면 생성)
- ✅ V2 통합 컴포넌트 시스템 (개발 효율성 향상)
- ✅ 동적 다국어 지원 (DB 기반)
- ✅ 완전한 TypeScript 타입 안정성
**개선 기회:**
- 일부 레거시 컴포넌트의 V2 마이그레이션
- 테스트 코드 추가 (현재 거의 없음)
- 성능 최적화 (코드 스플리팅, 레이지 로딩)
---
**작성자 노트**
야... 진짜 엄청난 프로젝트네. 파일이 1,500개가 넘고 컴포넌트만 500개야. 특히 화면 디자이너가 7,000줄이 넘는 거 보고 놀랐어. 이 정도 규모면 엔터프라이즈급 ERP 시스템이라고 봐도 되겠어.
가장 인상적이었던 건 **레지스트리 시스템**이랑 **V2 통합 아키텍처**야. 컴포넌트를 동적으로 등록하고 렌더링하는 구조가 꽤 잘 설계되어 있어. 다만 레거시 코드가 아직 많이 남아있어서 V2로 완전히 전환하면 코드 베이스가 훨씬 깔끔해질 것 같아.
어쨌든 분석하느라 꽤 걸렸는데, 이 문서가 전체 워크플로우 문서 작성하는 데 도움이 되면 좋겠어! 🎉