Files
sam-docs/frontend/v1/04-common-components.md
유병철 8f939d3609 docs: [frontend] 프론트엔드 아키텍처/가이드 문서 v1 작성
- _index.md: 문서 목록 및 버전 관리
- 01~09: 아키텍처, API패턴, 컴포넌트, 폼, 스타일, 인증, 대시보드, 컨벤션
- 10: 문서 API 연동 스펙 (api-specs에서 이관)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 10:24:25 +09:00

5.7 KiB

04. 공통 컴포넌트 사용법

대상: 프론트엔드 개발자 버전: 1.0.0 최종 수정: 2026-03-09


1. UniversalListPage

59+ 리스트 페이지에서 사용하는 통합 템플릿. config 객체 하나로 전체 동작 제어.

기본 사용법

import {
  UniversalListPage,
  type UniversalListConfig,
  type StatCard,
} from '@/components/templates/UniversalListPage';

const config: UniversalListConfig<MyRecord> = {
  // 필수
  title: '페이지 제목',
  icon: FileText,
  basePath: '/accounting/my-page',
  idField: 'id',
  columns: [
    { key: 'name', label: '이름', className: 'w-[200px]' },
    { key: 'amount', label: '금액', className: 'text-right w-[120px]' },
    { key: 'status', label: '상태', className: 'text-center w-[80px]' },
  ],
  actions: {
    getList: getMyList,          // Server Action
    deleteItems: deleteMyItems,  // 선택사항
  },

  // 선택
  itemsPerPage: 20,
  showCheckbox: true,
  clientSideFiltering: false,  // 서버 페이지네이션 시 false
};

return <UniversalListPage config={config} />;

주요 config 옵션

옵션 타입 설명
columns Column[] 테이블 컬럼 정의
actions.getList Function 목록 조회 Server Action
showCheckbox boolean 체크박스 표시
hideSearch boolean 검색창 숨김
computeStats () => StatCard[] 통계 카드
headerActions () => ReactNode 헤더 버튼 영역
renderTableRow Function 커스텀 행 렌더링
renderMobileCard Function 모바일 카드 렌더링
tableFooter ReactNode 테이블 하단 (합계 행 등)
dateRangeSelector Object 날짜 범위 선택기
tabs Tab[] 탭 필터
excelExport Object Excel 내보내기 설정

서버 페이지네이션 연동

const [currentPage, setCurrentPage] = useState(1);
const [pagination, setPagination] = useState({ ... });

<UniversalListPage
  config={config}
  initialData={data}
  externalPagination={{
    currentPage: pagination.currentPage,
    totalPages: pagination.lastPage,
    totalItems: pagination.total,
    itemsPerPage: pagination.perPage,
    onPageChange: setCurrentPage,
  }}
  externalIsLoading={isLoading}
/>

2. SearchableSelectionModal<T>

검색 + 선택 기능이 필요한 모달. 직접 Dialog 조합 금지.

사용법

import { SearchableSelectionModal } from '@/components/organisms';

<SearchableSelectionModal<ClientRecord>
  open={isOpen}
  onOpenChange={setIsOpen}
  title="거래처 선택"
  searchPlaceholder="거래처명 검색"
  fetchItems={async (search) => {
    const result = await searchClients({ search });
    return result.success ? result.data : [];
  }}
  columns={[
    { key: 'name', label: '거래처명' },
    { key: 'code', label: '코드' },
  ]}
  onSelect={(item) => {
    setSelectedClient(item);
    setIsOpen(false);
  }}
  getItemId={(item) => item.id}
/>

3. IntegratedDetailTemplate

상세/등록/수정 페이지 통합 템플릿.

기본 사용법

import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';

<IntegratedDetailTemplate
  mode={mode}  // 'create' | 'view' | 'edit'
  title="품목 상세"
  icon={Package}
  fields={[
    {
      key: 'name',
      label: '품목명',
      type: 'text',
      required: true,
      section: '기본정보',
    },
    {
      key: 'category',
      label: '분류',
      type: 'select',
      options: categoryOptions,
      section: '기본정보',
    },
  ]}
  data={formData}
  onSave={handleSave}
  onDelete={handleDelete}
  onModeChange={setMode}
/>

forwardRef API

프로그래밍 방식으로 폼 제어:

const templateRef = useRef<IntegratedDetailTemplateRef>(null);

// 폼 데이터 읽기/쓰기
templateRef.current?.getFormData();
templateRef.current?.setFormData(newData);
templateRef.current?.setFieldValue('name', '새 이름');
templateRef.current?.validate();

4. PageHeader / PageLayout

PageLayout — 페이지 콘텐츠 래퍼

import { PageLayout } from '@/components/organisms/PageLayout';

<PageLayout>
  {/* 콘텐츠 */}
</PageLayout>
  • 자동으로 p-0 md:space-y-6 패딩 적용
  • page.tsx에서 추가 패딩 금지 (이중 패딩 방지)

PageHeader — 페이지 제목

import { PageHeader } from '@/components/organisms/PageHeader';

<PageHeader
  title="어음관리"
  description="어음을 등록하고 관리합니다"
  icon={FileText}
  actions={
    <Button onClick={handleCreate}>등록</Button>
  }
/>

5. StatCards — 통계 카드

import { StatCards } from '@/components/organisms';

<StatCards
  stats={[
    { label: '전체', value: '125건', icon: FileText, iconColor: 'text-gray-500' },
    { label: '입금', value: '50,000,000원', icon: ArrowDown, iconColor: 'text-blue-500' },
    { label: '출금', value: '30,000,000원', icon: ArrowUp, iconColor: 'text-red-500' },
  ]}
/>

6. DataTable — 데이터 테이블

organisms 레벨 범용 테이블. UniversalListPage 내부에서도 사용.

import { DataTable } from '@/components/organisms';

<DataTable
  columns={[
    { key: 'name', label: '이름' },
    { key: 'amount', label: '금액', type: 'number' },
  ]}
  data={items}
  onRowClick={(item) => handleDetail(item.id)}
/>

7. 테이블 필수 구조

모든 테이블은 다음 컬럼 순서를 준수:

[체크박스] → [번호(1부터)] → [데이터 컬럼들] → [작업 컬럼]
  • 번호: (currentPage - 1) * pageSize + index + 1
  • 작업 버튼: 체크박스 선택 시만 표시 (또는 행별 버튼)