diff --git a/.env.example b/.env.example index d1e65ed6..182473ff 100644 --- a/.env.example +++ b/.env.example @@ -40,6 +40,15 @@ API_KEY=your-secret-api-key-here # 주의: 운영 환경에서는 반드시 false로 설정! NEXT_PUBLIC_DEV_TOOLBAR_ENABLED=false +# ============================================== +# Puppeteer (로컬 PDF 생성용) +# ============================================== +# puppeteer-core는 Chromium을 번들하지 않으므로 로컬 Chrome 경로 필요 +# macOS: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome +# Linux: /usr/bin/google-chrome-stable +# Vercel에서는 @sparticuz/chromium이 자동 처리하므로 설정 불필요 +PUPPETEER_EXECUTABLE_PATH=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome + # ============================================== # Development Notes # ============================================== diff --git a/claudedocs/_index.md b/claudedocs/_index.md index e73069c7..24b283a7 100644 --- a/claudedocs/_index.md +++ b/claudedocs/_index.md @@ -1,12 +1,12 @@ # claudedocs 문서 맵 -> 프로젝트 기술 문서 인덱스 (Last Updated: 2026-01-29) +> 프로젝트 기술 문서 인덱스 (Last Updated: 2026-02-06) -## ⭐ 빠른 참조 +## 빠른 참조 | 문서 | 설명 | |------|------| -| **[`[REF] all-pages-test-urls.md`](./[REF]%20all-pages-test-urls.md)** | 🔗 **전체 페이지 테스트 URL 목록** - 모든 페이지 직접 접근 주소 | +| **[`[REF] all-pages-test-urls.md`](./dev/[REF]%20all-pages-test-urls.md)** | 전체 페이지 테스트 URL 목록 | --- @@ -14,33 +14,40 @@ ``` claudedocs/ -├── _index.md # 이 파일 - 문서 맵 -├── auth/ # 🔐 인증 & 토큰 관리 -├── hr/ # 👥 인사관리 (부서/사원) -├── item-master/ # 📦 품목기준관리 -├── production/ # 🏭 생산관리 (생산현황판/작업지시) -├── quality/ # 🔬 품질관리 (검사관리) -├── sales/ # 💰 판매관리 (견적/거래처) -├── accounting/ # 💳 회계관리 (매입/매출/출금) -├── board/ # 📝 게시판 관리 -├── settings/ # ⚙️ 설정 관리 -├── dashboard/ # 📊 대시보드 & 사이드바 -├── api/ # 🔌 API 통합 -├── guides/ # 📚 범용 가이드 -├── architecture/ # 🏗️ 아키텍처 & 시스템 -├── juil/ # 🏗️ 주일 공사 MES (NEW) -└── archive/ # 📁 레거시/완료된 문서 +├── _index.md # 이 파일 - 문서 맵 +├── auth/ # 인증 & 토큰 관리 +├── hr/ # 인사관리 (부서/사원) +├── item-master/ # 품목기준관리 +├── production/ # 생산관리 (생산현황판/작업자화면) +├── quality/ # 품질관리 (검사관리) +├── sales/ # 판매관리 (견적/거래처/단가) +├── accounting/ # 회계관리 (매입/매출/출금) +├── construction/ # 주일 공사 MES +├── board/ # 게시판 관리 +├── settings/ # 설정 관리 +├── dashboard/ # 대시보드 & 사이드바 +├── security/ # 보안 & 권한 +├── api/ # API 통합 +├── dev/ # 개발도구 & 테스트 +├── guides/ # 범용 가이드 +├── architecture/ # 아키텍처 & 시스템 +├── changes/ # 변경이력 +├── vehicle/ # 차량관리 +├── material/ # 자재관리 +├── approval/ # 결재관리 +├── customer-center/ # 고객센터 +└── archive/ # 레거시/완료된 문서 ``` --- -## 🔐 auth/ - 인증 & 토큰 관리 +## 인증 & 토큰 관리 — `auth/` | 파일 | 설명 | |------|------| -| `[IMPL-2025-12-30] token-refresh-caching.md` | 🔴 **NEW** - 토큰 갱신 캐싱 구현 (동시 요청 충돌 해결, Request Coalescing 패턴) | -| `[IMPL-2025-12-04] signup-page-blocking.md` | ✅ **완료** - MVP 회원가입 페이지 차단 (운영 페이지 이동 예정) | -| `token-management-guide.md` | ⭐ **핵심** - Access/Refresh Token 완전 가이드 | +| `[IMPL-2025-12-30] token-refresh-caching.md` | 토큰 갱신 캐싱 구현 (Request Coalescing 패턴) | +| `[IMPL-2025-12-04] signup-page-blocking.md` | MVP 회원가입 페이지 차단 | +| `token-management-guide.md` | Access/Refresh Token 가이드 | | `jwt-cookie-authentication-final.md` | JWT + HttpOnly Cookie 구현 | | `auth-guard-usage.md` | AuthGuard 훅 사용법 | | `route-protection-architecture.md` | 라우트 보호 아키텍처 | @@ -53,85 +60,124 @@ claudedocs/ --- -## 👥 hr/ - 인사관리 (부서/사원/근태/휴가) +## 인사관리 — `hr/` | 파일 | 설명 | |------|------| -| `[IMPL-2025-12-16] mobile-attendance.md` | 🔴 **NEW** - 모바일 출퇴근 시스템 (카카오맵 GPS 기반, MVP) | -| `[IMPL-2025-12-05] department-management-checklist.md` | ✅ **완료** - 부서관리 구현 체크리스트 (무제한 트리구조) | -| `[IMPL-2025-12-05] employee-management-checklist.md` | ✅ **완료** - 사원관리 구현 체크리스트 | -| `[IMPL-2025-12-06] vacation-management-checklist.md` | ✅ **완료** - 휴가관리 구현 체크리스트 | +| `[IMPL-2025-12-16] mobile-attendance.md` | 모바일 출퇴근 시스템 (카카오맵 GPS) | +| `[IMPL-2025-12-05] department-management-checklist.md` | 부서관리 구현 체크리스트 | +| `[IMPL-2025-12-05] employee-management-checklist.md` | 사원관리 구현 체크리스트 | +| `[IMPL-2025-12-06] vacation-management-checklist.md` | 휴가관리 구현 체크리스트 | --- -## 📦 item-master/ - 품목기준관리 +## 품목기준관리 — `item-master/` | 파일 | 설명 | |------|------| -| `[PLAN-2025-12-16] dynamicitemform-hook-extraction.md` | 🔴 **NEW** - DynamicItemForm 훅 분리 계획서 (2161줄 → 900줄 목표, 6 Phase) | -| `[FIX-2025-12-16] options-details-duplicate-bug.md` | options vs item_details 중복 저장 버그 (bending_details 값 덮어쓰기 문제 해결) | -| `[IMPL-2025-12-15] backend-item-api-migration.md` | 백엔드 품목 API 통합 (product/material → items), group_id 파라미터, **향후 동적 변경 예정** | -| `[NEXT-2025-12-13] item-file-upload-session-context.md` | ⭐ **세션 체크포인트** - 파일 업로드 UI 개선 완료, 백엔드 대기 중, DynamicItemForm 분리 예정 | -| `[NEXT-2025-12-12] item-crud-session-context.md` | 📁 이전 세션 - BOM/파일 연동 완료, 파일 업로드 동적화 작업 추가 | -| `[DESIGN-2025-12-12] item-master-form-builder-roadmap.md` | 🆕 **로드맵** - Low-Code Form Builder 확장 설계 (노션 스타일 블록 시스템) | -| `[PLAN-2025-12-08] dynamic-form-separation-plan.md` | 📋 DynamicItemForm 품목별 분리 계획 (Phase 2: 컴포넌트 구조 설계) | -| `[REF] item-code-hardcoding.md` | ⭐ **핵심** - 품목관리 하드코딩 내역 종합 (품목유형/코드자동생성/전개도/BOM) | -| `[IMPL-2025-12-02] item-code-auto-generation.md` | 품목코드 자동생성 구현 상세 | -| `[PLAN-2025-12-01] service-layer-refactoring.md` | ✅ **완료** - 서비스 레이어 리팩토링 계획 (도메인 로직 중앙화) | -| `[REF-2025-12-01] state-sync-solutions.md` | 📋 **참조** - 상태 동기화 문제 및 해결 방안 (정규화, React Query 등) | -| `[PLAN-2025-11-28] dynamic-item-form-implementation.md` | ⚠️ **롤백됨** - 이전 구현 계획 (참조용) | -| `[IMPL-2025-12-02] dynamic-item-form-rebuild.md` | 🔄 **진행중** - 품목관리 동적 페이지 재구현 (디자인 100% 동일 유지) | -| `[API-REQUEST-2025-11-28] dynamic-page-rendering-api.md` | ⭐ **v3.1** - 동적 페이지 렌더링 API 요청서 (ID 기반 통일) | -| `[PLAN-2025-11-27] item-form-component-separation.md` | ✅ **완료** - ItemForm 컴포넌트 분리 (1607→415줄, 74% 감소) | -| `[IMPL-2025-11-27] realtime-sync-fixes.md` | 실시간 동기화 수정 (BOM, 섹션 복제, 항목 수정, **페이지 삭제 시 섹션 동기화** 2025-11-28) | -| `item-master-api-pending-tasks.md` | 진행중인 API 연동 작업 | -| `item-master-pending-integration.md` | 대기중인 통합 작업 | -| `item-master-specification.md` | API 명세 | -| `item-master-backend-requirements.md` | 백엔드 요구사항 | -| `item-management-dynamic-api-spec.md` | 동적 필드 API 스펙 | -| `item-management-dynamic-frontend.md` | 동적 필드 프론트엔드 설계 | -| `item-master-data-management.md` | 데이터 관리 분석 | -| `item-master-hooks-refactoring.md` | Hooks 리팩토링 | -| `ITEM-MANAGEMENT-MIGRATION.md` | 마이그레이션 가이드 | +| `[PLAN-2025-12-16] dynamicitemform-hook-extraction.md` | DynamicItemForm 훅 분리 계획서 | +| `[FIX-2025-12-16] options-details-duplicate-bug.md` | options vs item_details 중복 저장 버그 | +| `[IMPL-2025-12-15] backend-item-api-migration.md` | 백엔드 품목 API 통합 | +| `[DESIGN-2025-12-12] item-master-form-builder-roadmap.md` | Low-Code Form Builder 로드맵 | +| `[PLAN-2025-12-08] dynamic-form-separation-plan.md` | DynamicItemForm 품목별 분리 계획 | +| `[REF] item-code-hardcoding.md` | 품목관리 하드코딩 내역 종합 | +| `[REF] items-route-consolidation.md` | 품목 라우트 통합 | +| `[IMPL-2025-12-02] item-code-auto-generation.md` | 품목코드 자동생성 구현 | +| `[PLAN-2025-12-01] service-layer-refactoring.md` | 서비스 레이어 리팩토링 계획 | +| `[REF-2025-12-01] state-sync-solutions.md` | 상태 동기화 문제 및 해결 방안 | +| `[IMPL-2025-12-02] dynamic-item-form-rebuild.md` | 동적 페이지 재구현 | +| `[API-REQUEST-2025-11-28] dynamic-page-rendering-api.md` | 동적 페이지 렌더링 API 요청서 | +| `[PLAN-2025-11-27] item-form-component-separation.md` | ItemForm 컴포넌트 분리 | +| `[IMPL-2025-11-27] realtime-sync-fixes.md` | 실시간 동기화 수정 | +| `[IMPL-2026-01-09] item-management-api-integration.md` | 품목관리 API 연동 | +| `NEXT-*.md` | 세션 체크포인트 (다수) | +| `API-*.md` | API 명세/요청 (다수) | +| `ANALYSIS-*.md` | 분석 노트 (다수) | --- -## 🏭 production/ - 생산관리 (생산현황판/작업지시) +## 생산관리 — `production/` | 파일 | 설명 | |------|------| -| `[IMPL-2025-12-22] production-dashboard-checklist.md` | 🔴 **NEW** - 생산 현황판 구현 체크리스트 (메인/작업자화면, 8 Phase) | +| `[IMPL-2025-12-22] production-dashboard-checklist.md` | 생산 현황판 구현 체크리스트 (8 Phase) | +| `[DESIGN-2026-01-29] worker-screen-spec.md` | 작업자 화면 설계 스펙 | +| `[NEXT-2025-12-22] production-session-context.md` | 세션 체크포인트 | --- -## 🔬 quality/ - 품질관리 (검사관리) +## 품질관리 — `quality/` | 파일 | 설명 | |------|------| -| `[IMPL-2025-12-23] inspection-management-checklist.md` | 🔴 **NEW** - 검사관리 구현 체크리스트 (리스트/등록/상세/수정, 7 Phase) | +| `[IMPL-2025-12-23] inspection-management-checklist.md` | 검사관리 구현 체크리스트 (7 Phase) | +| `[PLAN-2026-02-02] document-viewer-architecture.md` | 문서 뷰어 아키텍처 | +| `[PLAN-2026-02-04] quality-audit-document-management.md` | 품질심사 문서관리 | --- -## 💰 sales/ - 판매관리 (견적/거래처/단가) +## 판매관리 — `sales/` | 파일 | 설명 | |------|------| -| `[API-2025-12-08] pricing-api-enhancement-request.md` | 🔴 **NEW** - 단가관리 백엔드 API 개선 요청서 (스키마 변경, 신규 엔드포인트) | -| `[IMPL-2025-12-05] pricing-management-migration.md` | 🔄 **진행중** - 단가관리 마이그레이션 계획 (7 Phase, 체크리스트, 원가/마진 계산 로직) | -| `[API-2025-12-04] quote-api-request.md` | ⭐ **NEW** - 견적관리 API 요청서 (데이터 모델, 엔드포인트, 수식 계산) | -| `[PLAN-2025-12-04] quote-management-implementation.md` | 📋 **NEW** - 견적관리 작업계획서 (6 Phase, 체크리스트) | -| `[NEXT-2025-12-09] client-session-context.md` | ⭐ **세션 체크포인트** - 다음 세션 이어하기용 (완료/숨긴 섹션/다음 작업) | -| `[IMPL-2025-12-04] client-management-api-integration.md` | ✅ **완료** - 거래처관리 API 연동 체크리스트 (CRUD, 그룹 훅) | -| `[API-2025-12-04] client-api-analysis.md` | ✅ **완료** - 거래처 API 분석 (2차 필드 완료, is_active Boolean) | -| `[PLAN-2025-12-02] sales-pages-migration.md` | 📋 견적관리/거래처관리 마이그레이션 계획 | +| `[API-2025-12-08] pricing-api-enhancement-request.md` | 단가관리 API 개선 요청서 | +| `[IMPL-2025-12-05] pricing-management-migration.md` | 단가관리 마이그레이션 | +| `[API-2025-12-04] quote-api-request.md` | 견적관리 API 요청서 | +| `[PLAN-2025-12-04] quote-management-implementation.md` | 견적관리 작업계획서 | +| `[IMPL-2025-12-04] client-management-api-integration.md` | 거래처관리 API 연동 | +| `[API-2025-12-04] client-api-analysis.md` | 거래처 API 분석 | +| `[PLAN-2025-12-02] sales-pages-migration.md` | 판매 페이지 마이그레이션 | +| `[IMPL-2025-12-22] order-management-sales.md` | 수주관리 | +| `[IMPL-2026-01-12] quote-v2-test-pages-checklist.md` | 견적 v2 테스트 페이지 | +| `[IMPL-2025-12-09] pricing-api-integration-checklist.md` | 단가 API 연동 체크리스트 | +| `[NEXT-2026-02-04] price-distribution-session-context.md` | 단가배포 세션 체크포인트 | +| `[NEXT-2025-12-09] client-session-context.md` | 거래처 세션 체크포인트 | --- -## 📊 dashboard/ - 대시보드 & 사이드바 +## 회계관리 — `accounting/` | 파일 | 설명 | |------|------| -| `[IMPL-2026-01-07] ceo-dashboard-checklist.md` | 🔴 **NEW** - 대표님 전용 대시보드 구현 체크리스트 (11개 섹션, 달력 포함) | +| `[IMPL-2025-12-18] vendor-management-checklist.md` | 거래처관리 구현 체크리스트 | +| `[IMPL-2025-12-18] purchase-management.md` | 매입관리 페이지 구현 | +| `[IMPL-2025-12-18] bill-management.md` | 어음관리 | +| `[IMPL-2025-12-18] expected-expense-checklist.md` | 지출예정 체크리스트 | +| `[IMPL-2025-12-18] receivables-status.md` | 미수금 현황 | +| `[IMPL-2025-12-18] vendor-ledger.md` | 거래처원장 | +| `[IMPL-2025-12-18] withdrawal-management-checklist.md` | 출금관리 체크리스트 | +| `[IMPL-2025-12-19] bad-debt-collection-management.md` | 부실채권 관리 | +| `[IMPL-2025-12-19] card-transaction-inquiry.md` | 카드거래 조회 | +| `[PLAN-2025-12-18] sales-management.md` | 매출관리 계획 | +| `[PLAN-2025-12-19] bank-account-transaction-inquiry.md` | 은행거래 조회 계획 | +| `[PLAN-2026-01-23] vendor-credit-analysis-modal.md` | 거래처 여신분석 모달 | + +--- + +## 주일 공사 MES — `construction/` + +| 파일 | 설명 | +|------|------| +| `[IMPL-2026-01-05] item-management-checklist.md` | 품목관리 구현 체크리스트 | +| `[IMPL-2026-01-05] category-management-checklist.md` | 카테고리관리 구현 체크리스트 | +| `[IMPL-2026-01-05] pricing-management-checklist.md` | 단가관리 구현 체크리스트 | +| `[IMPL-2026-01-09] partner-management-api-integration.md` | 거래처관리 API 연동 | +| `[IMPL-2026-01-09] site-management-api-integration.md` | 현장관리 API 연동 | +| `[IMPL-2026-01-12] project-detail-checklist.md` | 프로젝트 상세 체크리스트 | +| `[PLAN-2026-01-05] order-management-implementation.md` | 발주관리 구현 계획 | +| `[PLAN-2026-01-02] estimate-detail-form-refactoring.md` | 견적상세 폼 리팩토링 | +| `[PLAN-2026-01-05] order-detail-form-separation.md` | 발주상세 폼 분리 | +| `[REF] construction-project-flow.md` | 프로젝트 플로우 | +| `[REF] juil-project-structure.md` | 프로젝트 구조 가이드 | +| `[NEXT-2025-12-30] partner-management-session-context.md` | 세션 체크포인트 | + +--- + +## 대시보드 & 사이드바 — `dashboard/` + +| 파일 | 설명 | +|------|------| +| `[IMPL-2026-01-07] ceo-dashboard-checklist.md` | 대표님 전용 대시보드 (11개 섹션) | | `dashboard-integration-complete.md` | 대시보드 통합 완료 | | `dashboard-cleanup-summary.md` | 정리 요약 | | `dashboard-migration-summary.md` | 마이그레이션 요약 | @@ -140,7 +186,18 @@ claudedocs/ --- -## 🔌 api/ - API 통합 +## 보안 & 권한 — `security/` + +| 파일 | 설명 | +|------|------| +| `[PLAN-2025-01-20] permission-system-implementation.md` | 권한 시스템 구현 계획 | +| `[QA-2026-02-03] permission-verification-checklist.md` | 권한 검증 체크리스트 | +| `[PLAN-2025-12-12] tenant-data-isolation-implementation.md` | 테넌트 데이터 격리 구현 | +| `[SECURITY-2025-12-12] tenant-data-isolation-audit.md` | 테넌트 데이터 격리 감사 | + +--- + +## API 통합 — `api/` | 파일 | 설명 | |------|------| @@ -151,93 +208,136 @@ claudedocs/ --- -## 📚 guides/ - 범용 가이드 +## 개발도구 & 테스트 — `dev/` | 파일 | 설명 | |------|------| -| `[REF-2026-01-07] nextjs-security-update-and-migration-plan.md` | 🔴 **NEW** - Next.js 보안 업데이트 (15.5.9) 및 16 마이그레이션 계획 | -| `[DESIGN-2026-01-02] document-modal-common-component.md` | 문서 모달 공통 컴포넌트 설계 요구사항 (6개 모달 분석, 헤더/결재라인/테이블 조합형) | -| `[GUIDE] print-area-utility.md` | 인쇄 모달 printArea 유틸리티 가이드 (8개 모달 적용, print-utils.ts) | -| `[GUIDE-2025-12-29] vercel-deployment.md` | Vercel 배포 가이드 (환경변수, CORS, 테스트 체크리스트) | -| `[PLAN-2025-12-23] common-component-extraction-plan.md` | 공통 컴포넌트 추출 계획서 (Phase 1-4, 체크리스트 포함, ~1,900줄 절감) | -| `[ANALYSIS-2025-12-23] common-component-extraction-candidates.md` | 📋 공통 컴포넌트 추출 후보 분석 (다이얼로그 102개 중복, ~2,370줄 절감 예상) | -| `[PLAN-2025-12-19] project-health-improvement.md` | ✅ **Phase 1 완료** - 프로젝트 헬스 개선 계획서 (타입에러 0개, API키 보안, SSR 수정) | -| `[PLAN-2025-12-19] page-layout-standardization.md` | 🔴 **NEW** - 페이지 레이아웃 표준화 계획 | -| `[GUIDE-2025-12-16] options-vs-flattened-data.md` | options vs 평탄화 데이터 패턴 (API 응답 매핑 시 options 직접 파싱 금지) | -| `[GUIDE] large-file-handling-strategy.md` | 대용량 파일 처리 전략 (100MB+ CAD 도면, 청크 업로드, 스트리밍 다운로드) | -| `[FIX-2025-12-05] radix-ui-select-controlled-mode-bug.md` | ⭐ **핵심** - Radix UI Select 버그 해결 (Edit 모드 값 표시 안됨 → key prop 강제 리마운트) | -| `i18n-usage-guide.md` | 다국어 사용 가이드 | -| `form-validation-guide.md` | 폼 유효성 검사 | -| `CSS-MIGRATION-WORKFLOW.md` | CSS 마이그레이션 워크플로우 | -| `LARGE-FILE-WORKFLOW.md` | 대용량 파일 작업 워크플로우 | -| `ZOD-VALIDATION-TROUBLESHOOTING.md` | Zod 유효성 검사 트러블슈팅 | -| `nextjs-error-handling-guide.md` | Next.js 에러 처리 | +| `[REF] all-pages-test-urls.md` | 전체 페이지 테스트 URL 목록 | +| `[REF] construction-pages-test-urls.md` | 주일 페이지 테스트 URL | +| `[REF] page-builder-implementation.md` | 페이지 빌더 구현 참조 | +| `[REF] chrome-devtools-mcp-emoji-issue.md` | Chrome DevTools MCP 이모지 이슈 | +| `[PLAN] detail-page-pattern-classification.md` | 상세페이지 패턴 분류 | +| `[PLAN-2026-02-03] claude-config-optimization.md` | Claude 설정 최적화 | +| `[IMPL-2025-12-29] quality-inspection-checklist.md` | 품질검사 체크리스트 | +| `[IMPL-2026-01-23] full-page-inspection.md` | 전체 페이지 검사 | +| `[FIX-2026-01-29] typecheck-errors-checklist.md` | 타입체크 에러 체크리스트 | +| `[HOTFIX-2026-01-27] E2E-테스트-수정계획서.md` | E2E 테스트 수정 계획서 | --- -## 🏗️ architecture/ - 아키텍처 & 시스템 +## 범용 가이드 — `guides/` | 파일 | 설명 | |------|------| -| `[PLAN-2026-02-06] refactoring-roadmap.md` | 🔴 **NEW** - 리팩토링 종합 로드맵 (5 Phase, 공통훅~성능최적화, **전부 프론트 단독**) | -| `[PLAN-2026-02-06] multi-tenancy-optimization-roadmap.md` | 🔴 **NEW** - 멀티테넌시 공통화/최적화 종합 로드맵 (8 Phase, API 테넌트 주입~라우팅) | -| `[FIX-2026-01-29] masterdata-cache-tenant-isolation.md` | masterDataStore 캐시 테넌트 격리 수정 (page_config 키에 tenantId 추가, dead code 해소) | -| `[PLAN-2025-12-29] dynamic-menu-refresh.md` | 동적 메뉴 갱신 시스템 (1단계: 폴링, 2단계: SSE) | -| `multi-tenancy-implementation.md` | ✅ **Phase 1-2 완료** - 초기 멀티테넌시 구현 (AuthContext, TenantAwareCache) | -| `multi-tenancy-test-guide.md` | 멀티테넌시 캐시 격리 테스트 가이드 | -| `architecture-integration-risks.md` | 통합 리스크 | -| `browser-support-policy.md` | 브라우저 지원 정책 | -| `ssr-hydration-fix.md` | SSR 하이드레이션 수정 | +| **UI 컴포넌트** | | +| `[DESIGN-2026-01-14] universal-list-component.md` | UniversalListPage 설계 | +| `[IMPL-2026-01-14] universal-list-component-checklist.md` | UniversalListPage 구현 체크리스트 | +| `[PLAN] universal-detail-component.md` | UniversalDetail 컴포넌트 계획 | +| `[REF] UniversalListPage-QA-patterns.md` | UniversalListPage QA 패턴 | +| `UniversalListPage-검색기능-수정내역.md` | 검색 기능 수정 내역 | +| `UniversalListPage-검색리렌더링-해결가이드.md` | 검색 리렌더링 해결 | +| `[DESIGN-2026-01-02] document-modal-common-component.md` | 문서 모달 공통 컴포넌트 | +| `badge-commonization-guide.md` | 배지 공통화 가이드 | +| **공통화 & 마이그레이션** | | +| `[ANALYSIS-2025-12-23] common-component-extraction-candidates.md` | 공통 컴포넌트 추출 후보 분석 | +| `[ANALYSIS] common-component-patterns.md` | 공통 컴포넌트 패턴 | +| `[PLAN-2025-12-23] common-component-extraction-plan.md` | 공통 컴포넌트 추출 계획 | +| `[IMPL-2025-01-26] list-page-ui-standardization-checklist.md` | 리스트 페이지 UI 표준화 | +| `[IMPL-2026-01-23] button-navigation-checklist.md` | 버튼 네비게이션 체크리스트 | +| `[IMPL-2026-01-23] mode-migration-checklist.md` | 모드 마이그레이션 체크리스트 | +| `[IMPL-2026-01-23] mode-navigation-full-checklist.md` | 모드 네비게이션 전체 체크리스트 | +| `[IMPL-2026-01-21] utility-input-migration-checklist.md` | 유틸리티 입력 마이그레이션 | +| `[IMPL-2026-02-06] datepicker-migration-checklist.md` | DatePicker 마이그레이션 | +| `[REF-2026-01-09] server-to-client-component-migration-checklist.md` | Server→Client 마이그레이션 | +| **모바일** | | +| `[GUIDE] mobile-responsive-patterns.md` | 모바일 반응형 패턴 | +| `[IMPL-2026-01-13] mobile-filter-migration-checklist.md` | 모바일 필터 마이그레이션 | +| `[PLAN-2026-01-20] mobile-card-infinity-scroll.md` | 모바일 카드 무한스크롤 | +| `[PLAN] mobile-overflow-testing.md` | 모바일 오버플로우 테스트 | +| `[QA-2026-01-21] mobile-infinity-scroll-inspection.md` | 모바일 무한스크롤 검사 | +| `[REF] mobile-zoom-fix-guide.md` | 모바일 줌 수정 가이드 | +| `[REF] mobile-zoom-prevention-guide.md` | 모바일 줌 방지 가이드 | +| `[FIX-2026-02-04] mobile-zoom-panning.md` | 모바일 줌 패닝 수정 | +| `[GUIDE] foldable-device-layout-fix.md` | 폴더블 기기 레이아웃 | +| **프로젝트 헬스 & 문서 시스템** | | +| `[PLAN-2025-12-19] project-health-improvement.md` | 프로젝트 헬스 개선 계획 | +| `[PLAN-2025-12-19] page-layout-standardization.md` | 페이지 레이아웃 표준화 | +| `[PLAN-2025-01-21] document-system-integration.md` | 문서 시스템 통합 | +| `[QA-2025-01-21] document-system-inspection.md` | 문서 시스템 검사 | +| `[QA-2026-01-15] universal-list-page-inspection.md` | UniversalListPage 검사 | +| **기술 가이드** | | +| `[GUIDE] print-area-utility.md` | 인쇄 printArea 유틸리티 | +| `[GUIDE-2025-12-29] vercel-deployment.md` | Vercel 배포 가이드 | +| `[GUIDE-2025-12-16] options-vs-flattened-data.md` | options vs 평탄화 데이터 패턴 | +| `[GUIDE] large-file-handling-strategy.md` | 대용량 파일 처리 전략 | +| `[FIX-2025-12-05] radix-ui-select-controlled-mode-bug.md` | Radix UI Select 버그 해결 | +| `[GUIDE] CSS-MIGRATION-WORKFLOW.md` | CSS 마이그레이션 워크플로우 | +| `[GUIDE] LARGE-FILE-WORKFLOW.md` | 대용량 파일 작업 워크플로우 | +| `[GUIDE] ZOD-VALIDATION-TROUBLESHOOTING.md` | Zod 유효성 검사 트러블슈팅 | +| `[REF] nextjs-error-handling-guide.md` | Next.js 에러 처리 | +| `[REF-2026-01-07] nextjs-security-update-and-migration-plan.md` | Next.js 보안 업데이트 계획 | +| `[GUIDE] collaboration-with-claude.md` | Claude 협업 가이드 | +| `[IMPL-2025-11-06] i18n-usage-guide.md` | 다국어 사용 가이드 | +| `[IMPL-2025-11-07] form-validation-guide.md` | 폼 유효성 검사 | +| `[IMPL-2026-01-05] stat-cards-grid-layout.md` | 스탯 카드 그리드 레이아웃 | +| `[NEXT-2026-01-14] UniversalListPage-pilot-session-context.md` | 세션 체크포인트 | --- -## 💳 accounting/ - 회계관리 (거래처/매입/매출/출금) +## 아키텍처 & 시스템 — `architecture/` | 파일 | 설명 | |------|------| -| `[IMPL-2025-12-18] vendor-management-checklist.md` | 🔴 **NEW** - 거래처관리 구현 체크리스트 (리스트 + 상세 페이지) | -| `[IMPL-2025-12-18] purchase-management.md` | 매입관리 페이지 구현 (리스트 + 상세 모달) | +| **리팩토링 로드맵** | | +| `[PLAN-2026-02-06] refactoring-roadmap.md` | 리팩토링 종합 로드맵 (5 Phase) | +| `[PLAN-2026-02-06] multi-tenancy-optimization-roadmap.md` | 멀티테넌시 최적화 로드맵 (8 Phase) | +| **공통화 & 마이그레이션 분석** | | +| `[ANALYSIS-2026-01-20] 공통화-현황-분석.md` | 공통화 현황 분석 | +| `[ANALYSIS-2026-02-05] SAM-ERP-MES-정체성-분석.md` | SAM ERP/MES 정체성 분석 | +| `[ANALYSIS-2026-02-05] list-page-commonization-status.md` | 리스트 페이지 공통화 현황 | +| **컴포넌트 아키텍처** | | +| `[PLAN-2026-01-22] ui-component-abstraction.md` | UI 컴포넌트 추상화 | +| `[IMPL-2026-01-21] input-form-componentization.md` | 입력폼 컴포넌트화 | +| `[IMPL-2026-01-21] phase4-input-migration-rollout.md` | Phase 4 입력 마이그레이션 | +| `[IMPL-2026-02-05] detail-hooks-migration-plan.md` | 상세 훅 마이그레이션 | +| `[IMPL-2026-02-05] formatter-commonization-plan.md` | formatter 공통화 계획 | +| `[IMPL] IntegratedDetailTemplate-checklist.md` | 통합 상세 템플릿 체크리스트 | +| `[REF] template-migration-status.md` | 템플릿 마이그레이션 현황 | +| **시스템 설계** | | +| `[PLAN-2026-01-16] layout-restructure.md` | 레이아웃 구조 변경 | +| `[PLAN-2025-12-29] dynamic-menu-refresh.md` | 동적 메뉴 갱신 시스템 | +| `[FIX-2026-01-29] masterdata-cache-tenant-isolation.md` | masterData 캐시 테넌트 격리 | +| `[DESIGN-2025-12-20] item-master-zustand-refactoring.md` | Zustand 리팩토링 설계 | +| `[REF-2025-11-19] multi-tenancy-implementation.md` | 멀티테넌시 구현 | +| `[TEST-2025-11-19] multi-tenancy-test-guide.md` | 멀티테넌시 테스트 가이드 | +| `[IMPL-2025-11-13] browser-support-policy.md` | 브라우저 지원 정책 | +| `[IMPL-2025-11-18] ssr-hydration-fix.md` | SSR 하이드레이션 수정 | +| `[REF] architecture-integration-risks.md` | 통합 리스크 | +| `[NEXT-2025-12-20] zustand-refactoring-session-context.md` | 세션 체크포인트 | --- -## 📝 board/ - 게시판 관리 +## 게시판 관리 — `board/` | 파일 | 설명 | |------|------| -| `[PLAN-2025-12-19] board-management-implementation.md` | 🔴 **NEW** - 게시판 구현 계획서 (리스트/등록/상세/댓글, TipTap 에디터) | +| `[PLAN-2025-12-19] board-management-implementation.md` | 게시판 구현 계획서 | --- -## ⚙️ settings/ - 설정 관리 +## 설정 관리 — `settings/` | 파일 | 설명 | |------|------| -| `[IMPL-2025-12-19] company-info.md` | 🔴 **NEW** - 회사정보 구현 (폼 기반, 회사 추가 팝업) | -| `[IMPL-2025-12-19] popup-management.md` | 팝업관리 구현 (리스트/등록/상세/수정, RichTextEditor) | +| `[IMPL-2025-12-19] company-info.md` | 회사정보 구현 | +| `[IMPL-2025-12-19] popup-management.md` | 팝업관리 구현 | --- -## 🏗️ juil/ - 주일 공사 MES (NEW) - -| 파일 | 설명 | -|------|------| -| `[IMPL-2026-01-05] item-management-checklist.md` | 🔴 **NEW** - 품목관리 구현 체크리스트 (발주관리 > 기준정보 > 품목관리) | -| `[IMPL-2026-01-05] category-management-checklist.md` | 🔴 **NEW** - 카테고리관리 구현 체크리스트 (발주관리 > 기준정보) | -| `[PLAN-2026-01-05] order-management-implementation.md` | 발주관리 페이지 구현 계획서 (달력+리스트, ScheduleCalendar 공통 컴포넌트) | -| `[NEXT-2025-12-30] partner-management-session-context.md` | ⭐ **세션 체크포인트** - 거래처 관리 리스트 완료, 등록/상세/수정 예정 | -| `[REF] juil-project-structure.md` | 주일 프로젝트 구조 가이드 (경로, 컴포넌트, 테스트 URL) | - -**프로젝트 정보**: -- 업체: 주일 (공사/건설) -- 페이지 경로: `src/app/[locale]/(protected)/juil/` -- 컴포넌트: `src/components/business/juil/` -- 테스트 URL: http://localhost:3000/dev/juil-test-urls - ---- - -## 📁 archive/ - 레거시/완료된 문서 +## archive/ - 레거시/완료된 문서 완료되거나 더 이상 활성화되지 않은 문서들. 참조용으로 보관. +테스트 스크린샷(`qa-*.png`)도 여기에 보관. --- @@ -259,13 +359,12 @@ claudedocs/ - `TEST` - 테스트 가이드 - `NEXT` - 다음 작업 목록 - `FIX` - 버그 해결 문서 +- `QA` - 품질 검사 문서 +- `HOTFIX` - 긴급 수정 문서 ### 폴더 배치 기준 1. **기능/도메인 우선**: 문서 주제에 맞는 폴더에 배치 2. **범용 가이드**: 여러 기능에 적용되면 `guides/`에 배치 -3. **완료된 작업**: 더 이상 활성화되지 않으면 `archive/`로 이동 -4. **신규 도메인**: 3개 이상 문서가 생기면 새 폴더 생성 고려 - -### 문서 업데이트 -- 중요 변경 시 문서 상단에 날짜와 함께 변경사항 기록 -- `_index.md`에 새 문서 추가 시 테이블 업데이트 +3. **시스템 전체**: 아키텍처/리팩토링은 `architecture/`에 배치 +4. **개발도구**: 테스트 URL, 빌드, 설정은 `dev/`에 배치 +5. **완료된 작업**: 더 이상 활성화되지 않으면 `archive/`로 이동 diff --git a/claudedocs/[PLAN-2026-01-23] vendor-credit-analysis-modal.md b/claudedocs/accounting/[PLAN-2026-01-23] vendor-credit-analysis-modal.md similarity index 100% rename from claudedocs/[PLAN-2026-01-23] vendor-credit-analysis-modal.md rename to claudedocs/accounting/[PLAN-2026-01-23] vendor-credit-analysis-modal.md diff --git a/claudedocs/[ANALYSIS-2026-01-20] 공통화-현황-분석.md b/claudedocs/architecture/[ANALYSIS-2026-01-20] 공통화-현황-분석.md similarity index 100% rename from claudedocs/[ANALYSIS-2026-01-20] 공통화-현황-분석.md rename to claudedocs/architecture/[ANALYSIS-2026-01-20] 공통화-현황-분석.md diff --git a/claudedocs/[ANALYSIS-2026-02-05] SAM-ERP-MES-정체성-분석.md b/claudedocs/architecture/[ANALYSIS-2026-02-05] SAM-ERP-MES-정체성-분석.md similarity index 100% rename from claudedocs/[ANALYSIS-2026-02-05] SAM-ERP-MES-정체성-분석.md rename to claudedocs/architecture/[ANALYSIS-2026-02-05] SAM-ERP-MES-정체성-분석.md diff --git a/claudedocs/[ANALYSIS-2026-02-05] list-page-commonization-status.md b/claudedocs/architecture/[ANALYSIS-2026-02-05] list-page-commonization-status.md similarity index 100% rename from claudedocs/[ANALYSIS-2026-02-05] list-page-commonization-status.md rename to claudedocs/architecture/[ANALYSIS-2026-02-05] list-page-commonization-status.md diff --git a/claudedocs/[IMPL-2026-01-21] input-form-componentization.md b/claudedocs/architecture/[IMPL-2026-01-21] input-form-componentization.md similarity index 100% rename from claudedocs/[IMPL-2026-01-21] input-form-componentization.md rename to claudedocs/architecture/[IMPL-2026-01-21] input-form-componentization.md diff --git a/claudedocs/[IMPL-2026-01-21] phase4-input-migration-rollout.md b/claudedocs/architecture/[IMPL-2026-01-21] phase4-input-migration-rollout.md similarity index 100% rename from claudedocs/[IMPL-2026-01-21] phase4-input-migration-rollout.md rename to claudedocs/architecture/[IMPL-2026-01-21] phase4-input-migration-rollout.md diff --git a/claudedocs/[IMPL-2026-02-05] detail-hooks-migration-plan.md b/claudedocs/architecture/[IMPL-2026-02-05] detail-hooks-migration-plan.md similarity index 100% rename from claudedocs/[IMPL-2026-02-05] detail-hooks-migration-plan.md rename to claudedocs/architecture/[IMPL-2026-02-05] detail-hooks-migration-plan.md diff --git a/claudedocs/[IMPL-2026-02-05] formatter-commonization-plan.md b/claudedocs/architecture/[IMPL-2026-02-05] formatter-commonization-plan.md similarity index 100% rename from claudedocs/[IMPL-2026-02-05] formatter-commonization-plan.md rename to claudedocs/architecture/[IMPL-2026-02-05] formatter-commonization-plan.md diff --git a/claudedocs/[IMPL] IntegratedDetailTemplate-checklist.md b/claudedocs/architecture/[IMPL] IntegratedDetailTemplate-checklist.md similarity index 100% rename from claudedocs/[IMPL] IntegratedDetailTemplate-checklist.md rename to claudedocs/architecture/[IMPL] IntegratedDetailTemplate-checklist.md diff --git a/claudedocs/[PLAN-2026-01-22] ui-component-abstraction.md b/claudedocs/architecture/[PLAN-2026-01-22] ui-component-abstraction.md similarity index 100% rename from claudedocs/[PLAN-2026-01-22] ui-component-abstraction.md rename to claudedocs/architecture/[PLAN-2026-01-22] ui-component-abstraction.md diff --git a/claudedocs/[REF] template-migration-status.md b/claudedocs/architecture/[REF] template-migration-status.md similarity index 100% rename from claudedocs/[REF] template-migration-status.md rename to claudedocs/architecture/[REF] template-migration-status.md diff --git a/claudedocs/qa-inbox-modal-test.png b/claudedocs/archive/qa-inbox-modal-test.png similarity index 100% rename from claudedocs/qa-inbox-modal-test.png rename to claudedocs/archive/qa-inbox-modal-test.png diff --git a/claudedocs/qa-reference-modal-test.png b/claudedocs/archive/qa-reference-modal-test.png similarity index 100% rename from claudedocs/qa-reference-modal-test.png rename to claudedocs/archive/qa-reference-modal-test.png diff --git a/claudedocs/[FIX-2026-01-29] typecheck-errors-checklist.md b/claudedocs/dev/[FIX-2026-01-29] typecheck-errors-checklist.md similarity index 100% rename from claudedocs/[FIX-2026-01-29] typecheck-errors-checklist.md rename to claudedocs/dev/[FIX-2026-01-29] typecheck-errors-checklist.md diff --git a/claudedocs/[HOTFIX-2026-01-27] E2E-테스트-수정계획서.md b/claudedocs/dev/[HOTFIX-2026-01-27] E2E-테스트-수정계획서.md similarity index 100% rename from claudedocs/[HOTFIX-2026-01-27] E2E-테스트-수정계획서.md rename to claudedocs/dev/[HOTFIX-2026-01-27] E2E-테스트-수정계획서.md diff --git a/claudedocs/[IMPL-2026-01-23] full-page-inspection.md b/claudedocs/dev/[IMPL-2026-01-23] full-page-inspection.md similarity index 100% rename from claudedocs/[IMPL-2026-01-23] full-page-inspection.md rename to claudedocs/dev/[IMPL-2026-01-23] full-page-inspection.md diff --git a/claudedocs/[PLAN-2026-02-03] claude-config-optimization.md b/claudedocs/dev/[PLAN-2026-02-03] claude-config-optimization.md similarity index 100% rename from claudedocs/[PLAN-2026-02-03] claude-config-optimization.md rename to claudedocs/dev/[PLAN-2026-02-03] claude-config-optimization.md diff --git a/claudedocs/[ANALYSIS] common-component-patterns.md b/claudedocs/guides/[ANALYSIS] common-component-patterns.md similarity index 100% rename from claudedocs/[ANALYSIS] common-component-patterns.md rename to claudedocs/guides/[ANALYSIS] common-component-patterns.md diff --git a/claudedocs/[FIX-2026-02-04] mobile-zoom-panning.md b/claudedocs/guides/[FIX-2026-02-04] mobile-zoom-panning.md similarity index 100% rename from claudedocs/[FIX-2026-02-04] mobile-zoom-panning.md rename to claudedocs/guides/[FIX-2026-02-04] mobile-zoom-panning.md diff --git a/claudedocs/[IMPL-2025-01-26] list-page-ui-standardization-checklist.md b/claudedocs/guides/[IMPL-2025-01-26] list-page-ui-standardization-checklist.md similarity index 100% rename from claudedocs/[IMPL-2025-01-26] list-page-ui-standardization-checklist.md rename to claudedocs/guides/[IMPL-2025-01-26] list-page-ui-standardization-checklist.md diff --git a/claudedocs/[IMPL-2026-01-23] button-navigation-checklist.md b/claudedocs/guides/[IMPL-2026-01-23] button-navigation-checklist.md similarity index 100% rename from claudedocs/[IMPL-2026-01-23] button-navigation-checklist.md rename to claudedocs/guides/[IMPL-2026-01-23] button-navigation-checklist.md diff --git a/claudedocs/[IMPL-2026-01-23] mode-migration-checklist.md b/claudedocs/guides/[IMPL-2026-01-23] mode-migration-checklist.md similarity index 100% rename from claudedocs/[IMPL-2026-01-23] mode-migration-checklist.md rename to claudedocs/guides/[IMPL-2026-01-23] mode-migration-checklist.md diff --git a/claudedocs/[IMPL-2026-01-23] mode-navigation-full-checklist.md b/claudedocs/guides/[IMPL-2026-01-23] mode-navigation-full-checklist.md similarity index 100% rename from claudedocs/[IMPL-2026-01-23] mode-navigation-full-checklist.md rename to claudedocs/guides/[IMPL-2026-01-23] mode-navigation-full-checklist.md diff --git a/claudedocs/[IMPL-2026-02-06] datepicker-migration-checklist.md b/claudedocs/guides/[IMPL-2026-02-06] datepicker-migration-checklist.md similarity index 100% rename from claudedocs/[IMPL-2026-02-06] datepicker-migration-checklist.md rename to claudedocs/guides/[IMPL-2026-02-06] datepicker-migration-checklist.md diff --git a/claudedocs/[PLAN-2025-01-21] document-system-integration.md b/claudedocs/guides/[PLAN-2025-01-21] document-system-integration.md similarity index 100% rename from claudedocs/[PLAN-2025-01-21] document-system-integration.md rename to claudedocs/guides/[PLAN-2025-01-21] document-system-integration.md diff --git a/claudedocs/[QA-2025-01-21] document-system-inspection.md b/claudedocs/guides/[QA-2025-01-21] document-system-inspection.md similarity index 100% rename from claudedocs/[QA-2025-01-21] document-system-inspection.md rename to claudedocs/guides/[QA-2025-01-21] document-system-inspection.md diff --git a/claudedocs/[REF] items-route-consolidation.md b/claudedocs/item-master/[REF] items-route-consolidation.md similarity index 100% rename from claudedocs/[REF] items-route-consolidation.md rename to claudedocs/item-master/[REF] items-route-consolidation.md diff --git a/claudedocs/[DESIGN-2026-01-29] worker-screen-spec.md b/claudedocs/production/[DESIGN-2026-01-29] worker-screen-spec.md similarity index 100% rename from claudedocs/[DESIGN-2026-01-29] worker-screen-spec.md rename to claudedocs/production/[DESIGN-2026-01-29] worker-screen-spec.md diff --git a/claudedocs/[PLAN-2026-02-04] quality-audit-document-management.md b/claudedocs/quality/[PLAN-2026-02-04] quality-audit-document-management.md similarity index 100% rename from claudedocs/[PLAN-2026-02-04] quality-audit-document-management.md rename to claudedocs/quality/[PLAN-2026-02-04] quality-audit-document-management.md diff --git a/claudedocs/refactoring/[REF-2025-02-06] useListHandlers-commonization.md b/claudedocs/refactoring/[REF-2025-02-06] useListHandlers-commonization.md new file mode 100644 index 00000000..2490f2bf --- /dev/null +++ b/claudedocs/refactoring/[REF-2025-02-06] useListHandlers-commonization.md @@ -0,0 +1,97 @@ +# Handler Hook 공통화 완료 보고서 + +**작성일**: 2025-02-06 +**상태**: ✅ 완료 + +## 개요 + +리스트 페이지에서 중복 사용되던 `handleRowClick`, `handleEdit` 핸들러를 공통 Hook으로 추출하여 17개 파일에 적용 완료. + +## 생성된 Hook + +### `/src/hooks/useListHandlers.ts` + +```typescript +'use client'; + +import { useCallback } from 'react'; +import { useRouter } from 'next/navigation'; + +export function useListHandlers(basePath: string) { + const router = useRouter(); + + const handleRowClick = useCallback( + (item: T) => { + router.push(`/ko/${basePath}/${item.id}?mode=view`); + }, + [router, basePath] + ); + + const handleEdit = useCallback( + (item: T) => { + router.push(`/ko/${basePath}/${item.id}?mode=edit`); + }, + [router, basePath] + ); + + return { handleRowClick, handleEdit, router }; +} +``` + +## 마이그레이션 완료 파일 (17개) + +| # | 파일 | basePath | 비고 | +|---|------|----------|------| +| 1 | ContractListClient.tsx | `construction/project/contract` | | +| 2 | BiddingListClient.tsx | `construction/project/bidding` | | +| 3 | EstimateListClient.tsx | `construction/project/bidding/estimates` | | +| 4 | HandoverReportListClient.tsx | `construction/project/contract/handover-report` | | +| 5 | PartnerListClient.tsx | `construction/project/bidding/partners` | router 사용 (handleCreate) | +| 6 | OrderManagementListClient.tsx | `construction/order/order-management` | router 사용 (handleCreate, handleCalendarEventClick) | +| 7 | ProgressBillingManagementListClient.tsx | `construction/billing/progress-billing-management` | | +| 8 | SiteManagementListClient.tsx | `construction/order/site-management` | | +| 9 | WorkerStatusListClient.tsx | `construction/project/worker-status` | handleRowClick만 사용 (handleEdit 없음) | +| 10 | StructureReviewListClient.tsx | `construction/order/structure-review` | router 사용 (handleCreate) | +| 11 | IssueManagementListClient.tsx | `construction/project/issue-management` | router 사용 (handleCreate) | +| 12 | SiteBriefingListClient.tsx | `construction/project/bidding/site-briefings` | router 사용 (handleCreate) | +| 13 | ItemManagementClient.tsx | `construction/order/base-info/items` | handleRowClick만 (handleEdit 시그니처 다름) | +| 14 | LaborManagementClient.tsx | `construction/order/base-info/labor` | router 사용 (handleCreate) | +| 15 | PricingListClient.tsx | `construction/order/base-info/pricing` | router 사용 (handleCreate) | +| 16 | ConstructionManagementListClient.tsx | `construction/project/construction-management` | router 사용 (handleCalendarEventClick) | +| 17 | ProjectListClient.tsx | `construction/project/execution-management` | handleGanttProjectClick → handleRowClick로 대체 | + +## 사용 패턴 + +### 기본 사용 +```typescript +const { handleRowClick, handleEdit } = useListHandlers('your/base/path'); +``` + +### router도 필요한 경우 +```typescript +const { handleRowClick, handleEdit, router } = useListHandlers('your/base/path'); + +// 추가 핸들러에서 router 사용 +const handleCreate = useCallback(() => { + router.push('/ko/your/base/path?mode=new'); +}, [router]); +``` + +## 효과 + +- **~100줄 이상** 중복 코드 제거 +- 네비게이션 로직 중앙화 +- 유지보수 용이성 향상 +- 일관된 핸들러 패턴 적용 + +## 다음 작업 (우선순위) + +1. ~~Handler Hook 공통화~~ ✅ 완료 +2. **MOCK 데이터 공통화** - 중복되는 MOCK 옵션 데이터 통합 +3. **customSortFn 공통화** - 정렬 로직 공통 유틸 함수 추출 + +## 주의사항 + +- `ItemManagementClient.tsx`의 `handleEdit`는 시그니처가 다름 (`(e: React.MouseEvent, itemId: string)`) +- 일부 파일은 `handleEdit` 없이 `handleRowClick`만 사용 +- 추가 핸들러(handleCreate, handleCalendarEventClick 등)에서 router가 필요한 경우 hook에서 router도 destructuring 필요 diff --git a/claudedocs/[NEXT-2026-02-04] price-distribution-session-context.md b/claudedocs/sales/[NEXT-2026-02-04] price-distribution-session-context.md similarity index 100% rename from claudedocs/[NEXT-2026-02-04] price-distribution-session-context.md rename to claudedocs/sales/[NEXT-2026-02-04] price-distribution-session-context.md diff --git a/claudedocs/[PLAN-2025-01-20] permission-system-implementation.md b/claudedocs/security/[PLAN-2025-01-20] permission-system-implementation.md similarity index 100% rename from claudedocs/[PLAN-2025-01-20] permission-system-implementation.md rename to claudedocs/security/[PLAN-2025-01-20] permission-system-implementation.md diff --git a/claudedocs/[QA-2026-02-03] permission-verification-checklist.md b/claudedocs/security/[QA-2026-02-03] permission-verification-checklist.md similarity index 100% rename from claudedocs/[QA-2026-02-03] permission-verification-checklist.md rename to claudedocs/security/[QA-2026-02-03] permission-verification-checklist.md diff --git a/claudedocs/vercel/vercel-env-setup-guide.md b/claudedocs/vercel/vercel-env-setup-guide.md new file mode 100644 index 00000000..37b7e706 --- /dev/null +++ b/claudedocs/vercel/vercel-env-setup-guide.md @@ -0,0 +1,81 @@ +# Vercel 환경변수 설정 가이드 + +## 설정 위치 +Vercel Dashboard → Project → Settings → Environment Variables + +--- + +## 필수 환경변수 + +### 서버 전용 (Server-side Only) + +| 변수명 | 설명 | 예시 값 | 환경 | +|--------|------|---------|------| +| `API_KEY` | 백엔드 API 인증 키 (X-API-KEY 헤더) | `42Jfwc6Ea...` | Production, Preview | +| `API_URL` | 백엔드 API 내부 URL (서버 사이드 전용) | `https://api.codebridge-x.com` | Production, Preview | + +> `API_KEY`는 절대 `NEXT_PUBLIC_` 접두사를 붙이지 말 것. 클라이언트에 노출되면 안 됨. + +### 클라이언트 공개 (NEXT_PUBLIC_*) + +| 변수명 | 설명 | Production 값 | Preview 값 | +|--------|------|--------------|------------| +| `NEXT_PUBLIC_API_URL` | 프론트엔드에서 호출하는 API URL | `https://api.codebridge-x.com` | `https://api.codebridge-x.com` | +| `NEXT_PUBLIC_FRONTEND_URL` | 프론트엔드 자체 URL (CORS 용) | `https://dev.codebridge-x.com` | Vercel 자동 할당 URL | +| `NEXT_PUBLIC_AUTH_MODE` | 인증 모드 | `sanctum` | `sanctum` | +| `NEXT_PUBLIC_GOOGLE_MAPS_API_KEY` | Google Maps API 키 | `AIzaSy...` | `AIzaSy...` | +| `NEXT_PUBLIC_DEV_TOOLBAR_ENABLED` | 개발 도구 툴바 | `false` | `true` | + +### 선택 환경변수 + +| 변수명 | 설명 | 기본값 | +|--------|------|--------| +| `NEXT_PUBLIC_API_LOGGING` | API 로깅 활성화 | 미설정 (비활성) | +| `NEXT_PUBLIC_APP_VERSION` | 앱 버전 표시 | 미설정 | +| `PUPPETEER_EXECUTABLE_PATH` | Chromium 경로 (Vercel에서는 불필요) | 미설정 | + +--- + +## 환경별 설정 방법 + +### 1. Vercel Dashboard에서 설정 + +1. [Vercel Dashboard](https://vercel.com) 접속 +2. 프로젝트 선택 → **Settings** → **Environment Variables** +3. 각 변수 추가 시 적용 환경 선택: + - **Production**: 운영 배포에만 적용 + - **Preview**: PR/브랜치 배포에 적용 + - **Development**: `vercel dev` 로컬 실행 시 적용 + +### 2. 환경별 권장 설정 + +#### Production +``` +API_KEY=<실제 운영 API 키> +API_URL=https://api.codebridge-x.com +NEXT_PUBLIC_API_URL=https://api.codebridge-x.com +NEXT_PUBLIC_FRONTEND_URL=https://<운영 도메인> +NEXT_PUBLIC_AUTH_MODE=sanctum +NEXT_PUBLIC_GOOGLE_MAPS_API_KEY= +NEXT_PUBLIC_DEV_TOOLBAR_ENABLED=false +``` + +#### Preview +``` +API_KEY=<개발용 API 키> +API_URL=https://api.codebridge-x.com +NEXT_PUBLIC_API_URL=https://api.codebridge-x.com +NEXT_PUBLIC_FRONTEND_URL= +NEXT_PUBLIC_AUTH_MODE=sanctum +NEXT_PUBLIC_GOOGLE_MAPS_API_KEY= +NEXT_PUBLIC_DEV_TOOLBAR_ENABLED=true +``` + +--- + +## 주의사항 + +1. **API_KEY 보안**: Sensitive 체크박스를 반드시 활성화하여 Dashboard에서도 값이 마스킹되도록 설정 +2. **NEXT_PUBLIC_ 접두사**: 이 접두사가 붙은 변수는 클라이언트 번들에 포함되므로 민감 정보 절대 금지 +3. **VERCEL 환경변수**: `VERCEL=1`은 Vercel이 자동 주입하므로 별도 설정 불필요 (PDF 생성 분기에 사용) +4. **NODE_ENV**: Vercel이 자동 설정 (`production` for Production, `development` for Preview) diff --git a/next.config.ts b/next.config.ts index 73a3226e..8d9d076a 100644 --- a/next.config.ts +++ b/next.config.ts @@ -6,7 +6,7 @@ const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts'); const nextConfig: NextConfig = { reactStrictMode: false, // 🧪 TEST: Strict Mode 비활성화로 중복 요청 테스트 turbopack: {}, // ✅ CRITICAL: Next.js 15 + next-intl compatibility - serverExternalPackages: ['puppeteer'], // puppeteer는 Node.js 전용 - Webpack 번들 제외 + serverExternalPackages: ['puppeteer-core', '@sparticuz/chromium'], // Vercel 서버리스 PDF 생성용 - Webpack 번들 제외 images: { remotePatterns: [ { @@ -21,13 +21,9 @@ const nextConfig: NextConfig = { }, }, typescript: { - // ⚠️ WARNING: This allows production builds to complete even with TypeScript errors - // Only use during development. Remove for production deployments. - ignoreBuildErrors: true, + ignoreBuildErrors: false, }, eslint: { - // ⚠️ WARNING: Temporarily ignore ESLint during builds for migration - // TODO: Fix ESLint errors after migration is complete ignoreDuringBuilds: true, }, // Capacitor 패키지는 모바일 앱 전용 - 웹 빌드에서 제외 diff --git a/package-lock.json b/package-lock.json index d461bd7e..0588dc06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", + "@sparticuz/chromium": "^143.0.4", "@tiptap/extension-image": "^3.13.0", "@tiptap/extension-link": "^3.13.0", "@tiptap/extension-placeholder": "^3.13.0", @@ -48,7 +49,7 @@ "lucide-react": "^0.552.0", "next": "^15.5.9", "next-intl": "^4.4.0", - "puppeteer": "^23.11.1", + "puppeteer-core": "^24.37.2", "react": "^19.2.3", "react-day-picker": "^9.11.1", "react-dom": "^19.2.3", @@ -87,29 +88,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/runtime": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", @@ -1536,18 +1514,17 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.6.1.tgz", - "integrity": "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.12.0.tgz", + "integrity": "sha512-Xuq42yxcQJ54ti8ZHNzF5snFvtpgXzNToJ1bXUGQRaiO8t+B6UM8sTUJfvV+AJnqtkJU/7hdy6nbKyA12aHtRw==", "license": "Apache-2.0", "dependencies": { - "debug": "^4.4.0", + "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", - "semver": "^7.6.3", - "tar-fs": "^3.0.6", - "unbzip2-stream": "^1.4.3", + "semver": "^7.7.3", + "tar-fs": "^3.1.1", "yargs": "^17.7.2" }, "bin": { @@ -2862,6 +2839,19 @@ "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", "license": "MIT" }, + "node_modules/@sparticuz/chromium": { + "version": "143.0.4", + "resolved": "https://registry.npmjs.org/@sparticuz/chromium/-/chromium-143.0.4.tgz", + "integrity": "sha512-/6I7uQTRhRDD2/gGPQ1Gkf+Dqk0RYDACPJDZfSzz0OWk4JmUTonNHPXbrn6UIklOHlnDLf8xAAzkOZKB/cJpLA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "tar-fs": "^3.1.1" + }, + "engines": { + "node": ">=20.11.0" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -4977,26 +4967,6 @@ "node": ">= 0.6.0" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/basic-ftp": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz", @@ -5029,30 +4999,6 @@ "node": ">=8" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -5116,6 +5062,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5192,22 +5139,22 @@ } }, "node_modules/chromium-bidi": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.11.0.tgz", - "integrity": "sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-13.1.1.tgz", + "integrity": "sha512-zB9MpoPd7VJwjowQqiW3FKOvQwffFMjQ8Iejp5ZW+sJaKLRhZX1sTxzl3Zt22TDB4zP0OOqs8lRoY7eAW5geyQ==", "license": "Apache-2.0", "dependencies": { - "mitt": "3.0.1", - "zod": "3.23.8" + "mitt": "^3.0.1", + "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "node_modules/chromium-bidi/node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -5316,32 +5263,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -5700,9 +5621,9 @@ "license": "MIT" }, "node_modules/devtools-protocol": { - "version": "0.0.1367902", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz", - "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==", + "version": "0.0.1566079", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1566079.tgz", + "integrity": "sha512-MJfAEA1UfVhSs7fbSQOG4czavUp1ajfg6prlAN0+cmfa2zNjaIbvq8VneP7do1WAQQIvgNJWSMeP6UyI90gIlQ==", "license": "BSD-3-Clause" }, "node_modules/doctrine": { @@ -5791,24 +5712,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-abstract": { "version": "1.24.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", @@ -6674,6 +6577,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -7078,26 +7001,6 @@ "node": ">= 14" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -7122,6 +7025,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -7213,12 +7117,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, "node_modules/is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", @@ -7655,12 +7553,14 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -7676,12 +7576,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -8047,12 +7941,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" - }, "node_modules/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", @@ -8702,6 +8590,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -8710,24 +8599,6 @@ "node": ">=6" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -9150,40 +9021,19 @@ "node": ">=6" } }, - "node_modules/puppeteer": { - "version": "23.11.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.11.1.tgz", - "integrity": "sha512-53uIX3KR5en8l7Vd8n5DUv90Ae9QDQsyIthaUFVzwV6yU750RjqRznEtNMBT20VthqAdemnJN+hxVdmMHKt7Zw==", - "deprecated": "< 24.15.0 is no longer supported", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "2.6.1", - "chromium-bidi": "0.11.0", - "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1367902", - "puppeteer-core": "23.11.1", - "typed-query-selector": "^2.12.0" - }, - "bin": { - "puppeteer": "lib/cjs/puppeteer/node/cli.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/puppeteer-core": { - "version": "23.11.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.11.1.tgz", - "integrity": "sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==", + "version": "24.37.2", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.37.2.tgz", + "integrity": "sha512-nN8qwE3TGF2vA/+xemPxbesntTuqD9vCGOiZL2uh8HES3pPzLX20MyQjB42dH2rhQ3W3TljZ4ZaKZ0yX/abQuw==", "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.6.1", - "chromium-bidi": "0.11.0", - "debug": "^4.4.0", - "devtools-protocol": "0.0.1367902", + "@puppeteer/browsers": "2.12.0", + "chromium-bidi": "13.1.1", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1566079", "typed-query-selector": "^2.12.0", - "ws": "^8.18.0" + "webdriver-bidi-protocol": "0.4.0", + "ws": "^8.19.0" }, "engines": { "node": ">=18" @@ -9523,6 +9373,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -10277,12 +10128,6 @@ "utrie": "^1.0.2" } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "license": "MIT" - }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -10517,16 +10362,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", - "license": "MIT", - "dependencies": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -10695,6 +10530,12 @@ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "license": "MIT" }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.0.tgz", + "integrity": "sha512-U9VIlNRrq94d1xxR9JrCEAx5Gv/2W7ERSv8oWRoNe/QYbfccS0V3h/H6qeNeCRJxXGMhhnkqvwNrvPAYeuP9VA==", + "license": "Apache-2.0" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 3b342d93..5cab0515 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", + "@sparticuz/chromium": "^143.0.4", "@tiptap/extension-image": "^3.13.0", "@tiptap/extension-link": "^3.13.0", "@tiptap/extension-placeholder": "^3.13.0", @@ -54,7 +55,7 @@ "lucide-react": "^0.552.0", "next": "^15.5.9", "next-intl": "^4.4.0", - "puppeteer": "^23.11.1", + "puppeteer-core": "^24.37.2", "react": "^19.2.3", "react-day-picker": "^9.11.1", "react-dom": "^19.2.3", diff --git a/src/app/[locale]/(protected)/quality/qms/components/InspectionModalV2.tsx b/src/app/[locale]/(protected)/quality/qms/components/InspectionModalV2.tsx index 006638c7..e910d9ef 100644 --- a/src/app/[locale]/(protected)/quality/qms/components/InspectionModalV2.tsx +++ b/src/app/[locale]/(protected)/quality/qms/components/InspectionModalV2.tsx @@ -210,9 +210,9 @@ const createQmsMockWorkOrder = (subType?: string): WorkOrder => ({ shutterCount: 5, department: '생산부', items: [ - { id: '1', no: 1, status: 'completed', productName: '와이어 스크린', floorCode: '1층/FSS-01', specification: '3000×2500', quantity: 2, unit: 'EA' }, - { id: '2', no: 2, status: 'in_progress', productName: '메쉬 스크린', floorCode: '2층/FSS-03', specification: '3000×2500', quantity: 3, unit: 'EA' }, - { id: '3', no: 3, status: 'waiting', productName: '광폭 와이어', floorCode: '3층/FSS-05', specification: '12000×4500', quantity: 1, unit: 'EA' }, + { id: '1', no: 1, status: 'completed', productName: '와이어 스크린', floorCode: '1층/FSS-01', specification: '3000×2500', quantity: 2, unit: 'EA', orderNodeId: null, orderNodeName: '' }, + { id: '2', no: 2, status: 'in_progress', productName: '메쉬 스크린', floorCode: '2층/FSS-03', specification: '3000×2500', quantity: 3, unit: 'EA', orderNodeId: null, orderNodeName: '' }, + { id: '3', no: 3, status: 'waiting', productName: '광폭 와이어', floorCode: '3층/FSS-05', specification: '12000×4500', quantity: 1, unit: 'EA', orderNodeId: null, orderNodeName: '' }, ], currentStep: 2, issues: [], @@ -314,7 +314,7 @@ export const InspectionModalV2 = ({ // 저장된 측정값을 initialValues로 변환 const docData = result.resolveData?.document?.data; if (docData && docData.length > 0) { - const values = parseSavedDataToInitialValues(tmpl, docData); + const values = parseSavedDataToInitialValues(tmpl, docData.map((d: { field_key: string; field_value?: string | null }) => ({ field_key: d.field_key, field_value: d.field_value ?? null }))); setImportInitialValues(values); } else { setImportInitialValues(undefined); diff --git a/src/app/[locale]/(protected)/quality/qms/mockData.ts b/src/app/[locale]/(protected)/quality/qms/mockData.ts index 3ec3cf84..cf6a0e07 100644 --- a/src/app/[locale]/(protected)/quality/qms/mockData.ts +++ b/src/app/[locale]/(protected)/quality/qms/mockData.ts @@ -13,6 +13,7 @@ export const MOCK_WORK_ORDER: WorkOrder = { projectName: '강남 아파트 단지', assignees: ['김작업', '이생산'], quantity: 5, + shutterCount: 3, dueDate: '2024-10-05', priority: 1, status: 'inProgress', diff --git a/src/app/api/pdf/generate/route.ts b/src/app/api/pdf/generate/route.ts index 38238644..edccdf0e 100644 --- a/src/app/api/pdf/generate/route.ts +++ b/src/app/api/pdf/generate/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; -import puppeteer from 'puppeteer'; +import puppeteer from 'puppeteer-core'; +import chromium from '@sparticuz/chromium'; /** * PDF 생성 API @@ -35,17 +36,20 @@ export async function POST(request: NextRequest) { ); } - // Puppeteer 브라우저 실행 (Docker Alpine에서는 시스템 Chromium 사용) + // 로컬 개발 vs Vercel 환경 분기 + const isVercel = process.env.VERCEL === '1'; const browser = await puppeteer.launch({ - headless: true, - executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || undefined, - args: [ + args: isVercel ? chromium.args : [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--disable-software-rasterizer', ], + executablePath: isVercel + ? await chromium.executablePath() + : process.env.PUPPETEER_EXECUTABLE_PATH || '/usr/bin/google-chrome-stable', + headless: true, }); const page = await browser.newPage(); diff --git a/src/components/material/ReceivingManagement/actions.ts b/src/components/material/ReceivingManagement/actions.ts index 4ac08f16..62d265eb 100644 --- a/src/components/material/ReceivingManagement/actions.ts +++ b/src/components/material/ReceivingManagement/actions.ts @@ -1325,10 +1325,10 @@ export async function getInspectionTemplate(params: { }, // 결재선 데이터 approvalLines: [ - { id: 1, role: '작성', sortOrder: 1 }, - { id: 2, role: '검토', sortOrder: 2 }, - { id: 3, role: '승인', sortOrder: 3 }, - { id: 4, role: '승인', sortOrder: 4 }, + { id: 1, name: '', dept: '', role: '작성', sortOrder: 1 }, + { id: 2, name: '', dept: '', role: '검토', sortOrder: 2 }, + { id: 3, name: '', dept: '', role: '승인', sortOrder: 3 }, + { id: 4, name: '', dept: '', role: '승인', sortOrder: 4 }, ], }, inspectionItems: [ diff --git a/src/components/orders/OrderSalesDetailView.tsx b/src/components/orders/OrderSalesDetailView.tsx index 4803d8e9..c643a6a0 100644 --- a/src/components/orders/OrderSalesDetailView.tsx +++ b/src/components/orders/OrderSalesDetailView.tsx @@ -133,16 +133,16 @@ function OrderNodeCard({ node, depth = 0 }: { node: OrderNode; depth?: number }) )} {node.name} - {options.product_name && ( + {options.product_name ? ( - ({options.product_name as string}) + ({String(options.product_name)}) - )} - {(options.open_width || options.open_height) && ( + ) : null} + {(options.open_width || options.open_height) ? ( - {options.open_width as string}x{options.open_height as string}mm + {String(options.open_width ?? '')}x{String(options.open_height ?? '')}mm - )} + ) : null}
diff --git a/src/components/production/WorkOrders/WorkOrderDetail.tsx b/src/components/production/WorkOrders/WorkOrderDetail.tsx index 906154ea..1456b607 100644 --- a/src/components/production/WorkOrders/WorkOrderDetail.tsx +++ b/src/components/production/WorkOrders/WorkOrderDetail.tsx @@ -231,6 +231,8 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) { specification: '8,260 X 8,350 mm', quantity: 500, unit: 'm', + orderNodeId: null, + orderNodeName: '', }, { id: 'mock-2', @@ -241,6 +243,8 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) { specification: '1,200 X 2,400 mm', quantity: 100, unit: 'EA', + orderNodeId: null, + orderNodeName: '', }, ]; } diff --git a/src/components/production/WorkOrders/WorkOrderEdit.tsx b/src/components/production/WorkOrders/WorkOrderEdit.tsx index 9788a28f..9a3da2bb 100644 --- a/src/components/production/WorkOrders/WorkOrderEdit.tsx +++ b/src/components/production/WorkOrders/WorkOrderEdit.tsx @@ -164,6 +164,8 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) { specification: '8,260 X 8,350 mm', quantity: 500, unit: 'm', + orderNodeId: null, + orderNodeName: '', }, { id: 'mock-2', @@ -174,6 +176,8 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) { specification: '1,200 X 2,400 mm', quantity: 100, unit: 'EA', + orderNodeId: null, + orderNodeName: '', }, ]); } else { diff --git a/src/components/production/WorkOrders/documents/InspectionReportModal.tsx b/src/components/production/WorkOrders/documents/InspectionReportModal.tsx index 73b511be..74a1d7a1 100644 --- a/src/components/production/WorkOrders/documents/InspectionReportModal.tsx +++ b/src/components/production/WorkOrders/documents/InspectionReportModal.tsx @@ -96,9 +96,9 @@ export function InspectionReportModal({ shutterCount: 12, department: '생산부', items: [ - { id: '1', no: 1, status: 'in_progress', productName: '와이어 스크린', floorCode: '1층/FSS-01', specification: '8,260 X 8,350', quantity: 2, unit: 'EA' }, - { id: '2', no: 2, status: 'waiting', productName: '메쉬 스크린', floorCode: '2층/FSS-03', specification: '6,400 X 5,200', quantity: 4, unit: 'EA' }, - { id: '3', no: 3, status: 'completed', productName: '광폭 와이어', floorCode: '3층/FSS-05', specification: '12,000 X 4,500', quantity: 1, unit: 'EA' }, + { id: '1', no: 1, status: 'in_progress', productName: '와이어 스크린', floorCode: '1층/FSS-01', specification: '8,260 X 8,350', quantity: 2, unit: 'EA', orderNodeId: null, orderNodeName: '' }, + { id: '2', no: 2, status: 'waiting', productName: '메쉬 스크린', floorCode: '2층/FSS-03', specification: '6,400 X 5,200', quantity: 4, unit: 'EA', orderNodeId: null, orderNodeName: '' }, + { id: '3', no: 3, status: 'completed', productName: '광폭 와이어', floorCode: '3층/FSS-05', specification: '12,000 X 4,500', quantity: 1, unit: 'EA', orderNodeId: null, orderNodeName: '' }, ], currentStep: 2, issues: [], diff --git a/src/components/production/WorkerScreen/WorkLogModal.tsx b/src/components/production/WorkerScreen/WorkLogModal.tsx index 331cff06..9f45474c 100644 --- a/src/components/production/WorkerScreen/WorkLogModal.tsx +++ b/src/components/production/WorkerScreen/WorkLogModal.tsx @@ -60,9 +60,9 @@ export function WorkLogModal({ open, onOpenChange, workOrderId, processType }: W shutterCount: 12, department: '생산부', items: [ - { id: '1', no: 1, status: 'in_progress', productName: '와이어 스크린', floorCode: '1층/FSS-01', specification: '8,260 X 8,350', quantity: 2, unit: 'EA' }, - { id: '2', no: 2, status: 'waiting', productName: '메쉬 스크린', floorCode: '2층/FSS-03', specification: '6,400 X 5,200', quantity: 4, unit: 'EA' }, - { id: '3', no: 3, status: 'completed', productName: '광폭 와이어', floorCode: '3층/FSS-05', specification: '12,000 X 4,500', quantity: 1, unit: 'EA' }, + { id: '1', no: 1, status: 'in_progress', productName: '와이어 스크린', floorCode: '1층/FSS-01', specification: '8,260 X 8,350', quantity: 2, unit: 'EA', orderNodeId: null, orderNodeName: '' }, + { id: '2', no: 2, status: 'waiting', productName: '메쉬 스크린', floorCode: '2층/FSS-03', specification: '6,400 X 5,200', quantity: 4, unit: 'EA', orderNodeId: null, orderNodeName: '' }, + { id: '3', no: 3, status: 'completed', productName: '광폭 와이어', floorCode: '3층/FSS-05', specification: '12,000 X 4,500', quantity: 1, unit: 'EA', orderNodeId: null, orderNodeName: '' }, ], currentStep: 2, issues: [], diff --git a/src/components/production/WorkerScreen/index.tsx b/src/components/production/WorkerScreen/index.tsx index 12254ba6..0e72e78e 100644 --- a/src/components/production/WorkerScreen/index.tsx +++ b/src/components/production/WorkerScreen/index.tsx @@ -697,6 +697,7 @@ export default function WorkerScreen() { projectName: '-', assignees: [], quantity: mockItem.quantity, + shutterCount: 0, dueDate: '', priority: 5, status: 'waiting', @@ -823,6 +824,7 @@ export default function WorkerScreen() { projectName: '-', assignees: [], quantity: mockItem.quantity, + shutterCount: 0, dueDate: '', priority: 5, status: 'waiting', @@ -848,6 +850,7 @@ export default function WorkerScreen() { projectName: '-', assignees: [], quantity: item.quantity, + shutterCount: 0, dueDate: '', priority: 5, status: 'waiting', diff --git a/src/components/quotes/LocationDetailPanel.tsx b/src/components/quotes/LocationDetailPanel.tsx index 5f7a0ef8..aadbde44 100644 --- a/src/components/quotes/LocationDetailPanel.tsx +++ b/src/components/quotes/LocationDetailPanel.tsx @@ -154,9 +154,10 @@ export function LocationDetailPanel({ Object.entries(subtotals).forEach(([key, value]) => { if (typeof value === "object" && value !== null) { + const obj = value as { name?: string }; tabs.push({ value: key, - label: value.name || key, + label: obj.name || key, }); } }); diff --git a/src/components/quotes/QuotePreviewContent.tsx b/src/components/quotes/QuotePreviewContent.tsx index d5c4ad67..a3318141 100644 --- a/src/components/quotes/QuotePreviewContent.tsx +++ b/src/components/quotes/QuotePreviewContent.tsx @@ -10,6 +10,7 @@ import React from 'react'; import type { QuoteFormDataV2 } from './QuoteRegistrationV2'; +import type { BomCalculationResultItem } from './types'; // 양식 타입 type TemplateType = 'vendor' | 'calculation'; @@ -327,7 +328,7 @@ export function QuotePreviewContent({ {/* BOM 품목 상세 */} {bomItems.length > 0 ? ( - bomItems.map((item, itemIndex) => ( + bomItems.map((item: BomCalculationResultItem, itemIndex: number) => ( {itemIndex === 0 ? locationSymbol : ''} diff --git a/src/components/quotes/QuoteSummaryPanel.tsx b/src/components/quotes/QuoteSummaryPanel.tsx index ce50a5ca..e641e0f6 100644 --- a/src/components/quotes/QuoteSummaryPanel.tsx +++ b/src/components/quotes/QuoteSummaryPanel.tsx @@ -108,10 +108,11 @@ export function QuoteSummaryPanel({ unitPrice: item.unit_price || 0, totalPrice: item.total_price || 0, })); + const obj = value as { name?: string; count?: number; subtotal?: number }; result.push({ - label: value.name || key, - count: value.count || 0, - amount: value.subtotal || 0, + label: obj.name || key, + count: obj.count || 0, + amount: obj.subtotal || 0, items: groupItems, }); } else if (typeof value === "number") { diff --git a/src/components/quotes/actions.ts b/src/components/quotes/actions.ts index 9e199b20..0ccdd67b 100644 --- a/src/components/quotes/actions.ts +++ b/src/components/quotes/actions.ts @@ -32,6 +32,7 @@ import type { BomCalculationResultItem, BomCalculationResult, } from './types'; +export type { BomCalculationResult, BomCalculationResultItem }; import { transformApiToFrontend, transformFrontendToApi } from './types'; // ===== 페이지네이션 타입 ===== diff --git a/vercel.json b/vercel.json new file mode 100644 index 00000000..e62f2c3e --- /dev/null +++ b/vercel.json @@ -0,0 +1,9 @@ +{ + "regions": ["icn1"], + "functions": { + "src/app/api/pdf/generate/route.ts": { + "memory": 1024, + "maxDuration": 30 + } + } +}