- _index.md: 문서 목록 및 버전 관리 - 01~09: 아키텍처, API패턴, 컴포넌트, 폼, 스타일, 인증, 대시보드, 컨벤션 - 10: 문서 API 연동 스펙 (api-specs에서 이관) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
247 lines
5.7 KiB
Markdown
247 lines
5.7 KiB
Markdown
# 04. 공통 컴포넌트 사용법
|
|
|
|
> **대상**: 프론트엔드 개발자
|
|
> **버전**: 1.0.0
|
|
> **최종 수정**: 2026-03-09
|
|
|
|
---
|
|
|
|
## 1. UniversalListPage
|
|
|
|
59+ 리스트 페이지에서 사용하는 통합 템플릿. config 객체 하나로 전체 동작 제어.
|
|
|
|
### 기본 사용법
|
|
|
|
```typescript
|
|
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 내보내기 설정 |
|
|
|
|
### 서버 페이지네이션 연동
|
|
|
|
```typescript
|
|
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 조합 금지.
|
|
|
|
### 사용법
|
|
|
|
```typescript
|
|
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
|
|
|
|
상세/등록/수정 페이지 통합 템플릿.
|
|
|
|
### 기본 사용법
|
|
|
|
```typescript
|
|
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
|
|
|
|
프로그래밍 방식으로 폼 제어:
|
|
|
|
```typescript
|
|
const templateRef = useRef<IntegratedDetailTemplateRef>(null);
|
|
|
|
// 폼 데이터 읽기/쓰기
|
|
templateRef.current?.getFormData();
|
|
templateRef.current?.setFormData(newData);
|
|
templateRef.current?.setFieldValue('name', '새 이름');
|
|
templateRef.current?.validate();
|
|
```
|
|
|
|
---
|
|
|
|
## 4. PageHeader / PageLayout
|
|
|
|
### PageLayout — 페이지 콘텐츠 래퍼
|
|
|
|
```typescript
|
|
import { PageLayout } from '@/components/organisms/PageLayout';
|
|
|
|
<PageLayout>
|
|
{/* 콘텐츠 */}
|
|
</PageLayout>
|
|
```
|
|
|
|
- 자동으로 `p-0 md:space-y-6` 패딩 적용
|
|
- **page.tsx에서 추가 패딩 금지** (이중 패딩 방지)
|
|
|
|
### PageHeader — 페이지 제목
|
|
|
|
```typescript
|
|
import { PageHeader } from '@/components/organisms/PageHeader';
|
|
|
|
<PageHeader
|
|
title="어음관리"
|
|
description="어음을 등록하고 관리합니다"
|
|
icon={FileText}
|
|
actions={
|
|
<Button onClick={handleCreate}>등록</Button>
|
|
}
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
## 5. StatCards — 통계 카드
|
|
|
|
```typescript
|
|
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 내부에서도 사용.
|
|
|
|
```typescript
|
|
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`
|
|
- **작업 버튼**: 체크박스 선택 시만 표시 (또는 행별 버튼)
|