# 11. 페이지 패턴 가이드 > 대상: 프론트엔드 개발자 > 최종 업데이트: 2026-03-20 --- ## 목차 | 번호 | 항목 | |------|------| | 11.1 | [리스트 페이지](#111-리스트-페이지) | | 11.2 | [상세/폼 페이지](#112-상세폼-페이지) | | 11.3 | [검색 모달](#113-검색-모달) | | 11.4 | [공통 기능은 공통 컴포넌트에서](#114-공통-기능은-공통-컴포넌트에서) | --- ## 11.1 리스트 페이지 ### UniversalListPage (권장) 대부분의 리스트 페이지는 `UniversalListPage`로 구성합니다. ```typescript const config: UniversalListConfig = { // 검색 (클라이언트 사이드 필터링) clientSideFiltering: true, searchFilter: (item, searchValue) => { const q = searchValue.toLowerCase(); return item.name.toLowerCase().includes(q) || item.code.toLowerCase().includes(q); }, searchPlaceholder: '이름, 코드 검색...', // 컬럼 정의 columns: [...], // 모바일 카드 renderMobileCard: (item) => , }; ``` ### IntegratedListTemplateV2 필수 적용 항목 IntegratedListTemplateV2 사용 시 다음 10가지를 반드시 확인: | # | 항목 | 필수 | |---|------|------| | 1 | `useColumnSettings` + `ColumnSettingsPopover` | O | | 2 | `searchValue` + `onSearchChange` (or clientSideFiltering) | O | | 3 | 체크박스 `Set` 패턴 | O | | 4 | 페이지네이션 | O | | 5 | `renderMobileCard` | O | | 6 | 테이블 행 클릭 -> 상세 이동 | O | | 7 | 헤더 레이아웃 (StatCards, 필터) | O | | 8 | `tableHeaderActions` (테이블 내 필터) | 선택 | | 9 | `filterConfig` (필터 설정) | 선택 | | 10 | 탭 구성 | 선택 | ### 검색 패턴 (필수) ```typescript // ✅ 올바른 패턴 -- UniversalListPage + clientSideFiltering const config: UniversalListConfig = { clientSideFiltering: true, searchFilter: (item, searchValue) => { const q = searchValue.toLowerCase(); return item.name.toLowerCase().includes(q); }, }; // 데이터를 한 번 API로 로드 -> 검색은 메모리에서 즉시 필터링 -> 깜빡임 없음 ``` ```typescript // ❌ 금지 패턴 -- IntegratedListTemplateV2에 onSearchChange 직접 연결 { setSearchTerm(q); }} /> // 키입력마다 state 변경 -> re-render -> 화면 깜빡임, 한글 조합 불가 ``` --- ## 11.2 상세/폼 페이지 ### 라우팅 규칙 - 별도 `/new` 경로 금지 -> `?mode=new` 쿼리파라미터 사용 - 별도 `/edit` 경로 금지 -> `?mode=edit` 쿼리파라미터 사용 ```typescript // ✅ 올바른 패턴 router.push('/some-page?mode=new'); router.push('/some-page/123?mode=edit'); // ❌ 금지 패턴 router.push('/some-page/new'); router.push('/some-page/123/edit'); ``` ### 페이지 구조 ```typescript export default function SomePage() { const searchParams = useSearchParams(); const mode = searchParams.get('mode'); if (mode === 'new') return ; return ; } ``` ### 헤더 표준 | 위치 | 요소 | |------|------| | 상단 좌측 | 페이지 제목 (`

`) | | 상단 우측 | `<- 목록으로` 링크 | ### 하단 Sticky 액션 바 (필수) ```typescript
``` | 모드 | 좌측 | 우측 | |------|------|------| | 등록 (new) | 취소 | 저장 | | 상세 (view) | 취소 (목록으로) | 수정 | | 수정 (edit) | 취소 | 저장 | ### 폼 레이아웃 - Card 내부에 버튼 넣지 않음 -> sticky 하단 바 사용 - 아이콘 포함: 취소(`X`), 저장(`Save`), 수정(`Pencil`) --- ## 11.3 검색 모달 ### SearchableSelectionModal (필수) 검색+선택 기능이 필요한 모달은 반드시 `SearchableSelectionModal`을 사용합니다. ```typescript // ✅ 올바른 패턴 open={open} onOpenChange={setOpen} title="거래처 선택" fetchData={async () => { const result = await getVendors(); return result.success ? result.data : []; }} columns={[ { key: 'vendorName', label: '거래처명' }, { key: 'businessNumber', label: '사업자번호' }, ]} onSelect={(vendor) => { setSelectedVendor(vendor); }} searchFilter={(item, query) => item.vendorName.includes(query) || item.businessNumber?.includes(query) } /> ``` ```typescript // ❌ 금지 패턴 -- Dialog + Table 직접 조합 ...
``` --- ## 11.4 공통 기능은 공통 컴포넌트에서 리스트 페이지 전체에 적용해야 하는 기능은 개별 페이지 수정 금지. 반드시 공통 레이어에서 처리. | 기능 | 수정 위치 | 개별 페이지 수정 | |------|----------|----------------| | 검색 상태 보존 | `UniversalListPage` | 금지 | | 검색 X(클리어) 버튼 | `SearchFilter` + `IntegratedListTemplateV2` | 금지 | | 검색 디바운스 | `UniversalListPage` 내부 300ms | 금지 | | 체크박스 선택 | `IntegratedListTemplateV2` | 금지 | | 페이지네이션 | `IntegratedListTemplateV2` | 금지 | | 모바일 카드/인피니티 | `IntegratedListTemplateV2` | 금지 | | 컬럼 설정 | `useColumnSettings` + `ColumnSettingsPopover` | 금지 | **원칙**: "26개 페이지에 하나씩 적용" -> 잘못된 접근. "공통 1곳 수정 -> 전체 자동 적용" -> 올바른 접근.