- 법인차량 관리 3개 페이지 (차량등록, 운행일지, 정비이력) - MES 데이터 정합성 분석 보고서 v1/v2 - sam-docs 프론트엔드 기술문서 v1 (9개 챕터) - claudedocs 가이드/테스트URL 업데이트
206 lines
5.0 KiB
Markdown
206 lines
5.0 KiB
Markdown
# 코딩 컨벤션 및 필수 규칙
|
|
|
|
---
|
|
|
|
## 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 <div>로딩 중...</div>;
|
|
return <Component data={data} />;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 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 사용
|
|
<FormField label="회사명" value={v} onChange={handleChange} />
|
|
|
|
// ❌ 신규 폼에서 수동 조합 금지
|
|
<div className="space-y-2">
|
|
<Label>회사명</Label>
|
|
<Input value={v} onChange={handleChange} />
|
|
</div>
|
|
```
|
|
|
|
**기존 폼**: 건드리지 않음 (정상 작동 중이면 마이그레이션 불필요)
|
|
|
|
---
|
|
|
|
## 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<typeof formSchema>;
|
|
|
|
// 3. useForm에 연결
|
|
const form = useForm<FormData>({
|
|
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<T>
|
|
```
|
|
|
|
---
|
|
|
|
## 리스트 페이지 필수 항목
|
|
|
|
`IntegratedListTemplateV2` 사용 시:
|
|
- [ ] `useColumnSettings` + `ColumnSettingsPopover` 적용
|
|
- [ ] `renderMobileCard` (모바일 카드) 구현
|
|
- [ ] `selectedItems: Set<string>` (체크박스) 구현
|
|
- [ ] `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에 있으면 → <td rowSpan={span}>
|
|
// covered에 있으면 → skip (렌더링 안 함)
|
|
// 둘 다 아니면 → 일반 <td>
|
|
```
|
|
|
|
---
|
|
|
|
## 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`)
|