- Deleted the outdated `PageGroupNav` component and its related documentation. - Introduced a new document for the direct input navigation feature in pagination, detailing the rationale for the change and the new user experience. - Updated the checklist to reflect the completion of the new pagination input feature and its implementation steps. These changes enhance the clarity and usability of the pagination system in the project.
116 lines
5.5 KiB
Markdown
116 lines
5.5 KiB
Markdown
# [맥락노트] 페이징 - 페이지 번호 직접 입력 네비게이션
|
|
|
|
> 관련 문서: [계획서](./PGN[계획]-페이징-직접입력.md) | [체크리스트](./PGN[체크]-페이징-직접입력.md)
|
|
|
|
---
|
|
|
|
## 왜 이 작업을 하는가
|
|
|
|
- 현재 페이지네이션은 `1 / 38` 읽기 전용 텍스트만 표시
|
|
- 수십 페이지가 있을 때 원하는 페이지로 빠르게 이동할 수 없음 (`>` 연타 필요)
|
|
- 페이지 번호를 직접 입력하여 즉시 이동할 수 있어야 UX가 개선됨
|
|
|
|
---
|
|
|
|
## 핵심 결정 사항과 근거
|
|
|
|
### 1. 10개 번호 버튼 그룹 → 입력 필드로 설계 변경
|
|
|
|
- **결정**: 이전 설계(10개 페이지 번호 버튼 나열)를 폐기하고, 기존 `현재/총` 텍스트에서 현재 부분을 입력 필드로 교체
|
|
- **근거**: 10개 버튼은 공간을 많이 차지하고 고정 슬롯/고정 너비 등 복잡한 레이아웃 제약이 발생. 입력 필드 방식이 더 직관적이고 공간 효율적
|
|
- **이전 산출물**: `PageGroupNav.tsx` → 삭제 완료
|
|
|
|
### 2. `<< < > >>` 버튼 동작 유지
|
|
|
|
- **결정**: 4개 화살표 버튼의 동작은 기존과 완전히 동일하게 유지
|
|
- **근거**: 입력 필드가 "원하는 페이지로 점프" 역할을 하므로, 버튼은 기존의 순차 이동(+1/-1, 첫/끝) 그대로 유지하는 것이 자연스러움
|
|
|
|
### 3. 입력 중에는 페이지 이동 안 함
|
|
|
|
- **결정**: onChange는 입력 필드 표시만 변경. Enter 또는 blur로 실제 페이지 이동
|
|
- **근거**: `28`을 입력하려면 `2`를 먼저 치는데, `2`에서 바로 이동하면 안 됨
|
|
|
|
### 4. 포커스 시 전체 선택 (select all)
|
|
|
|
- **결정**: 입력 필드 클릭 시 기존 숫자를 전체 선택
|
|
- **근거**: 사용자가 "15페이지로 가고 싶다" → 클릭 → 바로 `15` 타이핑. 기존 값을 지우는 추가 동작 불필요
|
|
|
|
### 5. 유효 범위 자동 보정
|
|
|
|
- **결정**: 1 미만 → 1, totalPages 초과 → totalPages, 빈 값/비숫자 → 현재 페이지 유지
|
|
- **근거**: 에러 메시지보다 자동 보정이 UX에 유리
|
|
- **대안 검토**: 입력 자체를 숫자만 허용 → 기각 (백스페이스로 비울 때 불편)
|
|
|
|
### 6. `inputMode="numeric"` 사용
|
|
|
|
- **결정**: `type="text"` + `inputMode="numeric"`
|
|
- **근거**: `type="number"`는 브라우저별 스피너 UI가 추가되고, 빈 값 처리가 어려움. `inputMode="numeric"`은 모바일에서 숫자 키보드를 띄우면서 text 입력의 유연성 유지
|
|
|
|
### 7. 신규 컴포넌트 분리 안 함
|
|
|
|
- **결정**: v2-table-list의 paginationJSX 내부에 인라인으로 구현
|
|
- **근거**: 변경이 `<span>` → `<input>` + 핸들러 약 30줄 수준으로 매우 작음
|
|
|
|
### 8. `currentPage`를 fetch의 단일 소스로 사용
|
|
|
|
- **결정**: `fetchTableDataInternal`에서 `tableConfig.pagination?.currentPage || currentPage` 대신 `currentPage`만 사용
|
|
- **근거**: `handlePageSizeChange`에서 `setCurrentPage(1)` + `onConfigChange(...)` 호출 시, `onConfigChange`를 통한 부모의 `tableConfig` 갱신은 다음 렌더 사이클에서 전파됨. fetch가 실행되는 시점에 `tableConfig.pagination?.currentPage`가 아직 이전 값(예: 4)이고 truthy이므로 로컬 `currentPage`(1) 대신 4를 사용하게 되는 문제 발생. 로컬 `currentPage`는 `setCurrentPage`로 즉시 갱신되므로 이 문제가 없음
|
|
- **발견 과정**: 페이지 크기를 20→40으로 변경하면 1페이지로 설정되지만 리스트가 빈 상태로 표시되는 버그로 발견
|
|
|
|
### 9. `handlePageSizeChange`에서 `onConfigChange` 호출 필수
|
|
|
|
- **결정**: 페이지 크기 변경 시 `onConfigChange`로 `{ pageSize, currentPage: 1 }`을 부모에게 전달
|
|
- **근거**: 기존 코드는 `setLocalPageSize` + `setCurrentPage(1)`만 호출하고 `onConfigChange`를 호출하지 않았음. 이로 인해 부모 컴포넌트의 `tableConfig.pagination`이 갱신되지 않아 후속 동작에서 stale 값 참조 가능
|
|
- **발견 과정**: 위 8번과 같은 맥락에서 발견
|
|
|
|
---
|
|
|
|
## 관련 파일 위치
|
|
|
|
| 구분 | 파일 경로 | 설명 |
|
|
|------|----------|------|
|
|
| 수정 | `frontend/lib/registry/components/v2-table-list/TableListComponent.tsx` | paginationJSX 중앙 입력 필드 + fetch 소스 수정 |
|
|
| 삭제 | `frontend/components/common/PageGroupNav.tsx` | 이전 설계 산출물 (삭제 완료) |
|
|
|
|
---
|
|
|
|
## 기술 참고
|
|
|
|
### 로컬 입력 상태와 실제 페이지 상태 분리
|
|
|
|
```
|
|
pageInputValue (string) — 입력 필드에 표시되는 값 (사용자가 타이핑 중일 수 있음)
|
|
currentPage (number) — 실제 현재 페이지 (API 호출의 단일 소스)
|
|
|
|
동기화:
|
|
- currentPage 변경 시 → useEffect → setPageInputValue(String(currentPage))
|
|
- Enter/blur 시 → commitPageInput → parseInt + clamp → handlePageChange(보정된 값)
|
|
```
|
|
|
|
### handlePageChange 호출 흐름
|
|
|
|
```
|
|
입력 필드 Enter/blur
|
|
→ commitPageInput()
|
|
→ parseInt + clamp(1, totalPages)
|
|
→ handlePageChange(clampedPage)
|
|
→ setCurrentPage(clampedPage) + onConfigChange
|
|
→ useEffect 트리거 → fetchTableDataDebounced
|
|
→ fetchTableDataInternal(page = currentPage)
|
|
→ 백엔드 API 호출
|
|
```
|
|
|
|
### handlePageSizeChange 호출 흐름
|
|
|
|
```
|
|
좌측 페이지크기 입력 onChange/onBlur
|
|
→ handlePageSizeChange(newSize)
|
|
→ setLocalPageSize(newSize)
|
|
→ setCurrentPage(1)
|
|
→ sessionStorage 저장
|
|
→ onConfigChange({ pageSize: newSize, currentPage: 1 })
|
|
→ useEffect 트리거 → fetchTableDataDebounced
|
|
→ fetchTableDataInternal(page = 1, pageSize = newSize)
|
|
→ 백엔드 API 호출
|
|
```
|