From 85b88d9ee0f7362ef693dcdf4478fdd3affd0e47 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 08:55:23 +0000 Subject: [PATCH] Run SQL migrations --- package-lock.json | 1070 ++++++++++++++++- package.json | 4 + src/App.tsx | 39 +- src/components/ExportOptions.tsx | 146 ++- src/components/TaskCard.tsx | 261 ++-- src/components/TaskList.tsx | 764 ++++++------ src/components/UploadSection.tsx | 310 ++--- src/components/ui/progress.tsx | 49 +- src/components/ui/table.tsx | 16 +- src/integrations/supabase/types.ts | 80 ++ src/pages/Dashboard.tsx | 327 ++--- .../functions/process-document/config.toml | 2 + supabase/functions/process-document/index.ts | 155 +++ 13 files changed, 2327 insertions(+), 896 deletions(-) create mode 100644 supabase/functions/process-document/config.toml create mode 100644 supabase/functions/process-document/index.ts diff --git a/package-lock.json b/package-lock.json index 03b8c4a..7add442 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,10 +42,14 @@ "clsx": "^2.1.1", "cmdk": "^1.0.0", "date-fns": "^3.6.0", + "docx": "^8.2.3", "embla-carousel-react": "^8.3.0", "input-otp": "^1.2.4", + "jspdf": "^2.5.1", + "jspdf-autotable": "^3.8.1", "lucide-react": "^0.462.0", "next-themes": "^0.3.0", + "pdfjs-dist": "^3.11.174", "react": "^18.3.1", "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", @@ -882,6 +886,27 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3007,6 +3032,13 @@ "dev": true, "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.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", @@ -3279,6 +3311,13 @@ "vite": "^4 || ^5" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", + "optional": true + }, "node_modules/acorn": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", @@ -3302,6 +3341,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3369,6 +3421,28 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -3395,6 +3469,18 @@ "node": ">=10" } }, + "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/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -3437,9 +3523,19 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, + "devOptional": 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", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3457,7 +3553,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -3510,6 +3606,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/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3551,6 +3659,49 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "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", @@ -3606,6 +3757,16 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -4024,6 +4185,16 @@ "dev": true, "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -4038,7 +4209,32 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, + "devOptional": true, + "license": "MIT" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, + "node_modules/core-js": { + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.41.0.tgz", + "integrity": "sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==", + "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", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, "node_modules/cross-spawn": { @@ -4055,6 +4251,16 @@ "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", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -4209,7 +4415,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -4229,6 +4435,19 @@ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", "license": "MIT" }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4236,6 +4455,23 @@ "dev": true, "license": "MIT" }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -4256,6 +4492,49 @@ "dev": true, "license": "MIT" }, + "node_modules/docx": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/docx/-/docx-8.2.3.tgz", + "integrity": "sha512-Rlr/wPTDh+xQpFew3m4zYQ5OWEZO36HItyPCUbGdicB+ar4zIgseeJdqfcZIY0uQtMSrhGRpSFOTjii7h9cpHw==", + "license": "MIT", + "dependencies": { + "@types/node": "^20.3.1", + "jszip": "^3.10.1", + "nanoid": "^4.0.2", + "xml": "^1.0.1", + "xml-js": "^1.6.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/docx/node_modules/@types/node": { + "version": "20.17.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz", + "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/docx/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -4266,6 +4545,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/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -4641,6 +4927,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -4736,6 +5028,39 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC", + "optional": true + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4761,6 +5086,80 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "optional": true + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", @@ -4860,6 +5259,13 @@ "node": ">=8" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -4873,6 +5279,34 @@ "node": ">= 0.4" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "optional": true, + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4883,6 +5317,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -4910,6 +5350,24 @@ "node": ">=0.8.19" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/input-otp": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.2.4.tgz", @@ -4981,7 +5439,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -5010,6 +5468,12 @@ "node": ">=0.12.0" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5083,6 +5547,75 @@ "dev": true, "license": "MIT" }, + "node_modules/jspdf": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", + "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.14.0", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.4.8" + }, + "optionalDependencies": { + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.2.0", + "html2canvas": "^1.0.0-rc.5" + } + }, + "node_modules/jspdf-autotable": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.8.1.tgz", + "integrity": "sha512-UjJqo80Z3/WUzDi4JipTGp0pAvNvR3Gsm38inJ5ZnwsJH0Lw4pEbssRSH6zMWAhR1ZkTrsDpQo5p6rZk987/AQ==", + "license": "MIT", + "peerDependencies": { + "jspdf": "^2.5.1" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -5107,6 +5640,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -5655,6 +6197,32 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5679,11 +6247,24 @@ "node": ">=8.6" } }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -5702,11 +6283,51 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/mz": { @@ -5721,6 +6342,13 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nan": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", + "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", + "license": "MIT", + "optional": true + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -5757,6 +6385,27 @@ "react-dom": "^16.8 || ^17 || ^18" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -5764,6 +6413,22 @@ "dev": true, "license": "MIT" }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -5784,6 +6449,20 @@ "node": ">=0.10.0" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5803,6 +6482,16 @@ "node": ">= 6" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5860,6 +6549,12 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5883,6 +6578,16 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -5917,6 +6622,36 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path2d-polyfill": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", + "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pdfjs-dist": { + "version": "3.11.174", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz", + "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "canvas": "^2.11.2", + "path2d-polyfill": "^2.0.1" + } + }, + "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", @@ -6117,6 +6852,12 @@ "node": ">= 0.8.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -6165,6 +6906,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/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -6379,6 +7130,21 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -6469,6 +7235,55 @@ "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", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", @@ -6529,6 +7344,33 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -6542,7 +7384,7 @@ "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -6551,6 +7393,19 @@ "node": ">=10" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", + "optional": true + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6587,6 +7442,39 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sonner": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.5.0.tgz", @@ -6607,6 +7495,26 @@ "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_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -6773,6 +7681,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/tailwind-merge": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz", @@ -6830,6 +7748,44 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "optional": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "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", + "optional": true, + "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", @@ -7056,9 +8012,18 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, + "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", + "optional": true, + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/vaul": { "version": "0.9.9", "resolved": "https://registry.npmjs.org/vaul/-/vaul-0.9.9.tgz", @@ -7186,6 +8151,61 @@ "node": ">= 8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -7291,6 +8311,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "optional": true + }, "node_modules/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", @@ -7312,6 +8339,31 @@ } } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "license": "MIT" + }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "license": "MIT", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, "node_modules/yaml": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", diff --git a/package.json b/package.json index 3c69742..0316e7e 100644 --- a/package.json +++ b/package.json @@ -45,10 +45,14 @@ "clsx": "^2.1.1", "cmdk": "^1.0.0", "date-fns": "^3.6.0", + "docx": "^8.2.3", "embla-carousel-react": "^8.3.0", "input-otp": "^1.2.4", + "jspdf": "^2.5.1", + "jspdf-autotable": "^3.8.1", "lucide-react": "^0.462.0", "next-themes": "^0.3.0", + "pdfjs-dist": "^3.11.174", "react": "^18.3.1", "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", diff --git a/src/App.tsx b/src/App.tsx index 60ccefd..0cfa1ed 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,8 @@ import { AuthProvider, useAuth } from "@/contexts/AuthContext"; import Index from "./pages/Index"; import Auth from "./pages/Auth"; import NotFound from "./pages/NotFound"; +import Dashboard from "./pages/Dashboard"; +import Header from "./components/Header"; const queryClient = new QueryClient(); @@ -33,26 +35,23 @@ const App = () => ( - - } /> - } /> - {/* Protected routes example (Dashboard will be implemented later) */} - -
-
-

Dashboard

-

This is a protected route. Dashboard implementation coming soon.

-
-
- - } - /> - {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} - } /> -
+
+
+ + } /> + } /> + + + + } + /> + {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} + } /> + +
diff --git a/src/components/ExportOptions.tsx b/src/components/ExportOptions.tsx index 7ff4ead..428094e 100644 --- a/src/components/ExportOptions.tsx +++ b/src/components/ExportOptions.tsx @@ -1,76 +1,130 @@ import { useState } from 'react'; -import { Task } from '@/utils/mockData'; import { Button } from '@/components/ui/button'; -import { FileDown, Check } from 'lucide-react'; -import { toast } from 'sonner'; +import { Download } from 'lucide-react'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import jsPDF from 'jspdf'; +import autoTable from 'jspdf-autotable'; + +interface Task { + id: string; + task_name: string; + description: string; + priority: string; + due_date: string | null; + status: string; +} interface ExportOptionsProps { tasks: Task[]; } -const ExportOptions = ({ tasks }: ExportOptionsProps) => { - const [isExporting, setIsExporting] = useState(false); +export const ExportOptions = ({ tasks }: ExportOptionsProps) => { + const [exporting, setExporting] = useState(false); - const exportToCSV = () => { + const exportToCsv = () => { try { - setIsExporting(true); + setExporting(true); + + const headers = ['Task Name', 'Description', 'Priority', 'Due Date', 'Status']; - // Create CSV content - const headers = ['Description', 'Department', 'Deadline', 'Priority', 'Status', 'Context']; const csvContent = [ headers.join(','), - ...tasks.map(task => [ - `"${task.description.replace(/"/g, '""')}"`, - `"${task.department.replace(/"/g, '""')}"`, - task.deadline, - task.priority, - task.status, - `"${task.context.replace(/"/g, '""')}"` - ].join(',')) + ...tasks.map(task => { + return [ + `"${task.task_name.replace(/"/g, '""')}"`, + `"${task.description?.replace(/"/g, '""') || ''}"`, + `"${task.priority}"`, + `"${task.due_date ? new Date(task.due_date).toLocaleDateString() : ''}"`, + `"${task.status}"` + ].join(','); + }) ].join('\n'); - // Create a Blob and download const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.setAttribute('href', url); - link.setAttribute('download', `secupolicy_tasks_${new Date().toISOString().split('T')[0]}.csv`); + link.setAttribute('download', `tasks_export_${new Date().toISOString().split('T')[0]}.csv`); document.body.appendChild(link); link.click(); document.body.removeChild(link); - - toast.success('CSV file exported successfully'); } catch (error) { - console.error('Export error:', error); - toast.error('Failed to export CSV file'); + console.error('Error exporting to CSV:', error); } finally { - setIsExporting(false); + setExporting(false); + } + }; + + const exportToPdf = () => { + try { + setExporting(true); + + const doc = new jsPDF(); + + // Add title + doc.setFontSize(16); + doc.text('Policy Tasks Report', 14, 20); + + // Add date + doc.setFontSize(10); + doc.text(`Generated on: ${new Date().toLocaleDateString()}`, 14, 26); + + // Prepare data for table + const tableData = tasks.map(task => [ + task.task_name, + task.description?.length > 60 + ? task.description.substring(0, 60) + '...' + : task.description || '', + task.priority, + task.due_date ? new Date(task.due_date).toLocaleDateString() : '', + task.status + ]); + + // Create table + autoTable(doc, { + head: [['Task Name', 'Description', 'Priority', 'Due Date', 'Status']], + body: tableData, + startY: 32, + headStyles: { fillColor: [66, 66, 66] }, + margin: { top: 30 }, + styles: { overflow: 'linebreak' }, + columnStyles: { + 0: { cellWidth: 50 }, + 1: { cellWidth: 70 }, + }, + }); + + // Save the PDF + doc.save(`tasks_export_${new Date().toISOString().split('T')[0]}.pdf`); + } catch (error) { + console.error('Error exporting to PDF:', error); + } finally { + setExporting(false); } }; return ( -
- -
+ + + + + + + Export as CSV + + + Export as PDF + + + ); }; - -export default ExportOptions; diff --git a/src/components/TaskCard.tsx b/src/components/TaskCard.tsx index 8eac699..abe0af6 100644 --- a/src/components/TaskCard.tsx +++ b/src/components/TaskCard.tsx @@ -1,157 +1,156 @@ import { useState } from 'react'; -import { Task } from '@/utils/mockData'; +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; -import { Calendar, AlertCircle, Users, CornerDownRight } from 'lucide-react'; -import { cn } from '@/lib/utils'; import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; + MoreVertical, + Calendar, + FileText, + Check, + X, + AlertCircle, + Clock +} from 'lucide-react'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; + +interface Task { + id: string; + task_name: string; + description: string; + priority: string; + due_date: string | null; + status: string; +} interface TaskCardProps { task: Task; - onStatusChange: (id: string, status: 'Open' | 'In Progress' | 'Completed') => void; - onPriorityChange: (id: string, priority: 'High' | 'Medium' | 'Low') => void; + documentName: string; + onStatusChange: (status: string) => void; } -const TaskCard = ({ task, onStatusChange, onPriorityChange }: TaskCardProps) => { - const [isExpanded, setIsExpanded] = useState(false); +export const TaskCard = ({ task, documentName, onStatusChange }: TaskCardProps) => { + const [expanded, setExpanded] = useState(false); - const getPriorityColor = (priority: string) => { - switch (priority) { - case 'High': - return 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300 border-red-200 dark:border-red-800/30'; - case 'Medium': - return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300 border-yellow-200 dark:border-yellow-800/30'; - case 'Low': - return 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300 border-green-200 dark:border-green-800/30'; + const getPriorityIcon = () => { + switch (task.priority.toLowerCase()) { + case 'high': + return ; + case 'medium': + return ; + case 'low': + return ; default: - return 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300 border-gray-200 dark:border-gray-800/30'; + return null; } }; - const getStatusColor = (status: string) => { - switch (status) { - case 'Completed': - return 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300 border-green-200 dark:border-green-800/30'; - case 'In Progress': - return 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300 border-blue-200 dark:border-blue-800/30'; - case 'Open': - return 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300 border-gray-200 dark:border-gray-800/30'; + const getPriorityBadge = () => { + switch (task.priority.toLowerCase()) { + case 'high': + return {task.priority}; + case 'medium': + return {task.priority}; + case 'low': + return {task.priority}; default: - return 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300 border-gray-200 dark:border-gray-800/30'; + return {task.priority}; } }; - const formatDate = (dateString: string) => { - const date = new Date(dateString); - return new Intl.DateTimeFormat('en-US', { - year: 'numeric', - month: 'short', - day: 'numeric' - }).format(date); + const getStatusBadge = () => { + switch (task.status.toLowerCase()) { + case 'completed': + return {task.status}; + case 'in progress': + return {task.status}; + case 'open': + return {task.status}; + default: + return {task.status}; + } }; - const calculateDaysLeft = (dateString: string) => { - const today = new Date(); - const deadline = new Date(dateString); - const diffTime = deadline.getTime() - today.getTime(); - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - return diffDays; - }; - - const daysLeft = calculateDaysLeft(task.deadline); - const isUrgent = daysLeft <= 14 && daysLeft > 0; - const isOverdue = daysLeft < 0; - return ( -
setIsExpanded(!isExpanded)} - > -
-
-

{task.description}

- - {task.priority} - -
- -
-
- - {task.department} -
-
- - - {formatDate(task.deadline)} - {isUrgent && ` (${daysLeft} days left)`} - {isOverdue && ` (Overdue by ${Math.abs(daysLeft)} days)`} - -
-
- -
- - {task.status} - - -
e.stopPropagation()}> - - - -
-
- - {isExpanded && ( -
-
- -
-

Context from Policy:

-

{task.context}

-
+ + +
+ + {task.task_name} + +
+
+ {getPriorityIcon()} + {task.priority} +
+
+ + {documentName}
+
+ + + + + + {task.status !== 'Completed' && ( + onStatusChange('Completed')}> + + Mark as completed + + )} + {task.status === 'Completed' && ( + onStatusChange('Open')}> + + Mark as open + + )} + {task.status !== 'In Progress' && task.status !== 'Completed' && ( + onStatusChange('In Progress')}> + + Mark as in progress + + )} + + +
+ +
+ {task.description} +
+ {task.description && task.description.length > 100 && ( + )} -
-
+ + +
+ {task.due_date && ( +
+ + + {new Date(task.due_date).toLocaleDateString()} + +
+ )} +
+ {getStatusBadge()} +
+ ); }; - -export default TaskCard; diff --git a/src/components/TaskList.tsx b/src/components/TaskList.tsx index 4c8ecee..93df8c6 100644 --- a/src/components/TaskList.tsx +++ b/src/components/TaskList.tsx @@ -1,408 +1,418 @@ import { useState, useEffect } from 'react'; -import { Task } from '@/utils/mockData'; -import TaskCard from './TaskCard'; -import { Input } from '@/components/ui/input'; +import { useAuth } from '@/contexts/AuthContext'; +import { supabase } from '@/integrations/supabase/client'; +import { useToast } from '@/hooks/use-toast'; +import { Download, Filter, Search, Check, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; -import { Search, Filter, CheckCircle2, List, X } from 'lucide-react'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow +} from '@/components/ui/table'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuCheckboxItem, - DropdownMenuTrigger -} from "@/components/ui/dropdown-menu"; -import { cn } from '@/lib/utils'; +} from '@/components/ui/select'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { TaskCard } from '@/components/TaskCard'; +import { ExportOptions } from '@/components/ExportOptions'; -interface TaskListProps { - tasks: Task[]; - onTaskUpdate: (updatedTasks: Task[]) => void; +interface Task { + id: string; + task_name: string; + description: string; + priority: string; + due_date: string | null; + status: string; + created_at: string; + upload_id: string; } -const TaskList = ({ tasks, onTaskUpdate }: TaskListProps) => { - const [filteredTasks, setFilteredTasks] = useState(tasks); - const [searchQuery, setSearchQuery] = useState(''); - const [sortBy, setSortBy] = useState('deadline'); - const [filterPriority, setFilterPriority] = useState([]); - const [filterStatus, setFilterStatus] = useState([]); - const [filterDepartment, setFilterDepartment] = useState([]); - const [isFiltering, setIsFiltering] = useState(false); +interface Upload { + id: string; + file_name: string; +} - const handleStatusChange = (id: string, status: 'Open' | 'In Progress' | 'Completed') => { - const updatedTasks = tasks.map(task => - task.id === id ? { ...task, status } : task - ); - onTaskUpdate(updatedTasks); - }; - - const handlePriorityChange = (id: string, priority: 'High' | 'Medium' | 'Low') => { - const updatedTasks = tasks.map(task => - task.id === id ? { ...task, priority } : task - ); - onTaskUpdate(updatedTasks); - }; - - const uniqueDepartments = [...new Set(tasks.map(task => task.department))]; - - const sortTasks = (tasksToSort: Task[]) => { - return [...tasksToSort].sort((a, b) => { - switch (sortBy) { - case 'deadline': - return new Date(a.deadline).getTime() - new Date(b.deadline).getTime(); - case 'priority': - const priorityOrder = { High: 0, Medium: 1, Low: 2 }; - return priorityOrder[a.priority] - priorityOrder[b.priority]; - case 'department': - return a.department.localeCompare(b.department); - default: - return 0; - } - }); - }; - - const filterTasks = () => { - let result = tasks; - - // Search filter - if (searchQuery) { - result = result.filter(task => - task.description.toLowerCase().includes(searchQuery.toLowerCase()) || - task.department.toLowerCase().includes(searchQuery.toLowerCase()) || - task.context.toLowerCase().includes(searchQuery.toLowerCase()) - ); - } - - // Priority filter - if (filterPriority.length > 0) { - result = result.filter(task => filterPriority.includes(task.priority)); - } - - // Status filter - if (filterStatus.length > 0) { - result = result.filter(task => filterStatus.includes(task.status)); - } - - // Department filter - if (filterDepartment.length > 0) { - result = result.filter(task => filterDepartment.includes(task.department)); - } - - // Sort the filtered results - return sortTasks(result); - }; +const TaskList = () => { + const { user } = useAuth(); + const { toast } = useToast(); + const [tasks, setTasks] = useState([]); + const [filteredTasks, setFilteredTasks] = useState([]); + const [uploads, setUploads] = useState([]); + const [loading, setLoading] = useState(true); + const [searchTerm, setSearchTerm] = useState(''); + const [priorityFilter, setPriorityFilter] = useState(''); + const [statusFilter, setStatusFilter] = useState(''); + const [documentFilter, setDocumentFilter] = useState(''); + const [viewMode, setViewMode] = useState<'table' | 'card'>('table'); useEffect(() => { - setFilteredTasks(filterTasks()); - setIsFiltering( - searchQuery !== '' || - filterPriority.length > 0 || - filterStatus.length > 0 || - filterDepartment.length > 0 - ); - }, [tasks, searchQuery, sortBy, filterPriority, filterStatus, filterDepartment]); + if (user) { + fetchTasks(); + fetchUploads(); + } + }, [user]); - const clearFilters = () => { - setSearchQuery(''); - setFilterPriority([]); - setFilterStatus([]); - setFilterDepartment([]); + useEffect(() => { + applyFilters(); + }, [tasks, searchTerm, priorityFilter, statusFilter, documentFilter]); + + const fetchTasks = async () => { + try { + setLoading(true); + const { data, error } = await supabase + .from('tasks') + .select('*') + .eq('user_id', user?.id) + .order('created_at', { ascending: false }); + + if (error) throw error; + setTasks(data || []); + setFilteredTasks(data || []); + } catch (error) { + console.error('Error fetching tasks:', error); + toast({ + title: 'Error', + description: 'Failed to load tasks', + variant: 'destructive', + }); + } finally { + setLoading(false); + } }; - const completedTasksCount = tasks.filter(task => task.status === 'Completed').length; - const taskCompletion = Math.round((completedTasksCount / tasks.length) * 100) || 0; - + const fetchUploads = async () => { + try { + const { data, error } = await supabase + .from('uploads') + .select('id, file_name') + .eq('user_id', user?.id); + + if (error) throw error; + setUploads(data || []); + } catch (error) { + console.error('Error fetching uploads:', error); + } + }; + + const applyFilters = () => { + let result = [...tasks]; + + // Apply search term filter + if (searchTerm) { + result = result.filter( + task => + task.task_name.toLowerCase().includes(searchTerm.toLowerCase()) || + task.description.toLowerCase().includes(searchTerm.toLowerCase()) + ); + } + + // Apply priority filter + if (priorityFilter) { + result = result.filter(task => task.priority === priorityFilter); + } + + // Apply status filter + if (statusFilter) { + result = result.filter(task => task.status === statusFilter); + } + + // Apply document filter + if (documentFilter) { + result = result.filter(task => task.upload_id === documentFilter); + } + + setFilteredTasks(result); + }; + + const updateTaskStatus = async (taskId: string, newStatus: string) => { + try { + const { error } = await supabase + .from('tasks') + .update({ status: newStatus }) + .eq('id', taskId); + + if (error) throw error; + + setTasks(prevTasks => + prevTasks.map(task => + task.id === taskId ? { ...task, status: newStatus } : task + ) + ); + + toast({ + title: 'Task updated', + description: `Task status changed to ${newStatus}`, + }); + } catch (error) { + console.error('Error updating task:', error); + toast({ + title: 'Update failed', + description: 'Failed to update task status', + variant: 'destructive', + }); + } + }; + + const getDocumentName = (uploadId: string) => { + const upload = uploads.find(u => u.id === uploadId); + return upload ? upload.file_name : 'Unknown Document'; + }; + + const getPriorityBadge = (priority: string) => { + switch (priority.toLowerCase()) { + case 'high': + return {priority}; + case 'medium': + return {priority}; + case 'low': + return {priority}; + default: + return {priority}; + } + }; + + const getStatusBadge = (status: string) => { + switch (status.toLowerCase()) { + case 'completed': + return {status}; + case 'in progress': + return {status}; + case 'open': + return {status}; + default: + return {status}; + } + }; + + const clearFilters = () => { + setSearchTerm(''); + setPriorityFilter(''); + setStatusFilter(''); + setDocumentFilter(''); + }; + + if (loading) { + return ( + + +
+
+
+
+
+

Loading tasks...

+
+
+
+
+ ); + } + return ( -
-
-
-
-

Tasks

-

- {tasks.length} tasks extracted, {completedTasksCount} completed ({taskCompletion}%) -

-
- -
- - - -
-
- -
-
- - setSearchQuery(e.target.value)} - className="pl-9 rounded-full" - /> - {searchQuery && ( - - )} -
- -
- - - - - - { - if (checked) { - setFilterPriority([...filterPriority, 'High']); - } else { - setFilterPriority(filterPriority.filter(p => p !== 'High')); - } - }} - > - High - - { - if (checked) { - setFilterPriority([...filterPriority, 'Medium']); - } else { - setFilterPriority(filterPriority.filter(p => p !== 'Medium')); - } - }} - > - Medium - - { - if (checked) { - setFilterPriority([...filterPriority, 'Low']); - } else { - setFilterPriority(filterPriority.filter(p => p !== 'Low')); - } - }} - > - Low - - - + +
+
- - - - - - { - if (checked) { - setFilterStatus([...filterStatus, 'Open']); - } else { - setFilterStatus(filterStatus.filter(s => s !== 'Open')); - } - }} - > - Open - - { - if (checked) { - setFilterStatus([...filterStatus, 'In Progress']); - } else { - setFilterStatus(filterStatus.filter(s => s !== 'In Progress')); - } - }} - > - In Progress - - { - if (checked) { - setFilterStatus([...filterStatus, 'Completed']); - } else { - setFilterStatus(filterStatus.filter(s => s !== 'Completed')); - } - }} - > - Completed - - - + )} +
- - - - - - {uniqueDepartments.map(dept => ( - { - if (checked) { - setFilterDepartment([...filterDepartment, dept]); - } else { - setFilterDepartment(filterDepartment.filter(d => d !== dept)); - } - }} + {filteredTasks.length === 0 ? ( +
+
+
+ +
+
+

No tasks found

+

+ {tasks.length > 0 + ? 'Try changing your filters or search term' + : 'Upload a policy document to extract tasks'} +

+ {tasks.length === 0 && ( + + )} +
+ ) : viewMode === 'table' ? ( +
+ + + + Task + Priority + Status + Document + Actions + + + + {filteredTasks.map((task) => ( + + {task.task_name} + {getPriorityBadge(task.priority)} + {getStatusBadge(task.status)} + + {getDocumentName(task.upload_id)} + + +
+ {task.status !== 'Completed' ? ( + + ) : ( + + )} +
+
+
+ ))} +
+
+
+ ) : ( +
+ {filteredTasks.map((task) => ( + updateTaskStatus(task.id, newStatus)} + /> ))} - - -
-
- - {isFiltering && ( -
- {filterPriority.map(priority => ( - { - setFilterPriority(filterPriority.filter(p => p !== priority)); - }} - > - {priority} - - ))} - - {filterStatus.map(status => ( - { - setFilterStatus(filterStatus.filter(s => s !== status)); - }} - > - {status} - - ))} - - {filterDepartment.map(dept => ( - { - setFilterDepartment(filterDepartment.filter(d => d !== dept)); - }} - > - {dept} - - ))} - - {searchQuery && ( - setSearchQuery('')} - > - Search: {searchQuery} - +
)} - -
- )} -
- - {filteredTasks.length === 0 ? ( -
-

No tasks match your filters

-

Try adjusting your search or filters

- -
- ) : ( -
- {filteredTasks.map((task) => ( - - ))} -
- )} + + +
+ Showing {filteredTasks.length} of {tasks.length} tasks +
+
+ ); }; diff --git a/src/components/UploadSection.tsx b/src/components/UploadSection.tsx index 0897f5e..bfc6717 100644 --- a/src/components/UploadSection.tsx +++ b/src/components/UploadSection.tsx @@ -1,168 +1,206 @@ -import { useState, useRef } from 'react'; -import { Upload, FileType, FileUp, AlertCircle, Check } from 'lucide-react'; +import { useState } from 'react'; +import { useAuth } from '@/contexts/AuthContext'; +import { supabase } from '@/integrations/supabase/client'; +import { useToast } from '@/hooks/use-toast'; +import { Upload, FileIcon, Loader2, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; -import { toast } from 'sonner'; +import { Progress } from '@/components/ui/progress'; interface UploadSectionProps { - onUploadComplete: (fileName: string) => void; + onUploadComplete: () => void; } const UploadSection = ({ onUploadComplete }: UploadSectionProps) => { - const [isDragging, setIsDragging] = useState(false); + const { user } = useAuth(); + const { toast } = useToast(); const [file, setFile] = useState(null); - const [isUploading, setIsUploading] = useState(false); - const fileInputRef = useRef(null); + const [uploading, setUploading] = useState(false); + const [uploadProgress, setUploadProgress] = useState(0); - const handleDragOver = (e: React.DragEvent) => { - e.preventDefault(); - setIsDragging(true); - }; + const allowedFileTypes = [ + 'application/pdf', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + ]; - const handleDragLeave = () => { - setIsDragging(false); - }; - - const handleDrop = (e: React.DragEvent) => { - e.preventDefault(); - setIsDragging(false); - - const droppedFile = e.dataTransfer.files[0]; - if (isValidFile(droppedFile)) { - setFile(droppedFile); - } - }; - - const handleFileSelect = (e: React.ChangeEvent) => { - const selectedFile = e.target.files?.[0]; - if (selectedFile && isValidFile(selectedFile)) { + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + const selectedFile = e.target.files[0]; + + if (!allowedFileTypes.includes(selectedFile.type)) { + toast({ + title: 'Invalid file type', + description: 'Please upload a PDF or DOCX file', + variant: 'destructive', + }); + return; + } + setFile(selectedFile); } }; - const isValidFile = (file: File) => { - const validTypes = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']; - if (!validTypes.includes(file.type)) { - toast.error("Please upload a PDF or Word document"); - return false; - } - if (file.size > 10 * 1024 * 1024) { // 10MB limit - toast.error("File size exceeds 10MB limit"); - return false; - } - return true; + const clearFile = () => { + setFile(null); }; const handleUpload = async () => { - if (!file) return; + if (!file || !user) return; - setIsUploading(true); - - // Simulate file upload with a delay try { - await new Promise(resolve => setTimeout(resolve, 2000)); - toast.success(`${file.name} uploaded successfully`); - onUploadComplete(file.name); - } catch (error) { - toast.error("Upload failed. Please try again."); - console.error("Upload error:", error); + setUploading(true); + setUploadProgress(0); + + // First, create an entry in the uploads table + const fileName = file.name; + const fileType = file.type; + const filePath = `${user.id}/${Date.now()}_${fileName}`; + + const { data: uploadData, error: uploadError } = await supabase + .from('uploads') + .insert({ + user_id: user.id, + file_name: fileName, + file_path: filePath, + file_type: fileType, + status: 'Processing' + }) + .select() + .single(); + + if (uploadError) throw uploadError; + + // Simulate progress for a better UX + const progressInterval = setInterval(() => { + setUploadProgress((prev) => { + const increment = Math.random() * 10; + const newProgress = Math.min(prev + increment, 90); + return newProgress; + }); + }, 300); + + // Upload the file to storage + const { error: storageError } = await supabase + .storage + .from('documents') + .upload(filePath, file); + + clearInterval(progressInterval); + + if (storageError) throw storageError; + + // Call the edge function to process the document + const processingResponse = await supabase.functions.invoke('process-document', { + body: { + uploadId: uploadData.id, + userId: user.id, + filePath: filePath, + fileType: fileType + } + }); + + if (processingResponse.error) throw new Error(processingResponse.error.message); + + setUploadProgress(100); + + toast({ + title: 'Upload successful', + description: 'Your document is now being processed', + }); + + // Clear file and reset progress after 1 second + setTimeout(() => { + setFile(null); + setUploadProgress(0); + onUploadComplete(); + }, 1000); + + } catch (error: any) { + console.error('Upload error:', error); + toast({ + title: 'Upload failed', + description: error.message || 'An error occurred while uploading the document', + variant: 'destructive', + }); } finally { - setIsUploading(false); + setUploading(false); } }; return ( -
-
-

Upload Your Policy Document

-

Upload a PDF or Word document to extract actionable tasks

-
- -
- - - {!file ? ( - <> -
- -
-

Drag and drop your file here

-

- Support for PDF and Word documents up to 10MB -

- - - ) : ( -
-
-
- + +
+ ) : ( +
+
+
+
+ +
+
+

{file.name}

+

+ {(file.size / 1024 / 1024).toFixed(2)} MB +

-
-

- {file.name} - -

-

- {(file.size / 1024 / 1024).toFixed(2)} MB -

-
-
- - -
+
- )} -
+ + {uploading && ( +
+ +

{uploadProgress.toFixed(0)}% complete

+
+ )} + +
+ +
+
+ )} -
- - Your documents are processed securely and never stored permanently +
+

After uploading, your document will be automatically processed to extract actionable tasks.

); diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx index 105fb65..5dc8241 100644 --- a/src/components/ui/progress.tsx +++ b/src/components/ui/progress.tsx @@ -1,26 +1,37 @@ + import * as React from "react" -import * as ProgressPrimitive from "@radix-ui/react-progress" import { cn } from "@/lib/utils" const Progress = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, value, ...props }, ref) => ( - - - -)) -Progress.displayName = ProgressPrimitive.Root.displayName + HTMLDivElement, + React.HTMLAttributes & { + value?: number + max?: number + indicatorClassName?: string + } +>(({ className, value, max = 100, indicatorClassName, ...props }, ref) => { + return ( +
+
+
+ ) +}) +Progress.displayName = "Progress" export { Progress } diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx index 7f3502f..7f75669 100644 --- a/src/components/ui/table.tsx +++ b/src/components/ui/table.tsx @@ -1,10 +1,11 @@ + import * as React from "react" import { cn } from "@/lib/utils" const Table = React.forwardRef< HTMLTableElement, - React.HTMLAttributes + React.HTMLTableProps >(({ className, ...props }, ref) => (
+ React.HTMLTableSectionProps >(({ className, ...props }, ref) => ( )) @@ -26,7 +27,7 @@ TableHeader.displayName = "TableHeader" const TableBody = React.forwardRef< HTMLTableSectionElement, - React.HTMLAttributes + React.HTMLTableSectionProps >(({ className, ...props }, ref) => ( + React.HTMLTableSectionProps >(({ className, ...props }, ref) => ( tr]:last:border-b-0", - className - )} + className={cn("bg-primary font-medium text-primary-foreground", className)} {...props} /> )) @@ -53,7 +51,7 @@ TableFooter.displayName = "TableFooter" const TableRow = React.forwardRef< HTMLTableRowElement, - React.HTMLAttributes + React.HTMLTableRowProps >(({ className, ...props }, ref) => ( { - const [uploadCompleted, setUploadCompleted] = useState(false); - const [documentName, setDocumentName] = useState(''); - const [tasks, setTasks] = useState([]); + const { user } = useAuth(); + const { toast } = useToast(); + const [activeTab, setActiveTab] = useState<'upload' | 'tasks'>('upload'); + const [uploadCount, setUploadCount] = useState(0); + const [taskCount, setTaskCount] = useState(0); + const [recentUploads, setRecentUploads] = useState([]); + const [processingUploads, setProcessingUploads] = useState(0); - const handleUploadComplete = (fileName: string) => { - // Simulate task extraction with a delay - setTimeout(() => { - const extractedTasks = generateTasks(fileName); - setTasks(extractedTasks); - setDocumentName(fileName); - setUploadCompleted(true); - - toast.success(`${extractedTasks.length} tasks extracted successfully`, { - description: 'Tasks are now ready for review and prioritization' + useEffect(() => { + if (user) { + fetchDashboardData(); + } + }, [user]); + + const fetchDashboardData = async () => { + try { + // Get upload count + const { count: uploadCountData, error: uploadCountError } = await supabase + .from('uploads') + .select('*', { count: 'exact', head: true }) + .eq('user_id', user?.id); + + if (uploadCountError) throw uploadCountError; + setUploadCount(uploadCountData || 0); + + // Get task count + const { count: taskCountData, error: taskCountError } = await supabase + .from('tasks') + .select('*', { count: 'exact', head: true }) + .eq('user_id', user?.id); + + if (taskCountError) throw taskCountError; + setTaskCount(taskCountData || 0); + + // Get recent uploads + const { data: recentUploadsData, error: recentUploadsError } = await supabase + .from('uploads') + .select('*') + .eq('user_id', user?.id) + .order('created_at', { ascending: false }) + .limit(5); + + if (recentUploadsError) throw recentUploadsError; + setRecentUploads(recentUploadsData || []); + + // Get processing uploads count + const { count: processingCount, error: processingError } = await supabase + .from('uploads') + .select('*', { count: 'exact', head: true }) + .eq('user_id', user?.id) + .eq('status', 'Processing'); + + if (processingError) throw processingError; + setProcessingUploads(processingCount || 0); + } catch (error) { + console.error('Error fetching dashboard data:', error); + toast({ + title: 'Error', + description: 'Failed to load dashboard data', + variant: 'destructive', }); - }, 1500); - }; - - const handleTaskUpdate = (updatedTasks: Task[]) => { - setTasks(updatedTasks); + } }; return ( -
-
+
+

Dashboard

-
-
- -
- - - Upload Document - - { - if (!uploadCompleted) { - toast.error("Please upload a document first"); - } - }} - > - Review Tasks - - { - if (!uploadCompleted) { - toast.error("Please upload a document first"); - } - }} - > - Export - - +
+ + + Documents + Total uploaded documents + + +

{uploadCount}

+
+
+ + + + Tasks + Extracted actionable items + + +

{taskCount}

+
+
+ + + +
+ Processing + Documents being analyzed
- - - - - {uploadCompleted && ( -
-
- - Document processed successfully! Continue to Review Tasks. -
-
- )} -
- - - {uploadCompleted ? ( - <> -
-
- Extracted tasks from + {processingUploads > 0 && ( +
+ +
+ )} + + +

{processingUploads}

+
+ +
+ +
+ + + +
+ + {activeTab === 'upload' ? ( +
+ + + Upload Policy Document + + Upload your IT security or compliance policy documents to extract actionable tasks + + + + + + + + {recentUploads.length > 0 && ( + + + Recent Uploads + + Your most recent document uploads + + + +
+ {recentUploads.map((upload) => ( +
+
+ +
+

{upload.file_name}

+

+ {new Date(upload.created_at).toLocaleDateString()} +

+
+
+
+ + {upload.status} + +
-

{documentName}

-
- - - ) : ( -
-

Please upload a document first

+ ))}
- )} - - - - {uploadCompleted ? ( -
-
-

Export Task List

-

- Download your tasks to share with your team or import into your project management tool -

-
- -
-
-

Export Summary

-
    -
  • Document: {documentName}
  • -
  • Total Tasks: {tasks.length}
  • -
  • Open Tasks: {tasks.filter(t => t.status === 'Open').length}
  • -
  • In Progress Tasks: {tasks.filter(t => t.status === 'In Progress').length}
  • -
  • Completed Tasks: {tasks.filter(t => t.status === 'Completed').length}
  • -
  • High Priority Tasks: {tasks.filter(t => t.priority === 'High').length}
  • -
-
- - -
- -

- Need to make changes? Go back to the Review Tasks tab. -

-
- ) : ( -
-

Please upload a document first

-
- )} -
- +
+
+ )}
-
- - + ) : ( + + )}
); }; diff --git a/supabase/functions/process-document/config.toml b/supabase/functions/process-document/config.toml new file mode 100644 index 0000000..3787f0d --- /dev/null +++ b/supabase/functions/process-document/config.toml @@ -0,0 +1,2 @@ + +ffwcnetryatmpcnjkegg diff --git a/supabase/functions/process-document/index.ts b/supabase/functions/process-document/index.ts new file mode 100644 index 0000000..8157a8e --- /dev/null +++ b/supabase/functions/process-document/index.ts @@ -0,0 +1,155 @@ + +import "https://deno.land/x/xhr@0.1.0/mod.ts"; +import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; +import { createClient } from "https://esm.sh/@supabase/supabase-js@2.38.1"; + +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', +}; + +const OPENAI_API_KEY = Deno.env.get('OPENAI_API_KEY'); +const SUPABASE_URL = Deno.env.get('SUPABASE_URL') || "https://ffwcnetryatmpcnjkegg.supabase.co"; +const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') || ""; + +serve(async (req) => { + // Handle CORS preflight requests + if (req.method === 'OPTIONS') { + return new Response(null, { headers: corsHeaders }); + } + + try { + const { uploadId, userId, filePath, fileType } = await req.json(); + + // Create Supabase client with service role key to bypass RLS + const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, { + auth: { + persistSession: false, + }, + }); + + // Fetch the file content from Supabase Storage + const { data: fileData, error: fileError } = await supabase + .storage + .from('documents') + .download(filePath); + + if (fileError) { + console.error('Error downloading file:', fileError); + return new Response( + JSON.stringify({ error: 'Error downloading file' }), + { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } + ); + } + + // Convert file content to text + const text = await fileData.text(); + + // Extract tasks using OpenAI + const tasks = await extractTasksWithOpenAI(text, uploadId, userId); + + // Save extracted tasks to the database + for (const task of tasks) { + const { error: taskError } = await supabase + .from('tasks') + .insert(task); + + if (taskError) { + console.error('Error inserting task:', taskError); + } + } + + // Update upload status to 'Completed' + const { error: updateError } = await supabase + .from('uploads') + .update({ status: 'Completed' }) + .eq('id', uploadId); + + if (updateError) { + console.error('Error updating upload status:', updateError); + return new Response( + JSON.stringify({ error: 'Error updating upload status' }), + { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } + ); + } + + return new Response( + JSON.stringify({ success: true, tasksExtracted: tasks.length }), + { headers: { ...corsHeaders, 'Content-Type': 'application/json' } } + ); + + } catch (error) { + console.error('Error processing document:', error); + return new Response( + JSON.stringify({ error: error.message }), + { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } + ); + } +}); + +async function extractTasksWithOpenAI(text: string, uploadId: string, userId: string) { + try { + // Call OpenAI API to extract tasks + const response = await fetch('https://api.openai.com/v1/chat/completions', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${OPENAI_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + model: 'gpt-4o-mini', + messages: [ + { + role: 'system', + content: ` + You are a specialized task extractor for IT security and compliance policy documents. + Your job is to identify specific actionable tasks from policy documents. + For each task, extract the following information: + 1. Task name (short, clear description of what needs to be done) + 2. Description (more detailed explanation) + 3. Priority (High, Medium, Low) + 4. Due date (if mentioned) + + Format your response as a valid JSON array of task objects with these properties: + [ + { + "task_name": "string", + "description": "string", + "priority": "string", + "due_date": "ISO date string or null" + } + ] + + If no due date is mentioned for a task, return null for that field. + Only return the JSON array, nothing else. + ` + }, + { + role: 'user', + content: text + } + ] + }), + }); + + const data = await response.json(); + const extractedTasksJson = data.choices[0].message.content; + + // Parse the extracted tasks + const extractedTasks = JSON.parse(extractedTasksJson); + + // Format tasks for database insertion + return extractedTasks.map((task: any) => ({ + upload_id: uploadId, + user_id: userId, + task_name: task.task_name, + description: task.description, + priority: task.priority, + due_date: task.due_date, + status: 'Open' + })); + } catch (error) { + console.error('Error extracting tasks with OpenAI:', error); + throw new Error('Failed to extract tasks from document'); + } +}