# UniversalListPage 검수 패턴 가이드 > **목적**: 55개 페이지 검수 시 발생하는 공통 에러 패턴과 해결책 정리 > **작성일**: 2026-01-16 > **기준**: 지금까지 검수 중 발견된 13개 이상의 에러 분석 --- ## 검수 항목 체크리스트 | 항목 | 아이콘 | 설명 | |------|--------|------| | 검색 | 🔍 | 검색창 입력 시 필터링 동작 | | 탭 | 📑 | 탭 버튼 클릭 시 데이터 전환 | | 필터 | 🎛️ | 필터 선택/적용/초기화 동작 | | 체크박스 | ☑️ | 테이블 행 체크박스 선택 동작 | | 상세 | 👁️ | 테이블 로우 클릭 → 상세페이지/모달 이동 | | 등록 | ➕ | 등록 버튼 클릭 → 등록페이지 이동 | --- ## 🚨 공통 에러 패턴 및 해결책 ### 1. `headerActions.call is not a function` **증상**: 페이지 로드 시 에러 발생, 콘솔에 에러 메시지 표시 **원인**: `headerActions`가 ReactNode로 정의되어 있음 (함수가 아님) **잘못된 코드**: ```typescript // ❌ ReactNode로 정의 const headerActions = ( ); ``` **올바른 코드**: ```typescript // ✅ 함수로 정의 const headerActions = () => ( ); ``` --- ### 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()); }, }; ``` **원인 B (서버 사이드 필터링)**: - `onTabChange` prop이 누락됨 **해결책 B**: ```typescript // ✅ onTabChange prop 추가 ``` --- ### 3. 승인/거절 팝업에 선택 건수가 0으로 표시 **증상**: 체크박스 선택 후 버튼 클릭하면 팝업에 "0건" 표시 **원인**: `headerActions`에서 받는 `selected`와 컴포넌트 내부 `selectedItems` 상태가 동기화되지 않음 **잘못된 코드**: ```typescript // ❌ selected를 내부 상태로 복사하지 않음 const handleApproveClick = useCallback(() => { setApproveDialogOpen(true); }, []); // headerActions에서 ``` **올바른 코드**: ```typescript // ✅ selected를 받아서 내부 상태로 복사 const handleApproveClick = useCallback((selected: Set) => { setSelectedItems(selected); // 복사! setApproveDialogOpen(true); }, []); // headerActions에서 headerActions: ({ selected }) => ( ) ``` --- ### 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개 에러 패턴 분석) |