# 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()( 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((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((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 (
); } ``` **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 (
); } ``` #### 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} : ; } function App() { return ( }> {/* Public Routes */} } /> } /> } /> } /> } /> {/* Protected Routes */} }> } /> } /> }> } /> {/* ... 기타 라우트 */} ); } 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 (목)