Files
sam-react-prod/claudedocs/guides/universal-list/[REF] UniversalListPage-QA-patterns.md
유병철 07374c826c refactor(WEB): claudedocs 재정리 및 AuthContext/Zustand/유틸 코드 개선
- 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>
2026-02-23 17:17:13 +09:00

8.1 KiB
Raw Blame History

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 (서버 사이드 필터링):

  • onTabChange prop이 누락됨

해결책 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개 에러 패턴 분석)