Files
sam-react-prod/claudedocs/[REF] UniversalListPage-QA-patterns.md
byeongcheolryu ad493bcea6 feat(WEB): UniversalListPage 전체 마이그레이션 및 코드 정리
- UniversalListPage/IntegratedListTemplateV2 컴포넌트 기능 개선
- 회계, HR, 건설, 고객센터, 결재, 설정 등 전체 리스트 컴포넌트 마이그레이션
- 테스트 페이지 및 미사용 API 라우트 정리 (board-test, order-management-test 등)
- 미들웨어 토큰 갱신 로직 개선
- AuthenticatedLayout 구조 개선
- claudedocs 문서 업데이트

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 15:19:09 +09:00

314 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# UniversalListPage 검수 패턴 가이드
> **목적**: 55개 페이지 검수 시 발생하는 공통 에러 패턴과 해결책 정리
> **작성일**: 2026-01-16
> **기준**: 지금까지 검수 중 발견된 13개 이상의 에러 분석
---
## 검수 항목 체크리스트
| 항목 | 아이콘 | 설명 |
|------|--------|------|
| 검색 | 🔍 | 검색창 입력 시 필터링 동작 |
| 탭 | 📑 | 탭 버튼 클릭 시 데이터 전환 |
| 필터 | 🎛️ | 필터 선택/적용/초기화 동작 |
| 체크박스 | ☑️ | 테이블 행 체크박스 선택 동작 |
| 상세 | 👁️ | 테이블 로우 클릭 → 상세페이지/모달 이동 |
| 등록 | | 등록 버튼 클릭 → 등록페이지 이동 |
---
## 🚨 공통 에러 패턴 및 해결책
### 1. `headerActions.call is not a function`
**증상**: 페이지 로드 시 에러 발생, 콘솔에 에러 메시지 표시
**원인**: `headerActions`가 ReactNode로 정의되어 있음 (함수가 아님)
**잘못된 코드**:
```typescript
// ❌ ReactNode로 정의
const headerActions = (
<Button onClick={() => console.log('click')}>
버튼
</Button>
);
```
**올바른 코드**:
```typescript
// ✅ 함수로 정의
const headerActions = () => (
<Button onClick={() => console.log('click')}>
버튼
</Button>
);
```
---
### 2. 탭 클릭해도 데이터가 변경되지 않음
**증상**: 탭 버튼 클릭은 되지만 테이블 데이터가 그대로 유지됨
**원인 A (클라이언트 사이드 필터링)**:
- `filteredData`(이미 필터링된 데이터)를 `initialData`에 전달
- UniversalListPage 내부 상태가 외부 데이터 변경을 감지 못함
**해결책 A**:
```typescript
// ✅ 전체 데이터 전달 + tabFilter 함수 추가
const config = {
// ...
clientSideFiltering: true,
tabFilter: (item, activeTab) => {
if (activeTab === 'all') return true;
return item.type === activeTab;
},
searchFilter: (item, search) => {
return item.name.toLowerCase().includes(search.toLowerCase());
},
};
<UniversalListPage
config={config}
initialData={data} // ✅ 전체 데이터
onTabChange={setActiveTab}
/>
```
**원인 B (서버 사이드 필터링)**:
- `onTabChange` prop이 누락됨
**해결책 B**:
```typescript
// ✅ onTabChange prop 추가
<UniversalListPage
config={config}
initialData={items}
onTabChange={handleTypeChange} // ✅ 추가
externalPagination={{...}}
/>
```
---
### 3. 승인/거절 팝업에 선택 건수가 0으로 표시
**증상**: 체크박스 선택 후 버튼 클릭하면 팝업에 "0건" 표시
**원인**: `headerActions`에서 받는 `selected`와 컴포넌트 내부 `selectedItems` 상태가 동기화되지 않음
**잘못된 코드**:
```typescript
// ❌ selected를 내부 상태로 복사하지 않음
const handleApproveClick = useCallback(() => {
setApproveDialogOpen(true);
}, []);
// headerActions에서
<Button onClick={() => handleApproveClick()}>승인</Button>
```
**올바른 코드**:
```typescript
// ✅ selected를 받아서 내부 상태로 복사
const handleApproveClick = useCallback((selected: Set<string>) => {
setSelectedItems(selected); // 복사!
setApproveDialogOpen(true);
}, []);
// headerActions에서
headerActions: ({ selected }) => (
<Button onClick={() => handleApproveClick(selected)}>승인</Button>
)
```
---
### 4. `externalSelection.onToggleSelection is not a function`
**증상**: 체크박스 클릭 시 에러 발생
**원인**: `externalSelection` 프로퍼티 이름이 타입과 불일치
**잘못된 코드**:
```typescript
// ❌ 잘못된 프로퍼티 이름
externalSelection={{
selectedItems,
setSelectedItems, // ❌
toggleSelection, // ❌
toggleSelectAll, // ❌
}}
```
**올바른 코드**:
```typescript
// ✅ 올바른 프로퍼티 이름
externalSelection={{
selectedItems,
onToggleSelection: toggleSelection, // ✅
onToggleSelectAll: toggleSelectAll, // ✅
getItemId: (item) => item.id,
}}
```
---
### 5. `externalPagination` NaN 또는 globalIndex 오류
**증상**: 번호 컬럼에 NaN 표시, 페이지네이션 동작 안함
**원인**: `externalPagination` 프로퍼티 형태 불일치
**올바른 형태**:
```typescript
externalPagination={{
currentPage: pagination.currentPage,
totalPages: pagination.totalPages,
totalItems: pagination.totalItems,
itemsPerPage: pagination.perPage, // ✅ itemsPerPage (perPage 아님)
onPageChange: handlePageChange,
}}
```
---
### 6. 프리셋 버튼 (당월/전월/오늘) 미표시
**증상**: DateRangeSelector는 표시되지만 프리셋 버튼 없음
**원인**: `showPresets: false` 설정
**해결책**:
```typescript
dateRangeSelector: {
enabled: true,
showPresets: true, // ✅ true로 설정
startDate,
endDate,
onStartDateChange,
onEndDateChange,
},
```
---
### 7. 탭 카운트가 모두 동일하게 표시
**증상**: 모든 탭에 같은 숫자가 표시됨
**원인**: `config.tabs` 변경 시 UniversalListPage 내부 상태가 업데이트되지 않음
**해결책** (이미 UniversalListPage에 적용됨):
```typescript
// UniversalListPage/index.tsx에서
useEffect(() => {
if (config.tabs) {
setTabs(config.tabs);
}
}, [config.tabs]);
```
---
## 📋 검수 순서 권장
### Step 1: 페이지 로드 확인
- [ ] 에러 없이 페이지 로드되는가?
- [ ] 콘솔에 에러 메시지 없는가?
### Step 2: 기본 UI 확인
- [ ] 테이블/카드 목록 정상 표시되는가?
- [ ] 통계 카드 (있는 경우) 정상 표시되는가?
- [ ] 탭 버튼 (있는 경우) 정상 표시되는가?
### Step 3: 탭 기능 (있는 경우)
- [ ] 탭 클릭 시 데이터가 변경되는가?
- [ ] 탭별 건수가 정확하게 표시되는가?
- [ ] 탭 변경 후 검색/필터가 유지되는가?
### Step 4: 검색 기능
- [ ] 검색창에 입력 시 필터링되는가?
- [ ] 검색어 삭제 시 전체 목록 표시되는가?
### Step 5: 필터 기능 (있는 경우)
- [ ] PC에서 필터 선택 시 데이터 필터링되는가?
- [ ] 모바일에서 필터 바텀시트 열리는가?
- [ ] 필터 적용/초기화 정상 동작하는가?
### Step 6: 체크박스 선택
- [ ] 개별 체크박스 선택/해제 되는가?
- [ ] 전체 선택 체크박스 동작하는가?
- [ ] 선택 건수가 정확히 표시되는가?
### Step 7: 상세 이동
- [ ] 행 클릭 또는 상세 버튼 클릭 시 이동하는가?
- [ ] URL 파라미터 올바르게 전달되는가?
### Step 8: 등록 버튼 (있는 경우)
- [ ] 등록 버튼 표시되는가?
- [ ] 클릭 시 등록 페이지로 이동하는가?
### Step 9: 커스텀 액션 (승인/거절/삭제 등)
- [ ] 버튼이 올바른 위치에 표시되는가?
- [ ] 선택된 항목 수가 정확히 팝업에 표시되는가?
- [ ] 액션 실행 후 데이터가 갱신되는가?
---
## 🔧 데이터 흐름 패턴
### 패턴 A: 클라이언트 사이드 필터링
```
initialData={전체데이터}
config.tabFilter() → 탭 필터링
config.searchFilter() → 검색 필터링
내부 페이지네이션 → displayData
```
**적합한 경우**:
- 데이터량 적음 (500개 이하)
- 전체 데이터를 한번에 로드 가능
### 패턴 B: 서버 사이드 필터링
```
initialData={API로 받은 데이터}
onTabChange → 외부 상태 변경 → API 재호출
onSearchChange → 외부 상태 변경 → API 재호출
externalPagination으로 페이지 제어
```
**적합한 경우**:
- 데이터량 많음 (1000개 이상)
- 페이지네이션된 API 사용
---
## 발견된 에러 통계
| 에러 유형 | 발생 횟수 | 패턴 |
|----------|----------|------|
| headerActions 함수 아님 | 2회 | 거래처관리(영업), 단가관리(판매) |
| 탭 데이터 미갱신 | 2회 | 단가관리(판매), 품목관리 |
| 선택 건수 0 표시 | 1회 | 휴가관리 |
| externalSelection 형태 불일치 | 1회 | 휴가관리 |
| showPresets 누락 | 2회 | 근태관리, 사원관리 |
| 탭 카운트 동기화 | 1회 | 휴가관리 |
---
## 변경 이력
| 날짜 | 내용 |
|------|------|
| 2026-01-16 | 문서 초안 작성 (13개 에러 패턴 분석) |