# 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줄) | 화면 디자이너 | - 드래그&드롭으로 컴포넌트 배치
- 그리드 시스템 (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 초기화 흐름 ```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; sortBy?: string; sortOrder?: "asc" | "desc"; }) => { const response = await apiClient.get(`/data/${tableName}`, { params }); return response.data; }, // 데이터 생성 createData: async (tableName: string, data: Record) => { const response = await apiClient.post(`/data/${tableName}`, data); return response.data; }, // 데이터 수정 updateData: async (tableName: string, id: number, data: Record) => { 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 ( {children} ); } ``` **사용 예시** ```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; setModalData: (modalId: string, data: any) => void; getModalData: (modalId: string) => any; } export const useModalDataStore = create((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 ; } } // 자동 등록 실행 V2InputRenderer.registerSelf(); // 3. 레지스트리 조회 & 렌더링 (DynamicComponentRenderer.tsx) const newComponent = ComponentRegistry.getComponent("v2-input"); if (newComponent) { const Renderer = newComponent.component; return ; } ``` ### 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 = ({ 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 ; } // 5. 레이아웃 컴포넌트 if (componentType === "layout") { return ; } // 6. ComponentRegistry에서 조회 (신규) const newComponent = ComponentRegistry.getComponent(v2Type); if (newComponent) { const Renderer = newComponent.component; // 클래스 기반: new Renderer(props).render() // 함수형: return isClass(Renderer) ? new Renderer(props).render() : ; } // 7. LegacyComponentRegistry에서 조회 (레거시) const legacyRenderer = legacyComponentRegistry.get(componentType); if (legacyRenderer) { return legacyRenderer({ component, ...props }); } // 8. 폴백: 미등록 컴포넌트 플레이스홀더 return ; }; ``` --- ## 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 | 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 ↓ ┌─────────────────────────────────────────────────────┐ │ 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로 완전히 전환하면 코드 베이스가 훨씬 깔끔해질 것 같아. 어쨌든 분석하느라 꽤 걸렸는데, 이 문서가 전체 워크플로우 문서 작성하는 데 도움이 되면 좋겠어! 🎉