엑셀 다운로드 기능을 하는 컴포넌트는 하나로 두고 다른 조원들도 자신의 테이블 컬럼을 입력한 컴포넌트를 만들어 공통 컴포넌트에 값을 props로 전달하면 엑셀 다운로드가 되도록 처리하였다.
import React from 'react';
import { Button } from 'react-bootstrap';
import XLSX from 'xlsx-js-style';
import styles from './empDetailInfo.module.css';
const ExcelForm = ({empList}) => {
const excelDown = async () => {
try {
console.log('excelDown 호출');
if (empList.length === 0) {
throw new Error('직원 목록이 비어 있습니다.');
}
// Excel 파일 생성 및 다운로드
const wb = XLSX.utils.book_new();
const headerStyle = {
font: { bold: true, color: { rgb: '000000' }, name: '함초롱바탕', sz: 13 },
fill: { fgColor: { rgb: 'B588F7' } },
alignment: { horizontal: 'center', vertical: 'center' },
border: { left: { style: 'thin', color: { auto: 1 } }, right: { style: 'thin', color: { auto: 1 } }, top: { style: 'thin', color: { auto: 1 } }, bottom: { style: 'thin', color: { auto: 1 } } }
};
const dataStyle = {
font: { color: { rgb: '000000' }, name: '함초롱바탕', sz: 11 },
fill: { fgColor: { rgb: 'FFFFFF' } },
alignment: { horizontal: 'center', vertical: 'center' },
border: { left: { style: 'thin', color: { auto: 1 } }, right: { style: 'thin', color: { auto: 1 } }, top: { style: 'thin', color: { auto: 1 } }, bottom: { style: 'thin', color: { auto: 1 } } }
};
// 열의 폭을 정의
const colWidths = [120 ,80, 120, 80, 80, 130];
// cols 속성을 사용하여 각 열의 폭을 조절
const cols = colWidths.map(width => ({ wpx: width }));
const headerRow = [
{ v: '사원번호', t: 's', s: headerStyle },
{ v: '현황', t: 's', s: headerStyle },
{ v: '사원명', t: 's', s: headerStyle },
{ v: '부서', t: 's', s: headerStyle },
{ v: '직급', t: 's', s: headerStyle },
{ v: '전화번호', t: 's', s: headerStyle },
];
const dataRows = empList.map(emp => [ // 중간에 값이 비어도 스타일 적용
{ v: emp.E_NO || '', t: 's', s: emp.E_NO ? dataStyle : dataStyle }, // 사원번호
{ v: emp.E_STATUS || '', t: 's', s: emp.E_STATUS ? dataStyle : dataStyle }, // 현황
{ v: emp.E_NAME || '', t: 's', s: emp.E_NAME ? dataStyle : dataStyle }, // 사원명
{ v: emp.DEPT_NAME || '', t: 's', s: emp.DEPT_NAME ? dataStyle : dataStyle }, // 부서
{ v: emp.E_RANK || '', t: 's', s: emp.E_RANK ? dataStyle : dataStyle }, // 직급
{ v: emp.E_PHONE || '', t: 's', s: emp.E_PHONE ? dataStyle : dataStyle }, // 전화번호
]);
const rows = [headerRow, ...dataRows];
// 새로운 Sheet 객체 생성
const ws = XLSX.utils.aoa_to_sheet(rows);
// cols 속성 적용
ws['!cols'] = cols;
// workbook에 추가
XLSX.utils.book_append_sheet(wb, ws, '사원 목록');
// 파일 다운로드
XLSX.writeFile(wb, 'employee_list.xlsx');
console.log('Excel 파일 생성 및 다운로드 완료');
} catch (error) {
console.error('Error occurred while downloading Excel', error);
alert('Excel 파일 다운로드 중 오류가 발생했습니다. 다시 시도해주세요.');
}
};
return (
<div>
<button className={styles.empSaveButton4} id="btn_excelDown" onClick={excelDown}>
Excel Download
</button>
</div>
);
};
export default ExcelForm;
공통 컴포넌트와 테이블 컬럼을 담은 컴포넌트가 연결되기는 하였으나, 컬럼 값이 들어간 부분이 제외되고 엑셀이 다운로드 되는 문제가 발생하였다.
import React from 'react';
import styled from 'styled-components';
import XLSX from 'xlsx-js-style';
const StyledButton = styled.button`
margin-bottom: 10px;
padding: 7px 10px;
background-color: #477448;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
box-shadow: 0px 4px 8px rgba(128, 0, 128, 0.2);
transition: box-shadow 0.3s ease, transform 0.3s ease;
white-space: nowrap;
font-size: 0.75rem;
transform: translateX(5%);
&:hover {
transform: translateX(5%) translateY(-3px);
box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.2);
}
`;
const ExcelDownload = ({ data, columns, filename, buttonText, buttonStyle }) => {
const excelDown = async () => {
try {
// 데이터가 비어 있는 지 확인
if (data.length === 0) {
throw new Error('데이터가 비어 있습니다.');
}
// 엑셀 워크북 생성
const wb = XLSX.utils.book_new();
// 엑셀 헤더 스타일 정의
const headerStyle = {
font: { bold: true, color: { rgb: '000000' }, name: '함초롱바탕', sz: 13 },
fill: { fgColor: { rgb: 'B588F7' } },
alignment: { horizontal: 'center', vertical: 'center' },
border: { left: { style: 'thin', color: { auto: 1 } }, right: { style: 'thin', color: { auto: 1 } }, top: { style: 'thin', color: { auto: 1 } }, bottom: { style: 'thin', color: { auto: 1 } } }
};
// 엑셀 데이터 스타일 정의
const dataStyle = {
font: { color: { rgb: '000000' }, name: '함초롱바탕', sz: 11 },
fill: { fgColor: { rgb: 'FFFFFF' } },
alignment: { horizontal: 'center', vertical: 'center' },
border: { left: { style: 'thin', color: { auto: 1 } }, right: { style: 'thin', color: { auto: 1 } }, top: { style: 'thin', color: { auto: 1 } }, bottom: { style: 'thin', color: { auto: 1 } } }
};
// 각 열의 폭 정의
const colWidths = columns.map(width => ({ wpx: width }));
const cols = colWidths.map(width => ({ wpx: width }));
// 헤더 행 생성
const headerRow = columns.map((column, index) => ({ v: column, t: 's', s: headerStyle }));
// 데이터 행 생성
const dataRows = data.map(item => columns.map(column => ({ v: item[column] || '', t: 's', s: item[column] ? dataStyle : dataStyle })));
// 모든 행 결합
const rows = [headerRow, ...dataRows];
const ws = XLSX.utils.aoa_to_sheet(rows);
// 열 너비 설정
ws['!cols'] = cols;
// 워크북에 시트 추가
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
// 파일 다운로드
XLSX.writeFile(wb, filename);
console.log('Excel 파일 생성 및 다운로드 완료');
} catch (error) {
console.error('Excel 파일 다운로드 중 오류 발생', error);
alert('Excel 파일 다운로드 중 오류가 발생했습니다. 다시 시도해주세요.');
}
};
return (
<StyledButton onClick={excelDown}>
{buttonText}
</StyledButton>
);
};
export default ExcelDownload;
import React from 'react';
import ExcelDownload from './ExcelDownload';
const EmpExcelDownload = ({ empList }) => {
console.log("데이터 값: ", empList.toSorted());
const columns = ['사원번호', '현황', '사원명', '부서', '직급', '전화번호'];
const filename = 'employee_list.xlsx';
const buttonText = 'Excel Download';
// 각 데이터의 항목에 접근하여 적절한 값 가져오기
const data = empList.map(emp => ({
'사원번호': emp.E_NO || '',
'현황': emp.E_STATUS || '',
'사원명': emp.E_NAME || '',
'부서': emp.DEPT_NAME || '',
'직급': emp.E_RANK || '',
'전화번호': emp.E_PHONE || '',
}));
return (
<ExcelDownload
data={data}
columns={columns}
filename={filename}
buttonText={buttonText}
buttonStyle={{ backgroundColor: '#4CAF50', border: 'none', color: 'white', padding: '10px 24px', textAlign: 'center', textDecoration: 'none', display: 'inline-block', fontSize: '16px', margin: '4px 2px', cursor: 'pointer' }}
/>
);
};
export default EmpExcelDownload;
ver.1의 문제를 해결하고자 공통 컴포넌트 코드를 수정하였다.
또한 엑셀 다운로드 버튼의 디자인은 통일하되, 각자 사용하는 페이지에서 위치만 조정할 수 있도록 Styled를 사용하였다.
엑셀 다운로드 시, 정상적으로 값이 출력된다.
import React from 'react';
import styled from 'styled-components';
import XLSX from 'xlsx-js-style';
const Button = styled.button`
margin-bottom: 10px;
padding: 7px 10px;
background-color: #477448;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
box-shadow: 0px 4px 8px rgba(128, 0, 128, 0.2);
transition: box-shadow 0.3s ease, transform 0.3s ease;
white-space: nowrap;
font-size: 0.75rem;
&:hover {
transform: translateX(5%) translateY(-3px);
box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.2);
}
`;
const ExcelDownload = ({ title, data, fileName, header, colWidths }) => {
const excelDown = async () => {
try {
// 데이터가 비어 있는 지 확인
if (data.length === 0) {
throw new Error('데이터가 비어 있습니다.');
}
// 엑셀 워크북 생성
const wb = XLSX.utils.book_new();
// 엑셀 헤더 스타일 정의
const headerStyle = {
font: { bold: true, color: { rgb: '000000' }, name: '함초롱바탕', sz: 13 },
fill: { fgColor: { rgb: 'B588F7' } },
alignment: { horizontal: 'center', vertical: 'center' },
border: { left: { style: 'thin', color: { auto: 1 } }, right: { style: 'thin', color: { auto: 1 } }, top: { style: 'thin', color: { auto: 1 } }, bottom: { style: 'thin', color: { auto: 1 } } }
};
// 엑셀 데이터 스타일 정의
const dataStyle = {
font: { color: { rgb: '000000' }, name: '함초롱바탕', sz: 11 },
fill: { fgColor: { rgb: 'FFFFFF' } },
alignment: { horizontal: 'center', vertical: 'center' },
border: { left: { style: 'thin', color: { auto: 1 } }, right: { style: 'thin', color: { auto: 1 } }, top: { style: 'thin', color: { auto: 1 } }, bottom: { style: 'thin', color: { auto: 1 } } }
};
const headerRow = header.map((label, index) => ({ v: label, t: 's', s: headerStyle }));
const dataRows = data.map(row => header.map(label => {
const cellValue = typeof row[label] === 'object' ? Object.values(row[label])[0] : row[label];
return { v: cellValue || '', t: 's', s: dataStyle };
}));
const rows = [headerRow, ...dataRows];
const cols = colWidths.map(width => ({ wpx: width }));
const ws = XLSX.utils.aoa_to_sheet(rows);
ws['!cols'] = cols;
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
XLSX.writeFile(wb, `${fileName}.xlsx`);
} catch (error) {
console.error('엑셀 다운로드 중 오류 발생', error);
alert('엑셀 파일을 다운로드하는 중 오류가 발생했습니다. 다시 시도해주세요.');
}
};
return (
<Button onClick={excelDown}>
Excel Download
</Button>
);
};
export default ExcelDownload;
import React from 'react';
import ExcelDownload from './ExcelDownload';
import styled from 'styled-components';
const EmpContainer = styled.div`
/* 필요 시, 각자 페이지에 맞는 위치 선정하세요. */
`
const EmpExcelDownload = ({ empList }) => {
const header = ['사원번호', '현황', '사원명', '부서', '직급', '전화번호']; // 헤더 정보
// 엑셀 다운로드에 필요한 데이터 준비
const excelData = empList.map(emp => ({
'사원번호': emp.E_NO,
'현황': emp.E_STATUS,
'사원명': emp.E_NAME,
'부서': emp.DEPT_NAME,
'직급': emp.E_RANK,
'전화번호': emp.E_PHONE
}));
return (
<EmpContainer>
<ExcelDownload
header={header} // 헤더 정보
data={excelData} // 데이터
fileName="직원목록" // 엑셀 파일명
colWidths={[120, 80, 120, 80, 80, 130]} // 열의 폭 ==> 각자 정보에 맞게 너비 조정하세요.
/>
</EmpContainer>
);
};
export default EmpExcelDownload;
ver2. 에서 엑셀 다운로드를 받으면 최상단에 title을 붙이고자 하였다. title은 header 바로 위에 셀 병합하여 중앙 정렬되도록 설정하였다.
import React from 'react';
import styled from 'styled-components';
import XLSX from 'xlsx-js-style';
const Button = styled.button`
padding: 7px 10px;
background-color: #477448;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
box-shadow: 0px 4px 8px rgba(128, 0, 128, 0.2);
transition: box-shadow 0.3s ease, transform 0.3s ease;
white-space: nowrap;
font-size: 0.75rem;
&:hover {
transform: translateX(5%) translateY(-3px);
box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.2);
}
`;
const ExcelDownload = ({ title, data, fileName, header, colWidths }) => {
const excelDown = async () => {
try {
// 데이터가 비어 있는 지 확인
if (data.length === 0) {
throw new Error('데이터가 비어 있습니다.');
}
// 엑셀 워크북 생성
const wb = XLSX.utils.book_new();
// 엑셀 헤더 스타일 정의
const headerStyle = {
font: { bold: true, color: { rgb: '000000' }, name: '함초롱바탕', sz: 13 },
fill: { fgColor: { rgb: 'cccccc' } },
alignment: { horizontal: 'center', vertical: 'center' },
border: { left: { style: 'thin', color: { auto: 1 } }, right: { style: 'thin', color: { auto: 1 } }, top: { style: 'thin', color: { auto: 1 } }, bottom: { style: 'thin', color: { auto: 1 } } }
};
// 엑셀 데이터 스타일 정의
const dataStyle = {
font: { color: { rgb: '000000' }, name: '함초롱바탕', sz: 11 },
fill: { fgColor: { rgb: 'FFFFFF' } },
alignment: { horizontal: 'center', vertical: 'center' },
border: { left: { style: 'thin', color: { auto: 1 } }, right: { style: 'thin', color: { auto: 1 } }, top: { style: 'thin', color: { auto: 1 } }, bottom: { style: 'thin', color: { auto: 1 } } }
};
// 엑셀 제목 스타일 정의
const titleStyle = {
font: { bold: true, color: { rgb: '000000' }, name: '함초롱바탕', sz: 15 },
fill: { fgColor: { rgb: 'B588F7' } },
alignment: { horizontal: 'center', vertical: 'center' },
border: { left: { style: 'thin', color: { auto: 1 } }, right: { style: 'thin', color: { auto: 1 } }, top: { style: 'thin', color: { auto: 1 } }, bottom: { style: 'thin', color: { auto: 1 } } }
};
// 엑셀 시트 생성
const ws = XLSX.utils.aoa_to_sheet([]);
// A1 위치에 제목 넣기
ws['A1'] = { v: title, s: titleStyle }; // 제목이 들어갈 셀 좌표 / 제목 값 및 스타일 적용
const titleCellRange = { s: { r: 0, c: 0 }, e: { r: 0, c: header.length - 1 } }; // 제목이 들어갈 셀 범위
ws['!merges'] = [{ s: titleCellRange.s, e: titleCellRange.e }]; // 제목이 들어간 셀 병합
// A2 위치에 헤더 값 넣기
const headerRow = header.map((label, index) => ({ v: label, t: 's', s: headerStyle }));
XLSX.utils.sheet_add_aoa(ws, [headerRow], { origin: 'A2' });
/* const dataRows = data.map(row => header.map(label => {
const cellValue = typeof row[label] === 'object' ? Object.values(row[label])[0] : row[label];
return { v: cellValue || '', t: 's', s: dataStyle };
})); */
// A2 위치에 데이터 값 넣기
const dataRows = data.map(row => header.map(label => ({ v: row[label] || '', t: 's', s: dataStyle })));
XLSX.utils.sheet_add_aoa(ws, dataRows, { origin: 'A3' });
// 열의 폭 설정
const cols = colWidths.map(width => ({ wpx: width }));
//XLSX.utils.sheet_add_aoa(ws, rows);
ws['!cols'] = cols;
// 워크북에 시트 추가
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
// 파일 다운로드
XLSX.writeFile(wb, `${fileName}.xlsx`);
} catch (error) {
console.error('엑셀 다운로드 중 오류 발생', error);
alert('엑셀 파일을 다운로드하는 중 오류가 발생했습니다. 다시 시도해주세요.');
}
};
return (
<Button onClick={excelDown}>
Excel Download
</Button>
);
};
export default ExcelDownload;
ver.2 까지는 개별 컴포넌트에서 header의 값을 excelData에 또 중복 코딩했다면 ver.3에서는 header 정보를 한 번만 선언하고 그 정보를 사용하여 excelData를 구성할 수 있도록 수정하였다. 이를 위해 엑셀 데이터를 구성할 때 헤더 정보를 참조하여 데이터를 매핑하였다.
이렇게 수정하면 헤더 정보와 데이터를 한 번만 선언하여 코드를 더욱 간결하게 만들 수 있다. 또한, ver.3에서는 다른 조원들이 공통 컴포넌트를 사용할 때 좀 더 간결하게 사용할 수 있다.
import React from 'react';
import ExcelDownload from './ExcelDownload';
import styled from 'styled-components';
const EmpContainer = styled.div`
/* ===> 필요 시, 각자 페이지에 맞는 위치 선정하세요. */
`
const EmpExcelDownload = ({ empList }) => { /* ==> 엑셀 다운로드 받을 값 props로 가져오세요. */
const title = '직원 목록'; // 엑셀 내 최상단 제목 ==> 원하는 제목으로 입력하세요.
const header = ['사원번호', '현황', '사원명', '부서', '직급', '전화번호']; // 헤더 정보 ===> 원하는 헤더로 입력하세요.
const fileName = 'Employee List' // 엑셀 파일명 ===> 원하는 엑셀 파일명으로 입력하세요.
// 엑셀 다운로드하고 싶은 데이터 준비
const excelData = empList.map(data => ({ /* ===> props 입력 : '__'.map */
[header[0]]: data.E_NO, /* ===> 다운로드 받고 싶은 컬럼 입력하세요. header[x] : 배열 수 만큼 추가! */
[header[1]]: data.E_STATUS,
[header[2]]: data.E_NAME,
[header[3]]: data.DEPT_NAME,
[header[4]]: data.E_RANK,
[header[5]]: data.E_PHONE
}));
return (
<EmpContainer>
<ExcelDownload
title={title} // 엑셀 내 최상단 제목
header={header} // 헤더 정보
data={excelData} // 데이터
fileName={fileName} // 엑셀 파일명
colWidths={[150, 100, 120, 120, 120, 150]} // 열의 폭 ===> 필요 시, 각자 정보에 맞게 너비 조정하세요.
/>
</EmpContainer>
);
};
export default EmpExcelDownload;
위 코드는 특정 값만 가져오고 싶다면 사용하는 방법이고,
아래 코드는 props로 가져오는 값 전체를 엑셀 다운로드로 출력하고 싶을 때 사용할 수 있는 방법이다.
import React from 'react';
import ExcelDownload from './ExcelDownload';
import styled from 'styled-components';
const EmpContainer = styled.div`
/* 필요 시, 각자 페이지에 맞는 위치 선정하세요. */
`
const EmpExcelDownload = ({ empList }) => {
// 데이터 리스트가 비어있을 경우 빈 배열 반환
if (!empList || !empList.length) return null;
// 데이터의 첫 번째 객체에서 키 값을 가져옴
const keys = Object.keys(empList[0]);
// 엑셀 다운로드에 필요한 데이터 준비
const excelData = empList.map(item => {
const row = {};
keys.forEach(key => {
row[key] = item[key];
});
return row;
});
return (
<EmpContainer>
<ExcelDownload
title="직원목록" // 엑셀 내 최상단 제목
header={keys} // 헤더 정보로 데이터의 키 값을 사용
data={excelData} // 데이터
fileName="직원목록" // 엑셀 파일명
colWidths={[120, 80, 120, 80, 80, 130]} // 열의 폭 ==> 각자 정보에 맞게 너비 조정하세요.
/>
</EmpContainer>
);
};
export default EmpExcelDownload;