- 법인차량 관리 3개 페이지 (차량등록, 운행일지, 정비이력) - MES 데이터 정합성 분석 보고서 v1/v2 - sam-docs 프론트엔드 기술문서 v1 (9개 챕터) - claudedocs 가이드/테스트URL 업데이트
176 lines
3.9 KiB
Markdown
176 lines
3.9 KiB
Markdown
# 유틸리티 함수
|
|
|
|
---
|
|
|
|
## cn - 클래스명 병합
|
|
|
|
```typescript
|
|
import { cn } from '@/lib/utils';
|
|
|
|
// clsx + tailwind-merge
|
|
<div className={cn('p-4 bg-white', isActive && 'bg-blue-50', className)} />
|
|
```
|
|
|
|
## safeJsonParse - 안전한 JSON 파싱
|
|
|
|
```typescript
|
|
import { safeJsonParse } from '@/lib/utils';
|
|
|
|
const data = safeJsonParse<Config>(localStorage.getItem('config'), defaultConfig);
|
|
// 파싱 실패 시 fallback 반환
|
|
```
|
|
|
|
---
|
|
|
|
## 포맷팅 함수
|
|
|
|
**경로**: `src/lib/formatters.ts`
|
|
|
|
### 전화번호
|
|
|
|
```typescript
|
|
import { formatPhoneNumber, parsePhoneNumber } from '@/lib/formatters';
|
|
|
|
formatPhoneNumber('01012345678') // → "010-1234-5678"
|
|
parsePhoneNumber('010-1234-5678') // → "01012345678" (숫자만)
|
|
```
|
|
|
|
### 사업자번호
|
|
|
|
```typescript
|
|
import { formatBusinessNumber, validateBusinessNumber } from '@/lib/formatters';
|
|
|
|
formatBusinessNumber('1234567890') // → "123-45-67890"
|
|
validateBusinessNumber('1234567890') // → true/false (체크섬 검증)
|
|
```
|
|
|
|
### 주민번호
|
|
|
|
```typescript
|
|
import { formatPersonalNumber, formatPersonalNumberMasked } from '@/lib/formatters';
|
|
|
|
formatPersonalNumber('9001011234567') // → "900101-1234567"
|
|
formatPersonalNumberMasked('9001011234567') // → "900101-*******"
|
|
```
|
|
|
|
### 카드/계좌번호
|
|
|
|
```typescript
|
|
import { formatCardNumber, formatAccountNumber } from '@/lib/formatters';
|
|
|
|
formatCardNumber('1234567890123456') // → "1234-5678-9012-3456"
|
|
formatAccountNumber('12345678901234') // → "1234-5678-9012-34"
|
|
```
|
|
|
|
### 숫자/금액
|
|
|
|
```typescript
|
|
import { formatNumber, parseNumber, extractDigits } from '@/lib/formatters';
|
|
|
|
formatNumber(1234567) // → "1,234,567"
|
|
parseNumber('1,234,567') // → 1234567
|
|
extractDigits('abc-123-def') // → "123"
|
|
```
|
|
|
|
---
|
|
|
|
## URL 빌더
|
|
|
|
**경로**: `src/lib/api/query-params.ts`
|
|
|
|
```typescript
|
|
import { buildApiUrl, buildQueryParams } from '@/lib/api/query-params';
|
|
|
|
// API URL 생성 (undefined/null/'' 자동 필터링)
|
|
const url = buildApiUrl('/api/v1/items', {
|
|
search: 'test',
|
|
status: undefined, // 제외
|
|
page: 1,
|
|
});
|
|
|
|
// 쿼리 파라미터만 생성
|
|
const params = buildQueryParams({ search: 'test', page: 1 });
|
|
// → URLSearchParams 객체
|
|
```
|
|
|
|
---
|
|
|
|
## 인쇄 유틸리티
|
|
|
|
**경로**: `src/lib/print-utils.ts`
|
|
|
|
```typescript
|
|
import { printElement, printArea } from '@/lib/print-utils';
|
|
|
|
// 특정 요소 인쇄
|
|
printElement(document.getElementById('invoice'));
|
|
|
|
// .print-area 클래스 영역 인쇄
|
|
printArea({ title: '견적서' });
|
|
|
|
// 옵션
|
|
printElement('#invoice', {
|
|
title: '견적서', // 브라우저 탭 제목
|
|
styles: customCSS, // 추가 CSS
|
|
closeAfterPrint: true, // 인쇄 후 창 닫기
|
|
});
|
|
```
|
|
|
|
**HTML에서 사용**:
|
|
```tsx
|
|
<div className="print-area">
|
|
{/* 인쇄될 영역 */}
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 인증 헤더
|
|
|
|
**경로**: `src/lib/api/auth-headers.ts`
|
|
|
|
```typescript
|
|
import { getAuthHeaders, getMultipartHeaders, hasAuthToken } from '@/lib/api/auth-headers';
|
|
|
|
// JSON 요청 헤더 (프록시 사용 시)
|
|
const headers = getAuthHeaders();
|
|
// → { 'Content-Type': 'application/json', 'Accept': 'application/json' }
|
|
|
|
// Multipart FormData 헤더
|
|
const headers = getMultipartHeaders();
|
|
// → { 'Accept': 'application/json' }
|
|
|
|
// 인증 상태 확인 (클라이언트)
|
|
if (hasAuthToken()) { /* 인증됨 */ }
|
|
```
|
|
|
|
---
|
|
|
|
## 에러 처리
|
|
|
|
**경로**: `src/lib/api/errors.ts`
|
|
|
|
```typescript
|
|
import { createErrorResponse, isApiError, isAuthError } from '@/lib/api/errors';
|
|
|
|
// 에러 응답 생성
|
|
const error = createErrorResponse(404, '데이터를 찾을 수 없습니다');
|
|
|
|
// 에러 타입 확인
|
|
if (isApiError(response)) { /* API 에러 */ }
|
|
if (isAuthError(response)) { /* 인증 에러 (401) */ }
|
|
```
|
|
|
|
---
|
|
|
|
## localStorage 접근 (Next.js 호환)
|
|
|
|
```typescript
|
|
// ✅ Next.js Pattern (SSR 안전)
|
|
const [data, setData] = useState(() => {
|
|
if (typeof window === 'undefined') return defaultValue;
|
|
const saved = localStorage.getItem('key');
|
|
return saved ? JSON.parse(saved) : defaultValue;
|
|
});
|
|
```
|