- claudedocs 폴더 구조 재정리: archive/sessions, guides/migration·mobile·universal-list, refactoring 분류 - 오래된 세션 컨텍스트/체크리스트 문서 정리 (아카이브 이동 또는 삭제) - AuthContext → authStore(Zustand) 전환 시작, RootProvider 간소화 - GenericCRUDDialog 공통 다이얼로그 컴포넌트 추가 - PermissionDialog 삭제 → GenericCRUDDialog로 대체 - RankDialog/TitleDialog GenericCRUDDialog 기반으로 리팩토링 - toast-utils.ts 삭제 (미사용) - fileDownload.ts 개선, excel-download.ts 정리 - menuStore/themeStore Zustand 셀렉터 최적화 - useColumnSettings/useTableColumnStore 기능 보강 - 세금계산서/견적/작업자화면/결재 등 소규모 개선 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8.1 KiB
8.1 KiB
UniversalListPage 검수 패턴 가이드
목적: 55개 페이지 검수 시 발생하는 공통 에러 패턴과 해결책 정리 작성일: 2026-01-16 기준: 지금까지 검수 중 발견된 13개 이상의 에러 분석
검수 항목 체크리스트
| 항목 | 아이콘 | 설명 |
|---|---|---|
| 검색 | 🔍 | 검색창 입력 시 필터링 동작 |
| 탭 | 📑 | 탭 버튼 클릭 시 데이터 전환 |
| 필터 | 🎛️ | 필터 선택/적용/초기화 동작 |
| 체크박스 | ☑️ | 테이블 행 체크박스 선택 동작 |
| 상세 | 👁️ | 테이블 로우 클릭 → 상세페이지/모달 이동 |
| 등록 | ➕ | 등록 버튼 클릭 → 등록페이지 이동 |
🚨 공통 에러 패턴 및 해결책
1. headerActions.call is not a function
증상: 페이지 로드 시 에러 발생, 콘솔에 에러 메시지 표시
원인: headerActions가 ReactNode로 정의되어 있음 (함수가 아님)
잘못된 코드:
// ❌ ReactNode로 정의
const headerActions = (
<Button onClick={() => console.log('click')}>
버튼
</Button>
);
올바른 코드:
// ✅ 함수로 정의
const headerActions = () => (
<Button onClick={() => console.log('click')}>
버튼
</Button>
);
2. 탭 클릭해도 데이터가 변경되지 않음
증상: 탭 버튼 클릭은 되지만 테이블 데이터가 그대로 유지됨
원인 A (클라이언트 사이드 필터링):
filteredData(이미 필터링된 데이터)를initialData에 전달- UniversalListPage 내부 상태가 외부 데이터 변경을 감지 못함
해결책 A:
// ✅ 전체 데이터 전달 + 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 (서버 사이드 필터링):
onTabChangeprop이 누락됨
해결책 B:
// ✅ onTabChange prop 추가
<UniversalListPage
config={config}
initialData={items}
onTabChange={handleTypeChange} // ✅ 추가
externalPagination={{...}}
/>
3. 승인/거절 팝업에 선택 건수가 0으로 표시
증상: 체크박스 선택 후 버튼 클릭하면 팝업에 "0건" 표시
원인: headerActions에서 받는 selected와 컴포넌트 내부 selectedItems 상태가 동기화되지 않음
잘못된 코드:
// ❌ selected를 내부 상태로 복사하지 않음
const handleApproveClick = useCallback(() => {
setApproveDialogOpen(true);
}, []);
// headerActions에서
<Button onClick={() => handleApproveClick()}>승인</Button>
올바른 코드:
// ✅ 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 프로퍼티 이름이 타입과 불일치
잘못된 코드:
// ❌ 잘못된 프로퍼티 이름
externalSelection={{
selectedItems,
setSelectedItems, // ❌
toggleSelection, // ❌
toggleSelectAll, // ❌
}}
올바른 코드:
// ✅ 올바른 프로퍼티 이름
externalSelection={{
selectedItems,
onToggleSelection: toggleSelection, // ✅
onToggleSelectAll: toggleSelectAll, // ✅
getItemId: (item) => item.id,
}}
5. externalPagination NaN 또는 globalIndex 오류
증상: 번호 컬럼에 NaN 표시, 페이지네이션 동작 안함
원인: externalPagination 프로퍼티 형태 불일치
올바른 형태:
externalPagination={{
currentPage: pagination.currentPage,
totalPages: pagination.totalPages,
totalItems: pagination.totalItems,
itemsPerPage: pagination.perPage, // ✅ itemsPerPage (perPage 아님)
onPageChange: handlePageChange,
}}
6. 프리셋 버튼 (당월/전월/오늘) 미표시
증상: DateRangeSelector는 표시되지만 프리셋 버튼 없음
원인: showPresets: false 설정
해결책:
dateRangeSelector: {
enabled: true,
showPresets: true, // ✅ true로 설정
startDate,
endDate,
onStartDateChange,
onEndDateChange,
},
7. 탭 카운트가 모두 동일하게 표시
증상: 모든 탭에 같은 숫자가 표시됨
원인: config.tabs 변경 시 UniversalListPage 내부 상태가 업데이트되지 않음
해결책 (이미 UniversalListPage에 적용됨):
// 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개 에러 패턴 분석) |