Files
sam-react-prod/sam-docs/frontend/v1/08-utilities.md
유병철 c309ac479f feat: [vehicle] 법인차량 관리 모듈 + MES 분석 보고서 + 프론트엔드 문서
- 법인차량 관리 3개 페이지 (차량등록, 운행일지, 정비이력)
- MES 데이터 정합성 분석 보고서 v1/v2
- sam-docs 프론트엔드 기술문서 v1 (9개 챕터)
- claudedocs 가이드/테스트URL 업데이트
2026-03-13 17:52:57 +09:00

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;
});
```