@measurequick/measurequick-report-generator 1.5.190 → 1.5.191
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,490 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import fontkit from '@pdf-lib/fontkit';
|
|
3
|
-
import * as base64 from '../base-64/icons.js';
|
|
4
|
-
import * as util from '../util.js';
|
|
5
|
-
|
|
6
|
-
// Check if project is CoolMaxx or HeatMaxx (NCI branded)
|
|
7
|
-
function isNCIProject(payload) {
|
|
8
|
-
if (payload && payload.project && payload.project.specialProjectType) {
|
|
9
|
-
const type = payload.project.specialProjectType;
|
|
10
|
-
return type === 'CoolMaxx' || type === 'HeatMaxx';
|
|
11
|
-
}
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Color definitions matching MeasureQuick brand
|
|
16
|
-
const colors = {
|
|
17
|
-
headerBg: rgb(0.18, 0.31, 0.47), // Dark blue header
|
|
18
|
-
scoreGreen: rgb(0.18, 0.62, 0.35), // Green for A grades
|
|
19
|
-
scoreYellow: rgb(0.91, 0.73, 0.18), // Yellow for B grades
|
|
20
|
-
scoreOrange: rgb(0.91, 0.49, 0.18), // Orange for C grades
|
|
21
|
-
scoreRed: rgb(0.78, 0.22, 0.22), // Red for D/F grades
|
|
22
|
-
lightGray: rgb(0.95, 0.95, 0.95), // Light gray background
|
|
23
|
-
mediumGray: rgb(0.7, 0.7, 0.7), // Medium gray for borders
|
|
24
|
-
darkGray: rgb(0.3, 0.3, 0.3), // Dark gray for text
|
|
25
|
-
black: rgb(0, 0, 0),
|
|
26
|
-
white: rgb(1, 1, 1)
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
function getScoreColor(grade) {
|
|
30
|
-
if (grade.includes('A')) return colors.scoreGreen;
|
|
31
|
-
if (grade.includes('B')) return colors.scoreYellow;
|
|
32
|
-
if (grade.includes('C')) return colors.scoreOrange;
|
|
33
|
-
return colors.scoreRed;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Wrap text to fit within a given width
|
|
37
|
-
function wrapText(text, font, fontSize, maxWidth) {
|
|
38
|
-
if (!text) return [];
|
|
39
|
-
const words = text.split(' ');
|
|
40
|
-
const lines = [];
|
|
41
|
-
let currentLine = '';
|
|
42
|
-
|
|
43
|
-
for (const word of words) {
|
|
44
|
-
const testLine = currentLine ? currentLine + ' ' + word : word;
|
|
45
|
-
const testWidth = font.widthOfTextAtSize(testLine, fontSize);
|
|
46
|
-
|
|
47
|
-
if (testWidth > maxWidth && currentLine) {
|
|
48
|
-
lines.push(currentLine);
|
|
49
|
-
currentLine = word;
|
|
50
|
-
} else {
|
|
51
|
-
currentLine = testLine;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (currentLine) {
|
|
56
|
-
lines.push(currentLine);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return lines;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Handle newlines and wrap text
|
|
63
|
-
function wrapTextWithNewlines(text, font, fontSize, maxWidth) {
|
|
64
|
-
if (!text) return [];
|
|
65
|
-
const paragraphs = text.split('\n');
|
|
66
|
-
const allLines = [];
|
|
67
|
-
|
|
68
|
-
for (const paragraph of paragraphs) {
|
|
69
|
-
if (paragraph.trim() === '') {
|
|
70
|
-
allLines.push('');
|
|
71
|
-
} else {
|
|
72
|
-
const wrappedLines = wrapText(paragraph, font, fontSize, maxWidth);
|
|
73
|
-
allLines.push(...wrappedLines);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return allLines;
|
|
78
|
-
}
|
|
1
|
+
import * as notesSummaryPage from './notes-summary-page.js';
|
|
79
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Generate standalone AI Summary & Notes report using the notes-summary template
|
|
5
|
+
* @param {Object} payload - The report payload
|
|
6
|
+
* @returns {Promise<{status: number, data: string}>}
|
|
7
|
+
*/
|
|
80
8
|
export async function getReport(payload) {
|
|
81
|
-
|
|
82
|
-
// Create a new PDF document
|
|
83
|
-
const pdfDoc = await PDFDocument.create();
|
|
84
|
-
pdfDoc.registerFontkit(fontkit);
|
|
85
|
-
|
|
86
|
-
// Embed fonts
|
|
87
|
-
const fontRegular = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
|
88
|
-
const fontBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
89
|
-
|
|
90
|
-
// Page dimensions (Letter size: 612 x 792 points)
|
|
91
|
-
const pageWidth = 612;
|
|
92
|
-
const pageHeight = 792;
|
|
93
|
-
const margin = 36; // 0.5 inch margins
|
|
94
|
-
const contentWidth = pageWidth - (margin * 2);
|
|
95
|
-
|
|
96
|
-
// Create first page
|
|
97
|
-
let page = pdfDoc.addPage([pageWidth, pageHeight]);
|
|
98
|
-
|
|
99
|
-
// Load mQ logo
|
|
100
|
-
const mqLogoBytes = util._base64ToArrayBuffer(base64.mqLogo);
|
|
101
|
-
const mqLogo = await pdfDoc.embedPng(mqLogoBytes);
|
|
102
|
-
|
|
103
|
-
// Load NCI logo if applicable
|
|
104
|
-
let nciLogo = null;
|
|
105
|
-
const isNCI = isNCIProject(payload) && base64.nciLogo;
|
|
106
|
-
if (isNCI) {
|
|
107
|
-
const nciLogoBytes = util._base64ToArrayBuffer(base64.nciLogo);
|
|
108
|
-
nciLogo = await pdfDoc.embedPng(nciLogoBytes);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Load company logo and profile picture
|
|
112
|
-
let profileImage = null, companyImage = null;
|
|
113
|
-
const profileImageType = util.checkProfilePicture(payload.meta);
|
|
114
|
-
const companyImageType = util.checkCompanyLogo(payload.meta);
|
|
115
|
-
|
|
116
|
-
if (profileImageType) {
|
|
117
|
-
let profileData;
|
|
118
|
-
if (profileImageType.shape === 'square') {
|
|
119
|
-
profileData = payload.meta.profile_settings.profilePicSquareBase64;
|
|
120
|
-
} else if (profileImageType.shape === 'circle') {
|
|
121
|
-
profileData = payload.meta.profile_settings.profilePicCircleBase64;
|
|
122
|
-
}
|
|
123
|
-
if (profileData) {
|
|
124
|
-
if (profileImageType.type === 'jpg') {
|
|
125
|
-
profileImage = await pdfDoc.embedJpg(util._base64ToArrayBuffer(profileData));
|
|
126
|
-
} else if (profileImageType.type === 'png') {
|
|
127
|
-
profileImage = await pdfDoc.embedPng(util._base64ToArrayBuffer(profileData));
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (companyImageType === 'jpg') {
|
|
133
|
-
companyImage = await pdfDoc.embedJpg(util._base64ToArrayBuffer(payload.meta.profile_settings.companyLogoBase64));
|
|
134
|
-
} else if (companyImageType === 'png') {
|
|
135
|
-
companyImage = await pdfDoc.embedPng(util._base64ToArrayBuffer(payload.meta.profile_settings.companyLogoBase64));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
let yPos = pageHeight - margin;
|
|
139
|
-
|
|
140
|
-
// === HEADER SECTION ===
|
|
141
|
-
const headerHeight = 50;
|
|
142
|
-
page.drawRectangle({
|
|
143
|
-
x: margin,
|
|
144
|
-
y: yPos - headerHeight,
|
|
145
|
-
width: contentWidth,
|
|
146
|
-
height: headerHeight,
|
|
147
|
-
color: colors.headerBg
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// Header title
|
|
151
|
-
page.drawText('mQ AI Summary & Notes', {
|
|
152
|
-
x: margin + 15,
|
|
153
|
-
y: yPos - 32,
|
|
154
|
-
size: 18,
|
|
155
|
-
font: fontBold,
|
|
156
|
-
color: colors.white
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// Draw logo in header (right side)
|
|
160
|
-
const logoToUse = isNCI ? nciLogo : (companyImage || mqLogo);
|
|
161
|
-
if (logoToUse) {
|
|
162
|
-
const logoHeight = 35;
|
|
163
|
-
const logoWidth = logoHeight * (logoToUse.width / logoToUse.height);
|
|
164
|
-
page.drawImage(logoToUse, {
|
|
165
|
-
x: pageWidth - margin - logoWidth - 10,
|
|
166
|
-
y: yPos - headerHeight + 7,
|
|
167
|
-
width: logoWidth,
|
|
168
|
-
height: logoHeight
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
yPos -= headerHeight + 15;
|
|
173
|
-
|
|
174
|
-
// === CUSTOMER INFO SECTION ===
|
|
175
|
-
const customerName = payload.project && payload.project.customer_name ? payload.project.customer_name : '';
|
|
176
|
-
const address1 = payload.project && payload.project.address ? payload.project.address : '';
|
|
177
|
-
const city = payload.project && payload.project.city ? payload.project.city : '';
|
|
178
|
-
const state = payload.project && payload.project.state ? payload.project.state : '';
|
|
179
|
-
const zip = payload.project && payload.project.zip ? payload.project.zip : '';
|
|
180
|
-
const cityStateZip = [city, state, zip].filter(x => x).join(', ');
|
|
181
|
-
|
|
182
|
-
if (customerName) {
|
|
183
|
-
page.drawText(customerName, { x: margin, y: yPos, size: 11, font: fontBold, color: colors.darkGray });
|
|
184
|
-
yPos -= 14;
|
|
185
|
-
}
|
|
186
|
-
if (address1) {
|
|
187
|
-
page.drawText(address1, { x: margin, y: yPos, size: 10, font: fontRegular, color: colors.darkGray });
|
|
188
|
-
yPos -= 12;
|
|
189
|
-
}
|
|
190
|
-
if (cityStateZip) {
|
|
191
|
-
page.drawText(cityStateZip, { x: margin, y: yPos, size: 10, font: fontRegular, color: colors.darkGray });
|
|
192
|
-
yPos -= 12;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Date of service
|
|
196
|
-
let dateStr = '';
|
|
197
|
-
if (payload.tests && payload.tests[0] && payload.tests[0].testInfo && payload.tests[0].testInfo.data && payload.tests[0].testInfo.data.datetimeStart) {
|
|
198
|
-
const date = new Date(payload.tests[0].testInfo.data.datetimeStart);
|
|
199
|
-
dateStr = date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
|
|
200
|
-
}
|
|
201
|
-
if (dateStr) {
|
|
202
|
-
page.drawText('Date: ' + dateStr, { x: margin, y: yPos, size: 10, font: fontRegular, color: colors.darkGray });
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Tech name (right aligned)
|
|
206
|
-
const techFirstName = payload.meta && payload.meta.profile_settings && payload.meta.profile_settings.techFirstName ? payload.meta.profile_settings.techFirstName : '';
|
|
207
|
-
const techLastName = payload.meta && payload.meta.profile_settings && payload.meta.profile_settings.techLastName ? payload.meta.profile_settings.techLastName : '';
|
|
208
|
-
const techName = [techFirstName, techLastName].filter(x => x).join(' ');
|
|
209
|
-
if (techName) {
|
|
210
|
-
const techText = 'Technician: ' + techName;
|
|
211
|
-
const techWidth = fontRegular.widthOfTextAtSize(techText, 10);
|
|
212
|
-
page.drawText(techText, { x: pageWidth - margin - techWidth, y: yPos, size: 10, font: fontRegular, color: colors.darkGray });
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
yPos -= 25;
|
|
216
|
-
|
|
217
|
-
// === VITALS SCORES SECTION ===
|
|
218
|
-
const hasTest1 = payload.tests && payload.tests[0] && payload.tests[0].testInfo && payload.tests[0].testInfo.data;
|
|
219
|
-
const hasTest2 = payload.tests && payload.tests[1] && payload.tests[1].testInfo && payload.tests[1].testInfo.data;
|
|
220
|
-
const hasTwoTests = hasTest1 && hasTest2;
|
|
221
|
-
|
|
222
|
-
const scoreBoxWidth = hasTwoTests ? (contentWidth - 20) / 2 : contentWidth;
|
|
223
|
-
const scoreBoxHeight = 60;
|
|
224
|
-
|
|
225
|
-
// Draw score box(es)
|
|
226
|
-
if (hasTest1) {
|
|
227
|
-
const score1 = payload.tests[0].testInfo.data.vitals_score;
|
|
228
|
-
const scorePercent1 = score1 ? Math.round(score1) + '%' : '--';
|
|
229
|
-
const grade1 = score1 ? util.getGradeFromScore(Math.round(score1)) : '';
|
|
230
|
-
const scoreColor1 = score1 ? getScoreColor(grade1) : colors.mediumGray;
|
|
231
|
-
const scoreLabel1 = hasTwoTests ? 'Test In Score' : 'System Vitals Score';
|
|
232
|
-
|
|
233
|
-
// Score box background
|
|
234
|
-
page.drawRectangle({
|
|
235
|
-
x: margin,
|
|
236
|
-
y: yPos - scoreBoxHeight,
|
|
237
|
-
width: scoreBoxWidth,
|
|
238
|
-
height: scoreBoxHeight,
|
|
239
|
-
color: colors.lightGray,
|
|
240
|
-
borderColor: scoreColor1,
|
|
241
|
-
borderWidth: 2
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
// Score label
|
|
245
|
-
page.drawText(scoreLabel1, {
|
|
246
|
-
x: margin + 10,
|
|
247
|
-
y: yPos - 18,
|
|
248
|
-
size: 10,
|
|
249
|
-
font: fontRegular,
|
|
250
|
-
color: colors.darkGray
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
// Score value
|
|
254
|
-
const scoreText1 = scorePercent1 + (grade1 ? ' ' + grade1 : '');
|
|
255
|
-
page.drawText(scoreText1, {
|
|
256
|
-
x: margin + 10,
|
|
257
|
-
y: yPos - 45,
|
|
258
|
-
size: 24,
|
|
259
|
-
font: fontBold,
|
|
260
|
-
color: scoreColor1
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (hasTwoTests) {
|
|
265
|
-
const score2 = payload.tests[1].testInfo.data.vitals_score;
|
|
266
|
-
const scorePercent2 = score2 ? Math.round(score2) + '%' : '--';
|
|
267
|
-
const grade2 = score2 ? util.getGradeFromScore(Math.round(score2)) : '';
|
|
268
|
-
const scoreColor2 = score2 ? getScoreColor(grade2) : colors.mediumGray;
|
|
269
|
-
|
|
270
|
-
// Score box background
|
|
271
|
-
page.drawRectangle({
|
|
272
|
-
x: margin + scoreBoxWidth + 20,
|
|
273
|
-
y: yPos - scoreBoxHeight,
|
|
274
|
-
width: scoreBoxWidth,
|
|
275
|
-
height: scoreBoxHeight,
|
|
276
|
-
color: colors.lightGray,
|
|
277
|
-
borderColor: scoreColor2,
|
|
278
|
-
borderWidth: 2
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
// Score label
|
|
282
|
-
page.drawText('Test Out Score', {
|
|
283
|
-
x: margin + scoreBoxWidth + 30,
|
|
284
|
-
y: yPos - 18,
|
|
285
|
-
size: 10,
|
|
286
|
-
font: fontRegular,
|
|
287
|
-
color: colors.darkGray
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
// Score value
|
|
291
|
-
const scoreText2 = scorePercent2 + (grade2 ? ' ' + grade2 : '');
|
|
292
|
-
page.drawText(scoreText2, {
|
|
293
|
-
x: margin + scoreBoxWidth + 30,
|
|
294
|
-
y: yPos - 45,
|
|
295
|
-
size: 24,
|
|
296
|
-
font: fontBold,
|
|
297
|
-
color: scoreColor2
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
yPos -= scoreBoxHeight + 20;
|
|
302
|
-
|
|
303
|
-
// === AI SUMMARY SECTION ===
|
|
304
|
-
page.drawText('AI Summary', {
|
|
305
|
-
x: margin,
|
|
306
|
-
y: yPos,
|
|
307
|
-
size: 14,
|
|
308
|
-
font: fontBold,
|
|
309
|
-
color: colors.headerBg
|
|
310
|
-
});
|
|
311
|
-
yPos -= 5;
|
|
312
|
-
|
|
313
|
-
// Draw line under header
|
|
314
|
-
page.drawLine({
|
|
315
|
-
start: { x: margin, y: yPos },
|
|
316
|
-
end: { x: pageWidth - margin, y: yPos },
|
|
317
|
-
thickness: 1,
|
|
318
|
-
color: colors.headerBg
|
|
319
|
-
});
|
|
320
|
-
yPos -= 15;
|
|
321
|
-
|
|
322
|
-
// AI Summary content
|
|
323
|
-
const aiSummary = payload.ai_summary || payload.project && payload.project.ai_summary || 'No AI summary available.';
|
|
324
|
-
const summaryLines = wrapTextWithNewlines(aiSummary, fontRegular, 10, contentWidth);
|
|
325
|
-
const lineHeight = 14;
|
|
326
|
-
|
|
327
|
-
for (const line of summaryLines) {
|
|
328
|
-
if (yPos < margin + 100) {
|
|
329
|
-
// Add new page if needed
|
|
330
|
-
page = pdfDoc.addPage([pageWidth, pageHeight]);
|
|
331
|
-
yPos = pageHeight - margin;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (line) {
|
|
335
|
-
page.drawText(line, {
|
|
336
|
-
x: margin,
|
|
337
|
-
y: yPos,
|
|
338
|
-
size: 10,
|
|
339
|
-
font: fontRegular,
|
|
340
|
-
color: colors.black
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
yPos -= lineHeight;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
yPos -= 20;
|
|
347
|
-
|
|
348
|
-
// === NOTES SECTION ===
|
|
349
|
-
if (yPos < margin + 150) {
|
|
350
|
-
page = pdfDoc.addPage([pageWidth, pageHeight]);
|
|
351
|
-
yPos = pageHeight - margin;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
page.drawText('Notes', {
|
|
355
|
-
x: margin,
|
|
356
|
-
y: yPos,
|
|
357
|
-
size: 14,
|
|
358
|
-
font: fontBold,
|
|
359
|
-
color: colors.headerBg
|
|
360
|
-
});
|
|
361
|
-
yPos -= 5;
|
|
362
|
-
|
|
363
|
-
// Draw line under header
|
|
364
|
-
page.drawLine({
|
|
365
|
-
start: { x: margin, y: yPos },
|
|
366
|
-
end: { x: pageWidth - margin, y: yPos },
|
|
367
|
-
thickness: 1,
|
|
368
|
-
color: colors.headerBg
|
|
369
|
-
});
|
|
370
|
-
yPos -= 15;
|
|
371
|
-
|
|
372
|
-
// Notes content - check multiple possible sources
|
|
373
|
-
let notes = payload.notes || '';
|
|
374
|
-
if (!notes && payload.project && payload.project.notes) {
|
|
375
|
-
notes = payload.project.notes;
|
|
376
|
-
}
|
|
377
|
-
if (!notes && payload.tests && payload.tests[0] && payload.tests[0].testInfo && payload.tests[0].testInfo.notes) {
|
|
378
|
-
notes = payload.tests[0].testInfo.notes;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
if (!notes) {
|
|
382
|
-
notes = 'No notes available.';
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const notesLines = wrapTextWithNewlines(notes, fontRegular, 10, contentWidth);
|
|
386
|
-
|
|
387
|
-
for (const line of notesLines) {
|
|
388
|
-
if (yPos < margin + 50) {
|
|
389
|
-
// Add new page if needed
|
|
390
|
-
page = pdfDoc.addPage([pageWidth, pageHeight]);
|
|
391
|
-
yPos = pageHeight - margin;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (line) {
|
|
395
|
-
page.drawText(line, {
|
|
396
|
-
x: margin,
|
|
397
|
-
y: yPos,
|
|
398
|
-
size: 10,
|
|
399
|
-
font: fontRegular,
|
|
400
|
-
color: colors.black
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
yPos -= lineHeight;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// === FOOTER SECTION ===
|
|
407
|
-
const footerY = margin;
|
|
408
|
-
|
|
409
|
-
// Draw footer on all pages
|
|
410
|
-
const pages = pdfDoc.getPages();
|
|
411
|
-
for (let i = 0; i < pages.length; i++) {
|
|
412
|
-
const p = pages[i];
|
|
413
|
-
|
|
414
|
-
// Footer line
|
|
415
|
-
p.drawLine({
|
|
416
|
-
start: { x: margin, y: footerY + 25 },
|
|
417
|
-
end: { x: pageWidth - margin, y: footerY + 25 },
|
|
418
|
-
thickness: 0.5,
|
|
419
|
-
color: colors.mediumGray
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
// Profile picture (if available and not NCI)
|
|
423
|
-
if (!isNCI && profileImage) {
|
|
424
|
-
const profileSize = 20;
|
|
425
|
-
p.drawImage(profileImage, {
|
|
426
|
-
x: margin,
|
|
427
|
-
y: footerY,
|
|
428
|
-
width: profileSize,
|
|
429
|
-
height: profileSize
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
// Tech name next to profile
|
|
433
|
-
if (techName) {
|
|
434
|
-
p.drawText(techName, {
|
|
435
|
-
x: margin + profileSize + 5,
|
|
436
|
-
y: footerY + 6,
|
|
437
|
-
size: 8,
|
|
438
|
-
font: fontRegular,
|
|
439
|
-
color: colors.darkGray
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
} else if (techName) {
|
|
443
|
-
p.drawText(techName, {
|
|
444
|
-
x: margin,
|
|
445
|
-
y: footerY + 6,
|
|
446
|
-
size: 8,
|
|
447
|
-
font: fontRegular,
|
|
448
|
-
color: colors.darkGray
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Page number (center)
|
|
453
|
-
const pageNumText = 'Page ' + (i + 1) + ' of ' + pages.length;
|
|
454
|
-
const pageNumWidth = fontRegular.widthOfTextAtSize(pageNumText, 8);
|
|
455
|
-
p.drawText(pageNumText, {
|
|
456
|
-
x: (pageWidth - pageNumWidth) / 2,
|
|
457
|
-
y: footerY + 6,
|
|
458
|
-
size: 8,
|
|
459
|
-
font: fontRegular,
|
|
460
|
-
color: colors.darkGray
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
// mQ logo or company logo (right side)
|
|
464
|
-
const footerLogo = companyImage || mqLogo;
|
|
465
|
-
if (footerLogo) {
|
|
466
|
-
const footerLogoHeight = 18;
|
|
467
|
-
const footerLogoWidth = footerLogoHeight * (footerLogo.width / footerLogo.height);
|
|
468
|
-
p.drawImage(footerLogo, {
|
|
469
|
-
x: pageWidth - margin - footerLogoWidth,
|
|
470
|
-
y: footerY,
|
|
471
|
-
width: footerLogoWidth,
|
|
472
|
-
height: footerLogoHeight
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// Save and return
|
|
478
|
-
return {
|
|
479
|
-
status: 200,
|
|
480
|
-
data: await pdfDoc.saveAsBase64({ dataUri: true })
|
|
481
|
-
};
|
|
482
|
-
|
|
483
|
-
} catch (error) {
|
|
484
|
-
console.error('Error generating AI Summary report:', error);
|
|
485
|
-
return {
|
|
486
|
-
status: 400,
|
|
487
|
-
data: error.message || error
|
|
488
|
-
};
|
|
489
|
-
}
|
|
9
|
+
return notesSummaryPage.getReport(payload);
|
|
490
10
|
}
|
|
@@ -3,7 +3,7 @@ import * as base64 from "../base-64/icons.js";
|
|
|
3
3
|
import * as pdf from "../base-64/mq-vitals-cooling.js";
|
|
4
4
|
import * as systemInfoPage from "./system-info-page.js";
|
|
5
5
|
import * as util from "../util.js";
|
|
6
|
-
import * as
|
|
6
|
+
import * as notesSummaryPage from "./notes-summary-page.js";
|
|
7
7
|
|
|
8
8
|
// Check if project is CoolMaxx or HeatMaxx (NCI branded)
|
|
9
9
|
function isNCIProject(payload) {
|
|
@@ -425,7 +425,7 @@ export async function getReport(payload, _test) {
|
|
|
425
425
|
form.flatten();
|
|
426
426
|
|
|
427
427
|
// Add AI summary page if enabled and summary exists
|
|
428
|
-
await
|
|
428
|
+
await notesSummaryPage.addNotesSummaryPages(pdfDoc, payload);
|
|
429
429
|
|
|
430
430
|
return { status: 200, data: await pdfDoc.saveAsBase64({ dataUri: true }) };
|
|
431
431
|
} catch (error) {
|
|
@@ -3,7 +3,7 @@ import * as base64 from "../base-64/icons.js";
|
|
|
3
3
|
import * as pdf from "../base-64/mq-vitals-heating.js";
|
|
4
4
|
import * as systemInfoPage from "./system-info-page.js";
|
|
5
5
|
import * as util from "../util.js";
|
|
6
|
-
import * as
|
|
6
|
+
import * as notesSummaryPage from "./notes-summary-page.js";
|
|
7
7
|
|
|
8
8
|
// Check if project is CoolMaxx or HeatMaxx (NCI branded)
|
|
9
9
|
function isNCIProject(payload) {
|
|
@@ -632,7 +632,7 @@ export async function getReport(payload, _test) {
|
|
|
632
632
|
form.flatten();
|
|
633
633
|
|
|
634
634
|
// Add AI summary page if enabled and summary exists
|
|
635
|
-
await
|
|
635
|
+
await notesSummaryPage.addNotesSummaryPages(pdfDoc, payload);
|
|
636
636
|
|
|
637
637
|
return { status: 200, data: await pdfDoc.saveAsBase64({ dataUri: true }) };
|
|
638
638
|
} catch (error) {
|
|
@@ -3,7 +3,7 @@ import * as base64 from "../base-64/icons.js";
|
|
|
3
3
|
import * as pdf from "../base-64/mq-vitals-heatpump-heating.js";
|
|
4
4
|
import * as systemInfoPage from "./system-info-page.js";
|
|
5
5
|
import * as util from "../util.js";
|
|
6
|
-
import * as
|
|
6
|
+
import * as notesSummaryPage from "./notes-summary-page.js";
|
|
7
7
|
|
|
8
8
|
// Check if project is CoolMaxx or HeatMaxx (NCI branded)
|
|
9
9
|
function isNCIProject(payload) {
|
|
@@ -267,7 +267,7 @@ export async function getReport(payload, _test) {
|
|
|
267
267
|
form.flatten();
|
|
268
268
|
|
|
269
269
|
// Add AI summary page if enabled and summary exists
|
|
270
|
-
await
|
|
270
|
+
await notesSummaryPage.addNotesSummaryPages(pdfDoc, payload);
|
|
271
271
|
|
|
272
272
|
return { status: 200, data: await pdfDoc.saveAsBase64({ dataUri: true }) };
|
|
273
273
|
} catch (error) {
|