From a51c87366ae8c3bd7817de11e407c2e560a16bce Mon Sep 17 00:00:00 2001 From: Darshan Jain Date: Sun, 9 Feb 2025 22:08:59 +0530 Subject: [PATCH 01/10] added component Drop Down Button --- .../src/Components/DropDownButton/index.jsx | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 Client/src/Components/DropDownButton/index.jsx diff --git a/Client/src/Components/DropDownButton/index.jsx b/Client/src/Components/DropDownButton/index.jsx new file mode 100644 index 000000000..9f9e241fa --- /dev/null +++ b/Client/src/Components/DropDownButton/index.jsx @@ -0,0 +1,95 @@ +import {useState, useRef} from 'react'; +import { useTheme } from "@mui/material/styles"; +import Button from '@mui/material/Button'; +import ButtonGroup from '@mui/material/ButtonGroup'; +import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; +import ClickAwayListener from '@mui/material/ClickAwayListener'; +import Grow from '@mui/material/Grow'; +import Paper from '@mui/material/Paper'; +import Popper from '@mui/material/Popper'; +import MenuItem from '@mui/material/MenuItem'; +import MenuList from '@mui/material/MenuList'; + +const DropDownButton = ({options, setValue, hanldeClick}) => { + const theme = useTheme(); + const [open, setOpen] = useState(false); + const anchorRef = useRef(null); + const [selectedIndex, setSelectedIndex] = useState(1); + + const handleMenuItemClick = (event, index, tag) => { + setSelectedIndex(index); + setValue(tag); + setOpen(false); + }; + + const handleToggle = () => { + setOpen((prevOpen) => !prevOpen); + }; + + const handleClose = (event) => { + if (anchorRef.current && anchorRef.current.contains(event.target)) { + return; + } + + setOpen(false); + }; + + return ( + <> + + + + + + {({ TransitionProps, placement }) => ( + + + + + {options.map((option, index) => ( + handleMenuItemClick(event,index,option.tag)} + > + {option.name} + + ))} + + + + + )} + + + ); +} + +export default DropDownButton; \ No newline at end of file From 063c446a000dd32f5155113cb6b1c1923e276145 Mon Sep 17 00:00:00 2001 From: Darshan Jain Date: Mon, 10 Feb 2025 22:12:39 +0530 Subject: [PATCH 02/10] report download function --- Client/package-lock.json | 215 ++++++++++++++++++ Client/package.json | 1 + .../ReportDownloadButton/index.jsx | 40 ++++ .../Components/MonitorStatusHeader/index.jsx | 15 +- Client/src/Pages/Uptime/Details/index.jsx | 2 + Client/src/Utils/Report/downloadReport.jsx | 65 ++++++ Client/src/Utils/Report/report.jsx | 214 +++++++++++++++++ Client/src/Utils/Report/reportStyles.css | 20 ++ 8 files changed, 571 insertions(+), 1 deletion(-) create mode 100644 Client/src/Components/MonitorStatusHeader/ReportDownloadButton/index.jsx create mode 100644 Client/src/Utils/Report/downloadReport.jsx create mode 100644 Client/src/Utils/Report/report.jsx create mode 100644 Client/src/Utils/Report/reportStyles.css diff --git a/Client/package-lock.json b/Client/package-lock.json index 556caf1bc..014da8b06 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -22,6 +22,7 @@ "axios": "^1.7.4", "dayjs": "1.11.13", "flag-icons": "7.3.2", + "html2pdf.js": "^0.10.2", "i18next": "^24.2.2", "immutability-helper": "^3.1.1", "joi": "17.13.3", @@ -2537,6 +2538,13 @@ "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", "license": "MIT" }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/react": { "version": "18.3.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", @@ -2822,6 +2830,18 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2871,6 +2891,15 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2914,6 +2943,18 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -3005,6 +3046,33 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvg": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/canvg/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3076,6 +3144,18 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "license": "MIT" }, + "node_modules/core-js": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz", + "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -3115,6 +3195,15 @@ "tiny-invariant": "^1.0.6" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -3439,6 +3528,13 @@ "csstype": "^3.0.2" } }, + "node_modules/dompurify": { + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz", + "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true + }, "node_modules/dot-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", @@ -3663,6 +3759,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -4027,6 +4129,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -4504,6 +4612,30 @@ "void-elements": "3.1.0" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/html2pdf.js": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/html2pdf.js/-/html2pdf.js-0.10.2.tgz", + "integrity": "sha512-WyHVeMb18Bp7vYTmBv1GVsThH//K7SRfHdSdhHPkl4JvyQarNQXnailkYn0QUbRRmnN5rdbbmSIGEsPZtzPy2Q==", + "license": "MIT", + "dependencies": { + "es6-promise": "^4.2.5", + "html2canvas": "^1.0.0", + "jspdf": "^2.3.1" + } + }, "node_modules/i18next": { "version": "24.2.2", "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.2.tgz", @@ -5153,6 +5285,24 @@ "node": ">=6" } }, + "node_modules/jspdf": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz", + "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.5.4", + "html2canvas": "^1.0.0-rc.5" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -5708,6 +5858,13 @@ "pbf": "bin/pbf" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "optional": true + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5862,6 +6019,16 @@ "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", "license": "ISC" }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, "node_modules/raf-schd": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", @@ -6221,6 +6388,16 @@ "node": ">=0.10.0" } }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -6528,6 +6705,16 @@ "node": ">=0.10.0" } }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -6698,6 +6885,25 @@ "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", "license": "MIT" }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -6903,6 +7109,15 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/victory-vendor": { "version": "36.9.2", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", diff --git a/Client/package.json b/Client/package.json index d4c26e1c5..cc3657353 100644 --- a/Client/package.json +++ b/Client/package.json @@ -25,6 +25,7 @@ "axios": "^1.7.4", "dayjs": "1.11.13", "flag-icons": "7.3.2", + "html2pdf.js": "^0.10.2", "i18next": "^24.2.2", "immutability-helper": "^3.1.1", "joi": "17.13.3", diff --git a/Client/src/Components/MonitorStatusHeader/ReportDownloadButton/index.jsx b/Client/src/Components/MonitorStatusHeader/ReportDownloadButton/index.jsx new file mode 100644 index 000000000..424b377c2 --- /dev/null +++ b/Client/src/Components/MonitorStatusHeader/ReportDownloadButton/index.jsx @@ -0,0 +1,40 @@ +import { useState } from "react"; +import DropDownButton from "../../DropDownButton"; +import { Box } from "@mui/material"; +import {downloadReport} from "../../../Utils/Report/downloadReport"; + +const options = [ + { name: "Download HTML Report", tag: "html" }, + { name: "Download Pdf Report", tag: "pdf" }, +] + +const ReportDownloadButton = ({ shouldRender, monitor, certificateExpiry }) => { + const [downloadFormat, setDownloadFormat] = useState("pdf"); + if (!shouldRender) return null; + + + + const handleDownload = async () => { + try { + console.log(monitor); + await downloadReport({ + monitorData: monitor, + downloadFormat, + certificateExpiry, + }); + } finally { + setDownloadFormat("pdf"); //passing pdf as default + } + }; + + return ( + + + + + ); +}; + +export default ReportDownloadButton; diff --git a/Client/src/Components/MonitorStatusHeader/index.jsx b/Client/src/Components/MonitorStatusHeader/index.jsx index cbbba52b2..1462db0ab 100644 --- a/Client/src/Components/MonitorStatusHeader/index.jsx +++ b/Client/src/Components/MonitorStatusHeader/index.jsx @@ -5,16 +5,20 @@ import { useTheme } from "@emotion/react"; import useUtils from "../../Pages/Uptime/Monitors/Hooks/useUtils"; import { formatDurationRounded } from "../../Utils/timeUtils"; import ConfigButton from "./ConfigButton"; +import ReportDownloadButton from "./ReportDownloadButton"; import SkeletonLayout from "./skeleton"; import PropTypes from "prop-types"; -const MonitorStatusHeader = ({ path, shouldRender = true, isAdmin, monitor }) => { +const MonitorStatusHeader = ({ path, shouldRender = true, isAdmin, monitor, certificateExpiry }) => { const theme = useTheme(); const { statusColor, statusMsg, determineState } = useUtils(); if (!shouldRender) { return ; } + console.log(monitor); + console.log(certificateExpiry); + return ( + + + + ); }; @@ -51,6 +63,7 @@ MonitorStatusHeader.propTypes = { shouldRender: PropTypes.bool, isAdmin: PropTypes.bool, monitor: PropTypes.object, + certificateExpiry: PropTypes.object, }; export default MonitorStatusHeader; diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index 3063ba27a..f606d8f36 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -103,6 +103,7 @@ const UptimeDetails = () => { isAdmin={isAdmin} shouldRender={!monitorIsLoading} monitor={monitor} + certificateExpiry={certificateExpiry} /> There is no check history for this monitor yet. @@ -119,6 +120,7 @@ const UptimeDetails = () => { isAdmin={isAdmin} shouldRender={!monitorIsLoading} monitor={monitor} + certificateExpiry={certificateExpiry} /> { + // Create a temporary div to render the report + const tempDiv = document.createElement("div"); + tempDiv.style.position = "absolute"; + tempDiv.style.left = "-9999px"; + document.body.appendChild(tempDiv); + + // Create a promise to handle the rendering and download + return new Promise((resolve) => { + // Use ReactDOM to render the report + const reportRoot = createRoot(tempDiv); + reportRoot.render( + + ); + + // Wait for styles to be applied + setTimeout(() => { + try { + // Create the HTML content + const htmlContent = ` + + + + + + Product Report + + + ${tempDiv.innerHTML} + + `; + + if (downloadFormat === "html") { + // Create and trigger HTML download + const blob = new Blob([htmlContent], { type: "text/html" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `${monitorData.name}-monitor-report-${new Date().toISOString().split("T")[0]}.html`; + document.body.appendChild(a); + a.click(); + + // Cleanup + document.body.removeChild(a); + URL.revokeObjectURL(url); + } else if (downloadFormat === "pdf") { + // Create and trigger PDF download + html2pdf().from(htmlContent).save(`${monitorData.name}-monitor-report-${new Date().toISOString().split("T")[0]}.pdf`); + } + } finally { + // Always cleanup + reportRoot.unmount(); + document.body.removeChild(tempDiv); + resolve(); + } + }, 100); + }); +}; diff --git a/Client/src/Utils/Report/report.jsx b/Client/src/Utils/Report/report.jsx new file mode 100644 index 000000000..55960c7cc --- /dev/null +++ b/Client/src/Utils/Report/report.jsx @@ -0,0 +1,214 @@ +import "./reportStyles.css"; + +const formatDate = (dateString) => { + const options = { + weekday: "short", + year: "numeric", + month: "long", + day: "numeric", + hour: "numeric", + minute: "numeric", + hour12: true, + }; + return new Intl.DateTimeFormat("en-US", options).format(new Date(dateString)); +}; +const formatUptimeStreak = (uptimeStreak) => { + const seconds = Math.floor(uptimeStreak / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + if (days > 0) { + return `${days} day${days > 1 ? "s" : ""}`; + } else if (hours > 0) { + return `${hours} hour${hours > 1 ? "s" : ""}`; + } else if (minutes > 0) { + return `${minutes} minute${minutes > 1 ? "s" : ""}`; + } else { + return `${seconds} second${seconds > 1 ? "s" : ""}`; + } +}; +// const timeSince = (dateString) => { +// const now = new Date(); +// const updatedAt = new Date(dateString); +// const difference = now - updatedAt; + +// return formatUptimeStreak(difference); +// }; + +const calculateDowntimeCount = (checks) => { + if (!checks || checks.length < 2) return 0; + + let downtimeCount = 0; + for (let i = 0; i < checks.length - 1; i++) { + const currentCheck = checks[i]; + const nextCheck = checks[i + 1]; + + if (currentCheck.status === true && nextCheck.status === false) { + downtimeCount++; + } + } + return downtimeCount; +}; + +const calculatePingStats = (checks) => { + if (!checks || checks.length === 0) return { min: 0, max: 0 }; + + let min = Number.MAX_SAFE_INTEGER; + let max = 0; + + checks.forEach((check) => { + if (check.originalResponseTime) { + min = Math.min(min, check.originalResponseTime); + max = Math.max(max, check.originalResponseTime); + } + }); + + return { + min: min === Number.MAX_SAFE_INTEGER ? 0 : min, + max: max, + }; +}; + +const calculateActiveRanges = (checks) => { + if (!checks || checks.length < 2) return { min: 0, max: 0 }; + + const durations = []; + let startTime = null; + + for (let i = checks.length - 1; i >= 0; i--) { + const check = checks[i]; + + if (check.status && !startTime) { + startTime = new Date(check.createdAt); + } + + if (!check.status && startTime) { + const endTime = new Date(check.createdAt); + const duration = endTime - startTime; + if (duration > 0) { + durations.push(duration); + } + startTime = null; + } + } + + // Handle case where site is currently up + if (startTime && checks[0].status) { + const now = new Date(); + const duration = now - startTime; + durations.push(duration); + } + + return durations.length + ? { + min: Math.min(...durations), + max: Math.max(...durations), + } + : { min: 0, max: 0 }; +}; + +export const ProductReport = ({ monitorData, certificateExpiry }) => { + const pingStats = calculatePingStats(monitorData.checks); + + const activeRanges = calculateActiveRanges(monitorData.checks); + + return ( +
+
+

{monitorData.name} Monitoring Report

+
+
+
+

Basic Information

+
+

+ Site URL: {monitorData.url} +

+

+ Date Added:{" "} + {formatDate(monitorData.createdAt)} +

+

Certificate Expiration: {certificateExpiry}

+
+
+
+
+

Availability Statistics

+
+

+ Downtime Count:{" "} + {calculateDowntimeCount(monitorData.checks)}{" "} +

+

+ Uptime Percentage:{" "} + {Math.round(monitorData.periodUptime)} % +

+

+ Last Checked:{" "} + {formatDate(monitorData.updatedAt)} +

+
+
+
+
+

Performance Metrics

+
+

+ Minimum Ping: {pingStats.min}ms +

+

+ Maximum Ping: {pingStats.max}ms +

+

+ Average Ping:{" "} + {Math.round(monitorData.groupedAvgResponseTime.avgResponseTime)}ms +

+
+
+
+
+

Monitoring Details

+
+

+ Total Incidents:{" "} + {monitorData.periodIncidents} +

+

+ Total Checks:{" "} + {monitorData.periodTotalChecks} +

+

+ Check Interval:{" "} + {formatUptimeStreak(monitorData.interval)} +

+
+
+
+
+

Activity Times

+
+

+ Active Time:{" "} + {monitorData.isActive + ? formatUptimeStreak(monitorData.uptimeDuration) + : "0 sec"} +

+

+ Maximum Active Time:{" "} + {formatUptimeStreak(activeRanges.max)} +

+

+ Minimum Active Time:{" "} + {formatUptimeStreak(activeRanges.min)} +

+
+
+
+
+

Report generated on {new Date().toLocaleString()}

+
+
+
+ ); +}; diff --git a/Client/src/Utils/Report/reportStyles.css b/Client/src/Utils/Report/reportStyles.css new file mode 100644 index 000000000..a261e220b --- /dev/null +++ b/Client/src/Utils/Report/reportStyles.css @@ -0,0 +1,20 @@ +.product-report{ + display: flex; + padding: 1rem; + color: rgb(36, 36, 36); + padding: 25px; +} + +.report-content{ + +} + +.info-title { + font-weight:600; +} + +hr{ + border: 0; + border-top: 1px solid #ddd; + width: 700px; +} \ No newline at end of file From 19b36d6eb52101afac71d16273219777dc353564 Mon Sep 17 00:00:00 2001 From: Darshan Jain Date: Mon, 10 Feb 2025 22:40:18 +0530 Subject: [PATCH 03/10] new hook for getting report data --- .../Components/MonitorStatusHeader/index.jsx | 6 +-- .../Uptime/Details/Hooks/useReportFetch.jsx | 34 +++++++++++++ Client/src/Pages/Uptime/Details/index.jsx | 9 ++++ Client/src/Utils/Report/report.jsx | 48 +++++++++---------- 4 files changed, 69 insertions(+), 28 deletions(-) create mode 100644 Client/src/Pages/Uptime/Details/Hooks/useReportFetch.jsx diff --git a/Client/src/Components/MonitorStatusHeader/index.jsx b/Client/src/Components/MonitorStatusHeader/index.jsx index 1462db0ab..7fb3647b9 100644 --- a/Client/src/Components/MonitorStatusHeader/index.jsx +++ b/Client/src/Components/MonitorStatusHeader/index.jsx @@ -9,14 +9,14 @@ import ReportDownloadButton from "./ReportDownloadButton"; import SkeletonLayout from "./skeleton"; import PropTypes from "prop-types"; -const MonitorStatusHeader = ({ path, shouldRender = true, isAdmin, monitor, certificateExpiry }) => { +const MonitorStatusHeader = ({ path, shouldRender = true, isAdmin, monitor, certificateExpiry, reportData }) => { const theme = useTheme(); const { statusColor, statusMsg, determineState } = useUtils(); if (!shouldRender) { return ; } - console.log(monitor); + console.log(reportData); console.log(certificateExpiry); return ( @@ -49,7 +49,7 @@ const MonitorStatusHeader = ({ path, shouldRender = true, isAdmin, monitor, cert /> diff --git a/Client/src/Pages/Uptime/Details/Hooks/useReportFetch.jsx b/Client/src/Pages/Uptime/Details/Hooks/useReportFetch.jsx new file mode 100644 index 000000000..6f6c4458a --- /dev/null +++ b/Client/src/Pages/Uptime/Details/Hooks/useReportFetch.jsx @@ -0,0 +1,34 @@ +import { useEffect, useState } from "react"; +import { networkService } from "../../../../main"; +import { useNavigate } from "react-router-dom"; +import { createToast } from "../../../../Utils/toastUtils"; + +export const useReportFetch = ({ authToken, monitorId, dateRange }) => { + const [reportNetworkError, setReportNetworkError] = useState(false); + const [reportIsLoading, setReportIsLoading] = useState(true); + const [reportData, setReportData] = useState(undefined); + const navigate = useNavigate(); + + useEffect(() => { + const fetchMonitors = async () => { + try { + const res = await networkService.getStatsByMonitorId({ + authToken: authToken, + monitorId: monitorId, + dateRange: dateRange, + normalize: true, + }); + setReportData(res?.data?.data ?? {}); + } catch (error) { + setReportNetworkError(true); + createToast({ body: error.message }); + } finally { + setReportIsLoading(false); + } + }; + fetchMonitors(); + }, [authToken, dateRange, monitorId, navigate]); + return [reportData, reportIsLoading, reportNetworkError]; +}; + +export default useReportFetch; diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index f606d8f36..c7a998998 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -19,6 +19,7 @@ import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import useMonitorFetch from "./Hooks/useMonitorFetch"; import useCertificateFetch from "./Hooks/useCertificateFetch"; import useChecksFetch from "./Hooks/useChecksFetch"; +import useReportFetch from "./Hooks/useReportFetch"; // Constants const BREADCRUMBS = [ @@ -69,6 +70,12 @@ const UptimeDetails = () => { rowsPerPage, }); + const [reportData, reportIsLoading, reportNetworkError] = useReportFetch({ + authToken, + monitorId, + dateRange, + }); + // Handlers const handlePageChange = (_, newPage) => { setPage(newPage); @@ -103,6 +110,7 @@ const UptimeDetails = () => { isAdmin={isAdmin} shouldRender={!monitorIsLoading} monitor={monitor} + reportData={reportData} certificateExpiry={certificateExpiry} /> @@ -120,6 +128,7 @@ const UptimeDetails = () => { isAdmin={isAdmin} shouldRender={!monitorIsLoading} monitor={monitor} + reportData={reportData} certificateExpiry={certificateExpiry} /> { return (
-

{monitorData.name} Monitoring Report

+

{monitorData.name} - monitoring report

+

Report generated on {new Date().toLocaleString()}


-

Basic Information

+

Basic information

- Site URL: {monitorData.url} + Site url: {monitorData.url}

- Date Added:{" "} + Date added:{" "} {formatDate(monitorData.createdAt)}

-

Certificate Expiration: {certificateExpiry}

+

Certificate expiration: {certificateExpiry}


-

Availability Statistics

+

Availability statistics

- Downtime Count:{" "} + Downtime count:{" "} {calculateDowntimeCount(monitorData.checks)}{" "}

- Uptime Percentage:{" "} + Uptime percentage:{" "} {Math.round(monitorData.periodUptime)} %

- Last Checked:{" "} + Last checked:{" "} {formatDate(monitorData.updatedAt)}


-

Performance Metrics

+

Performance metrics

- Minimum Ping: {pingStats.min}ms + Minimum ping: {pingStats.min}ms

- Maximum Ping: {pingStats.max}ms + Maximum ping: {pingStats.max}ms

- Average Ping:{" "} - {Math.round(monitorData.groupedAvgResponseTime.avgResponseTime)}ms + Average ping:{" "} + {Math.round(monitorData.periodAvgResponseTime)}ms


-

Monitoring Details

+

Monitoring details

- Total Incidents:{" "} + Total incidents:{" "} {monitorData.periodIncidents}

- Total Checks:{" "} + Total checks:{" "} {monitorData.periodTotalChecks}

- Check Interval:{" "} + Check interval:{" "} {formatUptimeStreak(monitorData.interval)}


-

Activity Times

+

Activity times

- Active Time:{" "} + Active time:{" "} {monitorData.isActive ? formatUptimeStreak(monitorData.uptimeDuration) : "0 sec"}

- Maximum Active Time:{" "} + Maximum active time:{" "} {formatUptimeStreak(activeRanges.max)}

- Minimum Active Time:{" "} + Minimum active time:{" "} {formatUptimeStreak(activeRanges.min)}

-
-

Report generated on {new Date().toLocaleString()}

-
); From d05a9cad5c4d5e25815e213326538ce7a66aacaa Mon Sep 17 00:00:00 2001 From: Darshan Jain Date: Mon, 10 Feb 2025 23:04:28 +0530 Subject: [PATCH 04/10] using function from timeUtlis and new time util fucntion --- Client/src/Utils/Report/report.jsx | 66 ++++++++----------------- Client/src/Utils/Report/reportUtils.jsx | 0 Client/src/Utils/timeUtils.js | 13 +++++ 3 files changed, 33 insertions(+), 46 deletions(-) create mode 100644 Client/src/Utils/Report/reportUtils.jsx diff --git a/Client/src/Utils/Report/report.jsx b/Client/src/Utils/Report/report.jsx index cd47794fa..8a0b3f6b5 100644 --- a/Client/src/Utils/Report/report.jsx +++ b/Client/src/Utils/Report/report.jsx @@ -1,40 +1,5 @@ import "./reportStyles.css"; - -const formatDate = (dateString) => { - const options = { - weekday: "short", - year: "numeric", - month: "long", - day: "numeric", - hour: "numeric", - minute: "numeric", - hour12: true, - }; - return new Intl.DateTimeFormat("en-US", options).format(new Date(dateString)); -}; -const formatUptimeStreak = (uptimeStreak) => { - const seconds = Math.floor(uptimeStreak / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - const days = Math.floor(hours / 24); - - if (days > 0) { - return `${days} day${days > 1 ? "s" : ""}`; - } else if (hours > 0) { - return `${hours} hour${hours > 1 ? "s" : ""}`; - } else if (minutes > 0) { - return `${minutes} minute${minutes > 1 ? "s" : ""}`; - } else { - return `${seconds} second${seconds > 1 ? "s" : ""}`; - } -}; -// const timeSince = (dateString) => { -// const now = new Date(); -// const updatedAt = new Date(dateString); -// const difference = now - updatedAt; - -// return formatUptimeStreak(difference); -// }; +import { formatDurationRounded, formatDateWithWeekday } from "../timeUtils"; const calculateDowntimeCount = (checks) => { if (!checks || checks.length < 2) return 0; @@ -118,7 +83,7 @@ export const ProductReport = ({ monitorData, certificateExpiry }) => {

{monitorData.name} - monitoring report

Report generated on {new Date().toLocaleString()}

-
+

Basic information

@@ -128,9 +93,12 @@ export const ProductReport = ({ monitorData, certificateExpiry }) => {

Date added:{" "} - {formatDate(monitorData.createdAt)} + {formatDateWithWeekday(monitorData.createdAt)} +

+

+ Certificate expiration:{" "} + {certificateExpiry}

-

Certificate expiration: {certificateExpiry}


@@ -147,7 +115,7 @@ export const ProductReport = ({ monitorData, certificateExpiry }) => {

Last checked:{" "} - {formatDate(monitorData.updatedAt)} + {formatDateWithWeekday(monitorData.updatedAt)}

@@ -156,10 +124,16 @@ export const ProductReport = ({ monitorData, certificateExpiry }) => {

Performance metrics

- Minimum ping: {pingStats.min}ms + Minimum ping:{" "} + {pingStats.min > 2000 + ? (pingStats.min / 1000).toFixed(2) + "s" + : pingStats.min + "ms"}

- Maximum ping: {pingStats.max}ms + Maximum ping:{" "} + {pingStats.max > 2000 + ? (pingStats.max / 1000).toFixed(2) + "s" + : pingStats.max + "ms"}

Average ping:{" "} @@ -181,7 +155,7 @@ export const ProductReport = ({ monitorData, certificateExpiry }) => {

Check interval:{" "} - {formatUptimeStreak(monitorData.interval)} + {formatDurationRounded(monitorData.interval)}

@@ -192,16 +166,16 @@ export const ProductReport = ({ monitorData, certificateExpiry }) => {

Active time:{" "} {monitorData.isActive - ? formatUptimeStreak(monitorData.uptimeDuration) + ? formatDurationRounded(monitorData.uptimeDuration) : "0 sec"}

Maximum active time:{" "} - {formatUptimeStreak(activeRanges.max)} + {formatDurationRounded(activeRanges.max)}

Minimum active time:{" "} - {formatUptimeStreak(activeRanges.min)} + {formatDurationRounded(activeRanges.min)}

diff --git a/Client/src/Utils/Report/reportUtils.jsx b/Client/src/Utils/Report/reportUtils.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/Client/src/Utils/timeUtils.js b/Client/src/Utils/timeUtils.js index 2fd6c6654..e1702b9b0 100644 --- a/Client/src/Utils/timeUtils.js +++ b/Client/src/Utils/timeUtils.js @@ -111,6 +111,19 @@ export const formatDate = (date, customOptions) => { .replace(/\b(AM|PM)\b/g, (match) => match.toLowerCase()); }; +export const formatDateWithWeekday = (date) => { + const options = { + weekday: "short", + year: "numeric", + month: "long", + day: "numeric", + hour: "numeric", + minute: "numeric", + hour12: true, + }; + return new Intl.DateTimeFormat("en-US", options).format(new Date(date)); +} + export const formatDateWithTz = (timestamp, format, timezone) => { const formattedDate = dayjs(timestamp).tz(timezone).format(format); return formattedDate; From e187fb170cbe5d4616316baa6df50fe28a2cbb7f Mon Sep 17 00:00:00 2001 From: Darshan Jain Date: Mon, 10 Feb 2025 23:07:00 +0530 Subject: [PATCH 05/10] moved report Util function to new file --- Client/src/Utils/Report/report.jsx | 71 +------------------------ Client/src/Utils/Report/reportUtils.js | 71 +++++++++++++++++++++++++ Client/src/Utils/Report/reportUtils.jsx | 0 3 files changed, 72 insertions(+), 70 deletions(-) create mode 100644 Client/src/Utils/Report/reportUtils.js delete mode 100644 Client/src/Utils/Report/reportUtils.jsx diff --git a/Client/src/Utils/Report/report.jsx b/Client/src/Utils/Report/report.jsx index 8a0b3f6b5..389c6d6b4 100644 --- a/Client/src/Utils/Report/report.jsx +++ b/Client/src/Utils/Report/report.jsx @@ -1,77 +1,8 @@ import "./reportStyles.css"; import { formatDurationRounded, formatDateWithWeekday } from "../timeUtils"; +import { calculateDowntimeCount, calculatePingStats, calculateActiveRanges} from "./reportUtils"; -const calculateDowntimeCount = (checks) => { - if (!checks || checks.length < 2) return 0; - let downtimeCount = 0; - for (let i = 0; i < checks.length - 1; i++) { - const currentCheck = checks[i]; - const nextCheck = checks[i + 1]; - - if (currentCheck.status === true && nextCheck.status === false) { - downtimeCount++; - } - } - return downtimeCount; -}; - -const calculatePingStats = (checks) => { - if (!checks || checks.length === 0) return { min: 0, max: 0 }; - - let min = Number.MAX_SAFE_INTEGER; - let max = 0; - - checks.forEach((check) => { - if (check.originalResponseTime) { - min = Math.min(min, check.originalResponseTime); - max = Math.max(max, check.originalResponseTime); - } - }); - - return { - min: min === Number.MAX_SAFE_INTEGER ? 0 : min, - max: max, - }; -}; - -const calculateActiveRanges = (checks) => { - if (!checks || checks.length < 2) return { min: 0, max: 0 }; - - const durations = []; - let startTime = null; - - for (let i = checks.length - 1; i >= 0; i--) { - const check = checks[i]; - - if (check.status && !startTime) { - startTime = new Date(check.createdAt); - } - - if (!check.status && startTime) { - const endTime = new Date(check.createdAt); - const duration = endTime - startTime; - if (duration > 0) { - durations.push(duration); - } - startTime = null; - } - } - - // Handle case where site is currently up - if (startTime && checks[0].status) { - const now = new Date(); - const duration = now - startTime; - durations.push(duration); - } - - return durations.length - ? { - min: Math.min(...durations), - max: Math.max(...durations), - } - : { min: 0, max: 0 }; -}; export const ProductReport = ({ monitorData, certificateExpiry }) => { const pingStats = calculatePingStats(monitorData.checks); diff --git a/Client/src/Utils/Report/reportUtils.js b/Client/src/Utils/Report/reportUtils.js new file mode 100644 index 000000000..0aac8a732 --- /dev/null +++ b/Client/src/Utils/Report/reportUtils.js @@ -0,0 +1,71 @@ +export const calculateDowntimeCount = (checks) => { + if (!checks || checks.length < 2) return 0; + + let downtimeCount = 0; + for (let i = 0; i < checks.length - 1; i++) { + const currentCheck = checks[i]; + const nextCheck = checks[i + 1]; + + if (currentCheck.status === true && nextCheck.status === false) { + downtimeCount++; + } + } + return downtimeCount; +}; + +export const calculatePingStats = (checks) => { + if (!checks || checks.length === 0) return { min: 0, max: 0 }; + + let min = Number.MAX_SAFE_INTEGER; + let max = 0; + + checks.forEach((check) => { + if (check.originalResponseTime) { + min = Math.min(min, check.originalResponseTime); + max = Math.max(max, check.originalResponseTime); + } + }); + + return { + min: min === Number.MAX_SAFE_INTEGER ? 0 : min, + max: max, + }; +}; + +export const calculateActiveRanges = (checks) => { + if (!checks || checks.length < 2) return { min: 0, max: 0 }; + + const durations = []; + let startTime = null; + + for (let i = checks.length - 1; i >= 0; i--) { + const check = checks[i]; + + if (check.status && !startTime) { + startTime = new Date(check.createdAt); + } + + if (!check.status && startTime) { + const endTime = new Date(check.createdAt); + const duration = endTime - startTime; + if (duration > 0) { + durations.push(duration); + } + startTime = null; + } + } + + // Handle case where site is currently up + if (startTime && checks[0].status) { + const now = new Date(); + const duration = now - startTime; + durations.push(duration); + } + + return durations.length + ? { + min: Math.min(...durations), + max: Math.max(...durations), + } + : { min: 0, max: 0 }; +}; \ No newline at end of file diff --git a/Client/src/Utils/Report/reportUtils.jsx b/Client/src/Utils/Report/reportUtils.jsx deleted file mode 100644 index e69de29bb..000000000 From 107cc5c0599d7b1c13e91a24f1952076c23ff2be Mon Sep 17 00:00:00 2001 From: Darshan Jain Date: Mon, 10 Feb 2025 23:15:29 +0530 Subject: [PATCH 06/10] added proptypes for new files --- Client/src/Components/DropDownButton/index.jsx | 12 ++++++++++++ .../ReportDownloadButton/index.jsx | 9 +++++++-- Client/src/Components/MonitorStatusHeader/index.jsx | 4 +--- Client/src/Utils/Report/report.jsx | 6 ++++++ 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Client/src/Components/DropDownButton/index.jsx b/Client/src/Components/DropDownButton/index.jsx index 9f9e241fa..6621ec8d9 100644 --- a/Client/src/Components/DropDownButton/index.jsx +++ b/Client/src/Components/DropDownButton/index.jsx @@ -9,6 +9,7 @@ import Paper from '@mui/material/Paper'; import Popper from '@mui/material/Popper'; import MenuItem from '@mui/material/MenuItem'; import MenuList from '@mui/material/MenuList'; +import PropTypes from 'prop-types'; const DropDownButton = ({options, setValue, hanldeClick}) => { const theme = useTheme(); @@ -92,4 +93,15 @@ const DropDownButton = ({options, setValue, hanldeClick}) => { ); } +DropDownButton.propTypes = { + options: PropTypes.arrayOf( + PropTypes.shape({ + name: PropTypes.string.isRequired, + tag: PropTypes.string.isRequired, + }) + ).isRequired, + setValue: PropTypes.func.isRequired, + hanldeClick: PropTypes.func.isRequired, +}; + export default DropDownButton; \ No newline at end of file diff --git a/Client/src/Components/MonitorStatusHeader/ReportDownloadButton/index.jsx b/Client/src/Components/MonitorStatusHeader/ReportDownloadButton/index.jsx index 424b377c2..e8e6ec2f7 100644 --- a/Client/src/Components/MonitorStatusHeader/ReportDownloadButton/index.jsx +++ b/Client/src/Components/MonitorStatusHeader/ReportDownloadButton/index.jsx @@ -2,6 +2,7 @@ import { useState } from "react"; import DropDownButton from "../../DropDownButton"; import { Box } from "@mui/material"; import {downloadReport} from "../../../Utils/Report/downloadReport"; +import PropTypes from 'prop-types'; const options = [ { name: "Download HTML Report", tag: "html" }, @@ -12,8 +13,6 @@ const ReportDownloadButton = ({ shouldRender, monitor, certificateExpiry }) => { const [downloadFormat, setDownloadFormat] = useState("pdf"); if (!shouldRender) return null; - - const handleDownload = async () => { try { console.log(monitor); @@ -37,4 +36,10 @@ const ReportDownloadButton = ({ shouldRender, monitor, certificateExpiry }) => { ); }; +ReportDownloadButton.propTypes = { + shouldRender: PropTypes.bool.isRequired, + monitor: PropTypes.object.isRequired, + certificateExpiry: PropTypes.string.isRequired, +}; + export default ReportDownloadButton; diff --git a/Client/src/Components/MonitorStatusHeader/index.jsx b/Client/src/Components/MonitorStatusHeader/index.jsx index 7fb3647b9..64adf1d00 100644 --- a/Client/src/Components/MonitorStatusHeader/index.jsx +++ b/Client/src/Components/MonitorStatusHeader/index.jsx @@ -16,9 +16,6 @@ const MonitorStatusHeader = ({ path, shouldRender = true, isAdmin, monitor, cert return ; } - console.log(reportData); - console.log(certificateExpiry); - return ( { ); }; + +ProductReport.propTypes = { + monitorData: PropTypes.object.isRequired, + certificateExpiry: PropTypes.string.isRequired, +}; From a27b64ece28f0e168a334359783a39ebf27fdc03 Mon Sep 17 00:00:00 2001 From: Darshan Jain Date: Mon, 10 Feb 2025 23:34:35 +0530 Subject: [PATCH 07/10] minor UI change for download button --- Client/src/Components/DropDownButton/index.jsx | 1 - .../MonitorStatusHeader/ReportDownloadButton/index.jsx | 6 +++--- Client/src/Components/MonitorStatusHeader/index.jsx | 8 ++++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Client/src/Components/DropDownButton/index.jsx b/Client/src/Components/DropDownButton/index.jsx index 6621ec8d9..7cfe923a5 100644 --- a/Client/src/Components/DropDownButton/index.jsx +++ b/Client/src/Components/DropDownButton/index.jsx @@ -45,7 +45,6 @@ const DropDownButton = ({options, setValue, hanldeClick}) => { > - - - - {({ TransitionProps, placement }) => ( - - - - - {options.map((option, index) => ( - handleMenuItemClick(event,index,option.tag)} - > - {option.name} - - ))} - - - - - )} - - - ); -} - -DropDownButton.propTypes = { - options: PropTypes.arrayOf( - PropTypes.shape({ - name: PropTypes.string.isRequired, - tag: PropTypes.string.isRequired, - }) - ).isRequired, - setValue: PropTypes.func.isRequired, - hanldeClick: PropTypes.func.isRequired, -}; - -export default DropDownButton; \ No newline at end of file diff --git a/Client/src/Components/MonitorStatusHeader/ReportDownloadButton/index.jsx b/Client/src/Components/MonitorStatusHeader/ReportDownloadButton/index.jsx index e565deee1..9bac875c7 100644 --- a/Client/src/Components/MonitorStatusHeader/ReportDownloadButton/index.jsx +++ b/Client/src/Components/MonitorStatusHeader/ReportDownloadButton/index.jsx @@ -1,45 +1,70 @@ import { useState } from "react"; -import DropDownButton from "../../DropDownButton"; -import { Box } from "@mui/material"; -import {downloadReport} from "../../../Utils/Report/downloadReport"; +import { Box, Button } from "@mui/material"; +import { useTheme } from '@mui/material/styles'; +import FileDownloadIcon from '@mui/icons-material/FileDownload'; +import Select from "../../Inputs/Select"; +import { downloadReport } from "../../../Utils/Report/downloadReport"; import PropTypes from 'prop-types'; const options = [ - { name: "Download HTML Report", tag: "html" }, - { name: "Download Pdf Report", tag: "pdf" }, -] + { _id: "html", name: "HTML Report" }, + { _id: "pdf", name: "Pdf Report" }, +]; const ReportDownloadButton = ({ shouldRender, monitor, certificateExpiry }) => { - const [downloadFormat, setDownloadFormat] = useState("pdf"); + const [downloadFormat, setDownloadFormat] = useState(options[1]._id); + const theme = useTheme(); + if (!shouldRender) return null; + if(!monitor) return null; - const handleDownload = async () => { - try { - console.log(monitor); - await downloadReport({ - monitorData: monitor, - downloadFormat, - certificateExpiry, - }); - } finally { - setDownloadFormat("pdf"); //passing pdf as default + const handleDownload = async () => { + try { + console.log(monitor); + await downloadReport({ + monitorData: monitor, + downloadFormat, + certificateExpiry, + }); + } finally { + setDownloadFormat(options[1]._id); } - }; + }; - return ( - - - - - ); + return ( + + setDownloadFormat(e.target.value)} - items={options} - /> - - - ); -}; +