- 법인차량 관리 3개 페이지 (차량등록, 운행일지, 정비이력) - MES 데이터 정합성 분석 보고서 v1/v2 - sam-docs 프론트엔드 기술문서 v1 (9개 챕터) - claudedocs 가이드/테스트URL 업데이트
5.0 KiB
5.0 KiB
코딩 컨벤션 및 필수 규칙
Client Component 필수
모든 페이지는 'use client' 선언 필수. Server Component 사용 금지.
// ✅ 올바른 패턴
'use client';
export default function Page() { ... }
// ❌ 금지
export default async function Page() { ... }
이유: 폐쇄형 ERP (SEO 불필요), Server Component에서 쿠키 수정(토큰 갱신) 불가
데이터 로딩 패턴
'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 필수 사용
// ✅ 필수
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()}`;
컴포넌트 재사용 우선
새 컴포넌트 작성 전 확인 순서:
src/components/organisms/index.tsexport 목록src/components/molecules/내 공통 컴포넌트src/components/ui/내 UI 컴포넌트- dev/component-registry 페이지 검색
- 동일 도메인 기존 컴포넌트
FormField 사용 (신규 폼)
// ✅ 신규 폼 - FormField 사용
<FormField label="회사명" value={v} onChange={handleChange} />
// ❌ 신규 폼에서 수동 조합 금지
<div className="space-y-2">
<Label>회사명</Label>
<Input value={v} onChange={handleChange} />
</div>
기존 폼: 건드리지 않음 (정상 작동 중이면 마이그레이션 불필요)
Zod 스키마 검증 (신규 폼)
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 (문서/보고서)
반드시 구조 분석 → 코딩 순서:
- 플랫 인덱스 맵: 실제 렌더링 행 수 기준으로 인덱스 산정
- 병합 범위 표기: span은 그룹 첫 행에만
- Coverage Map 패턴:
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)