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

3.9 KiB

유틸리티 함수


cn - 클래스명 병합

import { cn } from '@/lib/utils';

// clsx + tailwind-merge
<div className={cn('p-4 bg-white', isActive && 'bg-blue-50', className)} />

safeJsonParse - 안전한 JSON 파싱

import { safeJsonParse } from '@/lib/utils';

const data = safeJsonParse<Config>(localStorage.getItem('config'), defaultConfig);
// 파싱 실패 시 fallback 반환

포맷팅 함수

경로: src/lib/formatters.ts

전화번호

import { formatPhoneNumber, parsePhoneNumber } from '@/lib/formatters';

formatPhoneNumber('01012345678')  // → "010-1234-5678"
parsePhoneNumber('010-1234-5678') // → "01012345678" (숫자만)

사업자번호

import { formatBusinessNumber, validateBusinessNumber } from '@/lib/formatters';

formatBusinessNumber('1234567890')    // → "123-45-67890"
validateBusinessNumber('1234567890')  // → true/false (체크섬 검증)

주민번호

import { formatPersonalNumber, formatPersonalNumberMasked } from '@/lib/formatters';

formatPersonalNumber('9001011234567')      // → "900101-1234567"
formatPersonalNumberMasked('9001011234567') // → "900101-*******"

카드/계좌번호

import { formatCardNumber, formatAccountNumber } from '@/lib/formatters';

formatCardNumber('1234567890123456')  // → "1234-5678-9012-3456"
formatAccountNumber('12345678901234') // → "1234-5678-9012-34"

숫자/금액

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

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

import { printElement, printArea } from '@/lib/print-utils';

// 특정 요소 인쇄
printElement(document.getElementById('invoice'));

// .print-area 클래스 영역 인쇄
printArea({ title: '견적서' });

// 옵션
printElement('#invoice', {
  title: '견적서',         // 브라우저 탭 제목
  styles: customCSS,       // 추가 CSS
  closeAfterPrint: true,   // 인쇄 후 창 닫기
});

HTML에서 사용:

<div className="print-area">
  {/* 인쇄될 영역 */}
</div>

인증 헤더

경로: src/lib/api/auth-headers.ts

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

import { createErrorResponse, isApiError, isAuthError } from '@/lib/api/errors';

// 에러 응답 생성
const error = createErrorResponse(404, '데이터를 찾을 수 없습니다');

// 에러 타입 확인
if (isApiError(response)) { /* API 에러 */ }
if (isAuthError(response)) { /* 인증 에러 (401) */ }

localStorage 접근 (Next.js 호환)

// ✅ 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;
});