773 lines
20 KiB
Markdown
773 lines
20 KiB
Markdown
|
|
# sam_prototype → react 마이그레이션 실행 계획
|
||
|
|
|
||
|
|
> 작성일: 2025-10-17 (목)
|
||
|
|
> 예상 소요 시간: 4시간
|
||
|
|
> 완료 목표: 오늘 오후 (로컬 및 개발 서버 실행 완료)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📊 현황 분석 요약
|
||
|
|
|
||
|
|
### sam_prototype (원본)
|
||
|
|
- **위치**: `/Users/hskwon/Works/@KD_SAM/SAM/sam_prototype/`
|
||
|
|
- **기술 스택**: React 18.3.1, Vite 6.3.5
|
||
|
|
- **UI 라이브러리**: Radix UI 23개 패키지 (shadcn/ui 완벽 구현)
|
||
|
|
- **컴포넌트**: 90개 (UI 40개 + 비즈니스 50개)
|
||
|
|
- **디자인 시스템**: Clean Glass Design + 3가지 테마 (light/dark/senior)
|
||
|
|
- **폰트**: Pretendard 한글 폰트
|
||
|
|
- **상태 관리**: 없음 (로컬 useState만)
|
||
|
|
- **라우팅**: 없음 (조건부 렌더링)
|
||
|
|
|
||
|
|
### react (타겟)
|
||
|
|
- **위치**: `/Users/hskwon/Works/@KD_SAM/SAM/react/`
|
||
|
|
- **기술 스택**: React 19.1.1, Vite 7.x, TypeScript 5.x
|
||
|
|
- **준비된 것**: Zustand, React Query v5, React Router v7, Tailwind CSS 4.x
|
||
|
|
- **현재 상태**: 거의 빈 프로젝트 (DemoPage만 존재)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ 핵심 의사결정 (완료됨)
|
||
|
|
|
||
|
|
### 1. React 버전: 19.1.1
|
||
|
|
- **근거**: Radix UI 공식 React 19 지원 확인 (Context7 MCP로 검증)
|
||
|
|
- **리스크**: 낮음 (모든 라이브러리 호환 확인)
|
||
|
|
|
||
|
|
### 2. 컴포넌트 이전 방식: 하이브리드 (Copy + Refactor)
|
||
|
|
- UI 컴포넌트 40개: 복사 + TypeScript 타입만 추가
|
||
|
|
- 비즈니스 컴포넌트 50개: 복사 + 구조 개선
|
||
|
|
|
||
|
|
### 3. 테마 시스템: Tailwind CSS 4.x Native
|
||
|
|
- `next-themes` 제거 (Vite에서 불필요)
|
||
|
|
- CSS 변수 + Zustand themeStore 사용
|
||
|
|
|
||
|
|
### 4. 상태 관리: Zustand (4개) + React Query
|
||
|
|
- themeStore: light/dark/senior
|
||
|
|
- authStore: 인증/사용자
|
||
|
|
- menuStore: 메뉴/사이드바
|
||
|
|
- demoStore: 데모 모드
|
||
|
|
|
||
|
|
### 5. API 통합: 하이브리드 (Mock → 점진적 통합)
|
||
|
|
- 초기에는 Mock 데이터로 작동
|
||
|
|
- 나중에 SAM API 연동
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🚀 5단계 실행 계획 (총 4시간)
|
||
|
|
|
||
|
|
### Phase 1: 환경 구축 (30분)
|
||
|
|
|
||
|
|
#### 1.1 의존성 설치
|
||
|
|
```bash
|
||
|
|
cd /Users/hskwon/Works/@KD_SAM/SAM/react
|
||
|
|
|
||
|
|
# React 19 (이미 설치되어 있음 - 확인만)
|
||
|
|
# npm install react@19.1.1 react-dom@19.1.1
|
||
|
|
|
||
|
|
# Radix UI 전체 패키지 설치 (23개)
|
||
|
|
npm install \
|
||
|
|
@radix-ui/react-accordion@^1.2.3 \
|
||
|
|
@radix-ui/react-alert-dialog@^1.1.6 \
|
||
|
|
@radix-ui/react-aspect-ratio@^1.1.2 \
|
||
|
|
@radix-ui/react-avatar@^1.1.3 \
|
||
|
|
@radix-ui/react-checkbox@^1.1.4 \
|
||
|
|
@radix-ui/react-collapsible@^1.1.3 \
|
||
|
|
@radix-ui/react-context-menu@^2.2.6 \
|
||
|
|
@radix-ui/react-dialog@^1.1.6 \
|
||
|
|
@radix-ui/react-dropdown-menu@^2.1.6 \
|
||
|
|
@radix-ui/react-hover-card@^1.1.6 \
|
||
|
|
@radix-ui/react-label@^2.1.2 \
|
||
|
|
@radix-ui/react-menubar@^1.1.6 \
|
||
|
|
@radix-ui/react-navigation-menu@^1.2.5 \
|
||
|
|
@radix-ui/react-popover@^1.1.6 \
|
||
|
|
@radix-ui/react-progress@^1.1.2 \
|
||
|
|
@radix-ui/react-radio-group@^1.2.3 \
|
||
|
|
@radix-ui/react-scroll-area@^1.2.3 \
|
||
|
|
@radix-ui/react-select@^2.1.6 \
|
||
|
|
@radix-ui/react-separator@^1.1.2 \
|
||
|
|
@radix-ui/react-slider@^1.2.3 \
|
||
|
|
@radix-ui/react-slot@^1.1.2 \
|
||
|
|
@radix-ui/react-switch@^1.1.3 \
|
||
|
|
@radix-ui/react-tabs@^1.1.3 \
|
||
|
|
@radix-ui/react-toggle@^1.1.2 \
|
||
|
|
@radix-ui/react-toggle-group@^1.1.2 \
|
||
|
|
@radix-ui/react-tooltip@^1.1.8
|
||
|
|
|
||
|
|
# 기타 필수 패키지
|
||
|
|
npm install \
|
||
|
|
class-variance-authority \
|
||
|
|
cmdk@^1.1.1 \
|
||
|
|
embla-carousel-react@^8.6.0 \
|
||
|
|
input-otp@^1.4.2 \
|
||
|
|
react-day-picker@^8.10.1 \
|
||
|
|
react-resizable-panels@^2.1.7 \
|
||
|
|
recharts@^2.15.2 \
|
||
|
|
sonner@^2.0.3 \
|
||
|
|
vaul@^1.1.2
|
||
|
|
|
||
|
|
# react-hook-form 버전 업그레이드 (현재 ^7.65.0 → ^7.55.0 이상)
|
||
|
|
# 이미 설치되어 있으므로 스킵
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 1.2 디렉토리 구조 생성
|
||
|
|
```bash
|
||
|
|
mkdir -p src/components/ui
|
||
|
|
mkdir -p src/components/business/{dashboard,production,sales,material,quality,master,system,hr,accounting,common}
|
||
|
|
mkdir -p src/components/landing
|
||
|
|
mkdir -p src/components/shared
|
||
|
|
mkdir -p src/app/layouts
|
||
|
|
mkdir -p src/styles/themes
|
||
|
|
mkdir -p public/fonts/pretendard
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 1.3 설정 파일 작성
|
||
|
|
|
||
|
|
**tailwind.config.js 확장:**
|
||
|
|
```javascript
|
||
|
|
export default {
|
||
|
|
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||
|
|
theme: {
|
||
|
|
extend: {
|
||
|
|
colors: {
|
||
|
|
border: 'var(--color-border)',
|
||
|
|
input: 'var(--color-input)',
|
||
|
|
ring: 'var(--color-ring)',
|
||
|
|
background: 'var(--color-background)',
|
||
|
|
foreground: 'var(--color-foreground)',
|
||
|
|
primary: {
|
||
|
|
DEFAULT: 'var(--color-primary)',
|
||
|
|
foreground: 'var(--color-primary-foreground)',
|
||
|
|
},
|
||
|
|
secondary: {
|
||
|
|
DEFAULT: 'var(--color-secondary)',
|
||
|
|
foreground: 'var(--color-secondary-foreground)',
|
||
|
|
},
|
||
|
|
muted: {
|
||
|
|
DEFAULT: 'var(--color-muted)',
|
||
|
|
foreground: 'var(--color-muted-foreground)',
|
||
|
|
},
|
||
|
|
accent: {
|
||
|
|
DEFAULT: 'var(--color-accent)',
|
||
|
|
foreground: 'var(--color-accent-foreground)',
|
||
|
|
},
|
||
|
|
destructive: {
|
||
|
|
DEFAULT: 'var(--color-destructive)',
|
||
|
|
foreground: 'var(--color-destructive-foreground)',
|
||
|
|
},
|
||
|
|
card: {
|
||
|
|
DEFAULT: 'var(--color-card)',
|
||
|
|
foreground: 'var(--color-card-foreground)',
|
||
|
|
},
|
||
|
|
popover: {
|
||
|
|
DEFAULT: 'var(--color-popover)',
|
||
|
|
foreground: 'var(--color-popover-foreground)',
|
||
|
|
},
|
||
|
|
'sidebar': {
|
||
|
|
DEFAULT: 'var(--color-sidebar)',
|
||
|
|
foreground: 'var(--color-sidebar-foreground)',
|
||
|
|
primary: 'var(--color-sidebar-primary)',
|
||
|
|
'primary-foreground': 'var(--color-sidebar-primary-foreground)',
|
||
|
|
accent: 'var(--color-sidebar-accent)',
|
||
|
|
'accent-foreground': 'var(--color-sidebar-accent-foreground)',
|
||
|
|
border: 'var(--color-sidebar-border)',
|
||
|
|
ring: 'var(--color-sidebar-ring)',
|
||
|
|
},
|
||
|
|
chart: {
|
||
|
|
1: 'var(--color-chart-1)',
|
||
|
|
2: 'var(--color-chart-2)',
|
||
|
|
3: 'var(--color-chart-3)',
|
||
|
|
4: 'var(--color-chart-4)',
|
||
|
|
5: 'var(--color-chart-5)',
|
||
|
|
},
|
||
|
|
},
|
||
|
|
borderRadius: {
|
||
|
|
lg: 'var(--radius)',
|
||
|
|
md: 'calc(var(--radius) - 2px)',
|
||
|
|
sm: 'calc(var(--radius) - 4px)',
|
||
|
|
},
|
||
|
|
fontFamily: {
|
||
|
|
sans: ['Pretendard Variable', 'ui-sans-serif', 'system-ui', 'sans-serif'],
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
plugins: [
|
||
|
|
require('@tailwindcss/forms'),
|
||
|
|
require('@tailwindcss/typography'),
|
||
|
|
],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**사용자 컨펌 포인트 #1**: `npm install` 및 디렉토리 생성 완료 확인
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Phase 2: 디자인 시스템 이전 (1시간)
|
||
|
|
|
||
|
|
#### 2.1 Pretendard 폰트 설정
|
||
|
|
```bash
|
||
|
|
# public/fonts/pretendard/ 에 폰트 파일이 있는지 확인
|
||
|
|
# 없으면 sam_prototype에서 복사하거나 CDN 사용
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 2.2 Clean Glass CSS 이전
|
||
|
|
|
||
|
|
**src/index.css 전체 교체:**
|
||
|
|
- sam_prototype/src/styles/globals.css 내용 복사
|
||
|
|
- Tailwind v4 `@theme` 문법으로 변환
|
||
|
|
- 3가지 테마 CSS 변수 정의
|
||
|
|
|
||
|
|
**주요 내용:**
|
||
|
|
```css
|
||
|
|
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css');
|
||
|
|
@import "tailwindcss";
|
||
|
|
|
||
|
|
@theme {
|
||
|
|
/* Light Mode 변수 */
|
||
|
|
--color-background: #FAFAFA;
|
||
|
|
--color-foreground: #1A1A1A;
|
||
|
|
--color-primary: #3B82F6;
|
||
|
|
/* ... 모든 색상 변수 */
|
||
|
|
|
||
|
|
--radius: 0.75rem;
|
||
|
|
--clean-blur: blur(8px);
|
||
|
|
/* ... 기타 변수 */
|
||
|
|
}
|
||
|
|
|
||
|
|
.dark {
|
||
|
|
/* Dark Mode 변수 */
|
||
|
|
}
|
||
|
|
|
||
|
|
.senior {
|
||
|
|
/* Senior Mode 변수 */
|
||
|
|
--font-size-base: 1.125rem;
|
||
|
|
--button-min-height: 3.5rem;
|
||
|
|
/* ... 고령자 접근성 변수 */
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Clean Glass 스타일 */
|
||
|
|
.clean-glass {
|
||
|
|
backdrop-filter: var(--clean-blur);
|
||
|
|
background: rgba(255, 255, 255, 0.95);
|
||
|
|
border: 1px solid var(--border);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ... 기타 Clean Design 스타일 */
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 2.3 Zustand themeStore 구현
|
||
|
|
|
||
|
|
**src/stores/themeStore.ts:**
|
||
|
|
```typescript
|
||
|
|
import { create } from 'zustand';
|
||
|
|
import { persist } from 'zustand/middleware';
|
||
|
|
|
||
|
|
interface ThemeState {
|
||
|
|
theme: 'light' | 'dark' | 'senior';
|
||
|
|
setTheme: (theme: 'light' | 'dark' | 'senior') => void;
|
||
|
|
toggleTheme: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const useThemeStore = create<ThemeState>()(
|
||
|
|
persist(
|
||
|
|
(set, get) => ({
|
||
|
|
theme: 'light',
|
||
|
|
|
||
|
|
setTheme: (theme) => {
|
||
|
|
// DOM 클래스 업데이트
|
||
|
|
document.documentElement.className = theme === 'light' ? '' : theme;
|
||
|
|
set({ theme });
|
||
|
|
},
|
||
|
|
|
||
|
|
toggleTheme: () => {
|
||
|
|
const themes = ['light', 'dark', 'senior'] as const;
|
||
|
|
const currentIndex = themes.indexOf(get().theme);
|
||
|
|
const nextTheme = themes[(currentIndex + 1) % 3];
|
||
|
|
get().setTheme(nextTheme);
|
||
|
|
},
|
||
|
|
}),
|
||
|
|
{
|
||
|
|
name: 'sam-theme',
|
||
|
|
onRehydrateStorage: () => (state) => {
|
||
|
|
// 초기 로드 시 테마 적용
|
||
|
|
if (state?.theme) {
|
||
|
|
document.documentElement.className = state.theme === 'light' ? '' : state.theme;
|
||
|
|
}
|
||
|
|
},
|
||
|
|
}
|
||
|
|
)
|
||
|
|
);
|
||
|
|
```
|
||
|
|
|
||
|
|
**src/hooks/useTheme.ts:**
|
||
|
|
```typescript
|
||
|
|
import { useEffect } from 'react';
|
||
|
|
import { useThemeStore } from '@/stores/themeStore';
|
||
|
|
|
||
|
|
export const useTheme = () => {
|
||
|
|
const { theme, setTheme } = useThemeStore();
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
// 초기 로드 시 테마 적용
|
||
|
|
document.documentElement.className = theme === 'light' ? '' : theme;
|
||
|
|
}, [theme]);
|
||
|
|
|
||
|
|
return { theme, setTheme };
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
**사용자 컨펌 포인트 #2**: CSS 및 테마 시스템 작동 확인 (light/dark/senior 전환)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Phase 3: 컴포넌트 대량 이전 (1.5시간)
|
||
|
|
|
||
|
|
#### 3.1 UI 컴포넌트 40개 이전
|
||
|
|
|
||
|
|
**복사할 파일 목록:**
|
||
|
|
```
|
||
|
|
sam_prototype/src/components/ui/*.tsx → react/src/components/ui/
|
||
|
|
|
||
|
|
accordion.tsx
|
||
|
|
alert.tsx
|
||
|
|
alert-dialog.tsx
|
||
|
|
aspect-ratio.tsx
|
||
|
|
avatar.tsx
|
||
|
|
badge.tsx
|
||
|
|
breadcrumb.tsx
|
||
|
|
button.tsx
|
||
|
|
calendar.tsx
|
||
|
|
card.tsx
|
||
|
|
carousel.tsx
|
||
|
|
chart.tsx
|
||
|
|
checkbox.tsx
|
||
|
|
collapsible.tsx
|
||
|
|
command.tsx
|
||
|
|
context-menu.tsx
|
||
|
|
dialog.tsx
|
||
|
|
drawer.tsx
|
||
|
|
dropdown-menu.tsx
|
||
|
|
form.tsx
|
||
|
|
hover-card.tsx
|
||
|
|
input.tsx
|
||
|
|
input-otp.tsx
|
||
|
|
label.tsx
|
||
|
|
menubar.tsx
|
||
|
|
navigation-menu.tsx
|
||
|
|
pagination.tsx
|
||
|
|
popover.tsx
|
||
|
|
progress.tsx
|
||
|
|
radio-group.tsx
|
||
|
|
resizable.tsx
|
||
|
|
scroll-area.tsx
|
||
|
|
select.tsx
|
||
|
|
separator.tsx
|
||
|
|
sheet.tsx
|
||
|
|
sidebar.tsx
|
||
|
|
skeleton.tsx
|
||
|
|
slider.tsx
|
||
|
|
sonner.tsx
|
||
|
|
switch.tsx
|
||
|
|
table.tsx
|
||
|
|
tabs.tsx
|
||
|
|
textarea.tsx
|
||
|
|
toggle.tsx
|
||
|
|
toggle-group.tsx
|
||
|
|
tooltip.tsx
|
||
|
|
```
|
||
|
|
|
||
|
|
**자동 처리 작업:**
|
||
|
|
1. 파일 복사
|
||
|
|
2. Import 경로 수정 (`../lib/utils` → `@/lib/utils`)
|
||
|
|
3. TypeScript 타입 추가 (Props interface 정의)
|
||
|
|
|
||
|
|
#### 3.2 비즈니스 컴포넌트 50개 이전
|
||
|
|
|
||
|
|
**복사할 파일 목록 (도메인별):**
|
||
|
|
|
||
|
|
**dashboard/**
|
||
|
|
- Dashboard.tsx
|
||
|
|
- SalesLeadDashboard.tsx
|
||
|
|
- SystemAdminDashboard.tsx
|
||
|
|
|
||
|
|
**landing/**
|
||
|
|
- LandingPage.tsx
|
||
|
|
- LoginPage.tsx
|
||
|
|
- SignupPage.tsx
|
||
|
|
- DemoRequestPage.tsx
|
||
|
|
|
||
|
|
**production/**
|
||
|
|
- ProductionManagement.tsx
|
||
|
|
- WorkerPerformance.tsx
|
||
|
|
|
||
|
|
**sales/**
|
||
|
|
- SalesManagement.tsx
|
||
|
|
- SalesManagement-clean.tsx
|
||
|
|
- QuoteCreation.tsx
|
||
|
|
- QuoteSimulation.tsx
|
||
|
|
|
||
|
|
**material/**
|
||
|
|
- MaterialManagement.tsx
|
||
|
|
- LotManagement.tsx
|
||
|
|
- ReceivingWrite.tsx
|
||
|
|
- ShippingManagement.tsx
|
||
|
|
|
||
|
|
**quality/**
|
||
|
|
- QualityManagement.tsx
|
||
|
|
|
||
|
|
**master/**
|
||
|
|
- MasterData.tsx
|
||
|
|
- BOMManagement.tsx
|
||
|
|
- ItemManagement.tsx
|
||
|
|
- ProductManagement.tsx
|
||
|
|
- PricingManagement.tsx
|
||
|
|
- CodeManagement.tsx
|
||
|
|
|
||
|
|
**system/**
|
||
|
|
- SystemManagement.tsx
|
||
|
|
- UserManagement.tsx
|
||
|
|
- MenuCustomization.tsx
|
||
|
|
- MenuCustomizationGuide.tsx
|
||
|
|
|
||
|
|
**hr/**
|
||
|
|
- HRManagement.tsx
|
||
|
|
|
||
|
|
**accounting/**
|
||
|
|
- AccountingManagement.tsx
|
||
|
|
- ApprovalManagement.tsx
|
||
|
|
|
||
|
|
**common/**
|
||
|
|
- Board.tsx
|
||
|
|
- Reports.tsx
|
||
|
|
- EquipmentManagement.tsx
|
||
|
|
- OrderManagement.tsx
|
||
|
|
- DrawingCanvas.tsx
|
||
|
|
- ContactModal.tsx
|
||
|
|
|
||
|
|
**figma/**
|
||
|
|
- ImageWithFallback.tsx
|
||
|
|
|
||
|
|
**자동 처리 작업:**
|
||
|
|
1. 파일 복사 및 도메인별 폴더 배치
|
||
|
|
2. Import 경로 수정 (`./components/ui/` → `@/components/ui/`)
|
||
|
|
3. TypeScript Props 타입 추가
|
||
|
|
4. useState → 유지 (나중에 Zustand 전환)
|
||
|
|
|
||
|
|
**사용자 컨펌 포인트 #3**: 주요 컴포넌트 렌더링 확인 (Dashboard, LoginPage 등)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Phase 4: 상태 관리 & 라우팅 (1시간)
|
||
|
|
|
||
|
|
#### 4.1 Zustand Stores 구현
|
||
|
|
|
||
|
|
**src/stores/menuStore.ts:**
|
||
|
|
```typescript
|
||
|
|
import { create } from 'zustand';
|
||
|
|
|
||
|
|
interface MenuState {
|
||
|
|
activeMenu: string;
|
||
|
|
expandedMenus: string[];
|
||
|
|
isSidebarCollapsed: boolean;
|
||
|
|
isMobileSidebarOpen: boolean;
|
||
|
|
|
||
|
|
setActiveMenu: (menuId: string) => void;
|
||
|
|
toggleMenu: (menuId: string) => void;
|
||
|
|
toggleSidebar: () => void;
|
||
|
|
closeMobileSidebar: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const useMenuStore = create<MenuState>((set) => ({
|
||
|
|
activeMenu: 'dashboard',
|
||
|
|
expandedMenus: [],
|
||
|
|
isSidebarCollapsed: false,
|
||
|
|
isMobileSidebarOpen: false,
|
||
|
|
|
||
|
|
setActiveMenu: (menuId) => set({ activeMenu: menuId }),
|
||
|
|
|
||
|
|
toggleMenu: (menuId) => set((state) => ({
|
||
|
|
expandedMenus: state.expandedMenus.includes(menuId)
|
||
|
|
? state.expandedMenus.filter((id) => id !== menuId)
|
||
|
|
: [...state.expandedMenus, menuId],
|
||
|
|
})),
|
||
|
|
|
||
|
|
toggleSidebar: () => set((state) => ({
|
||
|
|
isSidebarCollapsed: !state.isSidebarCollapsed,
|
||
|
|
})),
|
||
|
|
|
||
|
|
closeMobileSidebar: () => set({ isMobileSidebarOpen: false }),
|
||
|
|
}));
|
||
|
|
```
|
||
|
|
|
||
|
|
**src/stores/demoStore.ts:**
|
||
|
|
```typescript
|
||
|
|
import { create } from 'zustand';
|
||
|
|
|
||
|
|
interface DemoConfig {
|
||
|
|
token: string;
|
||
|
|
expiresAt: Date;
|
||
|
|
userName?: string;
|
||
|
|
companyName?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface DemoState {
|
||
|
|
isDemoMode: boolean;
|
||
|
|
demoConfig: DemoConfig | null;
|
||
|
|
|
||
|
|
activateDemo: (config: DemoConfig) => void;
|
||
|
|
deactivateDemo: () => void;
|
||
|
|
checkDemoExpiry: () => boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const useDemoStore = create<DemoState>((set, get) => ({
|
||
|
|
isDemoMode: false,
|
||
|
|
demoConfig: null,
|
||
|
|
|
||
|
|
activateDemo: (config) => set({
|
||
|
|
isDemoMode: true,
|
||
|
|
demoConfig: config,
|
||
|
|
}),
|
||
|
|
|
||
|
|
deactivateDemo: () => set({
|
||
|
|
isDemoMode: false,
|
||
|
|
demoConfig: null,
|
||
|
|
}),
|
||
|
|
|
||
|
|
checkDemoExpiry: () => {
|
||
|
|
const { demoConfig } = get();
|
||
|
|
if (!demoConfig) return false;
|
||
|
|
|
||
|
|
const isExpired = new Date() > new Date(demoConfig.expiresAt);
|
||
|
|
if (isExpired) {
|
||
|
|
get().deactivateDemo();
|
||
|
|
}
|
||
|
|
return !isExpired;
|
||
|
|
},
|
||
|
|
}));
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 4.2 레이아웃 컴포넌트 작성
|
||
|
|
|
||
|
|
**src/app/layouts/RootLayout.tsx:**
|
||
|
|
```typescript
|
||
|
|
import { Outlet } from 'react-router-dom';
|
||
|
|
import { useTheme } from '@/hooks/useTheme';
|
||
|
|
|
||
|
|
export function RootLayout() {
|
||
|
|
const { theme } = useTheme();
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className={theme}>
|
||
|
|
<Outlet />
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**src/app/layouts/DashboardLayout.tsx:**
|
||
|
|
```typescript
|
||
|
|
import { Outlet } from 'react-router-dom';
|
||
|
|
import { Sidebar } from '@/components/shared/Sidebar';
|
||
|
|
import { useMenuStore } from '@/stores/menuStore';
|
||
|
|
|
||
|
|
export function DashboardLayout() {
|
||
|
|
const { isSidebarCollapsed } = useMenuStore();
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="flex min-h-screen bg-background">
|
||
|
|
<Sidebar />
|
||
|
|
<main className={`flex-1 ${isSidebarCollapsed ? 'ml-20' : 'ml-64'} transition-all`}>
|
||
|
|
<Outlet />
|
||
|
|
</main>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 4.3 React Router 설정
|
||
|
|
|
||
|
|
**src/App.tsx 수정:**
|
||
|
|
```typescript
|
||
|
|
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||
|
|
import { QueryClientProvider } from '@tanstack/react-query';
|
||
|
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||
|
|
import { queryClient } from '@/lib/query-client';
|
||
|
|
import { useAuthStore } from '@/stores/auth';
|
||
|
|
|
||
|
|
// Layouts
|
||
|
|
import { RootLayout } from '@/app/layouts/RootLayout';
|
||
|
|
import { DashboardLayout } from '@/app/layouts/DashboardLayout';
|
||
|
|
import { AuthLayout } from '@/app/layouts/AuthLayout';
|
||
|
|
|
||
|
|
// Pages - Landing
|
||
|
|
import { LandingPage } from '@/components/landing/LandingPage';
|
||
|
|
import { LoginPage } from '@/components/landing/LoginPage';
|
||
|
|
import { SignupPage } from '@/components/landing/SignupPage';
|
||
|
|
import { DemoRequestPage } from '@/components/landing/DemoRequestPage';
|
||
|
|
|
||
|
|
// Pages - Dashboard
|
||
|
|
import { Dashboard } from '@/components/business/dashboard/Dashboard';
|
||
|
|
import { SalesLeadDashboard } from '@/components/business/dashboard/SalesLeadDashboard';
|
||
|
|
|
||
|
|
// Pages - Production
|
||
|
|
import { ProductionManagement } from '@/components/business/production/ProductionManagement';
|
||
|
|
|
||
|
|
// ... 기타 import
|
||
|
|
|
||
|
|
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
||
|
|
const { isAuthenticated } = useAuthStore();
|
||
|
|
return isAuthenticated ? <>{children}</> : <Navigate to="/login" />;
|
||
|
|
}
|
||
|
|
|
||
|
|
function App() {
|
||
|
|
return (
|
||
|
|
<QueryClientProvider client={queryClient}>
|
||
|
|
<BrowserRouter>
|
||
|
|
<Routes>
|
||
|
|
<Route element={<RootLayout />}>
|
||
|
|
{/* Public Routes */}
|
||
|
|
<Route path="/" element={<LandingPage />} />
|
||
|
|
<Route path="/login" element={<LoginPage />} />
|
||
|
|
<Route path="/signup" element={<SignupPage />} />
|
||
|
|
<Route path="/demo-request" element={<DemoRequestPage />} />
|
||
|
|
<Route path="/d/:token" element={<DemoPage />} />
|
||
|
|
|
||
|
|
{/* Protected Routes */}
|
||
|
|
<Route path="/dashboard" element={<ProtectedRoute><DashboardLayout /></ProtectedRoute>}>
|
||
|
|
<Route index element={<Dashboard />} />
|
||
|
|
<Route path="sales-leads" element={<SalesLeadDashboard />} />
|
||
|
|
</Route>
|
||
|
|
|
||
|
|
<Route path="/production" element={<ProtectedRoute><DashboardLayout /></ProtectedRoute>}>
|
||
|
|
<Route index element={<ProductionManagement />} />
|
||
|
|
</Route>
|
||
|
|
|
||
|
|
{/* ... 기타 라우트 */}
|
||
|
|
</Route>
|
||
|
|
</Routes>
|
||
|
|
</BrowserRouter>
|
||
|
|
<ReactQueryDevtools initialIsOpen={false} />
|
||
|
|
</QueryClientProvider>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export default App;
|
||
|
|
```
|
||
|
|
|
||
|
|
**사용자 컨펌 포인트 #4**: 라우팅 작동 확인 (/, /login, /dashboard 등 페이지 전환)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Phase 5: 통합 & 테스트 (30분)
|
||
|
|
|
||
|
|
#### 5.1 빌드 테스트
|
||
|
|
```bash
|
||
|
|
npm run build
|
||
|
|
|
||
|
|
# 예상 결과:
|
||
|
|
# ✓ built in XXXms
|
||
|
|
# dist/index.html
|
||
|
|
# dist/assets/*.css
|
||
|
|
# dist/assets/*.js
|
||
|
|
|
||
|
|
# 번들 크기 확인 (목표: < 500KB gzip)
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 5.2 개발 서버 실행
|
||
|
|
```bash
|
||
|
|
# 로컬
|
||
|
|
npm run dev
|
||
|
|
|
||
|
|
# Docker
|
||
|
|
cd ../docker
|
||
|
|
docker-compose restart react
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 5.3 주요 페이지 확인
|
||
|
|
|
||
|
|
**테스트 체크리스트:**
|
||
|
|
- [ ] http://localhost:5173/ → LandingPage 렌더링
|
||
|
|
- [ ] http://localhost:5173/login → LoginPage 렌더링
|
||
|
|
- [ ] 테마 전환 (light/dark/senior) 작동
|
||
|
|
- [ ] 로그인 후 /dashboard 이동
|
||
|
|
- [ ] Sidebar 접기/펴기 작동
|
||
|
|
- [ ] 주요 비즈니스 컴포넌트 렌더링 (Dashboard, ProductionManagement 등)
|
||
|
|
- [ ] 개발 서버: http://dev.sam.kr 접속
|
||
|
|
|
||
|
|
**사용자 컨펌 포인트 #5**: 최종 통합 테스트 완료 확인
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📊 작업 완료 기준
|
||
|
|
|
||
|
|
### 필수 완료 항목
|
||
|
|
- [x] React 19 + Radix UI 전체 패키지 설치
|
||
|
|
- [x] 디렉토리 구조 생성 완료
|
||
|
|
- [x] Clean Glass CSS 이전 완료
|
||
|
|
- [x] 3가지 테마 작동 확인
|
||
|
|
- [x] UI 컴포넌트 40개 이전 완료
|
||
|
|
- [x] 비즈니스 컴포넌트 50개 이전 완료
|
||
|
|
- [x] 4개 Zustand stores 구현 완료
|
||
|
|
- [x] React Router 설정 완료
|
||
|
|
- [x] 레이아웃 컴포넌트 작성 완료
|
||
|
|
- [x] `npm run build` 성공
|
||
|
|
- [x] 로컬 개발 서버 실행 성공
|
||
|
|
- [x] dev.sam.kr 접속 성공
|
||
|
|
|
||
|
|
### 성공 지표
|
||
|
|
- TypeScript 타입 에러 없음
|
||
|
|
- 빌드 성공
|
||
|
|
- 주요 페이지 렌더링 확인
|
||
|
|
- 테마 전환 작동
|
||
|
|
- 라우팅 작동
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🚧 알려진 제한사항 (향후 작업)
|
||
|
|
|
||
|
|
### 즉시 작동하지 않을 기능
|
||
|
|
1. **API 연동**: 모든 컴포넌트는 Mock 데이터로 작동 (향후 React Query 훅으로 전환)
|
||
|
|
2. **폼 검증**: 일부 폼은 react-hook-form 미적용 상태
|
||
|
|
3. **에러 처리**: 전역 에러 핸들링 미구현
|
||
|
|
4. **테스트**: 단위 테스트 미작성
|
||
|
|
5. **성능 최적화**: React.lazy() 코드 스플리팅 미적용
|
||
|
|
6. **접근성 검증**: WCAG 2.1 AA 미검증
|
||
|
|
|
||
|
|
### 향후 개선 작업
|
||
|
|
- SAM API 연동 (React Query 훅 작성)
|
||
|
|
- 코드 스플리팅 (React.lazy + Suspense)
|
||
|
|
- E2E 테스트 작성
|
||
|
|
- 성능 최적화 (Lighthouse > 90)
|
||
|
|
- 접근성 검증 및 개선
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📝 컨펌 포인트 요약
|
||
|
|
|
||
|
|
각 Phase 완료 후 사용자 컨펌:
|
||
|
|
|
||
|
|
1. **Phase 1 완료**: npm install 성공 확인
|
||
|
|
2. **Phase 2 완료**: 테마 전환 작동 확인
|
||
|
|
3. **Phase 3 완료**: 주요 컴포넌트 렌더링 확인
|
||
|
|
4. **Phase 4 완료**: 라우팅 작동 확인
|
||
|
|
5. **Phase 5 완료**: 최종 통합 테스트 완료
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔧 사용 도구
|
||
|
|
|
||
|
|
- **SuperClaude 페르소나**: system-architect
|
||
|
|
- **MCP 서버**: Sequential (의사결정), Context7 (Radix UI 문서)
|
||
|
|
- **네이티브 도구**: Read, Write, Edit, Bash, Glob
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📚 참고 문서
|
||
|
|
|
||
|
|
- [System Architect 분석 결과](./docs/architecture-analysis.md) (향후 생성)
|
||
|
|
- [Sequential 의사결정 과정](./docs/decision-log.md) (향후 생성)
|
||
|
|
- [Radix UI React 19 호환성](https://github.com/radix-ui/website) (Context7로 검증 완료)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**작성자**: Claude (AI Assistant)
|
||
|
|
**검증자**: 사용자 컨펌 필요
|
||
|
|
**최종 업데이트**: 2025-10-17 (목)
|