Files
sam-react-prod/sam-docs/frontend/v1/11-page-patterns.md

5.7 KiB

11. 페이지 패턴 가이드

대상: 프론트엔드 개발자 최종 업데이트: 2026-03-20


목차

번호 항목
11.1 리스트 페이지
11.2 상세/폼 페이지
11.3 검색 모달
11.4 공통 기능은 공통 컴포넌트에서

11.1 리스트 페이지

UniversalListPage (권장)

대부분의 리스트 페이지는 UniversalListPage로 구성합니다.

const config: UniversalListConfig<MyItem> = {
  // 검색 (클라이언트 사이드 필터링)
  clientSideFiltering: true,
  searchFilter: (item, searchValue) => {
    const q = searchValue.toLowerCase();
    return item.name.toLowerCase().includes(q) || item.code.toLowerCase().includes(q);
  },
  searchPlaceholder: '이름, 코드 검색...',

  // 컬럼 정의
  columns: [...],

  // 모바일 카드
  renderMobileCard: (item) => <MobileCard item={item} />,
};

IntegratedListTemplateV2 필수 적용 항목

IntegratedListTemplateV2 사용 시 다음 10가지를 반드시 확인:

# 항목 필수
1 useColumnSettings + ColumnSettingsPopover O
2 searchValue + onSearchChange (or clientSideFiltering) O
3 체크박스 Set<string> 패턴 O
4 페이지네이션 O
5 renderMobileCard O
6 테이블 행 클릭 -> 상세 이동 O
7 헤더 레이아웃 (StatCards, 필터) O
8 tableHeaderActions (테이블 내 필터) 선택
9 filterConfig (필터 설정) 선택
10 탭 구성 선택

검색 패턴 (필수)

// ✅ 올바른 패턴 -- UniversalListPage + clientSideFiltering
const config: UniversalListConfig<MyItem> = {
  clientSideFiltering: true,
  searchFilter: (item, searchValue) => {
    const q = searchValue.toLowerCase();
    return item.name.toLowerCase().includes(q);
  },
};
// 데이터를 한 번 API로 로드 -> 검색은 메모리에서 즉시 필터링 -> 깜빡임 없음
// ❌ 금지 패턴 -- IntegratedListTemplateV2에 onSearchChange 직접 연결
<IntegratedListTemplateV2
  searchValue={searchTerm}
  onSearchChange={(q) => { setSearchTerm(q); }}
/>
// 키입력마다 state 변경 -> re-render -> 화면 깜빡임, 한글 조합 불가

11.2 상세/폼 페이지

라우팅 규칙

  • 별도 /new 경로 금지 -> ?mode=new 쿼리파라미터 사용
  • 별도 /edit 경로 금지 -> ?mode=edit 쿼리파라미터 사용
// ✅ 올바른 패턴
router.push('/some-page?mode=new');
router.push('/some-page/123?mode=edit');

// ❌ 금지 패턴
router.push('/some-page/new');
router.push('/some-page/123/edit');

페이지 구조

export default function SomePage() {
  const searchParams = useSearchParams();
  const mode = searchParams.get('mode');

  if (mode === 'new') return <SomeForm />;
  return <SomeList />;
}

헤더 표준

위치 요소
상단 좌측 페이지 제목 (<h1>)
상단 우측 <- 목록으로 링크

하단 Sticky 액션 바 (필수)

<div className="sticky bottom-0 bg-white border-t shadow-sm">
  <div className="px-3 py-3 md:px-6 md:py-4 flex items-center justify-between">
    <Button variant="outline" onClick={() => router.push(listPath)}>
      <X className="h-4 w-4 mr-1" /> 취소
    </Button>
    <Button onClick={handleSubmit} disabled={isSubmitting}>
      <Save className="h-4 w-4 mr-1" /> 저장
    </Button>
  </div>
</div>
모드 좌측 우측
등록 (new) 취소 저장
상세 (view) 취소 (목록으로) 수정
수정 (edit) 취소 저장

폼 레이아웃

  • Card 내부에 버튼 넣지 않음 -> sticky 하단 바 사용
  • 아이콘 포함: 취소(X), 저장(Save), 수정(Pencil)

11.3 검색 모달

SearchableSelectionModal (필수)

검색+선택 기능이 필요한 모달은 반드시 SearchableSelectionModal<T>을 사용합니다.

// ✅ 올바른 패턴
<SearchableSelectionModal<VendorItem>
  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)
  }
/>
// ❌ 금지 패턴 -- Dialog + Table 직접 조합
<Dialog>
  <Input onChange={...} />
  <Table>...</Table>
</Dialog>

11.4 공통 기능은 공통 컴포넌트에서

리스트 페이지 전체에 적용해야 하는 기능은 개별 페이지 수정 금지. 반드시 공통 레이어에서 처리.

기능 수정 위치 개별 페이지 수정
검색 상태 보존 UniversalListPage 금지
검색 X(클리어) 버튼 SearchFilter + IntegratedListTemplateV2 금지
검색 디바운스 UniversalListPage 내부 300ms 금지
체크박스 선택 IntegratedListTemplateV2 금지
페이지네이션 IntegratedListTemplateV2 금지
모바일 카드/인피니티 IntegratedListTemplateV2 금지
컬럼 설정 useColumnSettings + ColumnSettingsPopover 금지

원칙: "26개 페이지에 하나씩 적용" -> 잘못된 접근. "공통 1곳 수정 -> 전체 자동 적용" -> 올바른 접근.