Skip to content

Commit

Permalink
add export to pdf with element auto page breaking
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasxsong committed Feb 28, 2024
1 parent 7c1bb81 commit f69d011
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 124 deletions.
11 changes: 3 additions & 8 deletions src/components/reports/DistributionChartFacet.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
<template>
<div class="distribution-wrapper">
<div :id="`roar-distribution-chart-${taskId}`"></div>
<div v-if="minGradeByRuns < 6" class="view-by-wrapper my-2">
<div v-if="minGradeByRuns < 6" class="view-by-wrapper my-2" data-html2canvas-ignore="true">
<div class="flex uppercase text-xs font-light">view scores by</div>
<PvSelectButton
v-model="scoreMode"
:allow-empty="false"
class="flex flex-row my-2 select-button"
:options="scoreModes"
option-label="name"
@change="handleModeChange"
/>
v-model="scoreMode" :allow-empty="false" class="flex flex-row my-2 select-button"
:options="scoreModes" option-label="name" @change="handleModeChange" />
</div>
</div>
</template>
Expand Down
63 changes: 29 additions & 34 deletions src/components/reports/DistributionChartSupport.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
<template>
<div :id="`roar-distribution-chart-support-${taskId}`"></div>
<div class="view-by-wrapper my-2">
<div class="view-by-wrapper my-2" data-html2canvas-ignore="true">
<div class="flex uppercase text-xs font-light">view support levels by</div>
<PvSelectButton
v-model="xMode"
class="flex flex-row my-2 select-button"
:allow-empty="false"
:options="xModes"
option-label="name"
@change="handleXModeChange"
/>
v-model="xMode" class="flex flex-row my-2 select-button" :allow-empty="false" :options="xModes"
option-label="name" @change="handleXModeChange" />
</div>
</template>

Expand Down Expand Up @@ -160,32 +155,32 @@ const distributionBySupport = computed(() => {
sort:
props.facetMode.name === 'Grade'
? [
'Kindergarten',
1,
'1',
2,
'2',
3,
'3',
4,
'4',
5,
'5',
6,
'6',
7,
'7',
8,
'8',
9,
'9',
10,
'10',
11,
'11',
12,
'12',
]
'Kindergarten',
1,
'1',
2,
'2',
3,
'3',
4,
'4',
5,
'5',
6,
'6',
7,
'7',
8,
'8',
9,
'9',
10,
'10',
11,
'11',
12,
'12',
]
: 'ascending',
axis: {
labelAngle: 0,
Expand Down
61 changes: 16 additions & 45 deletions src/components/reports/tasks/TaskReport.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div class="flex flex-col items-center justify-center mx-2">
<Accordion v-if="taskInfoById[taskId]" class="mb-5 w-full">
<div :id="'tab-view-description-'+taskId" class="flex flex-col items-center justify-center mx-2">
<Accordion v-if="taskInfoById[taskId]" class="mb-5 w-full" :active-index="0">
<AccordionTab :header="('About ' + taskInfoById[taskId]?.subheader).toUpperCase()">
<div>
<div style="text-transform: uppercase" class="text-2xl font-bold">{{ taskInfoById[taskId]?.subheader }}</div>
Expand All @@ -16,64 +16,35 @@
</Accordion>
</div>
<!-- <div class="grid grid-cols-2 w-full space-around items-center p-3"> -->
<div v-if="tasksToDisplayGraphs.includes(taskId)" class="chart-toggle-wrapper">
<div v-if="orgType === 'district'" class="mb-3">
<div v-if="tasksToDisplayGraphs.includes(taskId)" :id="'tab-view-chart-' + taskId" class='chart-toggle-wrapper'>
<div v-if="orgType === 'district'" class="mb-3" data-html2canvas-ignore="true">
<div class="flex uppercase text-xs font-light">view rows by</div>
<PvSelectButton
v-model="facetMode"
class="flex flex-row my-2 select-button"
:allow-empty="false"
:options="facetModes"
option-label="name"
/>
v-model="facetMode" class="flex flex-row my-2 select-button" :allow-empty="false"
:options="facetModes" option-label="name" />
</div>
<div class="chart-wrapper align-items-center">
<div class="h-full flex flex-column align-items-center">
<DistributionChartSupport
:initialized="initialized"
:administration-id="administrationId"
:org-type="orgType"
:org-id="orgId"
:task-id="taskId"
:runs="runs"
:facet-mode="facetMode"
/>
:initialized="initialized" :administration-id="administrationId" :org-type="orgType"
:org-id="orgId" :task-id="taskId" :runs="runs" :facet-mode="facetMode" />
</div>
<div class="h-full flex">
<DistributionChartFacet
:initialized="initialized"
:administration-id="administrationId"
:org-type="orgType"
:org-id="orgId"
:task-id="taskId"
:runs="runs"
:facet-mode="facetMode"
:min-grade-by-runs="minGradeByRuns"
/>
:initialized="initialized" :administration-id="administrationId" :org-type="orgType"
:org-id="orgId" :task-id="taskId" :runs="runs" :facet-mode="facetMode" :min-grade-by-runs="minGradeByRuns" />
</div>
</div>
</div>
<div class="my-2 mx-4">
<SubscoreTable
v-if="taskId === 'letter'"
task-id="letter"
:task-name="taskDisplayNames['letter'].name"
:administration-id="administrationId"
:org-type="orgType"
:org-id="orgId"
:administration-name="administrationInfo.name ?? undefined"
:org-name="orgInfo.name ?? undefined"
/>
v-if="taskId === 'letter'" task-id="letter" :task-name="taskDisplayNames['letter'].name"
:administration-id="administrationId" :org-type="orgType" :org-id="orgId"
:administration-name="administrationInfo.name ?? undefined" :org-name="orgInfo.name ?? undefined" />
<SubscoreTable
v-if="taskId === 'pa'"
task-id="pa"
:task-name="taskDisplayNames['pa'].name"
:administration-id="administrationId"
:org-type="orgType"
:org-id="orgId"
:administration-name="administrationInfo.name ?? undefined"
:org-name="orgInfo.name ?? undefined"
/>
v-if="taskId === 'pa'" task-id="pa" :task-name="taskDisplayNames['pa'].name"
:administration-id="administrationId" :org-type="orgType" :org-id="orgId"
:administration-name="administrationInfo.name ?? undefined" :org-name="orgInfo.name ?? undefined" />
</div>
</template>
<script setup>
Expand Down
103 changes: 66 additions & 37 deletions src/pages/ScoreReport.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<div>
<div class="uppercase font-light text-gray-500 text-sm">{{ props.orgType }} Score Report</div>
<div class="report-title">
{{ _toUpper(orgInfo.name) }}
{{ _toUpper(orgInfo?.name) }}
</div>
</div>
<div>
Expand All @@ -25,17 +25,18 @@
<div class="report-subheader mb-3 uppercase text-gray-500 font-normal">Scores at a glance</div>
</div>
<div class="flex flex-column align-items-end gap-2">
<div class="flex flex-row align-items-center gap-4">
<div class="flex flex-row align-items-center gap-4" data-html2canvas-ignore="true">
<div class="uppercase text-sm text-gray-600">VIEW</div>
<PvSelectButton
v-model="reportView" :options="reportViews" option-disabled="constant"
:allow-empty="false" option-label="name" class="flex my-2 select-button" @change="handleViewChange">
</PvSelectButton>
</div>
<div>
<div v-if="!isLoadingRunResults">
<PvButton
class="flex flex-row" :icon="!exportLoading ? 'pi pi-download' : 'pi pi-spin pi-spinner'"
:disabled="exportLoading" label="Export To Pdf" @click="handleExportToPdf" />
:disabled="exportLoading" label="Export To Pdf" data-html2canvas-ignore="true"
@click="handleExportToPdf" />
</div>
</div>
</div>
Expand Down Expand Up @@ -168,7 +169,7 @@ id="view-columns" v-model="viewMode" :options="viewOptions" option-label="label"
<AppSpinner style="margin: 1rem 0rem" />
<div class="uppercase text-sm">Loading Task Reports</div>
</div>
<PvTabView>
<PvTabView :active-index="activeTabIndex" @tab-change="onTabChange">
<PvTabPanel
v-for="taskId of sortedTaskIds" :key="taskId"
:header="taskDisplayNames[taskId]?.name ? ('ROAR-' + taskDisplayNames[taskId]?.name).toUpperCase() : ''">
Expand All @@ -180,7 +181,7 @@ v-if="taskId" :task-id="taskId" :initialized="initialized" :administration-id="a
</div>
</PvTabPanel>
</PvTabView>
<div class="bg-gray-200 px-4 py-2 mt-4">
<div id="score-report-closing" class="bg-gray-200 px-4 py-2 mt-4">
<h2 class="extra-info-title">HOW ROAR SCORES INFORM PLANNING TO PROVIDE SUPPORT</h2>
<p>
Each foundational reading skill is a building block of the subsequent skill. Phonological awareness supports
Expand Down Expand Up @@ -300,46 +301,74 @@ const handleViewChange = () => {

const exportLoading = ref(false);

const activeTabIndex = ref(0)

function onTabChange(event) {
event.preventDefault(); // Prevent the default scroll behavior
// Your tab change logic here
}

const pageWidth = 190; // Set page width for calculations
const returnScaleFactor = (width) => pageWidth / width; // Calculate the scale factor
// Helper function to add an element to a document and perform page break logic
const addElementToPdf = async (element, document, yCounter, offset = 0) => {
await html2canvas(element).then(function (canvas) {
const imgData = canvas.toDataURL('image/jpeg', 0.7, { willReadFrequently: true });
const scaledCanvasHeight = canvas.height * returnScaleFactor(canvas.width);
// Add a new page for each task if there is no more space in the page for task desc and graph
if (yCounter + scaledCanvasHeight + offset > 287) {
document.addPage();
yCounter = 10;
}
else {
// Add Margin
yCounter += 5
}

document.addImage(imgData, 'JPEG', 10, yCounter, pageWidth, scaledCanvasHeight)
yCounter += scaledCanvasHeight;
});
return yCounter;
}

const handleExportToPdf = async () => {
exportLoading.value = true;
console.log("export to pdf called")
exportLoading.value = true; // Set loading icon in button to prevent multiple clicks
const doc = new jsPDF();
let yCounter = 10; // yCounter tracks the y position in the PDF

doc.text(`Score Report for: ${orgInfo?.value.name}`, 10, 10);
doc.text(`Administration: ${administrationInfo?.value.name}`, 10, 20);
// Add At a Glance Charts and report header to the PDF
const atAGlanceCharts = document.getElementById('at-a-glance-charts')
if (atAGlanceCharts !== null) {
yCounter = await addElementToPdf(atAGlanceCharts, doc, yCounter);
}

// Use html2canvas to convert the HTML element to a canvas
await html2canvas(document.getElementById('at-a-glance-charts')).then(function (canvas) {
// Convert the canvas to an image
// Convert the canvas to an image with lower quality
var imgData = canvas.toDataURL('image/jpeg', 0.7); // Lower quality
// Initialize to first tab
activeTabIndex.value = 0;

// Add the image to the PDF
doc.addImage(imgData, 'JPEG', 10, 30, 200, 100); // adjust the coordinates and size as needed
});
for (const [i, taskId] of sortedTaskIds.value.entries()) {
activeTabIndex.value = i;
await new Promise(resolve => setTimeout(resolve, 500));

// let yCoord = 60;
for (const taskId of sortedTaskIds.value) {
console.log("taskid", taskId)
await html2canvas(document.getElementById('tab-view-' + taskId), {
onclone: function (clonedDoc) {
// Append the hidden elements to the cloned document
clonedDoc.body.appendChild(clonedDoc);
}
}).then(function (canvas) {
doc.addPage();
// Convert the canvas to an image
// Convert the canvas to an image with lower quality
var imgData = canvas.toDataURL('image/jpeg', 0.7); // Lower quality
// Add the image to the PDF
doc.addImage(imgData, 'JPEG', 10, 10, 200, 100); // adjust the coordinates and size as needed
});
// yCoord += 30
// Add Task Description and Task Chart to document
const tabViewDesc = document.getElementById('tab-view-description-' + taskId)
const tabViewChart = document.getElementById('tab-view-chart-' + taskId)
const chartHeight = tabViewChart && await html2canvas(document.getElementById('tab-view-chart-' + taskId)).then((canvas) => canvas.height * returnScaleFactor(canvas.width));

if (tabViewDesc !== null) {
yCounter = await addElementToPdf(tabViewDesc, doc, yCounter, chartHeight);
}
if (tabViewChart !== null) {
yCounter = await addElementToPdf(tabViewChart, doc, yCounter);
}
}

// Add Report Closing
const closing = document.getElementById('score-report-closing');
if (closing !== null) {
yCounter = await addElementToPdf(closing, doc, yCounter);
}

doc.save('score_report.pdf');
doc.save(`roar-scores-${_kebabCase(administrationInfo.value.name)}-${_kebabCase(orgInfo.value.name)}.pdf`)
exportLoading.value = false;

return;
Expand Down

0 comments on commit f69d011

Please sign in to comment.