# 코딩 컨벤션 및 필수 규칙 --- ## Client Component 필수 모든 페이지는 `'use client'` 선언 필수. Server Component 사용 금지. ```tsx // ✅ 올바른 패턴 'use client'; export default function Page() { ... } // ❌ 금지 export default async function Page() { ... } ``` **이유**: 폐쇄형 ERP (SEO 불필요), Server Component에서 쿠키 수정(토큰 갱신) 불가 ## 데이터 로딩 패턴 ```tsx 'use client'; import { useEffect, useState } from 'react'; import { getData } from '@/components/.../actions'; export default function Page() { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { getData() .then(result => { if (result.success) setData(result.data); }) .finally(() => setIsLoading(false)); }, []); if (isLoading) return
로딩 중...
; return ; } ``` --- ## buildApiUrl 필수 사용 ```tsx // ✅ 필수 import { buildApiUrl } from '@/lib/api/query-params'; const url = buildApiUrl('/api/v1/items', { search, page }); // ❌ 금지 const params = new URLSearchParams(); params.set('search', value); const url = `${API_URL}/api/v1/items?${params.toString()}`; ``` --- ## 컴포넌트 재사용 우선 새 컴포넌트 작성 전 확인 순서: 1. `src/components/organisms/index.ts` export 목록 2. `src/components/molecules/` 내 공통 컴포넌트 3. `src/components/ui/` 내 UI 컴포넌트 4. dev/component-registry 페이지 검색 5. 동일 도메인 기존 컴포넌트 --- ## FormField 사용 (신규 폼) ```tsx // ✅ 신규 폼 - FormField 사용 // ❌ 신규 폼에서 수동 조합 금지
``` **기존 폼**: 건드리지 않음 (정상 작동 중이면 마이그레이션 불필요) --- ## Zod 스키마 검증 (신규 폼) ```tsx import { z } from 'zod'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; // 1. 스키마 정의 const formSchema = z.object({ itemName: z.string().min(1, '품목명을 입력하세요'), quantity: z.number().min(1, '1 이상 입력하세요'), status: z.enum(['active', 'inactive']), memo: z.string().optional(), }); // 2. 타입 추출 (별도 interface 정의 불필요) type FormData = z.infer; // 3. useForm에 연결 const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { itemName: '', quantity: 1, status: 'active' }, }); ``` **규칙**: - 에러 메시지 한글 작성 - 스키마 위치: 컴포넌트 파일 상단 또는 `schema.ts` - `z.infer` 사용, 별도 `interface` 중복 정의 금지 --- ## 팝업 정책 ``` ❌ 금지: alert(), confirm(), prompt() ✅ 사용: Radix UI Dialog/AlertDialog, toast (sonner) ``` --- ## 검색 모달 표준 ``` ❌ 금지: Dialog + Input + 리스트 직접 조합 ✅ 사용: SearchableSelectionModal ``` --- ## 리스트 페이지 필수 항목 `IntegratedListTemplateV2` 사용 시: - [ ] `useColumnSettings` + `ColumnSettingsPopover` 적용 - [ ] `renderMobileCard` (모바일 카드) 구현 - [ ] `selectedItems: Set` (체크박스) 구현 - [ ] `tableHeaderActions` (테이블 내 필터) 필요 시 구현 --- ## 테이블 rowSpan/colSpan (문서/보고서) **반드시 구조 분석 → 코딩 순서**: 1. **플랫 인덱스 맵**: 실제 렌더링 행 수 기준으로 인덱스 산정 2. **병합 범위 표기**: span은 그룹 첫 행에만 3. **Coverage Map 패턴**: ```typescript function buildCoverageMap(items, spanKey) { const map = {}; const covered = new Set(); items.forEach((item, idx) => { const span = item[spanKey]; if (span && span > 1) { map[idx] = span; for (let i = idx + 1; i < idx + span; i++) covered.add(i); } }); return { map, covered }; } // map에 있으면 → // covered에 있으면 → skip (렌더링 안 함) // 둘 다 아니면 → 일반 ``` --- ## Git 규칙 - **develop**: 평소 작업 (자유롭게 커밋) - **main**: 기능별 squash merge만 (직접 push 금지) - **커밋 메시지**: `[타입]: 작업내용` (feat, fix, chore, refactor 등) - **`snapshot.txt`, `.DS_Store`**: 항상 제외 --- ## 빌드 정책 - 개발자가 직접 빌드 확인 - TypeScript strict 모드 사용 - ESLint: 빌드 시 무시 (CI에서 별도 처리) --- ## 신규 페이지 생성 체크리스트 - [ ] `'use client'` 선언 - [ ] `?mode=new/edit` 쿼리파라미터 패턴 사용 (`/new`, `/edit` 경로 금지) - [ ] Server Action에서 `buildApiUrl()` 사용 - [ ] 기존 컴포넌트 재사용 확인 (organisms, molecules 검색) - [ ] 리스트 페이지: `IntegratedListTemplateV2` 사용 검토 - [ ] 폼 페이지: FormField, Zod 스키마 사용 (신규) - [ ] 검색 모달: `SearchableSelectionModal` 사용 - [ ] 하단 sticky 액션 바 구현 - [ ] 모바일 반응형 대응 - [ ] 타입 체크 (`npx tsc --noEmit`)