신규 기능: - 작업지시 수정 페이지 추가 (/production/work-orders/[id]/edit) - WorkOrderEdit 컴포넌트 신규 생성 - bulk-actions.ts 일괄 작업 유틸리티 추가 - toast-utils.ts 알림 유틸리티 추가 기능 개선: - ProductionDashboard 대시보드 액션 및 표시 개선 - WorkOrderCreate 생성 화면 개선 - WorkResultList 작업 결과 목록 타입 및 표시 개선 - EstimateDetailForm 견적 폼 개선 - QuoteRegistration 견적 등록 개선 - client-management-sales-admin 거래처 관리 개선 - error-handler.ts 에러 처리 개선
137 lines
4.0 KiB
TypeScript
137 lines
4.0 KiB
TypeScript
// API 에러 핸들링 헬퍼 유틸리티
|
|
// API 요청 실패 시 에러 처리 및 사용자 친화적 메시지 생성
|
|
|
|
/**
|
|
* API 에러 클래스
|
|
* - 표준 Error를 확장하여 HTTP 상태 코드와 validation errors 포함
|
|
*/
|
|
export class ApiError extends Error {
|
|
constructor(
|
|
public status: number,
|
|
public message: string,
|
|
public errors?: Record<string, string[]>
|
|
) {
|
|
super(message);
|
|
this.name = 'ApiError';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 품목코드 중복 에러 클래스
|
|
* - 백엔드에서 400 에러와 함께 duplicate_id, duplicate_code 반환 시 사용
|
|
* - 2025-12-11: 백엔드 DuplicateCodeException 대응
|
|
*/
|
|
export class DuplicateCodeError extends ApiError {
|
|
constructor(
|
|
public message: string,
|
|
public duplicateId: number,
|
|
public duplicateCode: string
|
|
) {
|
|
super(400, message);
|
|
this.name = 'DuplicateCodeError';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* API 응답 에러를 처리하고 ApiError를 throw
|
|
* @param response - fetch Response 객체
|
|
* @throws {ApiError} HTTP 상태 코드, 메시지, validation errors 포함
|
|
*/
|
|
export const handleApiError = async (response: Response): Promise<never> => {
|
|
const data = await response.json().catch(() => ({}));
|
|
|
|
// 401 Unauthorized - 토큰 만료 또는 인증 실패
|
|
// ✅ 자동으로 로그인 페이지로 리다이렉트
|
|
if (response.status === 401) {
|
|
console.warn('⚠️ 401 Unauthorized - 로그인 페이지로 이동합니다.');
|
|
|
|
// 클라이언트 사이드에서만 리다이렉트
|
|
if (typeof window !== 'undefined') {
|
|
window.location.href = '/login';
|
|
}
|
|
|
|
throw new ApiError(
|
|
401,
|
|
data.message || '인증이 필요합니다. 로그인 상태를 확인해주세요.',
|
|
data.errors
|
|
);
|
|
}
|
|
|
|
// 403 Forbidden - 권한 없음
|
|
if (response.status === 403) {
|
|
throw new ApiError(
|
|
403,
|
|
data.message || '접근 권한이 없습니다.',
|
|
data.errors
|
|
);
|
|
}
|
|
|
|
// 400 Bad Request - 품목코드 중복 에러 체크
|
|
// 백엔드 DuplicateCodeException이 duplicate_id, duplicate_code 반환
|
|
if (response.status === 400 && data.duplicate_id) {
|
|
console.warn('⚠️ 품목코드 중복 감지:', {
|
|
duplicateId: data.duplicate_id,
|
|
duplicateCode: data.duplicate_code,
|
|
message: data.message
|
|
});
|
|
throw new DuplicateCodeError(
|
|
data.message || '해당 품목코드가 이미 존재합니다.',
|
|
data.duplicate_id,
|
|
data.duplicate_code
|
|
);
|
|
}
|
|
|
|
// 422 Unprocessable Entity - Validation 에러
|
|
if (response.status === 422) {
|
|
// 상세 validation 에러 로그 출력
|
|
console.error('🔴 [API 422 Validation Error]', {
|
|
message: data.message,
|
|
errors: data.errors,
|
|
fullResponse: data
|
|
});
|
|
throw new ApiError(
|
|
422,
|
|
data.message || '입력값을 확인해주세요.',
|
|
data.errors
|
|
);
|
|
}
|
|
|
|
// 기타 에러
|
|
throw new ApiError(
|
|
response.status,
|
|
data.message || '서버 오류가 발생했습니다',
|
|
data.errors
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 디버그 모드 설정
|
|
* - true: 에러 코드 표시 (개발/테스트)
|
|
* - false: 메시지만 표시 (프로덕션)
|
|
*
|
|
* TODO: 프로덕션 배포 시 false로 변경하거나 환경변수 사용
|
|
*/
|
|
const SHOW_ERROR_CODE = true;
|
|
|
|
/**
|
|
* 에러 객체에서 사용자 친화적인 메시지 추출
|
|
* @param error - 발생한 에러 객체 (ApiError, Error, unknown)
|
|
* @param includeCode - 에러 코드 포함 여부 (기본값: SHOW_ERROR_CODE 설정 따름)
|
|
* @returns 사용자에게 표시할 에러 메시지
|
|
*/
|
|
export const getErrorMessage = (error: unknown, includeCode?: boolean): string => {
|
|
const showCode = includeCode ?? SHOW_ERROR_CODE;
|
|
|
|
if (error instanceof DuplicateCodeError) {
|
|
return showCode
|
|
? `[${error.status}] ${error.message} (코드: ${error.duplicateCode})`
|
|
: error.message;
|
|
}
|
|
if (error instanceof ApiError) {
|
|
return showCode ? `[${error.status}] ${error.message}` : error.message;
|
|
}
|
|
if (error instanceof Error) {
|
|
return error.message;
|
|
}
|
|
return '알 수 없는 오류가 발생했습니다';
|
|
}; |