ERP-node/docs/ycshin-node/PGN[맥락]-페이징-단락이동.md

6.6 KiB

[맥락노트] 페이징 단락(그룹) 번호 네비게이션 - PageGroupNav 공통 컴포넌트

관련 문서: 계획서 | 체크리스트


왜 이 작업을 하는가

  • 현재 페이지네이션은 1 / 38 텍스트만 표시하고 < >로 한 페이지씩 이동
  • 수십 페이지가 있을 때 원하는 페이지로 빠르게 이동할 수 없음
  • 페이지 번호를 직접 클릭할 수 있어야 UX가 개선됨

핵심 결정 사항과 근거

1. 공통 컴포넌트로 분리 (C안)

  • 결정: PageGroupNav.tsx라는 순수 컨트롤 컴포넌트를 별도 파일로 생성
  • 근거: 프로젝트에 페이징이 15곳 이상 존재. 인라인 수정하면 같은 로직을 복사해야 함
  • 대안 검토 A: v2-table-list 인라인만 수정 → 기각 (미래 확장 시 복사-붙여넣기 기술 부채)
  • 대안 검토 B: 기존 Pagination.tsx 업그레이드 → 기각 (전체 행 레이아웃이 포함되어 v2-table-list와 레이아웃 충돌)
  • 대안 검토 D: 전체 한번에 적용 → 기각 (12파일 동시 수정은 블래스트 반경이 큼)

2. 레이아웃 무관 설계

  • 결정: PageGroupNav는 << < [번호들] > >>만 렌더링. 외부 레이아웃(페이지크기, 내보내기 등)을 포함하지 않음
  • 근거: 사용처마다 레이아웃이 다름. v2-table-list는 좌측(페이지크기)+중앙(컨트롤)+우측(내보내기), Pagination.tsx는 좌측(페이지정보)+우측(크기선택+컨트롤). 레이아웃을 강제하면 props 분기가 증가하여 복잡해짐

3. 10개 단위 단락(그룹)

  • 결정: 페이지를 10개씩 묶어 하나의 단락으로 취급
  • 근거: 사용자에게 익숙한 패턴 (네이버, 구글 등). 5개는 너무 적고, 20개는 너무 많음
  • 확장성: groupSize props로 기본값 10을 변경 가능하게 설계

4. < > = 단락 이동, << >> = 첫/끝 단락

  • 결정: <는 이전 단락 첫 페이지, >는 다음 단락 첫 페이지. <<는 1페이지, >>는 마지막 단락 첫 페이지
  • 근거: 사용자 요청. 기존의 "한 페이지씩 이동"은 번호 클릭으로 대체됨
  • 주의: >>는 마지막 페이지가 아닌 마지막 단락의 첫 페이지로 이동. 예: 총 38페이지일 때 >> 클릭 → 31페이지 선택 (38이 아님)

5. 고정 슬롯 + 고정 너비

  • 결정: 항상 10개 슬롯을 렌더링하고, 모든 버튼은 동일한 고정 너비(w-8 sm:w-9)
  • 근거: < > 버튼을 연속 클릭할 때 번호 자릿수(1자리→2자리)나 페이지 수(10개→8개) 변화로 버튼 위치가 흔들리면 안 됨
  • 구현: 마지막 단락에서 페이지가 10개 미만이면 남은 슬롯은 동일 크기의 빈 <div>로 채움

6. 단계적 적용 (1단계: v2-table-list만)

  • 결정: 이번 작업은 v2-table-list에만 적용. 나머지는 별도 작업으로 점진 적용
  • 근거: 15곳 동시 수정은 리스크가 높음. v2-table-list가 가장 많이 사용되므로 여기서 검증 후 확산

7. 비활성화 기준은 단락 기준

  • 결정: << <는 첫 번째 단락일 때 비활성화 (currentPage === 1이 아님). > >>는 마지막 단락일 때 비활성화
  • 근거: 기존은 currentPage 기준이었지만, 단락 이동이므로 단락 기준으로 변경이 자연스러움. 첫 단락 안에서 5페이지에 있어도 <는 비활성화

관련 파일 위치

구분 파일 경로 설명
생성 frontend/components/common/PageGroupNav.tsx 페이지 그룹 네비게이션 공통 컴포넌트
수정 frontend/lib/registry/components/v2-table-list/TableListComponent.tsx paginationJSX 중앙 영역 교체 (5139~5182행)
참고 frontend/components/common/Pagination.tsx 기존 공통 페이지네이션 (이번에 수정 안 함)

기술 참고

단락 계산 공식

groupSize = 10 (기본값)
currentGroupIndex = Math.floor((currentPage - 1) / groupSize)
groupStartPage = currentGroupIndex * groupSize + 1
groupEndPage = Math.min(groupStartPage + groupSize - 1, totalPages)

lastGroupIndex = Math.floor((totalPages - 1) / groupSize)
lastGroupStartPage = lastGroupIndex * groupSize + 1

isFirstGroup = currentGroupIndex === 0
isLastGroup = currentGroupIndex === lastGroupIndex

고정 슬롯 배열 생성

slots = [groupStart, groupStart+1, ..., groupEnd, null, null, ...]  (총 groupSize개)
예: 단락 31~38 → [31, 32, 33, 34, 35, 36, 37, 38, null, null]

handlePageChange 호출 흐름

PageGroupNav onPageChange(page)
  → TableListComponent handlePageChange(newPage)
    → setCurrentPage(newPage)
      → useEffect 트리거 → 백엔드 API 재호출 (page 파라미터 변경)
  • handlePageChange는 setCurrentPage만 호출. onConfigChange 전파는 제거됨 (pageSize/currentPage는 세션 전용)
  • handlePageChange는 기존 함수 그대로 사용. PageGroupNav가 올바른 page 값을 전달하기만 하면 됨

추가 결정: 표시갯수(pageSize) 캐시 정책

8. pageSize는 세션 전용, DB에 저장 안 함

  • 결정: pageSize를 onConfigChange로 부모/DB에 전파하지 않음. sessionStorage에만 탭별로 저장
  • 근거: pageSize는 일시적 탐색 설정이지 영구 화면 설정이 아님. DB에 저장하면 다른 사용자에게도 영향이 가고, 새로고침 시 의도치 않은 값이 남음
  • F5 정책: 활성 탭은 캐시 삭제 → 기본값 20으로 fresh start. 비활성 탭은 캐시 유지

9. 테이블 캐시는 탭별 격리 (탭 ID 스코프)

  • 결정: tableState_*, pageSize_*, filterSettings_*, groupSettings_* 키를 {prefix}_{tabId}_{tableName} 구조로 변경
  • 근거: 같은 테이블이 여러 탭에서 열릴 수 있음. 탭 구분 없으면 "활성 탭 캐시만 삭제" 불가능
  • 구현: useTabId() 훅으로 현재 탭 ID 접근. clearTabCache(tabId)에서 해당 탭의 모든 관련 키 일괄 삭제

10. localStorage vs sessionStorage 분류

  • 결정: 탭별 캐시는 sessionStorage, 사용자 설정은 localStorage
  • 근거: 탭별 캐시(컬럼 너비 캐시, 필터, 그룹, pageSize)는 탭 닫으면 무의미. 사용자 설정(컬럼 가시성, 순서, 정렬)은 사용자가 의도적으로 변경한 환경설정이므로 세션 간 보존
  • 분류:
    • sessionStorage: tableState_*, pageSize_*, filterSettings_*, groupSettings_*
    • localStorage: table_column_visibility_*, table_sort_state_*, table_column_order_*