@medicus.ai/medicus-report-pdf-generator 1.0.402 → 1.0.404
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/.vscode/settings.json +4 -0
- package/assets/jquery-2.1.0.min.js +3 -3
- package/assets/pha/labcrop-logo.png +0 -0
- package/assets/raphael-min.js +10 -10
- package/assets/wellbeing/css/nasco_report.css +32 -0
- package/assets/wellbeing/css/nasco_report_rtl.css +5 -0
- package/config/Pha.json +48 -0
- package/config/test.txt +5 -0
- package/index.js +27 -11
- package/lib/wellbeing_report_generator.js +1350 -963
- package/locales/ar-AE.json +168 -168
- package/locales/ar.json +199 -199
- package/locales/fr.json +166 -167
- package/package.json +1 -1
- package/templates/Mediclinic/blocks/first-section.html +162 -219
- package/templates/Mediclinic/blocks/header-template.html +4 -4
- package/templates/Mediclinic/blocks/header.html +6 -6
- package/templates/Mediclinic/blocks/pdf-header.html +2 -2
- package/templates/Mediclinic/rtl_no_pages.html +3 -3
- package/templates/Pha/blocks/first-section.html +208 -0
- package/templates/Pha/blocks/footer.html +8 -0
- package/templates/Pha/blocks/header-template.html +6 -0
- package/templates/Pha/blocks/header.html +20 -0
- package/templates/Pha/blocks/patient-table.html +38 -0
- package/templates/Pha/blocks/pdf-header.html +8 -0
- package/templates/Pha/blocks/tips.html +9 -0
- package/templates/Pha/ltr_no_pages.html +37 -0
- package/templates/Pha/rtl_no_pages.html +42 -0
- package/templates/Pha/wellbeing_template.html +42 -0
- package/templates/blocks/ar-first-page-header.html +3 -6
- package/templates/blocks/ar-header.html +1 -1
- package/templates/blocks/biomarker-compact-ar.html +2 -0
- package/templates/blocks/biomarker-compact.html +2 -0
- package/templates/blocks/biomarker-details.html +1 -1
- package/templates/blocks/biomarker-insight.html +14 -14
- package/templates/blocks/header-first-page.html +3 -6
- package/templates/blocks/header.html +1 -1
- package/templates/blocks/signature.html +1 -1
- package/templates/blocks/summary-insight.html +6 -6
- package/templates/corporate_report/ltr_no_pages.html +1 -2
- package/templates/corporate_report/rtl_no_pages.html +1 -2
- package/templates/first_page_head.html +3 -1
- package/templates/ltr.html +31 -79
- package/templates/no_pages.html +48 -85
- package/templates/popup/popup-template.html +15 -18
- package/templates/sanusx/blocks/personal-details.html +54 -78
- package/templates/sanusx/blocks/predictions-section.html +3 -3
- package/templates/sanusx/blocks/super-power-section.html +2 -1
- package/templates/sanusx/ltr_no_pages.html +1 -2
- package/templates/sanusx/rtl_no_pages.html +2 -3
- package/templates/template.html +1373 -1421
- package/templates/wellbeing/blocks/first-section.html +14 -46
- package/templates/wellbeing/blocks/header.html +4 -5
- package/templates/wellbeing/ltr_no_pages.html +1 -2
- package/templates/wellbeing/rtl_no_pages.html +2 -3
- package/test.js +18 -9
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const fs = require("fs");
|
|
3
|
-
const {promisify} = require('util');
|
|
4
|
-
const {JSDOM} = require("jsdom");
|
|
5
|
-
const fetch = require("node-fetch");
|
|
3
|
+
const { promisify } = require('util');
|
|
4
|
+
const { JSDOM } = require("jsdom");
|
|
6
5
|
const path = require("path");
|
|
7
6
|
const LocaleService = require('../app/services/localeService.js');
|
|
8
7
|
const i18n = require('../app/i18n_wellbeing.config.js');
|
|
9
8
|
const puppeteer = require("puppeteer");
|
|
10
|
-
const {
|
|
11
|
-
const {renderReportItems, find, renderSummary, renderReportSummary, renderHTMLTemplate} = require("./template.js");
|
|
12
|
-
const {cairoFontBase64} = require("../base64/cairo-font");
|
|
9
|
+
const { renderReportItems, find, renderSummary, renderReportSummary, renderHTMLTemplate, renderAllergyClasses } = require("./template.js");
|
|
13
10
|
|
|
14
11
|
|
|
15
12
|
var localeService = new LocaleService(i18n);
|
|
16
|
-
let isRTL = false;
|
|
17
13
|
|
|
18
14
|
|
|
19
15
|
let OUT_FILE = 'output/nasco-pdf.html';
|
|
20
16
|
let Pdf_file = '';
|
|
21
|
-
const appDir = path.dirname(require.main.filename);
|
|
22
17
|
|
|
23
18
|
|
|
24
19
|
let debug = true;
|
|
@@ -30,6 +25,125 @@ let debugLog = (val1, val2 = "") => {
|
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
|
|
28
|
+
// Config object to hold all configuration values
|
|
29
|
+
let config = {
|
|
30
|
+
logo: "",
|
|
31
|
+
title: "",
|
|
32
|
+
bulbIcon: "",
|
|
33
|
+
bulb_icon_font_color: "",
|
|
34
|
+
first_level_color: "",
|
|
35
|
+
second_level_color: "",
|
|
36
|
+
third_level_color: "",
|
|
37
|
+
forth_level_color: "",
|
|
38
|
+
fifth_level_color: "",
|
|
39
|
+
general_font_color: "",
|
|
40
|
+
disclaimer_text_color: "",
|
|
41
|
+
top_box_background: "",
|
|
42
|
+
footer_background: "",
|
|
43
|
+
lifestyleImage: path.resolve(__dirname + '/../assets/wellbeing/images/lifestyle-section.png'),
|
|
44
|
+
mindImage: path.resolve(__dirname + '/../assets/wellbeing/images/mind-section.png'),
|
|
45
|
+
bodyImage: path.resolve(__dirname + '/../assets/wellbeing/images/body-section.png'),
|
|
46
|
+
body_font_color: "",
|
|
47
|
+
list_icons_font_color: "",
|
|
48
|
+
insights_font_color: "",
|
|
49
|
+
section_title_border_color: ""
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Load color theme based on client name
|
|
53
|
+
* @param {string} clientName - Client name (mediclinic, pha, nasco, etc.)
|
|
54
|
+
* @returns {object} Color theme object
|
|
55
|
+
*/
|
|
56
|
+
const loadColorTheme = (clientName = 'default') => {
|
|
57
|
+
const themes = {
|
|
58
|
+
mediclinic: {
|
|
59
|
+
headerText: '#3B3B3B',
|
|
60
|
+
headerBackground: '#f2f5f8',
|
|
61
|
+
headerDivider: '#0092D2',
|
|
62
|
+
footerText: '#B5B5B5',
|
|
63
|
+
listIcon: '#8b8ba7',
|
|
64
|
+
insightPrimary: '#124374',
|
|
65
|
+
insightSecondary: '#000000',
|
|
66
|
+
warningIcon: '#ffc107'
|
|
67
|
+
},
|
|
68
|
+
pha: {
|
|
69
|
+
headerText: '#2C3E50',
|
|
70
|
+
headerBackground: '#ECF0F1',
|
|
71
|
+
headerDivider: '#0F2062',
|
|
72
|
+
footerText: '#95A5A6',
|
|
73
|
+
listIcon: '#7F8C8D',
|
|
74
|
+
insightPrimary: '#0F2062',
|
|
75
|
+
insightSecondary: '#2C3E50',
|
|
76
|
+
warningIcon: '#F39C12'
|
|
77
|
+
},
|
|
78
|
+
nasco: {
|
|
79
|
+
headerText: '#34495E',
|
|
80
|
+
headerBackground: '#EBF5FB',
|
|
81
|
+
headerDivider: '#5DADE2',
|
|
82
|
+
footerText: '#AAB7B8',
|
|
83
|
+
listIcon: '#85929E',
|
|
84
|
+
insightPrimary: '#1F618D',
|
|
85
|
+
insightSecondary: '#1C2833',
|
|
86
|
+
warningIcon: '#F4D03F'
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
};
|
|
90
|
+
return themes[clientName?.toLowerCase()] || themes.mediclinic;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Load and apply configuration for a specific client
|
|
94
|
+
* @param {string} clientName - The client name to load config for
|
|
95
|
+
* @returns {object} The loaded configuration
|
|
96
|
+
*/
|
|
97
|
+
const loadClientConfig = (clientName = 'default') => {
|
|
98
|
+
const pathFile = path.resolve(__dirname + '/../config/' + clientName + '.json');
|
|
99
|
+
let clientConfig = require('../config/default.json');
|
|
100
|
+
|
|
101
|
+
if (fs.existsSync(pathFile) && clientName !== 'default') {
|
|
102
|
+
clientConfig = require('../config/' + clientName + '.json');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Map config values with defaults
|
|
106
|
+
config.logo = clientConfig['logo'] || "";
|
|
107
|
+
config.labs_logos = clientConfig['labs-logo'] || [];
|
|
108
|
+
config.title = clientConfig['title'] || "";
|
|
109
|
+
config.bulbIcon = clientConfig['bulb_icon'] || "";
|
|
110
|
+
config.bulb_icon_font_color = clientConfig['bulb_icon_font_color'] || "";
|
|
111
|
+
config.first_level_color = clientConfig['first-level-color'] || "";
|
|
112
|
+
config.second_level_color = clientConfig['second-level-color'] || "";
|
|
113
|
+
config.third_level_color = clientConfig['third-level-color'] || "";
|
|
114
|
+
config.forth_level_color = clientConfig['forth-level-color'] || "";
|
|
115
|
+
config.fifth_level_color = clientConfig['fifth-level-color'] || "";
|
|
116
|
+
config.general_font_color = clientConfig['general-font-color'] || "";
|
|
117
|
+
config.disclaimer_text_color = clientConfig['disclaimer-text-color'] || "";
|
|
118
|
+
config.top_box_background = clientConfig['top-box-background'] || "";
|
|
119
|
+
config.footer_background = clientConfig['footer-background'] || "";
|
|
120
|
+
config.footer_border_color = clientConfig['footer-border-color'] || clientConfig['section-title-border-color'] || "";
|
|
121
|
+
|
|
122
|
+
// Handle image paths with fallbacks
|
|
123
|
+
config.lifestyleImage = clientConfig['lifestyle_icon'] || path.resolve(__dirname + '/../assets/wellbeing/images/lifestyle-section.png');
|
|
124
|
+
config.mindImage = clientConfig['mind_icon'] || path.resolve(__dirname + '/../assets/wellbeing/images/mind-section.png');
|
|
125
|
+
config.bodyImage = clientConfig['body_icon'] || path.resolve(__dirname + '/../assets/wellbeing/images/body-section.png');
|
|
126
|
+
config.body_font_color = clientConfig['body_font_color'] || "";
|
|
127
|
+
config.list_icons_font_color = clientConfig['list_icons_font_color'] || "";
|
|
128
|
+
config.insights_font_color = clientConfig['insights_font_color'] || "";
|
|
129
|
+
|
|
130
|
+
config.score_header_color = clientConfig['score-header-color'] || clientConfig['first-level-color'] || "";
|
|
131
|
+
config.score_description_color = clientConfig['score-description-color'] || clientConfig['first-level-color'] || "";
|
|
132
|
+
config.svg_icon_color = clientConfig['svg-icon-color'] || clientConfig['first-level-color'] || "";
|
|
133
|
+
config.low_score_text_color = clientConfig['low-score-text-color'] || clientConfig['first-level-color'] || "";
|
|
134
|
+
config.section_title_border_color =
|
|
135
|
+
clientConfig['section-title-border-color'] ||
|
|
136
|
+
(clientConfig['corporate_report'] ?
|
|
137
|
+
clientConfig['corporate_report']['section_title_border_color'] : null) ||
|
|
138
|
+
clientConfig['forth-level-color'] ||
|
|
139
|
+
"";
|
|
140
|
+
config.first_section_scores_border_color = clientConfig['first-section-scores-border-color'] || clientConfig['fifth-level-color'] || "";
|
|
141
|
+
|
|
142
|
+
config.colors = loadColorTheme(clientName);
|
|
143
|
+
|
|
144
|
+
return config;
|
|
145
|
+
};
|
|
146
|
+
|
|
33
147
|
const icons = {
|
|
34
148
|
"wellbeing_element_alcohol": "M 10.667969 5.144531 L 19.332031 5.144531 C 19.5 5.144531 19.65625 5.238281 19.710938 5.375 C 21.007812 8.671875 21.394531 10.714844 21.394531 12.316406 C 21.394531 15.285156 18.53125 17.695312 15 17.695312 C 11.46875 17.695312 8.605469 15.285156 8.605469 12.316406 C 8.605469 10.714844 8.988281 8.671875 10.289062 5.375 C 10.34375 5.238281 10.5 5.144531 10.667969 5.144531 Z M 9.804688 12.316406 C 9.804688 14.648438 11.785156 16.09375 15 16.339844 C 17.796875 16.550781 20.195312 14.71875 20.195312 12.316406 L 20.195312 11.929688 L 9.804688 11.929688 Z M 9.804688 12.316406 M 17.796875 24.140625 L 17.398438 24.140625 C 16.296875 24.140625 15.398438 23.378906 15.398438 22.445312 L 15.394531 18.28125 C 15.394531 17.945312 15.6875 17.667969 16.078125 17.613281 C 17.324219 17.433594 18.445312 16.953125 19.332031 16.261719 C 19.332031 16.261719 18.003906 17.015625 15 17.015625 C 11.996094 17.015625 10.667969 16.261719 10.667969 16.261719 C 11.558594 16.953125 12.675781 17.433594 13.921875 17.613281 C 14.3125 17.667969 14.605469 17.945312 14.601562 18.28125 L 14.601562 22.445312 C 14.601562 23.378906 13.703125 24.140625 12.601562 24.140625 L 12.203125 24.140625 C 11.980469 24.140625 11.804688 24.292969 11.804688 24.480469 C 11.804688 24.667969 11.980469 24.820312 12.203125 24.820312 L 17.796875 24.820312 C 18.019531 24.820312 18.195312 24.667969 18.195312 24.480469 C 18.195312 24.292969 18.019531 24.140625 17.796875 24.140625 Z M 17.796875 24.140625",
|
|
35
149
|
"wellbeing_element_blue_mood": "M 19.351562 7.859375 C 19.640625 6.664062 20.308594 5.144531 21.085938 5.144531 C 22.121094 5.144531 22.972656 7.855469 22.972656 8.882812 C 22.972656 9.371094 22.78125 9.8125 22.472656 10.148438 C 24.007812 11.851562 24.859375 14.066406 24.855469 16.363281 C 24.855469 21.527344 20.636719 25.714844 15.429688 25.714844 C 10.222656 25.714844 6 21.527344 6 16.363281 C 6 11.199219 10.222656 7.011719 15.429688 7.011719 C 16.828125 7.011719 18.15625 7.316406 19.351562 7.859375 Z M 12.191406 15.734375 C 12.636719 15.734375 13 15.375 13 14.933594 C 13 14.488281 12.636719 14.128906 12.191406 14.128906 C 11.75 14.128906 11.386719 14.488281 11.382812 14.933594 C 11.382812 15.375 11.746094 15.734375 12.191406 15.734375 Z M 18.835938 15.734375 C 19.28125 15.734375 19.644531 15.375 19.644531 14.933594 C 19.644531 14.488281 19.28125 14.128906 18.835938 14.128906 C 18.394531 14.128906 18.03125 14.488281 18.027344 14.933594 C 18.027344 15.375 18.390625 15.734375 18.835938 15.734375 Z M 12.929688 20.742188 C 13.726562 20.632812 14.59375 20.574219 15.53125 20.574219 C 16.464844 20.574219 17.3125 20.628906 18.066406 20.742188 C 17.785156 20.328125 17.5 20.023438 17.207031 19.835938 C 16.765625 19.550781 16.429688 19.3125 15.601562 19.3125 C 14.773438 19.3125 14.234375 19.554688 13.777344 19.835938 C 13.472656 20.019531 13.1875 20.324219 12.929688 20.742188 Z M 19.351562 7.859375 C 19.253906 8.257812 19.199219 8.625 19.199219 8.882812 C 19.199219 9.910156 20.046875 10.753906 21.085938 10.753906 C 21.632812 10.753906 22.128906 10.519531 22.472656 10.148438 C 21.601562 9.175781 20.539062 8.398438 19.351562 7.859375 Z M 9.941406 13.4375 L 10.421875 14.148438 L 12.554688 12.710938 L 12.074219 12 Z M 18.355469 12.832031 L 20.488281 14.269531 L 20.96875 13.558594 L 18.835938 12.121094 Z M 18.355469 12.832031",
|
|
@@ -68,27 +182,6 @@ const icons = {
|
|
|
68
182
|
"wellbeing_element_vitamin_balance": "M9.28358 5H18.2836C18.6866 5 19.0896 5.26866 19.0896 5.80597V8.08955C19.0896 8.49254 18.8209 8.89552 18.2836 8.89552H18.0149H18.4179V9.97015C18.4179 10.6418 18.8209 11.3134 19.4925 11.5821C19.7612 11.7164 20.1642 11.8507 20.2985 12.2537C20.5672 12.5224 20.5672 12.9254 20.5672 13.3284V14H7V13.194C7 12.5224 7.40299 11.8507 8.07463 11.5821C8.47761 11.4478 8.8806 11.1791 9.01493 10.9104C9.28358 10.6418 9.28358 10.2388 9.28358 9.97015V8.89552C8.8806 8.89552 8.61194 8.49254 8.61194 8.08955V5.80597C8.61194 5.26866 8.8806 5 9.28358 5ZM7 14.2687H20.5672V16.6866C19.4925 17.2239 18.6866 18.2985 18.6866 19.5075V23.806H7V14.2687ZM18.8209 24.0746L7.13433 24.209V25.6866C7.13433 26.4925 7.80597 27.1642 8.61194 27.1642H13.5821H19.2239C19.6269 27.1642 20.0299 27.0299 20.2985 26.6269C20.8358 26.8955 21.2388 27.0298 21.9104 27.0298C23.6567 27.0298 25 25.6866 25 23.9403V19.3731C25 18.5672 24.7313 17.7612 24.0597 17.2239C23.1194 16.2836 21.9104 16.0149 20.8358 16.5522V23.806V25.6866C20.8358 26.0896 20.7015 26.3582 20.4328 26.6269C19.3582 26.2239 18.8209 25.1493 18.8209 24.0746ZM13.0448 21.9254C13.1791 22.194 13.5821 22.4627 13.9851 22.4627C14.3881 22.4627 14.6567 22.194 14.9254 21.9254L17.209 17.0896C17.3433 16.8209 17.3433 16.4179 17.0746 16.1493C16.806 15.8806 16.5373 15.6119 16.2687 15.7463C15.8657 15.7463 15.597 15.8806 15.3284 16.2836L13.8507 19.3731L12.3731 16.2836C12.1045 15.7463 11.5672 15.6119 11.0299 15.8806C10.4925 16.1493 10.3582 16.6866 10.6269 17.2239L13.0448 21.9254Z"
|
|
69
183
|
};
|
|
70
184
|
|
|
71
|
-
let logo = ""
|
|
72
|
-
let title = ""
|
|
73
|
-
|
|
74
|
-
let bulbIcon = ""
|
|
75
|
-
let bulb_icon_font_color = ""
|
|
76
|
-
let first_level_color = ""
|
|
77
|
-
let second_level_color = ""
|
|
78
|
-
let third_level_color = ""
|
|
79
|
-
let forth_level_color = ""
|
|
80
|
-
let fifth_level_color = ""
|
|
81
|
-
let general_font_color = ""
|
|
82
|
-
let disclaimer_text_color = ""
|
|
83
|
-
let top_box_background = ""
|
|
84
|
-
let footer_background = ""
|
|
85
|
-
let lifestyleImage = path.resolve(__dirname + '/../assets/wellbeing/images/lifestyle-section.png')
|
|
86
|
-
let mindImage = path.resolve(__dirname + '/../assets/wellbeing/images/mind-section.png')
|
|
87
|
-
let bodyImage = path.resolve(__dirname + '/../assets/wellbeing/images/body-section.png')
|
|
88
|
-
let body_font_color = ""
|
|
89
|
-
let list_icons_font_color = ""
|
|
90
|
-
let insights_font_color = ""
|
|
91
|
-
|
|
92
185
|
const escapeHTML = (text) => {
|
|
93
186
|
return text
|
|
94
187
|
.replace(/&/g, '&') // Must come first
|
|
@@ -110,373 +203,448 @@ const wrapLTR = (text) => {
|
|
|
110
203
|
}
|
|
111
204
|
return `<span style="unicode-bidi: bidi-override;" dir="ltr">${text}</span>`;
|
|
112
205
|
};
|
|
113
|
-
/***
|
|
114
|
-
* HTML file generator to fill the html template
|
|
115
|
-
* based on the json wellbeingData from the POST request
|
|
116
|
-
*/
|
|
117
|
-
let generateHTMLWellbeingReport = async (data, isDebugging, clientName, language) => {
|
|
118
206
|
|
|
119
|
-
debug = isDebugging;
|
|
120
|
-
OUT_FILE = path.resolve(__dirname + '/../output/nasco-pdf' + Date.now() + '.html');
|
|
121
|
-
Pdf_file = '/output/nasco-pdf' + Date.now() + '.html'
|
|
122
|
-
if (empty(language)) {
|
|
123
|
-
language = 'en'
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
let isRtl = false;
|
|
127
|
-
if (language.indexOf('ar') !== -1) {
|
|
128
|
-
language = 'ar-SA';
|
|
129
|
-
isRtl = true;
|
|
130
|
-
}
|
|
131
207
|
|
|
132
208
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Calculate score metrics for visualization
|
|
211
|
+
* @param {number} score - The score value (0-100)
|
|
212
|
+
* @param {string} client - Client name for specific color logic
|
|
213
|
+
* @returns {object} Score metrics including color, amount, and rotation
|
|
214
|
+
*/
|
|
215
|
+
const calculateScoreMetrics = (score, client = 'default') => {
|
|
216
|
+
const scoreValue = parseFloat(score);
|
|
217
|
+
|
|
218
|
+
// Determine amount based on score range
|
|
219
|
+
let amount;
|
|
220
|
+
if (scoreValue > 40 && scoreValue < 70) {
|
|
221
|
+
amount = 355;
|
|
222
|
+
} else if (scoreValue >= 70) {
|
|
223
|
+
amount = 360;
|
|
224
|
+
} else {
|
|
225
|
+
amount = 214;
|
|
138
226
|
}
|
|
139
227
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
disclaimer_text_color = defaultConfig['disclaimer-text-color']
|
|
150
|
-
top_box_background = defaultConfig['top-box-background']
|
|
151
|
-
footer_background = defaultConfig['footer-background']
|
|
152
|
-
lifestyleImage = defaultConfig['lifestyle_icon'] ? defaultConfig['lifestyle_icon'] : path.resolve(__dirname + '/../assets/wellbeing/images/lifestyle-section.png')
|
|
153
|
-
mindImage = defaultConfig['mind_icon'] ? defaultConfig['mind_icon'] : path.resolve(__dirname + '/../assets/wellbeing/images/mind-section.png')
|
|
154
|
-
bodyImage = defaultConfig['body_icon'] ? defaultConfig['body_icon'] : path.resolve(__dirname + '/../assets/wellbeing/images/body-section.png')
|
|
155
|
-
body_font_color = defaultConfig['body_font_color']
|
|
156
|
-
list_icons_font_color = defaultConfig['list_icons_font_color']
|
|
157
|
-
insights_font_color = defaultConfig['insights_font_color']
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
/*check if the language is an RTL language*/
|
|
161
|
-
localeService.setLocale(language);
|
|
162
|
-
debugLog("pdf language:", language);
|
|
163
|
-
|
|
164
|
-
/*choose the right template*/
|
|
165
|
-
let html = !isRtl ? fs.readFileSync(path.resolve(__dirname + '/../templates/wellbeing/ltr_no_pages.html'), 'utf8') : fs.readFileSync(path.resolve(__dirname + '/../templates/wellbeing/rtl_no_pages.html'), 'utf8');
|
|
166
|
-
|
|
167
|
-
let headerTemplate = fs.readFileSync(path.resolve(__dirname + '/../templates/wellbeing/blocks/header.html'), 'utf8');
|
|
168
|
-
|
|
169
|
-
let footerTemplate = fs.readFileSync(path.resolve(__dirname + '/../templates/wellbeing/blocks/footer.html'), 'utf8');
|
|
228
|
+
// Determine color based on score range
|
|
229
|
+
let color;
|
|
230
|
+
if (scoreValue > 40 && scoreValue < 70) {
|
|
231
|
+
color = config.third_level_color;
|
|
232
|
+
} else if (scoreValue >= 70) {
|
|
233
|
+
color = config.fifth_level_color;
|
|
234
|
+
} else {
|
|
235
|
+
color = client === 'nasco' ? config.second_level_color : config.first_level_color;
|
|
236
|
+
}
|
|
170
237
|
|
|
171
|
-
|
|
238
|
+
// Calculate rotation amount
|
|
239
|
+
let rotateAmount = (scoreValue / 100) * amount;
|
|
240
|
+
if (rotateAmount > 336) {
|
|
241
|
+
rotateAmount = 336;
|
|
242
|
+
}
|
|
172
243
|
|
|
173
|
-
|
|
244
|
+
return {
|
|
245
|
+
score: scoreValue,
|
|
246
|
+
amount,
|
|
247
|
+
color,
|
|
248
|
+
rotateAmount,
|
|
249
|
+
scoreInteger: Math.floor(scoreValue),
|
|
250
|
+
scoreDecimal: parseFloat((scoreValue % 1).toFixed(1)).toString().substring(1)
|
|
251
|
+
};
|
|
252
|
+
};
|
|
174
253
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
254
|
+
/**
|
|
255
|
+
* Apply inactive classes to score sections based on score ranges
|
|
256
|
+
* @param {object} $ - jQuery instance
|
|
257
|
+
* @param {number} score - The score value
|
|
258
|
+
* @param {string} sectionClass - CSS class of the section
|
|
259
|
+
*/
|
|
260
|
+
const applyScoreInactiveClasses = ($, score, sectionClass) => {
|
|
261
|
+
const scoreValue = parseFloat(score);
|
|
262
|
+
const selector = `.first-section-scores .${sectionClass}`;
|
|
263
|
+
|
|
264
|
+
if (scoreValue > 40 && scoreValue < 70) {
|
|
265
|
+
$(`${selector} .part-1, ${selector} .part-3`).addClass('inactive');
|
|
266
|
+
} else if (scoreValue >= 70) {
|
|
267
|
+
$(`${selector} .part-3, ${selector} .part-2`).addClass('inactive');
|
|
268
|
+
} else {
|
|
269
|
+
$(`${selector} .part-1, ${selector} .part-2`).addClass('inactive');
|
|
270
|
+
}
|
|
271
|
+
};
|
|
180
272
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
273
|
+
/**
|
|
274
|
+
* Calculate extended score metrics for special score types (depression, anxiety)
|
|
275
|
+
* @param {number} score - The score value
|
|
276
|
+
* @param {string} scoreType - Type of score ('depression' or 'anxiety')
|
|
277
|
+
* @param {string} client - Client name
|
|
278
|
+
* @returns {object} Score metrics
|
|
279
|
+
*/
|
|
280
|
+
const calculateExtendedScoreMetrics = (score, scoreType, client = 'default') => {
|
|
281
|
+
const scoreValue = parseFloat(score);
|
|
282
|
+
let amount = 0;
|
|
283
|
+
|
|
284
|
+
if (scoreType === 'depression') {
|
|
285
|
+
if (scoreValue >= 0 && scoreValue <= 14.8) amount = 550;
|
|
286
|
+
else if (scoreValue >= 18.5 && scoreValue <= 33.3) amount = 400;
|
|
287
|
+
else if (scoreValue >= 37 && scoreValue <= 51.9) amount = 380;
|
|
288
|
+
else if (scoreValue >= 55.6 && scoreValue <= 70.4) amount = 370;
|
|
289
|
+
else if (scoreValue >= 74.1 && scoreValue <= 100) amount = 330;
|
|
290
|
+
} else if (scoreType === 'anxiety') {
|
|
291
|
+
if (scoreValue >= 0 && scoreValue <= 19) amount = 460;
|
|
292
|
+
else if (scoreValue >= 23.8 && scoreValue <= 42.9) amount = 400;
|
|
293
|
+
else if (scoreValue >= 47.6 && scoreValue <= 66.7) amount = 360;
|
|
294
|
+
else if (scoreValue >= 71.4 && scoreValue <= 100) amount = 330;
|
|
184
295
|
}
|
|
185
296
|
|
|
186
|
-
|
|
187
|
-
let
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
297
|
+
// Calculate color
|
|
298
|
+
let color;
|
|
299
|
+
if (scoreValue > 40 && scoreValue < 70) {
|
|
300
|
+
color = config.third_level_color;
|
|
301
|
+
} else if (scoreValue >= 70) {
|
|
302
|
+
color = config.fifth_level_color;
|
|
303
|
+
} else {
|
|
304
|
+
color = client === 'nasco' ? config.second_level_color : config.first_level_color;
|
|
194
305
|
}
|
|
195
306
|
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
let formattedDate = new Date();
|
|
199
|
-
let day = formattedDate.getDate();
|
|
200
|
-
let month = formattedDate.getMonth();
|
|
201
|
-
month += 1;
|
|
202
|
-
/* JavaScript months are 0-11 */
|
|
203
|
-
let year = formattedDate.getFullYear();
|
|
204
|
-
let Hours = (formattedDate.getHours() < 10 ? '0' : '') + formattedDate.getHours();
|
|
205
|
-
let m = (formattedDate.getMinutes() < 10 ? '0' : '') + formattedDate.getMinutes();
|
|
206
|
-
/* only -12 from hours if it is greater than 12 (if not back at mid night) */
|
|
207
|
-
let hours12 = (Hours > 12) ? Hours - 12 : Hours;
|
|
208
|
-
/* if 00 then it is 12 am */
|
|
209
|
-
hours12 = (hours12 == 0) ? 12 : hours12;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
let topInsight = data.lingualReading ? data.lingualReading.statement : localeService.t('reportHeaderDescription')
|
|
213
|
-
|
|
214
|
-
headerTemplate = headerTemplate
|
|
215
|
-
.replace("{{logo}}", img)
|
|
216
|
-
.replace("{{userImage}}", userImage)
|
|
217
|
-
.replace("{{topBoxBg}}", top_box_background)
|
|
218
|
-
.replace("{{scoresNote}}", localeService.t('scoresNote'))
|
|
219
|
-
.replace("{{bulbIcon}}", bulbIcon)
|
|
220
|
-
.replace("{{reportHeader}}", localeService.t('wellbeingReport'))
|
|
221
|
-
.replace("{{reportHeaderDescription}}", topInsight)
|
|
222
|
-
.replace("{{patientName}}", data.name)
|
|
223
|
-
.replace("{{patientOld}}", data.age)
|
|
224
|
-
.replace("{{yearsOld}}", localeService.t('yearsOld'))
|
|
225
|
-
.replace("{{patientGender}}", data.gender)
|
|
226
|
-
.replace("{{reportDate}}", reportDate);
|
|
227
|
-
|
|
228
|
-
let iconColor = !empty(bulb_icon_font_color) ? bulb_icon_font_color : general_font_color
|
|
229
|
-
/*get the window from the dom to be used as a jquery function*/
|
|
230
|
-
const $ = (require('jquery'))(dom.window);
|
|
231
|
-
$('#content').append(headerTemplate);
|
|
232
|
-
$('#content').append('<style>' + '.tips-text{color: ' + insights_font_color + ' !important;}' + '.first-level-font-color{color:' + general_font_color + ' !important;}' + '.first-section-scores{border-color:' + fifth_level_color + ' !important;}' + '.disclaimer-text-color{color:' + disclaimer_text_color + ' !important;}' + '.first-level-border-color{border-color:' + general_font_color + ' !important;}' + '.scores-note .first-level-font-color{color:' + iconColor + ' !important;}' + '.top-box-bg{background-color: ' + top_box_background + ' !important;}' + '.footer-background{background-color: ' + footer_background + ' !important;}' + '.report-date, .patient-info,.score-description,.list-text,.report-header,.list-score,.score-details-title{color: ' + body_font_color + ' !important;}' + '.list-item g, .list-item path{fill: ' + list_icons_font_color + ' !important;stroke: ' + list_icons_font_color + ' !important;}' + '.fifth-level-font-color{color:' + fifth_level_color + '!important;}' + '</style>');
|
|
233
|
-
if (empty(data.photo)) {
|
|
307
|
+
const rotateAmount = (scoreValue / 100) * amount;
|
|
234
308
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
309
|
+
return {
|
|
310
|
+
score: scoreValue,
|
|
311
|
+
amount,
|
|
312
|
+
color,
|
|
313
|
+
rotateAmount,
|
|
314
|
+
scoreInteger: Math.floor(scoreValue),
|
|
315
|
+
scoreDecimal: parseFloat((scoreValue % 1).toFixed(1)).toString().substring(1)
|
|
316
|
+
};
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Calculate physical score metrics with special handling for low scores
|
|
321
|
+
* @param {number} score - The score value
|
|
322
|
+
* @param {string} client - Client name
|
|
323
|
+
* @returns {object} Score metrics
|
|
324
|
+
*/
|
|
325
|
+
const calculatePhysicalScoreMetrics = (score, client = 'default') => {
|
|
326
|
+
const scoreValue = parseFloat(score);
|
|
327
|
+
|
|
328
|
+
// Use different amounts for this specific case
|
|
329
|
+
let amount;
|
|
330
|
+
if (scoreValue > 40 && scoreValue < 70) {
|
|
331
|
+
amount = 310;
|
|
332
|
+
} else if (scoreValue >= 70) {
|
|
333
|
+
amount = 310;
|
|
334
|
+
} else {
|
|
335
|
+
amount = 300;
|
|
241
336
|
}
|
|
242
|
-
$("#chartsData").append("<script> var RANGE_COLORS = ['" + first_level_color + "','" + second_level_color + "','" + third_level_color + "','" + forth_level_color + "','" + fifth_level_color + "'];</script>");
|
|
243
337
|
|
|
244
|
-
|
|
245
|
-
let
|
|
246
|
-
|
|
247
|
-
|
|
338
|
+
// Determine color
|
|
339
|
+
let color;
|
|
340
|
+
if (scoreValue > 40 && scoreValue < 70) {
|
|
341
|
+
color = config.third_level_color;
|
|
342
|
+
} else if (scoreValue >= 70) {
|
|
343
|
+
color = config.fifth_level_color;
|
|
344
|
+
} else {
|
|
345
|
+
color = client === 'nasco' ? config.second_level_color : config.first_level_color;
|
|
346
|
+
}
|
|
248
347
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
348
|
+
// Calculate rotation with special handling for low scores
|
|
349
|
+
let rotateAmount = (scoreValue / 100) * amount;
|
|
350
|
+
if (scoreValue <= 20) {
|
|
351
|
+
rotateAmount += 25;
|
|
252
352
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
let psychologicalColor = (psychologicalScore > 40 && psychologicalScore < 70) ? third_level_color : (psychologicalScore >= 70) ? fifth_level_color : client === 'nasco' ? second_level_color : first_level_color;
|
|
256
|
-
let rotatePsychologicalAmount = (psychologicalScore / 100) * psychologicalAmount;
|
|
257
|
-
if (rotatePsychologicalAmount > 336) {
|
|
258
|
-
rotatePsychologicalAmount = 336
|
|
353
|
+
if (rotateAmount > 336) {
|
|
354
|
+
rotateAmount = 336;
|
|
259
355
|
}
|
|
260
356
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
357
|
+
return {
|
|
358
|
+
score: scoreValue,
|
|
359
|
+
amount,
|
|
360
|
+
color,
|
|
361
|
+
rotateAmount,
|
|
362
|
+
scoreInteger: Math.floor(scoreValue),
|
|
363
|
+
scoreDecimal: parseFloat((scoreValue % 1).toFixed(1)).toString().substring(1)
|
|
364
|
+
};
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Render the first section with physical and psychological scores
|
|
369
|
+
* @param {object} template - The HTML template
|
|
370
|
+
* @param {object} calculationData - Data containing scores and statements
|
|
371
|
+
* @param {object} physicalMetrics - Calculated physical score metrics
|
|
372
|
+
* @param {object} psychologicalMetrics - Calculated psychological score metrics
|
|
373
|
+
* @param {string} client - Client name
|
|
374
|
+
* @returns {string} Rendered HTML
|
|
375
|
+
*/
|
|
376
|
+
const renderFirstSection = (template, calculationData, physicalMetrics, psychologicalMetrics, client) => {
|
|
377
|
+
return template
|
|
378
|
+
.replace("{{physicalScore}}", physicalMetrics.scoreInteger)
|
|
379
|
+
.replace("{{physicalScoreDecimalPart}}", physicalMetrics.scoreDecimal)
|
|
380
|
+
.replace("{{physicalScoreStatement}}", calculationData.physicalScoreStatement)
|
|
264
381
|
.replace("{{PhysicalWord}}", localeService.t('physical'))
|
|
265
|
-
.replace(/{{rotatePhysicalAmount}}/gi,
|
|
266
|
-
.replace(/{{physicalColor}}/gi,
|
|
267
|
-
.replace("{{psychologicalScore}}",
|
|
268
|
-
.replace("{{psychologicalScoreDecimalPart}}",
|
|
382
|
+
.replace(/{{rotatePhysicalAmount}}/gi, physicalMetrics.rotateAmount)
|
|
383
|
+
.replace(/{{physicalColor}}/gi, physicalMetrics.color)
|
|
384
|
+
.replace("{{psychologicalScore}}", psychologicalMetrics.scoreInteger)
|
|
385
|
+
.replace("{{psychologicalScoreDecimalPart}}", psychologicalMetrics.scoreDecimal)
|
|
269
386
|
.replace("{{psychologicalWord}}", localeService.t('psychological'))
|
|
270
|
-
.replace("{{psychologicalScoreStatement}}",
|
|
271
|
-
.replace(/{{rotatePsychologicalAmount}}/gi,
|
|
272
|
-
.replace(/{{fifthLevelColor}}/g, fifth_level_color)
|
|
273
|
-
.replace(/{{thirdLevelColor}}/g, third_level_color)
|
|
274
|
-
.replace(/{{firstLevelColor}}/g, client === 'nasco' ? second_level_color : first_level_color)
|
|
275
|
-
.replace("{{psychologicalColor}}",
|
|
276
|
-
.replace(/{{
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
.replace(
|
|
387
|
+
.replace("{{psychologicalScoreStatement}}", calculationData.psychologicalScoreStatement)
|
|
388
|
+
.replace(/{{rotatePsychologicalAmount}}/gi, psychologicalMetrics.rotateAmount)
|
|
389
|
+
.replace(/{{fifthLevelColor}}/g, config.fifth_level_color)
|
|
390
|
+
.replace(/{{thirdLevelColor}}/g, config.third_level_color)
|
|
391
|
+
.replace(/{{firstLevelColor}}/g, client === 'nasco' ? config.second_level_color : config.first_level_color)
|
|
392
|
+
.replace("{{psychologicalColor}}", psychologicalMetrics.color)
|
|
393
|
+
.replace(/{{scoreHeaderColor}}/gi, physicalMetrics.color)
|
|
394
|
+
.replace(/{{svgIconColor}}/gi, physicalMetrics.color)
|
|
395
|
+
.replace(/{{lowScoreTextColor}}/gi, physicalMetrics.color)
|
|
396
|
+
.replace(/{{scoreDescriptionColor}}/gi, physicalMetrics.color)
|
|
397
|
+
.replace(/{{listIconsFontColor}}/g, config.list_icons_font_color);
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Render extended first section with depression and anxiety scores
|
|
402
|
+
* @param {object} template - The HTML template
|
|
403
|
+
* @param {object} calculationData - Data containing all scores and statements
|
|
404
|
+
* @param {object} metrics - Object containing all calculated metrics
|
|
405
|
+
* @param {string} client - Client name
|
|
406
|
+
* @returns {string} Rendered HTML
|
|
407
|
+
*/
|
|
408
|
+
const renderExtendedFirstSection = (template, calculationData, metrics, client) => {
|
|
409
|
+
return template
|
|
410
|
+
.replace("{{depressionRiskScore}}", metrics.depression.scoreInteger)
|
|
411
|
+
.replace("{{depressionRiskScoreDecimalPart}}", metrics.depression.scoreDecimal)
|
|
412
|
+
.replace("{{depressionRiskScoreStatement}}", calculationData.depressionRiskScoreStatement)
|
|
413
|
+
.replace("{{DepressionRiskWord}}", localeService.t('depressionRisk'))
|
|
414
|
+
.replace(/{{rotateDepressionRiskAmount}}/gi, metrics.depression.rotateAmount)
|
|
415
|
+
.replace(/{{depressionRiskColor}}/gi, metrics.depression.color)
|
|
416
|
+
.replace("{{anxietyRiskScore}}", metrics.anxiety.scoreInteger)
|
|
417
|
+
.replace("{{anxietyRiskScoreDecimalPart}}", metrics.anxiety.scoreDecimal)
|
|
418
|
+
.replace("{{anxietyRiskScoreStatement}}", calculationData.anxietyRiskScoreStatement)
|
|
419
|
+
.replace("{{AnxietyRiskWord}}", localeService.t('anxietyRisk'))
|
|
420
|
+
.replace(/{{rotateAnxietyRiskAmount}}/gi, metrics.anxiety.rotateAmount)
|
|
421
|
+
.replace(/{{anxietyRiskColor}}/gi, metrics.anxiety.color)
|
|
422
|
+
.replace("{{physicalScore}}", metrics.physical.scoreInteger)
|
|
423
|
+
.replace("{{physicalScoreDecimalPart}}", metrics.physical.scoreDecimal)
|
|
424
|
+
.replace("{{physicalScoreStatement}}", calculationData.physicalScoreStatement)
|
|
425
|
+
.replace("{{PhysicalWord}}", localeService.t('physicalScore'))
|
|
426
|
+
.replace(/{{rotatePhysicalAmount}}/gi, metrics.physical.rotateAmount)
|
|
427
|
+
.replace(/{{physicalColor}}/gi, metrics.physical.color)
|
|
428
|
+
.replace("{{ScoreText}}", localeService.t('scoreText'))
|
|
429
|
+
.replace("{{lowScoreText}}", localeService.t('lowScoreText'))
|
|
430
|
+
.replace("{{PhysicalWellbeingText}}", localeService.t('PhysicalWellbeingText'))
|
|
431
|
+
.replace("{{PsychologicalWellbeingText}}", localeService.t('PsychologicalWellbeingText'))
|
|
432
|
+
.replace("{{psychologicalScore}}", metrics.psychological.scoreInteger)
|
|
433
|
+
.replace("{{psychologicalScoreDecimalPart}}", metrics.psychological.scoreDecimal)
|
|
434
|
+
.replace("{{psychologicalWord}}", localeService.t('psychological'))
|
|
435
|
+
.replace("{{psychologicalScoreStatement}}", calculationData.psychologicalScoreStatement)
|
|
436
|
+
.replace(/{{rotatePsychologicalAmount}}/gi, metrics.psychological.rotateAmount)
|
|
437
|
+
.replace(/{{fifthLevelColor}}/g, config.fifth_level_color)
|
|
438
|
+
.replace(/{{thirdLevelColor}}/g, config.third_level_color)
|
|
439
|
+
.replace(/{{secondLevelColor}}/g, config.second_level_color)
|
|
440
|
+
.replace(/{{forthLevelColor}}/g, config.forth_level_color)
|
|
441
|
+
.replace(/{{firstLevelColor}}/g, client === 'nasco' ? config.second_level_color : config.first_level_color)
|
|
442
|
+
.replace(/{{psychologicalColor}}/gi, metrics.psychological.color)
|
|
443
|
+
.replace(/{{depressionRiskColor}}/gi, metrics.depression.color)
|
|
444
|
+
.replace(/{{anxietyRiskColor}}/gi, metrics.anxiety.color)
|
|
445
|
+
.replace(/{{scoreHeaderColor}}/gi, config.score_header_color)
|
|
446
|
+
.replace(/{{svgIconColor}}/gi, config.svg_icon_color)
|
|
447
|
+
.replace(/{{lowScoreTextColor}}/gi, config.low_score_text_color)
|
|
448
|
+
.replace(/{{scoreDescriptionColor}}/gi, config.score_description_color)
|
|
449
|
+
.replace(/{{listIconsFontColor}}/g, config.list_icons_font_color)
|
|
450
|
+
.replace(/{{generalFontColor}}/g, config.general_font_color)
|
|
451
|
+
.replace(/{{sectionTitleBorderColor}}/g, config.section_title_border_color);
|
|
452
|
+
};
|
|
280
453
|
|
|
281
|
-
|
|
454
|
+
/**
|
|
455
|
+
* Load wellbeing templates based on language direction
|
|
456
|
+
* @param {boolean} isRtl - Whether the language is RTL
|
|
457
|
+
* @param {string} templatePath - Base template path (default: 'wellbeing')
|
|
458
|
+
* @returns {object} Object containing all loaded templates
|
|
459
|
+
*/
|
|
460
|
+
const loadWellbeingTemplates = (isRtl, templatePath = 'wellbeing') => {
|
|
461
|
+
const basePath = path.resolve(__dirname + `/../templates/${templatePath}`);
|
|
462
|
+
return {
|
|
463
|
+
html: fs.readFileSync(
|
|
464
|
+
path.resolve(basePath, isRtl ? 'rtl_no_pages.html' : 'ltr_no_pages.html'),
|
|
465
|
+
'utf8'
|
|
466
|
+
),
|
|
467
|
+
header: fs.readFileSync(path.resolve(basePath, 'blocks/header.html'), 'utf8'),
|
|
468
|
+
footer: fs.readFileSync(path.resolve(basePath, 'blocks/footer.html'), 'utf8'),
|
|
469
|
+
firstSection: fs.readFileSync(path.resolve(basePath, 'blocks/first-section.html'), 'utf8'),
|
|
470
|
+
tips: fs.readFileSync(path.resolve(basePath, 'blocks/tips.html'), 'utf8')
|
|
471
|
+
};
|
|
472
|
+
};
|
|
282
473
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
474
|
+
/**
|
|
475
|
+
* Get user image based on gender and photo availability
|
|
476
|
+
* @param {string} photo - User photo URL
|
|
477
|
+
* @param {number} genderId - Gender ID (90 for female)
|
|
478
|
+
* @returns {string} Path to user image
|
|
479
|
+
*/
|
|
480
|
+
const getUserImage = (photo, genderId) => {
|
|
481
|
+
if (!empty(photo)) {
|
|
482
|
+
return photo;
|
|
289
483
|
}
|
|
290
484
|
|
|
485
|
+
const imageFolder = path.resolve(__dirname + '/../assets/wellbeing/images');
|
|
486
|
+
return genderId === 90
|
|
487
|
+
? path.resolve(imageFolder, 'user-woman.png')
|
|
488
|
+
: path.resolve(imageFolder, 'user-man.png');
|
|
489
|
+
};
|
|
291
490
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
'
|
|
306
|
-
'
|
|
307
|
-
'
|
|
308
|
-
'
|
|
491
|
+
/**
|
|
492
|
+
* Generate dynamic CSS styles for the report
|
|
493
|
+
* @param {string} iconColor - Color for icon elements
|
|
494
|
+
* @returns {string} CSS style string
|
|
495
|
+
*/
|
|
496
|
+
const generateReportStyles = (iconColor) => {
|
|
497
|
+
const styles = {
|
|
498
|
+
'.tips-text': `color: ${config.insights_font_color} !important;`,
|
|
499
|
+
'.first-level-font-color': `color: ${config.general_font_color} !important;`,
|
|
500
|
+
'.first-section-scores': `border-color: ${config.first_section_scores_border_color} !important;`,
|
|
501
|
+
'.disclaimer-text-color': `color: ${config.disclaimer_text_color} !important;`,
|
|
502
|
+
'.first-level-border-color': `border-color: ${config.general_font_color} !important;`,
|
|
503
|
+
'.scores-note .first-level-font-color': `color: ${iconColor} !important;`,
|
|
504
|
+
'.top-box-bg': `background-color: ${config.top_box_background} !important;`,
|
|
505
|
+
'.footer-background': `background-color: ${config.footer_background} !important;`,
|
|
506
|
+
'.report-date, .patient-info, .score-description, .list-text, .report-header, .list-score, .score-details-title': `color: ${config.body_font_color} !important;`,
|
|
507
|
+
'.list-item g, .list-item path': `fill: ${config.list_icons_font_color} !important; stroke: ${config.list_icons_font_color} !important;`,
|
|
508
|
+
'.fifth-level-font-color': `color: ${config.fifth_level_color} !important;`
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
return '<style>' + Object.entries(styles).map(([selector, rules]) => `${selector}{${rules}}`).join('') + '</style>';
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Process part scores and organize by category
|
|
516
|
+
* @param {object} partScores - Raw part scores data
|
|
517
|
+
* @returns {object} Organized scores by category
|
|
518
|
+
*/
|
|
519
|
+
const processPartScores = (partScores) => {
|
|
520
|
+
const categories = {
|
|
521
|
+
lifeStyle: [],
|
|
522
|
+
body: [],
|
|
523
|
+
mind: [],
|
|
524
|
+
healthRisk: []
|
|
309
525
|
};
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
let mind = [];
|
|
313
|
-
let healthRisk = []
|
|
314
|
-
Object.entries(partScores).map(([key, value], index) => {
|
|
526
|
+
|
|
527
|
+
Object.entries(partScores).forEach(([key, items]) => {
|
|
315
528
|
if (key === 'body' || key === 'lifeStyle' || key === 'mind') {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if (empty(value)) {
|
|
321
|
-
console.log("missing icon", name)
|
|
322
|
-
}
|
|
529
|
+
items.forEach(item => {
|
|
530
|
+
const iconName = item['icon'].replace(/\s/g, '');
|
|
531
|
+
const name = iconName.split('.png')[0];
|
|
532
|
+
const iconPath = icons[name];
|
|
323
533
|
|
|
324
|
-
|
|
325
|
-
|
|
534
|
+
if (empty(iconPath)) {
|
|
535
|
+
console.log('Missing icon:', name);
|
|
536
|
+
}
|
|
326
537
|
|
|
538
|
+
const scoreData = {
|
|
539
|
+
percentage: Math.round(item['score'] / 2),
|
|
540
|
+
icon: iconPath,
|
|
541
|
+
scoreText: item['scoreText']
|
|
327
542
|
};
|
|
328
|
-
if (key === 'body') {
|
|
329
|
-
body.push(tempScore)
|
|
330
|
-
} else if (key === 'lifeStyle') {
|
|
331
|
-
lifeStyle.push(tempScore)
|
|
332
|
-
} else if (key === 'mind') {
|
|
333
|
-
mind.push(tempScore)
|
|
334
|
-
} else {
|
|
335
|
-
healthRisk.push(tempScore)
|
|
336
|
-
}
|
|
337
543
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
});
|
|
341
|
-
tempScore['lifeStyle'] = lifeStyle;
|
|
342
|
-
tempScore['body'] = body;
|
|
343
|
-
tempScore['mind'] = mind;
|
|
344
|
-
tempScore['healthRisk'] = healthRisk;
|
|
345
|
-
// $('#content').append("<div class='row scores-parts'>" + renderItems(allScores, partScores) + "</div>");
|
|
346
|
-
debugLog("after signature");
|
|
347
|
-
/*Footer*/
|
|
348
|
-
|
|
349
|
-
$('#content').after("<h5 class='recommended-tips-title first-level-font-color'>" + localeService.t('weRecommend') + "</h5>");
|
|
350
|
-
$('.footer').append(footerTemplateHTML);
|
|
351
|
-
/*tips*/
|
|
352
|
-
let tips = data.insights.tips
|
|
353
|
-
let allTips = []
|
|
354
|
-
let lifeStyleTips = []
|
|
355
|
-
let mindTips = []
|
|
356
|
-
let bodyTips = []
|
|
357
|
-
let i = 0;
|
|
358
|
-
tips.map((key, index) => {
|
|
359
|
-
if (key.partId === 1 || key.partId === 2 || key.partId === 3) {
|
|
360
|
-
if (key.partId === 1) {
|
|
361
|
-
key.type = 'lifeStyleInsights'
|
|
362
|
-
lifeStyleTips.push(key)
|
|
363
|
-
}
|
|
364
|
-
if (key.partId === 2) {
|
|
365
|
-
key.type = 'bodyInsights'
|
|
366
|
-
bodyTips.push(key)
|
|
367
|
-
}
|
|
368
|
-
if (key.partId === 3) {
|
|
369
|
-
key.type = 'mindInsights'
|
|
370
|
-
mindTips.push(key)
|
|
371
|
-
}
|
|
544
|
+
categories[key].push(scoreData);
|
|
545
|
+
});
|
|
372
546
|
}
|
|
373
547
|
});
|
|
374
548
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
allTips.push(mindTips)
|
|
378
|
-
$('.all-tips').append(tipsTemplate)
|
|
379
|
-
allTips.forEach((tip, index) => {
|
|
380
|
-
$(".tips-section .three-column").append("<div class='left-align'>" + renderInsightTips(tip) + "</div>");
|
|
381
|
-
})
|
|
549
|
+
return categories;
|
|
550
|
+
};
|
|
382
551
|
|
|
383
|
-
|
|
552
|
+
/**
|
|
553
|
+
* Organize tips by part ID
|
|
554
|
+
* @param {array} tips - Array of tip objects
|
|
555
|
+
* @returns {object} Tips organized by category
|
|
556
|
+
*/
|
|
557
|
+
const organizeTipsByPart = (tips) => {
|
|
558
|
+
const organized = {
|
|
559
|
+
lifeStyle: [],
|
|
560
|
+
body: [],
|
|
561
|
+
mind: []
|
|
562
|
+
};
|
|
384
563
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
564
|
+
const partMapping = {
|
|
565
|
+
1: 'lifeStyle',
|
|
566
|
+
2: 'body',
|
|
567
|
+
3: 'mind'
|
|
568
|
+
};
|
|
390
569
|
|
|
570
|
+
tips.forEach(tip => {
|
|
571
|
+
const category = partMapping[tip.partId];
|
|
572
|
+
if (category) {
|
|
573
|
+
tip.type = `${category}Insights`;
|
|
574
|
+
organized[category].push(tip);
|
|
575
|
+
}
|
|
576
|
+
});
|
|
391
577
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
fs.mkdirSync(REQUEST_OUTPUT_DIRECTORY)
|
|
395
|
-
}
|
|
578
|
+
return organized;
|
|
579
|
+
};
|
|
396
580
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
581
|
+
/**
|
|
582
|
+
* Render header template with data
|
|
583
|
+
* @param {string} template - Header template
|
|
584
|
+
* @param {object} data - Data object containing patient info
|
|
585
|
+
* @param {string} logo - Logo path
|
|
586
|
+
* @param {string} userImage - User image path
|
|
587
|
+
* @param {string} topInsight - Top insight text
|
|
588
|
+
* @returns {string} Rendered header HTML
|
|
589
|
+
*/
|
|
590
|
+
const renderHeaderTemplate = (template, data, logo, userImage, topInsight) => {
|
|
591
|
+
return template
|
|
592
|
+
.replace('{{logo}}', logo)
|
|
593
|
+
.replace('{{userImage}}', userImage)
|
|
594
|
+
.replace('{{topBoxBg}}', config.top_box_background)
|
|
595
|
+
.replace('{{scoresNote}}', localeService.t('scoresNote'))
|
|
596
|
+
.replace('{{bulbIcon}}', config.bulbIcon)
|
|
597
|
+
.replace('{{reportHeader}}', localeService.t('wellbeingReport'))
|
|
598
|
+
.replace('{{reportHeaderDescription}}', topInsight)
|
|
599
|
+
.replace('{{patientName}}', data.name)
|
|
600
|
+
.replace('{{patientOld}}', data.age)
|
|
601
|
+
.replace('{{yearsOld}}', localeService.t('yearsOld'))
|
|
602
|
+
.replace('{{patientGender}}', data.gender);
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Load extended templates for smart report
|
|
607
|
+
* @param {boolean} isRtl - Whether the language is RTL
|
|
608
|
+
* @param {string} clientName - Client name for template path
|
|
609
|
+
* @param {boolean} hasProfileInfo - Whether profile info exists
|
|
610
|
+
* @returns {object} Object containing all loaded templates
|
|
611
|
+
*/
|
|
612
|
+
const loadExtendedTemplates = (isRtl, clientName, hasProfileInfo = false) => {
|
|
613
|
+
const basePath = path.resolve(__dirname + `/../templates/${clientName}`);
|
|
614
|
+
|
|
615
|
+
const templates = {
|
|
616
|
+
html: fs.readFileSync(
|
|
617
|
+
path.resolve(basePath, isRtl ? 'rtl_no_pages.html' : 'ltr_no_pages.html'),
|
|
618
|
+
'utf8'
|
|
619
|
+
),
|
|
620
|
+
header: fs.readFileSync(path.resolve(basePath, 'blocks/header.html'), 'utf8'),
|
|
621
|
+
pdfHeader: fs.readFileSync(path.resolve(basePath, 'blocks/header-template.html'), 'utf8'),
|
|
622
|
+
footer: fs.readFileSync(path.resolve(basePath, 'blocks/footer.html'), 'utf8'),
|
|
623
|
+
firstSection: fs.readFileSync(path.resolve(basePath, 'blocks/first-section.html'), 'utf8'),
|
|
624
|
+
tips: fs.readFileSync(path.resolve(basePath, 'blocks/tips.html'), 'utf8'),
|
|
625
|
+
patientTable: ''
|
|
403
626
|
};
|
|
404
|
-
}
|
|
405
627
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
OUT_FILE = path.resolve(__dirname + `/../output/${clientName}-pdf` + Date.now() + '.html');
|
|
409
|
-
Pdf_file = `/output/${clientName}-pdf` + Date.now() + '.html'
|
|
410
|
-
let wellbeingData = data.wellbeing ? data.wellbeing : {}
|
|
411
|
-
let profileInfo = data.profileInfo ? data.profileInfo : {}
|
|
412
|
-
let doctorData = data.IsDoctor ? data.Profile : []
|
|
413
|
-
let smartReport = data.SR ? JSON.parse(data.SR) : {}
|
|
414
|
-
if (empty(language)) {
|
|
415
|
-
language = 'en'
|
|
416
|
-
}
|
|
417
|
-
let isRtl = false;
|
|
418
|
-
if (language.indexOf('ar') !== -1) {
|
|
419
|
-
language = 'ar-SA';
|
|
420
|
-
isRtl = true;
|
|
628
|
+
if (hasProfileInfo) {
|
|
629
|
+
templates.patientTable = fs.readFileSync(path.resolve(basePath, 'blocks/patient-table.html'), 'utf8');
|
|
421
630
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
general_font_color = defaultConfig['general-font-color']
|
|
438
|
-
disclaimer_text_color = defaultConfig['disclaimer-text-color']
|
|
439
|
-
top_box_background = defaultConfig['top-box-background']
|
|
440
|
-
footer_background = defaultConfig['footer-background']
|
|
441
|
-
lifestyleImage = path.resolve(__dirname + '/../assets/wellbeing/images/lifestyle-section.png')
|
|
442
|
-
mindImage = path.resolve(__dirname + '/../assets/wellbeing/images/mind-section.png')
|
|
443
|
-
bodyImage = path.resolve(__dirname + '/../assets/wellbeing/images/body-section.png')
|
|
444
|
-
body_font_color = defaultConfig['body_font_color']
|
|
445
|
-
list_icons_font_color = defaultConfig['list_icons_font_color']
|
|
446
|
-
insights_font_color = defaultConfig['insights_font_color']
|
|
447
|
-
/*check if the language is an RTL language*/
|
|
448
|
-
localeService.setLocale(language);
|
|
449
|
-
debugLog("pdf language:", language);
|
|
450
|
-
/*choose the right template*/
|
|
451
|
-
let html = !isRtl ? fs.readFileSync(path.resolve(__dirname + `/../templates/${clientName}/ltr_no_pages.html`), 'utf8') : fs.readFileSync(path.resolve(__dirname + `/../templates/${clientName}/rtl_no_pages.html`), 'utf8');
|
|
452
|
-
let headerTemplate = fs.readFileSync(path.resolve(__dirname + `/../templates/${clientName}/blocks/header.html`), 'utf8');
|
|
453
|
-
let pdfHeader = fs.readFileSync(path.resolve(__dirname + `/../templates/${clientName}/blocks/header-template.html`), 'utf8');
|
|
454
|
-
let patientTableTemplate = ''
|
|
455
|
-
if (!empty(profileInfo)) patientTableTemplate = fs.readFileSync(path.resolve(__dirname + `/../templates/${clientName}/blocks/patient-table.html`), 'utf8');
|
|
456
|
-
|
|
457
|
-
let footerTemplate = fs.readFileSync(path.resolve(__dirname + `/../templates/${clientName}/blocks/footer.html`), 'utf8');
|
|
458
|
-
let firstSectionTemplate = fs.readFileSync(path.resolve(__dirname + `/../templates/${clientName}/blocks/first-section.html`), 'utf8');
|
|
459
|
-
let tipsTemplate = fs.readFileSync(path.resolve(__dirname + `/../templates/${clientName}/blocks/tips.html`), 'utf8');
|
|
460
|
-
/*create the dom element*/
|
|
461
|
-
const dom = new JSDOM(html);
|
|
462
|
-
let historyData = [];
|
|
463
|
-
debugLog("templates loaded");
|
|
464
|
-
let img = logo;
|
|
465
|
-
|
|
466
|
-
pdfHeader = pdfHeader.replace("{{logo}}", img)
|
|
467
|
-
.replace("{{clientName}}", localeService.t(title))
|
|
468
|
-
.replace("{{notificationTitle}}", localeService.t('notificationTitle'));
|
|
469
|
-
|
|
470
|
-
headerTemplate = headerTemplate
|
|
471
|
-
.replace("{{logo}}", img)
|
|
472
|
-
.replace("{{clientName}}", localeService.t(title))
|
|
473
|
-
.replace("{{notificationTitle}}", localeService.t('notificationTitle'));
|
|
474
|
-
let pdfHeaderData = profileInfo;
|
|
475
|
-
let headerData = []
|
|
476
|
-
for (let index = 0; index < 4; index++) {
|
|
477
|
-
const element = profileInfo[index];
|
|
631
|
+
|
|
632
|
+
return templates;
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Generate header info HTML from profile data
|
|
637
|
+
* @param {array} profileInfo - Array of profile information
|
|
638
|
+
* @param {number} count - Number of items to process (default: 4)
|
|
639
|
+
* @returns {string} Generated HTML
|
|
640
|
+
*/
|
|
641
|
+
const generateHeaderInfo = (profileInfo, count = 4) => {
|
|
642
|
+
let html = '';
|
|
643
|
+
|
|
644
|
+
for (let i = 0; i < Math.min(count, profileInfo.length); i++) {
|
|
645
|
+
const element = profileInfo[i];
|
|
478
646
|
if (element) {
|
|
479
|
-
|
|
647
|
+
html += `
|
|
480
648
|
<div class="info-group">
|
|
481
649
|
<div class="info-title">${element.title}:</div>
|
|
482
650
|
<div class="info-value">${element.value}</div>
|
|
@@ -484,222 +652,124 @@ let generateHTMLWellbeingReportWithSmartReport = async (data, isDebugging, clien
|
|
|
484
652
|
`;
|
|
485
653
|
}
|
|
486
654
|
}
|
|
487
|
-
const $ = (require('jquery'))(dom.window);
|
|
488
|
-
$('#content').append(headerTemplate);
|
|
489
|
-
$('.report-info').html(headerData);
|
|
490
|
-
let iconColor = !empty(bulb_icon_font_color) ? bulb_icon_font_color : general_font_color
|
|
491
|
-
/*get the window from the dom to be used as a jquery function*/
|
|
492
|
-
$('#content').append('<style>' + '.tips-text{color: ' + insights_font_color + ' !important;}' + '.first-level-font-color{color:' + general_font_color + ' !important;}' + '.first-section-scores{border-color:' + fifth_level_color + ' !important;}' + '.disclaimer-text-color{color:' + disclaimer_text_color + ' !important;}' + '.first-level-border-color{border-color:' + general_font_color + ' !important;}' + '.scores-note .first-level-font-color{color:' + iconColor + ' !important;}' + '.top-box-bg{background-color: ' + top_box_background + ' !important;}' + '.footer-background{background-color: ' + footer_background + ' !important;}' + '.report-date, .patient-info,.score-description,.list-text,.report-header,.list-score,.score-details-title{color: ' + body_font_color + ' !important;}' + '.list-item g, .list-item path{fill: ' + list_icons_font_color + ' !important;stroke: ' + list_icons_font_color + ' !important;}' + '.fifth-level-font-color{color:' + fifth_level_color + '!important;}' + '</style>');
|
|
493
|
-
if (empty(wellbeingData.photo)) {
|
|
494
|
-
|
|
495
|
-
$('#content').append('<style>' + '.user-profile-img{display: none !important;}' + '</style>');
|
|
496
|
-
|
|
497
|
-
}
|
|
498
|
-
$("#chartsData").append("<script> var RANGE_COLORS = ['" + first_level_color + "','" + second_level_color + "','" + third_level_color + "','" + forth_level_color + "','" + fifth_level_color + "'];</script>");
|
|
499
|
-
let firstSectionlHtml = ''
|
|
500
|
-
if (!empty(wellbeingData)) {
|
|
501
|
-
/* First section */
|
|
502
|
-
let physicalScore = wellbeingData.calculation.physicalScore?.toFixed(1)
|
|
503
|
-
let physicalAmount = ((physicalScore > 40 && physicalScore < 70) ? 310 : (physicalScore >= 70) ? 310 : 300);
|
|
504
|
-
let physicalColor = (physicalScore > 40 && physicalScore < 70) ? third_level_color : (physicalScore >= 70) ? fifth_level_color : client === 'nasco' ? second_level_color : first_level_color;
|
|
505
|
-
|
|
506
|
-
let rotatePhysicalAmount = (physicalScore / 100) * physicalAmount;
|
|
507
|
-
if (physicalScore <= 20) {
|
|
508
|
-
rotatePhysicalAmount += 25
|
|
509
|
-
}
|
|
510
655
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
let depressionRiskScore = wellbeingData.calculation.depressionRiskScore?.toFixed(1)
|
|
516
|
-
const depressionRiskAmount = (depressionRiskScore >= 0 && depressionRiskScore <= 14.8) ? 550 : (depressionRiskScore >= 18.5 && depressionRiskScore <= 33.3) ? 400 : (depressionRiskScore >= 37 && depressionRiskScore <= 51.9) ? 380 : (depressionRiskScore >= 55.6 && depressionRiskScore <= 70.4) ? 370 : (depressionRiskScore >= 74.1 && depressionRiskScore <= 100) ? 330 : 0;
|
|
517
|
-
let depressionRiskColor = (depressionRiskScore > 40 && depressionRiskScore < 70) ? third_level_color : (depressionRiskScore >= 70) ? fifth_level_color : client === 'nasco' ? second_level_color : first_level_color;
|
|
518
|
-
|
|
519
|
-
let rotateDepressionRiskAmount = (depressionRiskScore / 100) * depressionRiskAmount;
|
|
656
|
+
return html;
|
|
657
|
+
};
|
|
520
658
|
|
|
659
|
+
/**
|
|
660
|
+
* Generate extended report styles with additional properties
|
|
661
|
+
* @param {string} iconColor - Color for icon elements
|
|
662
|
+
* @returns {string} CSS style string
|
|
663
|
+
*/
|
|
664
|
+
const generateExtendedReportStyles = (iconColor) => {
|
|
665
|
+
const styles = {
|
|
666
|
+
'.tips-text': `color: ${config.insights_font_color} !important;`,
|
|
667
|
+
'.first-level-font-color': `color: ${config.general_font_color} !important;`,
|
|
668
|
+
'.first-section-scores': `border-color: ${config.section_title_border_color} !important;`,
|
|
669
|
+
'.physical-score-section': `border-color: ${config.section_title_border_color} !important;`,
|
|
670
|
+
'.psychological-score-section, .psychological-score-section1, .psychological-score-section2': `border-color: ${config.body_font_color} !important;`,
|
|
671
|
+
'.row.score-section': `border-color: ${config.section_title_border_color} !important;`,
|
|
672
|
+
'.disclaimer-text-color': `color: ${config.disclaimer_text_color} !important;`,
|
|
673
|
+
'.first-level-border-color': `border-color: ${config.general_font_color} !important;`,
|
|
674
|
+
'.scores-note .first-level-font-color': `color: ${iconColor} !important;`,
|
|
675
|
+
'.top-box-bg': `background-color: ${config.top_box_background} !important;`,
|
|
676
|
+
'.footer-background': `background-color: ${config.footer_background} !important;`,
|
|
677
|
+
'.print-page-footer': `border-color: ${config.footer_border_color} !important;`,
|
|
678
|
+
'.report-date, .patient-info, .score-description, .list-text, .report-header, .list-score, .score-details-title': `color: ${config.body_font_color} !important;`,
|
|
679
|
+
'.list-item g, .list-item path': `fill: ${config.list_icons_font_color} !important; stroke: ${config.list_icons_font_color} !important;`,
|
|
680
|
+
'.fifth-level-font-color': `color: ${config.fifth_level_color} !important;`
|
|
681
|
+
};
|
|
521
682
|
|
|
522
|
-
|
|
523
|
-
|
|
683
|
+
return '<style>' + Object.entries(styles).map(([selector, rules]) => `${selector}{${rules}}`).join('') + '</style>';
|
|
684
|
+
};
|
|
524
685
|
|
|
525
|
-
|
|
686
|
+
/**
|
|
687
|
+
* Process extended part scores with base division
|
|
688
|
+
* @param {object} partScores - Raw part scores data
|
|
689
|
+
* @returns {object} Organized scores by category
|
|
690
|
+
*/
|
|
691
|
+
const processExtendedPartScores = (partScores) => {
|
|
692
|
+
const categories = {
|
|
693
|
+
lifeStyle: [],
|
|
694
|
+
body: [],
|
|
695
|
+
mind: [],
|
|
696
|
+
healthRisk: []
|
|
697
|
+
};
|
|
526
698
|
|
|
527
|
-
|
|
699
|
+
Object.entries(partScores).forEach(([key, items]) => {
|
|
700
|
+
if (key === 'body' || key === 'lifeStyle' || key === 'mind') {
|
|
701
|
+
items.forEach(item => {
|
|
702
|
+
const iconName = item['icon'].replace(/\s/g, '');
|
|
703
|
+
const name = iconName.split('.png')[0];
|
|
704
|
+
const iconPath = icons[name];
|
|
528
705
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
let rotatePsychologicalAmount = (psychologicalScore / 100) * psychologicalAmount;
|
|
533
|
-
if (rotatePsychologicalAmount > 336) {
|
|
534
|
-
rotatePsychologicalAmount = 336
|
|
535
|
-
}
|
|
706
|
+
if (empty(iconPath)) {
|
|
707
|
+
console.log('Missing icon:', name);
|
|
708
|
+
}
|
|
536
709
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
.replace("{{anxietyRiskScore}}", parseFloat(Math.floor(wellbeingData.calculation.anxietyRiskScore)))
|
|
544
|
-
.replace("{{anxietyRiskScoreDecimalPart}}", parseFloat((wellbeingData.calculation.anxietyRiskScore)?.toFixed(1) % 1)?.toFixed(1).substring(1))
|
|
545
|
-
.replace("{{anxietyRiskScoreStatement}}", wellbeingData.calculation.anxietyRiskScoreStatement)
|
|
546
|
-
.replace("{{AnxietyRiskWord}}", localeService.t('anxietyRisk'))
|
|
547
|
-
.replace(/{{rotateAnxietyRiskAmount}}/gi, rotateAnxietyRiskAmount)
|
|
548
|
-
.replace(/{{anxietyRiskColor}}/gi, anxietyRiskColor)
|
|
549
|
-
.replace("{{physicalScore}}", parseFloat(Math.floor(wellbeingData.calculation.physicalScore)))
|
|
550
|
-
.replace("{{physicalScoreDecimalPart}}", parseFloat((wellbeingData.calculation.physicalScore)?.toFixed(1) % 1)?.toFixed(1).substring(1))
|
|
551
|
-
.replace("{{physicalScoreStatement}}", wellbeingData.calculation.physicalScoreStatement)
|
|
552
|
-
.replace("{{PhysicalWord}}", localeService.t('physicalScore'))
|
|
553
|
-
.replace(/{{rotatePhysicalAmount}}/gi, rotatePhysicalAmount)
|
|
554
|
-
.replace(/{{physicalColor}}/gi, physicalColor)
|
|
555
|
-
.replace("{{ScoreText}}", localeService.t('scoreText'))
|
|
556
|
-
.replace("{{lowScoreText}}", localeService.t('lowScoreText'))
|
|
557
|
-
.replace("{{PhysicalWellbeingText}}", localeService.t('PhysicalWellbeingText'))
|
|
558
|
-
.replace("{{PsychologicalWellbeingText}}", localeService.t('PsychologicalWellbeingText'))
|
|
559
|
-
.replace("{{psychologicalScore}}", Math.floor(wellbeingData.calculation.psychologicalScore))
|
|
560
|
-
.replace("{{psychologicalScoreDecimalPart}}", parseFloat((wellbeingData.calculation.psychologicalScore)?.toFixed(1) % 1)?.toFixed(1).substring(1))
|
|
561
|
-
.replace("{{psychologicalWord}}", localeService.t('psychological'))
|
|
562
|
-
.replace("{{psychologicalScoreStatement}}", wellbeingData.calculation.psychologicalScoreStatement)
|
|
563
|
-
.replace(/{{rotatePsychologicalAmount}}/gi, rotatePsychologicalAmount)
|
|
564
|
-
.replace(/{{fifthLevelColor}}/g, fifth_level_color)
|
|
565
|
-
.replace(/{{thirdLevelColor}}/g, third_level_color)
|
|
566
|
-
.replace(/{{secondLevelColor}}/g, second_level_color)
|
|
567
|
-
.replace(/{{forthLevelColor}}/g, forth_level_color)
|
|
568
|
-
.replace(/{{firstLevelColor}}/g, client === 'nasco' ? second_level_color : first_level_color)
|
|
569
|
-
.replace("{{psychologicalColor}}", psychologicalColor)
|
|
570
|
-
.replace(/{{listIconsFontColor}}/g, list_icons_font_color);
|
|
571
|
-
$('.first-section-scores').html(firstSectionlHtml);
|
|
572
|
-
if (parseFloat(psychologicalScore) > 40 && parseFloat(psychologicalScore) < 70) {
|
|
573
|
-
$(".first-section-scores .psychological-score-section .part-1,.first-section-scores .psychological-score-section .part-3").addClass('inactive')
|
|
574
|
-
} else if (psychologicalScore >= 70) {
|
|
575
|
-
$(".first-section-scores .psychological-score-section .part-3,.first-section-scores .psychological-score-section .part-2").addClass('inactive')
|
|
576
|
-
} else {
|
|
577
|
-
$(".first-section-scores .psychological-score-section .part-1,.first-section-scores .psychological-score-section .part-2").addClass('inactive')
|
|
578
|
-
}
|
|
710
|
+
const scoreData = {
|
|
711
|
+
percentage: Math.round(item['score'] / (item['base'] / 5)),
|
|
712
|
+
icon: iconPath,
|
|
713
|
+
scoreText: item['scoreText'],
|
|
714
|
+
key
|
|
715
|
+
};
|
|
579
716
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
} else if (depressionRiskScore >= 70) {
|
|
583
|
-
$(".first-section-scores .psychological-score-section1 .part-3,.first-section-scores .psychological-score-section1 .part-2").addClass('inactive')
|
|
584
|
-
} else {
|
|
585
|
-
$(".first-section-scores .psychological-score-section1 .part-1,.first-section-scores .psychological-score-section1 .part-2").addClass('inactive')
|
|
717
|
+
categories[key].push(scoreData);
|
|
718
|
+
});
|
|
586
719
|
}
|
|
720
|
+
});
|
|
587
721
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
$(".first-section-scores .psychological-score-section2 .part-1,.first-section-scores .psychological-score-section2 .part-3").addClass('inactive')
|
|
591
|
-
} else if (anxietyRiskScore >= 70) {
|
|
592
|
-
$(".first-section-scores .psychological-score-section2 .part-3,.first-section-scores .psychological-score-section2 .part-2").addClass('inactive')
|
|
593
|
-
} else {
|
|
594
|
-
$(".first-section-scores .psychological-score-section2 .part-1,.first-section-scores .psychological-score-section2 .part-2").addClass('inactive')
|
|
595
|
-
}
|
|
722
|
+
return categories;
|
|
723
|
+
};
|
|
596
724
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
725
|
+
/**
|
|
726
|
+
* Organize tips by part ID for extended report
|
|
727
|
+
* @param {array} tips - Array of tip objects
|
|
728
|
+
* @returns {object} Tips organized by category
|
|
729
|
+
*/
|
|
730
|
+
const organizeExtendedTipsByPart = (tips) => {
|
|
731
|
+
const organized = {
|
|
732
|
+
lifeStyle: [],
|
|
733
|
+
body: [],
|
|
734
|
+
mind: []
|
|
735
|
+
};
|
|
604
736
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
let tempScore = {
|
|
611
|
-
'lifeStyle': partScores.lifeStyle,
|
|
612
|
-
'body': partScores.body,
|
|
613
|
-
'mind': partScores.mind,
|
|
614
|
-
'healthRisk': partScores.healthRisk
|
|
615
|
-
};
|
|
616
|
-
let body = [];
|
|
617
|
-
let lifeStyle = [];
|
|
618
|
-
let mind = [];
|
|
619
|
-
let healthRisk = []
|
|
620
|
-
Object.entries(partScores).map(([key, value], index) => {
|
|
621
|
-
if (key === 'body' || key === 'lifeStyle' || key === 'mind') {
|
|
622
|
-
partScores[key].map((item, index) => {
|
|
623
|
-
let iconName = item['icon'].split(" ").join("")
|
|
624
|
-
let name = iconName.split(".png", [1]);
|
|
625
|
-
let value = icons[name];
|
|
626
|
-
if (empty(value)) {
|
|
627
|
-
console.log("missing icon", name)
|
|
628
|
-
}
|
|
629
|
-
let tempScore = {
|
|
630
|
-
percentage: Math.round(item['score'] / (item['base'] / 5)),
|
|
631
|
-
icon: value,
|
|
632
|
-
scoreText: item['scoreText'],
|
|
633
|
-
key
|
|
634
|
-
};
|
|
635
|
-
if (key === 'body') {
|
|
636
|
-
body.push(tempScore)
|
|
637
|
-
} else if (key === 'lifeStyle') {
|
|
638
|
-
lifeStyle.push(tempScore)
|
|
639
|
-
} else if (key === 'mind') {
|
|
640
|
-
mind.push(tempScore)
|
|
641
|
-
} else {
|
|
642
|
-
healthRisk.push(tempScore)
|
|
643
|
-
}
|
|
644
|
-
})
|
|
645
|
-
}
|
|
646
|
-
});
|
|
647
|
-
tempScore['lifeStyle'] = lifeStyle;
|
|
648
|
-
tempScore['body'] = body;
|
|
649
|
-
tempScore['mind'] = mind;
|
|
650
|
-
tempScore['healthRisk'] = healthRisk;
|
|
651
|
-
$('#content').append("<div class='row scores-parts' style='width:99.5% !important;'>" + renderItems(allScores, partScores) + "</div>");
|
|
652
|
-
debugLog("after signature");
|
|
653
|
-
let tips = wellbeingData.insights.tips
|
|
654
|
-
if (!empty(tips)) $('#content').after("<h5 class='recommended-tips-title'>" + localeService.t('weRecommend') + "</h5>");
|
|
655
|
-
let allTips = []
|
|
656
|
-
let lifeStyleTips = []
|
|
657
|
-
let mindTips = []
|
|
658
|
-
let bodyTips = []
|
|
659
|
-
let i = 0;
|
|
660
|
-
if (!empty(tips)) {
|
|
661
|
-
tips.map((key, index) => {
|
|
662
|
-
if (key.partId === 1 || key.partId === 2 || key.partId === 3) {
|
|
663
|
-
if (key.partId === 1) {
|
|
664
|
-
key.type = 'lifeStyleInsights'
|
|
665
|
-
lifeStyleTips.push(key)
|
|
666
|
-
}
|
|
667
|
-
if (key.partId === 3) {
|
|
668
|
-
key.type = 'bodyInsights'
|
|
669
|
-
bodyTips.push(key)
|
|
670
|
-
}
|
|
671
|
-
if (key.partId === 2) {
|
|
672
|
-
key.type = 'mindInsights'
|
|
673
|
-
mindTips.push(key)
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
});
|
|
737
|
+
const partMapping = {
|
|
738
|
+
1: 'lifeStyle',
|
|
739
|
+
3: 'body',
|
|
740
|
+
2: 'mind'
|
|
741
|
+
};
|
|
677
742
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
$(".tips-section .three-column").append("<div class='left-align'>" + renderInsightTips(tip, client) + "</div>");
|
|
684
|
-
})
|
|
743
|
+
tips.forEach(tip => {
|
|
744
|
+
const category = partMapping[tip.partId];
|
|
745
|
+
if (category) {
|
|
746
|
+
tip.type = `${category}Insights`;
|
|
747
|
+
organized[category].push(tip);
|
|
685
748
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
}
|
|
689
|
-
if (empty(wellbeingData) || empty(wellbeingData?.insights?.tips)) {
|
|
749
|
+
});
|
|
690
750
|
|
|
691
|
-
|
|
692
|
-
|
|
751
|
+
return organized;
|
|
752
|
+
};
|
|
693
753
|
|
|
754
|
+
/**
|
|
755
|
+
* Render patient table from profile info
|
|
756
|
+
* @param {array} profileInfo - Profile information array
|
|
757
|
+
* @param {number} startIndex - Start index for processing (default: 4)
|
|
758
|
+
* @returns {string} Rendered patient table HTML
|
|
759
|
+
*/
|
|
760
|
+
const renderPatientTable = (profileInfo, startIndex = 4) => {
|
|
761
|
+
if (empty(profileInfo) || profileInfo.length <= startIndex) {
|
|
762
|
+
return '';
|
|
694
763
|
}
|
|
695
764
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
765
|
+
let details1HTML = '';
|
|
766
|
+
let details2HTML = '';
|
|
767
|
+
const midPoint = Math.min(8, profileInfo.length);
|
|
700
768
|
|
|
701
|
-
|
|
702
|
-
|
|
769
|
+
// First column (items 4-7)
|
|
770
|
+
for (let i = startIndex; i < midPoint; i++) {
|
|
771
|
+
const item = profileInfo[i];
|
|
772
|
+
if (item) {
|
|
703
773
|
const title = escapeHTML(item.title);
|
|
704
774
|
details1HTML += `
|
|
705
775
|
<div class="details-row">
|
|
@@ -708,342 +778,700 @@ let generateHTMLWellbeingReportWithSmartReport = async (data, isDebugging, clien
|
|
|
708
778
|
</div>
|
|
709
779
|
`;
|
|
710
780
|
}
|
|
781
|
+
}
|
|
711
782
|
|
|
712
|
-
|
|
713
|
-
|
|
783
|
+
// Second column (items 8+)
|
|
784
|
+
for (let i = midPoint; i < profileInfo.length; i++) {
|
|
785
|
+
const item = profileInfo[i];
|
|
786
|
+
if (item) {
|
|
714
787
|
const title = escapeHTML(item.title);
|
|
715
788
|
details2HTML += `
|
|
716
789
|
<div class="details-row">
|
|
717
|
-
<div class="details-label" style="
|
|
790
|
+
<div class="details-label" style="unicode-bidi: isolate;">${wrapLTR(title)}</div>
|
|
718
791
|
<div class="details-value">${item.value}</div>
|
|
719
792
|
</div>
|
|
720
793
|
`;
|
|
721
794
|
}
|
|
722
|
-
$('.patient-table').html(`<div class="patient-details"> <div class="details-column"> ${details1HTML} </div> <div class="details-column" style="margin-left:1%"> ${details2HTML} </div> </div>`);
|
|
723
795
|
}
|
|
724
796
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
797
|
+
return `
|
|
798
|
+
<div class="patient-details">
|
|
799
|
+
<div class="details-column">${details1HTML}</div>
|
|
800
|
+
<div class="details-column" style="margin-left:1%">${details2HTML}</div>
|
|
801
|
+
</div>
|
|
802
|
+
`;
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Render doctor quiz details
|
|
807
|
+
* @param {array} doctorData - Doctor data array
|
|
808
|
+
* @returns {string} Rendered doctor details HTML
|
|
809
|
+
*/
|
|
810
|
+
const renderDoctorDetails = (doctorData) => {
|
|
811
|
+
if (empty(doctorData)) {
|
|
812
|
+
return '';
|
|
813
|
+
}
|
|
728
814
|
|
|
729
|
-
|
|
815
|
+
let details1HTML = '';
|
|
816
|
+
let details2HTML = '';
|
|
817
|
+
const halfLength = Math.ceil(doctorData.length / 2);
|
|
730
818
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
819
|
+
// First column
|
|
820
|
+
for (let i = 0; i < halfLength; i++) {
|
|
821
|
+
const item = doctorData[i];
|
|
822
|
+
details1HTML += `
|
|
734
823
|
<div class="details-row page-break">
|
|
735
824
|
<div class="question-title">${item.title}</div>
|
|
736
825
|
<div class="details-value">${item.value}</div>
|
|
737
826
|
</div>
|
|
738
827
|
`;
|
|
739
|
-
|
|
828
|
+
}
|
|
740
829
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
830
|
+
// Second column
|
|
831
|
+
for (let i = halfLength; i < doctorData.length; i++) {
|
|
832
|
+
const item = doctorData[i];
|
|
833
|
+
details2HTML += `
|
|
744
834
|
<div class="details-row page-break">
|
|
745
835
|
<div class="question-title">${item.title}</div>
|
|
746
836
|
<div class="details-value">${item.value}</div>
|
|
747
837
|
</div>
|
|
748
838
|
`;
|
|
749
|
-
|
|
839
|
+
}
|
|
750
840
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
</div>
|
|
756
|
-
<div class="details-column" style="margin-left: 1%">
|
|
757
|
-
${details2HTML}
|
|
758
|
-
</div>
|
|
841
|
+
return `
|
|
842
|
+
<p class="resume-title">Quiz Answers</p>
|
|
843
|
+
<div class="details-column">${details1HTML}</div>
|
|
844
|
+
<div class="details-column" style="margin-left: 1%">${details2HTML}</div>
|
|
759
845
|
`;
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Render PDF header template
|
|
850
|
+
* @param {string} template - PDF header template
|
|
851
|
+
* @param {string} logo - Logo path
|
|
852
|
+
* @param {string} title - Client title
|
|
853
|
+
* @returns {string} Rendered PDF header
|
|
854
|
+
*/
|
|
855
|
+
const renderPdfHeaderTemplate = (template, logo, title) => {
|
|
856
|
+
return template
|
|
857
|
+
.replace('{{logo}}', logo)
|
|
858
|
+
.replace('{{clientName}}', localeService.t(title))
|
|
859
|
+
.replace('{{notificationTitle}}', localeService.t('notificationTitle'))
|
|
860
|
+
.replace(/{{sectionTitleBorderColor}}/g, config.section_title_border_color);
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Render basic header template for extended report
|
|
865
|
+
* @param {string} template - Header template
|
|
866
|
+
* @param {string} logo - Logo path
|
|
867
|
+
* @param {string} title - Client title
|
|
868
|
+
* @returns {string} Rendered header
|
|
869
|
+
*/
|
|
870
|
+
const renderBasicHeaderTemplate = (template, logo, title) => {
|
|
871
|
+
return template
|
|
872
|
+
.replace('{{logo}}', logo)
|
|
873
|
+
.replace('{{clientName}}', localeService.t(title))
|
|
874
|
+
.replace('{{notificationTitle}}', localeService.t('notificationTitle'));
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
/***
|
|
878
|
+
* HTML file generator to fill the html template
|
|
879
|
+
* based on the json wellbeingData from the POST request
|
|
880
|
+
*/
|
|
881
|
+
/**
|
|
882
|
+
* Generate HTML wellbeing report
|
|
883
|
+
* @param {object} data - Report data
|
|
884
|
+
* @param {boolean} isDebugging - Debug mode flag
|
|
885
|
+
* @param {string} clientName - Client name for configuration
|
|
886
|
+
* @param {string} language - Report language
|
|
887
|
+
* @returns {object} Generated report data
|
|
888
|
+
*/
|
|
889
|
+
let generateHTMLWellbeingReport = async (data, isDebugging, clientName, language) => {
|
|
890
|
+
// Setup
|
|
891
|
+
debug = isDebugging;
|
|
892
|
+
const client = clientName || 'default';
|
|
893
|
+
OUT_FILE = path.resolve(__dirname + `/../output/${client}-pdf-` + Date.now() + '.html');
|
|
894
|
+
Pdf_file = `/output/${client}-pdf-` + Date.now() + '.html';
|
|
895
|
+
|
|
896
|
+
// Language configuration
|
|
897
|
+
language = empty(language) ? 'en' : language;
|
|
898
|
+
const isRtl = language.indexOf('ar') !== -1;
|
|
899
|
+
if (isRtl) language = 'ar-SA';
|
|
900
|
+
|
|
901
|
+
// Load client configuration
|
|
902
|
+
loadClientConfig(client);
|
|
903
|
+
localeService.setLocale(language);
|
|
904
|
+
debugLog("pdf language:", language);
|
|
760
905
|
|
|
761
|
-
|
|
906
|
+
// Load templates
|
|
907
|
+
const templates = loadWellbeingTemplates(isRtl);
|
|
908
|
+
debugLog("templates loaded");
|
|
909
|
+
|
|
910
|
+
// Setup DOM
|
|
911
|
+
const dom = new JSDOM(templates.html);
|
|
912
|
+
const $ = (require('jquery'))(dom.window);
|
|
913
|
+
const historyData = [];
|
|
914
|
+
|
|
915
|
+
// Prepare data
|
|
916
|
+
const userImage = getUserImage(data.photo, data.genderId);
|
|
917
|
+
const topInsight = data.lingualReading?.statement || localeService.t('reportHeaderDescription');
|
|
918
|
+
|
|
919
|
+
// Render header
|
|
920
|
+
const renderedHeader = renderHeaderTemplate(
|
|
921
|
+
templates.header,
|
|
922
|
+
data,
|
|
923
|
+
config.logo,
|
|
924
|
+
userImage,
|
|
925
|
+
topInsight
|
|
926
|
+
);
|
|
927
|
+
|
|
928
|
+
// Apply styles
|
|
929
|
+
const iconColor = config.bulb_icon_font_color || config.general_font_color;
|
|
930
|
+
const dynamicStyles = generateReportStyles(iconColor);
|
|
931
|
+
|
|
932
|
+
$('#content').append(renderedHeader);
|
|
933
|
+
$('#content').append(dynamicStyles);
|
|
934
|
+
|
|
935
|
+
// Handle missing photo
|
|
936
|
+
if (empty(data.photo)) {
|
|
937
|
+
$('#content').append('<style>.user-profile-img{display: none !important;}</style>');
|
|
762
938
|
}
|
|
763
|
-
$('.report-info').html(headerData);
|
|
764
939
|
|
|
765
|
-
|
|
940
|
+
// Add chart colors
|
|
941
|
+
$("#chartsData").append(
|
|
942
|
+
`<script>var RANGE_COLORS = ['${config.first_level_color}','${config.second_level_color}','${config.third_level_color}','${config.forth_level_color}','${config.fifth_level_color}'];</script>`
|
|
943
|
+
);
|
|
944
|
+
|
|
945
|
+
// Render first section
|
|
946
|
+
const physicalMetrics = calculateScoreMetrics(data.calculation.physicalScore, client);
|
|
947
|
+
const psychologicalMetrics = calculateScoreMetrics(data.calculation.psychologicalScore, client);
|
|
948
|
+
|
|
949
|
+
const firstSectionHtml = renderFirstSection(
|
|
950
|
+
templates.firstSection,
|
|
951
|
+
data.calculation,
|
|
952
|
+
physicalMetrics,
|
|
953
|
+
psychologicalMetrics,
|
|
954
|
+
client
|
|
955
|
+
);
|
|
956
|
+
|
|
957
|
+
$('.first-section-scores').html(firstSectionHtml);
|
|
958
|
+
applyScoreInactiveClasses($, psychologicalMetrics.score, 'psychological-score-section');
|
|
959
|
+
applyScoreInactiveClasses($, physicalMetrics.score, 'physical-score-section');
|
|
960
|
+
$(".score-main").addClass(client).addClass(language);
|
|
961
|
+
|
|
962
|
+
// Process part scores
|
|
963
|
+
const processedScores = processPartScores(data.partsScore);
|
|
964
|
+
debugLog("after signature");
|
|
965
|
+
|
|
966
|
+
// Render footer
|
|
967
|
+
const footerHtml = templates.footer
|
|
968
|
+
.replace("{{Disclaimer}}", localeService.t('Disclaimer'))
|
|
969
|
+
.replace("{{Disclaimer Text}}", localeService.t('Disclaimer Text'));
|
|
970
|
+
|
|
971
|
+
$('#content').after(`<h5 class='recommended-tips-title first-level-font-color'>${localeService.t('weRecommend')}</h5>`);
|
|
972
|
+
$('.footer').append(footerHtml);
|
|
766
973
|
|
|
767
|
-
|
|
974
|
+
// Process and render tips
|
|
975
|
+
const organizedTips = organizeTipsByPart(data.insights.tips);
|
|
976
|
+
$('.all-tips').append(templates.tips);
|
|
977
|
+
|
|
978
|
+
Object.values(organizedTips).forEach(tipCategory => {
|
|
979
|
+
$(".tips-section .three-column").append(
|
|
980
|
+
`<div class='left-align'>${renderInsightTips(tipCategory)}</div>`
|
|
981
|
+
);
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
// Add scores data for charts
|
|
985
|
+
$("#chartsData").append(`<script>var allScores = ${JSON.stringify(processedScores)};</script>`);
|
|
986
|
+
|
|
987
|
+
// Ensure output directories exist
|
|
988
|
+
const OUTPUT_DIRECTORY = path.resolve(`${__dirname}/../output`);
|
|
768
989
|
if (!fs.existsSync(OUTPUT_DIRECTORY)) {
|
|
769
|
-
fs.mkdirSync(OUTPUT_DIRECTORY)
|
|
990
|
+
fs.mkdirSync(OUTPUT_DIRECTORY);
|
|
770
991
|
}
|
|
771
|
-
await promisify(fs.writeFile)(OUT_FILE, dom.serialize());
|
|
772
992
|
|
|
773
|
-
|
|
993
|
+
const REQUEST_OUTPUT_DIRECTORY = path.resolve(`${__dirname}/../output/request`);
|
|
774
994
|
if (!fs.existsSync(REQUEST_OUTPUT_DIRECTORY)) {
|
|
775
|
-
fs.mkdirSync(REQUEST_OUTPUT_DIRECTORY)
|
|
995
|
+
fs.mkdirSync(REQUEST_OUTPUT_DIRECTORY);
|
|
776
996
|
}
|
|
997
|
+
|
|
998
|
+
// Replace footer border color placeholder for PHA
|
|
999
|
+
let finalHtml = dom.serialize();
|
|
1000
|
+
if (client.toLowerCase() === 'pha') {
|
|
1001
|
+
finalHtml = finalHtml.replace(/\{\{footerBorderColor\}\}/g, config.footer_border_color);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Write file
|
|
1005
|
+
await promisify(fs.writeFile)(OUT_FILE, finalHtml);
|
|
1006
|
+
|
|
777
1007
|
return {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
"profileInfo": profileInfo,
|
|
784
|
-
"language": language,
|
|
785
|
-
"pdfHeader": cleanStyledHeader(pdfHeader, profileInfo, language),
|
|
1008
|
+
html: finalHtml,
|
|
1009
|
+
outputFile: OUT_FILE,
|
|
1010
|
+
header: renderedHeader,
|
|
1011
|
+
footer: footerHtml,
|
|
1012
|
+
historyData
|
|
786
1013
|
};
|
|
787
|
-
}
|
|
1014
|
+
};
|
|
788
1015
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1016
|
+
/**
|
|
1017
|
+
* Generate HTML wellbeing report with smart report integration
|
|
1018
|
+
* @param {object} data - Report data including wellbeing, profile, and smart report
|
|
1019
|
+
* @param {boolean} isDebugging - Debug mode flag
|
|
1020
|
+
* @param {string} clientName - Client name for configuration
|
|
1021
|
+
* @param {string} language - Report language
|
|
1022
|
+
* @param {array} selectedLabs - Selected labs default is []
|
|
1023
|
+
* @param {boolean} showHeaderLogo - Show header logo default is true
|
|
1024
|
+
* @returns {object} Generated report data
|
|
1025
|
+
*/
|
|
1026
|
+
let generateHTMLWellbeingReportWithSmartReport = async (data, isDebugging, clientName, language, selectedLabs = [], showHeaderLogo = true) => {
|
|
1027
|
+
// Setup
|
|
1028
|
+
debug = isDebugging;
|
|
1029
|
+
const client = clientName || 'default';
|
|
1030
|
+
OUT_FILE = path.resolve(__dirname + `/../output/${client}-pdf-` + Date.now() + '.html');
|
|
1031
|
+
Pdf_file = `/output/${client}-pdf-` + Date.now() + '.html';
|
|
1032
|
+
|
|
1033
|
+
// Extract data
|
|
1034
|
+
const wellbeingData = data.wellbeing || {};
|
|
1035
|
+
const profileInfo = data.profileInfo || {};
|
|
1036
|
+
const doctorData = data.IsDoctor ? data.Profile : [];
|
|
1037
|
+
const smartReport = data.SR ? JSON.parse(data.SR) : {};
|
|
1038
|
+
|
|
1039
|
+
// Language configuration
|
|
1040
|
+
language = empty(language) ? 'en' : language;
|
|
1041
|
+
const isRtl = language.indexOf('ar') !== -1;
|
|
1042
|
+
if (isRtl) language = 'ar-SA';
|
|
1043
|
+
|
|
1044
|
+
// Load client configuration
|
|
1045
|
+
loadClientConfig(client);
|
|
1046
|
+
localeService.setLocale(language);
|
|
1047
|
+
debugLog("pdf language:", language);
|
|
800
1048
|
|
|
801
|
-
|
|
1049
|
+
// Load templates
|
|
1050
|
+
const templates = loadExtendedTemplates(isRtl, client, !empty(profileInfo));
|
|
1051
|
+
debugLog("templates loaded");
|
|
802
1052
|
|
|
803
|
-
|
|
1053
|
+
// Setup DOM
|
|
1054
|
+
const dom = new JSDOM(templates.html);
|
|
1055
|
+
const $ = (require('jquery'))(dom.window);
|
|
1056
|
+
const historyData = [];
|
|
804
1057
|
|
|
805
|
-
|
|
806
|
-
|
|
1058
|
+
// Render templates
|
|
1059
|
+
const renderedPdfHeader = renderPdfHeaderTemplate(templates.pdfHeader, config.logo, config.title);
|
|
1060
|
+
const renderedHeader = renderBasicHeaderTemplate(templates.header, config.logo, config.title);
|
|
1061
|
+
const headerInfoHtml = generateHeaderInfo(profileInfo, 4);
|
|
807
1062
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1063
|
+
// Apply content and styles
|
|
1064
|
+
$('#content').append(renderedHeader);
|
|
1065
|
+
$('.report-info').html(headerInfoHtml);
|
|
1066
|
+
|
|
1067
|
+
const iconColor = config.bulb_icon_font_color || config.general_font_color;
|
|
1068
|
+
const dynamicStyles = generateExtendedReportStyles(iconColor);
|
|
1069
|
+
$('#content').append(dynamicStyles);
|
|
1070
|
+
|
|
1071
|
+
// Handle missing photo
|
|
1072
|
+
if (empty(wellbeingData.photo)) {
|
|
1073
|
+
$('#content').append('<style>.user-profile-img{display: none !important;}</style>');
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// Add chart colors
|
|
1077
|
+
$("#chartsData").append(
|
|
1078
|
+
`<script>var RANGE_COLORS = ['${config.first_level_color}','${config.second_level_color}','${config.third_level_color}','${config.forth_level_color}','${config.fifth_level_color}'];</script>`
|
|
1079
|
+
);
|
|
1080
|
+
|
|
1081
|
+
// Render wellbeing data if available
|
|
1082
|
+
let firstSectionHtml = '';
|
|
1083
|
+
if (!empty(wellbeingData)) {
|
|
1084
|
+
// Calculate all metrics
|
|
1085
|
+
const metrics = {
|
|
1086
|
+
physical: calculatePhysicalScoreMetrics(wellbeingData.calculation.physicalScore, client),
|
|
1087
|
+
psychological: calculateScoreMetrics(wellbeingData.calculation.psychologicalScore, client),
|
|
1088
|
+
depression: calculateExtendedScoreMetrics(wellbeingData.calculation.depressionRiskScore, 'depression', client),
|
|
1089
|
+
anxiety: calculateExtendedScoreMetrics(wellbeingData.calculation.anxietyRiskScore, 'anxiety', client)
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
// Render first section
|
|
1093
|
+
firstSectionHtml = renderExtendedFirstSection(
|
|
1094
|
+
templates.firstSection,
|
|
1095
|
+
wellbeingData.calculation,
|
|
1096
|
+
metrics,
|
|
1097
|
+
client
|
|
1098
|
+
);
|
|
1099
|
+
|
|
1100
|
+
$('.first-section-scores').html(firstSectionHtml);
|
|
1101
|
+
|
|
1102
|
+
// Apply inactive classes
|
|
1103
|
+
applyScoreInactiveClasses($, metrics.psychological.score, 'psychological-score-section');
|
|
1104
|
+
applyScoreInactiveClasses($, metrics.depression.score, 'psychological-score-section1');
|
|
1105
|
+
applyScoreInactiveClasses($, metrics.anxiety.score, 'psychological-score-section2');
|
|
1106
|
+
applyScoreInactiveClasses($, metrics.physical.score, 'physical-score-section');
|
|
1107
|
+
|
|
1108
|
+
$(".score-main").addClass(client).addClass(language);
|
|
1109
|
+
|
|
1110
|
+
// Process part scores
|
|
1111
|
+
const processedScores = processExtendedPartScores(wellbeingData.partsScore);
|
|
1112
|
+
const allScores = { lifeStyle: [], body: [], mind: [], healthRisk: [] };
|
|
1113
|
+
|
|
1114
|
+
$('#content').append(
|
|
1115
|
+
`<div class='row scores-parts' style='width:99.5% !important;'>${renderItems(allScores, wellbeingData.partsScore)}</div>`
|
|
1116
|
+
);
|
|
1117
|
+
debugLog("after signature");
|
|
1118
|
+
|
|
1119
|
+
// Process and render tips
|
|
1120
|
+
const tips = wellbeingData.insights.tips;
|
|
1121
|
+
if (!empty(tips)) {
|
|
1122
|
+
$('#content').after(`<h5 class='recommended-tips-title'>${localeService.t('weRecommend')}</h5>`);
|
|
814
1123
|
|
|
1124
|
+
const organizedTips = organizeExtendedTipsByPart(tips);
|
|
1125
|
+
$('.all-tips').append(templates.tips);
|
|
1126
|
+
|
|
1127
|
+
Object.values(organizedTips).forEach(tipCategory => {
|
|
1128
|
+
$(".tips-section .three-column").append(
|
|
1129
|
+
`<div class='left-align'>${renderInsightTips(tipCategory, client)}</div>`
|
|
1130
|
+
);
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// Add scores data for charts
|
|
1135
|
+
$("#chartsData").append(`<script>var allScores = ${JSON.stringify(processedScores)};</script>`);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// Handle empty wellbeing data
|
|
1139
|
+
if (empty(wellbeingData) || empty(wellbeingData?.insights?.tips)) {
|
|
1140
|
+
$('.footer-background').css('display', 'none');
|
|
1141
|
+
if (empty(wellbeingData)) {
|
|
1142
|
+
$('.first-section-scores').css('display', 'none');
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// Render patient table
|
|
1147
|
+
if (!empty(profileInfo)) {
|
|
1148
|
+
const patientTableHtml = renderPatientTable(profileInfo, 4);
|
|
1149
|
+
$('.patient-table').html(patientTableHtml);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// Render doctor details
|
|
1153
|
+
if (!empty(doctorData)) {
|
|
1154
|
+
const doctorDetailsHtml = renderDoctorDetails(doctorData);
|
|
1155
|
+
$('.doctor-details').html(doctorDetailsHtml);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// Update report info
|
|
1159
|
+
$('.report-info').html(headerInfoHtml);
|
|
1160
|
+
|
|
1161
|
+
// Add smart report if available
|
|
1162
|
+
if (!empty(smartReport)) {
|
|
1163
|
+
$('#smart-report').append(generateReportHtml(smartReport, isRtl, false, false, language).html);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// Ensure output directories exist
|
|
1167
|
+
const OUTPUT_DIRECTORY = path.resolve(`${__dirname}/../output`);
|
|
1168
|
+
if (!fs.existsSync(OUTPUT_DIRECTORY)) {
|
|
1169
|
+
fs.mkdirSync(OUTPUT_DIRECTORY);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
const REQUEST_OUTPUT_DIRECTORY = path.resolve(`${__dirname}/../output/request`);
|
|
1173
|
+
if (!fs.existsSync(REQUEST_OUTPUT_DIRECTORY)) {
|
|
1174
|
+
fs.mkdirSync(REQUEST_OUTPUT_DIRECTORY);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// Replace footer border color placeholder for PHA
|
|
1178
|
+
let finalHtml = dom.serialize();
|
|
1179
|
+
if (client.toLowerCase() === 'pha') {
|
|
1180
|
+
finalHtml = finalHtml.replace(/\{\{footerBorderColor\}\}/g, config.footer_border_color);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// Write file
|
|
1184
|
+
await promisify(fs.writeFile)(OUT_FILE, finalHtml);
|
|
1185
|
+
|
|
1186
|
+
return {
|
|
1187
|
+
html: finalHtml,
|
|
1188
|
+
outputFile: OUT_FILE,
|
|
1189
|
+
header: renderedHeader,
|
|
1190
|
+
footer: templates.footer,
|
|
1191
|
+
historyData,
|
|
1192
|
+
profileInfo,
|
|
1193
|
+
language,
|
|
1194
|
+
pdfHeader: cleanStyledHeader(renderedPdfHeader, profileInfo, language, clientName, selectedLabs, showHeaderLogo)
|
|
1195
|
+
};
|
|
1196
|
+
};
|
|
1197
|
+
|
|
1198
|
+
/**
|
|
1199
|
+
* Launch Puppeteer browser with standard configuration
|
|
1200
|
+
* @returns {object} Browser instance
|
|
1201
|
+
*/
|
|
1202
|
+
const launchBrowser = async () => {
|
|
1203
|
+
return await puppeteer.launch({
|
|
1204
|
+
args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"],
|
|
1205
|
+
headless: true,
|
|
1206
|
+
defaultViewport: null
|
|
1207
|
+
});
|
|
1208
|
+
};
|
|
1209
|
+
|
|
1210
|
+
/**
|
|
1211
|
+
* Setup page with content and dependencies
|
|
1212
|
+
* @param {object} page - Puppeteer page instance
|
|
1213
|
+
* @param {string} contentHtml - HTML content to load
|
|
1214
|
+
* @param {string} filePath - File path for navigation
|
|
1215
|
+
* @returns {object} Configured page
|
|
1216
|
+
*/
|
|
1217
|
+
const setupPageContent = async (page, contentHtml, filePath) => {
|
|
1218
|
+
await page.setContent(contentHtml, { waitUntil: 'networkidle2' });
|
|
1219
|
+
await page.evaluateHandle('document.fonts.ready');
|
|
1220
|
+
await page.addScriptTag({ url: 'https://code.jquery.com/jquery-3.3.1.min.js' });
|
|
1221
|
+
|
|
1222
|
+
debugLog("fonts loaded!");
|
|
1223
|
+
|
|
1224
|
+
await page.setContent(contentHtml, { waitUntil: 'networkidle2' });
|
|
1225
|
+
await page.goto(`file:///${path.resolve(__dirname + `/../${filePath}`)}`, {
|
|
1226
|
+
timeout: 30000000,
|
|
1227
|
+
waitUntil: 'networkidle2'
|
|
1228
|
+
}).catch(e => {
|
|
1229
|
+
console.log("ERROR IS File path: " + e.message);
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
return page;
|
|
1233
|
+
};
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* Generate PDF from HTML content (simple wellbeing report)
|
|
1237
|
+
* @param {object} data - Data containing HTML and output file info
|
|
1238
|
+
* @returns {Buffer} PDF buffer
|
|
1239
|
+
*/
|
|
1240
|
+
let generatePDFWellbeingReport = async (data) => {
|
|
1241
|
+
try {
|
|
1242
|
+
const contentHtml = data.html;
|
|
1243
|
+
OUT_FILE = data.outputFile;
|
|
1244
|
+
|
|
1245
|
+
// Launch browser and setup page
|
|
1246
|
+
const browser = await launchBrowser();
|
|
1247
|
+
const page = await browser.newPage();
|
|
1248
|
+
debugLog("page launched!");
|
|
1249
|
+
|
|
1250
|
+
// Setup content
|
|
1251
|
+
await setupPageContent(page, contentHtml, Pdf_file);
|
|
815
1252
|
debugLog("page opened!");
|
|
816
|
-
|
|
1253
|
+
|
|
1254
|
+
// Generate PDF
|
|
1255
|
+
const buffer = await page.pdf({
|
|
817
1256
|
width: "210mm",
|
|
818
1257
|
height: "297mm",
|
|
819
1258
|
pageRanges: '1',
|
|
820
1259
|
printBackground: true,
|
|
821
1260
|
displayHeaderFooter: true,
|
|
822
1261
|
margin: {
|
|
823
|
-
top: 0,
|
|
1262
|
+
top: 0,
|
|
1263
|
+
bottom: 0
|
|
824
1264
|
}
|
|
825
1265
|
});
|
|
826
1266
|
|
|
827
|
-
debugLog("
|
|
1267
|
+
debugLog("Buffer loaded!");
|
|
828
1268
|
|
|
829
|
-
let buffer = buffer1;
|
|
830
1269
|
await browser.close();
|
|
831
1270
|
return buffer;
|
|
832
|
-
|
|
833
|
-
|
|
834
1271
|
} catch (e) {
|
|
835
1272
|
debugLog("ERROR!: ", e);
|
|
836
1273
|
return "";
|
|
837
1274
|
}
|
|
838
1275
|
};
|
|
839
1276
|
|
|
840
|
-
|
|
841
|
-
// function cleanStyledHeader(html, language = 'ar') {
|
|
842
|
-
// const dom = new JSDOM(html);
|
|
843
|
-
// const { document } = dom.window;
|
|
844
|
-
// const fontFamily = language.startsWith('ar') ? 'Cairo' : 'Open Sans';
|
|
845
|
-
// const header = document.querySelector(".report-header");
|
|
846
|
-
// if (!header) return "";
|
|
847
|
-
//
|
|
848
|
-
// const styleEl = document.createElement("style");
|
|
849
|
-
// styleEl.textContent = `
|
|
850
|
-
// @media print {
|
|
851
|
-
// @page {
|
|
852
|
-
// size: A4 portrait;
|
|
853
|
-
// }
|
|
854
|
-
// .report-header-wrapper {
|
|
855
|
-
// position: fixed;
|
|
856
|
-
// top: 0;
|
|
857
|
-
// }
|
|
858
|
-
// }
|
|
859
|
-
// .report-header-wrapper * {
|
|
860
|
-
// font-family: '${fontFamily}', sans-serif !important;
|
|
861
|
-
// }
|
|
862
|
-
// `;
|
|
863
|
-
// document.head.insertBefore(styleEl, document.head.firstChild);
|
|
864
|
-
//
|
|
865
|
-
// const wrapper = document.createElement("div");
|
|
866
|
-
// wrapper.className = "report-header-wrapper";
|
|
867
|
-
// wrapper.setAttribute("style", `
|
|
868
|
-
// display:flex;
|
|
869
|
-
// align-items:center;
|
|
870
|
-
// justify-content:space-between;
|
|
871
|
-
// width:100%;
|
|
872
|
-
// padding:10px 15px;
|
|
873
|
-
// box-sizing:border-box;
|
|
874
|
-
// margin-bottom:10px;
|
|
875
|
-
// font-family:'${fontFamily}', sans-serif;
|
|
876
|
-
// font-size:10px;
|
|
877
|
-
// `);
|
|
878
|
-
//
|
|
879
|
-
// const top = header.querySelector(".report-top");
|
|
880
|
-
// if (top) {
|
|
881
|
-
// const cleanTop = document.createElement("div");
|
|
882
|
-
// cleanTop.setAttribute("style", `
|
|
883
|
-
// display:flex;
|
|
884
|
-
// justify-content:space-between;
|
|
885
|
-
// flex-direction:row-reverse;
|
|
886
|
-
// align-items:center;
|
|
887
|
-
// width:100%;
|
|
888
|
-
// `);
|
|
889
|
-
//
|
|
890
|
-
// const name = top.querySelector(".client-name");
|
|
891
|
-
// if (name) {
|
|
892
|
-
// const cleanName = document.createElement("div");
|
|
893
|
-
// cleanName.textContent = name.textContent.trim();
|
|
894
|
-
// cleanName.setAttribute("style", `
|
|
895
|
-
// color:#555;
|
|
896
|
-
// font-size:10px;
|
|
897
|
-
// font-weight:bold;
|
|
898
|
-
// text-transform:uppercase;
|
|
899
|
-
// overflow-wrap:break-word;
|
|
900
|
-
// `);
|
|
901
|
-
// cleanTop.appendChild(cleanName);
|
|
902
|
-
// }
|
|
903
|
-
//
|
|
904
|
-
// const logo = top.querySelector("img.report-logo");
|
|
905
|
-
// if (logo) {
|
|
906
|
-
// const logoWrapper = document.createElement("div");
|
|
907
|
-
// logoWrapper.setAttribute("style", `
|
|
908
|
-
// display:inline-block;
|
|
909
|
-
// width:80px;
|
|
910
|
-
// height:40px;
|
|
911
|
-
// overflow:hidden;
|
|
912
|
-
// `);
|
|
913
|
-
// const cleanImg = document.createElement("img");
|
|
914
|
-
// cleanImg.src = logo.src;
|
|
915
|
-
// cleanImg.alt = logo.alt || "";
|
|
916
|
-
// cleanImg.setAttribute("style", "width:100%; height:auto; display:block;");
|
|
917
|
-
// logoWrapper.appendChild(cleanImg);
|
|
918
|
-
// cleanTop.appendChild(logoWrapper);
|
|
919
|
-
// }
|
|
920
|
-
//
|
|
921
|
-
// wrapper.appendChild(cleanTop);
|
|
922
|
-
// }
|
|
923
|
-
//
|
|
924
|
-
// return wrapper.outerHTML;
|
|
925
|
-
// }
|
|
926
|
-
|
|
927
|
-
const cleanStyledHeader = (html, profileInfo, language = 'ar') => {
|
|
1277
|
+
const cleanStyledHeader = (html, profileInfo, language = 'ar', clientName = 'mediclinic', selectedLabs = [], showLogoInHeader = true) => {
|
|
928
1278
|
const dom = new JSDOM(html);
|
|
929
1279
|
const document = dom.window.document;
|
|
930
|
-
|
|
931
1280
|
const logo = document.querySelector("img.report-logo");
|
|
1281
|
+
const allClientLabs = config.labs_logos;
|
|
1282
|
+
console.log('selected labs : ',selectedLabs);
|
|
1283
|
+
const addedLogos = Object.keys(allClientLabs).filter(lab =>
|
|
1284
|
+
selectedLabs?.some(otherLab => otherLab.toLowerCase() === lab.toLowerCase())
|
|
1285
|
+
);
|
|
1286
|
+
console.log('added logos',addedLogos);
|
|
932
1287
|
const logoSrc = logo?.src || "";
|
|
933
|
-
const
|
|
1288
|
+
const headerTitle = document.querySelector(".client-name")?.textContent?.trim() || "";
|
|
934
1289
|
const fontFamily = language.startsWith('ar') ? 'Cairo' : 'Open Sans';
|
|
935
1290
|
const dir = language.startsWith('ar') ? 'rtl' : 'ltr';
|
|
936
|
-
const textAlign = dir === 'rtl' ? 'right' : 'left';
|
|
937
|
-
const justifyContent = dir === 'rtl' ? 'flex-end' : 'flex-start';
|
|
938
|
-
|
|
939
1291
|
let pdfHeaderData = "";
|
|
940
1292
|
|
|
1293
|
+
const headerStylesCustomization = () => {
|
|
1294
|
+
let logoStyle = 'width:130px;height:40px;margin:0 8px;';
|
|
1295
|
+
let titleStyle = `font-family: ${fontFamily};font-weight: 700;font-style: Bold;font-size: 14px;line-height: 100%;letter-spacing: 0;text-transform: uppercase;`;
|
|
1296
|
+
|
|
1297
|
+
if (clientName.toLocaleLowerCase() == 'mediclinic') {
|
|
1298
|
+
logoStyle = 'width: 92px;height: 28px;';
|
|
1299
|
+
titleStyle = titleStyle;
|
|
1300
|
+
} else if (clientName.toLocaleLowerCase() == 'pha') {
|
|
1301
|
+
// Fixed height with flexible width, but constrained
|
|
1302
|
+
logoStyle = 'max-height: 32px; max-width: 60px; width: auto; object-fit: contain; display: block;margin:0 4px;';
|
|
1303
|
+
titleStyle = `font-family: ${fontFamily};font-weight: 500;font-style: Bold;font-size: 12px;line-height: 100%;letter-spacing: 0;text-transform: uppercase;`;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
return {
|
|
1307
|
+
logoStyle,
|
|
1308
|
+
titleStyle
|
|
1309
|
+
};
|
|
1310
|
+
};
|
|
1311
|
+
|
|
941
1312
|
for (let index = 0; index < 4; index++) {
|
|
942
1313
|
const element = profileInfo[index];
|
|
943
1314
|
if (element) {
|
|
944
1315
|
pdfHeaderData += `
|
|
945
|
-
<div style="display: flex; align-items: center; justify-content: center; text-align: center;padding: 4px;">
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
</div>
|
|
954
|
-
`;
|
|
1316
|
+
<div style="display: flex; align-items: center; justify-content: center; text-align: center;padding: 4px;">
|
|
1317
|
+
<span style="font-weight: 500;font-style: normal;font-size: 8px;leading-trim: NONE;line-height: 100%;letter-spacing: 0;">
|
|
1318
|
+
${element.title}:
|
|
1319
|
+
</span>
|
|
1320
|
+
<span style="width: 4px;"></span>
|
|
1321
|
+
<span style="font-weight: 300;font-size: 8px;color: ${config.colors.headerText};leading-trim: NONE;line-height: 100%;letter-spacing: 0;">
|
|
1322
|
+
${element.value}
|
|
1323
|
+
</span>
|
|
1324
|
+
</div>
|
|
1325
|
+
`;
|
|
955
1326
|
}
|
|
956
1327
|
}
|
|
957
1328
|
|
|
1329
|
+
// the images should be in the same height and width 92x61
|
|
1330
|
+
const styles = headerStylesCustomization();
|
|
1331
|
+
const logoSection = showLogoInHeader && logoSrc ? `
|
|
1332
|
+
<div style="display: flex; gap: 12px; align-items: center;">
|
|
1333
|
+
<img src="${logoSrc}" alt="Logo" style="${styles?.logoStyle || ""}" />
|
|
1334
|
+
${addedLogos.length > 0 ? addedLogos.map(lab =>
|
|
1335
|
+
(!!lab && !!allClientLabs[lab])
|
|
1336
|
+
? `<img src="${allClientLabs[lab]}" style="${styles?.logoStyle || ""}" />`
|
|
1337
|
+
: ''
|
|
1338
|
+
).join('') : ''}
|
|
1339
|
+
</div>
|
|
1340
|
+
` : '';
|
|
1341
|
+
|
|
1342
|
+
return `
|
|
1343
|
+
<style>
|
|
1344
|
+
html { -webkit-print-color-adjust: exact; margin-bottom: 10px}
|
|
1345
|
+
</style>
|
|
1346
|
+
<div style="font-family: '${fontFamily}', sans-serif; direction: ${dir}; width: 100%;margin-left: 20px;margin-right: 20px;margin-bottom: 20px;">
|
|
1347
|
+
<div style="width: 100%; display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;gap: 12px;">
|
|
1348
|
+
${logoSection}
|
|
1349
|
+
<div style="${styles?.titleStyle || ""}">
|
|
1350
|
+
${headerTitle.toUpperCase()}
|
|
1351
|
+
</div>
|
|
1352
|
+
</div>
|
|
1353
|
+
<!-- Info Row -->
|
|
1354
|
+
<div style="
|
|
1355
|
+
padding: 2px 10px;
|
|
1356
|
+
background-color: ${config.colors.headerBackground};
|
|
1357
|
+
display: flex;
|
|
1358
|
+
justify-content: space-between;
|
|
1359
|
+
align-items: center;
|
|
1360
|
+
overflow: hidden;
|
|
1361
|
+
margin-bottom: 5px;
|
|
1362
|
+
">
|
|
1363
|
+
${pdfHeaderData}
|
|
1364
|
+
</div>
|
|
1365
|
+
<div style="width: 100%; height: 1px; background-color: ${config.colors.headerDivider}; margin: 6px 0 10px 0;"></div>
|
|
1366
|
+
</div>
|
|
1367
|
+
`;
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
/**
|
|
1371
|
+
* Get PDF footer template HTML
|
|
1372
|
+
* @returns {string} Footer template HTML
|
|
1373
|
+
*/
|
|
1374
|
+
const getPdfFooterTemplate = () => {
|
|
958
1375
|
return `
|
|
959
|
-
<style>
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
<
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1376
|
+
<div style="width: 100%; font-size:7.3px; color: ${config.colors.footerText}; padding: 0 20px; box-sizing: border-box; display: flex; justify-content: space-between; align-items: center; font-family: 'Open Sans', sans-serif;">
|
|
1377
|
+
<div style="display: flex; align-items: center;font-family: 'Open Sans', sans-serif;">
|
|
1378
|
+
<span style="font-style: italic; font-family: 'Open Sans', sans-serif;">Page
|
|
1379
|
+
<span class="pageNumber" style="font-family: 'Open Sans', sans-serif; font-feature-settings: 'tnum';"></span>
|
|
1380
|
+
of
|
|
1381
|
+
<span class="totalPages" style="font-family: 'Open Sans', sans-serif; font-feature-settings: 'tnum';"></span>
|
|
1382
|
+
</span>
|
|
1383
|
+
</div>
|
|
1384
|
+
<div style="display: flex; flex-direction: column; align-items: flex-end; font-family: 'Open Sans', sans-serif;">
|
|
1385
|
+
<div style="font-family: 'Open Sans', sans-serif; font-feature-settings: 'tnum';">Telephone: 800 20 33</div>
|
|
1386
|
+
<div style="font-family: 'Open Sans', sans-serif;">Email: info@mediclinic.ae</div>
|
|
1387
|
+
</div>
|
|
1388
|
+
</div>
|
|
1389
|
+
`;
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
/**
|
|
1393
|
+
* Launch browser for extended PDF with font optimization
|
|
1394
|
+
* @returns {object} Browser instance
|
|
1395
|
+
*/
|
|
1396
|
+
const launchExtendedBrowser = async () => {
|
|
1397
|
+
return await puppeteer.launch({
|
|
1398
|
+
args: [
|
|
1399
|
+
"--no-sandbox",
|
|
1400
|
+
"--disable-setuid-sandbox",
|
|
1401
|
+
"--font-render-hinting=none",
|
|
1402
|
+
"--force-color-profile=srgb",
|
|
1403
|
+
"--disable-web-security"
|
|
1404
|
+
],
|
|
1405
|
+
headless: true,
|
|
1406
|
+
defaultViewport: null
|
|
1407
|
+
});
|
|
1408
|
+
};
|
|
1409
|
+
|
|
1410
|
+
/**
|
|
1411
|
+
* Setup page for extended PDF report
|
|
1412
|
+
* @param {object} page - Puppeteer page instance
|
|
1413
|
+
* @param {string} contentHtml - HTML content
|
|
1414
|
+
* @param {string} filePath - File path for navigation
|
|
1415
|
+
* @returns {object} Configured page
|
|
1416
|
+
*/
|
|
1417
|
+
const setupExtendedPageContent = async (page, contentHtml, filePath) => {
|
|
1418
|
+
await page.evaluateHandle('document.fonts.ready');
|
|
1419
|
+
await page.evaluate(() => {
|
|
1420
|
+
const usedFonts = Array.from(document.fonts).map(f => f.family);
|
|
1421
|
+
console.log('Fonts in use:', usedFonts);
|
|
1422
|
+
});
|
|
1423
|
+
await page.screenshot({ encoding: 'binary' });
|
|
1424
|
+
|
|
1425
|
+
debugLog("page launched & fonts loaded!");
|
|
1426
|
+
|
|
1427
|
+
await page.addScriptTag({ url: 'https://code.jquery.com/jquery-3.3.1.min.js' });
|
|
1428
|
+
await page.setContent(contentHtml, { waitUntil: 'networkidle2', timeout: 30000 });
|
|
1429
|
+
await page.goto(`file:///${path.resolve(__dirname + `/../${filePath}`)}`, {
|
|
1430
|
+
timeout: 30000000,
|
|
1431
|
+
waitUntil: 'networkidle2'
|
|
1432
|
+
}).catch(e => {
|
|
1433
|
+
console.log("ERROR IS File path: " + e.message);
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
return page;
|
|
991
1437
|
};
|
|
992
1438
|
|
|
1439
|
+
/**
|
|
1440
|
+
* Generate PDF from HTML content (extended report with header/footer)
|
|
1441
|
+
* @param {object} data - Data containing HTML, header, and output file info
|
|
1442
|
+
* @returns {Buffer} PDF buffer
|
|
1443
|
+
*/
|
|
993
1444
|
const generatePDFReport = async (data) => {
|
|
994
1445
|
try {
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
const footerTemplate = `
|
|
998
|
-
<div style="width: 100%; font-size:7.3px; color: #B5B5B5; padding: 0 20px; box-sizing: border-box; display: flex; justify-content: space-between; align-items: center; font-family: 'Open Sans', sans-serif;">
|
|
999
|
-
<div style="display: flex; align-items: center;font-family: 'Open Sans', sans-serif;">
|
|
1000
|
-
<span style="font-style: italic; font-family: 'Open Sans', sans-serif;">Page
|
|
1001
|
-
<span class="pageNumber" style="font-family: 'Open Sans', sans-serif; font-feature-settings: 'tnum';"></span>
|
|
1002
|
-
of
|
|
1003
|
-
<span class="totalPages" style="font-family: 'Open Sans', sans-serif; font-feature-settings: 'tnum';"></span></span>
|
|
1004
|
-
</div>
|
|
1005
|
-
<div style="display: flex; flex-direction: column; align-items: flex-end; font-family: 'Open Sans', sans-serif;">
|
|
1006
|
-
<div style="font-family: 'Open Sans', sans-serif; font-feature-settings: 'tnum';">Telephone: 800 20 33</div>
|
|
1007
|
-
<div style="font-family: 'Open Sans', sans-serif;">Email: info@mediclinic.ae</div>
|
|
1008
|
-
</div>
|
|
1009
|
-
</div>`;
|
|
1010
|
-
|
|
1446
|
+
const contentHtml = data.html;
|
|
1011
1447
|
const OUT_FILE = data.outputFile;
|
|
1012
|
-
const
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
});
|
|
1448
|
+
const footerTemplate = getPdfFooterTemplate();
|
|
1449
|
+
|
|
1450
|
+
// Launch browser and setup page
|
|
1451
|
+
const browser = await launchExtendedBrowser();
|
|
1017
1452
|
const page = await browser.newPage();
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
await page.
|
|
1024
|
-
debugLog("page launched & fonts loaded!");
|
|
1025
|
-
await page.addScriptTag({url: 'https://code.jquery.com/jquery-3.3.1.min.js'});
|
|
1026
|
-
await page.setContent(contentHtml, {waitUntil: 'networkidle2', timeout: 30000});
|
|
1027
|
-
await page.goto("file:///" + path.resolve(__dirname + `/../${Pdf_file}`), {
|
|
1028
|
-
timeout: 30000000, waitUntil: 'networkidle2'
|
|
1029
|
-
}).catch(e => {
|
|
1030
|
-
console.log("ERROR IS File path: " + e.message);
|
|
1031
|
-
});
|
|
1032
|
-
const buffer1 = await page.pdf({
|
|
1453
|
+
|
|
1454
|
+
// Setup content
|
|
1455
|
+
await setupExtendedPageContent(page, contentHtml, Pdf_file);
|
|
1456
|
+
|
|
1457
|
+
// Generate PDF with header and footer
|
|
1458
|
+
const buffer = await page.pdf({
|
|
1033
1459
|
format: 'A4',
|
|
1034
1460
|
displayHeaderFooter: true,
|
|
1035
|
-
footerTemplate
|
|
1461
|
+
footerTemplate,
|
|
1036
1462
|
headerTemplate: data.pdfHeader,
|
|
1037
1463
|
width: "220mm",
|
|
1038
1464
|
margin: {
|
|
1039
|
-
top: '125px',
|
|
1465
|
+
top: '125px',
|
|
1466
|
+
bottom: '50px',
|
|
1467
|
+
left: '20px',
|
|
1468
|
+
right: '20px'
|
|
1040
1469
|
},
|
|
1041
|
-
printBackground: true
|
|
1470
|
+
printBackground: true
|
|
1042
1471
|
});
|
|
1043
|
-
|
|
1472
|
+
|
|
1044
1473
|
await browser.close();
|
|
1045
1474
|
return buffer;
|
|
1046
|
-
|
|
1047
1475
|
} catch (e) {
|
|
1048
1476
|
debugLog("ERROR!: ", e);
|
|
1049
1477
|
return "";
|
|
@@ -1066,211 +1494,170 @@ const renderDetails = (list) => {
|
|
|
1066
1494
|
return (`<li>${list[0].content.substring(list[0].sepCount, list[0].content.length + 1)}</li>`)
|
|
1067
1495
|
}
|
|
1068
1496
|
|
|
1069
|
-
|
|
1070
|
-
let isParent = false
|
|
1071
|
-
|
|
1072
|
-
list.map((child, index) => {
|
|
1073
|
-
if (child.parent_id === "0") isParent = true
|
|
1497
|
+
const columns = []
|
|
1074
1498
|
|
|
1499
|
+
list.forEach((child) => {
|
|
1075
1500
|
let div = '';
|
|
1076
1501
|
if (empty(child.children)) {
|
|
1077
|
-
div = `<li
|
|
1502
|
+
div = `<li>${child.content.substring(child.sepCount, child.content.length + 1)}</li>`
|
|
1078
1503
|
} else {
|
|
1079
|
-
div = `<li
|
|
1504
|
+
div = `<li>${child.content.substring(child.sepCount, child.content.length + 1)}</li><ul>${renderDetails(child.children)}</ul>`
|
|
1080
1505
|
}
|
|
1081
1506
|
|
|
1507
|
+
if (!empty(div)) {
|
|
1508
|
+
columns.push(div);
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
1082
1511
|
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
})
|
|
1086
|
-
|
|
1087
|
-
let results = ''
|
|
1088
|
-
columns.map((tabel) => {
|
|
1089
|
-
if (!empty(tabel)) results += tabel
|
|
1090
|
-
})
|
|
1091
|
-
return !empty(columns) ? `${results}` : null
|
|
1512
|
+
return !empty(columns) ? columns.join('') : null;
|
|
1092
1513
|
}
|
|
1093
1514
|
|
|
1094
1515
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
partScores[key].map((item, index) => {
|
|
1105
|
-
let name = item['icon'].split(".png", [1]);
|
|
1106
|
-
let icon = icons[name];
|
|
1107
|
-
let score_segments = item['score'] + "/" + item['base']; // get score between 1 and 5
|
|
1108
|
-
let color = '#8b8ba7';
|
|
1109
|
-
let tempElement = {
|
|
1110
|
-
id: i,
|
|
1111
|
-
icon: icon,
|
|
1112
|
-
name: item['name'],
|
|
1113
|
-
color: color,
|
|
1114
|
-
score: key === 'healthRisk' ? item['scoreText'] : score_segments
|
|
1115
|
-
};
|
|
1116
|
-
if (key === 'body') {
|
|
1117
|
-
body.push(tempElement)
|
|
1118
|
-
} else if (key === 'lifeStyle') {
|
|
1119
|
-
lifeStyle.push(tempElement)
|
|
1120
|
-
} else if (key === 'mind') {
|
|
1121
|
-
mind.push(tempElement)
|
|
1122
|
-
} else {
|
|
1123
|
-
healthRisk.push(tempElement)
|
|
1124
|
-
}
|
|
1125
|
-
i++;
|
|
1126
|
-
})
|
|
1127
|
-
tempElements['lifeStyle'] = lifeStyle;
|
|
1128
|
-
tempElements['mind'] = mind;
|
|
1129
|
-
tempElements['body'] = body;
|
|
1130
|
-
|
|
1131
|
-
tempElements['healthRisk'] = healthRisk;
|
|
1132
|
-
|
|
1133
|
-
tempElements[key].map((item, index) => {
|
|
1134
|
-
elementsHtml += "<li class='list-item'>\n" + " <svg fill-rule=\"evenodd\" clip-rule=\"evenodd\" xmlns=\"http://www.w3.org/2000/svg\" width=\"12\" height=\"12\" viewBox=\"0 0 30 30\">\n" + " <g fill=\"none\" fillRule=\"evenodd\">\n" + " <g fill='" + item.color + "' stroke='" + item.color + "' strokeOpacity={0.2} fillRule=\"nonzero\">\n" + " <path d='" + item.icon + "'/>\n" + " </g>\n" + " </g>\n" + " </svg>\n" + " <div class='list-text'>" + item.name + "</div>\n" + " <div class='list-score'>" + item.score + "</div>\n" + " </li>"
|
|
1135
|
-
})
|
|
1136
|
-
|
|
1516
|
+
/**
|
|
1517
|
+
* Render score list elements for a given category
|
|
1518
|
+
* @param {string} key - Category key (body, lifeStyle, mind, healthRisk)
|
|
1519
|
+
* @param {object} partScores - Part scores data
|
|
1520
|
+
* @returns {string} Rendered score list HTML
|
|
1521
|
+
*/
|
|
1522
|
+
const renderScoreListElements = (key, partScores) => {
|
|
1523
|
+
if (isEmpty(partScores) || !partScores[key]) {
|
|
1524
|
+
return '<ul class="score-list"></ul>';
|
|
1137
1525
|
}
|
|
1138
|
-
let parts = "<ul class='score-list'>" + elementsHtml + "</ul>"
|
|
1139
|
-
|
|
1140
|
-
return parts;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
1526
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1527
|
+
const elements = partScores[key].map((item) => {
|
|
1528
|
+
const iconName = item['icon'].split(".png")[0];
|
|
1529
|
+
const icon = icons[iconName];
|
|
1530
|
+
const color = config.colors.listIcon;
|
|
1531
|
+
const score = key === 'healthRisk'
|
|
1532
|
+
? item['scoreText']
|
|
1533
|
+
: `${item['score']}/${item['base']}`;
|
|
1534
|
+
|
|
1535
|
+
return `
|
|
1536
|
+
<li class='list-item'>
|
|
1537
|
+
<svg fill-rule="evenodd" clip-rule="evenodd" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 30 30">
|
|
1538
|
+
<g fill="none" fillRule="evenodd">
|
|
1539
|
+
<g fill='${color}' stroke='${color}' strokeOpacity={0.2} fillRule="nonzero">
|
|
1540
|
+
<path d='${icon}'/>
|
|
1541
|
+
</g>
|
|
1542
|
+
</g>
|
|
1543
|
+
</svg>
|
|
1544
|
+
<div class='list-text'>${item.name}</div>
|
|
1545
|
+
<div class='list-score'>${score}</div>
|
|
1546
|
+
</li>
|
|
1547
|
+
`;
|
|
1548
|
+
}).join('');
|
|
1162
1549
|
|
|
1163
|
-
|
|
1550
|
+
return `<ul class='score-list'>${elements}</ul>`;
|
|
1551
|
+
};
|
|
1164
1552
|
|
|
1165
|
-
if (Object.entries(allScores[key]).length === 7) numberOfE = "seven"
|
|
1166
1553
|
|
|
1167
|
-
|
|
1554
|
+
/**
|
|
1555
|
+
* Render all score items with charts
|
|
1556
|
+
* @param {object} allScores - All scores object
|
|
1557
|
+
* @param {object} partScores - Part scores data
|
|
1558
|
+
* @returns {string} Rendered HTML for all score items
|
|
1559
|
+
*/
|
|
1560
|
+
const renderItems = (allScores, partScores) => {
|
|
1561
|
+
if (isEmpty(allScores)) {
|
|
1562
|
+
return '';
|
|
1563
|
+
}
|
|
1168
1564
|
|
|
1169
|
-
|
|
1565
|
+
const numberWords = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
|
|
1170
1566
|
|
|
1171
|
-
|
|
1567
|
+
const itemsHtml = Object.entries(allScores).map(([key, value]) => {
|
|
1568
|
+
if (empty(partScores[key])) {
|
|
1569
|
+
return '';
|
|
1570
|
+
}
|
|
1172
1571
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1572
|
+
const itemCount = Object.entries(allScores[key]).length;
|
|
1573
|
+
const numberOfE = numberWords[itemCount] || itemCount.toString();
|
|
1574
|
+
const topOffset = itemCount * 30 / 2;
|
|
1575
|
+
const top = `calc(50% - ${topOffset}px)`;
|
|
1576
|
+
|
|
1577
|
+
const circleContainer = key !== "healthRisk"
|
|
1578
|
+
? `<div id='${key}' class="circle-container"></div>`
|
|
1579
|
+
: '';
|
|
1580
|
+
|
|
1581
|
+
return `
|
|
1582
|
+
<div key='${key}' class='percentage-meter-content center'>
|
|
1583
|
+
<div class='svg-container'>
|
|
1584
|
+
${circleContainer}
|
|
1585
|
+
<div class='chart-title'>
|
|
1586
|
+
<span class='score-details-title'>${localeService.t(key)}</span>
|
|
1587
|
+
</div>
|
|
1588
|
+
</div>
|
|
1589
|
+
<div class='element-list ${numberOfE}' style='top:${top}'>
|
|
1590
|
+
${renderScoreListElements(key, partScores)}
|
|
1591
|
+
</div>
|
|
1592
|
+
</div>
|
|
1593
|
+
`;
|
|
1594
|
+
}).join('');
|
|
1179
1595
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1596
|
+
return itemsHtml;
|
|
1597
|
+
};
|
|
1182
1598
|
|
|
1183
|
-
|
|
1599
|
+
/**
|
|
1600
|
+
* Render insight tips for a specific category
|
|
1601
|
+
* @param {array} tips - Array of tip objects
|
|
1602
|
+
* @param {string} clientName - Client type (e.g., 'Mediclinic')
|
|
1603
|
+
* @returns {string} Rendered tips HTML
|
|
1604
|
+
*/
|
|
1605
|
+
const renderInsightTips = (tips, clientName) => {
|
|
1606
|
+
if (empty(tips)) {
|
|
1607
|
+
return '';
|
|
1184
1608
|
}
|
|
1185
|
-
return partsScoreHtml;
|
|
1186
|
-
};
|
|
1187
1609
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1610
|
+
const partImages = {
|
|
1611
|
+
1: config.lifestyleImage,
|
|
1612
|
+
2: config.mindImage,
|
|
1613
|
+
3: config.bodyImage
|
|
1614
|
+
};
|
|
1190
1615
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1616
|
+
const partTitles = {
|
|
1617
|
+
1: localeService.t('lifeStyle'),
|
|
1618
|
+
2: localeService.t('mind'),
|
|
1619
|
+
3: localeService.t('body')
|
|
1620
|
+
};
|
|
1193
1621
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
let partTitles = {
|
|
1200
|
-
1: localeService.t('lifeStyle'), 3: localeService.t('body'), 2: localeService.t('mind')
|
|
1201
|
-
};
|
|
1202
|
-
|
|
1203
|
-
let tip = tips[0];
|
|
1204
|
-
let tipImage = partImages[tip.partId] || '';
|
|
1205
|
-
let translatedTitle = partTitles[tip.partId] || '';
|
|
1206
|
-
|
|
1207
|
-
let textColor = tip.isPositive ? '#124374' : 'black';
|
|
1208
|
-
let tipContents = ''
|
|
1209
|
-
tips.map((t, index) => {
|
|
1210
|
-
if (index == 0) {
|
|
1211
|
-
tipContents += `
|
|
1212
|
-
|
|
1213
|
-
<div class="insight-content" style="color: #124374;">${t.content}</div>
|
|
1214
|
-
`
|
|
1215
|
-
} else {
|
|
1216
|
-
tipContents += ` <div class="insight-content" style="color: #000000;">${t.content}</div>`
|
|
1622
|
+
if (clientName === 'Mediclinic' || clientName === 'pha') {
|
|
1623
|
+
const tip = tips[0];
|
|
1624
|
+
const tipImage = partImages[tip.partId] || '';
|
|
1625
|
+
const translatedTitle = partTitles[tip.partId] || '';
|
|
1217
1626
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1627
|
+
const tipContents = tips.map((t, index) => {
|
|
1628
|
+
const color = index === 0 ? config.colors.insightPrimary : config.colors.insightSecondary;
|
|
1629
|
+
return `<div class="insight-content" style="color: ${color};">${t.content}</div>`;
|
|
1630
|
+
}).join('');
|
|
1220
1631
|
|
|
1221
|
-
|
|
1632
|
+
return `
|
|
1222
1633
|
<div class="insight-wrapper">
|
|
1223
1634
|
<div class="insight-header">
|
|
1224
1635
|
<div class="insight-icon">
|
|
1225
|
-
|
|
1226
|
-
|
|
1636
|
+
<img src="${tipImage}" alt="${translatedTitle}" />
|
|
1227
1637
|
</div>
|
|
1228
1638
|
<div class="insight-title">${translatedTitle}</div>
|
|
1229
1639
|
</div>
|
|
1230
|
-
|
|
1640
|
+
${tipContents}
|
|
1231
1641
|
</div>
|
|
1232
1642
|
`;
|
|
1233
|
-
|
|
1234
|
-
return tipsHtml;
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
return tipsHtml;
|
|
1238
|
-
} else {
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
let tipsHtml = ''
|
|
1242
|
-
if (!empty(tips)) {
|
|
1243
|
-
tips.map((tip, index) => {
|
|
1244
|
-
let tipImage = ''
|
|
1245
|
-
let insightClass = 'insight-container-without-img';
|
|
1246
|
-
let tipsTextClass = ''
|
|
1247
|
-
if (index === 0) {
|
|
1248
|
-
if (tip.partId === 1) {
|
|
1249
|
-
tipImage = lifestyleImage
|
|
1250
|
-
}
|
|
1251
|
-
if (tip.partId === 2) {
|
|
1252
|
-
tipImage = bodyImage
|
|
1253
|
-
}
|
|
1254
|
-
if (tip.partId === 3) {
|
|
1255
|
-
tipImage = mindImage
|
|
1256
|
-
}
|
|
1257
|
-
insightClass = "insight-container-with-img";
|
|
1258
|
-
tipsTextClass = "first-level-font-color"
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
let tex = tip.content
|
|
1262
|
-
|
|
1263
|
-
tipsHtml += "<div class='insight-container " + insightClass + "'>" + "<div class='tips-text " + tipsTextClass + "'>" + tex + "</div>";
|
|
1264
|
-
if (index === 0) {
|
|
1265
|
-
tipsHtml += "<div class='chunk-image'><img class='chunk-image-mind' src='" + tipImage + "'/></div>"
|
|
1266
|
-
|
|
1267
|
-
tipsHtml += "</div>"
|
|
1268
|
-
}
|
|
1269
|
-
})
|
|
1270
|
-
}
|
|
1271
|
-
return tipsHtml;
|
|
1272
1643
|
}
|
|
1273
|
-
|
|
1644
|
+
|
|
1645
|
+
// Default rendering for other clients
|
|
1646
|
+
return tips.map((tip, index) => {
|
|
1647
|
+
const isFirst = index === 0;
|
|
1648
|
+
const tipImage = isFirst ? (partImages[tip.partId] || '') : '';
|
|
1649
|
+
const insightClass = isFirst ? 'insight-container-with-img' : 'insight-container-without-img';
|
|
1650
|
+
const tipsTextClass = isFirst ? 'first-level-font-color' : '';
|
|
1651
|
+
const imageHtml = isFirst ? `<div class='chunk-image'><img class='chunk-image-mind' src='${tipImage}'/></div>` : '';
|
|
1652
|
+
|
|
1653
|
+
return `
|
|
1654
|
+
<div class='insight-container ${insightClass}'>
|
|
1655
|
+
<div class='tips-text ${tipsTextClass}'>${tip.content}</div>
|
|
1656
|
+
${imageHtml}
|
|
1657
|
+
</div>
|
|
1658
|
+
`;
|
|
1659
|
+
}).join('');
|
|
1660
|
+
};
|
|
1274
1661
|
|
|
1275
1662
|
function generateReportHtml(data, isRtl, isRectified, isIMCLab, currentLanguage) {
|
|
1276
1663
|
let detailsViewClass = data.showCompactView ? "" : "one-page";
|
|
@@ -1285,7 +1672,7 @@ function generateReportHtml(data, isRtl, isRectified, isIMCLab, currentLanguage)
|
|
|
1285
1672
|
<td>
|
|
1286
1673
|
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 12" width="20" height="20">
|
|
1287
1674
|
<style>
|
|
1288
|
-
.s0 { fill:
|
|
1675
|
+
.s0 { fill: ${config.colors.warningIcon} }
|
|
1289
1676
|
</style>
|
|
1290
1677
|
<path id="Layer" class="s0" d="m0.7 0.6q-0.1 0-0.1 0.1v8.8q0 0.1 0.1 0.1h3.1l-0.6 0.6h-2.5c-0.4 0-0.7-0.3-0.7-0.7v-8.8c0-0.4 0.3-0.7 0.7-0.7h6.2c0.4 0 0.7 0.3 0.7 0.7v5l-0.6 0.6v-5.6q0-0.1-0.1-0.1z" />
|
|
1291
1678
|
<path id="Layer" class="s0" d="m9.9 5.4c0.1 0.2 0.1 0.4 0 0.5l-5.5 5.5l-1.6 0.6l0.6-1.6l5.5-5.6c0.1-0.1 0.3-0.1 0.4 0z" />
|