From 6f457b28f3d7282735d529f3a8a97cd9e4016f2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EB=B3=91=EC=B2=A0?= Date: Tue, 20 Jan 2026 11:34:59 +0900 Subject: [PATCH] =?UTF-8?q?refactor(WEB):=20=ED=92=88=EB=AA=A9=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EA=B2=BD=EB=A1=9C=20=ED=86=B5=ED=95=A9=20-=20/item?= =?UTF-8?q?s=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20/production/screen-product?= =?UTF-8?q?ion=EC=9C=BC=EB=A1=9C=20=EC=9D=BC=EC=9B=90=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /items 폴더 삭제 (중복 경로 제거) - /production/screen-production에 신버전 DynamicItemForm 기반 페이지 적용 - 구버전 ItemForm 연결 제거로 등록/수정 오류 해결 - 컴포넌트 내부 경로 참조 /items → /production/screen-production 변경 - ItemListClient, ItemForm, ItemDetailClient, ItemDetailEdit, DynamicItemForm Co-Authored-By: Claude Opus 4 --- ...L] integrated-detail-template-checklist.md | 1331 +++-------------- .../(protected)/items/[id]/edit/page.tsx | 32 - .../[locale]/(protected)/items/[id]/page.tsx | 34 - .../(protected)/items/create/page.tsx | 101 -- src/app/[locale]/(protected)/items/page.tsx | 24 - .../screen-production/[id]/edit/page.tsx | 221 +-- .../screen-production/[id]/page.tsx | 193 +-- .../screen-production/create/page.tsx | 95 +- .../production/screen-production/page.tsx | 18 +- .../items/DynamicItemForm/index.tsx | 4 +- src/components/items/ItemDetailClient.tsx | 2 +- src/components/items/ItemDetailEdit.tsx | 4 +- src/components/items/ItemForm/index.tsx | 2 +- src/components/items/ItemListClient.tsx | 6 +- 14 files changed, 333 insertions(+), 1734 deletions(-) delete mode 100644 src/app/[locale]/(protected)/items/[id]/edit/page.tsx delete mode 100644 src/app/[locale]/(protected)/items/[id]/page.tsx delete mode 100644 src/app/[locale]/(protected)/items/create/page.tsx delete mode 100644 src/app/[locale]/(protected)/items/page.tsx diff --git a/claudedocs/[IMPL] integrated-detail-template-checklist.md b/claudedocs/[IMPL] integrated-detail-template-checklist.md index 1320e6fc..608205f4 100644 --- a/claudedocs/[IMPL] integrated-detail-template-checklist.md +++ b/claudedocs/[IMPL] integrated-detail-template-checklist.md @@ -1,1064 +1,218 @@ -# IntegratedDetailTemplate 통합 구현 체크리스트 +# V2 URL 패턴 마이그레이션 최종 현황 > 브랜치: `feature/universal-detail-component` -> 작성일: 2026-01-17 -> 최종 수정: 2026-01-19 (v26 - Phase 5 완료: 5개 V2 URL 패턴 통합) +> 최종 수정: 2026-01-20 (v27 - 문서 정리) --- -## 작업 범위 개요 - -### 두 가지 병행 작업 +## 📌 V2 URL 패턴이란? ``` -┌─────────────────────────────────────────────────────┐ -│ Work A: IntegratedDetailTemplate 양산 마이그레이션 │ -│ (감싸는 껍데기 - 페이지 레벨) │ -│ ┌───────────────────────────────────────────────┐ │ -│ │ PageLayout + PageHeader + Card │ │ -│ │ ┌─────────────────────────────────────────┐ │ │ -│ │ │ Work B: 내부 컴포넌트 공통화 │ │ │ -│ │ │ (DetailSection, DetailGrid 등) │ │ │ -│ │ │ - 섹션 구조 │ │ │ -│ │ │ - 그리드 레이아웃 │ │ │ -│ │ │ - 필드 배치 │ │ │ -│ │ └─────────────────────────────────────────┘ │ │ -│ │ 버튼 영역 (목록/삭제/수정/저장) │ │ -│ └───────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────┘ +기존: /[id] (조회) + /[id]/edit (수정) → 별도 페이지 +V2: /[id]?mode=view (조회) + /[id]?mode=edit (수정) → 단일 페이지 ``` -| 작업 | 설명 | 상태 | -|------|------|------| -| **Work A** | IntegratedDetailTemplate 양산 마이그레이션 | ✅ Phase 1 완료 (계좌/카드) | -| **Work B** | 상세 페이지 내부 컴포넌트 공통화 | ✅ 완료 (B.2.4 이슈 해결) | +**핵심**: `searchParams.get('mode')` 로 view/edit 분기 --- -## 📊 47개 상세 페이지 전체 분석 (v8) +## 📊 최종 현황 표 -### 페이지 분류 기준 +### 통계 요약 -| 분류 | 설명 | 적용 가능 여부 | -|------|------|---------------| -| **A. 단순 CRUD** | 표준 등록/조회/수정 패턴 | ✅ IntegratedDetailTemplate 바로 적용 | -| **B. 중간 복잡도** | 섹션 구조, 일부 커스텀 필요 | ⚠️ 커스텀 섹션으로 적용 가능 | -| **C. 복잡 구조** | 칸반, 탭, 트리, 다중 테이블 | ❌ 제외 (독자 구조 유지) | -| **D. 모달 형태** | Dialog 기반 등록/상세 | ❌ 제외 | -| **E. 조회 전용** | 수정 기능 없음 | ⚠️ view 모드만 사용 | - ---- - -### 도메인별 상세 분석 - -#### 🏦 회계 (Accounting) - 8개 - -| 페이지 | 경로 | 분류 | 특이사항 | Phase | -|--------|------|------|----------|-------| -| 거래처 | `/accounting/vendors/[id]` | B | 30+ 필드, 7 섹션, 이미지 업로드 | Phase 3 | -| 거래처원장 | `/accounting/vendor-ledger/[id]` | B | 원장/거래내역 탭 구조 | Phase 3 | -| 매출 | `/accounting/sales/[id]` | B | **품목 테이블 포함**, 세금계산서/거래명세서 섹션 | Phase 3 (재분류) | -| 입금 | `/accounting/deposits/[id]` | A | ✅ 완료, 상단 버튼 | Phase 2 ✅ | -| 세금계산서 | `/accounting/bills/[id]` | B | **차수 관리 테이블 포함** | Phase 3 (재분류) | -| 출금 | `/accounting/withdrawals/[id]` | A | ✅ 완료, 상단 버튼 | Phase 2 ✅ | -| 매입 | `/accounting/purchase/[id]` | B | **품목 테이블 + 문서 모달 포함** | Phase 3 (재분류) | -| 대손추심 | `/accounting/bad-debt-collection/[id]` | B | 추심 상태 추적 | Phase 3 | - -**소계**: 2개 완료 (입금, 출금), 6개 커스텀 필요 (Phase 3) - ---- - -#### 🏗️ 건설 (Construction) - 16개 - -| 페이지 | 경로 | 분류 | 특이사항 | Phase | -|--------|------|------|----------|-------| -| 수주관리 | `/construction/order-management/[id]` | C | 계약, 결제, 다단계 승인 | 제외 | -| 현장관리 | `/construction/site-management/[id]` | B | 현장 정보 + 상태 | Phase 3 | -| 실행내역 | `/construction/structure-review/[id]` | B | 계층 구조 데이터 | Phase 3 | -| 품목관리(건설) | `/construction/base-info/items/[id]` | B | 품목 상세 | Phase 3 | -| 단가관리 | `/construction/base-info/pricing/[id]` | A | 단순 단가 정보 | Phase 2 | -| 노무관리 | `/construction/base-info/labor/[id]` | A | 단순 노무 정보 | Phase 2 | -| 계약서 | `/construction/contract/[id]` | C | 계약 문서, 승인 플로우 | 제외 | -| 인수인계서 | `/construction/handover-report/[id]` | C | 문서 생성, 프린트 | 제외 | -| 현장종합현황 | `/construction/project/management/[id]` | C | 칸반 보드 | 제외 | -| 이슈관리 | `/construction/issue-management/[id]` | B | 이슈 상세 + 댓글 | Phase 3 | -| 입찰관리 | `/construction/bidding/[id]` | B | 입찰 정보 + 첨부파일 | Phase 3 | -| 현장설명회 | `/construction/site-briefings/[id]` | B | 설명회 정보 | Phase 3 | -| 견적서 | `/construction/estimates/[id]` | B | 견적 상세 + 품목 | Phase 3 | -| 협력업체 | `/construction/partners/[id]` | B | 업체 정보 | Phase 3 | -| 시공관리 | `/construction/construction-management/[id]` | B | 시공 상세 | Phase 3 | -| 기성관리 | `/construction/progress-billing/[id]` | B | 기성 내역 | Phase 3 | - -**소계**: 2개 적용 가능 (Phase 2), 10개 커스텀 필요 (Phase 3), 4개 제외 - ---- - -#### 💼 판매 (Sales) - 6개 - -| 페이지 | 경로 | 분류 | 특이사항 | Phase | -|--------|------|------|----------|-------| -| 거래처(영업) | `/sales/client-management-sales-admin/[id]` | A | 하단 버튼 | Phase 2 | -| 견적관리 | `/sales/quote-management/[id]` | B | 견적서 출력, 품목 테이블 | Phase 3 | -| 견적(테스트) | `/sales/quote-management/test/[id]` | B | V2 UI 테스트, 실제 API 연동 예정 | Phase 3 ✅ | -| 수주관리 | `/sales/order-management/[id]` | B | 주문 추적 | Phase 3 | -| 생산의뢰 | `/sales/production-orders/[id]` | B | 의뢰 상세 | Phase 3 | -| 단가관리 | `/sales/pricing-management/[id]` | B | 단가 테이블 | Phase 3 | - -**소계**: 1개 적용 가능 (Phase 2), 5개 커스텀 필요 (Phase 3) - 견적(테스트) 포함 - ---- - -#### 👥 인사 (HR) - 2개 - -| 페이지 | 경로 | 분류 | 특이사항 | Phase | -|--------|------|------|----------|-------| -| 카드관리 | `/hr/card-management/[id]` | A | ✅ 완료 | Phase 1 | -| 사원관리 | `/hr/employee-management/[id]` | C | 40+ 필드, 탭 구조 | 제외 | - -**소계**: 1개 완료, 1개 제외 - ---- - -#### 🏭 생산 (Production) - 2개 - -| 페이지 | 경로 | 분류 | 특이사항 | Phase | -|--------|------|------|----------|-------| -| 작업지시 | `/production/work-orders/[id]` | C | 칸반, 공정 단계, 동적 테이블 | 제외 | -| 스크린생산 | `/production/screen-production/[id]` | B | 생산 상세 | Phase 3 | - -**소계**: 1개 커스텀 필요 (Phase 3), 1개 제외 - ---- - -#### 🔍 품질 (Quality) - 1개 - -| 페이지 | 경로 | 분류 | 특이사항 | Phase | -|--------|------|------|----------|-------| -| 검수관리 | `/quality/inspections/[id]` | C | 동적 계산, 측정값 입력 | 제외 | - -**소계**: 1개 제외 - ---- - -#### 📦 출고 (Outbound) - 1개 - -| 페이지 | 경로 | 분류 | 특이사항 | Phase | -|--------|------|------|----------|-------| -| 출하관리 | `/outbound/shipments/[id]` | C | 3종 문서 출력, 상태별 UI | 제외 | - -**소계**: 1개 제외 - ---- - -#### 📥 자재 (Material) - 2개 - -| 페이지 | 경로 | 분류 | 특이사항 | Phase | -|--------|------|------|----------|-------| -| 재고현황 | `/material/stock-status/[id]` | B | **LOT 테이블 포함**, 조회 전용 복합 UI | Phase 3 (재분류) | -| 입고관리 | `/material/receiving-management/[id]` | B | **복잡한 워크플로우**, 다중 다이얼로그 | Phase 3 (재분류) | - -**소계**: 2개 커스텀 필요 (Phase 3) - ---- - -#### 📞 고객센터 (Customer Center) - 3개 - -| 페이지 | 경로 | 분류 | 특이사항 | Phase | -|--------|------|------|----------|-------| -| 공지사항 | `/customer-center/notices/[id]` | E | 조회 전용 | Phase 2 | -| 이벤트 | `/customer-center/events/[id]` | E | 조회 전용 | Phase 2 | -| Q&A | `/customer-center/qna/[id]` | B | 스레드 답변 구조 | Phase 3 | - -**소계**: 2개 조회전용 (Phase 2), 1개 커스텀 필요 (Phase 3) - ---- - -#### 📋 게시판 (Board) - 1개 - -| 페이지 | 경로 | 분류 | 특이사항 | Phase | -|--------|------|------|----------|-------| -| 게시판관리 | `/board/board-management/[id]` | A | 단순 CRUD | Phase 2 | - -**소계**: 1개 적용 가능 (Phase 2) - ---- - -#### ⚙️ 설정 (Settings) - 3개 - -| 페이지 | 경로 | 분류 | 특이사항 | Phase | -|--------|------|------|----------|-------| -| 계좌관리 | `/settings/accounts/[id]` | A | ✅ 완료 | Phase 1 | -| 팝업관리 | `/settings/popup-management/[id]` | A | RichTextEditor | Phase 2 | -| 권한관리 | `/settings/permissions/[id]` | C | Matrix UI | 제외 | - -**소계**: 1개 완료, 1개 적용 가능 (Phase 2), 1개 제외 - ---- - -#### 🔧 기준정보 (Master Data) - 1개 - -| 페이지 | 경로 | 분류 | 특이사항 | Phase | -|--------|------|------|----------|-------| -| 공정관리 | `/master-data/process-management/[id]` | A | RuleModal 포함 | Phase 2 | - -**소계**: 1개 적용 가능 (Phase 2) - ---- - -#### 📦 품목 (Items) - 1개 - -| 페이지 | 경로 | 분류 | 특이사항 | Phase | -|--------|------|------|----------|-------| -| 품목관리 | `/items/[id]` | D | DynamicItemForm | 제외 | - -**소계**: 1개 제외 (DynamicForm 사용) - ---- - -## 📋 리스트 페이지 vs 상세 페이지 차이 설명 - -> test-urls 리스트 페이지 수가 상세([id]) 페이지 수보다 많은 이유 - -### 상세 페이지가 없는 경우 - -#### 1. 모달 형태 (별도 [id] 페이지 없음) -리스트에서 클릭 시 **Dialog 모달**로 상세/등록/수정 처리 - -| 리스트 페이지 | 상세 형태 | -|-------------|----------| -| 직급관리 `/settings/ranks` | 모달 | -| 직책관리 `/settings/titles` | 모달 | -| 근태관리 `/hr/attendance-management` | 모달 | -| 휴가관리 `/hr/vacation-management` | 모달 | -| 부서관리 `/hr/department-management` | 트리 + 모달 | - -#### 2. 조회 전용 / 보고서 형태 -등록/수정 기능 없이 **데이터 조회만** 하는 페이지 - -| 페이지 | 특징 | -|-------|------| -| 일일 일보 | 보고서 | -| 지출 예상 내역서 | 보고서 | -| 미수금 현황 | 보고서 | -| 입출금 계좌조회 | 조회 전용 | -| 카드 내역 조회 | 조회 전용 | -| 종합 경영 분석 | 대시보드 | - -#### 3. 설정 페이지 (단일 페이지) -[id]가 아닌 **설정 폼 하나**로 구성 - -| 페이지 | 특징 | -|-------|------| -| 휴가정책 `/settings/leave-policy` | 설정 폼 | -| 근무일정 `/settings/work-schedule` | 설정 폼 | -| 출퇴근관리 `/settings/attendance-settings` | 설정 폼 | -| 알림설정 `/settings/notification-settings` | 설정 폼 | -| 계정정보, 회사정보 | 프로필 형태 | - -#### 4. 특수 구조 -| 페이지 | 특징 | -|-------|------| -| 전자결재 (기안/결재/참조함) | 워크플로우 구조 | -| 대시보드 | 상세 개념 없음 | - -### 결론 -``` -IntegratedDetailTemplate 적용 대상 = 실제 [id] 폴더가 있는 페이지 기반 상세 -= 47개 (모달/보고서/설정 페이지 제외) -``` - ---- - -## 📈 통계 요약 - -### 전체 현황 - -| 분류 | 개수 | 비율 | -|------|------|------| -| **A. 단순 CRUD** | 16개 | 34% | -| **B. 중간 복잡도** | 17개 | 36% | -| **C. 복잡 구조** | 11개 | 23% | -| **D. DynamicForm** | 1개 | 2% | -| **E. 조회 전용** | 2개 | 4% | -| **합계** | **47개** | 100% | - -### Phase별 분류 - -| Phase | 상태 | 대상 페이지 수 | 설명 | -|-------|------|---------------|------| -| **Phase 1** | ✅ 완료 | 2개 | 계좌관리, 카드관리 | -| **Phase 2** | ✅ 완료 | 4개 | 노무, 단가, 입금, 출금 | -| **Phase 3** | 🔄 대기 | 27개 | 중간 복잡도 (별도 라우팅 4개 추가) | -| **마이그레이션 불필요** | ❌ | 2개 | View only (공지사항, 이벤트) | -| **제외** | ❌ | 12개 | 복잡 구조, DynamicForm | -| **합계** | - | **47개** | - | - -> **v14 재분류**: 매출, 세금계산서, 매입, 재고현황, 입고관리 → Phase 3 이동 (내부 테이블/복잡 워크플로우) -> **v15 재분류**: 거래처(영업), 팝업관리, 공정관리, 게시판관리 → Phase 3 이동 (별도 라우팅 구조) -> **v15 마이그레이션 불필요**: 공지사항, 이벤트 (View only) - ---- - -## Work B: 내부 컴포넌트 공통화 (✅ 완료) - -### B.1 컴포넌트 설계 - -#### B.1.1 컴포넌트 구조 - -``` -src/components/templates/IntegratedDetailTemplate/ -├── index.tsx # 메인 템플릿 -├── types.ts # 타입 정의 -├── FieldInput.tsx # ✅ 순수 입력 컴포넌트 (FieldRenderer에서 리팩토링) -├── components/ -│ ├── DetailSection.tsx # ✅ 섹션 wrapper -│ ├── DetailGrid.tsx # ✅ 반응형 그리드 -│ ├── DetailField.tsx # ✅ 필드 레이아웃 (라벨, required, error, description) -│ ├── DetailActions.tsx # ✅ 액션 버튼 영역 (view/edit/create 모드별 버튼) -│ ├── index.ts # ✅ 컴포넌트 export -│ └── skeletons/ # ✅ 스켈레톤 컴포넌트 -│ ├── index.ts -│ ├── DetailSectionSkeleton.tsx -│ ├── DetailGridSkeleton.tsx -│ └── DetailFieldSkeleton.tsx -└── hooks/ - └── useDetailForm.ts # 폼 상태 관리 -``` - -#### B.1.2 컴포넌트 인터페이스 - -```typescript -// DetailSection - 섹션 wrapper -interface DetailSectionProps { - title: string; - description?: string; - children: ReactNode; - collapsible?: boolean; - defaultOpen?: boolean; -} - -// DetailGrid - 반응형 그리드 -interface DetailGridProps { - cols?: 1 | 2 | 3 | 4; // default: 2 - gap?: 'sm' | 'md' | 'lg'; // default: 'md' - children: ReactNode; -} - -// DetailField - 필드 레이아웃 -interface DetailFieldProps { - label: string; - required?: boolean; - error?: string; - description?: string; - colSpan?: 1 | 2 | 3 | 4; // 그리드 칸 수 - children: ReactNode; - mode?: 'view' | 'edit' | 'create'; // view 모드에서 필수마크/에러 숨김 -} - -// DetailActions - 버튼 영역 -interface DetailActionsProps { - mode: 'view' | 'edit' | 'create'; - isSubmitting?: boolean; - permissions?: { canEdit?: boolean; canDelete?: boolean; }; - showButtons?: { back?: boolean; delete?: boolean; edit?: boolean; }; - labels?: { back?: string; cancel?: string; delete?: string; edit?: string; submit?: string; }; - onBack?: () => void; - onCancel?: () => void; - onDelete?: () => void; - onEdit?: () => void; - onSubmit?: () => void; - extraActions?: ReactNode; -} -``` - -### B.2 구현 체크리스트 - -#### B.2.1 컴포넌트 구현 -- [x] `DetailSection.tsx` 구현 ✅ 2026-01-19 -- [x] `DetailGrid.tsx` 구현 ✅ 2026-01-19 -- [x] `DetailField.tsx` 구현 ✅ 2026-01-19 -- [x] **스켈레톤 컴포넌트** 구현 ✅ 2026-01-19 -- [x] **index.ts export 정리** ✅ 2026-01-19 - -#### B.2.2 기존 페이지 적용 (검증) -- [x] **IntegratedDetailTemplate 리팩토링** ✅ 2026-01-19 -- [x] **계좌관리** 동작 검증 ✅ 2026-01-19 -- [x] **카드관리** 동작 검증 ✅ 2026-01-19 - -#### B.2.3 패턴 확정 -- [ ] 공통 패턴 문서화 -- [ ] 예외 케이스 정리 -- [ ] 양산 적용 가이드 작성 - -#### B.2.4 ✅ DetailField 미적용 이슈 해결 완료 - -**해결된 구조:** -``` -IntegratedDetailTemplate - └── DetailSection ✅ - └── DetailGrid ✅ - └── DetailField ✅ (라벨, required, error, description, colSpan, mode) - └── FieldInput ✅ (순수 입력 컴포넌트만) -``` - ---- - -## 🎨 ErrorCard 공통 컴포넌트 (v16 신규) - -> 에러 페이지 UI 통일을 위한 재사용 가능한 에러 카드 컴포넌트 - -### 위치 -``` -src/components/ui/error-card.tsx -``` - -### 지원 에러 타입 - -| 타입 | 아이콘 | 색상 | 기본 제목 | 용도 | -|------|--------|------|----------|------| -| `not-found` | SearchX | Yellow/Orange | 페이지를 찾을 수 없습니다 | 404, 데이터 없음 | -| `network` | ServerCrash | Orange/Red | 데이터를 불러올 수 없습니다 | API 오류, 네트워크 실패 | -| `error` | AlertCircle | Red/Pink | 오류가 발생했습니다 | 일반 에러 | - -### 인터페이스 - -```typescript -interface ErrorCardProps { - type?: 'not-found' | 'network' | 'error'; // default: 'not-found' - title?: string; // 커스텀 제목 - description?: string; // 커스텀 설명 - tips?: string[]; // 안내 메시지 목록 - showBackButton?: boolean; // 이전 페이지 버튼 (default: true) - showHomeButton?: boolean; // 목록으로 이동 버튼 (default: true) - backButtonLabel?: string; // 이전 버튼 라벨 (default: '이전 페이지') - homeButtonLabel?: string; // 홈 버튼 라벨 (default: '목록으로 이동') - homeButtonHref?: string; // 홈 버튼 이동 경로 - onBack?: () => void; // 커스텀 뒤로가기 핸들러 -} -``` - -### 사용 예시 - -```tsx -import { ErrorCard } from '@/components/ui/error-card'; - -// 네트워크 에러 - - -// 데이터 없음 - -``` - -### 적용된 V2 컴포넌트 - -| 컴포넌트 | 파일 | 적용 에러 타입 | -|----------|------|---------------| -| ProcessDetailClientV2 | `/src/components/process-management/ProcessDetailClientV2.tsx` | network, not-found | -| BoardDetailClientV2 | `/src/components/board/BoardManagement/BoardDetailClientV2.tsx` | network, not-found | - ---- - -## 🚨 ServerErrorPage 필수 적용 (v22 신규) - -> **V2 마이그레이션 필수 요구사항**: 모든 V2 페이지는 에러 발생 시 `ServerErrorPage` 컴포넌트를 사용해야 합니다. - -### 위치 -``` -src/components/common/ServerErrorPage.tsx -``` - -### 적용 시점 - -| 상황 | 사용 컴포넌트 | -|------|--------------| -| API 호출 실패 | `ServerErrorPage` | -| 데이터 로딩 실패 | `ServerErrorPage` | -| 서버 에러 (500 등) | `ServerErrorPage` | -| 데이터 없음 (Not Found) | `ErrorCard` (기존) | -| 네트워크 연결 실패 | `ErrorCard` (기존) | - -### ❌ 잘못된 패턴 (기본 div 사용 - 마이그레이션 누락) - -```tsx -// ❌ 이런 형태는 ServerErrorPage 미적용 -if (error) { - return ( -
-
{error}
- -
- ); -} -``` - -### ✅ 올바른 패턴 (ServerErrorPage 사용) - -```tsx -import { ServerErrorPage } from '@/components/common/ServerErrorPage'; - -// ✅ ServerErrorPage 컴포넌트 사용 -if (error) { - return ( - window.location.reload()} - showBackButton={true} - showHomeButton={true} - /> - ); -} -``` - -### ServerErrorPage Props - -```typescript -interface ServerErrorPageProps { - title?: string; // 에러 제목 (기본: "서버 오류가 발생했습니다") - message?: string; // 에러 메시지 - errorCode?: string | number; // 에러 코드 (500, 503 등) - onRetry?: () => void; // 재시도 버튼 클릭 핸들러 - showBackButton?: boolean; // 뒤로가기 버튼 표시 (기본: true) - showHomeButton?: boolean; // 홈으로 버튼 표시 (기본: true) - showContactInfo?: boolean; // 연락처 정보 표시 (기본: false) - contactEmail?: string; // 연락처 이메일 -} -``` - -### V2 마이그레이션 체크리스트 추가 항목 - -- [ ] 에러 상태에서 `ServerErrorPage` 컴포넌트 사용 여부 확인 -- [ ] 기본 div로 에러 표시하는 페이지 → `ServerErrorPage`로 교체 필요 -- [ ] `onRetry` 핸들러 구현 (페이지 새로고침 또는 데이터 재요청) - -### 미적용 페이지 식별 방법 - -Chrome DevTools로 페이지 접근 시 아래와 같은 기본 에러 UI가 표시되면 `ServerErrorPage` 미적용: -- 단순 텍스트 + "뒤로 가기" 링크만 있는 경우 -- 아이콘 없이 텍스트만 표시되는 경우 -- 재시도/홈으로 버튼이 없는 경우 - ---- - -## Work A: IntegratedDetailTemplate 양산 마이그레이션 - -### A.0 현재 상태 - -| 모듈 | 상태 | 비고 | -|------|------|------| -| 계좌관리 (accounts) | ✅ 완료 | Phase 1 | -| 카드관리 (card-management) | ✅ 완료 | Phase 1 | - ---- - -### A.1 Phase 2 대상 (단순 CRUD) - 10개 (4개 완료) - -> **buttonPosition 확장 필요**: 회계 도메인 대부분 상단 버튼 - -#### ✅ 완료된 페이지 (4개) - -| # | 모듈 | URL 패턴 | 완료일 | 비고 | -|---|------|----------|--------|------| -| 1 | 입금 | `/accounting/deposits/[id]`, `/new` | 2026-01-19 | ✅ V2 마이그레이션 완료 | -| 2 | 출금 | `/accounting/withdrawals/[id]`, `/new` | 2026-01-19 | ✅ V2 마이그레이션 완료 | -| 3 | 노무관리 | `/construction/base-info/labor/[id]`, `/new` | 2026-01-19 | ✅ V2 마이그레이션 완료 | -| 4 | 단가관리 | `/construction/base-info/pricing/[id]`, `/new` | 2026-01-19 | ✅ V2 마이그레이션 완료 | - -#### ✅ Phase 3 라우팅 구조 변경 완료 (14개) - v21 - -> **라우팅 구조 변경**: `/[id]`, `/[id]/edit`, `/new` → `/[id]?mode=view|edit`, `/new` -> **V2 래퍼 패턴**: 기존 컴포넌트(Detail/Form) 활용, 라우팅만 통합 - -| 모듈 | URL 패턴 | 완료일 | 비고 | -|------|----------|--------|------| -| 거래처(영업) | `/sales/client-management-sales-admin/[id]` | 2026-01-19 | ✅ ClientDetailClientV2 생성 | -| 팝업관리 | `/settings/popup-management/[id]` | 2026-01-19 | ✅ PopupDetailClientV2 생성 (IntegratedDetailTemplate 활용) | -| 공정관리 | `/master-data/process-management/[id]` | 2026-01-19 | ✅ ProcessDetailClientV2 생성 (기존 컴포넌트 래핑) | -| 게시판관리 | `/board/board-management/[id]` | 2026-01-19 | ✅ BoardDetailClientV2 생성 (기존 컴포넌트 래핑) | -| 대손추심 | `/accounting/bad-debt-collection/[id]` | 2026-01-19 | ✅ BadDebtDetailClientV2 생성 (기존 컴포넌트 래핑) | -| Q&A | `/customer-center/qna/[id]` | 2026-01-19 | ✅ InquiryDetailClientV2 생성 (기존 Detail/Form 컴포넌트 래핑) | -| 현장관리 | `/construction/site-management/[id]` | 2026-01-19 | ✅ SiteDetailClientV2 생성 (기존 컴포넌트 래핑) | -| 실행내역 | `/construction/order/structure-review/[id]` | 2026-01-19 | ✅ StructureReviewDetailClientV2 생성 (기존 컴포넌트 래핑) | -| 견적관리 | `/sales/quote-management/[id]` | 2026-01-19 | ✅ 기존 page.tsx에 mode 체크 추가 (V2 패턴 적용) | -| 견적(테스트) | `/sales/quote-management/test/[id]` | 2026-01-19 | ✅ 기존 page.tsx에 mode 체크 추가 (실제 API 연동 예정) | -| 입찰관리 | `/construction/project/bidding/[id]` | 2026-01-19 | ✅ 기존 page.tsx에 mode 체크 추가 | -| 이슈관리 | `/construction/project/issue-management/[id]` | 2026-01-19 | ✅ 기존 page.tsx에 mode 체크 추가 | -| 현장설명회 | `/construction/project/bidding/site-briefings/[id]` | 2026-01-19 | ✅ 기존 page.tsx에 mode 체크 추가 | -| 견적서(건설) | `/construction/project/bidding/estimates/[id]` | 2026-01-19 | ✅ 기존 page.tsx에 mode 체크 추가 | - -#### ❌ 마이그레이션 불필요 (2개) - v15 최종 분석 - -> **사유**: View only 페이지, 별도 Detail 컴포넌트 사용 중, IntegratedDetailTemplate의 이점(view/edit/create 통합) 없음 - -| 모듈 | URL 패턴 | 현재 구조 | -|------|----------|-----------| -| 공지사항 | `/customer-center/notices/[id]` | NoticeDetail 컴포넌트, View only | -| 이벤트 | `/customer-center/events/[id]` | EventDetail 컴포넌트, View only | - -#### ⚠️ Phase 3으로 재분류된 페이지 (5개) - -> **재분류 사유**: 내부 테이블/복잡 워크플로우로 IntegratedDetailTemplate 단순 적용 불가 - -| 모듈 | 재분류 사유 | -|------|-------------| -| 매출 | 품목 테이블 (`SaleItem[]`), 세금계산서/거래명세서 발행 섹션 | -| 세금계산서 | 차수 관리 테이블 (`Installment[]`), 복합 구조 | -| 매입 | 품목 테이블 + 문서 모달 (세금계산서/거래명세서) | -| 재고현황 | LOT 테이블, 조회 전용 복합 UI | -| 입고관리 | 다중 다이얼로그, 복잡한 워크플로우 | - ---- - -### A.2 Phase 3 대상 (중간 복잡도) - 23개 - -> **커스텀 섹션/renderForm 활용 필요** - -#### 회계 도메인 (6개) - 3개 추가 (v14 재분류) - -| # | 모듈 | URL 패턴 | 복잡도 요인 | -|---|------|----------|-------------| -| 1 | 거래처 | `/accounting/vendors/[id]` | 30+ 필드, 7 섹션, 이미지 업로드, 우편번호 검색 | -| 2 | 거래처원장 | `/accounting/vendor-ledger/[id]` | 원장 탭 + 거래내역 | -| 3 | 대손추심 | `/accounting/bad-debt-collection/[id]` | 추심 상태 트래킹 | -| 4 | **매출** | `/accounting/sales/[id]` | ⚠️ 품목 테이블, 세금계산서/거래명세서 섹션 (v14) | -| 5 | **세금계산서** | `/accounting/bills/[id]` | ⚠️ 차수 관리 테이블 (v14) | -| 6 | **매입** | `/accounting/purchase/[id]` | ⚠️ 품목 테이블 + 문서 모달 (v14) | - -#### 건설 도메인 (10개) - -| # | 모듈 | URL 패턴 | 복잡도 요인 | -|---|------|----------|-------------| -| 3 | 현장관리 | `/construction/site-management/[id]` | 현장 상태 정보 | -| 4 | 실행내역 | `/construction/structure-review/[id]` | 계층 구조 데이터 | -| 5 | 품목관리(건설) | `/construction/base-info/items/[id]` | 품목 상세 정보 | -| 6 | 이슈관리 | `/construction/issue-management/[id]` | 댓글/히스토리 | -| 7 | 입찰관리 | `/construction/bidding/[id]` | 입찰 정보 + 첨부 | -| 8 | 현장설명회 | `/construction/site-briefings/[id]` | 설명회 정보 | -| 9 | 견적서 | `/construction/estimates/[id]` | 품목 테이블 포함 | -| 10 | 협력업체 | `/construction/partners/[id]` | 업체 정보 | -| 11 | 시공관리 | `/construction/construction-management/[id]` | 시공 상세 | -| 12 | 기성관리 | `/construction/progress-billing/[id]` | 기성 내역 | - -#### 판매 도메인 (4개) - -| # | 모듈 | URL 패턴 | 복잡도 요인 | -|---|------|----------|-------------| -| 13 | 견적관리 | `/sales/quote-management/[id]` | 견적서 출력, 품목 테이블 | -| 14 | 수주관리 | `/sales/order-management/[id]` | 주문 추적 | -| 15 | 생산의뢰 | `/sales/production-orders/[id]` | 의뢰 상세 | -| 16 | 단가관리 | `/sales/pricing-management/[id]` | 단가 테이블 | - -#### 자재 도메인 (2개) - v14 재분류 - -| # | 모듈 | URL 패턴 | 복잡도 요인 | -|---|------|----------|-------------| -| 17 | **재고현황** | `/material/stock-status/[id]` | ⚠️ LOT 테이블, 조회 전용 복합 UI (v14) | -| 18 | **입고관리** | `/material/receiving-management/[id]` | ⚠️ 다중 다이얼로그, 복잡 워크플로우 (v14) | - -#### 기타 (1개) - ✅ 완료 - -| # | 모듈 | URL 패턴 | 복잡도 요인 | 상태 | -|---|------|----------|-------------|------| -| 19 | Q&A | `/customer-center/qna/[id]` | 스레드 답변 구조 | ✅ v18 완료 | - ---- - -### A.3 제외 대상 - 11개 - -| # | 모듈 | 경로 | 제외 사유 | -|---|------|------|----------| -| 1 | 권한관리 | `/settings/permissions/[id]` | Matrix UI (복잡한 테이블) | -| 2 | 사원관리 | `/hr/employee-management/[id]` | 40+ 필드, 탭 구조 | -| 3 | 품목관리 | `/items/[id]` | DynamicItemForm (동적 폼) | -| 4 | 작업지시 | `/production/work-orders/[id]` | 칸반 보드, 공정 단계 | -| 5 | 검수관리 | `/quality/inspections/[id]` | 동적 계산, 측정값 | -| 6 | 출하관리 | `/outbound/shipments/[id]` | 3종 문서 출력, 상태별 UI | -| 7 | 수주관리(건설) | `/construction/order-management/[id]` | 계약/결제/다단계 승인 | -| 8 | 계약서 | `/construction/contract/[id]` | 계약 문서, 승인 플로우 | -| 9 | 인수인계서 | `/construction/handover-report/[id]` | 문서 생성, 프린트 | -| 10 | 현장종합현황 | `/construction/project/management/[id]` | 칸반 보드 | -| 11 | 스크린생산 | `/production/screen-production/[id]` | 생산 전용 UI | - ---- - -## 통합 진행 순서 - -### Step 1: Work B 완료 ✅ -``` -DetailSection, DetailGrid, DetailField, DetailActions 컴포넌트 구현 완료 -``` - -### Step 2: Phase 2 작업 (🔄 진행 예정) -``` -우선순위 순서: -1. buttonPosition="top" 확장 (회계 도메인 공통) -2. 회계 도메인 6개 마이그레이션 -3. 판매 거래처, 설정/기준정보 3개 -4. 건설 기준정보, 자재 4개 -5. 게시판/고객센터 3개 -``` - -### Step 3: Phase 3 작업 (📋 계획) -``` -커스텀 섹션 패턴 확립 후 진행 -- renderForm prop 활용 -- 복합 섹션 구조 지원 -``` - ---- - -## 페이지 패턴 분류 - -### 1️⃣ 페이지 형태 - 하단 버튼 (표준) -- IntegratedDetailTemplate 바로 적용 -- 예: 계좌관리, 카드관리, 팝업관리 등 - -### 2️⃣ 페이지 형태 - 상단 버튼 -- `buttonPosition="top"` 확장 필요 -- 예: 회계 도메인 전체 - -### 3️⃣ 조회 전용 (E) -- view 모드만 사용, 수정 버튼 없음 -- 예: 공지사항, 이벤트 - -### 4️⃣ 중간 복잡도 (B) -- 커스텀 섹션 or `renderForm` prop 활용 -- 예: 견적관리, 이슈관리 등 - -### 5️⃣ 복잡 구조 (C) - 제외 -- 독자 구조 유지 -- 예: 칸반, 탭 구조, DynamicForm - ---- - -## 예외 처리 프로세스 - -### 예외 발생 시 절차 - -1. **즉시 중단**: 현재 모듈 작업 중단 -2. **문서화**: 아래 예외 기록에 상황 기록 -3. **분류**: - - **Type A**: 템플릿/컴포넌트 수정으로 해결 - - **Type B**: 해당 모듈만 특수 처리 (`renderView/renderForm`) - - **Type C**: 완전 제외 -4. **계획 수정**: 체크리스트 업데이트 -5. **재개**: 다음 작업 진행 - -### 예외 기록 - -| 날짜 | 모듈 | 예외 유형 | 상황 설명 | 조치 | -|------|------|----------|----------|------| -| 2026-01-19 | IntegratedDetailTemplate | Type A | DetailField 구현 후 미적용 | ✅ 해결 | - ---- - -## 진행 통계 - -| 항목 | 완료 | 진행 중 | 대기 | 제외/불필요 | -|------|------|--------|------|-------------| -| 내부 컴포넌트 | 4 | 0 | 0 | - | -| 스켈레톤 컴포넌트 | 3 | 0 | 0 | - | -| Phase 1 (기반) | 2 | 0 | 0 | - | -| Phase 2 (단순) | 4 | 0 | 0 | 2 (View only) | -| Phase 3 라우팅 변경 | 22 | 0 | 0 | - | -| Phase 3 (중간) | 0 | 0 | 0 | - | -| 제외 대상 | - | - | - | 11 | -| 마이그레이션 불필요 | - | - | - | 8 | -| **총 상세 페이지** | **28** | **0** | **0** | **19** | - -> **Phase 2 ✅ 완료 (4개)**: 노무관리, 단가관리, 입금, 출금 -> **Phase 3 라우팅 구조 변경 ✅ 완료 (22개)**: -> - 거래처(영업), 팝업관리, 공정관리, 게시판관리, 대손추심, Q&A, 현장관리, 실행내역 -> - 견적관리, 견적(테스트), 입찰관리, 이슈관리, 현장설명회, 견적서(건설) -> - 협력업체, 시공관리, 기성관리, 품목관리(건설) -> - **회계 도메인 (기존 V2)**: 거래처, 매출, 세금계산서, 매입 -> **마이그레이션 불필요 (8개)**: -> - 공지사항, 이벤트 (View only) -> - 거래처원장 (조회 전용 탭 구조), 재고현황 (LOT 테이블 조회), 입고관리 (복잡 워크플로우) -> - 수주관리(판매) (복잡 워크플로우, 별도 edit 경로), 생산지시 (조회 전용 복잡 UI), 판매 단가관리 (Edit 전용) -> **공통 컴포넌트 추가**: ErrorCard (에러 페이지 UI 통일), ServerErrorPage (에러 페이지 필수) - ---- - -## 🧪 기능 검수 기록 (Chrome DevTools MCP) - -> 각 마이그레이션 완료 후 Chrome DevTools MCP로 기능 검수 진행 - -### 검수 항목 체크리스트 - -| 항목 | 설명 | +| 구분 | 개수 | |------|------| -| **View 모드** | 데이터 정상 표시, 필수마크(*) 숨김 | -| **Edit 모드** | 폼 필드 활성화, 필수마크(*) 표시, 값 변경 가능 | -| **Create 모드** | 빈 폼, 플레이스홀더 표시, 필수마크(*) 표시 | -| **버튼 전환** | 모드별 버튼 정상 동작 (목록/삭제/수정 ↔ 취소/저장) | -| **저장 기능** | 폼 제출, 토스트 메시지, 페이지 이동 | -| **에러 처리** | 필수값 미입력 시 인라인 에러 + 토스트 | - -### Phase 1 검수 기록 - -| 날짜 | 모듈 | View | Edit | Create | 버튼 | 저장 | 에러 | 결과 | -|------|------|------|------|--------|------|------|------|------| -| 2026-01-19 | 계좌관리 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **PASS** | -| 2026-01-19 | 카드관리 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **PASS** | - -### buttonPosition 확장 검수 (v10) - -> `buttonPosition` prop 추가 후 기존 기능 정상 동작 확인 - -**검수 일시**: 2026-01-19 - -**테스트 페이지**: `/hr/card-management/14` (카드관리 상세) - -| 항목 | 상태 | 비고 | -|------|------|------| -| View 모드 | ✅ | 데이터 정상 표시, 필수마크 숨김 | -| Edit 모드 | ✅ | 폼 필드 활성화, 필수마크(*) 표시 | -| 모드 전환 | ✅ | View↔Edit 전환, 취소 시 원래 값 복원 | -| 버튼 위치 | ✅ | buttonPosition='bottom' 기본값 적용 | -| 섹션 구조 | ✅ | "기본 정보", "사용자 정보" 섹션 정상 | - -**결과**: ✅ **PASS** - buttonPosition 확장 후 기존 기능 정상 동작 - -### Phase 2 검수 기록 - -| 날짜 | 모듈 | View | Edit | Create | 버튼 | 저장 | 에러 | 결과 | -|------|------|------|------|--------|------|------|------|------| -| 2026-01-19 | 노무관리 | ⏳ 데이터없음 | ⏳ 데이터없음 | ✅ | ✅ | ⏳ | ⏳ | **Create PASS** | -| 2026-01-19 | 단가관리(건설) | ⏳ 데이터없음 | ⏳ 데이터없음 | ✅ | ✅ | ⏳ | ⏳ | **Create PASS** | -| 2026-01-19 | 입금 | ✅ | ✅ | ✅ | ✅ | ⏳ | ⏳ | **PASS** | -| 2026-01-19 | 출금 | ✅ | ✅ | ✅ | ✅ | ⏳ | ⏳ | **PASS** | - -> **✅ Chrome DevTools MCP 검증 완료 (2026-01-19)** -> - 입금/출금: View + Edit + Create 모드 모두 정상 -> - 노무/단가: 테스트 데이터 없어 Create 모드만 검증 (View/Edit 대기) -> -> **⚠️ Phase 3으로 이동됨**: 매출, 세금계산서, 매입, 재고현황, 입고관리 (테이블 포함 복합 구조) - -#### 단가관리(건설) 상세 검수 (2026-01-19) - -**마이그레이션 내용**: -- `PricingDetailClientV2.tsx` 생성 (IntegratedDetailTemplate 기반) -- `pricingDetailConfig.ts` config 파일 생성 -- `buttonPosition="top"` 적용 (상단 버튼) -- 12개 필드: 단가번호, 품목유형, 카테고리명, 품목명, 규격, 무게, 단위, 구분, 거래처, 판매단가, 상태, 비고 -- `fetchOptions` 활용하여 거래처 동적 로딩 -- `disabled: (mode) => mode === 'view'` 패턴으로 모드별 필드 활성화 제어 - -**Create 모드 검수** (`/construction/order/base-info/pricing/new`): -| 항목 | 상태 | 비고 | -|------|------|------| -| 페이지 제목 | ✅ | "단가 등록" 정상 표시 | -| 상단 버튼 | ✅ | "취소", "등록" 버튼 TOP 위치 | -| 12개 필드 | ✅ | 모든 필드 정상 렌더링 | -| readonly 필드 | ✅ | 8개 필드 (단가번호~구분) disabled | -| editable 필드 | ✅ | 4개 필드 (거래처, 판매단가, 상태, 비고) 활성화 | -| helpText | ✅ | "발주항목 (동적 컬럼)" 표시 | -| 취소 버튼 | ✅ | 클릭 시 목록 페이지 이동 | - -**View/Edit 모드 검수**: ⏳ 테스트 데이터 없음 (목록 0건) -- 기존 단가 데이터가 없어 View/Edit 모드 검증 불가 -- 데이터 등록 후 재검수 필요 - -**결과**: ✅ Create 모드 PASS, ⏳ View/Edit 대기 +| ✅ V2 완료 | 37개 | +| ❌ 제외 (복잡 구조) | 2개 | +| ⚪ 불필요 (View only 등) | 8개 | +| **합계** | **47개** | --- -| 2026-01-19 | 노무관리 | ⏳ | ⏳ | ✅ | ✅ | ⏳ | ⏳ | **부분 PASS** | +### 🏦 회계 (Accounting) - 8개 -#### 노무관리 상세 검수 (2026-01-19) - -**마이그레이션 내용**: -- `LaborDetailClientV2.tsx` 생성 (IntegratedDetailTemplate 기반) -- `laborDetailConfig.ts` config 파일 생성 -- `buttonPosition="top"` 적용 (상단 버튼) -- 6개 필드: 노임번호, 구분, 최소M, 최대M, 노임단가, 상태 - -**Create 모드 검수** (`/construction/order/base-info/labor/new`): -| 항목 | 상태 | 비고 | -|------|------|------| -| 페이지 제목 | ✅ | "노임 등록" 정상 표시 | -| 상단 버튼 | ✅ | "취소", "등록" 버튼 TOP 위치 | -| 6개 필드 | ✅ | 모든 필드 정상 렌더링 | -| 필수마크(*) | ✅ | 노임번호, 구분, 상태에 표시 | -| helpText | ✅ | "소수점 둘째자리까지 입력 가능" 표시 | -| 취소 버튼 | ✅ | 클릭 시 목록 페이지 이동 | - -**View/Edit 모드 검수**: ⏳ 테스트 데이터 없음 (목록 0건) -- 기존 노임 데이터가 없어 View/Edit 모드 검증 불가 -- 데이터 등록 후 재검수 필요 - -**결과**: ✅ Create 모드 PASS, ⏳ View/Edit 대기 +| 페이지 | 경로 | V2 상태 | 비고 | +|--------|------|---------|------| +| 입금 | `/accounting/deposits/[id]` | ✅ 완료 | Phase 5 | +| 출금 | `/accounting/withdrawals/[id]` | ✅ 완료 | Phase 5 | +| 거래처 | `/accounting/vendors/[id]` | ✅ 완료 | 기존 V2 | +| 매출 | `/accounting/sales/[id]` | ✅ 완료 | 기존 V2 | +| 매입 | `/accounting/purchase/[id]` | ✅ 완료 | 기존 V2 | +| 세금계산서 | `/accounting/bills/[id]` | ✅ 완료 | 기존 V2 | +| 대손추심 | `/accounting/bad-debt-collection/[id]` | ✅ 완료 | Phase 3 | +| 거래처원장 | `/accounting/vendor-ledger/[id]` | ⚪ 불필요 | 조회 전용 탭 | --- -| 2026-01-19 | 입금 | ⏳ | ⏳ | ✅ | ✅ | ⏳ | ⏳ | **부분 PASS** | +### 🏗️ 건설 (Construction) - 16개 -#### 입금 상세 검수 (2026-01-19) - -**마이그레이션 내용**: -- `DepositDetailClientV2.tsx` 생성 (IntegratedDetailTemplate 기반) -- `depositDetailConfig.ts` config 파일 생성 -- `buttonPosition="top"` 적용 (상단 버튼) -- 7개 필드: 입금일, 입금계좌, 입금자명, 입금금액, 적요, 거래처, 입금 유형 -- `fetchOptions` 활용하여 거래처 동적 로딩 -- `disabled: (mode) => mode === 'view'` 패턴으로 모드별 필드 활성화 제어 -- `/new`, `/[id]`, `/[id]/edit` 페이지 구조로 변경 - -**Create 모드 검수** (`/accounting/deposits/new`): -| 항목 | 상태 | 비고 | -|------|------|------| -| 페이지 제목 | ✅ | "입금 등록" 정상 표시 | -| 상단 버튼 | ✅ | "취소", "등록" 버튼 TOP 위치 | -| 7개 필드 | ✅ | 모든 필드 정상 렌더링 | -| readonly 필드 | ✅ | 4개 필드 (입금일, 입금계좌, 입금자명, 입금금액) disabled | -| editable 필드 | ✅ | 3개 필드 (적요, 거래처, 입금 유형) 활성화 | -| 필수마크(*) | ✅ | 거래처, 입금 유형에 표시 | -| 취소 버튼 | ✅ | 클릭 시 목록 페이지 이동 | - -**View/Edit 모드 검수**: ⏳ 테스트 데이터 의존 (QA 팀에 위임) - -**결과**: ✅ Create 모드 PASS, ⏳ View/Edit 대기 +| 페이지 | 경로 | V2 상태 | 비고 | +|--------|------|---------|------| +| 노무관리 | `/construction/base-info/labor/[id]` | ✅ 완료 | Phase 2 | +| 단가관리 | `/construction/base-info/pricing/[id]` | ✅ 완료 | Phase 5 | +| 품목관리(건설) | `/construction/base-info/items/[id]` | ✅ 완료 | 기존 V2 | +| 현장관리 | `/construction/site-management/[id]` | ✅ 완료 | Phase 3 | +| 실행내역 | `/construction/order/structure-review/[id]` | ✅ 완료 | Phase 3 | +| 입찰관리 | `/construction/project/bidding/[id]` | ✅ 완료 | Phase 3 | +| 이슈관리 | `/construction/project/issue-management/[id]` | ✅ 완료 | Phase 3 | +| 현장설명회 | `/construction/project/bidding/site-briefings/[id]` | ✅ 완료 | Phase 3 | +| 견적서 | `/construction/project/bidding/estimates/[id]` | ✅ 완료 | Phase 3 | +| 협력업체 | `/construction/partners/[id]` | ✅ 완료 | Phase 3 | +| 시공관리 | `/construction/construction-management/[id]` | ✅ 완료 | Phase 3 | +| 기성관리 | `/construction/billing/progress-billing-management/[id]` | ✅ 완료 | Phase 3 | +| 발주관리 | `/construction/order/order-management/[id]` | ✅ 완료 | Phase 4 | +| 계약관리 | `/construction/project/contract/[id]` | ✅ 완료 | Phase 4 | +| 인수인계보고서 | `/construction/project/contract/handover-report/[id]` | ✅ 완료 | Phase 4 | +| 현장종합현황 | `/construction/project/management/[id]` | ❌ 제외 | 칸반 보드 | --- -| 2026-01-19 | 출금 | ⏳ | ⏳ | ✅ | ✅ | ⏳ | ⏳ | **부분 PASS** | +### 💼 판매 (Sales) - 7개 -#### 출금 상세 검수 (2026-01-19) - -**마이그레이션 내용**: -- `WithdrawalDetailClientV2.tsx` 생성 (IntegratedDetailTemplate 기반) -- `withdrawalDetailConfig.ts` config 파일 생성 -- `buttonPosition="top"` 적용 (상단 버튼) -- 7개 필드: 출금일, 출금계좌, 수취인명, 출금금액, 적요, 거래처, 출금 유형 -- `fetchOptions` 활용하여 거래처 동적 로딩 -- `disabled: (mode) => mode === 'view'` 패턴으로 모드별 필드 활성화 제어 -- `/new`, `/[id]`, `/[id]/edit` 페이지 구조로 변경 - -**Create 모드 검수** (`/accounting/withdrawals/new`): -| 항목 | 상태 | 비고 | -|------|------|------| -| 페이지 제목 | ✅ | "출금 등록" 정상 표시 | -| 상단 버튼 | ✅ | "취소", "등록" 버튼 TOP 위치 | -| 7개 필드 | ✅ | 모든 필드 정상 렌더링 | -| readonly 필드 | ✅ | 4개 필드 (출금일, 출금계좌, 수취인명, 출금금액) disabled | -| editable 필드 | ✅ | 3개 필드 (적요, 거래처, 출금 유형) 활성화 | -| 필수마크(*) | ✅ | 거래처, 출금 유형에 표시 | -| 취소 버튼 | ✅ | 클릭 시 목록 페이지 이동 | - -**View/Edit 모드 검수**: ⏳ 테스트 데이터 의존 (QA 팀에 위임) - -**결과**: ✅ Create 모드 PASS, ⏳ View/Edit 대기 +| 페이지 | 경로 | V2 상태 | 비고 | +|--------|------|---------|------| +| 거래처(영업) | `/sales/client-management-sales-admin/[id]` | ✅ 완료 | Phase 3 | +| 견적관리 | `/sales/quote-management/[id]` | ✅ 완료 | Phase 3 | +| 견적(테스트) | `/sales/quote-management/test/[id]` | ✅ 완료 | Phase 3 | +| 판매수주관리 | `/sales/order-management-sales/[id]` | ✅ 완료 | Phase 5 | +| 단가관리 | `/sales/pricing-management/[id]` | ✅ 완료 | Phase 4 | +| 수주관리 | `/sales/order-management/[id]` | ⚪ 불필요 | 복잡 워크플로우 | +| 생산의뢰 | `/sales/production-orders/[id]` | ⚪ 불필요 | 조회 전용 | --- -### Phase 2 최종 현황 (v15) +### 👥 인사 (HR) - 2개 -> **Phase 2 완료**: 4개 마이그레이션 완료 (노무관리, 단가관리, 입금, 출금) -> **Phase 3 이동**: 4개 별도 라우팅 구조로 Phase 3 이동 (거래처(영업), 팝업관리, 공정관리, 게시판관리) -> **마이그레이션 불필요**: 2개 View only 페이지 (공지사항, 이벤트) +| 페이지 | 경로 | V2 상태 | 비고 | +|--------|------|---------|------| +| 카드관리 | `/hr/card-management/[id]` | ✅ 완료 | Phase 1 | +| 사원관리 | `/hr/employee-management/[id]` | ✅ 완료 | Phase 4 | --- -### Phase 3 라우팅 구조 변경 검수 기록 (v16) +### 🏭 생산 (Production) - 2개 -> **V2 래퍼 패턴**: 기존 Detail/Form 컴포넌트 활용, 라우팅만 통합 (`/[id]?mode=view|edit`, `/new`) -> **ErrorCard 적용**: 에러 페이지 UI 통일 +| 페이지 | 경로 | V2 상태 | 비고 | +|--------|------|---------|------| +| 작업지시 | `/production/work-orders/[id]` | ✅ 완료 | Phase 4 | +| 스크린생산 | `/production/screen-production/[id]` | ✅ 완료 | Phase 4 | --- -## 🚨 V2 패턴 핵심 원칙 (필독) +### 🔍 품질 (Quality) - 1개 -### ❌ 절대 하지 말 것 -``` -- 별도 파일로 분리 (QuoteDetailContent.tsx, QuoteEditContent.tsx 등) -- 새로운 컴포넌트 파일 생성해서 view/edit 나누기 -- V2 래퍼에서 다른 파일의 컴포넌트 import해서 분기 -``` +| 페이지 | 경로 | V2 상태 | 비고 | +|--------|------|---------|------| +| 검수관리 | `/quality/inspections/[id]` | ✅ 완료 | Phase 4 | + +--- + +### 📦 출고 (Outbound) - 1개 + +| 페이지 | 경로 | V2 상태 | 비고 | +|--------|------|---------|------| +| 출하관리 | `/outbound/shipments/[id]` | ✅ 완료 | Phase 4 | + +--- + +### 📥 자재 (Material) - 2개 + +| 페이지 | 경로 | V2 상태 | 비고 | +|--------|------|---------|------| +| 재고현황 | `/material/stock-status/[id]` | ⚪ 불필요 | LOT 테이블 조회 | +| 입고관리 | `/material/receiving-management/[id]` | ⚪ 불필요 | 복잡 워크플로우 | + +--- + +### 📞 고객센터 (Customer Center) - 3개 + +| 페이지 | 경로 | V2 상태 | 비고 | +|--------|------|---------|------| +| Q&A | `/customer-center/qna/[id]` | ✅ 완료 | Phase 3 | +| 공지사항 | `/customer-center/notices/[id]` | ⚪ 불필요 | View only | +| 이벤트 | `/customer-center/events/[id]` | ⚪ 불필요 | View only | + +--- + +### 📋 게시판 (Board) - 1개 + +| 페이지 | 경로 | V2 상태 | 비고 | +|--------|------|---------|------| +| 게시판관리 | `/board/board-management/[id]` | ✅ 완료 | Phase 3 | + +--- + +### ⚙️ 설정 (Settings) - 3개 + +| 페이지 | 경로 | V2 상태 | 비고 | +|--------|------|---------|------| +| 계좌관리 | `/settings/accounts/[id]` | ✅ 완료 | Phase 1 | +| 팝업관리 | `/settings/popup-management/[id]` | ✅ 완료 | Phase 3 | +| 권한관리 | `/settings/permissions/[id]` | ❌ 제외 | Matrix UI | + +--- + +### 🔧 기준정보 (Master Data) - 1개 + +| 페이지 | 경로 | V2 상태 | 비고 | +|--------|------|---------|------| +| 공정관리 | `/master-data/process-management/[id]` | ✅ 완료 | Phase 3 | + +--- + +### 📦 품목 (Items) - 1개 + +| 페이지 | 경로 | V2 상태 | 비고 | +|--------|------|---------|------| +| 품목관리 | `/items/[id]` | ✅ 완료 | Phase 5 | + +--- + +## 🔧 V2 마이그레이션 패턴 + +### Pattern A: mode prop 지원 + +기존 컴포넌트가 `mode` prop을 지원하는 경우 -### ✅ 올바른 방식 ```tsx -// /[id]/page.tsx 또는 V2Client.tsx 에서 -// 1. mode 쿼리 파라미터 확인 -const mode = searchParams.get('mode'); +// page.tsx +const mode = searchParams.get('mode') === 'edit' ? 'edit' : 'view'; +return ; + +// edit/page.tsx → 리다이렉트 +router.replace(`/path/${id}?mode=edit`); +``` + +### Pattern B: View/Edit 컴포넌트 분리 + +View와 Edit가 완전히 다른 구현인 경우 + +```tsx +// 새 컴포넌트: ComponentDetailView.tsx, ComponentDetailEdit.tsx +const mode = searchParams.get('mode') === 'edit' ? 'edit' : 'view'; -// 2. 같은 파일 내에서 조건부 렌더링 if (mode === 'edit') { - return ; + return ; } -return ; - -// 또는 같은 컴포넌트가 mode prop으로 구분하는 경우 -return ; +return ; ``` -### 핵심 원칙 요약 -| 원칙 | 설명 | -|------|------| -| **한 페이지** | `/[id]/page.tsx` 하나에서 view/edit 모두 처리 | -| **mode 체크** | `searchParams.get('mode')` 또는 `useSearchParams()` 사용 | -| **기존 컴포넌트 재활용** | 새 파일 만들지 않고 기존 Detail/Form 컴포넌트 그대로 사용 | -| **파일 분리 금지** | ViewContent, EditContent 같은 별도 파일 생성 금지 | +--- -### 실수 방지 체크리스트 -- [ ] 새 파일 만들려고 하면 → **STOP! 기존 파일에서 mode 체크로 해결** -- [ ] 컴포넌트 분리하려고 하면 → **STOP! 한 페이지에서 조건부 렌더링** -- [ ] import 여러 개 하려고 하면 → **STOP! 기존 컴포넌트만 import** +## 📚 공통 컴포넌트 참조 + +| 컴포넌트 | 위치 | 용도 | +|----------|------|------| +| IntegratedDetailTemplate | `src/components/templates/IntegratedDetailTemplate/` | 상세 페이지 템플릿 | +| ErrorCard | `src/components/ui/error-card.tsx` | 에러 UI (not-found, network) | +| ServerErrorPage | `src/components/common/ServerErrorPage.tsx` | 서버 에러 페이지 | --- -| 날짜 | 모듈 | View | Edit | Create | 에러UI | URL 구조 | 결과 | -|------|------|------|------|--------|--------|----------|------| -| 2026-01-19 | 거래처(영업) | ✅ | ✅ | ✅ | ✅ | `?mode=view\|edit` | **PASS** | -| 2026-01-19 | 팝업관리 | ✅ | ✅ | ✅ | ✅ | `?mode=view\|edit` | **PASS** | -| 2026-01-19 | 공정관리 | ✅ | ✅ | ✅ | ✅ | `?mode=view\|edit` | **PASS** | -| 2026-01-19 | 게시판관리 | ✅ | ✅ | ✅ | ✅ | `?mode=view\|edit` | **PASS** | -| 2026-01-19 | 대손추심 | ✅ | ✅ | ✅ | ✅ | `?mode=view\|edit` | **PASS** | -| 2026-01-19 | Q&A | ✅ | ✅ | ✅ | ✅ | `?mode=view\|edit` | **PASS** | -| 2026-01-19 | 현장관리 | ✅ | ✅ | - | ✅ | `?mode=view\|edit` | **PASS** | -| 2026-01-19 | 실행내역 | ✅ | ✅ | - | ✅ | `?mode=view\|edit` | **PASS** | -| 2026-01-19 | 견적관리 | ✅ | ✅ | - | - | `?mode=view\|edit` | **PASS** | -| 2026-01-19 | 견적(테스트) | ✅ | ✅ | - | - | `?mode=view\|edit` | **PASS** | -| 2026-01-19 | 입찰관리 | ⏳ | ⏳ | - | - | `?mode=view\|edit` | **코드완료** | -| 2026-01-19 | 이슈관리 | ⏳ | ⏳ | - | - | `?mode=view\|edit` | **코드완료** | -| 2026-01-19 | 현장설명회 | ⏳ | ⏳ | - | - | `?mode=view\|edit` | **코드완료** | -| 2026-01-19 | 견적서(건설) | ⏳ | ⏳ | - | - | `?mode=view\|edit` | **코드완료** | +## 📝 변경 이력 -**검증 항목**: -- `/new` → Create 모드 정상 동작 -- `/999` (존재하지 않는 ID) → ErrorCard 표시 (network 타입) -- `/1?mode=edit` → Edit 모드 전환 -- `/[id]/edit` → `/[id]?mode=edit` 리다이렉트 (하위 호환성) - -### 검수 실패 시 조치 - -1. **즉시 중단**: 해당 모듈 작업 중단 -2. **원인 분석**: Chrome DevTools 콘솔/네트워크 확인 -3. **수정**: 버그 수정 -4. **재검수**: 전체 항목 다시 검수 -5. **기록**: 이슈 내용 및 해결 방법 문서화 - ---- - -## 변경 이력 +
+전체 변경 이력 보기 (v1 ~ v27) | 날짜 | 버전 | 내용 | |------|------|------| @@ -1069,140 +223,25 @@ return ; | 2026-01-19 | v5 | Chrome DevTools 동작 검증 완료 | | 2026-01-19 | v6 | DetailField 미적용 이슈 발견 | | 2026-01-19 | v7 | DetailField 미적용 이슈 해결 완료 | -| 2026-01-19 | v8 | **📊 47개 상세 페이지 전체 분석 완료**: 도메인별 상세 분류, Phase별 재정리, 통계 업데이트 | -| 2026-01-19 | v9 | **📋 리스트/상세 차이 설명 추가, 🧪 기능 검수 섹션 추가** | -| 2026-01-19 | v10 | **🔧 buttonPosition prop 추가**: 상단(top)/하단(bottom) 버튼 배치 지원 - Phase 2 회계 도메인 대비 -| 2026-01-19 | v11 | **🚀 노무관리 마이그레이션 완료**: LaborDetailClientV2 생성, Create 모드 검수 PASS (View/Edit 대기) -| 2026-01-19 | v12 | **🚀 단가관리(건설) 마이그레이션 완료**: PricingDetailClientV2 생성, fetchOptions/disabled 함수 패턴 활용, Create 모드 검수 PASS -| 2026-01-19 | v13 | **🚀 입금관리 마이그레이션 완료**: DepositDetailClientV2 생성, 거래처를 Phase 3으로 재분류, Create 모드 검수 PASS -| 2026-01-19 | v14 | **📊 Phase 2 분석 및 대규모 재분류**: 출금관리 완료 (4개 완료), 5개 페이지 Phase 3 이동 (매출/세금계산서/매입/재고현황/입고관리 - 테이블 포함), 통계 업데이트 -| 2026-01-19 | v15 | **✅ Phase 2 최종 완료**: 대기 6개 페이지 분석 완료 - 4개 Phase 3 이동 (별도 라우팅 구조: 거래처/팝업관리/공정관리/게시판관리), 2개 마이그레이션 불필요 (View only: 공지사항/이벤트) -| 2026-01-19 | v16 | **🚀 Phase 3 라우팅 구조 변경 4개 완료, 🎨 ErrorCard 공통 컴포넌트 추가**: 거래처(영업)/팝업관리/공정관리/게시판관리 V2 래퍼 패턴 적용, 에러 페이지 UI 통일용 ErrorCard 컴포넌트 신규 생성 -| 2026-01-19 | v17 | **🚀 Phase 3 대손추심 완료**: BadDebtDetailClientV2 생성 (기존 컴포넌트 래핑), 검수 PASS -| 2026-01-19 | v18 | **🚀 Phase 3 Q&A 완료**: InquiryDetailClientV2 생성 (기존 Detail/Form 컴포넌트 래핑), `/create` URL 패턴 지원, 검수 PASS -| 2026-01-19 | v19 | **🚀 Phase 3 건설/판매 도메인 3개 추가 완료**: 현장관리(SiteDetailClientV2), 실행내역(StructureReviewDetailClientV2), 견적관리(기존 page.tsx에 mode 체크) - 총 15개 완료, 18개 대기 -| 2026-01-19 | v20 | **🧪 견적 테스트 페이지 V2 패턴 적용**: `/sales/quote-management/test/[id]` 기존 page.tsx에 mode 체크 추가 (실제 API 연동 예정 페이지) - 총 16개 완료, 17개 대기, 제외 11개로 축소 -| 2026-01-19 | v21 | **🚀 Phase 3 건설 도메인 4개 추가 완료**: 입찰관리, 이슈관리, 현장설명회, 견적서(건설) - 기존 page.tsx에 mode 체크 추가 - 총 20개 완료, 13개 대기 -| 2026-01-19 | v22 | **🚨 ServerErrorPage 필수 적용 섹션 추가**: V2 마이그레이션 시 기본 div 에러 UI 대신 ServerErrorPage 컴포넌트 사용 필수 -| 2026-01-19 | v23 | **🚀 기성관리 V2 마이그레이션 완료**: `/construction/billing/progress-billing-management/[id]` - mode 체크 + ServerErrorPage 적용 - 총 21개 완료 -| 2026-01-19 | v24 | **📊 Phase 3 최종 분석 완료**: 품목관리(건설) 이미 V2 적용 확인, 3개 추가 제외 (수주관리-복잡워크플로우, 생산지시-조회전용복잡UI, 판매단가관리-Edit전용) -| 2026-01-19 | v25 | **🚀 Phase 4 추가**: 제외 대상 재분석으로 9개 V2 마이그레이션 가능 페이지 식별 (판매단가관리, 사원관리, 품목관리, 작업지시, 검수관리, 출하관리, 발주관리(건설), 계약관리, 인수인계보고서) -| 2026-01-19 | v26 | **🎯 Phase 5 완료**: 5개 V2 URL 패턴 미적용 페이지 통합 - Pattern A (입금관리, 출금관리, 단가관리(건설)) + Pattern B (판매수주관리, 품목관리 - View/Edit 컴포넌트 분리) +| 2026-01-19 | v8 | 📊 47개 상세 페이지 전체 분석 완료 | +| 2026-01-19 | v9 | 📋 리스트/상세 차이 설명 추가, 🧪 기능 검수 섹션 추가 | +| 2026-01-19 | v10 | 🔧 buttonPosition prop 추가 | +| 2026-01-19 | v11 | 🚀 노무관리 마이그레이션 완료 | +| 2026-01-19 | v12 | 🚀 단가관리(건설) 마이그레이션 완료 | +| 2026-01-19 | v13 | 🚀 입금관리 마이그레이션 완료 | +| 2026-01-19 | v14 | 📊 Phase 2 분석 및 대규모 재분류 | +| 2026-01-19 | v15 | ✅ Phase 2 최종 완료 | +| 2026-01-19 | v16 | 🚀 Phase 3 라우팅 구조 변경 4개 완료, 🎨 ErrorCard 추가 | +| 2026-01-19 | v17 | 🚀 Phase 3 대손추심 완료 | +| 2026-01-19 | v18 | 🚀 Phase 3 Q&A 완료 | +| 2026-01-19 | v19 | 🚀 Phase 3 건설/판매 도메인 3개 추가 완료 | +| 2026-01-19 | v20 | 🧪 견적 테스트 페이지 V2 패턴 적용 | +| 2026-01-19 | v21 | 🚀 Phase 3 건설 도메인 4개 추가 완료 | +| 2026-01-19 | v22 | 🚨 ServerErrorPage 필수 적용 섹션 추가 | +| 2026-01-19 | v23 | 🚀 기성관리 V2 마이그레이션 완료 | +| 2026-01-19 | v24 | 📊 Phase 3 최종 분석 완료 | +| 2026-01-19 | v25 | 🚀 Phase 4 추가 (9개 페이지 식별) | +| 2026-01-19 | v26 | 🎯 Phase 5 완료 (5개 V2 URL 패턴 통합) | +| 2026-01-20 | v27 | 📋 문서 정리 - 최종 현황 표 중심으로 재구성 | ---- - -## 🚀 Phase 4: 추가 V2 마이그레이션 대상 (9개) - -> **발견 경위**: Phase 3 제외 대상 19개 페이지 검증 중 V2 마이그레이션 가능 페이지 식별 -> **공통 특징**: 별도 `/[id]`, `/[id]/edit` 라우팅 사용, DetailForm 컴포넌트로 view/edit mode 구분 - -### Phase 4 대상 페이지 - -| # | 모듈 | 경로 | 현재 구조 | 마이그레이션 내용 | 상태 | -|---|------|------|----------|------------------|------| -| 1 | 판매 단가관리 | `/sales/pricing-management/[id]` | `/new`, `/[id]/edit` | URL 통합: `/[id]?mode=view\|edit` | ⏳ 대기 | -| 2 | 사원관리 | `/hr/employee-management/[id]` | `/new`, `/[id]`, `/[id]/edit` | URL 통합: `/[id]?mode=view\|edit` | ⏳ 대기 | -| 3 | 품목관리 | `/items/[id]` | `/new`, `/[id]`, `/[id]/edit` | URL 통합: `/[id]?mode=view\|edit` | ⏳ 대기 | -| 4 | 작업지시 | `/production/work-orders/[id]` | `/new`, `/[id]`, `/[id]/edit` | URL 통합: `/[id]?mode=view\|edit` | ⏳ 대기 | -| 5 | 검수관리 | `/quality/inspections/[id]` | `/new`, `/[id]`, `/[id]/edit` | URL 통합: `/[id]?mode=view\|edit` | ⏳ 대기 | -| 6 | 출하관리 | `/outbound/shipments/[id]` | `/new`, `/[id]`, `/[id]/edit` | URL 통합: `/[id]?mode=view\|edit` | ⏳ 대기 | -| 7 | 발주관리(건설) | `/construction/order/order-management/[id]` | `/[id]`, `/[id]/edit` (등록 없음) | `/new` 페이지 생성 필요 + URL 통합 | ⏳ 대기 | -| 8 | 계약관리 | `/construction/project/contract/[id]` | `/[id]`, `/[id]/edit` (등록 자동) | URL 통합: `/[id]?mode=view\|edit` | ⏳ 대기 | -| 9 | 인수인계보고서 | `/construction/project/contract/handover-report/[id]` | `/[id]`, `/[id]/edit` (등록 자동) | URL 통합: `/[id]?mode=view\|edit` | ⏳ 대기 | - -### Phase 4 마이그레이션 패턴 - -**패턴 A: 기존 view/edit 분리 → URL 통합** -``` -현재: /[id] (view) + /[id]/edit (edit) -변경: /[id]?mode=view (기본) + /[id]?mode=edit -``` - -**패턴 B: 등록 페이지 신규 생성 필요** -``` -현재: /[id] + /[id]/edit (등록 없음) -변경: /new (신규) + /[id]?mode=view|edit -``` - -### Phase 4 작업 순서 - -1. **그룹 A (view/edit만 통합)**: 계약관리, 인수인계보고서 (2개) -2. **그룹 B (create/view/edit 모두 있음)**: 사원관리, 품목관리, 작업지시, 검수관리, 출하관리, 판매단가관리 (6개) -3. **그룹 C (등록 페이지 신규 생성)**: 발주관리(건설) (1개) - ---- - -## 🎯 Phase 5: V2 URL 패턴 미적용 페이지 통합 (5개 완료) - -> **발견 경위**: Phase 4 완료 후 전체 상세 페이지 URL 패턴 점검 중 V2 패턴 미적용 페이지 식별 -> **대상**: `/[id]`, `/[id]/edit` 분리 구조로 되어 있으나 V2 URL 패턴(`?mode=view|edit`) 미적용 페이지 - -### Phase 5 대상 페이지 - -| # | 모듈 | 경로 | 마이그레이션 패턴 | 상태 | -|---|------|------|------------------|------| -| 1 | 입금관리 | `/accounting/deposits/[id]` | Pattern A: URL 통합 (기존 V2 컴포넌트) | ✅ 완료 | -| 2 | 출금관리 | `/accounting/withdrawals/[id]` | Pattern A: URL 통합 (기존 V2 컴포넌트) | ✅ 완료 | -| 3 | 단가관리(건설) | `/construction/base-info/pricing/[id]` | Pattern A: URL 통합 (기존 V2 컴포넌트) | ✅ 완료 | -| 4 | 판매수주관리 | `/sales/order-management-sales/[id]` | Pattern B: View/Edit 컴포넌트 분리 | ✅ 완료 | -| 5 | 품목관리 | `/items/[id]` | Pattern B: View/Edit 컴포넌트 분리 | ✅ 완료 | - -### Phase 5 마이그레이션 패턴 - -**Pattern A: 기존 V2 컴포넌트가 mode prop 지원** -```tsx -// page.tsx - mode 쿼리 파라미터만 추가 -const mode = searchParams.get('mode') === 'edit' ? 'edit' : 'view'; -return ; - -// edit/page.tsx - 리다이렉트로 변환 -router.replace(`/path/${id}?mode=edit`); -``` - -**Pattern B: View/Edit 컴포넌트가 완전히 다른 경우** -```tsx -// 새 컴포넌트 생성: ComponentDetailView.tsx, ComponentDetailEdit.tsx -// page.tsx에서 mode에 따라 분기 -const mode = searchParams.get('mode') === 'edit' ? 'edit' : 'view'; - -if (mode === 'edit') { - return ; -} -return ; -``` - -### Phase 5 완료 상세 - -#### 1-3. 입금관리, 출금관리, 단가관리(건설) - Pattern A - -| 모듈 | 파일 변경 | 비고 | -|------|----------|------| -| 입금관리 | `page.tsx` (mode 체크), `edit/page.tsx` (리다이렉트) | 기존 DepositDetailClientV2 활용 | -| 출금관리 | `page.tsx` (mode 체크), `edit/page.tsx` (리다이렉트) | 기존 WithdrawalDetailClientV2 활용 | -| 단가관리(건설) | `page.tsx` (mode 체크), `edit/page.tsx` (리다이렉트) | 기존 PricingDetailClientV2 활용 | - -#### 4. 판매수주관리 - Pattern B - -| 파일 | 내용 | -|------|------| -| `OrderSalesDetailView.tsx` | View 모드 컴포넌트 (660줄 → 추출) | -| `OrderSalesDetailEdit.tsx` | Edit 모드 컴포넌트 (559줄 → 추출) | -| `components/orders/index.ts` | 새 컴포넌트 export 추가 | -| `page.tsx` | mode 분기로 View/Edit 렌더링 | -| `edit/page.tsx` | 리다이렉트 (`?mode=edit`) | - -**특징**: View와 Edit가 완전히 다른 구현 (문서 모달 vs 폼 입력) - -#### 5. 품목관리 - Pattern B - -| 파일 | 내용 | -|------|------| -| `ItemDetailView.tsx` | View 모드 컴포넌트 (ItemDetailClient 래핑) | -| `ItemDetailEdit.tsx` | Edit 모드 컴포넌트 (DynamicItemForm 래핑) | -| `page.tsx` | mode 분기 + 기존 쿼리 파라미터(type, id) 유지 | -| `edit/page.tsx` | 리다이렉트 (type, id 쿼리 파라미터 보존) | - -**특징**: -- 품목 유형(type)과 품목 ID(id) 쿼리 파라미터를 mode와 함께 유지 -- API 매핑 함수(`mapApiResponseToItemMaster`, `mapApiResponseToFormData`) 포함 -- BOM 데이터 별도 API 호출 로직 포함 +
diff --git a/src/app/[locale]/(protected)/items/[id]/edit/page.tsx b/src/app/[locale]/(protected)/items/[id]/edit/page.tsx deleted file mode 100644 index 0f3df608..00000000 --- a/src/app/[locale]/(protected)/items/[id]/edit/page.tsx +++ /dev/null @@ -1,32 +0,0 @@ -'use client'; - -import { use, useEffect } from 'react'; -import { useRouter, useSearchParams } from 'next/navigation'; - -interface PageProps { - params: Promise<{ id: string }>; -} - -/** - * V2 하위 호환성: /[id]/edit → /[id]?mode=edit 리다이렉트 - * 기존 쿼리 파라미터(type, id)는 유지 - */ -export default function ItemEditPage({ params }: PageProps) { - const { id } = use(params); - const router = useRouter(); - const searchParams = useSearchParams(); - - useEffect(() => { - // 기존 쿼리 파라미터 유지하면서 mode=edit 추가 - const type = searchParams.get('type') || 'FG'; - const itemId = searchParams.get('id') || ''; - - router.replace(`/items/${id}?type=${type}&id=${itemId}&mode=edit`); - }, [id, router, searchParams]); - - return ( -
-
리다이렉트 중...
-
- ); -} diff --git a/src/app/[locale]/(protected)/items/[id]/page.tsx b/src/app/[locale]/(protected)/items/[id]/page.tsx deleted file mode 100644 index cb70c3b3..00000000 --- a/src/app/[locale]/(protected)/items/[id]/page.tsx +++ /dev/null @@ -1,34 +0,0 @@ -'use client'; - -/** - * 품목 상세/수정 페이지 (Client Component) - * V2 패턴: ?mode=edit로 수정 모드 전환 - */ - -import { use } from 'react'; -import { useSearchParams } from 'next/navigation'; -import { ItemDetailView } from '@/components/items/ItemDetailView'; -import { ItemDetailEdit } from '@/components/items/ItemDetailEdit'; - -interface PageProps { - params: Promise<{ id: string }>; -} - -export default function ItemDetailPage({ params }: PageProps) { - const { id } = use(params); - const searchParams = useSearchParams(); - - // URL에서 type, id, mode 쿼리 파라미터 읽기 - const itemType = searchParams.get('type') || 'FG'; - const itemId = searchParams.get('id') || ''; - const mode = searchParams.get('mode') === 'edit' ? 'edit' : 'view'; - - // 품목 코드 디코딩 - const itemCode = decodeURIComponent(id); - - if (mode === 'edit') { - return ; - } - - return ; -} diff --git a/src/app/[locale]/(protected)/items/create/page.tsx b/src/app/[locale]/(protected)/items/create/page.tsx deleted file mode 100644 index a14c0810..00000000 --- a/src/app/[locale]/(protected)/items/create/page.tsx +++ /dev/null @@ -1,101 +0,0 @@ -/** - * 품목 등록 페이지 - * - * DynamicItemForm을 사용하여 품목기준관리 데이터 기반 동적 폼 렌더링 - */ - -'use client'; - -import { useState } from 'react'; -import DynamicItemForm from '@/components/items/DynamicItemForm'; -import type { DynamicFormData } from '@/components/items/DynamicItemForm/types'; -// 2025-12-16: options 관련 변환 로직 제거 -// 백엔드가 품목기준관리 field_key 매핑을 처리하므로 프론트에서 변환 불필요 -import { DuplicateCodeError } from '@/lib/api/error-handler'; - -// 기존 ItemForm (주석처리 - 롤백 시 사용) -// import ItemForm from '@/components/items/ItemForm'; -// import type { CreateItemFormData } from '@/lib/utils/validation'; - -export default function CreateItemPage() { - const [submitError, setSubmitError] = useState(null); - - const handleSubmit = async (data: DynamicFormData) => { - setSubmitError(null); - - // 필드명 변환: spec → specification (백엔드 API 규격) - const submitData = { ...data }; - if (submitData.spec !== undefined) { - submitData.specification = submitData.spec; - delete submitData.spec; - } - - // 2025-12-15: item_type은 Request Body에서 필수 (ItemService.store validation) - // product_type과 item_type을 동일하게 설정 - const itemType = submitData.product_type as string; - submitData.item_type = itemType; - - // API 호출 전 이미지 데이터 제거 (파일 업로드는 별도 API 사용) - // bending_diagram이 base64 데이터인 경우 제거 (JSON에 포함시키면 안됨) - if (submitData.bending_diagram && typeof submitData.bending_diagram === 'string' && (submitData.bending_diagram as string).startsWith('data:')) { - delete submitData.bending_diagram; - } - // 시방서/인정서 파일 필드도 base64면 제거 - if (submitData.specification_file && typeof submitData.specification_file === 'string' && (submitData.specification_file as string).startsWith('data:')) { - delete submitData.specification_file; - } - if (submitData.certification_file && typeof submitData.certification_file === 'string' && (submitData.certification_file as string).startsWith('data:')) { - delete submitData.certification_file; - } - - // API 호출: POST /api/proxy/items - // 백엔드에서 product_type에 따라 Product/Material 분기 처리 - const response = await fetch('/api/proxy/items', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(submitData), - }); - - const result = await response.json(); - - if (!response.ok || !result.success) { - // 2025-12-11: 백엔드 중복 에러 처리 (DuplicateCodeException) - // duplicate_id가 있으면 DuplicateCodeError throw → DynamicItemForm에서 다이얼로그 표시 - if (response.status === 400 && result.duplicate_id) { - console.warn('[CreateItemPage] 품목코드 중복 에러:', result); - throw new DuplicateCodeError( - result.message || '해당 품목코드가 이미 존재합니다.', - result.duplicate_id, - result.duplicate_code - ); - } - - const errorMessage = result.message || '품목 등록에 실패했습니다.'; - console.error('[CreateItemPage] 품목 등록 실패:', errorMessage); - setSubmitError(errorMessage); - throw new Error(errorMessage); - } - - // 성공 시 DynamicItemForm 내부에서 /items로 리다이렉트 처리됨 - // console.log('[CreateItemPage] 품목 등록 성공:', result.data); - - // 생성된 품목 ID를 포함한 데이터 반환 (파일 업로드용) - return { id: result.data.id, ...result.data }; - }; - - return ( -
- {submitError && ( -
- ⚠️ {submitError} -
- )} - -
- ); -} \ No newline at end of file diff --git a/src/app/[locale]/(protected)/items/page.tsx b/src/app/[locale]/(protected)/items/page.tsx deleted file mode 100644 index d0ffef41..00000000 --- a/src/app/[locale]/(protected)/items/page.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/** - * 품목 관리 페이지 - * - * 품목기준관리 API 연동 - * - 품목 목록: API에서 조회 - * - 테이블 컬럼: custom-tabs API에서 동적 구성 - */ - -import ItemListClient from '@/components/items/ItemListClient'; - -/** - * 품목 목록 페이지 - */ -export default function ItemsPage() { - return ; -} - -/** - * 메타데이터 설정 - */ -export const metadata = { - title: '품목 관리', - description: '품목 목록 조회 및 관리', -}; \ No newline at end of file diff --git a/src/app/[locale]/(protected)/production/screen-production/[id]/edit/page.tsx b/src/app/[locale]/(protected)/production/screen-production/[id]/edit/page.tsx index 446bf723..36e14944 100644 --- a/src/app/[locale]/(protected)/production/screen-production/[id]/edit/page.tsx +++ b/src/app/[locale]/(protected)/production/screen-production/[id]/edit/page.tsx @@ -1,213 +1,32 @@ -/** - * 품목 수정 페이지 - */ - 'use client'; -import { useEffect, useState } from 'react'; -import { useParams, useRouter } from 'next/navigation'; -import { ContentLoadingSpinner } from '@/components/ui/loading-spinner'; -import ItemForm from '@/components/items/ItemForm'; -import { ServerErrorPage } from '@/components/common/ServerErrorPage'; -import type { ItemMaster } from '@/types/item'; -import type { CreateItemFormData } from '@/lib/utils/validation'; +import { use, useEffect } from 'react'; +import { useRouter, useSearchParams } from 'next/navigation'; -// Mock 데이터 (API 연동 전 임시) -const mockItems: ItemMaster[] = [ - { - id: '1', - itemCode: 'KD-FG-001', - itemName: '스크린 제품 A', - itemType: 'FG', - unit: 'EA', - specification: '2000x2000', - isActive: true, - category1: '본체부품', - category2: '가이드시스템', - salesPrice: 150000, - purchasePrice: 100000, - marginRate: 33.3, - processingCost: 20000, - laborCost: 15000, - installCost: 10000, - productCategory: 'SCREEN', - lotAbbreviation: 'KD', - note: '스크린 제품 샘플입니다.', - safetyStock: 10, - leadTime: 7, - currentRevision: 0, - isFinal: false, - createdAt: '2025-01-10T00:00:00Z', - updatedAt: '2025-01-12T00:00:00Z', - bom: [ - { - id: 'bom-1', - childItemCode: 'KD-PT-001', - childItemName: '가이드레일(벽면형)', - quantity: 2, - unit: 'EA', - unitPrice: 35000, - quantityFormula: 'H / 1000', - }, - { - id: 'bom-2', - childItemCode: 'KD-PT-002', - childItemName: '절곡품 샘플', - quantity: 4, - unit: 'EA', - unitPrice: 30000, - isBending: true, - }, - { - id: 'bom-3', - childItemCode: 'KD-SM-001', - childItemName: '볼트 M6x20', - quantity: 20, - unit: 'EA', - unitPrice: 50, - }, - ], - }, - { - id: '2', - itemCode: 'KD-PT-001', - itemName: '가이드레일(벽면형)', - itemType: 'PT', - unit: 'EA', - specification: '2438mm', - isActive: true, - category1: '본체부품', - category2: '가이드시스템', - category3: '가이드레일', - salesPrice: 50000, - purchasePrice: 35000, - marginRate: 30, - partType: 'ASSEMBLY', - partUsage: 'GUIDE_RAIL', - installationType: '벽면형', - assemblyType: 'M', - assemblyLength: '2438', - currentRevision: 0, - isFinal: false, - createdAt: '2025-01-10T00:00:00Z', - }, - { - id: '3', - itemCode: 'KD-PT-002', - itemName: '절곡품 샘플', - itemType: 'PT', - unit: 'EA', - specification: 'EGI 1.55T', - isActive: true, - partType: 'BENDING', - material: 'EGI 1.55T', - length: '2000', - salesPrice: 30000, - currentRevision: 0, - isFinal: false, - createdAt: '2025-01-10T00:00:00Z', - }, - { - id: '4', - itemCode: 'KD-RM-001', - itemName: 'SPHC-SD', - itemType: 'RM', - unit: 'KG', - specification: '1.6T x 1219 x 2438', - isActive: true, - category1: '철강재', - purchasePrice: 1500, - material: 'SPHC-SD', - currentRevision: 0, - isFinal: false, - createdAt: '2025-01-10T00:00:00Z', - }, - { - id: '5', - itemCode: 'KD-SM-001', - itemName: '볼트 M6x20', - itemType: 'SM', - unit: 'EA', - specification: 'M6x20', - isActive: true, - category1: '구조재/부속품', - category2: '볼트/너트', - purchasePrice: 50, - currentRevision: 0, - isFinal: false, - createdAt: '2025-01-10T00:00:00Z', - }, -]; +interface PageProps { + params: Promise<{ id: string }>; +} -export default function EditItemPage() { - const params = useParams(); +/** + * V2 하위 호환성: /[id]/edit → /[id]?mode=edit 리다이렉트 + * 기존 쿼리 파라미터(type, id)는 유지 + */ +export default function ItemEditPage({ params }: PageProps) { + const { id } = use(params); const router = useRouter(); - const [item, setItem] = useState(null); - const [isLoading, setIsLoading] = useState(true); + const searchParams = useSearchParams(); useEffect(() => { - // TODO: API 연동 시 fetchItemByCode() 호출 - const fetchItem = async () => { - setIsLoading(true); - try { - // params.id 타입 체크 - if (!params.id || typeof params.id !== 'string') { - alert('잘못된 품목 ID입니다.'); - router.push('/items'); - return; - } + // 기존 쿼리 파라미터 유지하면서 mode=edit 추가 + const type = searchParams.get('type') || 'FG'; + const itemId = searchParams.get('id') || ''; - // Mock: 데이터 조회 - const itemCode = decodeURIComponent(params.id); - const foundItem = mockItems.find((item) => item.itemCode === itemCode); - - if (foundItem) { - setItem(foundItem); - } else { - alert('품목을 찾을 수 없습니다.'); - router.push('/items'); - } - } catch { - alert('품목 조회에 실패했습니다.'); - router.push('/items'); - } finally { - setIsLoading(false); - } - }; - - fetchItem(); - }, [params.id, router]); - - const handleSubmit = async (data: CreateItemFormData) => { - // TODO: API 연동 시 updateItem() 호출 - console.log('품목 수정 데이터:', data); - - // Mock: 성공 메시지 - alert(`품목 "${data.itemName}" (${data.itemCode})이(가) 수정되었습니다.`); - - // API 연동 예시: - // const updatedItem = await updateItem(item.itemCode, data); - // router.push(`/items/${updatedItem.itemCode}`); - }; - - if (isLoading) { - return ; - } - - if (!item) { - return ( - - ); - } + router.replace(`/production/screen-production/${id}?type=${type}&id=${itemId}&mode=edit`); + }, [id, router, searchParams]); return ( -
- +
+
리다이렉트 중...
); -} \ No newline at end of file +} diff --git a/src/app/[locale]/(protected)/production/screen-production/[id]/page.tsx b/src/app/[locale]/(protected)/production/screen-production/[id]/page.tsx index a42fe96d..cb70c3b3 100644 --- a/src/app/[locale]/(protected)/production/screen-production/[id]/page.tsx +++ b/src/app/[locale]/(protected)/production/screen-production/[id]/page.tsx @@ -1,183 +1,34 @@ 'use client'; /** - * 품목 상세 조회 페이지 (Client Component) + * 품목 상세/수정 페이지 (Client Component) + * V2 패턴: ?mode=edit로 수정 모드 전환 */ -import { use, useEffect, useState } from 'react'; -import { useRouter } from 'next/navigation'; -import { ContentLoadingSpinner } from '@/components/ui/loading-spinner'; -import ItemDetailClient from '@/components/items/ItemDetailClient'; -import { ServerErrorPage } from '@/components/common/ServerErrorPage'; -import type { ItemMaster } from '@/types/item'; +import { use } from 'react'; +import { useSearchParams } from 'next/navigation'; +import { ItemDetailView } from '@/components/items/ItemDetailView'; +import { ItemDetailEdit } from '@/components/items/ItemDetailEdit'; -// Mock 데이터 (API 연동 전 임시) -const mockItems: ItemMaster[] = [ - { - id: '1', - itemCode: 'KD-FG-001', - itemName: '스크린 제품 A', - itemType: 'FG', - unit: 'EA', - specification: '2000x2000', - isActive: true, - category1: '본체부품', - category2: '가이드시스템', - salesPrice: 150000, - purchasePrice: 100000, - marginRate: 33.3, - processingCost: 20000, - laborCost: 15000, - installCost: 10000, - productCategory: 'SCREEN', - lotAbbreviation: 'KD', - note: '스크린 제품 샘플입니다.', - safetyStock: 10, - leadTime: 7, - currentRevision: 0, - isFinal: false, - createdAt: '2025-01-10T00:00:00Z', - updatedAt: '2025-01-12T00:00:00Z', - bom: [ - { - id: 'bom-1', - childItemCode: 'KD-PT-001', - childItemName: '가이드레일(벽면형)', - quantity: 2, - unit: 'EA', - unitPrice: 35000, - quantityFormula: 'H / 1000', - }, - { - id: 'bom-2', - childItemCode: 'KD-PT-002', - childItemName: '절곡품 샘플', - quantity: 4, - unit: 'EA', - unitPrice: 30000, - isBending: true, - }, - { - id: 'bom-3', - childItemCode: 'KD-SM-001', - childItemName: '볼트 M6x20', - quantity: 20, - unit: 'EA', - unitPrice: 50, - }, - ], - }, - { - id: '2', - itemCode: 'KD-PT-001', - itemName: '가이드레일(벽면형)', - itemType: 'PT', - unit: 'EA', - specification: '2438mm', - isActive: true, - category1: '본체부품', - category2: '가이드시스템', - category3: '가이드레일', - salesPrice: 50000, - purchasePrice: 35000, - marginRate: 30, - partType: 'ASSEMBLY', - partUsage: 'GUIDE_RAIL', - installationType: '벽면형', - assemblyType: 'M', - assemblyLength: '2438', - currentRevision: 0, - isFinal: false, - createdAt: '2025-01-10T00:00:00Z', - }, - { - id: '3', - itemCode: 'KD-PT-002', - itemName: '절곡품 샘플', - itemType: 'PT', - unit: 'EA', - specification: 'EGI 1.55T', - isActive: true, - partType: 'BENDING', - material: 'EGI 1.55T', - length: '2000', - salesPrice: 30000, - currentRevision: 0, - isFinal: false, - createdAt: '2025-01-10T00:00:00Z', - }, - { - id: '4', - itemCode: 'KD-RM-001', - itemName: 'SPHC-SD', - itemType: 'RM', - unit: 'KG', - specification: '1.6T x 1219 x 2438', - isActive: true, - category1: '철강재', - purchasePrice: 1500, - material: 'SPHC-SD', - currentRevision: 0, - isFinal: false, - createdAt: '2025-01-10T00:00:00Z', - }, - { - id: '5', - itemCode: 'KD-SM-001', - itemName: '볼트 M6x20', - itemType: 'SM', - unit: 'EA', - specification: 'M6x20', - isActive: true, - category1: '구조재/부속품', - category2: '볼트/너트', - purchasePrice: 50, - currentRevision: 0, - isFinal: false, - createdAt: '2025-01-10T00:00:00Z', - }, -]; - -/** - * 품목 상세 페이지 - */ -export default function ItemDetailPage({ - params, -}: { +interface PageProps { params: Promise<{ id: string }>; -}) { +} + +export default function ItemDetailPage({ params }: PageProps) { const { id } = use(params); - const router = useRouter(); - const [item, setItem] = useState(null); - const [isLoading, setIsLoading] = useState(true); + const searchParams = useSearchParams(); - useEffect(() => { - // API 연동 전 mock 데이터 사용 - const foundItem = mockItems.find( - (item) => item.itemCode === decodeURIComponent(id) - ); - setItem(foundItem || null); - setIsLoading(false); - }, [id]); + // URL에서 type, id, mode 쿼리 파라미터 읽기 + const itemType = searchParams.get('type') || 'FG'; + const itemId = searchParams.get('id') || ''; + const mode = searchParams.get('mode') === 'edit' ? 'edit' : 'view'; - if (isLoading) { - return ; + // 품목 코드 디코딩 + const itemCode = decodeURIComponent(id); + + if (mode === 'edit') { + return ; } - if (!item) { - return ( - - ); - } - - return ( -
- -
- ); -} \ No newline at end of file + return ; +} diff --git a/src/app/[locale]/(protected)/production/screen-production/create/page.tsx b/src/app/[locale]/(protected)/production/screen-production/create/page.tsx index 96919eb1..a14c0810 100644 --- a/src/app/[locale]/(protected)/production/screen-production/create/page.tsx +++ b/src/app/[locale]/(protected)/production/screen-production/create/page.tsx @@ -1,28 +1,101 @@ /** * 품목 등록 페이지 + * + * DynamicItemForm을 사용하여 품목기준관리 데이터 기반 동적 폼 렌더링 */ 'use client'; -import ItemForm from '@/components/items/ItemForm'; -import type { CreateItemFormData } from '@/lib/utils/validation'; +import { useState } from 'react'; +import DynamicItemForm from '@/components/items/DynamicItemForm'; +import type { DynamicFormData } from '@/components/items/DynamicItemForm/types'; +// 2025-12-16: options 관련 변환 로직 제거 +// 백엔드가 품목기준관리 field_key 매핑을 처리하므로 프론트에서 변환 불필요 +import { DuplicateCodeError } from '@/lib/api/error-handler'; + +// 기존 ItemForm (주석처리 - 롤백 시 사용) +// import ItemForm from '@/components/items/ItemForm'; +// import type { CreateItemFormData } from '@/lib/utils/validation'; export default function CreateItemPage() { - const handleSubmit = async (data: CreateItemFormData) => { - // TODO: API 연동 시 createItem() 호출 - console.log('품목 등록 데이터:', data); + const [submitError, setSubmitError] = useState(null); - // Mock: 성공 메시지 - alert(`품목 "${data.itemName}" (${data.itemCode})이(가) 등록되었습니다.`); + const handleSubmit = async (data: DynamicFormData) => { + setSubmitError(null); - // API 연동 예시: - // const newItem = await createItem(data); - // router.push(`/items/${newItem.itemCode}`); + // 필드명 변환: spec → specification (백엔드 API 규격) + const submitData = { ...data }; + if (submitData.spec !== undefined) { + submitData.specification = submitData.spec; + delete submitData.spec; + } + + // 2025-12-15: item_type은 Request Body에서 필수 (ItemService.store validation) + // product_type과 item_type을 동일하게 설정 + const itemType = submitData.product_type as string; + submitData.item_type = itemType; + + // API 호출 전 이미지 데이터 제거 (파일 업로드는 별도 API 사용) + // bending_diagram이 base64 데이터인 경우 제거 (JSON에 포함시키면 안됨) + if (submitData.bending_diagram && typeof submitData.bending_diagram === 'string' && (submitData.bending_diagram as string).startsWith('data:')) { + delete submitData.bending_diagram; + } + // 시방서/인정서 파일 필드도 base64면 제거 + if (submitData.specification_file && typeof submitData.specification_file === 'string' && (submitData.specification_file as string).startsWith('data:')) { + delete submitData.specification_file; + } + if (submitData.certification_file && typeof submitData.certification_file === 'string' && (submitData.certification_file as string).startsWith('data:')) { + delete submitData.certification_file; + } + + // API 호출: POST /api/proxy/items + // 백엔드에서 product_type에 따라 Product/Material 분기 처리 + const response = await fetch('/api/proxy/items', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(submitData), + }); + + const result = await response.json(); + + if (!response.ok || !result.success) { + // 2025-12-11: 백엔드 중복 에러 처리 (DuplicateCodeException) + // duplicate_id가 있으면 DuplicateCodeError throw → DynamicItemForm에서 다이얼로그 표시 + if (response.status === 400 && result.duplicate_id) { + console.warn('[CreateItemPage] 품목코드 중복 에러:', result); + throw new DuplicateCodeError( + result.message || '해당 품목코드가 이미 존재합니다.', + result.duplicate_id, + result.duplicate_code + ); + } + + const errorMessage = result.message || '품목 등록에 실패했습니다.'; + console.error('[CreateItemPage] 품목 등록 실패:', errorMessage); + setSubmitError(errorMessage); + throw new Error(errorMessage); + } + + // 성공 시 DynamicItemForm 내부에서 /items로 리다이렉트 처리됨 + // console.log('[CreateItemPage] 품목 등록 성공:', result.data); + + // 생성된 품목 ID를 포함한 데이터 반환 (파일 업로드용) + return { id: result.data.id, ...result.data }; }; return (
- + {submitError && ( +
+ ⚠️ {submitError} +
+ )} +
); } \ No newline at end of file diff --git a/src/app/[locale]/(protected)/production/screen-production/page.tsx b/src/app/[locale]/(protected)/production/screen-production/page.tsx index e9c54b24..d0ffef41 100644 --- a/src/app/[locale]/(protected)/production/screen-production/page.tsx +++ b/src/app/[locale]/(protected)/production/screen-production/page.tsx @@ -1,9 +1,9 @@ -'use client'; - /** - * 품목 목록 페이지 (Client Component) + * 품목 관리 페이지 * - * Next.js 15 App Router + * 품목기준관리 API 연동 + * - 품목 목록: API에서 조회 + * - 테이블 컬럼: custom-tabs API에서 동적 구성 */ import ItemListClient from '@/components/items/ItemListClient'; @@ -13,4 +13,12 @@ import ItemListClient from '@/components/items/ItemListClient'; */ export default function ItemsPage() { return ; -} \ No newline at end of file +} + +/** + * 메타데이터 설정 + */ +export const metadata = { + title: '품목 관리', + description: '품목 목록 조회 및 관리', +}; \ No newline at end of file diff --git a/src/components/items/DynamicItemForm/index.tsx b/src/components/items/DynamicItemForm/index.tsx index 92c1fa85..d2eadfdd 100644 --- a/src/components/items/DynamicItemForm/index.tsx +++ b/src/components/items/DynamicItemForm/index.tsx @@ -419,7 +419,7 @@ export default function DynamicItemForm({ } } - router.push('/items'); + router.push('/production/screen-production'); router.refresh(); }); } catch (error) { @@ -603,7 +603,7 @@ export default function DynamicItemForm({ const itemType = duplicateCheckResult.duplicateItemType || selectedItemType || 'PT'; const itemId = duplicateCheckResult.duplicateId; // code는 없으므로 id를 path에 사용 (edit 페이지에서 id 쿼리 파라미터로 조회) - router.push(`/items/${itemId}/edit?type=${itemType}&id=${itemId}`); + router.push(`/production/screen-production/${itemId}/edit?type=${itemType}&id=${itemId}`); } }; diff --git a/src/components/items/ItemDetailClient.tsx b/src/components/items/ItemDetailClient.tsx index 15a8fae8..db7a2a2f 100644 --- a/src/components/items/ItemDetailClient.tsx +++ b/src/components/items/ItemDetailClient.tsx @@ -122,7 +122,7 @@ export default function ItemDetailClient({ item }: ItemDetailClientProps) {