# Phase 2: 규격 기반 컴포넌트 — FieldConfig로 테이블/폼/검색 렌더링

> **목적**: FieldConfig[]을 입력으로 받아 테이블/폼/검색 UI를 렌더링하는 React 컴포넌트 구현
> **전제 조건**: Phase 1 완료 (GET /api/meta/tables/{tableName}/fields API가 FieldConfig[]을 반환)
> **산출물**: FieldConfig 기반 3대 핵심 컴포넌트 (FcTable, FcForm, FcSearch) + 보조 컴포넌트 (FcButton, FcButtonBar, FcPagination)
> **다음 단계**: Phase 3에서 이 컴포넌트들을 개발자 빌더 캔버스에 배치

---

## 1. 핵심 원칙

**"같은 FieldConfig가 컴포넌트에 따라 다르게 렌더된다"**

```
FieldConfig { column: 'order_date', label: '수주일', type: 'date' }

→ FcTable에서:  <td>2026-04-08</td>         (format 적용된 텍스트)
→ FcForm에서:   <DatePicker label="수주일" /> (입력 위젯)
→ FcSearch에서: <DateRangePicker />          (범위 검색)
```

이 렌더링 규칙은 `notes/gbpark/2026-04-08-invyone-component-spec.md` Section 2.2에 명시되어 있다. **반드시 이 매핑 테이블을 따를 것.**

---

## 2. 현재 존재하는 것

### 2.1 타입 정의 (이미 완성)

`frontend/types/invyone-component.ts` — FieldConfig, Component, DataPort, Template, 모든 Config 타입이 정의됨. **이 파일의 타입을 그대로 사용한다. 새로 만들지 말 것.**

### 2.2 기존 컴포넌트 (참고용, 직접 사용 금지)

기존 `frontend/lib/registry/components/`에 85개+ 컴포넌트가 있지만, 이것들은 VEX 규격(ColumnConfig 354줄 등)에 맞춰 만들어진 것이므로 **FieldConfig 기반 신규 컴포넌트를 만든다.** 기존 컴포넌트는 코드 패턴 참고용으로만 사용.

### 2.3 UI 라이브러리 (활용)

| 라이브러리 | 용도 |
|---|---|
| Radix UI | 기본 UI 프리미티브 (Dialog, Select, Checkbox 등) |
| TanStack Table v8 | 테이블 렌더링 |
| React Hook Form v7 | 폼 상태 관리 |
| Zod v4 | 폼 유효성 검증 |
| date-fns v4 | 날짜 포맷팅 |
| Lucide React | 아이콘 |

### 2.4 디자인 시스템

v5 Cosmic Glassmorphism (`frontend/styles/v5-layout.css`, `frontend/app/globals.css`). 모든 컴포넌트는 v5 토큰을 따른다. 즉흥 hex/rgb 금지.

---

## 3. 파일 구조

```
frontend/components/fc/          ← FieldConfig 기반 컴포넌트 (fc = FieldConfig)
├── FcTable.tsx                  ← 데이터 테이블
├── FcForm.tsx                   ← 입력 폼
├── FcSearch.tsx                 ← 검색 필터
├── FcButton.tsx                 ← 단일 버튼
├── FcButtonBar.tsx              ← 버튼 그룹
├── FcPagination.tsx             ← 페이지네이션
├── fields/                      ← FieldType별 렌더러
│   ├── FieldRenderer.tsx        ← FieldType→위젯 디스패처
│   ├── TextField.tsx
│   ├── NumberField.tsx
│   ├── DateField.tsx
│   ├── DateTimeField.tsx
│   ├── SelectField.tsx
│   ├── EntityField.tsx
│   ├── CheckboxField.tsx
│   ├── TextareaField.tsx
│   ├── FileField.tsx
│   └── CodeField.tsx
├── table/                       ← 테이블 전용 셀 렌더러
│   └── CellRenderer.tsx         ← FieldType→셀 포맷 디스패처
└── index.ts                     ← 공개 exports
```

---

## 4. 상세 설계

### 4.1 FieldRenderer — FieldType→위젯 디스패처

모든 폼/검색 입력 필드를 렌더링하는 허브 컴포넌트.

```typescript
// frontend/components/fc/fields/FieldRenderer.tsx

interface FieldRendererProps {
  field: FieldConfig;
  value: any;
  onChange: (value: any) => void;
  mode: 'form' | 'search';     // 폼인지 검색인지에 따라 위젯이 달라짐
  disabled?: boolean;
  error?: string;
}

/**
 * FieldConfig.type을 보고 적절한 입력 위젯을 렌더한다.
 *
 * mode='form' 이면 폼 렌더링 규칙 적용:
 *   date → DatePicker(단일), select → Select(단일)
 *
 * mode='search' 이면 검색 렌더링 규칙 적용:
 *   date → DateRangePicker(범위), select → MultiSelect(다중)
 */
function FieldRenderer({ field, value, onChange, mode, disabled, error }: FieldRendererProps) {
  // field.type에 따라 switch/case로 분기
  // 각 케이스에서 fields/ 폴더의 개별 컴포넌트를 렌더
}
```

### 4.2 렌더링 계약 (★ 반드시 이 테이블대로 구현)

| FieldType | FcTable (셀) | FcForm (입력) | FcSearch (검색) |
|---|---|---|---|
| `text` | 텍스트 그대로 | `<input type="text">` | `<input>` (부분 일치) |
| `number` | format 적용 (#,##0) | `<input type="number">` | min~max 범위 입력 2개 |
| `date` | format 적용 (YYYY-MM-DD) | DatePicker (단일) | **DateRangePicker (시작~종료)** |
| `datetime` | format 적용 | DateTimePicker | DateTimeRangePicker |
| `select` | 텍스트 그대로 | `<Select>` (단일) | **MultiSelect (다중, 체크박스)** |
| `entity` | ref.displayColumn 값 표시 | 팝업 검색 버튼 + 입력 | 팝업 검색 (단일) |
| `checkbox` | ✓/✗ 아이콘 | `<Checkbox>` | `<Select>` (전체/✓/✗) |
| `textarea` | 말줄임 (...) 40자 | `<textarea>` rows=3 | `<input>` (부분 일치) |
| `file` | 파일명 링크 | 파일 업로드 | — (검색 불가, 렌더 안 함) |
| `code` | 텍스트 그대로 | readonly 표시 | `<input>` (완전 일치) |

### 4.3 FcTable — 데이터 테이블

```typescript
// frontend/components/fc/FcTable.tsx

interface FcTableProps {
  fields: FieldConfig[];           // 컬럼 정의
  data: Record<string, any>[];     // 행 데이터
  config?: TableConfig;            // 테이블 설정 (invyone-component.ts)
  loading?: boolean;

  // ─── DataPort 출력 (콜백) ───
  onRowSelect?: (row: Record<string, any>) => void;           // selectedRow(row)
  onRowsSelect?: (rows: Record<string, any>[]) => void;      // selectedRows(rows)

  // ─── DataPort 입력 ───
  searchParams?: Record<string, any>;  // 검색 조건 (적용 시 필터링/API 재호출)
}
```

**렌더링 규칙:**
1. `fields`에서 `visible: true`인 것만 컬럼으로 표시
2. `order` 순으로 정렬
3. `width` 적용 (없으면 타입별 기본값)
4. `align` 적용
5. `sortable: true`인 컬럼은 헤더 클릭 시 정렬
6. 셀 값은 `CellRenderer`가 FieldType에 따라 포맷팅
7. 행 클릭 시 `onRowSelect` 호출
8. 체크박스 선택 시 `onRowsSelect` 호출

**데이터 조회:**
- `config.autoLoad: true`이면 마운트 시 자동으로 `GET /api/data/{tableName}` 호출
- `searchParams`가 변경되면 재조회
- 페이지네이션은 FcPagination과 연동

### 4.4 FcForm — 입력 폼

```typescript
// frontend/components/fc/FcForm.tsx

interface FcFormProps {
  fields: FieldConfig[];           // 필드 정의
  config?: FormConfig;             // 폼 설정
  initialData?: Record<string, any>;  // 수정 모드 시 초기값

  // ─── DataPort 출력 ───
  onSubmit?: (data: Record<string, any>) => void;     // formData(row)
  onSaved?: (data: Record<string, any>) => void;      // savedRow(row)

  // ─── DataPort 입력 ───
  loadRow?: Record<string, any>;   // 외부에서 행 데이터 로드
}
```

**렌더링 규칙:**
1. `fields`에서 `system: true`인 것은 숨김
2. `visible: true`인 것만 표시
3. `order` 순으로 배치
4. `config.columns` (1/2/3)에 따라 그리드 레이아웃
5. 각 필드는 `FieldRenderer(mode='form')`로 렌더
6. `required: true`인 필드에 * 표시
7. `editable: false`인 필드는 disabled
8. `pk: true`이고 `type: 'code'`이면 자동채번 표시 (readonly)
9. `config.sections`가 있으면 섹션별로 그룹핑

**유효성 검증:**
- 제출 시 `required: true` + 값이 비어있으면 에러 표시
- empty 판단: `value === null || value === undefined || value === ''` (0, false는 유효)

### 4.5 FcSearch — 검색 필터

```typescript
// frontend/components/fc/FcSearch.tsx

interface FcSearchProps {
  fields: FieldConfig[];           // 전체 필드 (searchable: true인 것만 렌더)
  config?: SearchConfig;

  // ─── DataPort 출력 ───
  onSearch?: (params: Record<string, any>) => void;    // searchParams(params)
}
```

**렌더링 규칙:**
1. `fields`에서 `searchable: true`인 것만 표시
2. 각 필드는 `FieldRenderer(mode='search')`로 렌더
3. `config.layout: 'inline'`이면 한 줄에 나열, `'stacked'`이면 세로
4. "검색" 버튼 + "초기화" 버튼 (showResetButton)
5. `config.autoSearch: true`이면 입력 시 300ms 디바운스 후 자동 검색
6. 검색 버튼 클릭 또는 자동 검색 시 `onSearch(params)` 호출

**검색 파라미터 형식:**
```typescript
// date 범위: { order_date_from: '2026-01-01', order_date_to: '2026-12-31' }
// select 다중: { status: ['확정', '완료'] }
// text 부분 일치: { customer_name: '삼성' }
// number 범위: { amount_min: 1000, amount_max: 9999 }
```

### 4.6 FcButton / FcButtonBar

```typescript
// frontend/components/fc/FcButton.tsx
interface FcButtonProps {
  config: ButtonConfig;
  onClick?: () => void;           // clicked(value)
  disabled?: boolean;
}

// frontend/components/fc/FcButtonBar.tsx
interface FcButtonBarProps {
  config: ButtonBarConfig;
  onAction?: (actionType: ActionType) => void;
}
```

**ActionType 12종 (invyone-component.ts에 정의됨):**
save, edit, delete, add, cancel, close, navigate, popup, search, reset, submit, approval

### 4.7 FcPagination

```typescript
// frontend/components/fc/FcPagination.tsx
interface FcPaginationProps {
  config?: PaginationConfig;
  total: number;
  page: number;
  onPageChange?: (params: { page: number; size: number }) => void;  // pageChange(params)
}
```

### 4.8 CellRenderer — 테이블 셀 포맷터

```typescript
// frontend/components/fc/table/CellRenderer.tsx
interface CellRendererProps {
  field: FieldConfig;
  value: any;
}

/**
 * FieldType에 따라 셀 내용을 포맷팅한다:
 * - number: format 적용 (#,##0 → 1,234,567)
 * - date: format 적용 (YYYY-MM-DD)
 * - checkbox: ✓/✗ 아이콘
 * - entity: ref.displayColumn 값 표시 (별도 조회 필요 시 캐시)
 * - textarea: 40자 말줄임 (...)
 * - file: 파일명 링크
 * - 나머지: 텍스트 그대로
 */
```

---

## 5. 데이터 연동 (CRUD API)

FcTable이 데이터를 조회하고, FcForm이 데이터를 저장할 때 사용하는 API.

기존 `DataController.java`가 범용 CRUD를 제공하지만, FieldConfig 기반으로 파라미터를 맞춰주는 래퍼가 필요.

**프론트엔드 API: `frontend/lib/api/fcData.ts`**

```typescript
/**
 * ★ 별도 인터페이스 정의 안 함 — 백엔드가 Map<String, Object>이므로 프론트도 Record<string, any>
 * ★ 유일하게 타입이 있는 건 FieldConfig (invyone-component.ts에 규격 확정된 것)
 */

/** FieldConfig 기반 목록 조회 */
export async function fcList(params: Record<string, any>): Promise<Record<string, any>>;

/** FieldConfig 기반 단건 조회 */
export async function fcGet(tableName: string, id: string): Promise<Record<string, any>>;

/** FieldConfig 기반 등록 */
export async function fcInsert(tableName: string, data: Record<string, any>): Promise<any>;

/** FieldConfig 기반 수정 */
export async function fcUpdate(tableName: string, id: string, data: Record<string, any>): Promise<any>;

/** FieldConfig 기반 삭제 (soft delete) */
export async function fcDelete(tableName: string, ids: string[]): Promise<any>;
```

**백엔드**: 기존 `DataController` 또는 `DataAdvancedController`의 엔드포인트를 활용. 필요하면 FieldConfig의 searchable/computed 등을 반영하는 동적 쿼리 빌더 추가.

---

## 6. 테스트 페이지

구현 확인용 테스트 페이지를 하나 만든다:

**`frontend/app/(main)/test-fc/page.tsx`**

```typescript
/**
 * FieldConfig 컴포넌트 테스트 페이지
 *
 * 1. 드롭다운에서 테이블 선택
 * 2. getTableFields(tableName) 호출 → FieldConfig[] 수신
 * 3. FcSearch + FcTable + FcForm을 동시에 렌더
 * 4. FcSearch → searchParams → FcTable (검색 연동)
 * 5. FcTable 행 클릭 → selectedRow → FcForm (데이터 로드)
 * 6. FcForm 저장 → DB에 실제 저장 → FcTable 새로고침
 */
```

이 페이지가 동작하면 Phase 2 완료.

---

## 7. 스타일링 규칙

1. v5 CSS 토큰 사용 (`var(--v5-primary)`, `var(--glass)`, `var(--glow-sm)` 등)
2. 컴팩트 폰트 (0.55~0.85rem)
3. 글래스모피즘: `backdrop-filter:blur(20px) saturate(1.4)` + `var(--glass)` + `var(--glass-border)`
4. 다크/라이트 모드 둘 다 지원
5. CSS 파일: `frontend/styles/fc-components.css` (필요 시) 또는 Tailwind 클래스 활용
6. 기존 `v5-layout.css`와 `globals.css`의 토큰 먼저 확인 후 재사용

---

## 8. 참고 파일

| 파일 | 용도 |
|---|---|
| `notes/gbpark/2026-04-10-phase1-implementation-log.md` | **Phase 1 구현 결과** — API 응답 형태, 병합 규칙, 실 DB 테스트 결과, 범위 밖 항목 |
| `frontend/types/invyone-component.ts` | **모든 타입 정의 (진실의 원천)** |
| `notes/gbpark/2026-04-08-invyone-component-spec.md` (Section 2.2) | **렌더링 계약 테이블** — 반드시 이대로 구현 |
| `notes/gbpark/2026-04-09-invyone-architecture.md` (Section 4) | 렌더링 계약 요약 |
| `notes/gbpark/2026-04-08-invyone-mockup/js/08-admin-builder.js` | mockup의 프리뷰 렌더 함수 (pvTable, pvForm, pvSearch) — UI 감각 참고 |
| `notes/gbpark/2026-04-08-invyone-mockup/css/05-widgets.css` | HR 테이블 스타일 — 테이블 CSS 참고 |
| `frontend/styles/v5-layout.css` | v5 디자인 토큰 |
| `frontend/app/globals.css` | Tailwind/shadcn 토큰 |

---

## 9. 완료 기준

1. **FcTable**: FieldConfig[]과 데이터를 받아 테이블을 렌더링한다. 정렬, 행 선택, 페이지네이션이 동작한다.
2. **FcForm**: FieldConfig[]을 받아 입력 폼을 렌더링한다. 10종 FieldType 전부 동작한다. required 검증이 동작한다.
3. **FcSearch**: FieldConfig[]에서 searchable 필드만 추출하여 검색 UI를 렌더한다. 검색 시 params 객체를 반환한다.
4. **FcSearch → FcTable 연동**: 검색 실행 시 테이블이 필터링된 데이터를 표시한다.
5. **FcTable → FcForm 연동**: 행 클릭 시 폼에 해당 행 데이터가 로드된다.
6. **FcForm 저장**: 폼 제출 시 DB에 INSERT/UPDATE가 실행되고 테이블이 새로고침된다.
7. **테스트 페이지** (`/test-fc`)에서 위 전체 흐름이 실제 DB 데이터로 동작한다.

---

## 10. 다음 단계 연결

Phase 3 (개발자 빌더)는 이 컴포넌트들을:
- 팔레트에서 선택하여 캔버스에 배치
- 속성 패널에서 config(TableConfig, FormConfig 등) 조정
- Template JSON으로 저장/로드

Phase 3은 FcTable/FcForm/FcSearch를 "배치하고 설정하는 UI"이고, Phase 2는 그 컴포넌트 자체다.
