- 법인차량 관리 3개 페이지 (차량등록, 운행일지, 정비이력) - MES 데이터 정합성 분석 보고서 v1/v2 - sam-docs 프론트엔드 기술문서 v1 (9개 챕터) - claudedocs 가이드/테스트URL 업데이트
4.2 KiB
4.2 KiB
라우팅 및 페이지 패턴
라우팅 구조
/[locale]/(auth)/login # 로그인
/[locale]/(protected)/dashboard # 대시보드
/[locale]/(protected)/{domain}/{feature} # 목록
/[locale]/(protected)/{domain}/{feature}?mode=new # 등록
/[locale]/(protected)/{domain}/{feature}/[id] # 상세(view)
/[locale]/(protected)/{domain}/{feature}/[id]?mode=edit # 수정
레이아웃 계층
Root Layout ([locale]/layout.tsx) - Server Component
├── i18n 설정 (NextIntlClientProvider)
├── 폰트 로드 (PretendardVariable)
├── Toaster (sonner)
└── Protected Layout ((protected)/layout.tsx) - Client Component
├── useAuthGuard() - 인증 보호
├── RootProvider - 전역 상태
├── ApiErrorProvider - 401 에러 처리
├── FCMProvider - 푸시 알림
├── PermissionGate - 권한 제어
└── AuthenticatedLayout
├── Sidebar - 메뉴
├── Header - 회사선택, 검색, 알림
├── HeaderFavoritesBar - 즐겨찾기
└── {children} - 페이지 컨텐츠
페이지 모드 패턴 (mode 쿼리파라미터)
규칙
- 별도
/new,/edit경로 금지 →?mode=new,?mode=edit사용 - 목록과 등록을 같은 page.tsx에서 분기
목록 + 등록 (page.tsx)
'use client';
import { useSearchParams } from 'next/navigation';
export default function ItemsPage() {
const searchParams = useSearchParams();
const mode = searchParams.get('mode');
// mode=new → 등록 폼
if (mode === 'new') {
return <ItemDetail mode="new" />;
}
// 기본 → 목록
return <ItemList />;
}
상세 + 수정 ([id]/page.tsx)
'use client';
import { useParams, useSearchParams } from 'next/navigation';
export default function ItemDetailPage() {
const params = useParams();
const searchParams = useSearchParams();
const id = params.id as string;
const mode = searchParams.get('mode') === 'edit' ? 'edit' : 'view';
return <ItemDetail id={id} mode={mode} />;
}
네비게이션
// 목록 → 등록
router.push('/master-data/items?mode=new');
// 목록 → 상세
router.push(`/master-data/items/${id}`);
// 상세 → 수정
router.push(`/master-data/items/${id}?mode=edit`);
// 수정 → 상세 (저장 후)
router.push(`/master-data/items/${id}`);
// → 목록으로
router.push('/master-data/items');
페이지 레이아웃 표준
PageLayout 패딩 규칙
AuthenticatedLayout의<main>에는 패딩 없음PageLayout컴포넌트가p-3 md:p-6패딩 담당- page.tsx에서 패딩 wrapper 추가 금지 (이중 패딩 방지)
등록/수정/상세 페이지 헤더
<div className="flex items-center justify-between">
<h1 className="text-xl font-bold">페이지 제목</h1>
<Button variant="link" className="text-muted-foreground"
onClick={() => router.push(listPath)}>
← 목록으로
</Button>
</div>
하단 Sticky 액션 바 (필수)
폼 페이지 하단에 sticky bar로 버튼 배치:
| 모드 | 좌측 | 우측 |
|---|---|---|
| 등록 (new) | X 취소 |
💾 저장 |
| 상세 (view) | X 취소 (목록으로) |
✏️ 수정 |
| 수정 (edit) | X 취소 |
💾 저장 |
<div className="sticky bottom-0 bg-white border-t shadow-sm">
<div className="px-3 py-3 md:px-6 md:py-4 flex items-center justify-between">
<Button variant="outline" onClick={() => router.push(listPath)}>
<X className="h-4 w-4 mr-1" />
취소
</Button>
<Button onClick={handleSubmit} disabled={isSubmitting}>
{isSubmitting
? <Loader2 className="h-4 w-4 mr-1 animate-spin" />
: <Save className="h-4 w-4 mr-1" />}
저장
</Button>
</div>
</div>
테이블 표준
필수 컬럼 구조
체크박스 → 번호(1부터) → 데이터 컬럼 → 작업 컬럼
// 번호 계산 (페이지네이션 고려)
const globalIndex = (currentPage - 1) * pageSize + index + 1;
작업 버튼
- 체크박스 선택 시에만 표시
i18n
지원 언어: ko (기본), en, ja
경로: /ko/..., /en/..., /ja/...