feat(WEB): Phase 2-3 V2 마이그레이션 완료 및 ServerErrorPage 적용

Phase 2 완료 (4개):
- 노무관리, 단가관리(건설), 입금, 출금

Phase 3 라우팅 구조 변경 완료 (22개):
- 거래처(영업), 팝업관리, 공정관리, 게시판관리, 대손추심, Q&A
- 현장관리, 실행내역, 견적관리, 견적(테스트)
- 입찰관리, 이슈관리, 현장설명회, 견적서(건설)
- 협력업체, 시공관리, 기성관리, 품목관리(건설)
- 회계 도메인: 거래처, 매출, 세금계산서, 매입

신규 컴포넌트:
- ErrorCard: 에러 페이지 UI 통일
- ServerErrorPage: V2 페이지 에러 처리 필수
- V2 Client 컴포넌트 및 Config 파일들

총 47개 상세 페이지 중 28개 완료, 19개 제외/불필요

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-19 17:31:28 +09:00
parent 1a6cde2d36
commit 1d7b028693
109 changed files with 6811 additions and 2562 deletions

View File

@@ -0,0 +1,158 @@
# 공통 컴포넌트 패턴 분석
> Phase 3 마이그레이션 진행하면서 발견되는 공통화 후보 패턴 수집
> 페이지 마이그레이션 완료 후 이 리스트 기반으로 공통 컴포넌트 설계
## 최종 목표: IntegratedDetailTemplate Config 통합
공통 컴포넌트 추출 후 `IntegratedDetailTemplate`의 필드 config 옵션으로 통합
**현재 지원 타입:**
```typescript
type: 'text' | 'select' | 'date' | 'textarea' | 'number' | 'checkbox'
```
**확장 예정 타입:**
```typescript
// 주소 입력
{ name: 'address', label: '주소', type: 'address', withCoords?: boolean }
// 파일 업로드
{ name: 'files', label: '첨부파일', type: 'file-upload', maxSize?: number, multiple?: boolean, accept?: string }
// 음성 메모
{ name: 'memo', label: '메모', type: 'voice-memo' }
// 삭제 확인 (페이지 레벨 옵션)
deleteConfirm?: { title: string, message: string }
```
**장점:**
- 페이지별 config만 수정하면 UI 자동 구성
- 일관된 UX/UI 보장
- 유지보수 용이
## 발견된 패턴 목록
### 1. 주소 입력 (Address Input)
| 발견 위치 | 구성 요소 | 특이사항 |
|-----------|-----------|----------|
| site-management/SiteDetailForm | 우편번호 찾기 버튼 + 주소 Input + 상세주소 | 경도/위도 필드 별도 |
**공통 요소:**
- Daum Postcode API 연동 (`useDaumPostcode` 훅 이미 존재)
- 우편번호 찾기 버튼
- 기본주소 (자동 입력)
- 상세주소 (수동 입력)
**변형 가능성:**
- 경도/위도 필드 포함 여부
- 읽기 전용 모드 지원
---
### 2. 파일 업로드 (File Upload)
| 발견 위치 | 구성 요소 | 특이사항 |
|-----------|-----------|----------|
| site-management/SiteDetailForm | 드래그앤드롭 영역 + 파일 목록 | 10MB 제한, 다중 파일 |
| structure-review/StructureReviewDetailForm | 드래그앤드롭 영역 + 파일 목록 | 10MB 제한, 다중 파일 (동일 패턴) |
**공통 요소:**
- 드래그앤드롭 영역 (점선 박스)
- 클릭하여 파일 선택
- 드래그 중 시각적 피드백
- 파일 크기 검증
**변형 가능성:**
- 허용 파일 타입 (이미지만 / 문서만 / 전체)
- 단일 vs 다중 파일
- 최대 파일 크기
- 최대 파일 개수
---
### 3. 파일 목록 (File List)
| 발견 위치 | 구성 요소 | 특이사항 |
|-----------|-----------|----------|
| site-management/SiteDetailForm | 파일명 + 크기 + 다운로드/삭제 | view/edit 모드별 다른 액션 |
| structure-review/StructureReviewDetailForm | 파일명 + 크기 + 다운로드/삭제 | 동일 패턴 |
**공통 요소:**
- 파일 아이콘
- 파일명 표시
- 파일 크기 표시
- 액션 버튼
**변형 가능성:**
- view 모드: 다운로드 버튼
- edit 모드: 삭제(X) 버튼
- 미리보기 지원 (이미지)
- 업로드 날짜 표시 여부
---
### 4. 음성 녹음 (Voice Recorder)
| 발견 위치 | 구성 요소 | 특이사항 |
|-----------|-----------|----------|
| site-management/SiteDetailForm | 녹음 버튼 (Textarea 내부) | edit 모드에서만 표시 |
**공통 요소:**
- 녹음 시작/중지 버튼
- Textarea와 연동 (STT 결과 입력)
**변형 가능성:**
- 버튼 위치 (Textarea 내부 / 외부)
- 녹음 시간 제한
- 녹음 중 시각적 피드백
---
### 5. 삭제 확인 다이얼로그 (Delete Confirmation Dialog)
| 발견 위치 | 구성 요소 | 특이사항 |
|-----------|-----------|----------|
| structure-review/StructureReviewDetailForm | AlertDialog + 제목 + 설명 + 취소/삭제 버튼 | view 모드에서 삭제 버튼 클릭 시 |
**공통 요소:**
- AlertDialog 컴포넌트 사용
- 제목: "[항목명] 삭제"
- 설명: 삭제 확인 메시지 + 되돌릴 수 없음 경고
- 취소/삭제 버튼
**변형 가능성:**
- 항목명 커스터마이징
- 삭제 후 리다이렉트 경로
- 추가 경고 메시지
---
## 추가 예정
> Phase 3 마이그레이션 진행하면서 새로운 패턴 발견 시 여기에 추가
### 예상 패턴 (확인 필요)
- [ ] 이미지 미리보기 (썸네일)
- [ ] 서명 입력
- [ ] 날짜/시간 선택
- [ ] 검색 가능한 Select (Combobox)
- [ ] 태그 입력
- [ ] 금액 입력 (천단위 콤마)
---
## 공통화 우선순위 (마이그레이션 완료 후 결정)
| 우선순위 | 패턴 | 사용 빈도 | 복잡도 |
|----------|------|-----------|--------|
| - | 주소 입력 | 1 | 중 |
| - | 파일 업로드 | 2 | 상 |
| - | 파일 목록 | 2 | 중 |
| - | 음성 녹음 | 1 | 상 |
| - | 삭제 확인 다이얼로그 | 1 | 하 |
> 사용 빈도는 마이그레이션 진행하면서 카운트
---
## 변경 이력
- 2025-01-19: 초안 작성, site-management에서 4개 패턴 발견
- 2025-01-19: structure-review에서 동일 패턴 확인 (파일 업로드/목록), 삭제 확인 다이얼로그 패턴 추가

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,155 @@
# 상세/등록/수정 페이지 패턴 분류표
> Chrome DevTools MCP로 직접 확인한 결과 기반 (2026-01-19)
## 패턴 분류 기준
### 1⃣ 페이지 형태 - 하단 버튼 (표준 패턴)
- URL이 변경되며 별도 페이지로 이동
- 버튼 위치: **하단** (좌: 목록/취소, 우: 삭제/수정/저장)
- **IntegratedDetailTemplate 적용 대상**
### 2⃣ 페이지 형태 - 상단 버튼
- URL이 변경되며 별도 페이지로 이동
- 버튼 위치: **상단**
- IntegratedDetailTemplate 확장 필요 (`buttonPosition="top"`)
### 3⃣ 모달 형태
- URL 변경 없음, Dialog/Modal로 표시
- **IntegratedDetailTemplate 적용 제외**
### 4⃣ 인라인 입력 형태
- 리스트 페이지 내에서 직접 입력/수정
- **IntegratedDetailTemplate 적용 제외**
### 5⃣ DynamicForm 형태
- API 기반 동적 폼 생성
- IntegratedDetailTemplate의 `renderForm` prop으로 분기 처리
---
## 📄 페이지 형태 - 하단 버튼 (통합 대상)
| 도메인 | 페이지 | URL 패턴 | 상태 |
|--------|--------|----------|------|
| **설정** | 계좌관리 | `/settings/accounts/[id]`, `/new` | ✅ 이미 마이그레이션 완료 |
| **설정** | 카드관리 | `/hr/card-management/[id]`, `/new` | ✅ 이미 마이그레이션 완료 |
| **설정** | 팝업관리 | `/settings/popup-management/[id]`, `/new` | 🔄 대상 |
| **설정** | 게시판관리 | `/board/board-management/[id]`, `/new` | 🔄 대상 |
| **기준정보** | 공정관리 | `/master-data/process-management/[id]`, `/new` | 🔄 대상 |
| **판매** | 거래처관리 | `/sales/client-management-sales-admin/[id]`, `/new` | 🔄 대상 |
| **판매** | 견적관리 | `/sales/quote-management/[id]`, `/new` | 🔄 대상 |
| **판매** | 수주관리 | `/sales/order-management-sales/[id]`, `/new` | 🔄 대상 |
| **품질** | 검사관리 | `/quality/inspections/[id]`, `/new` | 🔄 대상 |
| **출고** | 출하관리 | `/outbound/shipments/[id]`, `/new` | 🔄 대상 |
| **고객센터** | 공지사항 | `/customer-center/notices/[id]` | 🔄 대상 |
| **고객센터** | 이벤트 | `/customer-center/events/[id]` | 🔄 대상 |
---
## 📄 페이지 형태 - 상단 버튼 (확장 필요)
| 도메인 | 페이지 | URL 패턴 | 버튼 구성 | 비고 |
|--------|--------|----------|-----------|------|
| **회계** | 거래처관리 | `/accounting/vendors/[id]`, `/new` | 목록/삭제/수정 | 다중 섹션 구조 |
| **회계** | 매출관리 | `/accounting/sales/[id]`, `/new` | - | 🔄 대상 |
| **회계** | 매입관리 | `/accounting/purchase/[id]` | - | 🔄 대상 |
| **회계** | 입금관리 | `/accounting/deposits/[id]` | - | 🔄 대상 |
| **회계** | 출금관리 | `/accounting/withdrawals/[id]` | - | 🔄 대상 |
| **회계** | 어음관리 | `/accounting/bills/[id]`, `/new` | - | 🔄 대상 |
| **회계** | 악성채권 | `/accounting/bad-debt-collection/[id]`, `/new` | - | 🔄 대상 |
| **전자결재** | 기안함 (임시저장) | `/approval/draft/new?id=:id&mode=edit` | 상세/삭제/상신/저장 | 복잡한 섹션 구조 |
---
## 🔲 모달 형태 (통합 제외)
| 도메인 | 페이지 | 모달 컴포넌트 | 비고 |
|--------|--------|--------------|------|
| **설정** | 직급관리 | `RankDialog.tsx` | 인라인 입력 + 수정 모달 |
| **설정** | 직책관리 | `TitleDialog.tsx` | 인라인 입력 + 수정 모달 |
| **인사** | 부서관리 | `DepartmentDialog.tsx` | 트리 구조 |
| **인사** | 근태관리 | `AttendanceInfoDialog.tsx` | 모달로 등록 |
| **인사** | 휴가관리 | `VacationRequestDialog.tsx` | 모달로 등록/조정 |
| **인사** | 급여관리 | `SalaryDetailDialog.tsx` | 모달로 상세 |
| **전자결재** | 기안함 (결재대기) | 품의서 상세 Dialog | 상세만 모달 |
| **건설** | 카테고리관리 | `CategoryDialog.tsx` | 모달로 등록/수정 |
---
## 🔧 DynamicForm 형태 (renderForm 분기)
| 도메인 | 페이지 | URL 패턴 | 비고 |
|--------|--------|----------|------|
| **품목** | 품목관리 | `/items/[id]` | `DynamicItemForm` 사용 |
---
## ⚠️ 특수 케이스 (개별 처리 필요)
| 도메인 | 페이지 | URL 패턴 | 특이사항 |
|--------|--------|----------|----------|
| **설정** | 권한관리 | `/settings/permissions/[id]`, `/new` | Matrix UI, 복잡한 구조 |
| **인사** | 사원관리 | `/hr/employee-management/[id]`, `/new` | 40+ 필드, 탭 구조 |
| **게시판** | 게시글 | `/board/[boardCode]/[postId]` | 동적 게시판 |
| **건설** | 다수 페이지 | `/construction/...` | 별도 분류 필요 |
---
## 📊 통합 우선순위
### Phase 1: 단순 CRUD (우선 작업)
1. 팝업관리
2. 게시판관리
3. 공정관리
4. 공지사항/이벤트
### Phase 2: 중간 복잡도
1. 판매 > 거래처관리
2. 판매 > 견적관리
3. 품질 > 검사관리
4. 출고 > 출하관리
### Phase 3: 회계 도메인 (상단 버튼 확장 후)
1. 회계 > 거래처관리
2. 회계 > 매출/매입/입금/출금
3. 회계 > 어음/악성채권
### 제외 (개별 유지)
- 권한관리 (Matrix UI)
- 사원관리 (40+ 필드)
- 부서관리 (트리 구조)
- 전자결재 (복잡한 워크플로우)
- DynamicForm 페이지 (renderForm 분기)
- 모달 형태 페이지들
---
## IntegratedDetailTemplate 확장 필요 Props
```typescript
interface IntegratedDetailTemplateProps {
// 기존 props...
// 버튼 위치 제어
buttonPosition?: 'top' | 'bottom'; // default: 'bottom'
// 뒤로가기 버튼 표시 여부
showBackButton?: boolean; // default: true
// 상단 버튼 커스텀 (문서 결재 등)
headerActions?: ReactNode;
// 다중 섹션 지원
sections?: Array<{
title: string;
fields: FieldConfig[];
}>;
}
```
---
## 작성일
- 최초 작성: 2026-01-19
- Chrome DevTools MCP 확인 완료

View File

@@ -0,0 +1,118 @@
# Chrome DevTools MCP - 이모지 JSON 직렬화 오류
> 작성일: 2025-01-17
## 문제 현상
Chrome DevTools MCP가 특정 페이지 접근 시 다운되는 현상
### 에러 메시지
```
API Error: 400 {"type":"error","error":{"type":"invalid_request_error",
"message":"The request body is not valid JSON: invalid high surrogate in string:
line 1 column XXXXX (char XXXXX)"},"request_id":"req_XXXXX"}
```
### 발생 조건
- 페이지에 **이모지**가 많이 포함된 경우
- `take_snapshot` 또는 다른 MCP 도구 호출 시
- a11y tree를 JSON으로 직렬화하는 과정에서 발생
## 원인
### 유니코드 서로게이트 쌍 (Surrogate Pair) 문제
이모지는 UTF-16에서 **서로게이트 쌍**으로 인코딩됨:
- High surrogate: U+D800 ~ U+DBFF
- Low surrogate: U+DC00 ~ U+DFFF
Chrome DevTools MCP가 페이지 스냅샷을 JSON으로 직렬화할 때, 이모지의 서로게이트 쌍이 깨지면서 "invalid high surrogate" 오류 발생.
### 문제가 되는 케이스
1. **DOM에 직접 렌더링된 이모지**: `<span>🏠</span>`
2. **데이터에 포함된 이모지**: API 응답, 파싱된 데이터
3. **대량의 이모지**: 수십 개 이상의 이모지가 한 페이지에 존재
## 해결 방법
### 1. 이모지를 Lucide 아이콘으로 교체 (UI)
**Before**
```tsx
const iconMap = {
'기본': '🏠',
'인사관리': '👥',
};
<span className="text-xl">{category.icon}</span>
```
**After**
```tsx
import { Home, Users, type LucideIcon } from 'lucide-react';
const iconComponents: Record<string, LucideIcon> = {
Home,
Users,
};
function CategoryIcon({ name }: { name: string }) {
const IconComponent = iconComponents[name] || FileText;
return <IconComponent className="w-5 h-5" />;
}
<CategoryIcon name={category.icon} />
```
### 2. 데이터 파싱 시 이모지 제거/변환 (Server)
```typescript
function convertEmojiToText(text: string): string {
// 특정 이모지를 의미있는 텍스트로 변환
let result = text
.replace(/✅/g, '[완료]')
.replace(/⚠️?/g, '[주의]')
.replace(/🧪/g, '[테스트]')
.replace(/🆕/g, '[NEW]')
.replace(/•/g, '-');
// 모든 이모지 및 특수 유니코드 문자 제거
result = result
.replace(/[\u{1F300}-\u{1F9FF}]/gu, '') // 이모지 범위
.replace(/[\u{2600}-\u{26FF}]/gu, '') // 기타 기호
.replace(/[\u{2700}-\u{27BF}]/gu, '') // 딩뱃
.replace(/[\u{FE00}-\u{FE0F}]/gu, '') // Variation Selectors
.replace(/[\u{1F000}-\u{1F02F}]/gu, '') // 마작 타일
.replace(/[\u{1F0A0}-\u{1F0FF}]/gu, '') // 플레잉 카드
.replace(/[\u200D]/g, '') // Zero Width Joiner
.trim();
return result;
}
```
## 체크리스트
새 페이지 개발 시 Chrome DevTools MCP 호환성 확인:
- [ ] 페이지에 이모지 직접 렌더링하지 않음
- [ ] 아이콘은 Lucide 또는 SVG 사용
- [ ] 외부 데이터(API, 파일) 파싱 시 이모지 제거 처리
- [ ] status, label 등에 이모지 대신 텍스트 사용
## 관련 파일
이 문제로 수정된 파일들:
| 파일 | 변경 내용 |
|------|----------|
| `dev/test-urls/actions.ts` | iconMap, convertEmojiToText 함수 추가 |
| `dev/test-urls/TestUrlsClient.tsx` | Lucide 아이콘 동적 렌더링 |
| `dev/construction-test-urls/actions.ts` | 동일 |
| `dev/construction-test-urls/ConstructionTestUrlsClient.tsx` | 동일 |
## 참고
- 이 문제는 Chrome DevTools MCP의 JSON 직렬화 로직에서 발생
- MCP 자체 버그일 가능성 있으나, 클라이언트에서 이모지 제거로 우회 가능
- 다른 MCP 도구에서도 비슷한 문제 발생 가능성 있음