From 7669baa17157b6144e7cf87a374190d935b1547c Mon Sep 17 00:00:00 2001 From: gbpark Date: Mon, 6 Apr 2026 16:34:21 +0900 Subject: [PATCH] ]] --- README.md | 285 ++++++++++++++++------- frontend/components/layout/AppLayout.tsx | 42 ++-- frontend/components/layout/TabBar.tsx | 5 +- frontend/styles/v5-layout.css | 12 +- 4 files changed, 226 insertions(+), 118 deletions(-) diff --git a/README.md b/README.md index 16e4d466..d738bb67 100644 --- a/README.md +++ b/README.md @@ -1,78 +1,146 @@ -# WACE 솔루션 (ERP/PLM) +# INVION — Low-Code ERP/PLM Platform ## 프로젝트 개요 -본 프로젝트는 WACE ERP/PLM(Product Lifecycle Management) 솔루션입니다. -Node.js + Next.js 기반 풀스택 웹 애플리케이션으로, 멀티테넌시를 지원합니다. +**INVION**은 제조업 특화 Low-Code ERP/PLM(Product Lifecycle Management) 플랫폼입니다. +사용자가 런타임에 화면·테이블·워크플로우를 정의할 수 있는 **메타데이터 기반 설계**로, 코드 수정 없이 업무 화면을 구성합니다. + +듀얼 백엔드(Node.js + Spring Boot) 아키텍처로 점진적 마이그레이션을 지원하며, 멀티테넌시(company_code 기반)로 복수 기업을 단일 인스턴스에서 운영합니다. ## 주요 특징 -- **모던 프론트엔드**: Next.js (App Router) + TypeScript + shadcn/ui -- **Node.js 백엔드**: Express + TypeScript + PostgreSQL -- **반응형 디자인**: 데스크톱, 태블릿, 모바일 모든 기기 지원 -- **멀티테넌시**: 회사별 데이터 격리 (company_code 기반) -- **Docker 기반 배포**: 개발/운영 환경 일관성 보장 -- **타입 안전성**: TypeScript로 런타임 에러 방지 +- **Low-Code 화면 디자이너**: 드래그앤드롭으로 업무 화면 구성, V2 컴포넌트 시스템 +- **듀얼 백엔드**: Node.js(Express) ↔ Spring Boot(MyBatis) 동일 API 스펙 유지 +- **v5 Cosmic Glassmorphism UI**: 코스믹 배경 + 글래스 블러 기반 모던 디자인 시스템 +- **멀티테넌시**: company_code 기반 데이터 격리, Super Admin 전사 접근 +- **멀티 DB 연결**: PostgreSQL(메인) + MSSQL + Oracle + MySQL 외부 DB 지원 +- **반응형 디자인**: 데스크톱 / 태블릿 / 모바일 완전 대응 +- **3D 시각화**: React Three Fiber 기반 Digital Twin / Yard Layout ## 기술 스택 ### Frontend -- **프레임워크**: Next.js (App Router, Turbopack) -- **언어**: TypeScript -- **UI 라이브러리**: shadcn/ui + Radix UI -- **스타일링**: Tailwind CSS -- **상태 관리**: TanStack Query + React Context -- **아이콘**: Lucide React +| 영역 | 기술 | 비고 | +|------|------|------| +| 프레임워크 | **Next.js 15** (App Router, Turbopack) | React 19 | +| 언어 | **TypeScript 5** | strict mode | +| 스타일링 | **Tailwind CSS v4** + v5 커스텀 CSS (`--v5-` prefix) | 글래스모피즘 테마 | +| UI 라이브러리 | **shadcn/ui** + Radix UI | 커스텀 오버라이드 | +| 상태 관리 | **Zustand** (글로벌) + **TanStack Query** (서버) | | +| 테이블 | **TanStack Table** + **TanStack Virtual** | 가상 스크롤 | +| 플로우 디자이너 | **XY Flow** (@xyflow/react) | 데이터플로우 | +| 3D | **React Three Fiber** + Drei | Digital Twin | +| 리치 텍스트 | **Tiptap** | 에디터 | +| 드래그앤드롭 | **DnD Kit** | 화면 디자이너 / 탭 정렬 | +| 폼 | **React Hook Form** + Zod | 유효성 검증 | +| 아이콘 | **Lucide React** | | -### Backend +### Backend — Node.js (Express) -- **런타임**: Node.js 20+ -- **프레임워크**: Express 4 -- **언어**: TypeScript -- **데이터베이스**: PostgreSQL (pg 드라이버) -- **인증**: JWT (jsonwebtoken) + bcryptjs -- **로깅**: Winston +| 영역 | 기술 | 비고 | +|------|------|------| +| 런타임 | **Node.js 20+** | LTS | +| 프레임워크 | **Express 4** | TypeScript | +| 데이터베이스 | **PostgreSQL** (pg) + MSSQL + Oracle + MySQL | 멀티 DB | +| 인증 | **JWT** (jsonwebtoken) + bcryptjs | | +| 로깅 | **Winston** | | +| 메일 | **Nodemailer** + IMAP | 수신/발신 | +| 바코드 | **bwip-js** | | +| 문서 생성 | **docx** + html-to-docx | | +| 스케줄링 | **node-cron** | 배치 | + +### Backend — Spring Boot (마이그레이션 대상) + +| 영역 | 기술 | 비고 | +|------|------|------| +| 언어 | **Java 21** | LTS | +| 프레임워크 | **Spring Boot 3.3.x** | | +| 빌드 | **Gradle** (Groovy DSL) | | +| SQL Mapper | **MyBatis 3** (SqlSessionTemplate 직접 사용) | Mapper Interface 미사용 | +| 데이터베이스 | **PostgreSQL** + HikariCP | | +| 보안 | **Spring Security** + JWT (jjwt) | | +| JSON | **Jackson** (숫자→문자열 직렬화) | Node API 호환 | + +> **아키텍처 핵심**: `Map` 기반 — Low-Code 플랫폼 특성상 테이블/컬럼이 런타임에 결정되므로 DTO 클래스를 사용하지 않습니다. Service에서 `sqlSession`으로 XML을 직접 호출하는 3레이어 구조입니다. +> +> ``` +> Controller → Service (extends BaseService) → XML Mapper +> ``` ### 개발 도구 -- **컨테이너화**: Docker + Docker Compose -- **코드 품질**: ESLint + Prettier -- **테스트**: Jest + Supertest -- **백엔드 핫리로드**: nodemon -- **CI/CD**: Jenkins +| 영역 | 기술 | +|------|------| +| 컨테이너화 | Docker + Docker Compose | +| E2E 테스트 | **Playwright** | +| 단위 테스트 | Jest + Supertest | +| 코드 품질 | ESLint + Prettier | +| 백엔드 핫리로드 | nodemon | +| CI/CD | Jenkins | ## 프로젝트 구조 ``` -ERP-node/ -├── backend-node/ # Express + TypeScript 백엔드 -│ ├── src/ -│ │ ├── app.ts # 엔트리포인트 -│ │ ├── controllers/ # API 컨트롤러 -│ │ ├── services/ # 비즈니스 로직 -│ │ ├── middleware/ # 인증, 에러처리 미들웨어 -│ │ ├── routes/ # 라우터 -│ │ └── config/ # DB 연결 등 설정 -│ └── package.json -├── frontend/ # Next.js 프론트엔드 -│ ├── app/ # App Router 페이지 -│ ├── components/ # React 컴포넌트 -│ │ ├── ui/ # shadcn/ui 기본 컴포넌트 -│ │ ├── admin/ # 관리자 컴포넌트 -│ │ ├── screen/ # 화면 디자이너 -│ │ └── v2/ # V2 컴포넌트 -│ ├── lib/ # 유틸리티, API 클라이언트 -│ ├── hooks/ # Custom React Hooks -│ └── package.json -├── db/ # 데이터베이스 -│ └── migrations/ # 순차 마이그레이션 SQL -├── docker/ # Docker 설정 (dev/prod/deploy) -├── scripts/ # 개발/배포 스크립트 -├── docs/ # 프로젝트 문서 -├── Dockerfile # 프로덕션 멀티스테이지 빌드 -├── Jenkinsfile # CI/CD 파이프라인 -└── .cursorrules # AI 개발 가이드 +INVION/ +├── frontend/ # Next.js 15 프론트엔드 +│ ├── app/ # App Router 페이지 +│ │ ├── (main)/ # 메인 레이아웃 (인증 후) +│ │ ├── (auth)/ # 로그인/인증 +│ │ ├── (admin)/ # 관리자 페이지 +│ │ └── (pop)/ # 팝업 페이지 +│ ├── components/ # React 컴포넌트 (~27개 도메인) +│ │ ├── ui/ # shadcn/ui 기본 컴포넌트 +│ │ ├── layout/ # AppLayout, TabBar, Sidebar, Header +│ │ ├── screen/ # 화면 디자이너 (~57개 파일) +│ │ ├── dataflow/ # 데이터플로우 디자이너 +│ │ ├── v2/ # V2 동적 컴포넌트 시스템 +│ │ ├── admin/ # 관리자 컴포넌트 +│ │ ├── approval/ # 전자결재 +│ │ ├── flow/ # 워크플로우 +│ │ ├── barcode/ # 바코드/라벨 +│ │ ├── report/ # 리포트 +│ │ ├── mail/ # 메일 +│ │ ├── multilang/ # 다국어 +│ │ ├── animations/ # 애니메이션 컴포넌트 +│ │ └── ... # dashboard, vehicle, tax-invoice 등 +│ ├── lib/ # 유틸리티, API 클라이언트, 서비스 +│ │ ├── api/ # API 클라이언트 (fetch 직접 사용 금지) +│ │ ├── stores/ # Zustand 스토어 +│ │ ├── services/ # 프론트 비즈니스 로직 +│ │ └── types/ # TypeScript 타입 정의 +│ ├── hooks/ # Custom React Hooks +│ └── styles/ # v5-layout.css (글래스모피즘) +│ +├── backend-node/ # Express + TypeScript 백엔드 (~87개 서비스) +│ └── src/ +│ ├── app.ts # 엔트리포인트 +│ ├── controllers/ # API 컨트롤러 +│ ├── services/ # 비즈니스 로직 +│ ├── middleware/ # 인증, 에러처리 +│ ├── routes/ # 라우터 +│ └── config/ # DB 연결 설정 +│ +├── backend-spring/ # Spring Boot 백엔드 (~95개 컨트롤러, ~96개 XML) +│ └── src/main/ +│ ├── java/com/erp/ +│ │ ├── common/ # BaseService (sqlSession 주입) +│ │ ├── config/ # Security, MyBatis, Jackson, Exception +│ │ ├── controller/ # @RestController +│ │ ├── service/ # @Service extends BaseService +│ │ ├── security/ # JWT 인증 +│ │ └── util/ # 유틸리티 +│ └── resources/ +│ ├── application.yml +│ └── mapper/ # MyBatis XML (소문자 camelCase) +│ +├── db/ # 데이터베이스 +│ └── migrations/ # 순차 마이그레이션 SQL +├── docker/ # Docker 설정 (dev/prod/deploy) +├── scripts/ # 개발/배포 스크립트 +├── docs/ # 프로젝트 문서 +├── Dockerfile # 프로덕션 멀티스테이지 빌드 (Spring + Next.js) +└── Jenkinsfile # CI/CD 파이프라인 ``` ## 빠른 시작 @@ -80,23 +148,27 @@ ERP-node/ ### 1. 필수 요구사항 - **Node.js**: 20.10+ +- **Java**: 21 (Spring Boot 백엔드 사용 시) - **PostgreSQL**: 데이터베이스 서버 - **npm**: 10.0+ ### 2. 개발 환경 실행 ```bash -# 백엔드 (nodemon으로 자동 재시작) +# 프론트엔드 (Turbopack, port 9771) +cd frontend && npm install && npm run dev + +# 백엔드 — Node.js (port 8080) cd backend-node && npm install && npm run dev -# 프론트엔드 (Turbopack) -cd frontend && npm install && npm run dev +# 백엔드 — Spring Boot (port 8081) +cd backend-spring && ./gradlew bootRun ``` ### 3. Docker 환경 실행 ```bash -# 백엔드 + 프론트엔드 (개발) +# 개발 (백엔드 + 프론트엔드) docker-compose -f docker-compose.backend.win.yml up -d docker-compose -f docker-compose.frontend.win.yml up -d @@ -106,38 +178,65 @@ docker-compose -f docker/deploy/docker-compose.yml up -d ### 4. 서비스 접속 -| 서비스 | URL | 설명 | -| -------------- | --------------------- | ------------------------------ | -| **프론트엔드** | http://localhost:9771 | Next.js 사용자 인터페이스 | -| **백엔드 API** | http://localhost:8080 | Express REST API | +| 서비스 | URL | 설명 | +|--------|-----|------| +| **프론트엔드** | http://localhost:9771 | Next.js UI | +| **백엔드 (Node)** | http://localhost:8080 | Express REST API | +| **백엔드 (Spring)** | http://localhost:8081 | Spring Boot REST API | ## 주요 기능 -### 1. 사용자 및 권한 관리 -- 사용자 계정 관리 (CRUD) +### 1. 화면 디자이너 (Screen Designer) +- 드래그앤드롭 기반 업무 화면 구성 +- 그리드 레이아웃 빌더 + 레이어 관리 +- V2 동적 컴포넌트 시스템 (Input, Select, Date, List, Hierarchy, Media 등) +- 화면 복사 / 메뉴 할당 / 임베딩 + +### 2. 데이터플로우 디자이너 (Data Flow) +- XY Flow 기반 비주얼 데이터 흐름 설계 +- 테이블 노드 + 관계 에지 + 외부 호출 노드 +- 조건부 로직 / 연결 설정 + +### 3. 사용자 및 권한 관리 - 역할 기반 접근 제어 (RBAC) -- 부서/조직 관리 -- 멀티테넌시 (회사별 데이터 격리) +- 부서/조직 계층 관리 +- 멀티테넌시 (company_code 기반 데이터 격리) -### 2. 메뉴 및 화면 관리 -- 동적 메뉴 구성 -- 화면 디자이너 (드래그앤드롭) -- V2 컴포넌트 시스템 +### 4. 전자결재 (Approval) +- 결재 요청/승인/반려 워크플로우 +- 글로벌 리스너 기반 실시간 알림 -### 3. 플로우(워크플로우) 관리 -- 비즈니스 프로세스 정의 -- 데이터 흐름 관리 -- 감사 로그 - -### 4. 제품/BOM 관리 +### 5. 제품/BOM 관리 - BOM 구성 및 버전 관리 -- 제품 정보 관리 +- 제품 카테고리 트리 -### 5. 기타 -- 파일/문서 관리 -- 메일 연동 -- 외부 DB 연결 +### 6. Digital Twin / 3D 시각화 +- React Three Fiber 기반 3D 뷰 +- Yard Layout 시각화 +- Digital Twin 템플릿 관리 + +### 7. 기타 기능 +- 메일 연동 (IMAP 수신 + SMTP 발신) +- 바코드/라벨 생성 (bwip-js) +- 리포트 / 세금계산서 +- 다국어 지원 - 번호 채번 규칙 +- 배치 스케줄링 + 외부 DB 연동 +- 파일/문서 관리 (DOCX 생성) + +## 디자인 시스템 — v5 Cosmic Glassmorphism + +INVION v5는 **코스믹 글래스모피즘** 디자인 언어를 사용합니다. + +- **코스믹 배경**: 별/파티클/성운 애니메이션 (`CosmicBackground.tsx`) +- **글래스 UI**: `backdrop-filter: blur()` 기반 반투명 패널 +- **CSS 변수**: `--v5-` prefix로 shadcn/Tailwind 변수와 충돌 방지 +- **다크/라이트 테마**: 크로스페이드 전환 애니메이션 +- **모션 디테일**: 모든 전환/호버/진입에 애니메이션 적용 + +주요 스타일 파일: +- `frontend/styles/v5-layout.css` — v5 전체 CSS +- `frontend/app/(auth)/login/login.css` — 로그인 전용 ## 환경 변수 @@ -149,6 +248,9 @@ JWT_EXPIRES_IN=24h PORT=8080 CORS_ORIGIN=http://localhost:9771 +# backend-spring/src/main/resources/application.yml +# spring.datasource.url, spring.datasource.username, etc. + # frontend/.env.local NEXT_PUBLIC_API_URL=http://localhost:8080/api ``` @@ -158,8 +260,8 @@ NEXT_PUBLIC_API_URL=http://localhost:8080/api ### 프로덕션 빌드 ```bash -# 멀티스테이지 Docker 빌드 (백엔드 + 프론트엔드) -docker build -t wace-solution . +# 멀티스테이지 Docker 빌드 (Spring Boot + Next.js) +docker build -t invion . ``` ### CI/CD @@ -168,8 +270,23 @@ Jenkins 파이프라인 (`Jenkinsfile`)으로 자동 빌드 및 배포가 설정 ## 코드 컨벤션 +### 네이밍 규칙 + +| 레이어 | 케이스 | 예시 | +|--------|--------|------| +| DB 컬럼/테이블 | UPPER_SNAKE_CASE | `SCREEN_ID`, `COMPANY_CODE` | +| MyBatis XML SQL | UPPER_SNAKE_CASE | `SELECT SCREEN_ID FROM SCREEN_DEFINITIONS` | +| MyBatis #{파라미터} | camelCase | `#{screenId}`, `#{companyCode}` | +| API 응답 키 (Map) | lower_snake_case | `screen_id`, `company_code` | +| 프론트 타입 필드 | lower_snake_case | `screen_id`, `menu_name_kor` | +| JS 변수/함수/props | camelCase | `screenId`, `handleClick` | + +### 공통 규칙 + - **TypeScript**: 엄격한 타입 정의 사용 - **ESLint + Prettier**: 일관된 코드 스타일 - **shadcn/ui**: UI 컴포넌트 표준 - **API 클라이언트**: `frontend/lib/api/` 전용 클라이언트 사용 (fetch 직접 사용 금지) - **멀티테넌시**: 모든 쿼리에 company_code 필터링 필수 +- **Map 기반**: Spring Boot 백엔드는 DTO 대신 `Map` 사용 +- **snake→camel 변환 금지**: `toCamelCaseKeys()` 등 변환 함수 사용 불가 diff --git a/frontend/components/layout/AppLayout.tsx b/frontend/components/layout/AppLayout.tsx index b124e24b..2a93ea9c 100644 --- a/frontend/components/layout/AppLayout.tsx +++ b/frontend/components/layout/AppLayout.tsx @@ -240,6 +240,7 @@ function AppLayoutInner({ children }: AppLayoutProps) { const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [tabsCollapsed, setTabsCollapsed] = useState(false); const [flyoutMenu, setFlyoutMenu] = useState<{ menu: any; rect: DOMRect } | null>(null); + const [modeTransition, setModeTransition] = useState<"idle" | "out" | "in">("idle"); const [expandedMenus, setExpandedMenus] = useState>(new Set()); const [isMobile, setIsMobile] = useState(false); const [showCompanySwitcher, setShowCompanySwitcher] = useState(false); @@ -465,31 +466,21 @@ function AppLayoutInner({ children }: AppLayoutProps) { }; const handleModeSwitch = useCallback(() => { - const modeFade = document.getElementById("v5-mode-fade"); - const sidebar = document.querySelector(".v5-side") as HTMLElement; - const tabBar = document.querySelector(".v5-tabs") as HTMLElement; - - // Phase 1: overlay flash + slide out - modeFade?.classList.add("in"); - sidebar?.classList.add("slide-out"); - tabBar?.classList.add("fade-out"); + if (modeTransition !== "idle") return; + setModeTransition("out"); + // Phase 1: slide out sidebar + fade tabs (300ms) setTimeout(() => { - // Phase 2: swap mode + // Phase 2: swap mode — React re-renders with new menus/tabs setTabMode(isAdminMode ? "user" : "admin"); - sidebar?.classList.remove("slide-out"); - sidebar?.classList.add("slide-in"); - tabBar?.classList.remove("fade-out"); - tabBar?.classList.add("fade-in"); + setModeTransition("in"); - // Phase 3: cleanup - setTimeout(() => { - modeFade?.classList.remove("in"); - sidebar?.classList.remove("slide-in"); - tabBar?.classList.remove("fade-in"); - }, 400); + // Phase 3: slide in completes, cleanup + requestAnimationFrame(() => { + setTimeout(() => setModeTransition("idle"), 450); + }); }, 300); - }, [isAdminMode, setTabMode]); + }, [isAdminMode, setTabMode, modeTransition]); const handleLogout = async () => { try { @@ -712,8 +703,7 @@ function AppLayoutInner({ children }: AppLayoutProps) { {/* Theme fade overlay */}
- {/* Mode transition overlay */} -
+ {/* Mode transition — no overlay, sidebar/tabs animate via modeTransition state */} {/* V5 Shell */}
@@ -812,7 +802,7 @@ function AppLayoutInner({ children }: AppLayoutProps) { {/* ===== Tab Bar ===== */} - setTabsCollapsed(!tabsCollapsed)} /> + setTabsCollapsed(!tabsCollapsed)} modeTransition={modeTransition} /> {/* Mobile overlay */} {sidebarOpen && isMobile && ( @@ -822,7 +812,7 @@ function AppLayoutInner({ children }: AppLayoutProps) { {/* ===== Body (sidebar + content) ===== */}
{/* Sidebar */} -