• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CBIIT / crdc-datahub-ui / 11668452957

04 Nov 2024 04:28PM UTC coverage: 53.886% (+0.009%) from 53.877%
11668452957

push

github

web-flow
Merge pull request #522 from CBIIT/3.1.0

Sync 3.2.0 with 3.1.0

2590 of 5301 branches covered (48.86%)

Branch coverage included in aggregate %.

10 of 30 new or added lines in 4 files covered. (33.33%)

1 existing line in 1 file now uncovered.

3713 of 6396 relevant lines covered (58.05%)

133.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/components/ExportRequestButton/pdf/Generate.ts
1
import { jsPDF as JsPDF } from "jspdf";
2
import { loadFont } from "./utils";
3
import fonts from "./Fonts";
4
import { FormatDate, formatFullStudyName } from "../../../utils";
5
import env from "../../../env";
6
import Logo from "../../../assets/header/Logo.jpg";
7
import {
8
  COLOR_HR,
9
  COLOR_FIELD_BASE,
10
  COLOR_EMAIL,
11
  COLOR_BASE,
12
  COLOR_HYPERLINK,
13
  COLOR_DOC_FOOTER,
14
} from "./Colors";
15

16
/**
17
 * Describes the minimal padding from the edges of the document.
18
 *
19
 * @note No content should be placed from the document edge to this margin.
20
 */
21
const BASE_MARGIN = 20;
×
22

23
/**
24
 * Describes the margin around the content of the document.
25
 *
26
 * @note The design spec suggests it should be closer to +50p,
27
 * but this is a compromise to ensure the content fits within the page.
28
 * @note Any horizontal lines bypass this constraint.
29
 */
30
const CONTENT_MARGIN = BASE_MARGIN + 25;
×
31

32
/**
33
 * Describes the drawing width of horizontal lines.
34
 */
35
const HR_WIDTH = 0.8;
×
36

37
/**
38
 * Writes the PDF header to the document.
39
 *
40
 * @param doc The jsPDF instance to add the header to.
41
 * @param request The submission request to generate the header from.
42
 * @returns {number} The current Y position after writing the header including padding.
43
 */
44
const writeHeader = (doc: JsPDF, request: Application): number => {
×
45
  const { applicant, questionnaireData, status, submittedDate } = request;
×
46
  const { study } = questionnaireData || {};
×
47
  const formattedSubmittedDate =
48
    status !== "In Progress" ? FormatDate(submittedDate, "YYYY-MM-DD") : "N/A";
×
49

50
  const RIGHT_EDGE = doc.internal.pageSize.width - BASE_MARGIN;
×
51
  let y = BASE_MARGIN;
×
52

53
  doc.addImage(Logo, "JPEG", BASE_MARGIN, y, 278, 56);
×
54
  y += 63;
×
55

56
  // Submitter name
57
  doc.setDrawColor(...COLOR_HR);
×
58
  doc.setLineWidth(HR_WIDTH);
×
59
  doc.line(BASE_MARGIN, (y += 15), RIGHT_EDGE, y);
×
60
  doc.setFont("Nunito", "normal", 800);
×
61
  doc.setTextColor(...COLOR_FIELD_BASE);
×
62
  doc.setFontSize(10);
×
63
  doc.text("SUBMITTER'S NAME", CONTENT_MARGIN, (y += 15));
×
64
  doc.setFont("Nunito", "normal", 700);
×
65
  doc.setFontSize(14);
×
66
  doc.setTextColor(...COLOR_EMAIL);
×
67
  doc.text(applicant.applicantName, CONTENT_MARGIN + 85, (y += 1));
×
68
  doc.line(BASE_MARGIN, (y += 10), RIGHT_EDGE, y);
×
69
  y += 24;
×
70

71
  // Study name and abbreviation
72
  doc.setFont("Nunito", "normal", 600);
×
73
  doc.setTextColor(...COLOR_BASE);
×
74
  doc.setFontSize(10);
×
75
  doc.text("STUDY NAME", CONTENT_MARGIN, y);
×
76

77
  const formattedName = formatFullStudyName(study?.name, study?.abbreviation);
×
NEW
78
  const maxNameWidth = RIGHT_EDGE - CONTENT_MARGIN - 85;
×
79
  doc.setFont("Nunito", "normal", 400);
×
80
  doc.setFontSize(12);
×
81
  doc.setTextColor(...COLOR_FIELD_BASE);
×
NEW
82
  doc.text(formattedName, CONTENT_MARGIN + 85, y, {
×
83
    // NOTE – Take the full width of the page and subtract the margins/offsets
84
    maxWidth: maxNameWidth,
85
  });
86

NEW
87
  const computedNameLines = doc.splitTextToSize(formattedName, maxNameWidth);
×
NEW
88
  y += (computedNameLines?.length || 1) * 8 + 11;
×
89

90
  // Submitted Date
91
  doc.setFont("Nunito", "normal", 600);
×
92
  doc.setTextColor(...COLOR_BASE);
×
93
  doc.setFontSize(10);
×
94
  doc.text("SUBMITTED DATE", CONTENT_MARGIN, y);
×
95

96
  doc.setFont("Nunito", "normal", 400);
×
97
  doc.setFontSize(12);
×
98
  doc.setTextColor(...COLOR_FIELD_BASE);
×
99
  doc.text(formattedSubmittedDate, CONTENT_MARGIN + 85, y);
×
100
  y += 18;
×
101

102
  // Status
103
  doc.setFont("Nunito", "normal", 600);
×
104
  doc.setTextColor(...COLOR_BASE);
×
105
  doc.setFontSize(10);
×
106
  doc.text("STATUS", CONTENT_MARGIN, y);
×
107

108
  doc.setFont("Nunito", "normal", 400);
×
109
  doc.setFontSize(12);
×
110
  doc.setTextColor(...COLOR_FIELD_BASE);
×
111
  doc.text(status, CONTENT_MARGIN + 85, y);
×
112
  y += 24;
×
113

114
  // Click to view (text)
115
  doc.setFont("Nunito", "normal", 400);
×
116
  doc.setFontSize(12);
×
117
  doc.setTextColor(...COLOR_FIELD_BASE);
×
118
  doc.text("Click here to view the Submission Request Form", CONTENT_MARGIN, y, {
×
119
    align: "left",
120
  });
121

122
  // Click to view (overlapping link)
123
  doc.setTextColor(...COLOR_HYPERLINK);
×
124
  doc.textWithLink("here", CONTENT_MARGIN + 22, y, {
×
125
    url: `${env.REACT_APP_NIH_REDIRECT_URL}/submission/${request._id}`,
126
    align: "left",
127
  });
128

129
  doc.setDrawColor(...COLOR_HYPERLINK);
×
130
  doc.setLineWidth(HR_WIDTH);
×
131
  doc.line(CONTENT_MARGIN + 22, (y += 2), CONTENT_MARGIN + 40, y);
×
132

133
  doc.setDrawColor(...COLOR_HR);
×
134
  doc.line(BASE_MARGIN, (y += 22), RIGHT_EDGE, y);
×
135

136
  return y + 30;
×
137
};
138

139
/**
140
 * A function to write a footer across all pages in the PDF document.
141
 *
142
 * @param doc The jsPDF instance to add the footer to.
143
 * @returns {void}
144
 */
145
const writeFooters = (doc: JsPDF): void => {
×
146
  const pdfRightEdge = doc.internal.pageSize.width - BASE_MARGIN;
×
147
  const bottomMargin = doc.internal.pageSize.height - BASE_MARGIN;
×
148
  const pageCount: number = doc.internal.pages.filter((p) => p !== null).length;
×
149

150
  for (let pageNum = 0; pageNum < pageCount; pageNum += 1) {
×
151
    doc.setPage(pageNum + 1);
×
152

153
    // Draw the horizontal line 20px from the bottom
154
    doc.setDrawColor(...COLOR_HR);
×
155
    doc.setLineWidth(HR_WIDTH);
×
156
    doc.line(
×
157
      BASE_MARGIN,
158
      bottomMargin - 20,
159
      doc.internal.pageSize.width - BASE_MARGIN,
160
      bottomMargin - 20
161
    );
162

163
    // Write the page number
164
    doc.setFontSize(10);
×
165
    doc.setTextColor(...COLOR_DOC_FOOTER);
×
166
    doc.setFont("Nunito", "normal", 600);
×
167
    doc.text(`Page ${pageNum + 1} of ${pageCount}`, pdfRightEdge, bottomMargin, {
×
168
      align: "right",
169
    });
170
  }
171
};
172

173
/**
174
 * A function to generate a PDF document from a submission request.
175
 *
176
 * @note This function temporarily modifies the width of the print region to ensure the
177
 *  content fits within the page.
178
 * @param request The submission request to generate a PDF from.
179
 * @param printRegion The region to print to the PDF.
180
 * @returns {Promise<URL>} A promise that resolves when the PDF is generated.
181
 * @throws {Error} If the submission request is invalid.
182
 */
183
export const GenerateDocument = async (
×
184
  request: Application,
185
  printRegion: HTMLElement
186
): Promise<Blob> => {
187
  if (!request || !request?._id) {
×
188
    throw new Error("Invalid submission request provided.");
×
189
  }
190
  if (!printRegion || !(printRegion instanceof Element)) {
×
191
    throw new Error("Invalid print region provided.");
×
192
  }
193

194
  const doc = new JsPDF({
×
195
    orientation: "portrait",
196
    unit: "px",
197
    format: "letter",
198
  });
199

200
  doc.setProperties({
×
201
    title: `Submission Request ${request._id}`,
202
    subject: `PDF Export of Submission Request ${request._id}`,
203
    keywords: "CRDC, submission request, PDF export",
204
    author: "CRDC Submission Portal",
205
    creator: "crdc-datahub-ui",
206
  });
207

208
  await Promise.allSettled(fonts.map((font) => loadFont(doc, font)));
×
209

210
  return new Promise((resolve) => {
×
211
    const y = writeHeader(doc, request);
×
212

213
    // NOTE: This fixes the width of the form to prevent the form from being too wide in the PDF
214
    // html2canvas width/windowWidth props do not work as expected
215
    printRegion.style.width = "794px";
×
216

217
    doc.html(printRegion, {
×
218
      callback: (doc) => {
219
        printRegion.style.width = "";
×
220

221
        writeFooters(doc);
×
222
        resolve(doc.output("blob"));
×
223
      },
224
      html2canvas: {
225
        scale: 0.5,
226
        ignoreElements: (element: HTMLElement) => {
227
          if (element?.getAttribute("data-print") === "false") {
×
228
            return true;
×
229
          }
230

231
          return false;
×
232
        },
233
        logging: false,
234
      },
235
      autoPaging: "text",
236
      x: 0,
237
      y: y - 25,
238
      margin: [BASE_MARGIN, BASE_MARGIN + 10, 50, BASE_MARGIN + 10],
239
    });
240
  });
241
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc