6.6 KiB
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개는 너무 많음
- 확장성:
groupSizeprops로 기본값 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_*
- sessionStorage: