From 020d74f36c9c7484c2673a47bd3e91ef299a7298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EB=B3=91=EC=B2=A0?= Date: Thu, 12 Feb 2026 11:17:57 +0900 Subject: [PATCH] =?UTF-8?q?feat(WEB):=20DynamicItemForm=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=ED=83=80=EC=9E=85=20=ED=99=95=EC=9E=A5=20=EB=B0=8F?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=A0=88=EC=A7=80?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DynamicFieldRenderer에 신규 필드 타입 추가 (Currency, File, MultiSelect, Radio, Reference, Toggle, UnitValue, Computed) - DynamicTableSection 및 TableCellRenderer 추가 - 필드 프리셋 및 설정 구조 분리 - 컴포넌트 레지스트리 개발 도구 페이지 추가 - UniversalListPage 개선 - 근태관리 코드 정리 - 즐겨찾기 기능 및 동적 필드 타입 백엔드 스펙 문서 추가 Co-Authored-By: Claude Opus 4.6 --- claudedocs/_index.md | 5 + ...PL-2026-02-11] dynamic-field-components.md | 343 + claudedocs/components/_registry.md | 691 ++ .../[IMPL-2026-02-11] favorites-feature.md | 122 + .../[FIX-2026-02-09] PDF-이미지-누락-해결.md | 53 + ...-02-12] dynamic-field-type-backend-spec.md | 390 + package.json | 3 +- scripts/gen-component-registry.mjs | 258 + .../ComponentRegistryClient.tsx | 522 ++ .../dev/component-registry/actions.ts | 19 + .../dev/component-registry/page.tsx | 8 + .../dev/component-registry/previews.tsx | 1191 +++ src/components/dev/DevToolbar.tsx | 11 + .../hr/AttendanceManagement/index.tsx | 77 +- .../config/reference-sources.ts | 112 + .../DynamicItemForm/fields/ComputedField.tsx | 135 + .../DynamicItemForm/fields/CurrencyField.tsx | 126 + .../fields/DynamicFieldRenderer.tsx | 42 + .../DynamicItemForm/fields/FileField.tsx | 199 + .../fields/MultiSelectField.tsx | 191 + .../DynamicItemForm/fields/RadioField.tsx | 91 + .../DynamicItemForm/fields/ReferenceField.tsx | 167 + .../DynamicItemForm/fields/ToggleField.tsx | 61 + .../DynamicItemForm/fields/UnitValueField.tsx | 128 + .../items/DynamicItemForm/fields/index.ts | 11 + .../hooks/useConditionalDisplay.ts | 46 +- .../items/DynamicItemForm/index.tsx | 3 + .../presets/section-presets.ts | 253 + .../sections/DynamicTableSection.tsx | 199 + .../sections/TableCellRenderer.tsx | 84 + .../items/DynamicItemForm/sections/index.ts | 5 +- src/components/items/DynamicItemForm/types.ts | 141 +- .../services/fieldService.ts | 3 +- .../services/masterFieldService.ts | 3 +- .../templates/UniversalListPage/index.tsx | 60 +- src/generated/component-registry.json | 6691 +++++++++++++++++ src/stores/item-master/types.ts | 3 +- src/types/item-master-api.ts | 31 +- src/types/item-master.types.ts | 6 +- 39 files changed, 12368 insertions(+), 116 deletions(-) create mode 100644 claudedocs/architecture/[IMPL-2026-02-11] dynamic-field-components.md create mode 100644 claudedocs/components/_registry.md create mode 100644 claudedocs/dashboard/[IMPL-2026-02-11] favorites-feature.md create mode 100644 claudedocs/item-master/[API-REQUEST-2026-02-12] dynamic-field-type-backend-spec.md create mode 100644 scripts/gen-component-registry.mjs create mode 100644 src/app/[locale]/(protected)/dev/component-registry/ComponentRegistryClient.tsx create mode 100644 src/app/[locale]/(protected)/dev/component-registry/actions.ts create mode 100644 src/app/[locale]/(protected)/dev/component-registry/page.tsx create mode 100644 src/app/[locale]/(protected)/dev/component-registry/previews.tsx create mode 100644 src/components/items/DynamicItemForm/config/reference-sources.ts create mode 100644 src/components/items/DynamicItemForm/fields/ComputedField.tsx create mode 100644 src/components/items/DynamicItemForm/fields/CurrencyField.tsx create mode 100644 src/components/items/DynamicItemForm/fields/FileField.tsx create mode 100644 src/components/items/DynamicItemForm/fields/MultiSelectField.tsx create mode 100644 src/components/items/DynamicItemForm/fields/RadioField.tsx create mode 100644 src/components/items/DynamicItemForm/fields/ReferenceField.tsx create mode 100644 src/components/items/DynamicItemForm/fields/ToggleField.tsx create mode 100644 src/components/items/DynamicItemForm/fields/UnitValueField.tsx create mode 100644 src/components/items/DynamicItemForm/presets/section-presets.ts create mode 100644 src/components/items/DynamicItemForm/sections/DynamicTableSection.tsx create mode 100644 src/components/items/DynamicItemForm/sections/TableCellRenderer.tsx create mode 100644 src/generated/component-registry.json diff --git a/claudedocs/_index.md b/claudedocs/_index.md index d5b91de1..c0467c86 100644 --- a/claudedocs/_index.md +++ b/claudedocs/_index.md @@ -270,6 +270,7 @@ claudedocs/ | `[PLAN-2025-11-27] item-form-component-separation.md` | ItemForm 컴포넌트 분리 | | `[IMPL-2025-11-27] realtime-sync-fixes.md` | 실시간 동기화 수정 | | `[IMPL-2026-01-09] item-management-api-integration.md` | 품목관리 API 연동 | +| `[API-REQUEST-2026-02-12] dynamic-field-type-backend-spec.md` | 동적 필드 타입 확장 백엔드 API 스펙 | | `NEXT-*.md` | 세션 체크포인트 (다수) | | `API-*.md` | API 명세/요청 (다수) | | `ANALYSIS-*.md` | 분석 노트 (다수) | @@ -357,6 +358,7 @@ claudedocs/ | 파일 | 설명 | |------|------| +| `[IMPL-2026-02-11] favorites-feature.md` | 즐겨찾기 기능 (localStorage → 추후 API 전환) | | `[IMPL-2026-01-07] ceo-dashboard-checklist.md` | 대표님 전용 대시보드 (11개 섹션) | | `dashboard-integration-complete.md` | 대시보드 통합 완료 | | `dashboard-cleanup-summary.md` | 정리 요약 | @@ -483,6 +485,9 @@ claudedocs/ | `[IMPL-2026-02-05] formatter-commonization-plan.md` | formatter 공통화 계획 | | `[IMPL] IntegratedDetailTemplate-checklist.md` | 통합 상세 템플릿 체크리스트 | | `[REF] template-migration-status.md` | 템플릿 마이그레이션 현황 | +| **동적 필드 타입 확장** | | +| `[DESIGN-2026-02-11] dynamic-field-type-extension.md` | 동적 필드 타입 확장 설계서 (4-Level 구조) | +| `[IMPL-2026-02-11] dynamic-field-components.md` | 동적 필드 컴포넌트 구현 기획서 (Phase 1~3 완료) | | **시스템 설계** | | | `[PLAN-2026-01-16] layout-restructure.md` | 레이아웃 구조 변경 | | `[PLAN-2025-12-29] dynamic-menu-refresh.md` | 동적 메뉴 갱신 시스템 | diff --git a/claudedocs/architecture/[IMPL-2026-02-11] dynamic-field-components.md b/claudedocs/architecture/[IMPL-2026-02-11] dynamic-field-components.md new file mode 100644 index 00000000..7a54aed4 --- /dev/null +++ b/claudedocs/architecture/[IMPL-2026-02-11] dynamic-field-components.md @@ -0,0 +1,343 @@ +# 동적 필드 타입 컴포넌트 — 프론트엔드 구현 기획서 + +> 작성일: 2026-02-11 +> 설계 근거: `[DESIGN-2026-02-11] dynamic-field-type-extension.md` +> 상태: ✅ 프론트 구현 완료 — 백엔드 작업 대기 +> 백엔드 스펙: `item-master/[API-REQUEST-2026-02-12] dynamic-field-type-backend-spec.md` + +--- + +## 목적 + +현재 DynamicItemForm의 필드 타입이 6종(textbox, number, dropdown, checkbox, date, textarea)으로 제한되어 있어 제조/공사/유통/물류 등 다양한 산업의 품목관리 요구를 충족하지 못함. + +**이 작업의 목표**: +- 8종의 신규 필드 컴포넌트를 미리 만들어둔다 +- 범용 테이블 섹션(DynamicTableSection)을 만든다 +- 백엔드가 `field_type` + `properties` config를 보내면 자동 렌더링되는 구조를 완성한다 +- 백엔드 작업 전에도 mock props로 독립 테스트 가능하게 한다 + +**API 연동 시 동작 흐름**: +``` +백엔드 DB: field_type = "reference", properties = { "source": "vendors" } + ↓ +API 응답: GET /v1/item-master/pages/{id}/structure + ↓ +프론트: DynamicFieldRenderer → switch("reference") → + ReferenceField가 properties.source 읽어서 /api/proxy/vendors 검색 API 호출 +``` + +--- + +## 컴포넌트 구현 목록 + +### Phase 1: 핵심 컴포넌트 + +| # | 컴포넌트 | field_type | 상태 | 비고 | +|---|---------|-----------|------|------| +| 1-1 | ReferenceField | `reference` | ✅ 완료 | 다른 테이블 검색/선택 | +| 1-2 | MultiSelectField | `multi-select` | ✅ 완료 | 복수 선택 태그 | +| 1-3 | FileField | `file` | ✅ 완료 | 파일/이미지 업로드 | +| 1-4 | DynamicTableSection | (섹션) | ✅ 완료 | 범용 테이블 섹션 | +| 1-5 | TableCellRenderer | (내부) | ✅ 완료 | 테이블 셀 렌더러 | +| 1-6 | reference-sources.ts | (config) | ✅ 완료 | 참조 소스 프리셋 정의 | +| 1-7 | DynamicFieldRenderer 확장 | (수정) | ✅ 완료 | switch문 + 신규 import | +| 1-8 | 타입 정의 확장 | (수정) | ✅ 완료 | ItemFieldType 통합 + config 인터페이스 | + +### Phase 2: 편의 컴포넌트 + +| # | 컴포넌트 | field_type | 상태 | 비고 | +|---|---------|-----------|------|------| +| 2-1 | CurrencyField | `currency` | ✅ 완료 | 통화 금액 (천단위 포맷) | +| 2-2 | UnitValueField | `unit-value` | ✅ 완료 | 값+단위 조합 (100mm) | +| 2-3 | RadioField | `radio` | ✅ 완료 | 라디오 버튼 그룹 | +| 2-4 | section-presets.ts | (config) | ✅ 완료 | 산업별 섹션 프리셋 JSON | + +### Phase 3: 고급 컴포넌트 + +| # | 컴포넌트 | field_type | 상태 | 비고 | +|---|---------|-----------|------|------| +| 3-1 | ToggleField | `toggle` | ✅ 완료 | On/Off 스위치 | +| 3-2 | ComputedField | `computed` | ✅ 완료 | 계산 필드 (읽기전용) | +| 3-3 | 조건부 표시 연산자 확장 | (수정) | ✅ 완료 | in/not_in/greater_than 등 9종 | + +--- + +## 각 컴포넌트 스펙 + +### 1-1. ReferenceField + +**파일**: `DynamicItemForm/fields/ReferenceField.tsx` + +**역할**: 다른 테이블의 데이터를 검색하여 선택 (거래처, 품목, 고객, 현장, 차량 등) + +**UI 구성**: +- 읽기전용 Input + 검색 버튼(돋보기 아이콘) +- 클릭 시 SearchableSelectionModal 열림 +- 선택 후 displayField 값 표시 +- X 버튼으로 선택 해제 + +**properties에서 읽는 값**: +```typescript +interface ReferenceConfig { + source: string; // "vendors" | "items" | "custom" 등 + displayField?: string; // 기본 "name" + valueField?: string; // 기본 "id" + searchFields?: string[]; // 기본 ["name"] + searchApiUrl?: string; // source="custom"일 때 필수 + columns?: Array<{ key: string; label: string; width?: string }>; + displayFormat?: string; // "{code} - {name}" + returnFields?: string[]; // ["id", "code", "name"] +} +``` + +**API 연동 시**: +- `REFERENCE_SOURCES[source]`에서 apiUrl 조회 +- `GET {apiUrl}?search={query}&size=20` 호출 +- 결과를 SearchableSelectionModal에 표시 + +**API 연동 전 (mock)**: +- props로 전달된 options 사용 또는 +- 빈 상태에서 UI/UX만 확인 + +--- + +### 1-2. MultiSelectField + +**파일**: `DynamicItemForm/fields/MultiSelectField.tsx` + +**역할**: 여러 항목을 동시에 선택 (태그 칩 형태로 표시) + +**UI 구성**: +- Combobox (검색 가능한 드롭다운) +- 선택된 항목은 칩(Chip/Badge)으로 표시 +- 칩의 X 버튼으로 개별 해제 + +**properties에서 읽는 값**: +```typescript +interface MultiSelectConfig { + maxSelections?: number; // 최대 선택 수 (기본: 무제한) + allowCustom?: boolean; // 직접 입력 허용 (기본: false) + layout?: 'chips' | 'list'; // 기본: "chips" +} +``` + +**options**: 기존 dropdown과 동일 `[{label, value}]` + +**저장값**: `string[]` (예: `["CUT", "BEND", "WELD"]`) + +--- + +### 1-3. FileField + +**파일**: `DynamicItemForm/fields/FileField.tsx` + +**역할**: 파일/이미지 첨부 + +**UI 구성**: +- 파일 선택 버튼 ("파일 선택" 또는 드래그 앤 드롭 영역) +- 선택된 파일 목록 표시 (이름, 크기, 삭제 버튼) +- 이미지 파일일 경우 미리보기 썸네일 + +**properties에서 읽는 값**: +```typescript +interface FileConfig { + accept?: string; // ".pdf,.doc" (기본: "*") + maxSize?: number; // bytes (기본: 10MB = 10485760) + maxFiles?: number; // 기본: 1 + preview?: boolean; // 이미지 미리보기 (기본: true) + category?: string; // 파일 카테고리 태그 +} +``` + +**API 연동 시**: `POST /v1/files/upload` (multipart) +**API 연동 전**: File 객체를 로컬 상태로 관리, URL.createObjectURL로 미리보기 + +--- + +### 1-4. DynamicTableSection + +**파일**: `DynamicItemForm/sections/DynamicTableSection.tsx` + +**역할**: config 기반 범용 테이블 (공정, 품질검사, 구매처, 공정표, 배차 등) + +**UI 구성**: +- 테이블 헤더 (columns config 기반) +- 행 추가/삭제 버튼 +- 각 셀은 TableCellRenderer (= DynamicFieldRenderer 재사용) +- 요약행 (선택, summaryRow config) +- 빈 상태 메시지 + +**props**: +```typescript +interface DynamicTableSectionProps { + section: ItemSectionResponse; + tableConfig: TableConfig; + rows: Record[]; + onRowsChange: (rows: Record[]) => void; + disabled?: boolean; +} +``` + +**tableConfig 구조**: 설계서 섹션 4.3 참조 + +**API 연동 시**: `GET/PUT /v1/items/{itemId}/section-data/{sectionId}` +**API 연동 전**: rows를 폼 상태(formData)에 로컬 관리 + +--- + +### 1-5. TableCellRenderer + +**파일**: `DynamicItemForm/sections/TableCellRenderer.tsx` + +**역할**: 테이블 셀 = DynamicFieldRenderer를 테이블 셀용 축소 모드로 래핑 + +**핵심**: column config → ItemFieldResponse 호환 객체로 변환 → DynamicFieldRenderer 호출 + +```typescript +function TableCellRenderer({ column, value, onChange, compact }) { + const fieldLike = columnToFieldResponse(column); + return ; +} +``` + +--- + +### 1-6. reference-sources.ts + +**파일**: `DynamicItemForm/config/reference-sources.ts` + +**역할**: reference 필드의 소스별 기본 설정 (API URL, 표시 필드, 검색 컬럼) + +**내용**: 공통(vendors, items, customers, employees, warehouses) + 산업별(processes, sites, vehicles, stores 등) + +**확장 방법**: 새 소스 추가 = 이 파일에 객체 1개 추가 + +--- + +### 2-1. CurrencyField + +**파일**: `DynamicItemForm/fields/CurrencyField.tsx` + +**역할**: 통화 금액 입력 (천단위 콤마, 통화 기호) + +**UI**: Input + 통화기호(₩) prefix + 천단위 포맷 +- 입력 중: 숫자만 +- 포커스 아웃: "₩15,000" 포맷 + +**properties**: `{ currency, precision, showSymbol, allowNegative }` + +**저장값**: `number` (포맷 없이) + +--- + +### 2-2. UnitValueField + +**파일**: `DynamicItemForm/fields/UnitValueField.tsx` + +**역할**: 값 + 단위 조합 입력 (100mm, 50kg) + +**UI**: Input(숫자) + Select(단위) 가로 배치 + +**properties**: `{ units, defaultUnit, precision }` + +**저장값**: `{ value: number, unit: string }` + +--- + +### 2-3. RadioField + +**파일**: `DynamicItemForm/fields/RadioField.tsx` + +**역할**: 라디오 버튼 그룹 + +**UI**: RadioGroup (수평/수직) + +**properties**: `{ layout: "horizontal" | "vertical" }` + +**options**: `[{label, value}]` + +--- + +### 3-1. ToggleField + +**파일**: `DynamicItemForm/fields/ToggleField.tsx` + +**역할**: On/Off 토글 스위치 + +**UI**: Switch + 라벨 + +**properties**: `{ onLabel, offLabel, onValue, offValue }` + +--- + +### 3-2. ComputedField + +**파일**: `DynamicItemForm/fields/ComputedField.tsx` + +**역할**: 다른 필드 기반 자동 계산 (읽기 전용) + +**UI**: 읽기전용 표시 (배경색 구분, muted) + +**properties**: `{ formula, dependsOn, format, precision }` + +**동작**: `dependsOn` 필드 값이 변경될 때마다 formula 재계산 + +--- + +## 파일 구조 + +``` +DynamicItemForm/ +├── fields/ +│ ├── DynamicFieldRenderer.tsx ← switch 확장 +│ ├── TextField.tsx (기존) +│ ├── NumberField.tsx (기존) +│ ├── DropdownField.tsx (기존) +│ ├── CheckboxField.tsx (기존) +│ ├── DateField.tsx (기존) +│ ├── TextareaField.tsx (기존) +│ ├── ReferenceField.tsx ★ Phase 1 +│ ├── MultiSelectField.tsx ★ Phase 1 +│ ├── FileField.tsx ★ Phase 1 +│ ├── CurrencyField.tsx ★ Phase 2 +│ ├── UnitValueField.tsx ★ Phase 2 +│ ├── RadioField.tsx ★ Phase 2 +│ ├── ToggleField.tsx ★ Phase 3 +│ └── ComputedField.tsx ★ Phase 3 +├── sections/ +│ ├── DynamicBOMSection.tsx (기존) +│ ├── DynamicTableSection.tsx ★ Phase 1 +│ └── TableCellRenderer.tsx ★ Phase 1 +├── config/ +│ └── reference-sources.ts ★ Phase 1 +├── presets/ +│ └── section-presets.ts ★ Phase 2 +├── hooks/ (기존, 변경 없음) +├── types.ts ← 타입 확장 +└── index.tsx ← table 섹션 렌더링 추가 +``` + +## 기존 코드 수정 범위 + +| 파일 | 수정 내용 | 줄 수 | +|------|----------|-------| +| `DynamicFieldRenderer.tsx` | switch case 8개 추가 + import | +20줄 | +| `types.ts` | ExtendedFieldType union + config 인터페이스 | +80줄 | +| `index.tsx` | 섹션 렌더링에 `case 'table'` 추가 | +15줄 | +| `item-master-api.ts` | field_type union 확장 | +3줄 | + +**기존 컴포넌트 6개 + BOM + hooks 7개 = 변경 없음** + +--- + +## 상태 범례 + +- ⬜ 대기 +- 🔄 진행 중 +- ✅ 완료 +- ⏸️ 보류 + +--- + +**마지막 업데이트**: 2026-02-12 — Phase 1+2+3 전체 완료 (15/15 항목) diff --git a/claudedocs/components/_registry.md b/claudedocs/components/_registry.md new file mode 100644 index 00000000..7859de84 --- /dev/null +++ b/claudedocs/components/_registry.md @@ -0,0 +1,691 @@ +# Component Registry + +> Auto-generated: 2026-02-12T01:56:50.520Z +> Total: **501** components + +## UI (53) + +### ui (53) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| Accordion | accordion.tsx | none | | Y | 66 | +| AccountNumberInput | account-number-input.tsx | none | AccountNumberInputProps | Y | 95 | +| Alert | alert.tsx | none | VariantProps | | 59 | +| AlertDialog | alert-dialog.tsx | none | | Y | 158 | +| Badge | badge.tsx | none | VariantProps | | 47 | +| BusinessNumberInput | business-number-input.tsx | none | BusinessNumberInputProps | Y | 114 | +| Button | button.tsx | none | VariantProps | | 62 | +| Calendar | calendar.tsx | none | | Y | 138 | +| Card | card.tsx | none | | | 93 | +| CardNumberInput | card-number-input.tsx | none | CardNumberInputProps | Y | 95 | +| ChartWrapper | chart-wrapper.tsx | named | ChartWrapperProps | | 66 | +| Checkbox | checkbox.tsx | none | | Y | 33 | +| Collapsible | collapsible.tsx | none | | Y | 33 | +| Command | command.tsx | none | | Y | 177 | +| ConfirmDialog | confirm-dialog.tsx | both | ConfirmDialogProps | Y | 226 | +| CurrencyInput | currency-input.tsx | none | CurrencyInputProps | Y | 220 | +| DatePicker | date-picker.tsx | none | DatePickerProps | Y | 279 | +| Dialog | dialog.tsx | none | | Y | 137 | +| Drawer | drawer.tsx | none | | Y | 133 | +| DropdownMenu | dropdown-menu.tsx | none | | Y | 258 | +| EmptyState | empty-state.tsx | both | ButtonProps | Y | 227 | +| ErrorCard | error-card.tsx | named | ErrorCardProps | Y | 196 | +| ErrorMessage | error-message.tsx | named | ErrorMessageProps | | 38 | +| FileDropzone | file-dropzone.tsx | both | FileDropzoneProps | Y | 227 | +| FileInput | file-input.tsx | both | FileInputProps | Y | 226 | +| FileList | file-list.tsx | both | FileListProps | Y | 276 | +| ImageUpload | image-upload.tsx | both | ImageUploadProps | Y | 309 | +| Input | input.tsx | none | | | 22 | +| Label | label.tsx | none | | Y | 25 | +| LoadingSpinner | loading-spinner.tsx | named | LoadingSpinnerProps | | 114 | +| MultiSelectCombobox | multi-select-combobox.tsx | named | MultiSelectComboboxProps | Y | 128 | +| NumberInput | number-input.tsx | none | NumberInputProps | Y | 280 | +| PersonalNumberInput | personal-number-input.tsx | none | PersonalNumberInputProps | Y | 101 | +| PhoneInput | phone-input.tsx | none | PhoneInputProps | Y | 95 | +| Popover | popover.tsx | none | | Y | 53 | +| Progress | progress.tsx | none | | Y | 32 | +| QuantityInput | quantity-input.tsx | none | QuantityInputProps | Y | 271 | +| RadioGroup | radio-group.tsx | none | | Y | 46 | +| ScrollArea | scroll-area.tsx | none | | Y | 53 | +| SearchableSelect | searchable-select.tsx | named | SearchableSelectProps | Y | 219 | +| Select | select.tsx | none | | Y | 192 | +| Separator | separator.tsx | none | SeparatorProps | Y | 32 | +| Sheet | sheet.tsx | none | | Y | 146 | +| Skeleton | skeleton.tsx | none | SkeletonProps | Y | 679 | +| Slider | slider.tsx | none | | | 26 | +| StatusBadge | status-badge.tsx | both | StatusBadgeProps | Y | 123 | +| Switch | switch.tsx | none | | Y | 32 | +| Table | table.tsx | none | | | 117 | +| Tabs | tabs.tsx | none | | Y | 66 | +| Textarea | textarea.tsx | none | | | 25 | +| TimePicker | time-picker.tsx | none | TimePickerProps | Y | 191 | +| Tooltip | tooltip.tsx | none | | Y | 48 | +| VisuallyHidden | visually-hidden.tsx | none | | Y | 14 | + +## ATOMS (3) + +### atoms (3) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| BadgeSm | BadgeSm.tsx | named | BadgeSmProps | Y | 117 | +| ScrollableButtonGroup | ScrollableButtonGroup.tsx | named | ScrollableButtonGroupProps | | 53 | +| TabChip | TabChip.tsx | named | TabChipProps | Y | 72 | + +## MOLECULES (8) + +### molecules (8) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| DateRangeSelector | DateRangeSelector.tsx | named | DateRangeSelectorProps | Y | 217 | +| FormField | FormField.tsx | named | FormFieldProps | | 296 | +| IconWithBadge | IconWithBadge.tsx | named | IconWithBadgeProps | Y | 51 | +| MobileFilter | MobileFilter.tsx | named | MobileFilterProps | Y | 335 | +| StandardDialog | StandardDialog.tsx | named | StandardDialogProps | Y | 219 | +| StatusBadge | StatusBadge.tsx | named | StatusBadgeProps | Y | 111 | +| TableActions | TableActions.tsx | named | TableActionsProps | Y | 89 | +| YearQuarterFilter | YearQuarterFilter.tsx | named | YearQuarterFilterProps | Y | 98 | + +## ORGANISMS (12) + +### organisms (12) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| DataTable | DataTable.tsx | named | DataTableProps | Y | 363 | +| EmptyState | EmptyState.tsx | named | EmptyStateProps | Y | 38 | +| FormActions | FormActions.tsx | named | FormActionsProps | | 74 | +| FormFieldGrid | FormFieldGrid.tsx | named | FormFieldGridProps | | 35 | +| FormSection | FormSection.tsx | named | FormSectionProps | | 62 | +| MobileCard | MobileCard.tsx | named | InfoFieldProps | Y | 347 | +| PageHeader | PageHeader.tsx | named | PageHeaderProps | Y | 42 | +| PageLayout | PageLayout.tsx | named | PageLayoutProps | Y | 32 | +| ScreenVersionHistory | ScreenVersionHistory.tsx | named | ScreenVersionHistoryProps | Y | 75 | +| SearchableSelectionModal | SearchableSelectionModal.tsx | named | | Y | 253 | +| SearchFilter | SearchFilter.tsx | named | SearchFilterProps | Y | 58 | +| StatCards | StatCards.tsx | named | StatCardsProps | Y | 67 | + +## COMMON (16) + +### common (16) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| AccessDenied | AccessDenied.tsx | named | AccessDeniedProps | Y | 68 | +| CalendarHeader | CalendarHeader.tsx | named | | Y | 148 | +| DayCell | DayCell.tsx | named | DayCellProps | Y | 93 | +| DayTimeView | DayTimeView.tsx | named | | Y | 167 | +| EditableTable | EditableTable.tsx | both | EditableTableProps | Y | 333 | +| EmptyPage | EmptyPage.tsx | named | EmptyPageProps | Y | 150 | +| MonthView | MonthView.tsx | named | WeekRowProps | Y | 264 | +| MorePopover | MorePopover.tsx | named | MorePopoverProps | Y | 45 | +| NoticePopupModal | NoticePopupModal.tsx | named | NoticePopupModalProps | Y | 171 | +| ParentMenuRedirect | ParentMenuRedirect.tsx | named | ParentMenuRedirectProps | Y | 82 | +| PermissionGuard | PermissionGuard.tsx | named | PermissionGuardProps | Y | 44 | +| ScheduleBar | ScheduleBar.tsx | named | ScheduleBarProps | Y | 96 | +| ScheduleCalendar | ScheduleCalendar.tsx | named | | Y | 194 | +| ServerErrorPage | ServerErrorPage.tsx | named | ServerErrorPageProps | Y | 140 | +| WeekTimeView | WeekTimeView.tsx | named | | Y | 217 | +| WeekView | WeekView.tsx | named | | Y | 211 | + +## LAYOUT (3) + +### layout (3) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| CommandMenuSearch | CommandMenuSearch.tsx | default | | Y | 199 | +| HeaderFavoritesBar | HeaderFavoritesBar.tsx | default | HeaderFavoritesBarProps | Y | 156 | +| Sidebar | Sidebar.tsx | default | SidebarProps | | 390 | + +## DEV (2) + +### dev (2) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| DevFillProvider | DevFillContext.tsx | named | DevFillProviderProps | Y | 179 | +| DevToolbar | DevToolbar.tsx | both | | Y | 499 | + +## DOMAIN (404) + +### LanguageSelect.tsx (1) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| LanguageSelect | LanguageSelect.tsx | named | LanguageSelectProps | Y | 90 | + +### ThemeSelect.tsx (1) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| ThemeSelect | ThemeSelect.tsx | named | ThemeSelectProps | Y | 82 | + +### accounting (19) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| BadDebtDetail | BadDebtDetail.tsx | named | BadDebtDetailProps | Y | 963 | +| BadDebtDetailClientV2 | BadDebtDetailClientV2.tsx | named | BadDebtDetailClientV2Props | Y | 136 | +| BillDetail | BillDetail.tsx | named | BillDetailProps | Y | 540 | +| BillManagementClient | BillManagementClient.tsx | named | BillManagementClientProps | Y | 522 | +| CardTransactionDetailClient | CardTransactionDetailClient.tsx | default | CardTransactionDetailClientProps | Y | 139 | +| CreditAnalysisDocument | CreditAnalysisDocument.tsx | named | CreditAnalysisDocumentProps | Y | 210 | +| CreditSignal | CreditSignal.tsx | named | CreditSignalProps | Y | 58 | +| DepositDetail | DepositDetail.tsx | named | DepositDetailProps | Y | 327 | +| DepositDetailClientV2 | DepositDetailClientV2.tsx | default | DepositDetailClientV2Props | Y | 144 | +| PurchaseDetail | PurchaseDetail.tsx | named | PurchaseDetailProps | Y | 697 | +| PurchaseDetailModal | PurchaseDetailModal.tsx | named | PurchaseDetailModalProps | Y | 402 | +| RiskRadarChart | RiskRadarChart.tsx | named | RiskRadarChartProps | Y | 95 | +| SalesDetail | SalesDetail.tsx | named | SalesDetailProps | Y | 579 | +| VendorDetail | VendorDetail.tsx | named | VendorDetailProps | Y | 684 | +| VendorDetailClient | VendorDetailClient.tsx | named | VendorDetailClientProps | Y | 586 | +| VendorLedgerDetail | VendorLedgerDetail.tsx | named | VendorLedgerDetailProps | Y | 386 | +| VendorManagementClient | VendorManagementClient.tsx | named | VendorManagementClientProps | Y | 574 | +| WithdrawalDetail | WithdrawalDetail.tsx | named | WithdrawalDetailProps | Y | 327 | +| WithdrawalDetailClientV2 | WithdrawalDetailClientV2.tsx | default | WithdrawalDetailClientV2Props | Y | 144 | + +### approval (11) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| ApprovalLineBox | ApprovalLineBox.tsx | named | ApprovalLineBoxProps | Y | 85 | +| ApprovalLineSection | ApprovalLineSection.tsx | named | ApprovalLineSectionProps | Y | 108 | +| BasicInfoSection | BasicInfoSection.tsx | named | BasicInfoSectionProps | Y | 81 | +| DocumentDetailModalV2 | DocumentDetailModalV2.tsx | named | | Y | 94 | +| ExpenseEstimateDocument | ExpenseEstimateDocument.tsx | named | ExpenseEstimateDocumentProps | Y | 130 | +| ExpenseEstimateForm | ExpenseEstimateForm.tsx | named | ExpenseEstimateFormProps | Y | 167 | +| ExpenseReportDocument | ExpenseReportDocument.tsx | named | ExpenseReportDocumentProps | Y | 138 | +| ExpenseReportForm | ExpenseReportForm.tsx | named | ExpenseReportFormProps | Y | 243 | +| ProposalDocument | ProposalDocument.tsx | named | ProposalDocumentProps | Y | 117 | +| ProposalForm | ProposalForm.tsx | named | ProposalFormProps | Y | 234 | +| ReferenceSection | ReferenceSection.tsx | named | ReferenceSectionProps | Y | 109 | + +### attendance (2) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| AttendanceComplete | AttendanceComplete.tsx | default | AttendanceCompleteProps | Y | 83 | +| GoogleMap | GoogleMap.tsx | default | GoogleMapProps | Y | 309 | + +### auth (2) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| LoginPage | LoginPage.tsx | named | | Y | 301 | +| SignupPage | SignupPage.tsx | named | | Y | 763 | + +### board (8) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| BoardDetail | BoardDetail.tsx | named | BoardDetailProps | Y | 120 | +| BoardDetailClientV2 | BoardDetailClientV2.tsx | named | BoardDetailClientV2Props | Y | 308 | +| BoardForm | BoardForm.tsx | named | BoardFormProps | Y | 271 | +| BoardListUnified | BoardListUnified.tsx | both | | Y | 372 | +| CommentItem | CommentItem.tsx | both | CommentItemProps | Y | 161 | +| DynamicBoardCreateForm | DynamicBoardCreateForm.tsx | named | DynamicBoardCreateFormProps | Y | 166 | +| DynamicBoardEditForm | DynamicBoardEditForm.tsx | named | DynamicBoardEditFormProps | Y | 253 | +| MenuBar | MenuBar.tsx | named | MenuBarProps | Y | 289 | + +### business (97) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| BiddingDetailForm | BiddingDetailForm.tsx | default | BiddingDetailFormProps | Y | 533 | +| BiddingListClient | BiddingListClient.tsx | default | BiddingListClientProps | Y | 385 | +| CalendarSection | CalendarSection.tsx | named | CalendarSectionProps | Y | 421 | +| CardManagementSection | CardManagementSection.tsx | named | CardManagementSectionProps | Y | 71 | +| CategoryDialog | CategoryDialog.tsx | named | | Y | 89 | +| CEODashboard | CEODashboard.tsx | named | | Y | 407 | +| ConstructionDashboard | ConstructionDashboard.tsx | named | | Y | 19 | +| ConstructionDetailCard | ConstructionDetailCard.tsx | named | ConstructionDetailCardProps | Y | 83 | +| ConstructionDetailClient | ConstructionDetailClient.tsx | default | ConstructionDetailClientProps | Y | 732 | +| ConstructionMainDashboard | ConstructionMainDashboard.tsx | named | | Y | 196 | +| ConstructionManagementListClient | ConstructionManagementListClient.tsx | default | ConstructionManagementListClientProps | Y | 540 | +| ContractDetailForm | ContractDetailForm.tsx | default | ContractDetailFormProps | Y | 541 | +| ContractDocumentModal | ContractDocumentModal.tsx | named | ContractDocumentModalProps | Y | 64 | +| ContractInfoCard | ContractInfoCard.tsx | named | ContractInfoCardProps | Y | 169 | +| ContractInfoCard | ContractInfoCard.tsx | named | ContractInfoCardProps | Y | 58 | +| ContractListClient | ContractListClient.tsx | default | ContractListClientProps | Y | 399 | +| DailyReportSection | DailyReportSection.tsx | named | DailyReportSectionProps | Y | 37 | +| Dashboard | Dashboard.tsx | named | | Y | 33 | +| DashboardSettingsDialog | DashboardSettingsDialog.tsx | named | DashboardSettingsDialogProps | Y | 744 | +| DashboardSwitcher | DashboardSwitcher.tsx | named | | Y | 89 | +| DebtCollectionSection | DebtCollectionSection.tsx | named | DebtCollectionSectionProps | Y | 62 | +| DetailAccordion | DetailAccordion.tsx | default | DetailAccordionProps | Y | 199 | +| DetailCard | DetailCard.tsx | default | DetailCardProps | Y | 69 | +| DetailModal | DetailModal.tsx | named | DetailModalProps | Y | 763 | +| DirectConstructionContent | DirectConstructionContent.tsx | named | DirectConstructionContentProps | Y | 157 | +| DirectConstructionModal | DirectConstructionModal.tsx | named | DirectConstructionModalProps | Y | 60 | +| ElectronicApprovalModal | ElectronicApprovalModal.tsx | named | ElectronicApprovalModalProps | Y | 299 | +| ElectronicApprovalModal | ElectronicApprovalModal.tsx | none | | | 2 | +| EnhancedDailyReportSection | EnhancedSections.tsx | named | EnhancedDailyReportSectionProps | Y | 534 | +| EntertainmentSection | EntertainmentSection.tsx | named | EntertainmentSectionProps | Y | 53 | +| EstimateDetailForm | EstimateDetailForm.tsx | default | EstimateDetailFormProps | Y | 761 | +| EstimateDetailTableSection | EstimateDetailTableSection.tsx | named | EstimateDetailTableSectionProps | Y | 657 | +| EstimateDocumentContent | EstimateDocumentContent.tsx | named | EstimateDocumentContentProps | Y | 286 | +| EstimateDocumentModal | EstimateDocumentModal.tsx | named | EstimateDocumentModalProps | Y | 88 | +| EstimateInfoSection | EstimateInfoSection.tsx | named | EstimateInfoSectionProps | Y | 262 | +| EstimateListClient | EstimateListClient.tsx | default | EstimateListClientProps | Y | 376 | +| EstimateSummarySection | EstimateSummarySection.tsx | named | EstimateSummarySectionProps | Y | 182 | +| ExpenseDetailSection | ExpenseDetailSection.tsx | named | ExpenseDetailSectionProps | Y | 197 | +| HandoverReportDetailForm | HandoverReportDetailForm.tsx | default | HandoverReportDetailFormProps | Y | 694 | +| HandoverReportDocumentModal | HandoverReportDocumentModal.tsx | named | HandoverReportDocumentModalProps | Y | 236 | +| HandoverReportListClient | HandoverReportListClient.tsx | default | HandoverReportListClientProps | Y | 387 | +| IndirectConstructionContent | IndirectConstructionContent.tsx | named | IndirectConstructionContentProps | Y | 143 | +| IndirectConstructionModal | IndirectConstructionModal.tsx | named | IndirectConstructionModalProps | Y | 60 | +| IssueDetailForm | IssueDetailForm.tsx | default | IssueDetailFormProps | Y | 625 | +| IssueManagementListClient | IssueManagementListClient.tsx | default | IssueManagementListClientProps | Y | 514 | +| ItemDetailClient | ItemDetailClient.tsx | default | ItemDetailClientProps | Y | 487 | +| ItemManagementClient | ItemManagementClient.tsx | default | ItemManagementClientProps | Y | 618 | +| KanbanColumn | KanbanColumn.tsx | default | KanbanColumnProps | Y | 53 | +| LaborDetailClient | LaborDetailClient.tsx | default | LaborDetailClientProps | Y | 121 | +| LaborManagementClient | LaborManagementClient.tsx | default | LaborManagementClientProps | Y | 372 | +| MainDashboard | MainDashboard.tsx | named | | | 2652 | +| MonthlyExpenseSection | MonthlyExpenseSection.tsx | named | MonthlyExpenseSectionProps | Y | 38 | +| OrderDetailForm | OrderDetailForm.tsx | default | OrderDetailFormProps | Y | 276 | +| OrderDetailItemTable | OrderDetailItemTable.tsx | named | OrderDetailItemTableProps | Y | 445 | +| OrderDialogs | OrderDialogs.tsx | named | OrderDialogsProps | Y | 66 | +| OrderDocumentModal | OrderDocumentModal.tsx | named | OrderDocumentModalProps | Y | 311 | +| OrderInfoCard | OrderInfoCard.tsx | named | OrderInfoCardProps | Y | 143 | +| OrderManagementListClient | OrderManagementListClient.tsx | default | OrderManagementListClientProps | Y | 608 | +| OrderManagementUnified | OrderManagementUnified.tsx | both | OrderManagementUnifiedProps | Y | 641 | +| OrderMemoCard | OrderMemoCard.tsx | named | OrderMemoCardProps | Y | 29 | +| OrderScheduleCard | OrderScheduleCard.tsx | named | OrderScheduleCardProps | Y | 42 | +| PartnerForm | PartnerForm.tsx | default | PartnerFormProps | Y | 642 | +| PartnerListClient | PartnerListClient.tsx | default | PartnerListClientProps | Y | 335 | +| PhotoDocumentContent | PhotoDocumentContent.tsx | named | PhotoDocumentContentProps | Y | 130 | +| PhotoDocumentModal | PhotoDocumentModal.tsx | named | PhotoDocumentModalProps | Y | 60 | +| PhotoTable | PhotoTable.tsx | named | PhotoTableProps | Y | 153 | +| PriceAdjustmentSection | PriceAdjustmentSection.tsx | named | PriceAdjustmentSectionProps | Y | 150 | +| PricingDetailClient | PricingDetailClient.tsx | default | PricingDetailClientProps | Y | 135 | +| PricingListClient | PricingListClient.tsx | default | PricingListClientProps | Y | 477 | +| ProgressBillingDetailForm | ProgressBillingDetailForm.tsx | default | ProgressBillingDetailFormProps | Y | 193 | +| ProgressBillingInfoCard | ProgressBillingInfoCard.tsx | named | ProgressBillingInfoCardProps | Y | 78 | +| ProgressBillingItemTable | ProgressBillingItemTable.tsx | named | ProgressBillingItemTableProps | Y | 193 | +| ProgressBillingManagementListClient | ProgressBillingManagementListClient.tsx | default | ProgressBillingManagementListClientProps | Y | 343 | +| ProjectCard | ProjectCard.tsx | default | ProjectCardProps | Y | 89 | +| ProjectDetailClient | ProjectDetailClient.tsx | default | ProjectDetailClientProps | Y | 197 | +| ProjectEndDialog | ProjectEndDialog.tsx | default | ProjectEndDialogProps | Y | 192 | +| ProjectGanttChart | ProjectGanttChart.tsx | default | ProjectGanttChartProps | Y | 367 | +| ProjectKanbanBoard | ProjectKanbanBoard.tsx | default | ProjectKanbanBoardProps | Y | 244 | +| ProjectListClient | ProjectListClient.tsx | default | ProjectListClientProps | Y | 629 | +| ReceivableSection | ReceivableSection.tsx | named | ReceivableSectionProps | Y | 69 | +| ScheduleDetailModal | ScheduleDetailModal.tsx | named | ScheduleDetailModalProps | Y | 290 | +| SECTION_THEME_STYLES | components.tsx | named | | Y | 434 | +| SiteBriefingForm | SiteBriefingForm.tsx | default | SiteBriefingFormProps | Y | 957 | +| SiteBriefingListClient | SiteBriefingListClient.tsx | default | SiteBriefingListClientProps | Y | 362 | +| SiteDetailClientV2 | SiteDetailClientV2.tsx | both | SiteDetailClientV2Props | Y | 141 | +| SiteDetailForm | SiteDetailForm.tsx | default | SiteDetailFormProps | Y | 386 | +| SiteManagementListClient | SiteManagementListClient.tsx | default | SiteManagementListClientProps | Y | 338 | +| StageCard | StageCard.tsx | default | StageCardProps | Y | 89 | +| StatusBoardSection | StatusBoardSection.tsx | named | StatusBoardSectionProps | Y | 72 | +| StructureReviewDetailClientV2 | StructureReviewDetailClientV2.tsx | both | StructureReviewDetailClientV2Props | Y | 149 | +| StructureReviewDetailForm | StructureReviewDetailForm.tsx | default | StructureReviewDetailFormProps | Y | 390 | +| StructureReviewListClient | StructureReviewListClient.tsx | default | StructureReviewListClientProps | Y | 375 | +| TodayIssueSection | TodayIssueSection.tsx | named | TodayIssueSectionProps | Y | 453 | +| UtilityManagementListClient | UtilityManagementListClient.tsx | default | UtilityManagementListClientProps | Y | 395 | +| VatSection | VatSection.tsx | named | VatSectionProps | Y | 38 | +| WelfareSection | WelfareSection.tsx | named | WelfareSectionProps | Y | 53 | +| WorkerStatusListClient | WorkerStatusListClient.tsx | default | WorkerStatusListClientProps | Y | 416 | + +### checklist-management (7) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| ChecklistDetail | ChecklistDetail.tsx | named | ChecklistDetailProps | Y | 316 | +| ChecklistDetailClient | ChecklistDetailClient.tsx | named | ChecklistDetailClientProps | Y | 123 | +| ChecklistForm | ChecklistForm.tsx | named | ChecklistFormProps | Y | 173 | +| ChecklistListClient | ChecklistListClient.tsx | default | | Y | 520 | +| ItemDetail | ItemDetail.tsx | named | ItemDetailProps | Y | 224 | +| ItemDetailClient | ItemDetailClient.tsx | named | ItemDetailClientProps | Y | 111 | +| ItemForm | ItemForm.tsx | named | ItemFormProps | Y | 351 | + +### clients (3) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| ClientDetail | ClientDetail.tsx | named | ClientDetailProps | Y | 254 | +| ClientDetailClientV2 | ClientDetailClientV2.tsx | named | ClientDetailClientV2Props | Y | 253 | +| ClientRegistration | ClientRegistration.tsx | named | ClientRegistrationProps | Y | 468 | + +### customer-center (9) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| EventDetail | EventDetail.tsx | both | EventDetailProps | Y | 102 | +| EventList | EventList.tsx | both | | Y | 261 | +| FAQList | FAQList.tsx | both | | Y | 172 | +| InquiryDetail | InquiryDetail.tsx | both | InquiryDetailProps | Y | 359 | +| InquiryDetailClientV2 | InquiryDetailClientV2.tsx | both | InquiryDetailClientV2Props | Y | 224 | +| InquiryForm | InquiryForm.tsx | both | InquiryFormProps | Y | 237 | +| InquiryList | InquiryList.tsx | both | | Y | 292 | +| NoticeDetail | NoticeDetail.tsx | both | NoticeDetailProps | Y | 102 | +| NoticeList | NoticeList.tsx | both | | Y | 227 | + +### document-system (11) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| ApprovalLine | ApprovalLine.tsx | named | ApprovalLineProps | Y | 170 | +| ConstructionApprovalTable | ConstructionApprovalTable.tsx | named | ConstructionApprovalTableProps | Y | 116 | +| DocumentContent | DocumentContent.tsx | named | DocumentContentProps | Y | 59 | +| DocumentHeader | DocumentHeader.tsx | named | DocumentHeaderProps | Y | 248 | +| DocumentToolbar | DocumentToolbar.tsx | named | DocumentToolbarProps | Y | 327 | +| DocumentViewer | DocumentViewer.tsx | named | | Y | 378 | +| InfoTable | InfoTable.tsx | named | InfoTableProps | Y | 95 | +| LotApprovalTable | LotApprovalTable.tsx | named | LotApprovalTableProps | Y | 122 | +| QualityApprovalTable | QualityApprovalTable.tsx | named | QualityApprovalTableProps | Y | 123 | +| SectionHeader | SectionHeader.tsx | named | SectionHeaderProps | Y | 46 | +| SignatureSection | SignatureSection.tsx | named | SignatureSectionProps | Y | 107 | + +### hr (24) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| AttendanceInfoDialog | AttendanceInfoDialog.tsx | named | | Y | 301 | +| CardDetail | CardDetail.tsx | named | CardDetailProps | Y | 132 | +| CardForm | CardForm.tsx | named | CardFormProps | Y | 246 | +| CardManagementUnified | CardManagementUnified.tsx | named | CardManagementUnifiedProps | Y | 267 | +| CSVUploadDialog | CSVUploadDialog.tsx | named | CSVUploadDialogProps | Y | 252 | +| CSVUploadPage | CSVUploadPage.tsx | named | CSVUploadPageProps | Y | 355 | +| DepartmentDialog | DepartmentDialog.tsx | named | | Y | 92 | +| DepartmentStats | DepartmentStats.tsx | named | | Y | 18 | +| DepartmentToolbar | DepartmentToolbar.tsx | named | | Y | 60 | +| DepartmentTree | DepartmentTree.tsx | named | | Y | 70 | +| DepartmentTreeItem | DepartmentTreeItem.tsx | named | | Y | 118 | +| EmployeeDetail | EmployeeDetail.tsx | named | EmployeeDetailProps | Y | 222 | +| EmployeeDialog | EmployeeDialog.tsx | named | | Y | 582 | +| EmployeeForm | EmployeeForm.tsx | named | EmployeeFormProps | Y | 1052 | +| EmployeeToolbar | EmployeeToolbar.tsx | named | EmployeeToolbarProps | Y | 82 | +| FieldSettingsDialog | FieldSettingsDialog.tsx | named | FieldSettingsDialogProps | Y | 259 | +| ReasonInfoDialog | ReasonInfoDialog.tsx | named | | Y | 140 | +| SalaryDetailDialog | SalaryDetailDialog.tsx | named | SalaryDetailDialogProps | Y | 420 | +| UserInviteDialog | UserInviteDialog.tsx | named | UserInviteDialogProps | Y | 116 | +| VacationAdjustDialog | VacationAdjustDialog.tsx | named | VacationAdjustDialogProps | Y | 225 | +| VacationGrantDialog | VacationGrantDialog.tsx | named | VacationGrantDialogProps | Y | 202 | +| VacationRegisterDialog | VacationRegisterDialog.tsx | named | VacationRegisterDialogProps | Y | 201 | +| VacationRequestDialog | VacationRequestDialog.tsx | named | VacationRequestDialogProps | Y | 208 | +| VacationTypeSettingsDialog | VacationTypeSettingsDialog.tsx | named | VacationTypeSettingsDialogProps | Y | 192 | + +### items (65) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| AssemblyPartForm | AssemblyPartForm.tsx | default | AssemblyPartFormProps | | 337 | +| AttributeTabContent | AttributeTabContent.tsx | named | AttributeTabContentProps | Y | 453 | +| BendingDiagramSection | BendingDiagramSection.tsx | default | BendingDiagramSectionProps | | 477 | +| BendingPartForm | BendingPartForm.tsx | default | BendingPartFormProps | | 304 | +| BOMManagementSection | BOMManagementSection.tsx | named | BOMManagementSectionProps | Y | 293 | +| BOMSection | BOMSection.tsx | default | BOMSectionProps | | 366 | +| CheckboxField | CheckboxField.tsx | named | | Y | 47 | +| ColumnDialog | ColumnDialog.tsx | named | ColumnDialogProps | Y | 124 | +| ColumnManageDialog | ColumnManageDialog.tsx | named | ColumnManageDialogProps | Y | 210 | +| ComputedField | ComputedField.tsx | named | | Y | 136 | +| ConditionalDisplayUI | ConditionalDisplayUI.tsx | named | ConditionalDisplayUIProps | | 349 | +| CurrencyField | CurrencyField.tsx | named | | Y | 127 | +| DateField | DateField.tsx | named | | Y | 45 | +| DraggableField | DraggableField.tsx | named | DraggableFieldProps | | 130 | +| DraggableSection | DraggableSection.tsx | named | DraggableSectionProps | | 140 | +| DrawingCanvas | DrawingCanvas.tsx | named | DrawingCanvasProps | Y | 404 | +| DropdownField | DropdownField.tsx | named | | Y | 141 | +| DuplicateCodeDialog | DuplicateCodeDialog.tsx | named | DuplicateCodeDialogProps | Y | 49 | +| DynamicBOMSection | DynamicBOMSection.tsx | default | DynamicBOMSectionProps | Y | 515 | +| DynamicFieldRenderer | DynamicFieldRenderer.tsx | named | | Y | 86 | +| DynamicTableSection | DynamicTableSection.tsx | default | DynamicTableSectionProps | Y | 200 | +| ErrorAlertDialog | ErrorAlertDialog.tsx | named | ErrorAlertDialogProps | Y | 51 | +| ErrorAlertProvider | ErrorAlertContext.tsx | named | ErrorAlertProviderProps | Y | 94 | +| FieldDialog | FieldDialog.tsx | named | FieldDialogProps | Y | 478 | +| FieldDrawer | FieldDrawer.tsx | named | FieldDrawerProps | Y | 682 | +| FileField | FileField.tsx | named | | Y | 200 | +| FileUpload | FileUpload.tsx | default | FileUploadProps | Y | 233 | +| FileUploadFields | FileUploadFields.tsx | named | FileUploadFieldsProps | Y | 240 | +| FormHeader | FormHeader.tsx | named | FormHeaderProps | Y | 31 | +| FormHeader | FormHeader.tsx | default | FormHeaderProps | | 62 | +| ImportFieldDialog | ImportFieldDialog.tsx | named | ImportFieldDialogProps | Y | 279 | +| ImportSectionDialog | ImportSectionDialog.tsx | named | ImportSectionDialogProps | Y | 221 | +| ItemDetailClient | ItemDetailClient.tsx | default | ItemDetailClientProps | Y | 638 | +| ItemDetailEdit | ItemDetailEdit.tsx | named | ItemDetailEditProps | Y | 390 | +| ItemDetailView | ItemDetailView.tsx | named | ItemDetailViewProps | Y | 275 | +| ItemFormContext | ItemFormContext.tsx | both | ItemFormProviderProps | Y | 77 | +| ItemListClient | ItemListClient.tsx | default | | Y | 607 | +| ItemMasterDataManagement | ItemMasterDataManagement.tsx | named | | Y | 1006 | +| ItemMasterDialogs | ItemMasterDialogs.tsx | named | ItemMasterDialogsProps | Y | 968 | +| ItemTypeSelect | ItemTypeSelect.tsx | default | ItemTypeSelectProps | Y | 76 | +| LoadTemplateDialog | LoadTemplateDialog.tsx | named | LoadTemplateDialogProps | Y | 103 | +| MasterFieldDialog | MasterFieldDialog.tsx | named | MasterFieldDialogProps | Y | 306 | +| MaterialForm | MaterialForm.tsx | default | MaterialFormProps | | 354 | +| MultiSelectField | MultiSelectField.tsx | named | | Y | 192 | +| NumberField | NumberField.tsx | named | | Y | 58 | +| OptionDialog | OptionDialog.tsx | named | OptionDialogProps | Y | 262 | +| PageDialog | PageDialog.tsx | named | PageDialogProps | Y | 107 | +| PartForm | PartForm.tsx | default | PartFormProps | | 273 | +| PathEditDialog | PathEditDialog.tsx | named | PathEditDialogProps | Y | 86 | +| ProductForm | ProductForm.tsx | both | ProductFormProps | | 307 | +| PurchasedPartForm | PurchasedPartForm.tsx | default | PurchasedPartFormProps | | 336 | +| RadioField | RadioField.tsx | named | | Y | 92 | +| ReferenceField | ReferenceField.tsx | named | | Y | 168 | +| SectionDialog | SectionDialog.tsx | named | SectionDialogProps | Y | 335 | +| SectionsTab | SectionsTab.tsx | named | SectionsTabProps | Y | 363 | +| SectionTemplateDialog | SectionTemplateDialog.tsx | named | SectionTemplateDialogProps | Y | 180 | +| TableCellRenderer | TableCellRenderer.tsx | named | TableCellRendererProps | Y | 85 | +| TabManagementDialogs | TabManagementDialogs.tsx | named | TabManagementDialogsProps | Y | 409 | +| TemplateFieldDialog | TemplateFieldDialog.tsx | named | TemplateFieldDialogProps | Y | 392 | +| TextareaField | TextareaField.tsx | named | | Y | 51 | +| TextField | TextField.tsx | named | | Y | 48 | +| ToggleField | ToggleField.tsx | named | | Y | 62 | +| UnitValueField | UnitValueField.tsx | named | | Y | 129 | +| ValidationAlert | ValidationAlert.tsx | named | ValidationAlertProps | Y | 42 | +| ValidationAlert | ValidationAlert.tsx | default | ValidationAlertProps | | 50 | + +### material (13) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| ImportInspectionInputModal | ImportInspectionInputModal.tsx | named | ImportInspectionInputModalProps | Y | 798 | +| InspectionCreate | InspectionCreate.tsx | named | Props | Y | 364 | +| InventoryAdjustmentDialog | InventoryAdjustmentDialog.tsx | named | Props | Y | 236 | +| ReceivingDetail | ReceivingDetail.tsx | named | Props | Y | 921 | +| ReceivingList | ReceivingList.tsx | named | | Y | 467 | +| ReceivingProcessDialog | ReceivingProcessDialog.tsx | named | Props | Y | 238 | +| ReceivingReceiptContent | ReceivingReceiptContent.tsx | named | ReceivingReceiptContentProps | Y | 132 | +| ReceivingReceiptDialog | ReceivingReceiptDialog.tsx | named | Props | Y | 46 | +| StockAuditModal | StockAuditModal.tsx | named | StockAuditModalProps | Y | 237 | +| StockStatusDetail | StockStatusDetail.tsx | named | StockStatusDetailProps | Y | 313 | +| StockStatusList | StockStatusList.tsx | named | | Y | 473 | +| SuccessDialog | SuccessDialog.tsx | named | Props | Y | 49 | +| SupplierSearchModal | SupplierSearchModal.tsx | named | SupplierSearchModalProps | Y | 161 | + +### orders (10) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| ContractDocument | ContractDocument.tsx | named | ContractDocumentProps | Y | 246 | +| ItemAddDialog | ItemAddDialog.tsx | named | ItemAddDialogProps | Y | 317 | +| OrderDocumentModal | OrderDocumentModal.tsx | named | OrderDocumentModalProps | Y | 207 | +| OrderRegistration | OrderRegistration.tsx | named | OrderRegistrationProps | Y | 1087 | +| OrderSalesDetailEdit | OrderSalesDetailEdit.tsx | named | OrderSalesDetailEditProps | Y | 735 | +| OrderSalesDetailView | OrderSalesDetailView.tsx | named | OrderSalesDetailViewProps | Y | 824 | +| PurchaseOrderDocument | PurchaseOrderDocument.tsx | named | PurchaseOrderDocumentProps | Y | 223 | +| QuotationSelectDialog | QuotationSelectDialog.tsx | named | QuotationSelectDialogProps | Y | 114 | +| SalesOrderDocument | SalesOrderDocument.tsx | named | SalesOrderDocumentProps | Y | 638 | +| TransactionDocument | TransactionDocument.tsx | named | TransactionDocumentProps | Y | 226 | + +### outbound (11) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| DeliveryConfirmation | DeliveryConfirmation.tsx | named | DeliveryConfirmationProps | Y | 18 | +| ShipmentCreate | ShipmentCreate.tsx | named | | Y | 772 | +| ShipmentDetail | ShipmentDetail.tsx | named | ShipmentDetailProps | Y | 671 | +| ShipmentEdit | ShipmentEdit.tsx | named | ShipmentEditProps | Y | 791 | +| ShipmentList | ShipmentList.tsx | named | | Y | 399 | +| ShipmentOrderDocument | ShipmentOrderDocument.tsx | named | ShipmentOrderDocumentProps | Y | 647 | +| ShippingSlip | ShippingSlip.tsx | named | ShippingSlipProps | Y | 18 | +| TransactionStatement | TransactionStatement.tsx | named | TransactionStatementProps | Y | 154 | +| VehicleDispatchDetail | VehicleDispatchDetail.tsx | named | VehicleDispatchDetailProps | Y | 181 | +| VehicleDispatchEdit | VehicleDispatchEdit.tsx | named | VehicleDispatchEditProps | Y | 399 | +| VehicleDispatchList | VehicleDispatchList.tsx | named | | Y | 331 | + +### pricing (5) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| PricingFinalizeDialog | PricingFinalizeDialog.tsx | both | PricingFinalizeDialogProps | Y | 95 | +| PricingFormClient | PricingFormClient.tsx | both | PricingFormClientProps | Y | 780 | +| PricingHistoryDialog | PricingHistoryDialog.tsx | both | PricingHistoryDialogProps | Y | 170 | +| PricingListClient | PricingListClient.tsx | both | PricingListClientProps | Y | 387 | +| PricingRevisionDialog | PricingRevisionDialog.tsx | both | PricingRevisionDialogProps | Y | 95 | + +### pricing-distribution (3) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| PriceDistributionDetail | PriceDistributionDetail.tsx | both | Props | Y | 539 | +| PriceDistributionDocumentModal | PriceDistributionDocumentModal.tsx | both | Props | Y | 158 | +| PriceDistributionList | PriceDistributionList.tsx | both | | Y | 328 | + +### pricing-table-management (3) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| PricingTableDetailClient | PricingTableDetailClient.tsx | named | PricingTableDetailClientProps | Y | 93 | +| PricingTableForm | PricingTableForm.tsx | named | PricingTableFormProps | Y | 486 | +| PricingTableListClient | PricingTableListClient.tsx | default | | Y | 381 | + +### process-management (12) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| InspectionPreviewModal | InspectionPreviewModal.tsx | named | InspectionPreviewModalProps | Y | 265 | +| InspectionSettingModal | InspectionSettingModal.tsx | named | InspectionSettingModalProps | Y | 294 | +| ProcessDetail | ProcessDetail.tsx | named | ProcessDetailProps | Y | 451 | +| ProcessDetailClientV2 | ProcessDetailClientV2.tsx | named | ProcessDetailClientV2Props | Y | 137 | +| ProcessForm | ProcessForm.tsx | named | ProcessFormProps | Y | 829 | +| ProcessListClient | ProcessListClient.tsx | default | ProcessListClientProps | Y | 546 | +| ProcessWorkLogContent | ProcessWorkLogContent.tsx | named | ProcessWorkLogContentProps | Y | 136 | +| ProcessWorkLogPreviewModal | ProcessWorkLogPreviewModal.tsx | named | ProcessWorkLogPreviewModalProps | Y | 45 | +| RuleModal | RuleModal.tsx | named | RuleModalProps | Y | 352 | +| StepDetail | StepDetail.tsx | named | StepDetailProps | Y | 212 | +| StepDetailClient | StepDetailClient.tsx | named | StepDetailClientProps | Y | 115 | +| StepForm | StepForm.tsx | named | StepFormProps | Y | 397 | + +### production (31) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| AssigneeSelectModal | AssigneeSelectModal.tsx | named | AssigneeSelectModalProps | Y | 317 | +| BendingInspectionContent | BendingInspectionContent.tsx | named | BendingInspectionContentProps | Y | 490 | +| BendingWipInspectionContent | BendingWipInspectionContent.tsx | named | BendingWipInspectionContentProps | Y | 304 | +| BendingWorkLogContent | BendingWorkLogContent.tsx | named | BendingWorkLogContentProps | Y | 194 | +| CompletionConfirmDialog | CompletionConfirmDialog.tsx | named | CompletionConfirmDialogProps | Y | 64 | +| CompletionToast | CompletionToast.tsx | named | CompletionToastProps | Y | 28 | +| InspectionCheckbox | inspection-shared.tsx | named | | Y | 282 | +| InspectionInputModal | InspectionInputModal.tsx | named | InspectionInputModalProps | Y | 978 | +| InspectionReportModal | InspectionReportModal.tsx | named | InspectionReportModalProps | Y | 409 | +| IssueReportModal | IssueReportModal.tsx | named | IssueReportModalProps | Y | 178 | +| MaterialInputModal | MaterialInputModal.tsx | named | MaterialInputModalProps | Y | 333 | +| ProcessDetailSection | ProcessDetailSection.tsx | named | ProcessDetailSectionProps | Y | 392 | +| SalesOrderSelectModal | SalesOrderSelectModal.tsx | named | SalesOrderSelectModalProps | Y | 102 | +| ScreenInspectionContent | ScreenInspectionContent.tsx | named | ScreenInspectionContentProps | Y | 310 | +| ScreenWorkLogContent | ScreenWorkLogContent.tsx | named | ScreenWorkLogContentProps | Y | 201 | +| SlatInspectionContent | SlatInspectionContent.tsx | named | SlatInspectionContentProps | Y | 297 | +| SlatJointBarInspectionContent | SlatJointBarInspectionContent.tsx | named | SlatJointBarInspectionContentProps | Y | 311 | +| SlatWorkLogContent | SlatWorkLogContent.tsx | named | SlatWorkLogContentProps | Y | 198 | +| TemplateInspectionContent | TemplateInspectionContent.tsx | named | TemplateInspectionContentProps | Y | 719 | +| WipProductionModal | WipProductionModal.tsx | named | WipProductionModalProps | Y | 272 | +| WorkCard | WorkCard.tsx | named | WorkCardProps | Y | 188 | +| WorkCompletionResultDialog | WorkCompletionResultDialog.tsx | named | WorkCompletionResultDialogProps | Y | 85 | +| WorkItemCard | WorkItemCard.tsx | named | WorkItemCardProps | Y | 382 | +| WorkLogContent | WorkLogContent.tsx | named | WorkLogContentProps | Y | 195 | +| WorkLogModal | WorkLogModal.tsx | named | WorkLogModalProps | Y | 152 | +| WorkOrderCreate | WorkOrderCreate.tsx | named | | Y | 545 | +| WorkOrderDetail | WorkOrderDetail.tsx | named | WorkOrderDetailProps | Y | 656 | +| WorkOrderEdit | WorkOrderEdit.tsx | named | WorkOrderEditProps | Y | 656 | +| WorkOrderList | WorkOrderList.tsx | named | | Y | 460 | +| WorkOrderListPanel | WorkOrderListPanel.tsx | named | WorkOrderListPanelProps | Y | 132 | +| WorkResultList | WorkResultList.tsx | named | | Y | 374 | + +### quality (11) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| InspectionCreate | InspectionCreate.tsx | named | | Y | 695 | +| InspectionDetail | InspectionDetail.tsx | named | InspectionDetailProps | Y | 1126 | +| InspectionList | InspectionList.tsx | named | | Y | 388 | +| InspectionReportDocument | InspectionReportDocument.tsx | named | InspectionReportDocumentProps | Y | 416 | +| InspectionReportModal | InspectionReportModal.tsx | named | InspectionReportModalProps | Y | 170 | +| InspectionRequestDocument | InspectionRequestDocument.tsx | named | InspectionRequestDocumentProps | Y | 258 | +| InspectionRequestModal | InspectionRequestModal.tsx | named | InspectionRequestModalProps | Y | 40 | +| MemoModal | MemoModal.tsx | named | MemoModalProps | Y | 92 | +| OrderSelectModal | OrderSelectModal.tsx | named | OrderSelectModalProps | Y | 111 | +| PerformanceReportList | PerformanceReportList.tsx | named | | Y | 604 | +| ProductInspectionInputModal | ProductInspectionInputModal.tsx | named | ProductInspectionInputModalProps | Y | 486 | + +### quotes (15) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| DiscountModal | DiscountModal.tsx | named | DiscountModalProps | Y | 232 | +| FormulaViewModal | FormulaViewModal.tsx | named | FormulaViewModalProps | Y | 316 | +| ItemSearchModal | ItemSearchModal.tsx | named | ItemSearchModalProps | Y | 114 | +| LocationDetailPanel | LocationDetailPanel.tsx | named | LocationDetailPanelProps | Y | 827 | +| LocationEditModal | LocationEditModal.tsx | named | LocationEditModalProps | Y | 283 | +| LocationListPanel | LocationListPanel.tsx | named | LocationListPanelProps | Y | 575 | +| PurchaseOrderDocument | PurchaseOrderDocument.tsx | named | PurchaseOrderDocumentProps | | 265 | +| QuoteDocument | QuoteDocument.tsx | named | QuoteDocumentProps | | 409 | +| QuoteFooterBar | QuoteFooterBar.tsx | named | QuoteFooterBarProps | Y | 236 | +| QuoteManagementClient | QuoteManagementClient.tsx | named | QuoteManagementClientProps | Y | 713 | +| QuotePreviewContent | QuotePreviewContent.tsx | named | QuotePreviewContentProps | Y | 434 | +| QuotePreviewModal | QuotePreviewModal.tsx | named | QuotePreviewModalProps | Y | 132 | +| QuoteRegistration | QuoteRegistration.tsx | named | QuoteRegistrationProps | Y | 1023 | +| QuoteSummaryPanel | QuoteSummaryPanel.tsx | named | QuoteSummaryPanelProps | Y | 277 | +| QuoteTransactionModal | QuoteTransactionModal.tsx | named | QuoteTransactionModalProps | Y | 324 | + +### settings (16) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| AccountDetail | AccountDetail.tsx | named | AccountDetailProps | Y | 356 | +| AccountDetail | AccountDetail.tsx | named | AccountDetailProps | Y | 369 | +| AddCompanyDialog | AddCompanyDialog.tsx | named | AddCompanyDialogProps | Y | 149 | +| ItemSettingsDialog | ItemSettingsDialog.tsx | named | ItemSettingsDialogProps | Y | 336 | +| PaymentHistoryClient | PaymentHistoryClient.tsx | named | PaymentHistoryClientProps | Y | 255 | +| PermissionDetail | PermissionDetail.tsx | named | PermissionDetailProps | Y | 456 | +| PermissionDetailClient | PermissionDetailClient.tsx | named | PermissionDetailClientProps | Y | 700 | +| PermissionDialog | PermissionDialog.tsx | named | | Y | 109 | +| PopupDetail | PopupDetail.tsx | both | PopupDetailProps | Y | 125 | +| PopupDetailClientV2 | PopupDetailClientV2.tsx | named | PopupDetailClientV2Props | Y | 199 | +| PopupForm | PopupForm.tsx | both | PopupFormProps | Y | 319 | +| PopupList | PopupList.tsx | both | PopupListProps | Y | 198 | +| RankDialog | RankDialog.tsx | named | | Y | 89 | +| SubscriptionClient | SubscriptionClient.tsx | named | SubscriptionClientProps | Y | 242 | +| SubscriptionManagement | SubscriptionManagement.tsx | named | SubscriptionManagementProps | Y | 250 | +| TitleDialog | TitleDialog.tsx | named | | Y | 90 | + +### templates (11) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| DetailActions | DetailActions.tsx | both | DetailActionsProps | Y | 172 | +| DetailField | DetailField.tsx | both | DetailFieldProps | Y | 91 | +| DetailFieldSkeleton | DetailFieldSkeleton.tsx | both | DetailFieldSkeletonProps | Y | 48 | +| DetailGrid | DetailGrid.tsx | both | DetailGridProps | Y | 63 | +| DetailGridSkeleton | DetailGridSkeleton.tsx | both | DetailGridSkeletonProps | Y | 61 | +| DetailSection | DetailSection.tsx | both | DetailSectionProps | Y | 97 | +| DetailSectionSkeleton | DetailSectionSkeleton.tsx | both | DetailSectionSkeletonProps | Y | 53 | +| DetailSectionSkeleton | skeletons.tsx | both | DetailFieldSkeletonProps | Y | 183 | +| FieldInput | FieldInput.tsx | both | FieldInputProps | Y | 408 | +| FieldRenderer | FieldRenderer.tsx | named | FieldRendererProps | Y | 390 | +| IntegratedListTemplateV2 | IntegratedListTemplateV2.tsx | named | IntegratedListTemplateV2Props | Y | 1087 | + +### vehicle-management (3) + +| Component | File | Export | Props | Client | Lines | +|-----------|------|--------|-------|--------|-------| +| Config | config.tsx | none | | Y | 431 | +| Config | config.tsx | none | | Y | 479 | +| Config | config.tsx | none | | Y | 266 | diff --git a/claudedocs/dashboard/[IMPL-2026-02-11] favorites-feature.md b/claudedocs/dashboard/[IMPL-2026-02-11] favorites-feature.md new file mode 100644 index 00000000..3b8270fa --- /dev/null +++ b/claudedocs/dashboard/[IMPL-2026-02-11] favorites-feature.md @@ -0,0 +1,122 @@ +# 즐겨찾기(Favorites) 기능 구현 + +> 2026-02-11 | 상태: 완료 (localStorage 기반) | 추후: API 전환 예정 + +## 개요 + +사이드바 메뉴에 별표(즐겨찾기) 기능을 추가하여 사용자가 자주 쓰는 메뉴를 헤더에 동적으로 표시. +기존 하드코딩된 종합분석/품질인정심사 버튼을 제거하고 사용자 선택 기반으로 전환. + +## 파일 구조 + +| 파일 | 작업 | 설명 | +|------|------|------| +| `src/stores/favoritesStore.ts` | NEW | Zustand persist 스토어 | +| `src/lib/utils/menuTransform.ts` | MODIFY | reverseIconMap, getIconName, DEFAULT_FAVORITES 추가 | +| `src/components/layout/HeaderFavoritesBar.tsx` | NEW | 헤더 즐겨찾기 바 (반응형) | +| `src/components/layout/Sidebar.tsx` | MODIFY | leaf 메뉴에 별표 토글 추가 | +| `src/layouts/AuthenticatedLayout.tsx` | MODIFY | 하드코딩 버튼 → HeaderFavoritesBar 교체 | + +## 핵심 동작 + +### 즐겨찾기 등록/해제 +- 사이드바 leaf 메뉴(children 없는 항목) hover 시 별표 아이콘 표시 +- 이미 즐겨찾기인 항목은 항상 노란 별표 표시 +- 별표 클릭으로 토글 (메뉴 클릭과 분리 - `e.stopPropagation()`) +- sidebar collapsed 상태에서는 별표 숨김 + +### 헤더 표시 (반응형) +| 조건 | 표시 방식 | +|------|----------| +| 데스크톱 (1024px+), 1~8개 | 아이콘 버튼 + Tooltip | +| 데스크톱 (1024px+), 9~10개 | ★ 드롭다운 | +| 태블릿 (768~1024px) | ★ 드롭다운 | +| 모바일 (<768px) | ★ 드롭다운 | + +### 제한 +- 최대 **10개** +- 초과 시 토스트: `즐겨찾기는 최대 10개까지 등록할 수 있습니다.` + +### 저장 +- **현재**: localStorage (`sam-favorites-{userId}` 키) +- Zustand persist 사용, 사용자별 분리 저장 +- 기본값 없음 (사용자가 직접 추가한 것만 표시) + +## 주요 구현 상세 + +### favoritesStore.ts +```typescript +interface FavoriteItem { + id: string; // 메뉴 id + label: string; // 메뉴 라벨 + iconName: string; // 아이콘 문자열 (iconMap 키) + path: string; // 라우트 경로 + addedAt: number; // 추가 시각 (정렬용) +} + +// toggleFavorite 반환값으로 토스트 제어 +type ToggleResult = 'added' | 'removed' | 'max_reached'; +``` + +### menuTransform.ts 확장 +- `reverseIconMap`: iconMap을 뒤집어 `LucideIcon → string` 조회 +- `getIconName(icon)`: 아이콘 컴포넌트 → 문자열 이름 변환 +- `DEFAULT_FAVORITES`: 종합분석 + 품질인정심사 (현재 미사용, 참고용 보관) + +### button 중첩 방지 +별표 아이콘은 메뉴 ` ← 메뉴 클릭 + ← 별표 클릭 (형제 관계) + +``` + +## API 전환 계획 + +현재 localStorage 기반이라 기기/브라우저별로 동기화되지 않음. +추후 API 준비되면 스토어 내부만 교체하면 됨. + +### 변경 범위 +- `favoritesStore.ts`만 수정 (컴포넌트 수정 불필요) + +### 예상 API 엔드포인트 +``` +GET /api/v1/favorites → 목록 조회 +POST /api/v1/favorites → 추가 +DELETE /api/v1/favorites/{menuId} → 삭제 +``` + +### 전환 방식 +```typescript +// Before (localStorage) +toggleFavorite: (item) => { + // Zustand set()으로 localStorage 저장 +} + +// After (API) +toggleFavorite: async (item) => { + const exists = get().favorites.some(f => f.id === item.id); + if (exists) { + await fetch(`/api/v1/favorites/${item.id}`, { method: 'DELETE' }); + } else { + await fetch('/api/v1/favorites', { method: 'POST', body: JSON.stringify(item) }); + } + // 성공 후 로컬 상태 업데이트 +} +``` + +### 초기 로딩 +```typescript +// AuthenticatedLayout useEffect에서 +const res = await fetch('/api/v1/favorites'); +const data = await res.json(); +useFavoritesStore.getState().setFavorites(data); +``` + +### 주의사항 +- API 실패 시 localStorage fallback 고려 +- 낙관적 업데이트(optimistic update)로 UX 저하 방지 +- 서버 응답 전에 UI 먼저 반영 → 실패 시 롤백 diff --git a/claudedocs/dev/[FIX-2026-02-09] PDF-이미지-누락-해결.md b/claudedocs/dev/[FIX-2026-02-09] PDF-이미지-누락-해결.md index 8d09c1c5..fc562de2 100644 --- a/claudedocs/dev/[FIX-2026-02-09] PDF-이미지-누락-해결.md +++ b/claudedocs/dev/[FIX-2026-02-09] PDF-이미지-누락-해결.md @@ -125,3 +125,56 @@ const html = clonedElement.outerHTML; 3. canvas drawImage → toDataURL (로컬 이미지 fallback) 4. 실패 시 원본 src 유지 (graceful degradation) ``` + +--- + +## 미해결: 도해 이미지 데이터 파이프라인 (2026-02-09 확인) + +### 현재 상황 + +PDF 변환(파이프 끝)은 수정 완료했으나, **데이터 주입(파이프 시작)이 연결되지 않은 상태**. + +``` +[백엔드 설정 사이트] [sam-api] [프론트엔드] + 도해 이미지 업로드 → inspection_setting? → inspectionSetting + 검사기준 설정 (❌ 미구현) schematicImage + ↓ + DocumentViewer + ↓ + PDF (base64 변환 ✅) +``` + +### 백엔드 API 확인 결과 + +| 항목 | 상태 | +|------|------| +| `ProcessStep` 테이블에 `inspection_setting` 컬럼 | ❌ 없음 | +| `ApiProcessStep` → `inspectionSetting` 매핑 | ❌ 없음 | +| 검사 설정 위치 | Process 레벨 `DocumentTemplate`으로 최근(2/10~11) 이동 | +| 도해 이미지 URL 필드 | ❌ 백엔드에 미정의 | + +### 프론트 vs 백엔드 구조 불일치 + +| 항목 | 프론트엔드 기대 | 백엔드 실제 | +|------|----------------|-------------| +| 위치 | `step.inspectionSetting` | `process.documentTemplate` | +| 구조 | `{ standardName, schematicImage, appearance, dimension }` | DocumentTemplate (sections, fields) | +| 이미지 | `schematicImage` 직접 필드 | 없음 | + +### 프론트 누락 코드 (합치고 나서 수정 필요) + +**`src/components/process-management/actions.ts`**: +- `ApiProcessStep` 인터페이스에 `inspection_setting` 필드 미정의 (line 582) +- `transformStepApiToFrontend()`에서 `inspectionSetting` 매핑 안 함 (line 599) + +### 해결 방향 + +백엔드 머지 후 아래 중 하나로 진행: + +| 옵션 | 내용 | 수정 위치 | +|------|------|----------| +| A | ProcessStep에 `inspection_setting` JSON 컬럼 추가 | 백엔드 | +| B | API에서 DocumentTemplate → InspectionSetting 변환하여 내려주기 | 백엔드 | +| C | 프론트에서 `process.documentTemplate` 직접 사용하도록 변경 | 프론트 | + +**백엔드 담당자와 구조 협의 필요.** diff --git a/claudedocs/item-master/[API-REQUEST-2026-02-12] dynamic-field-type-backend-spec.md b/claudedocs/item-master/[API-REQUEST-2026-02-12] dynamic-field-type-backend-spec.md new file mode 100644 index 00000000..b1690749 --- /dev/null +++ b/claudedocs/item-master/[API-REQUEST-2026-02-12] dynamic-field-type-backend-spec.md @@ -0,0 +1,390 @@ +# 동적 필드 타입 확장 — 백엔드 API 스펙 + +> 작성일: 2026-02-12 +> 프론트 구현 완료: `[IMPL-2026-02-11] dynamic-field-components.md` +> 설계 근거: `[DESIGN-2026-02-11] dynamic-field-type-extension.md` + +--- + +## 요약 + +프론트엔드에서 **14종 필드 타입** + **table 섹션 타입** + **조건부 표시 연산자 9종**을 렌더링할 준비가 완료되었습니다. + +백엔드에서 해당 `field_type`과 `properties` JSON을 DB에 저장하고 API로 반환하면, 프론트에서 추가 작업 없이 자동 렌더링됩니다. + +--- + +## 1. field_type 확장 + +### 현재 (6종) +``` +textbox | number | dropdown | checkbox | date | textarea +``` + +### 추가 필요 (8종) +``` +reference | multi-select | file | currency | unit-value | radio | toggle | computed +``` + +### 전체 (14종) +```sql +-- item_fields.field_type 컬럼의 허용값 +ENUM('textbox', 'number', 'dropdown', 'checkbox', 'date', 'textarea', + 'reference', 'multi-select', 'file', + 'currency', 'unit-value', 'radio', + 'toggle', 'computed') +``` + +### 영향 범위 +- `item_fields` 테이블의 `field_type` 컬럼 +- `item_master_fields` 테이블의 `field_type` 컬럼 (deprecated지만 아직 사용 중이면) +- 필드 생성/수정 API의 validation rule + +--- + +## 2. 각 field_type별 properties JSON 스펙 + +모든 설정은 기존 `item_fields.properties` JSON 컬럼에 저장합니다. + +### 2-1. reference (다른 테이블 검색/선택) + +```jsonc +{ + "source": "vendors", // 필수. 프리셋: vendors|items|customers|employees|warehouses|processes|equipment|sites|vehicles + "displayField": "name", // 선택. 기본 "name" + "valueField": "id", // 선택. 기본 "id" + "searchFields": ["name"], // 선택. 검색 대상 필드 + "searchApiUrl": "/api/...", // source="custom"일 때만 필수 + "columns": [ // 선택. 검색 모달 표시 컬럼 + { "key": "code", "label": "코드", "width": "100px" }, + { "key": "name", "label": "이름" } + ], + "displayFormat": "{code} - {name}", // 선택. 선택 후 표시 포맷 + "returnFields": ["id", "code", "name"] // 선택. 프론트에 반환할 필드 +} +``` + +**저장값**: `string` (선택한 항목의 valueField 값, 기본 id) + +**프론트 동작**: source 프리셋이 있으면 `/api/proxy/{source}?search={query}` 호출. 없으면 searchApiUrl 사용. + +--- + +### 2-2. multi-select (복수 선택 태그) + +```jsonc +{ + "maxSelections": 5, // 선택. 최대 선택 수 (기본: 무제한) + "allowCustom": false, // 선택. 직접 입력 허용 (기본: false) + "layout": "chips" // 선택. "chips" | "list" (기본: "chips") +} +``` + +**options**: 기존 dropdown과 동일 `[{label, value}]` — `item_fields.options` 컬럼 사용 + +**저장값**: `string[]` JSON (예: `["CUT","BEND","WELD"]`) + +--- + +### 2-3. file (파일/이미지 첨부) + +```jsonc +{ + "accept": ".pdf,.doc,.jpg,.png", // 선택. 허용 확장자 (기본: "*") + "maxSize": 10485760, // 선택. 최대 파일 크기 bytes (기본: 10MB) + "maxFiles": 3, // 선택. 최대 파일 수 (기본: 1) + "preview": true, // 선택. 이미지 미리보기 (기본: true) + "category": "drawing" // 선택. 파일 카테고리 태그 +} +``` + +**저장값**: 파일 업로드 후 반환된 URL 또는 file ID + +**필요 API**: `POST /v1/files/upload` (multipart) — 이미 있으면 그대로 사용 + +--- + +### 2-4. currency (통화 금액) + +```jsonc +{ + "currency": "KRW", // 선택. 기본 "KRW". 지원: KRW|USD|EUR|JPY|CNY|GBP + "precision": 0, // 선택. 소수점 자리 (기본: 0) + "showSymbol": true, // 선택. 통화 기호 표시 (기본: true) + "allowNegative": false // 선택. 음수 허용 (기본: false) +} +``` + +**저장값**: `number` (포맷 없는 원시 숫자) + +--- + +### 2-5. unit-value (값 + 단위 조합) + +```jsonc +{ + "units": [ // 필수. 단위 목록 + { "label": "mm", "value": "mm" }, + { "label": "cm", "value": "cm" }, + { "label": "m", "value": "m" } + ], + "defaultUnit": "mm", // 선택. 기본 단위 + "precision": 2 // 선택. 소수점 자리 +} +``` + +**저장값**: `{"value": 100, "unit": "mm"}` JSON + +--- + +### 2-6. radio (라디오 버튼 그룹) + +```jsonc +{ + "layout": "horizontal" // 선택. "horizontal" | "vertical" (기본: "vertical") +} +``` + +**options**: 기존 dropdown과 동일 `[{label, value}]` — `item_fields.options` 컬럼 사용 + +**저장값**: `string` (선택한 value) + +--- + +### 2-7. toggle (On/Off 스위치) + +```jsonc +{ + "onLabel": "사용", // 선택. 기본 "ON" + "offLabel": "미사용", // 선택. 기본 "OFF" + "onValue": "Y", // 선택. 기본 "true" + "offValue": "N" // 선택. 기본 "false" +} +``` + +**저장값**: `string` (onValue 또는 offValue) + +--- + +### 2-8. computed (계산 필드, 읽기전용) + +```jsonc +{ + "formula": "unit_price * quantity", // 필수. 수식 (field_key 참조) + "dependsOn": ["unit_price", "quantity"], // 필수. 의존 필드 field_key 목록 + "format": "currency", // 선택. "currency" | "number" | "percent" + "precision": 0 // 선택. 소수점 자리 +} +``` + +**저장값**: `number` (프론트에서 자동 계산, 백엔드 저장 시에도 계산 검증 권장) + +**지원 연산**: `+`, `-`, `*`, `/`, `(`, `)` — field_key를 변수로 사용 + +--- + +## 3. section type 확장 + +### 현재 +```sql +item_sections.type: ENUM('fields', 'bom') +``` + +### 추가 필요 +```sql +item_sections.type: ENUM('fields', 'bom', 'table') +``` + +### table 섹션의 properties JSON + +`item_sections.properties` 컬럼 (신규 또는 기존 description 등과 함께): + +```jsonc +{ + "tableConfig": { + "columns": [ + { + "key": "process_name", // 컬럼 키 + "label": "공정명", // 컬럼 헤더 + "fieldType": "reference", // 셀 입력 타입 (ItemFieldType) + "width": "150px", // 선택 + "isRequired": true, // 선택 + "isReadonly": false, // 선택 + "options": [...], // dropdown/radio/multi-select용 + "properties": { // 해당 필드 타입의 properties + "source": "processes" + } + }, + { + "key": "cycle_time", + "label": "사이클타임(초)", + "fieldType": "number", + "width": "100px" + } + ], + "minRows": 1, // 선택. 최소 행 수 + "maxRows": 30, // 선택. 최대 행 수 + "summaryRow": [ // 선택. 합계행 + { "columnKey": "cycle_time", "type": "sum" }, + { "columnKey": "setup_time", "type": "avg" } + ] + } +} +``` + +**summaryRow type**: `"sum"` | `"avg"` | `"count"` | `"label"` + +### table 섹션 데이터 저장/조회 API + +``` +GET /v1/items/{itemId}/section-data/{sectionId} +PUT /v1/items/{itemId}/section-data/{sectionId} +``` + +**요청/응답 body**: +```jsonc +{ + "rows": [ + { "process_name": "절단", "cycle_time": 30, "setup_time": 10 }, + { "process_name": "절곡", "cycle_time": 45, "setup_time": 15 } + ] +} +``` + +> 이미 유사한 API가 있으면 그대로 사용. 없으면 신규 생성 필요. + +--- + +## 4. display_condition 연산자 확장 + +### 현재 +```jsonc +// item_fields.display_condition +{ + "fieldConditions": [ + { + "fieldKey": "item_type", + "expectedValue": "FG", // 정확히 일치 (equals만) + "targetFieldIds": ["10", "11"] + } + ] +} +``` + +### 확장 (operator 필드 추가) + +```jsonc +{ + "fieldConditions": [ + { + "fieldKey": "item_type", + "operator": "in", // 신규: 연산자 + "expectedValue": "FG,PT", // in/not_in은 콤마 구분 + "targetFieldIds": ["10", "11"] + }, + { + "fieldKey": "quantity", + "operator": "greater_than", + "expectedValue": "100", + "targetSectionIds": ["5"] + } + ] +} +``` + +### 지원 연산자 목록 + +| operator | 설명 | expectedValue 형식 | 예시 | +|----------|------|-------------------|------| +| `equals` | 정확히 일치 (기본값) | 단일값 | `"FG"` | +| `not_equals` | 불일치 | 단일값 | `"RM"` | +| `in` | 목록 중 하나 일치 | 콤마 구분 | `"FG,PT,SM"` | +| `not_in` | 목록 중 어느 것에도 불일치 | 콤마 구분 | `"RM,CS"` | +| `greater_than` | 초과 (숫자) | 숫자 문자열 | `"100"` | +| `less_than` | 미만 (숫자) | 숫자 문자열 | `"0"` | +| `gte` | 이상 (숫자) | 숫자 문자열 | `"50"` | +| `lte` | 이하 (숫자) | 숫자 문자열 | `"1000"` | +| `contains` | 부분 문자열 포함 | 문자열 | `"강판"` | + +**하위호환**: `operator`가 없으면 프론트에서 `equals`로 처리. 기존 데이터 마이그레이션 불필요. + +--- + +## 5. 기존 API 변경 없음 (확인) + +다음 API들은 이미 `field_type`과 `properties`를 통과시키므로 **변경 불필요**: + +| API | 용도 | 비고 | +|-----|------|------| +| `GET /v1/item-master/pages/{id}/structure` | 페이지 구조 조회 | field_type, properties 이미 반환 | +| `POST /v1/item-master/fields` | 필드 생성 | field_type, properties 이미 수신 | +| `PUT /v1/item-master/fields/{id}` | 필드 수정 | 동일 | +| `GET /v1/item-master/init` | 초기화 조회 | field_type 이미 반환 | + +**변경이 필요한 부분**: +1. `field_type` validation rule에 8종 추가 +2. `item_sections.type`에 `'table'` 추가 +3. (선택) table 섹션 데이터 저장/조회 API 신규 + +--- + +## 6. 작업 우선순위 제안 + +### 즉시 (프론트와 바로 연동 가능) + +| # | 작업 | 난이도 | 설명 | +|---|------|--------|------| +| 1 | field_type 허용값 8종 추가 | 낮음 | DB ENUM 또는 validation 수정만 | +| 2 | display_condition operator 저장 허용 | 낮음 | JSON이므로 스키마 변경 없음 | + +### 다음 단계 + +| # | 작업 | 난이도 | 설명 | +|---|------|--------|------| +| 3 | section type 'table' 추가 | 중간 | DB + API validation | +| 4 | table 섹션 데이터 API | 중간 | 신규 엔드포인트 (CRUD) | +| 5 | reference 소스별 검색 API | 높음 | 각 소스(vendors, items 등)의 검색 API 표준화 | + +### 나중 (필요 시) + +| # | 작업 | 난이도 | 설명 | +|---|------|--------|------| +| 6 | 파일 업로드 API | 높음 | `POST /v1/files/upload` (이미 있으면 불필요) | +| 7 | computed 필드 서버사이드 검증 | 중간 | 저장 시 수식 재계산하여 값 검증 | + +--- + +## 7. 테스트 방법 + +백엔드 작업 완료 후 확인 순서: + +1. **페이지 빌더** (`/dev/page-builder`)에서 신규 field_type으로 필드 추가 +2. **품목 등록** 페이지에서 해당 필드가 올바르게 렌더링되는지 확인 +3. **저장/조회** 사이클 테스트 (값이 properties 형식에 맞게 저장/복원되는지) + +--- + +## 부록: 프론트 파일 매핑 + +| field_type | 프론트 컴포넌트 | 파일 | +|------------|---------------|------| +| textbox | TextField | `DynamicItemForm/fields/TextField.tsx` | +| number | NumberField | `DynamicItemForm/fields/NumberField.tsx` | +| dropdown | DropdownField | `DynamicItemForm/fields/DropdownField.tsx` | +| checkbox | CheckboxField | `DynamicItemForm/fields/CheckboxField.tsx` | +| date | DateField | `DynamicItemForm/fields/DateField.tsx` | +| textarea | TextareaField | `DynamicItemForm/fields/TextareaField.tsx` | +| **reference** | **ReferenceField** | `DynamicItemForm/fields/ReferenceField.tsx` | +| **multi-select** | **MultiSelectField** | `DynamicItemForm/fields/MultiSelectField.tsx` | +| **file** | **FileField** | `DynamicItemForm/fields/FileField.tsx` | +| **currency** | **CurrencyField** | `DynamicItemForm/fields/CurrencyField.tsx` | +| **unit-value** | **UnitValueField** | `DynamicItemForm/fields/UnitValueField.tsx` | +| **radio** | **RadioField** | `DynamicItemForm/fields/RadioField.tsx` | +| **toggle** | **ToggleField** | `DynamicItemForm/fields/ToggleField.tsx` | +| **computed** | **ComputedField** | `DynamicItemForm/fields/ComputedField.tsx` | + +| section type | 프론트 컴포넌트 | 파일 | +|-------------|---------------|------| +| fields | (기존 필드 렌더링) | `DynamicItemForm/index.tsx` | +| bom | DynamicBOMSection | `DynamicItemForm/sections/DynamicBOMSection.tsx` | +| **table** | **DynamicTableSection** | `DynamicItemForm/sections/DynamicTableSection.tsx` | diff --git a/package.json b/package.json index 86f5ecf5..ea32fa11 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "lint": "eslint", "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui", - "test:e2e:headed": "playwright test --h 1eaded" + "test:e2e:headed": "playwright test --h 1eaded", + "gen:components": "node scripts/gen-component-registry.mjs" }, "dependencies": { "@capacitor/app": "^8.0.0", diff --git a/scripts/gen-component-registry.mjs b/scripts/gen-component-registry.mjs new file mode 100644 index 00000000..86285a5c --- /dev/null +++ b/scripts/gen-component-registry.mjs @@ -0,0 +1,258 @@ +#!/usr/bin/env node + +/** + * Component Registry Generator + * + * src/components/ 하위 .tsx 파일을 스캔하여 + * - src/generated/component-registry.json (페이지용 데이터) + * - claudedocs/components/_registry.md (에디터/git 이력용) + * 두 가지 출력을 생성합니다. + * + * 사용: node scripts/gen-component-registry.mjs + */ + +import { readdir, readFile, writeFile, mkdir, stat } from 'node:fs/promises'; +import { join, relative, basename, dirname, extname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(__dirname, '..'); +const SRC_COMPONENTS = join(ROOT, 'src', 'components'); +const OUT_JSON = join(ROOT, 'src', 'generated', 'component-registry.json'); +const OUT_MD = join(ROOT, 'claudedocs', 'components', '_registry.md'); + +// Tier mapping by directory name +const TIER_MAP = { + ui: 'ui', + atoms: 'atoms', + molecules: 'molecules', + organisms: 'organisms', + common: 'common', + layout: 'layout', + dev: 'dev', +}; + +// Skip patterns +const SKIP_FILES = new Set(['index.ts', 'index.tsx', 'actions.ts', 'actions.tsx', 'types.ts', 'types.tsx', 'utils.ts', 'utils.tsx', 'constants.ts', 'constants.tsx', 'schema.ts', 'schema.tsx']); +const SKIP_PATTERNS = [ + /\.test\./, + /\.spec\./, + /\.stories\./, + /^use[A-Z].*\.ts$/, // hooks files (useXxx.ts only, not .tsx) +]; + +function shouldSkip(fileName) { + if (SKIP_FILES.has(fileName)) return true; + return SKIP_PATTERNS.some(p => p.test(fileName)); +} + +function getTier(relPath) { + const firstDir = relPath.split('/')[0]; + return TIER_MAP[firstDir] || 'domain'; +} + +function getCategory(relPath) { + const parts = relPath.split('/'); + // category = first directory under components/ + return parts[0]; +} + +function getSubcategory(relPath) { + const parts = relPath.split('/'); + // subcategory = second directory if exists + return parts.length > 2 ? parts[1] : null; +} + +function extractComponentInfo(content, fileName) { + const isClient = /^['"]use client['"];?/m.test(content); + + // Extract component name from exports + let name = null; + let exportType = 'none'; + + // Check default export: export default function Foo / export default Foo + const defaultFuncMatch = content.match(/export\s+default\s+function\s+([A-Z]\w*)/); + const defaultConstMatch = content.match(/export\s+default\s+([A-Z]\w*)/); + // Check named exports: export function Foo / export const Foo + const namedFuncMatches = [...content.matchAll(/export\s+(?:async\s+)?function\s+([A-Z]\w*)/g)]; + const namedConstMatches = [...content.matchAll(/export\s+(?:const|let)\s+([A-Z]\w*)/g)]; + + const hasDefault = !!(defaultFuncMatch || defaultConstMatch); + const namedExports = [ + ...namedFuncMatches.map(m => m[1]), + ...namedConstMatches.map(m => m[1]), + ].filter(n => n !== undefined); + + // Remove default export name from named list if it also appears + if (defaultFuncMatch) { + name = defaultFuncMatch[1]; + } else if (defaultConstMatch) { + name = defaultConstMatch[1]; + } + + const hasNamed = namedExports.length > 0; + + if (hasDefault && hasNamed) { + exportType = 'both'; + } else if (hasDefault) { + exportType = 'default'; + } else if (hasNamed) { + exportType = 'named'; + name = namedExports[0]; // Use first named export as component name + } + + // Fallback: derive name from file name + if (!name) { + const base = basename(fileName, extname(fileName)); + name = base + .split(/[-_]/) + .map(s => s.charAt(0).toUpperCase() + s.slice(1)) + .join(''); + } + + // Props detection + const hasProps = /(?:interface|type)\s+\w*Props/.test(content); + const propsNameMatch = content.match(/(?:interface|type)\s+(\w*Props)/); + const propsName = propsNameMatch ? propsNameMatch[1] : null; + + // Line count + const lineCount = content.split('\n').length; + + return { name, exportType, hasProps, propsName, isClientComponent: isClient, lineCount }; +} + +async function scanDirectory(dir) { + const entries = await readdir(dir, { withFileTypes: true }); + const results = []; + + for (const entry of entries) { + const fullPath = join(dir, entry.name); + + if (entry.isDirectory()) { + results.push(...await scanDirectory(fullPath)); + } else if (entry.isFile() && entry.name.endsWith('.tsx')) { + if (shouldSkip(entry.name)) continue; + + const relPath = relative(SRC_COMPONENTS, fullPath); + const content = await readFile(fullPath, 'utf-8'); + const info = extractComponentInfo(content, entry.name); + + results.push({ + name: info.name, + fileName: entry.name, + filePath: `src/components/${relPath}`, + tier: getTier(relPath), + category: getCategory(relPath), + subcategory: getSubcategory(relPath), + exportType: info.exportType, + hasProps: info.hasProps, + propsName: info.propsName, + isClientComponent: info.isClientComponent, + lineCount: info.lineCount, + }); + } + } + + return results; +} + +function buildCategories(components) { + const map = new Map(); + for (const comp of components) { + const key = `${comp.tier}::${comp.category}`; + if (!map.has(key)) { + map.set(key, { tier: comp.tier, category: comp.category, count: 0 }); + } + map.get(key).count++; + } + return [...map.values()].sort((a, b) => a.tier.localeCompare(b.tier) || a.category.localeCompare(b.category)); +} + +function generateMarkdown(registry) { + const now = registry.generatedAt; + const lines = [ + `# Component Registry`, + ``, + `> Auto-generated: ${now} `, + `> Total: **${registry.totalCount}** components`, + ``, + ]; + + // Group by tier, then category + const byTier = new Map(); + for (const comp of registry.components) { + if (!byTier.has(comp.tier)) byTier.set(comp.tier, new Map()); + const tierMap = byTier.get(comp.tier); + if (!tierMap.has(comp.category)) tierMap.set(comp.category, []); + tierMap.get(comp.category).push(comp); + } + + const tierOrder = ['ui', 'atoms', 'molecules', 'organisms', 'common', 'layout', 'dev', 'domain']; + for (const tier of tierOrder) { + const categories = byTier.get(tier); + if (!categories) continue; + + const tierCount = [...categories.values()].reduce((s, arr) => s + arr.length, 0); + lines.push(`## ${tier.toUpperCase()} (${tierCount})`); + lines.push(``); + + for (const [category, comps] of [...categories.entries()].sort()) { + lines.push(`### ${category} (${comps.length})`); + lines.push(``); + lines.push(`| Component | File | Export | Props | Client | Lines |`); + lines.push(`|-----------|------|--------|-------|--------|-------|`); + + for (const c of comps.sort((a, b) => a.name.localeCompare(b.name))) { + const client = c.isClientComponent ? 'Y' : ''; + const props = c.hasProps ? (c.propsName || 'Y') : ''; + lines.push(`| ${c.name} | ${c.fileName} | ${c.exportType} | ${props} | ${client} | ${c.lineCount} |`); + } + lines.push(``); + } + } + + return lines.join('\n'); +} + +async function main() { + console.log('Scanning src/components/...'); + + const components = await scanDirectory(SRC_COMPONENTS); + components.sort((a, b) => a.tier.localeCompare(b.tier) || a.category.localeCompare(b.category) || a.name.localeCompare(b.name)); + + const registry = { + generatedAt: new Date().toISOString(), + totalCount: components.length, + categories: buildCategories(components), + components, + }; + + // Ensure output directories + await mkdir(dirname(OUT_JSON), { recursive: true }); + await mkdir(dirname(OUT_MD), { recursive: true }); + + // Write JSON + await writeFile(OUT_JSON, JSON.stringify(registry, null, 2), 'utf-8'); + console.log(` JSON: ${relative(ROOT, OUT_JSON)} (${registry.totalCount} components)`); + + // Write Markdown + const md = generateMarkdown(registry); + await writeFile(OUT_MD, md, 'utf-8'); + console.log(` MD: ${relative(ROOT, OUT_MD)}`); + + // Summary + console.log(`\nSummary by tier:`); + const tierCounts = {}; + for (const c of components) { + tierCounts[c.tier] = (tierCounts[c.tier] || 0) + 1; + } + for (const [tier, count] of Object.entries(tierCounts).sort()) { + console.log(` ${tier}: ${count}`); + } + console.log(`\nDone!`); +} + +main().catch(err => { + console.error('Error:', err); + process.exit(1); +}); diff --git a/src/app/[locale]/(protected)/dev/component-registry/ComponentRegistryClient.tsx b/src/app/[locale]/(protected)/dev/component-registry/ComponentRegistryClient.tsx new file mode 100644 index 00000000..009d3581 --- /dev/null +++ b/src/app/[locale]/(protected)/dev/component-registry/ComponentRegistryClient.tsx @@ -0,0 +1,522 @@ +'use client'; + +import { useState, useMemo, useCallback } from 'react'; +import { + Search, + Copy, + Check, + ChevronDown, + ChevronRight, + Layers, + FileCode, + Code, + Eye, + Loader2, + X, +} from 'lucide-react'; +import { getComponentSource } from './actions'; +import { UI_PREVIEWS } from './previews'; + +interface ComponentEntry { + name: string; + fileName: string; + filePath: string; + tier: string; + category: string; + subcategory: string | null; + exportType: 'default' | 'named' | 'both' | 'none'; + hasProps: boolean; + propsName: string | null; + isClientComponent: boolean; + lineCount: number; +} + +interface CategorySummary { + tier: string; + category: string; + count: number; +} + +export interface RegistryData { + generatedAt: string; + totalCount: number; + categories: CategorySummary[]; + components: ComponentEntry[]; +} + +interface ComponentRegistryClientProps { + registry: RegistryData; +} + +const TIER_CONFIG: Record = { + ui: { label: 'UI', color: 'text-blue-700', bg: 'bg-blue-50', border: 'border-blue-200' }, + atoms: { label: 'Atoms', color: 'text-green-700', bg: 'bg-green-50', border: 'border-green-200' }, + molecules: { label: 'Molecules', color: 'text-purple-700', bg: 'bg-purple-50', border: 'border-purple-200' }, + organisms: { label: 'Organisms', color: 'text-orange-700', bg: 'bg-orange-50', border: 'border-orange-200' }, + common: { label: 'Common', color: 'text-teal-700', bg: 'bg-teal-50', border: 'border-teal-200' }, + layout: { label: 'Layout', color: 'text-indigo-700', bg: 'bg-indigo-50', border: 'border-indigo-200' }, + dev: { label: 'Dev', color: 'text-yellow-700', bg: 'bg-yellow-50', border: 'border-yellow-200' }, + domain: { label: 'Domain', color: 'text-gray-700', bg: 'bg-gray-50', border: 'border-gray-200' }, +}; + +const ALL_TIERS = ['전체', 'ui', 'atoms', 'molecules', 'organisms', 'common', 'layout', 'dev', 'domain']; + +function CopyButton({ text, label }: { text: string; label: string }) { + const [copied, setCopied] = useState(false); + + const handleCopy = async (e: React.MouseEvent) => { + e.stopPropagation(); + await navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + + ); +} + +function TierBadge({ tier }: { tier: string }) { + const config = TIER_CONFIG[tier] || TIER_CONFIG.domain; + return ( + + {config.label} + + ); +} + +function SourceCodeViewer({ source }: { source: string }) { + const lines = source.split('\n'); + + return ( +
+
+ +
+
+        
+          {lines.map((line, i) => (
+            
+ + {i + 1} + + {line} +
+ ))} +
+
+
+ ); +} + +function ComponentCard({ + comp, + isExpanded, + onToggle, +}: { + comp: ComponentEntry; + isExpanded: boolean; + onToggle: () => void; +}) { + const [activeTab, setActiveTab] = useState<'preview' | 'code'>('preview'); + const [source, setSource] = useState(null); + const [isLoadingSource, setIsLoadingSource] = useState(false); + + const importPath = '@/' + comp.filePath.replace(/^src\//, '').replace(/\.tsx$/, ''); + const hasPreview = !!UI_PREVIEWS[comp.fileName]; + + const handleToggle = useCallback(async () => { + if (!isExpanded) { + // Opening: load source code + if (!source && !isLoadingSource) { + setIsLoadingSource(true); + const result = await getComponentSource(comp.filePath); + if (result.source) setSource(result.source); + setIsLoadingSource(false); + } + if (hasPreview) { + setActiveTab('preview'); + } else { + setActiveTab('code'); + } + } + onToggle(); + }, [isExpanded, source, isLoadingSource, comp.filePath, hasPreview, onToggle]); + + const handleTabChange = useCallback(async (tab: 'preview' | 'code') => { + setActiveTab(tab); + if (tab === 'code' && !source && !isLoadingSource) { + setIsLoadingSource(true); + const result = await getComponentSource(comp.filePath); + if (result.source) setSource(result.source); + setIsLoadingSource(false); + } + }, [source, isLoadingSource, comp.filePath]); + + return ( +
+ {/* Card Header */} + + + {/* Expanded Content */} + {isExpanded && ( +
+ {/* Tab Buttons */} +
+ {hasPreview && ( + + )} + +
+ + {/* Tab Content */} +
+ {activeTab === 'preview' && hasPreview && ( +
+ {UI_PREVIEWS[comp.fileName]!.map((preview) => ( +
+

{preview.label}

+
+ {preview.render()} +
+
+ ))} +
+ )} + + {activeTab === 'code' && ( + <> + {isLoadingSource && ( +
+ + 소스코드 로딩 중... +
+ )} + {source && } + {!isLoadingSource && !source && ( +

소스코드를 불러올 수 없습니다.

+ )} + + )} +
+
+ )} +
+ ); +} + +function CategorySection({ + category, + components, + tier, + expandedCard, + onCardToggle, +}: { + category: string; + components: ComponentEntry[]; + tier: string; + expandedCard: string | null; + onCardToggle: (filePath: string) => void; +}) { + const [expanded, setExpanded] = useState(true); + + // Group by subcategory + const groups = useMemo(() => { + const map = new Map(); + for (const comp of components) { + const key = comp.subcategory || '__root__'; + if (!map.has(key)) map.set(key, []); + map.get(key)!.push(comp); + } + return map; + }, [components]); + + return ( +
+ + + {expanded && ( +
+ {[...groups.entries()] + .sort(([a], [b]) => a.localeCompare(b)) + .map(([sub, comps]) => ( +
+ {sub !== '__root__' && ( +

+ {sub} +

+ )} +
+ {comps.map((comp) => ( + onCardToggle(comp.filePath)} + /> + ))} +
+
+ ))} +
+ )} +
+ ); +} + +export default function ComponentRegistryClient({ registry }: ComponentRegistryClientProps) { + const [searchTerm, setSearchTerm] = useState(''); + const [activeTier, setActiveTier] = useState('전체'); + const [expandedCard, setExpandedCard] = useState(null); + + const handleCardToggle = useCallback((filePath: string) => { + setExpandedCard((prev) => (prev === filePath ? null : filePath)); + }, []); + + const filtered = useMemo(() => { + let comps = registry.components; + + if (activeTier !== '전체') { + comps = comps.filter((c) => c.tier === activeTier); + } + + if (searchTerm) { + const q = searchTerm.toLowerCase(); + comps = comps.filter( + (c) => + c.name.toLowerCase().includes(q) || + c.fileName.toLowerCase().includes(q) || + c.filePath.toLowerCase().includes(q) || + (c.propsName && c.propsName.toLowerCase().includes(q)) + ); + } + + return comps; + }, [registry.components, activeTier, searchTerm]); + + // Group filtered components by category + const groupedByCategory = useMemo(() => { + const map = new Map(); + for (const comp of filtered) { + if (!map.has(comp.category)) { + map.set(comp.category, { tier: comp.tier, components: [] }); + } + map.get(comp.category)!.components.push(comp); + } + return [...map.entries()].sort(([, a], [, b]) => { + const tierOrder = ALL_TIERS.indexOf(a.tier) - ALL_TIERS.indexOf(b.tier); + if (tierOrder !== 0) return tierOrder; + return a.tier.localeCompare(b.tier); + }); + }, [filtered]); + + // Tier counts for chips + const tierCounts = useMemo(() => { + const counts: Record = { '전체': registry.components.length }; + for (const comp of registry.components) { + counts[comp.tier] = (counts[comp.tier] || 0) + 1; + } + return counts; + }, [registry.components]); + + // Count of previewable components + const previewCount = useMemo(() => { + return registry.components.filter( + (c) => c.tier === 'ui' && UI_PREVIEWS[c.fileName] + ).length; + }, [registry.components]); + + const generatedDate = new Date(registry.generatedAt).toLocaleString('ko-KR'); + + return ( +
+
+ {/* Header */} +
+
+ +

+ Component Registry +

+ + {registry.totalCount}개 + +
+

+ 생성: {generatedDate} · npm run gen:components로 갱신 + · 카드 클릭: 소스코드 보기 · UI 프리뷰: {previewCount}개 +

+
+ + {/* Search */} +
+ + setSearchTerm(e.target.value)} + className="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> + {searchTerm && ( + + )} +
+ + {/* Tier Filter Chips */} +
+ {ALL_TIERS.map((tier) => { + const isActive = activeTier === tier; + const count = tierCounts[tier] || 0; + if (tier !== '전체' && count === 0) return null; + + return ( + + ); + })} +
+ + {/* Results count */} + {(searchTerm || activeTier !== '전체') && ( +

+ {filtered.length}개 결과 +

+ )} + + {/* Component List */} +
+ {groupedByCategory.map(([category, { tier, components }]) => ( + + ))} +
+ + {/* Empty State */} + {filtered.length === 0 && ( +
+ +

검색 결과가 없습니다.

+
+ )} + + {/* Footer */} +
+

+ 데이터 소스: src/generated/component-registry.json +

+
+
+
+ ); +} diff --git a/src/app/[locale]/(protected)/dev/component-registry/actions.ts b/src/app/[locale]/(protected)/dev/component-registry/actions.ts new file mode 100644 index 00000000..6ea939f5 --- /dev/null +++ b/src/app/[locale]/(protected)/dev/component-registry/actions.ts @@ -0,0 +1,19 @@ +'use server'; + +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +export async function getComponentSource(filePath: string): Promise<{ source: string | null; error: string | null }> { + // filePath는 "src/components/ui/button.tsx" 형태 + if (!filePath.startsWith('src/components/') || !filePath.endsWith('.tsx')) { + return { source: null, error: 'Invalid file path' }; + } + + try { + const fullPath = join(process.cwd(), filePath); + const source = await readFile(fullPath, 'utf-8'); + return { source, error: null }; + } catch { + return { source: null, error: 'File not found' }; + } +} diff --git a/src/app/[locale]/(protected)/dev/component-registry/page.tsx b/src/app/[locale]/(protected)/dev/component-registry/page.tsx new file mode 100644 index 00000000..03320afd --- /dev/null +++ b/src/app/[locale]/(protected)/dev/component-registry/page.tsx @@ -0,0 +1,8 @@ +'use client'; + +import registry from '@/generated/component-registry.json'; +import ComponentRegistryClient, { type RegistryData } from './ComponentRegistryClient'; + +export default function ComponentRegistryPage() { + return ; +} diff --git a/src/app/[locale]/(protected)/dev/component-registry/previews.tsx b/src/app/[locale]/(protected)/dev/component-registry/previews.tsx new file mode 100644 index 00000000..45e8b20b --- /dev/null +++ b/src/app/[locale]/(protected)/dev/component-registry/previews.tsx @@ -0,0 +1,1191 @@ +'use client'; + +import { ReactNode, useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Switch } from '@/components/ui/switch'; +import { Textarea } from '@/components/ui/textarea'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Separator } from '@/components/ui/separator'; +import { Progress } from '@/components/ui/progress'; +import { Alert, AlertTitle, AlertDescription } from '@/components/ui/alert'; +import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; +import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; +import { Slider } from '@/components/ui/slider'; +import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '@/components/ui/tooltip'; +import { LoadingSpinner } from '@/components/ui/loading-spinner'; +import { EmptyState } from '@/components/ui/empty-state'; +import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/components/ui/accordion'; +import { + AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, + AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, +} from '@/components/ui/alert-dialog'; +import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@/components/ui/collapsible'; +import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from '@/components/ui/command'; +import { ConfirmDialog } from '@/components/ui/confirm-dialog'; +import { CurrencyInput } from '@/components/ui/currency-input'; +import { DatePicker } from '@/components/ui/date-picker'; +import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'; +import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription } from '@/components/ui/drawer'; +import { + DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, + DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel, +} from '@/components/ui/dropdown-menu'; +import { ErrorCard } from '@/components/ui/error-card'; +import { ErrorMessage } from '@/components/ui/error-message'; +import { NumberInput } from '@/components/ui/number-input'; +import { PhoneInput } from '@/components/ui/phone-input'; +import { AccountNumberInput } from '@/components/ui/account-number-input'; +import { BusinessNumberInput } from '@/components/ui/business-number-input'; +import { CardNumberInput } from '@/components/ui/card-number-input'; +import { PersonalNumberInput } from '@/components/ui/personal-number-input'; +import { QuantityInput } from '@/components/ui/quantity-input'; +import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@/components/ui/select'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '@/components/ui/sheet'; +import { TimePicker } from '@/components/ui/time-picker'; +import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'; +import { FileDropzone } from '@/components/ui/file-dropzone'; +import { FileInput } from '@/components/ui/file-input'; +import { ImageUpload } from '@/components/ui/image-upload'; +import { SearchableSelect } from '@/components/ui/searchable-select'; +import { MultiSelectCombobox } from '@/components/ui/multi-select-combobox'; +import { BadgeSm } from '@/components/atoms/BadgeSm'; +import { ScrollableButtonGroup } from '@/components/atoms/ScrollableButtonGroup'; +import { TabChip } from '@/components/atoms/TabChip'; +// UI - 추가 컴포넌트 +import { StatusBadge } from '@/components/ui/status-badge'; +import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table'; +import { FileList } from '@/components/ui/file-list'; +import { Calendar } from '@/components/ui/calendar'; +import { ChartWrapper } from '@/components/ui/chart-wrapper'; +// Molecules +import { FormField } from '@/components/molecules/FormField'; +import { IconWithBadge } from '@/components/molecules/IconWithBadge'; +import { StatusBadge as MoleculeStatusBadge } from '@/components/molecules/StatusBadge'; +import { TableActions } from '@/components/molecules/TableActions'; +import { StandardDialog } from '@/components/molecules/StandardDialog'; +import { DateRangeSelector } from '@/components/molecules/DateRangeSelector'; +import { YearQuarterFilter } from '@/components/molecules/YearQuarterFilter'; +import { MobileFilter } from '@/components/molecules/MobileFilter'; +// Organisms +import { EmptyState as OrganismEmptyState } from '@/components/organisms/EmptyState'; +import { FormActions } from '@/components/organisms/FormActions'; +import { FormSection } from '@/components/organisms/FormSection'; +import { PageHeader } from '@/components/organisms/PageHeader'; +import { StatCards } from '@/components/organisms/StatCards'; +import { MobileCard } from '@/components/organisms/MobileCard'; +import { FormFieldGrid } from '@/components/organisms/FormFieldGrid'; +// Lucide icons for demos +import { Bell, Package, FileText, Users, TrendingUp, Settings, Inbox } from 'lucide-react'; + +// ── Stateful demo wrappers (hooks 사용이 필요한 컴포넌트) ── + +function CurrencyInputDemo() { + const [v, setV] = useState(50000); + return ( +
+ + +
+ ); +} + +function DatePickerDemo() { + const [d, setD] = useState(); + return setD(date)} placeholder="날짜 선택" />; +} + +function NumberInputDemo() { + const [v, setV] = useState(1234); + return ( +
+ + +
+ ); +} + +function PhoneInputDemo() { + const [v, setV] = useState('01012345678'); + return ; +} + +function AccountNumberInputDemo() { + const [v, setV] = useState(''); + return ; +} + +function BusinessNumberInputDemo() { + const [v, setV] = useState(''); + return ; +} + +function CardNumberInputDemo() { + const [v, setV] = useState(''); + return ; +} + +function PersonalNumberInputDemo() { + const [v, setV] = useState(''); + return ; +} + +function QuantityInputDemo() { + const [v, setV] = useState(5); + return ( +
+ + +
+ ); +} + +function TimePickerDemo() { + const [v, setV] = useState('09:30'); + return ; +} + +function ConfirmDialogDemo() { + const [open, setOpen] = useState(false); + return ( + <> + + setOpen(false)} + onCancel={() => setOpen(false)} + /> + + ); +} + +function SearchableSelectDemo() { + const [v, setV] = useState(''); + const opts = [ + { value: 'apple', label: '사과' }, + { value: 'banana', label: '바나나' }, + { value: 'cherry', label: '체리' }, + { value: 'grape', label: '포도' }, + ]; + return ; +} + +function MultiSelectDemo() { + const [v, setV] = useState([]); + const opts = [ + { value: 'react', label: 'React' }, + { value: 'next', label: 'Next.js' }, + { value: 'vue', label: 'Vue' }, + { value: 'svelte', label: 'Svelte' }, + ]; + return ; +} + +function ImageUploadDemo() { + const [v, setV] = useState(); + return setV('/placeholder.png')} onRemove={() => setV(undefined)} size="sm" hint="JPG, PNG (최대 10MB)" />; +} + +function StandardDialogDemo() { + const [open, setOpen] = useState(false); + return ( + <> + + +

다이얼로그 내용이 여기에 표시됩니다.

+
+ + ); +} + +function DateRangeSelectorDemo() { + const [startDate, setStartDate] = useState('2026-01-01'); + const [endDate, setEndDate] = useState('2026-02-12'); + return ( +
+ +
+ ); +} + +function YearQuarterFilterDemo() { + const [year, setYear] = useState(2026); + const [quarter, setQuarter] = useState<'전체' | 'Q1' | 'Q2' | 'Q3' | 'Q4'>('전체'); + return ; +} + +function MobileFilterDemo() { + const [values, setValues] = useState>({ status: 'all' }); + const fields = [ + { key: 'status', label: '상태', type: 'single' as const, options: [ + { value: 'active', label: '활성' }, + { value: 'inactive', label: '비활성' }, + { value: 'pending', label: '대기' }, + ]}, + ]; + return setValues(prev => ({ ...prev, [k]: v }))} onReset={() => setValues({ status: 'all' })} />; +} + +// ── Preview Registry ── + +type PreviewEntry = { + label: string; + render: () => ReactNode; +}; + +export const UI_PREVIEWS: Record = { + // ─── Basic ─── + 'button.tsx': [ + { + label: 'Variants', + render: () => ( +
+ + + + + + +
+ ), + }, + { + label: 'Sizes', + render: () => ( +
+ + + + +
+ ), + }, + ], + + 'badge.tsx': [ + { + label: 'Variants', + render: () => ( +
+ Default + Secondary + Outline + Destructive +
+ ), + }, + ], + + 'input.tsx': [ + { + label: 'States', + render: () => ( +
+ + +
+ ), + }, + ], + + 'label.tsx': [ + { + label: 'Basic', + render: () => ( +
+ + +
+ ), + }, + ], + + 'checkbox.tsx': [ + { + label: 'States', + render: () => ( +
+
+ + +
+
+ + +
+
+ + +
+
+ ), + }, + ], + + 'switch.tsx': [ + { + label: 'States', + render: () => ( +
+
+ + +
+
+ + +
+
+ ), + }, + ], + + 'textarea.tsx': [ + { + label: 'Basic', + render: () =>