From 5899aef8b7102944fb5bb90fcc8d6f685ec1fb6b Mon Sep 17 00:00:00 2001 From: woleary2 Date: Tue, 27 Jan 2026 17:09:32 -0500 Subject: [PATCH 1/6] Began exporting graphs to pdf --- package-lock.json | 261 +++++++++++++++++++++++++++ package.json | 2 + src/app/graphs/page.tsx | 7 +- src/app/signin/page.tsx | 1 + src/components/BarGraph.tsx | 7 +- src/components/ConditionalLayout.tsx | 11 +- src/components/Dashboard.tsx | 2 +- src/components/LineGraph.tsx | 8 +- src/components/Sidebar.tsx | 8 +- src/lib/exporttopdf.ts | 47 +++++ src/proxy.ts | 4 +- 11 files changed, 343 insertions(+), 15 deletions(-) create mode 100644 src/lib/exporttopdf.ts diff --git a/package-lock.json b/package-lock.json index d4695fc..eddded0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,9 @@ "dotenv": "^17.2.3", "drizzle-orm": "^0.44.7", "framer-motion": "^12.23.24", + "html2canvas": "^1.4.1", "input-otp": "^1.4.2", + "jspdf": "^4.0.0", "lucide": "^0.544.0", "lucide-react": "^0.545.0", "next": "^16.0.7", @@ -220,6 +222,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -236,6 +239,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -252,6 +256,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -268,6 +273,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -284,6 +290,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -300,6 +307,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -316,6 +324,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -332,6 +341,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -348,6 +358,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -364,6 +375,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -380,6 +392,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -396,6 +409,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -412,6 +426,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -428,6 +443,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -444,6 +460,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -460,6 +477,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -476,6 +494,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -492,6 +511,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -508,6 +528,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -524,6 +545,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -540,6 +562,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -556,6 +579,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -622,6 +646,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -638,6 +663,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -654,6 +680,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -670,6 +697,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -686,6 +714,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -702,6 +731,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -718,6 +748,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -734,6 +765,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -750,6 +782,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -766,6 +799,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -782,6 +816,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -798,6 +833,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -814,6 +850,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -830,6 +867,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -846,6 +884,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -862,6 +901,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -878,6 +918,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -894,6 +935,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -910,6 +952,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -926,6 +969,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -942,6 +986,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -958,6 +1003,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -974,6 +1020,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -990,6 +1037,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1006,6 +1054,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1022,6 +1071,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4185,6 +4235,12 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "license": "MIT" + }, "node_modules/@types/pg": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.16.0.tgz", @@ -4196,6 +4252,13 @@ "pg-types": "^2.2.0" } }, + "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": "19.2.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz", @@ -4216,6 +4279,13 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.53.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", @@ -5427,6 +5497,15 @@ "node": ">= 0.4" } }, + "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/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -6168,6 +6247,26 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvg": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "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/cfb": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", @@ -6659,6 +6758,18 @@ "node": ">=0.10.0" } }, + "node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -6769,6 +6880,15 @@ "node": ">=8" } }, + "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.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -7490,6 +7610,16 @@ "npm": ">=1.2" } }, + "node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -8917,6 +9047,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-png": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", + "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", + "license": "MIT", + "dependencies": { + "@types/pako": "^2.0.3", + "iobuffer": "^5.3.2", + "pako": "^2.1.0" + } + }, + "node_modules/fast-png/node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/fast-sha256": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", @@ -8983,6 +9130,12 @@ "node": "^12.20 || >= 14.13" } }, + "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/figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -9962,6 +10115,19 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "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/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", @@ -10170,6 +10336,12 @@ "node": ">=12" } }, + "node_modules/iobuffer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", + "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==", + "license": "MIT" + }, "node_modules/is-accessor-descriptor": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", @@ -10911,6 +11083,23 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jspdf": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.0.0.tgz", + "integrity": "sha512-w12U97Z6edKd2tXDn3LzTLg7C7QLJlx0BPfM3ecjK2BckUl9/81vZ+r5gK4/3KQdhAcEZhENUxRhtgYBj75MqQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "fast-png": "^6.2.0", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.11", + "core-js": "^3.6.0", + "dompurify": "^3.2.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", @@ -12973,6 +13162,13 @@ "node": ">= 0.10" } }, + "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/pg": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/pg/-/pg-8.17.1.tgz", @@ -13502,6 +13698,16 @@ ], "license": "MIT" }, + "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/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -13980,6 +14186,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -14243,6 +14456,16 @@ "dev": true, "license": "MIT" }, + "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", @@ -15071,6 +15294,16 @@ "figgy-pudding": "^3.5.1" } }, + "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/standardwebhooks": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", @@ -15456,6 +15689,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/svix": { "version": "1.84.1", "resolved": "https://registry.npmjs.org/svix/-/svix-1.84.1.tgz", @@ -15575,6 +15818,15 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, + "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/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -16396,6 +16648,15 @@ "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", "license": "ISC" }, + "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/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", diff --git a/package.json b/package.json index 42dd7ec..a8e11c8 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,9 @@ "dotenv": "^17.2.3", "drizzle-orm": "^0.44.7", "framer-motion": "^12.23.24", + "html2canvas": "^1.4.1", "input-otp": "^1.4.2", + "jspdf": "^4.0.0", "lucide": "^0.544.0", "lucide-react": "^0.545.0", "next": "^16.0.7", diff --git a/src/app/graphs/page.tsx b/src/app/graphs/page.tsx index f2d84b0..a1eae92 100644 --- a/src/app/graphs/page.tsx +++ b/src/app/graphs/page.tsx @@ -20,7 +20,7 @@ import { Link, Share, } from "lucide-react"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; import BarGraph, { type BarDataset } from "@/components/BarGraph"; import { Breadcrumbs } from "@/components/Breadcrumbs"; @@ -35,6 +35,7 @@ import { PopoverTrigger, } from "@/components/ui/popover"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { downloadGraph } from "@/lib/exporttopdf"; // define Project type type Project = { @@ -103,6 +104,7 @@ export default function GraphsPage() { start: 2020, end: 2025, }); + const svgRef = useRef(null); // Fetch all project data useEffect(() => { @@ -398,6 +400,7 @@ export default function GraphsPage() { variant="outline" size="sm" className="flex items-center gap-2" + onClick={() => downloadGraph(svgRef)} > Export @@ -589,6 +592,7 @@ export default function GraphsPage() { measuredAsLabels[filters.measuredAs] } xAxisLabel={groupByLabels[filters.groupBy]} + svgRefCopy={svgRef} /> ) : ( )} diff --git a/src/app/signin/page.tsx b/src/app/signin/page.tsx index 6255d92..b2a3c7f 100644 --- a/src/app/signin/page.tsx +++ b/src/app/signin/page.tsx @@ -1,4 +1,5 @@ import AuthForm from "@/components/AuthForm"; +import Dashboard from "@/components/Dashboard"; import WarpShader from "@/components/WarpShader"; export default function SignInPage() { diff --git a/src/components/BarGraph.tsx b/src/components/BarGraph.tsx index 70a206d..1fb26a0 100644 --- a/src/components/BarGraph.tsx +++ b/src/components/BarGraph.tsx @@ -23,17 +23,22 @@ type BarGraphProps = { dataset: BarDataset[]; yAxisLabel: string; xAxisLabel: string; + svgRefCopy: React.RefObject; }; export default function BarGraph({ dataset, yAxisLabel, xAxisLabel, + svgRefCopy, }: BarGraphProps) { const svgRef = useRef(null); // Use same color scheme as LineGraph - const colorScale = d3.scaleOrdinal(d3.schemeCategory10); + //const colorScale = d3.scaleOrdinal(d3.schemeCategory10); + const colorScale = d3.scaleOrdinal( + d3.schemeCategory10.map((c) => c.toString()), + ); useEffect(() => { if (!svgRef.current || dataset.length === 0) return; diff --git a/src/components/ConditionalLayout.tsx b/src/components/ConditionalLayout.tsx index 509526a..b870882 100644 --- a/src/components/ConditionalLayout.tsx +++ b/src/components/ConditionalLayout.tsx @@ -9,13 +9,14 @@ export default function ConditionalLayout({ }: { children: React.ReactNode; }) { - const pathname = usePathname(); - const isAuthPage = pathname === "/signin"; + //const pathname = usePathname(); + + //const isAuthPage = pathname === "/signin"; // If on auth pages, just render children without sidebar - if (isAuthPage) { - return
{children}
; - } + // if (isAuthPage) { + // return
{children}
; + // } // Otherwise, render with sidebar and responsive layout return ( diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx index c059912..a74e88d 100644 --- a/src/components/Dashboard.tsx +++ b/src/components/Dashboard.tsx @@ -12,7 +12,7 @@ "use client"; import { useEffect, useState } from "react"; -import { toast } from "sonner"; +//import { toast } from "sonner"; import { Select, SelectContent, diff --git a/src/components/LineGraph.tsx b/src/components/LineGraph.tsx index a625c6d..39724b2 100644 --- a/src/components/LineGraph.tsx +++ b/src/components/LineGraph.tsx @@ -12,12 +12,14 @@ type MultiLineGraphProps = { datasets: GraphDataset[]; yAxisLabel: string; xAxisLabel: string; + svgRefCopy: React.RefObject; }; export default function MultiLineGraph({ datasets, yAxisLabel, xAxisLabel, + svgRefCopy, }: MultiLineGraphProps) { const svgRef = useRef(null); const wrapperRef = useRef(null); @@ -39,7 +41,11 @@ export default function MultiLineGraph({ }>({}); // Memoize color scale to prevent re-running useEffect unnecessarily - const colorScale = useMemo(() => d3.scaleOrdinal(d3.schemeCategory10), []); + //const colorScale = useMemo(() => d3.scaleOrdinal(d3.schemeCategory10), []); + const colorScale = useMemo( + () => d3.scaleOrdinal(d3.schemeCategory10.map((c) => c.toString())), + [], + ); useEffect(() => { const svg = d3.select(svgRef.current); diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 08e9026..c226cea 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -13,12 +13,12 @@ import { ChevronRight, User, } from "lucide-react"; -import { authClient } from "@/lib/auth-client"; +//import { authClient } from "@/lib/auth-client"; export default function Sidebar() { const pathname = usePathname(); const [isOverviewOpen, setIsOverviewOpen] = useState(false); - const { data: session } = authClient.useSession(); + //const { data: session } = authClient.useSession(); // Automatically open Overview if any subitem is active useEffect(() => { @@ -203,7 +203,7 @@ export default function Sidebar() { -
+ {/*
{session?.user?.image ? ( {session?.user?.email || "Loading..."} -
+
*/} ); } diff --git a/src/lib/exporttopdf.ts b/src/lib/exporttopdf.ts new file mode 100644 index 0000000..30628dd --- /dev/null +++ b/src/lib/exporttopdf.ts @@ -0,0 +1,47 @@ +import React, { ReactElement, SVGProps } from "react"; +import html2canvas from "html2canvas"; +import jsPDF from "jspdf"; + +export async function downloadGraph( + svgRef: React.RefObject, +) { + const newSVG = getClonedSvg(svgRef); + const wrapper = document.createElement("div"); + if (newSVG != null) { + wrapper.appendChild(newSVG); + } + + document.body.append(wrapper); + + const canvas = await html2canvas(wrapper, { + backgroundColor: "#fff", + scale: 2, // higher resolution + }); + + const graphData = canvas.toDataURL("image/png"); + + const pdf = new jsPDF({ + orientation: "landscape", + unit: "pt", + format: "a4", + }); + + const pdfWidth = pdf.internal.pageSize.getWidth(); + const pdfHeight = pdf.internal.pageSize.getHeight(); + + pdf.addImage(graphData, "PNG", 0, 0, pdfWidth, pdfHeight); + pdf.save("graph.pdf"); +} + +export function getClonedSvg( + svgRef: React.RefObject, +): SVGSVGElement | null { + const original = svgRef.current; + if (!original) return null; + + const clone = original.cloneNode(true) as SVGSVGElement; + + clone.setAttribute("xmlns", "http://www.w3.org/2000/svg"); + + return clone; +} diff --git a/src/proxy.ts b/src/proxy.ts index 6b441c9..5c60edc 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -9,7 +9,7 @@ export async function proxy(request: NextRequest) { }); if (!session) { - return NextResponse.redirect(new URL("/signin", request.url)); + return NextResponse.redirect(new URL("/graphs", request.url)); } const sessionCookie = getSessionCookie(request); @@ -32,6 +32,6 @@ export const config = { * - /static/* (static files) * - /*.* (files with extensions like favicon.ico, images, etc.) */ - "/((?!signin|api/auth|_next/static|_next/image|favicon.ico|.*\\.png$|.*\\.jpg$|.*\\.jpeg$|.*\\.svg$).*)", + //"/((?!signin|api/auth|_next/static|_next/image|favicon.ico|.*\\.png$|.*\\.jpg$|.*\\.jpeg$|.*\\.svg$).*)", ], }; From 472d09ade244118c344b10bd3d0e042b5d73f5a7 Mon Sep 17 00:00:00 2001 From: woleary2 Date: Wed, 28 Jan 2026 13:04:39 -0500 Subject: [PATCH 2/6] fixed color parsing and passing through svg object --- package-lock.json | 14 ++++++++++++++ package.json | 1 + src/app/graphs/page.tsx | 1 + src/components/BarGraph.tsx | 2 ++ src/components/LineGraph.tsx | 2 ++ src/lib/exporttopdf.ts | 16 ++++++---------- 6 files changed, 26 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index eddded0..255125d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "drizzle-orm": "^0.44.7", "framer-motion": "^12.23.24", "html2canvas": "^1.4.1", + "html2canvas-pro": "^1.6.6", "input-otp": "^1.4.2", "jspdf": "^4.0.0", "lucide": "^0.544.0", @@ -10128,6 +10129,19 @@ "node": ">=8.0.0" } }, + "node_modules/html2canvas-pro": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/html2canvas-pro/-/html2canvas-pro-1.6.6.tgz", + "integrity": "sha512-5mRhTXZhv4B0kIcsn3bFBjol2o8vzP35mhtxdXBGPA3V3gZd6Sa2PIIFbT//DiqAX8UuywlcJit5jRKej4nV4Q==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", diff --git a/package.json b/package.json index a8e11c8..fdb72f7 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "drizzle-orm": "^0.44.7", "framer-motion": "^12.23.24", "html2canvas": "^1.4.1", + "html2canvas-pro": "^1.6.6", "input-otp": "^1.4.2", "jspdf": "^4.0.0", "lucide": "^0.544.0", diff --git a/src/app/graphs/page.tsx b/src/app/graphs/page.tsx index a1eae92..169b0e9 100644 --- a/src/app/graphs/page.tsx +++ b/src/app/graphs/page.tsx @@ -90,6 +90,7 @@ const groupByLabels: Record = { export default function GraphsPage() { const [allProjects, setAllProjects] = useState([]); const [filters, setFilters] = useState(defaultFilters); + //const [isExporting, setIsExporting] = useState(false); potentially for loading stat const [gatewayCities, setGatewayCities] = useState([]); const [chartType, setChartType] = useState<"line" | "bar">("line"); const [timePeriod, setTimePeriod] = useState< diff --git a/src/components/BarGraph.tsx b/src/components/BarGraph.tsx index 1fb26a0..5405b6e 100644 --- a/src/components/BarGraph.tsx +++ b/src/components/BarGraph.tsx @@ -186,6 +186,8 @@ export default function BarGraph({ xOffset += itemWidth; return transform; }); + + svgRefCopy.current = svgRef.current; }, [dataset]); return ( diff --git a/src/components/LineGraph.tsx b/src/components/LineGraph.tsx index 39724b2..da00b44 100644 --- a/src/components/LineGraph.tsx +++ b/src/components/LineGraph.tsx @@ -396,6 +396,8 @@ export default function MultiLineGraph({ return transform; }); }); + + svgRefCopy.current = svgRef.current; }, [datasets, xAxisLabel, yAxisLabel, colorScale]); return ( diff --git a/src/lib/exporttopdf.ts b/src/lib/exporttopdf.ts index 30628dd..9ad910b 100644 --- a/src/lib/exporttopdf.ts +++ b/src/lib/exporttopdf.ts @@ -1,11 +1,13 @@ import React, { ReactElement, SVGProps } from "react"; -import html2canvas from "html2canvas"; +import html2canvas from "html2canvas-pro"; import jsPDF from "jspdf"; export async function downloadGraph( svgRef: React.RefObject, ) { const newSVG = getClonedSvg(svgRef); + + //if (!newSVG) { console.log("SVG is empty"); return; } const wrapper = document.createElement("div"); if (newSVG != null) { wrapper.appendChild(newSVG); @@ -15,21 +17,17 @@ export async function downloadGraph( const canvas = await html2canvas(wrapper, { backgroundColor: "#fff", - scale: 2, // higher resolution + scale: 2, }); const graphData = canvas.toDataURL("image/png"); - const pdf = new jsPDF({ - orientation: "landscape", - unit: "pt", - format: "a4", - }); + const pdf = new jsPDF(); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = pdf.internal.pageSize.getHeight(); - pdf.addImage(graphData, "PNG", 0, 0, pdfWidth, pdfHeight); + pdf.addImage(canvas, "JPEG", 0, 0, pdfWidth, pdfHeight); pdf.save("graph.pdf"); } @@ -41,7 +39,5 @@ export function getClonedSvg( const clone = original.cloneNode(true) as SVGSVGElement; - clone.setAttribute("xmlns", "http://www.w3.org/2000/svg"); - return clone; } From be25d227a9c13487f24726b502d00125738ae1f8 Mon Sep 17 00:00:00 2001 From: woleary2 Date: Sun, 1 Feb 2026 14:10:24 -0500 Subject: [PATCH 3/6] Added file header and comments --- src/lib/exporttopdf.ts | 18 +++++++++++++++--- src/proxy.ts | 4 ++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/lib/exporttopdf.ts b/src/lib/exporttopdf.ts index 9ad910b..d7acdf1 100644 --- a/src/lib/exporttopdf.ts +++ b/src/lib/exporttopdf.ts @@ -1,3 +1,13 @@ +/*************************************************************** + * + * /src/lib/exporttopdf.ts + * + * Author: Will and Justin + * Date: 2/1/2025 + * + * Summary: Export an svg graph as a pdf + **************************************************************/ + import React, { ReactElement, SVGProps } from "react"; import html2canvas from "html2canvas-pro"; import jsPDF from "jspdf"; @@ -7,7 +17,7 @@ export async function downloadGraph( ) { const newSVG = getClonedSvg(svgRef); - //if (!newSVG) { console.log("SVG is empty"); return; } + // Adds the svg element to the page temporarily const wrapper = document.createElement("div"); if (newSVG != null) { wrapper.appendChild(newSVG); @@ -20,15 +30,16 @@ export async function downloadGraph( scale: 2, }); - const graphData = canvas.toDataURL("image/png"); - const pdf = new jsPDF(); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = pdf.internal.pageSize.getHeight(); + //Creates a jsPDF object with the following specifications pdf.addImage(canvas, "JPEG", 0, 0, pdfWidth, pdfHeight); pdf.save("graph.pdf"); + + document.body.removeChild(wrapper); } export function getClonedSvg( @@ -37,6 +48,7 @@ export function getClonedSvg( const original = svgRef.current; if (!original) return null; + //Creates and returns a clone of the svg element passed in const clone = original.cloneNode(true) as SVGSVGElement; return clone; diff --git a/src/proxy.ts b/src/proxy.ts index 5c60edc..6b441c9 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -9,7 +9,7 @@ export async function proxy(request: NextRequest) { }); if (!session) { - return NextResponse.redirect(new URL("/graphs", request.url)); + return NextResponse.redirect(new URL("/signin", request.url)); } const sessionCookie = getSessionCookie(request); @@ -32,6 +32,6 @@ export const config = { * - /static/* (static files) * - /*.* (files with extensions like favicon.ico, images, etc.) */ - //"/((?!signin|api/auth|_next/static|_next/image|favicon.ico|.*\\.png$|.*\\.jpg$|.*\\.jpeg$|.*\\.svg$).*)", + "/((?!signin|api/auth|_next/static|_next/image|favicon.ico|.*\\.png$|.*\\.jpg$|.*\\.jpeg$|.*\\.svg$).*)", ], }; From 60dc2acff0c5175f99e5431d4bac7f5c5dda48f7 Mon Sep 17 00:00:00 2001 From: Dan Glorioso <125602370+danglorioso@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:18:14 -0500 Subject: [PATCH 4/6] Fix SVG brief overlay on export --- package-lock.json | 48 ------------------------------------------ src/lib/exporttopdf.ts | 35 ++++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 55 deletions(-) diff --git a/package-lock.json b/package-lock.json index 255125d..dd941f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -223,7 +223,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -240,7 +239,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -257,7 +255,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -274,7 +271,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -291,7 +287,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -308,7 +303,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -325,7 +319,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -342,7 +335,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -359,7 +351,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -376,7 +367,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -393,7 +383,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -410,7 +399,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -427,7 +415,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -444,7 +431,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -461,7 +447,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -478,7 +463,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -495,7 +479,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -512,7 +495,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -529,7 +511,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -546,7 +527,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -563,7 +543,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -580,7 +559,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -647,7 +625,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -664,7 +641,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -681,7 +657,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -698,7 +673,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -715,7 +689,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -732,7 +705,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -749,7 +721,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -766,7 +737,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -783,7 +753,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -800,7 +769,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -817,7 +785,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -834,7 +801,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -851,7 +817,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -868,7 +833,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -885,7 +849,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -902,7 +865,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -919,7 +881,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -936,7 +897,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -953,7 +913,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -970,7 +929,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -987,7 +945,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1004,7 +961,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1021,7 +977,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1038,7 +993,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1055,7 +1009,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1072,7 +1025,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ diff --git a/src/lib/exporttopdf.ts b/src/lib/exporttopdf.ts index d7acdf1..b0e192e 100644 --- a/src/lib/exporttopdf.ts +++ b/src/lib/exporttopdf.ts @@ -16,13 +16,35 @@ export async function downloadGraph( svgRef: React.RefObject, ) { const newSVG = getClonedSvg(svgRef); + if (!newSVG) return; - // Adds the svg element to the page temporarily - const wrapper = document.createElement("div"); - if (newSVG != null) { - wrapper.appendChild(newSVG); + // Get SVG dimensions from viewBox or attributes with fallbacks + const viewBox = newSVG.getAttribute("viewBox"); + let svgWidth = 1000; + let svgHeight = 400; + let aspectRatio = svgHeight / svgWidth; + + if (viewBox) { + const viewBoxValues = viewBox.split(" "); + svgWidth = parseFloat(viewBoxValues[2]) || 1000; + svgHeight = parseFloat(viewBoxValues[3]) || 400; + } else { + const width = newSVG.getAttribute("width"); + const height = newSVG.getAttribute("height"); + if (width) svgWidth = parseFloat(width) || 1000; + if (height) svgHeight = parseFloat(height) || 400; } + aspectRatio = svgHeight / svgWidth; + + // Adds the svg element to the page temporarily (offscreen) + const wrapper = document.createElement("div"); + wrapper.style.position = "fixed"; + wrapper.style.left = "-9999px"; + wrapper.style.top = "-9999px"; + wrapper.style.width = `${svgWidth}px`; + wrapper.style.height = `${svgHeight}px`; + wrapper.appendChild(newSVG); document.body.append(wrapper); const canvas = await html2canvas(wrapper, { @@ -31,11 +53,10 @@ export async function downloadGraph( }); const pdf = new jsPDF(); - const pdfWidth = pdf.internal.pageSize.getWidth(); - const pdfHeight = pdf.internal.pageSize.getHeight(); + const pdfHeight = pdfWidth * aspectRatio; - //Creates a jsPDF object with the following specifications + // Add image with proper dimensions that match PDF width while preserving aspect ratio pdf.addImage(canvas, "JPEG", 0, 0, pdfWidth, pdfHeight); pdf.save("graph.pdf"); From 7cc68301ae39b0b5cab857d009d7c01345e6b8bf Mon Sep 17 00:00:00 2001 From: shaynesidman <147111519+shaynesidman@users.noreply.github.com> Date: Thu, 5 Feb 2026 14:36:40 -0500 Subject: [PATCH 5/6] changed name of export-to-pdf file --- package-lock.json | 48 ++++++++++++++++++++ src/app/graphs/page.tsx | 2 +- src/lib/{exporttopdf.ts => export-to-pdf.ts} | 2 +- 3 files changed, 50 insertions(+), 2 deletions(-) rename src/lib/{exporttopdf.ts => export-to-pdf.ts} (98%) diff --git a/package-lock.json b/package-lock.json index dd941f1..255125d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -223,6 +223,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -239,6 +240,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -255,6 +257,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -271,6 +274,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -287,6 +291,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -303,6 +308,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -319,6 +325,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -335,6 +342,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -351,6 +359,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -367,6 +376,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -383,6 +393,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -399,6 +410,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -415,6 +427,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -431,6 +444,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -447,6 +461,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -463,6 +478,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -479,6 +495,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -495,6 +512,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -511,6 +529,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -527,6 +546,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -543,6 +563,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -559,6 +580,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -625,6 +647,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -641,6 +664,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -657,6 +681,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -673,6 +698,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -689,6 +715,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -705,6 +732,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -721,6 +749,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -737,6 +766,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -753,6 +783,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -769,6 +800,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -785,6 +817,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -801,6 +834,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -817,6 +851,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -833,6 +868,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -849,6 +885,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -865,6 +902,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -881,6 +919,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -897,6 +936,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -913,6 +953,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -929,6 +970,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -945,6 +987,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -961,6 +1004,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -977,6 +1021,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -993,6 +1038,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1009,6 +1055,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1025,6 +1072,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ diff --git a/src/app/graphs/page.tsx b/src/app/graphs/page.tsx index 169b0e9..1f679d6 100644 --- a/src/app/graphs/page.tsx +++ b/src/app/graphs/page.tsx @@ -35,7 +35,7 @@ import { PopoverTrigger, } from "@/components/ui/popover"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { downloadGraph } from "@/lib/exporttopdf"; +import { downloadGraph } from "@/lib/export-to-pdf"; // define Project type type Project = { diff --git a/src/lib/exporttopdf.ts b/src/lib/export-to-pdf.ts similarity index 98% rename from src/lib/exporttopdf.ts rename to src/lib/export-to-pdf.ts index b0e192e..8c2ff0e 100644 --- a/src/lib/exporttopdf.ts +++ b/src/lib/export-to-pdf.ts @@ -1,6 +1,6 @@ /*************************************************************** * - * /src/lib/exporttopdf.ts + * /src/lib/export-to-pdf.ts * * Author: Will and Justin * Date: 2/1/2025 From 939cc40ecc7f71941bb2e5868ab2de1f3d11184d Mon Sep 17 00:00:00 2001 From: shaynesidman <147111519+shaynesidman@users.noreply.github.com> Date: Thu, 5 Feb 2026 15:06:13 -0500 Subject: [PATCH 6/6] changed name of export-to-pdf file --- src/components/Dashboard.tsx | 2 +- src/lib/export-to-pdf.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx index a74e88d..c059912 100644 --- a/src/components/Dashboard.tsx +++ b/src/components/Dashboard.tsx @@ -12,7 +12,7 @@ "use client"; import { useEffect, useState } from "react"; -//import { toast } from "sonner"; +import { toast } from "sonner"; import { Select, SelectContent, diff --git a/src/lib/export-to-pdf.ts b/src/lib/export-to-pdf.ts index 8c2ff0e..6e2b9a6 100644 --- a/src/lib/export-to-pdf.ts +++ b/src/lib/export-to-pdf.ts @@ -8,7 +8,7 @@ * Summary: Export an svg graph as a pdf **************************************************************/ -import React, { ReactElement, SVGProps } from "react"; +import React from "react"; import html2canvas from "html2canvas-pro"; import jsPDF from "jspdf";