]]
This commit is contained in:
@@ -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<String, Object>` 기반 — 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<String, Object>` 사용
|
||||
- **snake→camel 변환 금지**: `toCamelCaseKeys()` 등 변환 함수 사용 불가
|
||||
|
||||
@@ -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<Set<string>>(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 */}
|
||||
<div className="v5-theme-fade" id="v5-theme-fade" />
|
||||
|
||||
{/* Mode transition overlay */}
|
||||
<div className="v5-mode-fade" id="v5-mode-fade" />
|
||||
{/* Mode transition — no overlay, sidebar/tabs animate via modeTransition state */}
|
||||
|
||||
{/* V5 Shell */}
|
||||
<div className={`v5-shell ${isAdminMode ? "v5-admin-mode" : ""}`}>
|
||||
@@ -812,7 +802,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
</header>
|
||||
|
||||
{/* ===== Tab Bar ===== */}
|
||||
<TabBar collapsed={tabsCollapsed} onToggleCollapse={() => setTabsCollapsed(!tabsCollapsed)} />
|
||||
<TabBar collapsed={tabsCollapsed} onToggleCollapse={() => setTabsCollapsed(!tabsCollapsed)} modeTransition={modeTransition} />
|
||||
|
||||
{/* Mobile overlay */}
|
||||
{sidebarOpen && isMobile && (
|
||||
@@ -822,7 +812,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
{/* ===== Body (sidebar + content) ===== */}
|
||||
<div className="v5-body">
|
||||
{/* Sidebar */}
|
||||
<aside className={`v5-side v5-side-anim ${isAdminMode ? "v5-admin-side" : ""} ${sidebarCollapsed ? "collapsed" : ""} ${isMobile ? (sidebarOpen ? "mobile-open" : "") : ""}`}
|
||||
<aside className={`v5-side v5-side-anim ${isAdminMode ? "v5-admin-side" : ""} ${sidebarCollapsed ? "collapsed" : ""} ${isMobile ? (sidebarOpen ? "mobile-open" : "") : ""} ${modeTransition === "out" ? "slide-out" : modeTransition === "in" ? "slide-in" : ""}`}
|
||||
style={isMobile ? { position: "fixed", left: 0, top: 0, bottom: 0, zIndex: 30, paddingTop: "60px", width: "260px", transform: sidebarOpen ? "none" : "translateX(-100%)" } : undefined}
|
||||
>
|
||||
{/* SUPER_ADMIN company info */}
|
||||
@@ -839,8 +829,8 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
||||
)}
|
||||
|
||||
{/* Menu items */}
|
||||
<div className="flex-1 overflow-y-auto" style={{ padding: "0" }}>
|
||||
<nav style={{ display: "flex", flexDirection: "column", gap: "1px" }}>
|
||||
<div className={`flex-1 ${sidebarCollapsed ? "overflow-visible" : "overflow-y-auto"}`} style={{ padding: "0" }}>
|
||||
<nav style={{ display: "flex", flexDirection: "column", gap: "1px", overflow: sidebarCollapsed ? "visible" : undefined }}>
|
||||
{loading ? (
|
||||
<div className="animate-pulse space-y-2 p-3">
|
||||
{[...Array(5)].map((_, i) => (
|
||||
|
||||
@@ -44,9 +44,10 @@ interface DropGhost {
|
||||
interface TabBarProps {
|
||||
collapsed?: boolean;
|
||||
onToggleCollapse?: () => void;
|
||||
modeTransition?: "idle" | "out" | "in";
|
||||
}
|
||||
|
||||
export function TabBar({ collapsed = false, onToggleCollapse }: TabBarProps) {
|
||||
export function TabBar({ collapsed = false, onToggleCollapse, modeTransition = "idle" }: TabBarProps) {
|
||||
const tabs = useTabStore(selectTabs);
|
||||
const activeTabId = useTabStore(selectActiveTabId);
|
||||
const {
|
||||
@@ -542,7 +543,7 @@ export function TabBar({ collapsed = false, onToggleCollapse }: TabBarProps) {
|
||||
<>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`v5-tabs relative flex shrink-0 items-stretch gap-[1px] overflow-hidden ${collapsed ? "collapsed" : ""}`}
|
||||
className={`v5-tabs relative flex shrink-0 items-stretch gap-[1px] overflow-hidden ${collapsed ? "collapsed" : ""} ${modeTransition === "out" ? "fade-out" : modeTransition === "in" ? "fade-in" : ""}`}
|
||||
onDragOver={handleBarDragOver}
|
||||
onDragLeave={handleBarDragLeave}
|
||||
onDrop={handleBarDrop}
|
||||
|
||||
@@ -393,12 +393,12 @@ html:not(.dark) .v5-cosmos .neb-4{width:600px;height:600px;top:-10%;right:20%;bo
|
||||
transition:opacity .5s cubic-bezier(.4,0,.2,1);}
|
||||
.v5-mode-fade.in{opacity:1;}
|
||||
|
||||
.v5-side.slide-out{transform:translateX(-100%);opacity:0;}
|
||||
.v5-side.slide-in{animation:v5-sideSlideIn .4s cubic-bezier(.16,1,.3,1) both;}
|
||||
@keyframes v5-sideSlideIn{from{transform:translateX(-30px);opacity:0}to{transform:none;opacity:1}}
|
||||
.v5-tabs.fade-out{opacity:0;}
|
||||
.v5-tabs.fade-in{animation:v5-tabsFadeIn .3s ease-out both;}
|
||||
@keyframes v5-tabsFadeIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
|
||||
.v5-side.slide-out{transform:translateX(-20px);opacity:0;transition:transform .3s cubic-bezier(.4,0,1,1),opacity .2s;}
|
||||
.v5-side.slide-in{animation:v5-sideSlideIn .35s cubic-bezier(.16,1,.3,1) both;}
|
||||
@keyframes v5-sideSlideIn{from{transform:translateX(-20px);opacity:0}to{transform:none;opacity:1}}
|
||||
.v5-tabs.fade-out{opacity:0;transition:opacity .2s;}
|
||||
.v5-tabs.fade-in{animation:v5-tabsFadeIn .3s cubic-bezier(.16,1,.3,1) both;}
|
||||
@keyframes v5-tabsFadeIn{from{opacity:0}to{opacity:1}}
|
||||
|
||||
/* ===== THEME TRANSITION ===== */
|
||||
.v5-theme-fade{position:fixed;inset:0;z-index:9999;pointer-events:none;opacity:0;
|
||||
|
||||
Reference in New Issue
Block a user