diff --git a/src/styles/Components.js b/src/styles/Components.js index 02f2b2c..688aa04 100644 --- a/src/styles/Components.js +++ b/src/styles/Components.js @@ -679,6 +679,12 @@ export const SearchItem = styled.div` } `; +export const DownloadContainer = styled.div` + display: flex; + align-items: center; + gap: 10px; +`; + export const SearchRow = styled.div` display: flex; flex-wrap: wrap; @@ -689,4 +695,43 @@ export const SearchRow = styled.div` padding-top: 15px; margin-top: 15px; } +`; + +export const TabScroll = styled.div` + width: 100%; + overflow: auto; +`; + +export const TabItem = styled(Link)` + display: inline-flex; + width: 120px; + height: 30px; + justify-content: center; + align-items: center; + background: #f9f9f9; + border-left: 1px solid #d9d9d9; + &:hover { + background: #888; + color: #fff; + } + ${props => + props.$state === 'active' && + ` + background: #888; + color: #fff;`} +`; + +export const TabWrapper = styled.ul` + display: flex; + li:first-child { + ${TabItem} { + border-left: 0; + } + } +`; + +export const CircularProgressWrapper = styled.div` + display: flex; + align-items: center; + justify-content: center; `; \ No newline at end of file diff --git a/src/utils/common.js b/src/utils/common.js index 5213be8..6bd85b0 100644 --- a/src/utils/common.js +++ b/src/utils/common.js @@ -86,4 +86,109 @@ export const getFieldLabel = (key, value) => { } return key; -}; \ No newline at end of file +}; + +export const numberFormatter = { + formatCurrency: (number, decimals = 2) => { + if (number === null || number === undefined) return '0'; + + try { + const num = typeof number === 'string' ? parseFloat(number) : number; + if (isNaN(num)) return '0'; + + // 정수인지 확인 + const isInteger = Number.isInteger(num); + + // 작은 수이거나 소수점이 있는 경우 + if ((Math.abs(num) < 1 && num !== 0) || !isInteger) { + return num.toFixed(decimals); + } + + // 정수인 경우 + return new Intl.NumberFormat('ko-KR', { + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(num); + + } catch (e) { + console.error('Currency formatting error:', e); + return '0'; + } + } + +}; + +/** + * API 응답으로부터 파일 다운로드를 처리하는 함수 + * @param {Object} response - API 응답 객체 + * @param {Object} response.data - 응답 데이터 + * @param {Object} response.headers - 응답 헤더 + * @param {Object} options - 추가 옵션 + * @param {string} options.defaultFileName - 기본 파일명 (기본값: 'download') + * @returns {void} + * @throws {Error} 데이터가 없거나 파일 형식이 잘못된 경우 + */ +export const responseFileDownload = (response, options = {}) => { + const { defaultFileName = 'download' } = options; + + if (!response.data) { + console.log(response); + throw new Error('No data received'); + } + + const contentType = response.headers['content-type'] || response.headers['Content-Type']; + const contentDisposition = response.headers['content-disposition'] || response.headers['Content-Disposition']; + + // Excel이나 ZIP 파일 형식 검증 + if (!contentType.includes('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') && + !contentType.includes('application/zip')) { + console.log(response); + throw new Error(`잘못된 파일 형식입니다. Content-Type: ${contentType}`); + } + + let fileName = defaultFileName; + let fileExtension = '.xlsx'; + let mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; + + // ZIP 파일 처리 + if (contentType && contentType.includes('application/zip')) { + fileExtension = '.zip'; + mimeType = 'application/zip'; + fileName = `${defaultFileName}_multiple_files`; + } + + // Content-Disposition에서 파일명 추출 + if (contentDisposition) { + const fileNameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/); + if (fileNameMatch && fileNameMatch[1]) { + fileName = decodeURIComponent(fileNameMatch[1].replace(/['"]/g, '')); + } + } else { + fileName = fileName + fileExtension; + } + + const blob = new Blob([response.data], { type: mimeType }); + + // 파일 유효성 검사 + if (blob.size === 0) { + throw new Error('다운로드된 파일이 비어있습니다.'); + } + + if (blob.size < 1024) { + throw new Error('파일 크기가 너무 작습니다. 올바른 Excel 파일이 아닐 수 있습니다.'); + } + + // 파일 다운로드 실행 + const href = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = href; + link.download = fileName; + link.style.display = 'none'; + link.rel = 'noopener noreferrer'; + document.body.appendChild(link); + link.click(); + + // 정리 + link.remove(); + window.URL.revokeObjectURL(href); +};