feat: 품목관리 기능 개선 및 문서화 업데이트
- 품목 상세/수정 페이지 파일 다운로드 기능 개선 - DynamicItemForm 파일 업로드 UI/UX 개선 (시방서, 인정서) - BendingDiagramSection 조립/절곡 부품 전개도 통합 - API proxy route 품목 타입별 라우팅 개선 - ItemListClient 파일 다운로드 유틸리티 적용 - 품목코드 중복 체크 및 다이얼로그 추가 문서화: - DynamicItemForm 훅 분리 계획서 추가 (2161줄 → 900줄 목표) - 백엔드 API 마이그레이션 문서 추가 - 대용량 파일 처리 전략 가이드 추가 - 테넌트 데이터 격리 감사 문서 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
71
src/lib/utils/fileDownload.ts
Normal file
71
src/lib/utils/fileDownload.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 파일 다운로드 유틸리티
|
||||
*
|
||||
* 백엔드 API: GET /api/v1/files/{id}/download
|
||||
* 프록시: GET /api/proxy/files/{id}/download
|
||||
*/
|
||||
|
||||
/**
|
||||
* 파일 ID로 다운로드
|
||||
* @param fileId 파일 ID
|
||||
* @param fileName 저장할 파일명 (선택, 없으면 서버에서 제공하는 이름 사용)
|
||||
*/
|
||||
export async function downloadFileById(fileId: number, fileName?: string): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(`/api/proxy/files/${fileId}/download`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`다운로드 실패: ${response.status}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
|
||||
// 파일명이 없으면 Content-Disposition 헤더에서 추출 시도
|
||||
let downloadFileName = fileName;
|
||||
if (!downloadFileName) {
|
||||
const contentDisposition = response.headers.get('Content-Disposition');
|
||||
if (contentDisposition) {
|
||||
const match = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
|
||||
if (match && match[1]) {
|
||||
downloadFileName = match[1].replace(/['"]/g, '');
|
||||
// URL 디코딩 (한글 파일명 처리)
|
||||
try {
|
||||
downloadFileName = decodeURIComponent(downloadFileName);
|
||||
} catch {
|
||||
// 디코딩 실패 시 그대로 사용
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 그래도 없으면 기본 파일명
|
||||
if (!downloadFileName) {
|
||||
downloadFileName = `file_${fileId}`;
|
||||
}
|
||||
|
||||
// Blob URL 생성 및 다운로드 트리거
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = downloadFileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
} catch (error) {
|
||||
console.error('[fileDownload] 다운로드 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 파일 경로로 새 탭에서 열기 (미리보기용)
|
||||
* @param filePath 파일 경로
|
||||
*/
|
||||
export function openFileInNewTab(filePath: string): void {
|
||||
// 백엔드 파일 서빙 URL 구성
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
|
||||
const fileUrl = `${baseUrl}/storage/${filePath}`;
|
||||
window.open(fileUrl, '_blank');
|
||||
}
|
||||
Reference in New Issue
Block a user