html2canvas는 컴포넌트(html 요소)를 이미지로 캡쳐하는 라이브러리입니다.
jspdf 캡처한 이미지 파일을 pdf 형태로 다운로드 가능하게 해주는 라이브러리 입니다.
npm install html2canvas jspdf
html2canvas, jspdf는 페이지 나누기 기능이 따로 없습니다.jspdf는 출력물의 용지 사이즈(A4 등), 마진, 가로, 높이를 설정할 수 있습니다. 따라서 원하는 결과물에 맞춰서 적절하게 크기를 설정합니다.align-item : stretch와 유사한 결과) 이를 방지하기 위해 적용합니다.
import { useRecoilValue } from "recoil"
import { appTestEnrollState, middleType } from "./store/stores"
import { AlertComponent, Button, H1, H2, SelectBox, Wrapper } from "./common/Style"
import styled from "styled-components"
import { reportTypeOption } from "./common/selectBoxOptions"
import i18next, { t } from "i18next"
import { useEffect, useRef, useState } from "react"
import html2canvas from "html2canvas"
import jsPDF from "jspdf"
// (이하 import 생략)
// (컴포넌트 스타일 적용 생략)
export default function CompatiFuncTestReport({setModalOpen}: {[key : string] : Function}) {
const tableRows = Object.keys(scoresByCombination).map((combinationKey) => {
const { questionStr, 0: score1 = 0, 1: score2 = 0, 2: score3 = 0, 3: score4 = 0, 4: score5 = 0 } = scoresByCombination[combinationKey];
return (
<Tr key={combinationKey}>
<Td style={{ width: "390px" }}>{t(questionStr!)}</Td>
<Td>{score1}</Td>
<Td>{score2}</Td>
<Td>{score3}</Td>
<Td>{score4}</Td>
<Td>{score5}</Td>
</Tr>
);
});
/* 결과 리포트 pdf 파일로 저장 */
const printToPDF = async () => {
const pdf = new jsPDF("p", "mm", "a4");
const addPageContent = async (element : any) => {
const canvas = await html2canvas(element);
const imgData = canvas.toDataURL("image/jpeg");
pdf.addImage(imgData, "JPEG", 10, 10, 190, 277);
pdf.addPage();
};
const reportContents = [
document.getElementById("report-contents-1"),
document.getElementById("report-contents-2"),
document.getElementById("report-contents-3"),
document.getElementById("report-contents-4"),
];
for (const reportContent of reportContents) {
if (reportContent) {
await addPageContent(reportContent);
}
}
pdf.save("test_report.pdf");
};
const nowDate = getDateHourMinuteSecond(appTestInfo.test_endtime)?.substring(0,11)
const tableRowsFirst = tableRows.slice(0, 5);
const tableRowsSecond = tableRows.slice(5, 28);
const tableRowsThird = tableRows.slice(28, 51);
const tableRowsFourth = tableRows.slice(51);
return(
<ModalBackground>
<ModalComponent>
...
<TestReportContents id ="report-contents-1">
<TestSummaryWrapper>
<TestSummaryItem>
<p>Test App</p>
<p>{appTestInfo.tname}</p>
</TestSummaryItem>
<TestSummaryItem>
<p>Test Period</p>
<p>{nowDate}</p>
</TestSummaryItem>
<TestSummaryItem>
<p>No.of Tester</p>
<p>{data?.report_mchoice_list?.length}</p>
</TestSummaryItem>
<TestSummaryItem>
<p>Male / Female</p>
<p>{maleCount} / {femaleCount}</p>
</TestSummaryItem>
</TestSummaryWrapper>
<DeviceInfo>
<DeviceInfoContainer>
<DonutChart data={deviceInfo} type ="device_manuf"/>
<DonutChart data={deviceInfo} type= "device_os"/>
<DonutChart data={deviceInfo} type= "device_memory"/>
</DeviceInfoContainer>
</DeviceInfo>
<Detail>
<H2 style={{fontSize : "20px", fontWeight : 700, marginTop : "24px"}}>{t('상세 결과')}</H2>
<DetailResultWrapper>
<DetailDonutChart data={result}/>
<div style={{marginTop : "87px",
marginLeft : "100px",
display : "flex",
flexDirection : "column",
gap : "20px"}}>
<p>{participantString} {totalQuestionString}
</p>
<Wrapper display="flex" gap={10}>
<Wrapper>
<ResultStatusText>{t('정상(Pass)')}</ResultStatusText>
<ResultStatusText style={{backgroundColor : "#EAF2FF",
borderRadius: "0px 0px 10px 10px"}}>
{result.pass} {t('건')}
</ResultStatusText>
</Wrapper>
<Wrapper>
<ResultStatusText>{t('에러(Fail)')}</ResultStatusText>
<ResultStatusText style={{backgroundColor : "#EAF2FF",
borderRadius: "0px 0px 10px 10px"}}>
{result.fail} {t('건')}
</ResultStatusText>
</Wrapper>
<Wrapper>
<ResultStatusText>{t('확인불가(N/A)')}</ResultStatusText>
<ResultStatusText style={{backgroundColor : "#EAF2FF",
borderRadius: "0px 0px 10px 10px"}}>
{result.notApplicable} {t('건')}
</ResultStatusText>
</Wrapper>
</Wrapper>
<p>{t('이슈가 확인 됩니다.')}</p>
</div>
</DetailResultWrapper>
</Detail>
<DetailTable style={{ marginBottom: !tableRowsSecond.length ? `${1200 - 36 * tableRowsFirst.length}px` :"" }}>
<Tr>
<Th style={{width : "390px"}}>{t('문항')}</Th>
<Th>{t('확인불가')}</Th>
<Th>{t('발생하지 않음')}</Th>
<Th>{t('드물게 발생')}</Th>
<Th>{t('빈번하게 발생')}</Th>
<Th>{t('100% 발생')}</Th>
</Tr>
{tableRowsFirst}
</DetailTable>
</TestReportContents>
{tableRowsSecond.length > 0 &&
<TestReportContents id ="report-contents-2">
<DetailTable style={{ marginBottom: !tableRowsThird.length ? `${1200 - 36 * tableRowsSecond.length}px` :"" }}>
{tableRowsSecond}
</DetailTable>
</TestReportContents>
}
{tableRowsThird.length > 0 && (
<TestReportContents id="report-contents-3">
<DetailTable style={{ marginBottom: !tableRowsFourth.length ? `${1200 - 36 * tableRowsThird.length}px` :"" }}>
{tableRowsThird}
</DetailTable>
</TestReportContents>
)}
{tableRowsFourth.length > 0 && (
<TestReportContents id="report-contents-4">
<DetailTable style={{ marginBottom: `${1200 - 36 * tableRowsFourth.length}px` }}>
{tableRowsFourth}
</DetailTable>
</TestReportContents>
)}
</ModalComponent>
{isPopupOpen && <AlertComponent message={t('데이터 조회에 실패하였습니다.')} popupClose = {setIsPopupOpen} callback = {handleRefetch} buttonText={t('다시 조회하기')}/>}
</ModalBackground>
)
}