refactor(WEB): claudedocs 재정리 및 AuthContext/Zustand/유틸 코드 개선

- claudedocs 폴더 구조 재정리: archive/sessions, guides/migration·mobile·universal-list, refactoring 분류
- 오래된 세션 컨텍스트/체크리스트 문서 정리 (아카이브 이동 또는 삭제)
- AuthContext → authStore(Zustand) 전환 시작, RootProvider 간소화
- GenericCRUDDialog 공통 다이얼로그 컴포넌트 추가
- PermissionDialog 삭제 → GenericCRUDDialog로 대체
- RankDialog/TitleDialog GenericCRUDDialog 기반으로 리팩토링
- toast-utils.ts 삭제 (미사용)
- fileDownload.ts 개선, excel-download.ts 정리
- menuStore/themeStore Zustand 셀렉터 최적화
- useColumnSettings/useTableColumnStore 기능 보강
- 세금계산서/견적/작업자화면/결재 등 소규모 개선

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-23 17:17:13 +09:00
parent 6c3572e568
commit 07374c826c
75 changed files with 1704 additions and 1376 deletions

View File

@@ -5,6 +5,26 @@
* 프록시: GET /api/proxy/files/{id}/download
*/
import { downloadBlob } from './export';
/**
* Content-Disposition 헤더에서 파일명 추출
*/
function extractFilenameFromHeader(response: Response): string | null {
const contentDisposition = response.headers.get('Content-Disposition');
if (!contentDisposition) return null;
const match = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
if (!match?.[1]) return null;
const raw = match[1].replace(/['"]/g, '');
try {
return decodeURIComponent(raw);
} catch {
return raw;
}
}
/**
* 파일 ID로 다운로드
* @param fileId 파일 ID
@@ -19,40 +39,11 @@ export async function downloadFileById(fileId: number, fileName?: string): Promi
}
const blob = await response.blob();
const downloadFileName = fileName
?? extractFilenameFromHeader(response)
?? `file_${fileId}`;
// 파일명이 없으면 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);
downloadBlob(blob, downloadFileName);
} catch (error) {
console.error('[fileDownload] 다운로드 오류:', error);
throw error;