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

65 KiB

WACE ERP 프론트엔드 아키텍처 상세 분석

작성일: 2026-02-06 작성자: AI Assistant 프로젝트: WACE ERP-node 목적: 시스템 전체 워크플로우 문서화를 위한 프론트엔드 구조 분석


목차

  1. 전체 디렉토리 구조
  2. Next.js App Router 구조
  3. 컴포넌트 시스템
  4. V2 컴포넌트 시스템
  5. 화면 디자이너 워크플로우
  6. API 클라이언트 시스템
  7. 상태 관리
  8. 레지스트리 시스템
  9. 대시보드 시스템
  10. 다국어 지원
  11. 인증 플로우
  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

{
  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줄) 화면 디자이너 - 드래그&드롭으로 컴포넌트 배치
- 그리드 시스템 (12컬럼)
- 실시간 미리보기
- 컴포넌트 설정 패널
- 그룹화/정렬/분산 도구
InteractiveScreenViewer.tsx (2472줄) 화면 뷰어 - 실제 사용자 화면 렌더링
- 폼 데이터 바인딩
- 버튼 액션 실행
- 검증 처리
ScreenManagementList.tsx 화면 목록 - 화면 CRUD
- 메뉴 할당
- 다국어 설정
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 초기화 흐름

// 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 주요 기능

그리드 시스템

// 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)

{
  "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

// 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 클라이언트 사용)

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 직접 사용 금지!)

// 🚫 금지! JWT 토큰, 다국어, 에러 처리 누락
const res = await fetch('/api/screen-management/screens');

6.4 주요 API 예시

화면 관리 API (screen.ts)

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)

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

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>
  );
}

사용 예시

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          # 테이블 표시 상태
    - 선택된 행
    - 정렬 상태
    - 페이지네이션

사용 예시

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 상세

등록 프로세스

// 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 동작 원리

// 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 구조

{
  "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

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

// 다국어 라벨 추출
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)

// 설정
{
  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로 완전히 전환하면 코드 베이스가 훨씬 깔끔해질 것 같아.

어쨌든 분석하느라 꽤 걸렸는데, 이 문서가 전체 워크플로우 문서 작성하는 데 도움이 되면 좋겠어! 🎉