This may not be much, however since i took so much time to get it done and accomplished what i wanted, posting it here, for someone who may be interested.
I already had a flow where a temperature report is sent on a daily basis to intended recipients as a pdf file attachment. however i noticed that opening an attachment and looking at the report is really not very user friendly and people would skip it and move on unless they WANTED to see the report. i tried putting the report in the body of the mail, so that the user can see the report as soon as they see the mail, however, i was unable to get the 'charts' on the body, table and other markup text was no issue. i tried very hard but realised that microsoft outlook doesn't accept any html created by dynamic javascript considering it as a threat (or may be it is my office IT policy, not sure). so i turned to AI to help. Perplexity helped me build a flow, which allowed me to create a email body that can be rendered rather beautifully, charts along with tables and other tiles. it suggested to use https://quickchart.io/ API and it was successful.
[{"id":"d02a816e413cefad","type":"inject","z":"9fcb1fec5d4354f5","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"datetime\":\"2025-12-10T00:30:00.000Z\",\"Time\":\"10-Dec 06:00\",\"CR-1\":8,\"CR-2\":-11,\"CR-3\":10.5,\"CR-4\":10.1},{\"datetime\":\"2025-12-10T00:31:00.000Z\",\"Time\":\"10-Dec 06:01\",\"CR-1\":8.3,\"CR-2\":-11,\"CR-3\":10.5,\"CR-4\":10.2},{\"datetime\":\"2025-12-10T00:32:00.000Z\",\"Time\":\"10-Dec 06:02\",\"CR-1\":8.1,\"CR-2\":-11,\"CR-3\":10.5,\"CR-4\":10.6},{\"datetime\":\"2025-12-10T00:33:00.000Z\",\"Time\":\"10-Dec 06:03\",\"CR-1\":8.2,\"CR-2\":-11,\"CR-3\":10.5,\"CR-4\":10.9},{\"datetime\":\"2025-12-10T00:34:00.000Z\",\"Time\":\"10-Dec 06:04\",\"CR-1\":8.1,\"CR-2\":-11,\"CR-3\":10.5,\"CR-4\":11.1},{\"datetime\":\"2025-12-10T00:35:00.000Z\",\"Time\":\"10-Dec 06:05\",\"CR-1\":8.2,\"CR-2\":-11,\"CR-3\":10.5,\"CR-4\":11.2},{\"datetime\":\"2025-12-10T00:36:00.000Z\",\"Time\":\"10-Dec 06:06\",\"CR-1\":8.2,\"CR-2\":-11.1,\"CR-3\":10.5,\"CR-4\":11.4},{\"datetime\":\"2025-12-10T00:37:00.000Z\",\"Time\":\"10-Dec 06:07\",\"CR-1\":8.1,\"CR-2\":-11.1,\"CR-3\":10.5,\"CR-4\":11.5},{\"datetime\":\"2025-12-10T00:38:00.000Z\",\"Time\":\"10-Dec 06:08\",\"CR-1\":8.4,\"CR-2\":-11.1,\"CR-3\":10.5,\"CR-4\":11.6},{\"datetime\":\"2025-12-10T00:39:00.000Z\",\"Time\":\"10-Dec 06:09\",\"CR-1\":8.2,\"CR-2\":-11.2,\"CR-3\":10.5,\"CR-4\":11.8},{\"datetime\":\"2025-12-10T00:40:00.000Z\",\"Time\":\"10-Dec 06:10\",\"CR-1\":8.4,\"CR-2\":-11.6,\"CR-3\":10.5,\"CR-4\":11.9},{\"datetime\":\"2025-12-10T00:41:00.000Z\",\"Time\":\"10-Dec 06:11\",\"CR-1\":8.2,\"CR-2\":-12.3,\"CR-3\":10.5,\"CR-4\":12},{\"datetime\":\"2025-12-10T00:42:00.000Z\",\"Time\":\"10-Dec 06:12\",\"CR-1\":8.2,\"CR-2\":-12.6,\"CR-3\":10.5,\"CR-4\":12.1},{\"datetime\":\"2025-12-10T00:43:01.000Z\",\"Time\":\"10-Dec 06:13\",\"CR-1\":8.3,\"CR-2\":-12,\"CR-3\":10.5,\"CR-4\":12.2},{\"datetime\":\"2025-12-10T00:44:00.000Z\",\"Time\":\"10-Dec 06:14\",\"CR-1\":8.3,\"CR-2\":-11.9,\"CR-3\":10.5,\"CR-4\":12.3},{\"datetime\":\"2025-12-10T00:45:00.000Z\",\"Time\":\"10-Dec 06:15\",\"CR-1\":8.3,\"CR-2\":-11.8,\"CR-3\":10.5,\"CR-4\":12.4},{\"datetime\":\"2025-12-10T00:46:00.000Z\",\"Time\":\"10-Dec 06:16\",\"CR-1\":8.3,\"CR-2\":-11.9,\"CR-3\":10.5,\"CR-4\":12.5},{\"datetime\":\"2025-12-10T00:47:00.000Z\",\"Time\":\"10-Dec 06:17\",\"CR-1\":8,\"CR-2\":-11.8,\"CR-3\":10.5,\"CR-4\":12.6},{\"datetime\":\"2025-12-10T00:48:00.000Z\",\"Time\":\"10-Dec 06:18\",\"CR-1\":8.5,\"CR-2\":-11.9,\"CR-3\":10.5,\"CR-4\":12.6},{\"datetime\":\"2025-12-10T00:49:00.000Z\",\"Time\":\"10-Dec 06:19\",\"CR-1\":8.2,\"CR-2\":-11.9,\"CR-3\":10.5,\"CR-4\":12.7},{\"datetime\":\"2025-12-10T00:50:00.000Z\",\"Time\":\"10-Dec 06:20\",\"CR-1\":8.5,\"CR-2\":-12,\"CR-3\":10.5,\"CR-4\":12.8},{\"datetime\":\"2025-12-10T00:51:00.000Z\",\"Time\":\"10-Dec 06:21\",\"CR-1\":8.3,\"CR-2\":-11.9,\"CR-3\":10.5,\"CR-4\":12.9},{\"datetime\":\"2025-12-10T00:52:00.000Z\",\"Time\":\"10-Dec 06:22\",\"CR-1\":8.4,\"CR-2\":-12,\"CR-3\":10.5,\"CR-4\":13},{\"datetime\":\"2025-12-10T00:53:00.000Z\",\"Time\":\"10-Dec 06:23\",\"CR-1\":8.3,\"CR-2\":-12,\"CR-3\":10.5,\"CR-4\":13},{\"datetime\":\"2025-12-10T00:54:00.000Z\",\"Time\":\"10-Dec 06:24\",\"CR-1\":8.5,\"CR-2\":-12,\"CR-3\":10.5,\"CR-4\":13.1},{\"datetime\":\"2025-12-10T00:55:00.000Z\",\"Time\":\"10-Dec 06:25\",\"CR-1\":8.2,\"CR-2\":-12.1,\"CR-3\":10.5,\"CR-4\":13.2},{\"datetime\":\"2025-12-10T00:56:00.000Z\",\"Time\":\"10-Dec 06:26\",\"CR-1\":8.4,\"CR-2\":-12.1,\"CR-3\":10.5,\"CR-4\":13.2},{\"datetime\":\"2025-12-10T00:57:00.000Z\",\"Time\":\"10-Dec 06:27\",\"CR-1\":8.4,\"CR-2\":-12.1,\"CR-3\":10.5,\"CR-4\":13.3},{\"datetime\":\"2025-12-10T00:58:00.000Z\",\"Time\":\"10-Dec 06:28\",\"CR-1\":8.3,\"CR-2\":-12.2,\"CR-3\":10.5,\"CR-4\":13.3},{\"datetime\":\"2025-12-10T00:59:00.000Z\",\"Time\":\"10-Dec 06:29\",\"CR-1\":8.5,\"CR-2\":-12.1,\"CR-3\":10.5,\"CR-4\":13.4},{\"datetime\":\"2025-12-10T01:00:00.000Z\",\"Time\":\"10-Dec 06:30\",\"CR-1\":8.3,\"CR-2\":-12.2,\"CR-3\":10.5,\"CR-4\":13.4},{\"datetime\":\"2025-12-10T01:01:00.000Z\",\"Time\":\"10-Dec 06:31\",\"CR-1\":8.6,\"CR-2\":-12.2,\"CR-3\":10.4,\"CR-4\":13.5},{\"datetime\":\"2025-12-10T01:02:00.000Z\",\"Time\":\"10-Dec 06:32\",\"CR-1\":8.3,\"CR-2\":-12.2,\"CR-3\":10.4,\"CR-4\":13.1},{\"datetime\":\"2025-12-10T01:03:00.000Z\",\"Time\":\"10-Dec 06:33\",\"CR-1\":8.7,\"CR-2\":-12.3,\"CR-3\":10.4,\"CR-4\":12.6},{\"datetime\":\"2025-12-10T01:04:00.000Z\",\"Time\":\"10-Dec 06:34\",\"CR-1\":8.5,\"CR-2\":-12.3,\"CR-3\":10.4,\"CR-4\":12.3},{\"datetime\":\"2025-12-10T01:05:00.000Z\",\"Time\":\"10-Dec 06:35\",\"CR-1\":8.6,\"CR-2\":-12.3,\"CR-3\":10.4,\"CR-4\":12.2},{\"datetime\":\"2025-12-10T01:06:00.000Z\",\"Time\":\"10-Dec 06:36\",\"CR-1\":8.5,\"CR-2\":-12.3,\"CR-3\":10.4,\"CR-4\":12},{\"datetime\":\"2025-12-10T01:07:00.000Z\",\"Time\":\"10-Dec 06:37\",\"CR-1\":8.6,\"CR-2\":-12.3,\"CR-3\":10.4,\"CR-4\":11.9},{\"datetime\":\"2025-12-10T01:08:00.000Z\",\"Time\":\"10-Dec 06:38\",\"CR-1\":8.5,\"CR-2\":-12.3,\"CR-3\":10.4,\"CR-4\":11.8},{\"datetime\":\"2025-12-10T01:09:00.000Z\",\"Time\":\"10-Dec 06:39\",\"CR-1\":8.4,\"CR-2\":-12.3,\"CR-3\":10.3,\"CR-4\":11.7},{\"datetime\":\"2025-12-10T01:10:00.000Z\",\"Time\":\"10-Dec 06:40\",\"CR-1\":8.5,\"CR-2\":-12.3,\"CR-3\":10.3,\"CR-4\":11.7},{\"datetime\":\"2025-12-10T01:11:00.000Z\",\"Time\":\"10-Dec 06:41\",\"CR-1\":8.4,\"CR-2\":-12.3,\"CR-3\":10.3,\"CR-4\":11.6},{\"datetime\":\"2025-12-10T01:12:00.000Z\",\"Time\":\"10-Dec 06:42\",\"CR-1\":8.7,\"CR-2\":-12.4,\"CR-3\":10.3,\"CR-4\":11.5},{\"datetime\":\"2025-12-10T01:13:00.000Z\",\"Time\":\"10-Dec 06:43\",\"CR-1\":8.4,\"CR-2\":-12.4,\"CR-3\":10.3,\"CR-4\":11.4},{\"datetime\":\"2025-12-10T01:14:00.000Z\",\"Time\":\"10-Dec 06:44\",\"CR-1\":8.6,\"CR-2\":-12.4,\"CR-3\":10.3,\"CR-4\":11.3},{\"datetime\":\"2025-12-10T01:15:00.000Z\",\"Time\":\"10-Dec 06:45\",\"CR-1\":8.4,\"CR-2\":-12.4,\"CR-3\":10.3,\"CR-4\":11.5},{\"datetime\":\"2025-12-10T01:16:00.000Z\",\"Time\":\"10-Dec 06:46\",\"CR-1\":8.8,\"CR-2\":-12.4,\"CR-3\":10.3,\"CR-4\":11.5},{\"datetime\":\"2025-12-10T01:17:00.000Z\",\"Time\":\"10-Dec 06:47\",\"CR-1\":8.5,\"CR-2\":-12.4,\"CR-3\":10.3,\"CR-4\":11.2},{\"datetime\":\"2025-12-10T01:18:00.000Z\",\"Time\":\"10-Dec 06:48\",\"CR-1\":8.8,\"CR-2\":-12.4,\"CR-3\":10.3,\"CR-4\":11.1},{\"datetime\":\"2025-12-10T01:19:00.000Z\",\"Time\":\"10-Dec 06:49\",\"CR-1\":8.5,\"CR-2\":-12.4,\"CR-3\":10.3,\"CR-4\":11.1},{\"datetime\":\"2025-12-10T01:20:00.000Z\",\"Time\":\"10-Dec 06:50\",\"CR-1\":8.7,\"CR-2\":-12.4,\"CR-3\":10.3,\"CR-4\":11},{\"datetime\":\"2025-12-10T01:21:00.000Z\",\"Time\":\"10-Dec 06:51\",\"CR-1\":8.6,\"CR-2\":-12.4,\"CR-3\":10.3,\"CR-4\":10.9},{\"datetime\":\"2025-12-10T01:22:00.000Z\",\"Time\":\"10-Dec 06:52\",\"CR-1\":8.7,\"CR-2\":-12.5,\"CR-3\":10.3,\"CR-4\":10.8},{\"datetime\":\"2025-12-10T01:23:00.000Z\",\"Time\":\"10-Dec 06:53\",\"CR-1\":8.7,\"CR-2\":-12.4,\"CR-3\":10.3,\"CR-4\":10.8},{\"datetime\":\"2025-12-10T01:24:00.000Z\",\"Time\":\"10-Dec 06:54\",\"CR-1\":8.6,\"CR-2\":-12.5,\"CR-3\":10.2,\"CR-4\":10.7},{\"datetime\":\"2025-12-10T01:25:00.000Z\",\"Time\":\"10-Dec 06:55\",\"CR-1\":8.8,\"CR-2\":-12.4,\"CR-3\":10.2,\"CR-4\":10.7},{\"datetime\":\"2025-12-10T01:26:00.000Z\",\"Time\":\"10-Dec 06:56\",\"CR-1\":8.6,\"CR-2\":-12.4,\"CR-3\":10.2,\"CR-4\":10.6},{\"datetime\":\"2025-12-10T01:27:00.000Z\",\"Time\":\"10-Dec 06:57\",\"CR-1\":8.8,\"CR-2\":-12.4,\"CR-3\":10.2,\"CR-4\":10.6},{\"datetime\":\"2025-12-10T01:28:00.000Z\",\"Time\":\"10-Dec 06:58\",\"CR-1\":8.5,\"CR-2\":-12.5,\"CR-3\":10.2,\"CR-4\":10.5},{\"datetime\":\"2025-12-10T01:29:00.000Z\",\"Time\":\"10-Dec 06:59\",\"CR-1\":8.8,\"CR-2\":-12.4,\"CR-3\":10.2,\"CR-4\":10.4},{\"datetime\":\"2025-12-10T01:30:00.000Z\",\"Time\":\"10-Dec 07:00\",\"CR-1\":8.5,\"CR-2\":-12.4,\"CR-3\":10.2,\"CR-4\":10.4},{\"datetime\":\"2025-12-10T01:31:00.000Z\",\"Time\":\"10-Dec 07:01\",\"CR-1\":8.8,\"CR-2\":-12.4,\"CR-3\":10.3,\"CR-4\":10.4},{\"datetime\":\"2025-12-10T01:32:00.000Z\",\"Time\":\"10-Dec 07:02\",\"CR-1\":8.6,\"CR-2\":-12.5,\"CR-3\":10.3,\"CR-4\":10.3},{\"datetime\":\"2025-12-10T01:33:00.000Z\",\"Time\":\"10-Dec 07:03\",\"CR-1\":8.8,\"CR-2\":-12.5,\"CR-3\":10.2,\"CR-4\":10.7},{\"datetime\":\"2025-12-10T01:34:00.000Z\",\"Time\":\"10-Dec 07:04\",\"CR-1\":8.7,\"CR-2\":-12.4,\"CR-3\":10.3,\"CR-4\":10.4},{\"datetime\":\"2025-12-10T01:35:00.000Z\",\"Time\":\"10-Dec 07:05\",\"CR-1\":8.9,\"CR-2\":-12.6,\"CR-3\":10.3,\"CR-4\":10.3},{\"datetime\":\"2025-12-10T01:36:00.000Z\",\"Time\":\"10-Dec 07:06\",\"CR-1\":8.7,\"CR-2\":-12.5,\"CR-3\":10.3,\"CR-4\":10.3},{\"datetime\":\"2025-12-10T01:37:00.000Z\",\"Time\":\"10-Dec 07:07\",\"CR-1\":8.9,\"CR-2\":-12.5,\"CR-3\":10.3,\"CR-4\":10.2},{\"datetime\":\"2025-12-10T01:38:00.000Z\",\"Time\":\"10-Dec 07:08\",\"CR-1\":8.6,\"CR-2\":-12.5,\"CR-3\":10.2,\"CR-4\":10.2},{\"datetime\":\"2025-12-10T01:39:00.000Z\",\"Time\":\"10-Dec 07:09\",\"CR-1\":8.9,\"CR-2\":-12.5,\"CR-3\":10.2,\"CR-4\":10.2},{\"datetime\":\"2025-12-10T01:40:00.000Z\",\"Time\":\"10-Dec 07:10\",\"CR-1\":8.8,\"CR-2\":-12.5,\"CR-3\":10.3,\"CR-4\":10.1},{\"datetime\":\"2025-12-10T01:41:00.000Z\",\"Time\":\"10-Dec 07:11\",\"CR-1\":8.9,\"CR-2\":-12.5,\"CR-3\":10.3,\"CR-4\":10},{\"datetime\":\"2025-12-10T01:42:00.000Z\",\"Time\":\"10-Dec 07:12\",\"CR-1\":8.7,\"CR-2\":-12.6,\"CR-3\":10.2,\"CR-4\":10.6},{\"datetime\":\"2025-12-10T01:43:00.000Z\",\"Time\":\"10-Dec 07:13\",\"CR-1\":9,\"CR-2\":-12.6,\"CR-3\":10.2,\"CR-4\":10.9},{\"datetime\":\"2025-12-10T01:44:00.000Z\",\"Time\":\"10-Dec 07:14\",\"CR-1\":8.8,\"CR-2\":-12.6,\"CR-3\":10.2,\"CR-4\":11},{\"datetime\":\"2025-12-10T01:45:00.000Z\",\"Time\":\"10-Dec 07:15\",\"CR-1\":8.9,\"CR-2\":-12.6,\"CR-3\":10.2,\"CR-4\":11.2},{\"datetime\":\"2025-12-10T01:46:00.000Z\",\"Time\":\"10-Dec 07:16\",\"CR-1\":8.8,\"CR-2\":-12.6,\"CR-3\":10.2,\"CR-4\":11.3}]","payloadType":"json","x":130,"y":1700,"wires":[["4a5aa503c41910cc"]]},{"id":"4a5aa503c41910cc","type":"change","z":"9fcb1fec5d4354f5","name":"Data","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":270,"y":1700,"wires":[["9257161960e7a2d3"]]},{"id":"9257161960e7a2d3","type":"function","z":"9fcb1fec5d4354f5","name":"Chart Limits","func":"msg.lsl1= flow.get(\"lsl1\");\nmsg.lsl2= flow.get(\"lsl2\");\nmsg.lsl3= flow.get(\"lsl3\");\nmsg.lsl4= flow.get(\"lsl4\");\n\nmsg.usl1= flow.get(\"usl1\");\nmsg.usl2= flow.get(\"usl2\");\nmsg.usl3= flow.get(\"usl3\");\nmsg.usl4= flow.get(\"usl4\");\n\nmsg.min1=0\nmsg.min2=-20\nmsg.min3=0\nmsg.min4=0\n\nmsg.max1=30\nmsg.max2=0\nmsg.max3=30\nmsg.max4=30\n\nmsg.charttitle1=\"FG Cold Room-1\"\nmsg.charttitle2=\"Frozen Room-1\"\nmsg.charttitle3=\"RM Cold Room-1\"\nmsg.charttitle4=\"WIP Cold Room-1\"\n\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":430,"y":1700,"wires":[["21ca0829bbb67b56"]]},{"id":"21ca0829bbb67b56","type":"change","z":"9fcb1fec5d4354f5","name":"Variables","rules":[{"t":"set","p":"pagetitle","pt":"msg","to":"COLD ROOM TEMPERATURE REPORT","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":580,"y":1700,"wires":[["552002a3f019cf7f"]]},{"id":"552002a3f019cf7f","type":"function","z":"9fcb1fec5d4354f5","name":"Generate 1x4 Chart-email friendly","func":"// Input data is expected in msg.payload as an array of objects:\n// [{\"datetime\":..., \"Time\":\"...\", \"CR-1\":..., \"CR-2\":...}, ...]\n\n// --- GLOBAL DYNAMIC INPUTS ---\nconst inputData = msg.payload;\n\n// Global Y-AXIS SCALE CONTROL\nconst globalYMin = msg.min !== undefined ? parseFloat(msg.min) : -20;\nconst globalYMax = msg.max !== undefined ? parseFloat(msg.max) : 50;\n\n// REPORT TITLES\nconst reportMainTitle = 'COLD ROOM TEMPERATURE MONITORING REPORT';\n\n// Define the core configurations for the 4 sensors\nconst sensorData = {\n \"CR-1\": {\n title: msg.charttitle1 || \"FG Cold Room-1\",\n lsl: msg.lsl1 !== undefined ? parseFloat(msg.lsl1) : undefined,\n usl: msg.usl1 !== undefined ? parseFloat(msg.usl1) : undefined,\n yMin: msg.min1 !== undefined ? parseFloat(msg.min1) : undefined,\n yMax: msg.max1 !== undefined ? parseFloat(msg.max1) : undefined,\n color: { border: '#4CAF50', bg: 'rgba(76, 175, 80, 0.3)', highlight: '#66BB6A' }\n },\n \"CR-2\": {\n title: msg.charttitle2 || \"Frozen Room-1\",\n lsl: msg.lsl2 !== undefined ? parseFloat(msg.lsl2) : undefined,\n usl: msg.usl2 !== undefined ? parseFloat(msg.usl2) : undefined,\n yMin: msg.min2 !== undefined ? parseFloat(msg.min2) : undefined,\n yMax: msg.max2 !== undefined ? parseFloat(msg.max2) : undefined,\n color: { border: '#2196F3', bg: 'rgba(33, 150, 243, 0.3)', highlight: '#42A5F5' }\n },\n \"CR-3\": {\n title: msg.charttitle3 || \"RM Cold Room-1\",\n lsl: msg.lsl3 !== undefined ? parseFloat(msg.lsl3) : undefined,\n usl: msg.usl3 !== undefined ? parseFloat(msg.usl3) : undefined,\n yMin: msg.min3 !== undefined ? parseFloat(msg.min3) : undefined,\n yMax: msg.max3 !== undefined ? parseFloat(msg.max3) : undefined,\n color: { border: '#FF9800', bg: 'rgba(255, 152, 0, 0.3)', highlight: '#FFA726' }\n },\n \"CR-4\": {\n title: msg.charttitle4 || \"WIP Cold Room-1\",\n lsl: msg.lsl4 !== undefined ? parseFloat(msg.lsl4) : undefined,\n usl: msg.usl4 !== undefined ? parseFloat(msg.usl4) : undefined,\n yMin: msg.min4 !== undefined ? parseFloat(msg.min4) : undefined,\n yMax: msg.max4 !== undefined ? parseFloat(msg.max4) : undefined,\n color: { border: '#E91E63', bg: 'rgba(233, 30, 99, 0.3)', highlight: '#EC407A' }\n }\n};\n\n// Define display orders\nconst metricsDisplayOrder = [\"CR-1\", \"CR-3\", \"CR-2\", \"CR-4\"];\nconst chartDisplayOrder = [\"CR-1\", \"CR-2\", \"CR-3\", \"CR-4\"];\n\nlet overallMinTime = \"N/A\";\nlet overallMaxTime = \"N/A\";\n\n// 1. Process Data\nconst fieldsToProcess = [\"CR-1\", \"CR-2\", \"CR-3\", \"CR-4\"];\nfor (const fieldName of fieldsToProcess) {\n const config = sensorData[fieldName];\n const labels = inputData.map(item => item.Time);\n const temperatures = inputData.map(item => item[fieldName]);\n\n const finalYMin = config.yMin !== undefined ? config.yMin : globalYMin;\n const finalYMax = config.yMax !== undefined ? config.yMax : globalYMax;\n\n let stats = {\n minTemp: \"N/A\", minTime: \"N/A\", maxTemp: \"N/A\", maxTime: \"N/A\", avg: \"N/A\"\n };\n\n if (temperatures.length > 0) {\n const sum = temperatures.reduce((a, b) => parseFloat(a) + (parseFloat(b) || 0), 0);\n stats.avg = (sum / temperatures.length).toFixed(1);\n\n let minVal = Infinity;\n let maxVal = -Infinity;\n let minIndex = -1;\n let maxIndex = -1;\n\n temperatures.forEach((temp, index) => {\n const numericalTemp = parseFloat(temp);\n if (isNaN(numericalTemp)) return;\n\n if (numericalTemp < minVal) {\n minVal = numericalTemp;\n minIndex = index;\n }\n if (numericalTemp > maxVal) {\n maxVal = numericalTemp;\n maxIndex = index;\n }\n });\n\n if (minIndex !== -1) {\n stats.minTemp = minVal.toFixed(1);\n stats.minTime = labels[minIndex];\n }\n if (maxIndex !== -1) {\n stats.maxTemp = maxVal.toFixed(1);\n stats.maxTime = labels[maxIndex];\n }\n }\n\n if (labels.length > 0) {\n if (overallMinTime === \"N/A\") overallMinTime = labels[0];\n overallMaxTime = labels[labels.length - 1];\n }\n\n config.labelData = labels;\n config.tempData = temperatures;\n config.stats = stats;\n config.finalYMin = finalYMin;\n config.finalYMax = finalYMax;\n}\n\n// 2. Build Metrics HTML (1x4 - TIGHT)\nlet metricsHTML = '';\nfor (const fieldName of metricsDisplayOrder) {\n const config = sensorData[fieldName];\n const finalYMin = config.finalYMin;\n const finalYMax = config.finalYMax;\n const LSL = config.lsl;\n const USL = config.usl;\n const displayFinalYMin = finalYMin.toFixed(1);\n const displayFinalYMax = finalYMax.toFixed(1);\n const displayLSLUSL = (LSL !== undefined && USL !== undefined)\n ? `${LSL.toFixed(1)}°C to ${USL.toFixed(1)}°C`\n : 'N/A';\n\n metricsHTML += `\n <td class=\"summary-tile\" style=\"border-left-color: ${config.color.border}; background-color: #252525;\">\n <div class=\"tile-title\" style=\"color: ${config.color.highlight};\">${config.title} (${fieldName})</div>\n <div class=\"tile-stat\">\n <span class=\"stat-label\">Min:</span> <span class=\"stat-value\">${config.stats.minTemp}°C</span>\n <span class=\"stat-subvalue\">(@ ${config.stats.minTime})</span>\n </div>\n <div class=\"tile-stat\">\n <span class=\"stat-label\">Max:</span> <span class=\"stat-value\">${config.stats.maxTemp}°C</span>\n <span class=\"stat-subvalue\">(@ ${config.stats.maxTime})</span>\n </div>\n <div class=\"tile-stat\">\n <span class=\"stat-label\">Avg:</span> <span class=\"stat-value\">${config.stats.avg}°C</span>\n <span class=\"stat-subvalue\">Limit: ${displayLSLUSL} (Y: ${displayFinalYMin}°C to ${displayFinalYMax}°C)</span>\n </div>\n </td>\n `;\n}\n\n// 3. Build FULL WIDTH Charts (ONE PER ROW)\nlet chartsHTML = '';\nconst chartURLs = {};\n\nfor (const fieldName of chartDisplayOrder) {\n const config = sensorData[fieldName];\n\n const annotations = [];\n\n // BRIGHT VISIBLE LSL LINE\n if (config.lsl !== undefined) {\n annotations.push({\n type: 'line',\n mode: 'horizontal',\n scaleID: 'y-axis-0',\n value: config.lsl,\n borderColor: '#FFD700',\n borderWidth: 1,\n borderDash: [10, 5],\n label: {\n enabled: true,\n content: 'LSL: ' + config.lsl.toFixed(1) + '°C',\n position: 'left',\n backgroundColor: '#FFD700',\n fontColor: '#000',\n fontSize: 11,\n fontStyle: 'bold',\n xPadding: 8,\n yPadding: 5,\n cornerRadius: 4\n }\n });\n }\n\n // BRIGHT VISIBLE USL LINE\n if (config.usl !== undefined) {\n annotations.push({\n type: 'line',\n mode: 'horizontal',\n scaleID: 'y-axis-0',\n value: config.usl,\n borderColor: '#FF4500',\n borderWidth: 1,\n borderDash: [10, 5],\n label: {\n enabled: true,\n content: 'USL: ' + config.usl.toFixed(1) + '°C',\n position: 'right',\n backgroundColor: '#FF4500',\n fontColor: '#FFF',\n fontSize: 11,\n fontStyle: 'bold',\n xPadding: 8,\n yPadding: 5,\n cornerRadius: 4\n }\n });\n }\n\n let labels = config.labelData;\n let data = config.tempData;\n\n if (labels.length > 150) {\n const step = Math.ceil(labels.length / 150);\n labels = labels.filter((_, i) => i % step === 0);\n data = data.filter((_, i) => i % step === 0);\n }\n\n const simplifiedLabels = labels.map(label => {\n const parts = label.split(' ');\n if (parts.length > 1) {\n const timePart = parts[1];\n const timeComponents = timePart.split(':');\n return timeComponents.length >= 2 ? `${timeComponents[0]}:${timeComponents[1]}` : timePart;\n }\n return label;\n });\n\n const chartConfig = {\n type: 'line',\n data: {\n labels: simplifiedLabels,\n datasets: [{\n label: config.title,\n data: data,\n borderColor: config.color.border,\n backgroundColor: config.color.bg,\n borderWidth: 2.5,\n fill: false,\n pointRadius: 0,\n lineTension: 0.1\n }]\n },\n options: {\n responsive: false,\n legend: { display: false },\n title: {\n display: true,\n text: config.title + ' (' + fieldName + ')',\n fontSize: 14,\n fontStyle: 'bold',\n fontColor: '#E8E8E8',\n padding: 10\n },\n annotation: {\n annotations: annotations\n },\n scales: {\n yAxes: [{\n id: 'y-axis-0',\n ticks: {\n fontSize: 11,\n fontColor: '#C0C0C0',\n beginAtZero: false,\n min: config.finalYMin,\n max: config.finalYMax,\n callback: function (value) {\n return value.toFixed(1) + '°';\n }\n },\n scaleLabel: {\n display: true,\n labelString: 'Temperature (°C)',\n fontSize: 12,\n fontColor: '#C0C0C0',\n fontStyle: 'bold'\n },\n gridLines: {\n color: 'rgba(255,255,255,0.1)',\n lineWidth: 1,\n drawBorder: true\n }\n }],\n xAxes: [{\n ticks: {\n fontSize: 9,\n fontColor: '#A0A0A0',\n maxRotation: 90,\n minRotation: 90,\n autoSkip: true,\n maxTicksLimit: 25\n },\n scaleLabel: {\n display: true,\n labelString: 'Time (HH:MM)',\n fontSize: 11,\n fontColor: '#C0C0C0'\n },\n gridLines: {\n display: false\n }\n }]\n },\n layout: {\n padding: {\n left: 15,\n right: 15,\n top: 10,\n bottom: 10\n }\n }\n }\n };\n\n const chartJSON = JSON.stringify(chartConfig);\n const encodedChart = encodeURIComponent(chartJSON);\n // FULL WIDTH: 1100x280\n const chartImageUrl = `https://quickchart.io/chart?bkg=rgb(32,32,32)&devicePixelRatio=1&c=${encodedChart}&width=1100&height=280`;\n\n chartURLs[fieldName] = chartImageUrl;\n\n chartsHTML += `\n <div class=\"chart-container\" style=\"border-left-color: ${config.color.border}; background-color: #252525;\">\n <img src=\"${chartImageUrl}\" alt=\"${config.title}\" />\n </div>\n `;\n}\n\n// 4. Build HTML - FULL WIDTH CHARTS\nconst htmlContent = `\n<!DOCTYPE html>\n<html>\n<head>\n <title>${reportMainTitle}</title>\n <meta charset=\"UTF-8\">\n <style>\n * { \n margin: 0; \n padding: 0; \n box-sizing: border-box; \n }\n body { \n font-family: Arial, Helvetica, sans-serif;\n background-color: #1a1a1a !important;\n color: #E8E8E8; \n padding: 15px; \n max-width: 1150px; \n margin: 0 auto; \n }\n .report-title { \n font-size: 24px; \n font-weight: bold; \n color: #4CAF50; \n text-align: center; \n margin-bottom: 5px;\n }\n .overall-period { \n font-size: 13px; \n text-align: center; \n margin-bottom: 15px; \n color: #B0B0B0;\n }\n .metrics-grid-summary { \n width: 100%; \n border-collapse: separate; \n border-spacing: 8px; \n margin-bottom: 18px; \n }\n .summary-tile { \n width: 25%; \n padding: 8px; \n border-radius: 4px; \n border-left: 4px solid; \n vertical-align: top;\n }\n .tile-title { \n font-size: 13px; \n font-weight: bold; \n margin-bottom: 3px; \n padding-bottom: 3px; \n border-bottom: 1px solid #404040;\n }\n .tile-stat { \n font-size: 11px; \n line-height: 1.2; \n margin: 1px 0; \n padding: 1px 0;\n }\n .stat-label { \n font-weight: bold; \n color: #999; \n display: inline-block; \n width: 34px; \n font-size: 11px; \n }\n .stat-value { \n font-weight: bold; \n color: #FFFFFF; \n margin-right: 3px; \n font-size: 11px; \n }\n .stat-subvalue { \n font-size: 9px; \n color: #777; \n display: block; \n margin-left: 36px; \n margin-top: 0px;\n line-height: 1.1;\n }\n .chart-container { \n background-color: #252525;\n padding: 6px; \n border-radius: 4px; \n border-left: 5px solid; \n margin-bottom: 12px;\n text-align: center;\n }\n .chart-container img { \n width: 100%; \n max-width: 1100px;\n height: auto; \n display: block; \n margin: 0 auto;\n border-radius: 3px; \n }\n </style>\n</head>\n<body bgcolor=\"#1a1a1a\">\n <div class=\"report-title\">${reportMainTitle}</div>\n <div class=\"overall-period\">Report Period: ${overallMinTime} to ${overallMaxTime}</div>\n <table class=\"metrics-grid-summary\"><tr>${metricsHTML}</tr></table>\n ${chartsHTML}\n</body>\n</html>\n`;\n\n// 5. Build JSON\nconst jsonPayload = {\n reportTitle: reportMainTitle,\n reportPeriod: { start: overallMinTime, end: overallMaxTime },\n sensors: {},\n chartURLs: chartURLs,\n generatedAt: new Date().toISOString()\n};\n\nfor (const fieldName of [\"CR-1\", \"CR-2\", \"CR-3\", \"CR-4\"]) {\n const config = sensorData[fieldName];\n jsonPayload.sensors[fieldName] = {\n title: config.title,\n statistics: {\n min: { value: config.stats.minTemp, time: config.stats.minTime },\n max: { value: config.stats.maxTemp, time: config.stats.maxTime },\n average: config.stats.avg\n },\n limits: { lsl: config.lsl, usl: config.usl, yMin: config.finalYMin, yMax: config.finalYMax },\n dataPoints: config.labelData.length,\n color: config.color\n };\n}\n\n// 6. Set outputs\nconst rawDateString = overallMinTime;\nconst dateParts = rawDateString.split(' ');\nconst dayMonth = dateParts.length > 0 ? dateParts[0] : 'Report';\nconst filenameBase = reportMainTitle.replace(/\\s/g, '_');\nconst pathPrefix = \"C:\\\\temp\\\\\";\n\nmsg.payload = htmlContent;\nmsg.filename = `${pathPrefix}${dayMonth}_${filenameBase}.html`;\nmsg.jsonPayload = jsonPayload;\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":800,"y":1700,"wires":[["3f74c0309b626ca7"]]}]
