diff --git a/.env b/.env new file mode 100644 index 0000000..2dc42fc --- /dev/null +++ b/.env @@ -0,0 +1,19 @@ +# Supabase Configuration +VITE_SUPABASE_URL=https://yuanqfswhberkoevtmfr.supabase.co +VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ + +# API Configuration +VITE_API_URL=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key= + + +# App Configuration +VITE_APP_NAME=MedConnect +VITE_ENVIRONMENT=development + +#Chat configuration +VITE_CHAT_SERVICE_URL=https://sxnbrchqhzednsuvjegd.supabase.co +VITE_CHAT_ANO_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InN4bmJyY2hxaHplZG5zdXZqZWdkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjQ2Mzc0NjQsImV4cCI6MjA4MDIxMzQ2NH0.AQHfpaTsKXUYc1Tv3NqFagWP9Ok1R9pQy7v3tHa_WmM + +VITE_SERVICE_KEY= service_u248bv3 +VITE_TEMPLATE_KEY= template_v51obqa +VITE_PUBLIC_KEY= QDDGCfWv5_DlZGisG \ No newline at end of file diff --git a/components.json b/components.json new file mode 100644 index 0000000..8bfb5f0 --- /dev/null +++ b/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": false, + "tailwind": { + "config": "", + "css": "src/assets/css/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/eslint.config.js b/eslint.config.js index cee1e2c..5661d95 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -24,6 +24,7 @@ export default defineConfig([ }, rules: { 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react/prop-types': 'off', }, }, ]) diff --git a/index.html b/index.html index dbfd51e..c9e02d3 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,21 @@ - - - - - - MediConnect - - -
- - - + + + + + + + MediConnect + + + + + + +
+ + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f88eedb..ce6bed4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,24 +8,40 @@ "name": "medconnect", "version": "0.0.0", "dependencies": { + "20": "^3.1.9", + "22": "^0.0.0", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", "@fortawesome/fontawesome-free": "^7.0.0", "@fullcalendar/core": "^6.1.19", "@fullcalendar/daygrid": "^6.1.19", "@fullcalendar/interaction": "^6.1.19", "@fullcalendar/react": "^6.1.19", "@fullcalendar/timegrid": "^6.1.19", - "@supabase/supabase-js": "^2.57.0", + "@mui/icons-material": "^7.3.4", + "@mui/material": "^7.3.4", + "@supabase/supabase-js": "^2.76.1", + "@tailwindcss/vite": "^4.1.14", "@tiptap/extension-image": "^3.4.2", "@tiptap/pm": "^3.4.2", "@tiptap/react": "^3.4.2", "@tiptap/starter-kit": "^3.4.2", + "@zegocloud/zego-uikit-prebuilt": "^2.17.0", "bootstrap": "^5.3.8", + "chart.js": "^4.5.1", + "emailjs-com": "^3.2.0", + "lucide-react": "^0.544.0", "react": "^19.1.1", "react-bootstrap": "^2.10.10", + "react-chartjs-2": "^5.3.1", "react-dom": "^19.1.1", + "react-google-recaptcha": "^3.1.0", "react-icons": "^5.5.0", + "react-responsive": "^10.0.1", "react-router-dom": "^7.8.2", - "recharts": "^3.1.2", + "react-select": "^5.10.2", + "recharts": "^3.3.0", + "sweetalert2": "^11.23.0", "use-mask-input": "^3.5.0" }, "devDependencies": { @@ -33,6 +49,7 @@ "@types/react": "^19.1.9", "@types/react-dom": "^19.1.7", "@vitejs/plugin-react": "^4.7.0", + "autoprefix": "^1.0.1", "eslint": "^9.32.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", @@ -58,7 +75,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -114,7 +130,6 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.3", @@ -148,7 +163,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -158,7 +172,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -200,7 +213,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -210,7 +222,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -244,7 +255,6 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.2" @@ -289,9 +299,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", - "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -301,7 +311,6 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -316,7 +325,6 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", @@ -335,7 +343,6 @@ "version": "7.28.2", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -345,6 +352,176 @@ "node": ">=6.9.0" } }, + "node_modules/@borewit/text-codec": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", + "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", @@ -352,7 +529,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -369,7 +545,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -386,7 +561,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -403,7 +577,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -420,7 +593,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -437,7 +609,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -454,7 +625,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -471,7 +641,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -488,7 +657,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -505,7 +673,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -522,7 +689,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -539,7 +705,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -556,7 +721,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -573,7 +737,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -590,7 +753,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -607,7 +769,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -624,7 +785,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -641,7 +801,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -658,7 +817,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -675,7 +833,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -692,7 +849,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -709,7 +865,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -726,7 +881,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -743,7 +897,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -760,7 +913,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -777,7 +929,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -946,7 +1097,6 @@ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", "license": "MIT", - "optional": true, "dependencies": { "@floating-ui/utils": "^0.2.10" } @@ -956,7 +1106,6 @@ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "license": "MIT", - "optional": true, "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" @@ -966,8 +1115,7 @@ "version": "0.2.10", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/@fortawesome/fontawesome-free": { "version": "7.0.0", @@ -1028,6 +1176,25 @@ "@fullcalendar/core": "~6.1.19" } }, + "node_modules/@gulpjs/messages": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@gulpjs/messages/-/messages-1.1.0.tgz", + "integrity": "sha512-Ys9sazDatyTgZVb4xPlDufLweJ/Os2uHWOv+Caxvy2O85JcnT4M3vc73bi8pdLWlv3fdWQz3pdI9tVwo8rQQSg==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@gulpjs/to-absolute-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", + "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", + "dependencies": { + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1094,22 +1261,42 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1119,20 +1306,289 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.30", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.4.tgz", + "integrity": "sha512-BIktMapG3r4iXwIhYNpvk97ZfYWTreBBQTWjQKbNbzI64+ULHfYavQEX2w99aSWHS58DvXESWIgbD9adKcUOBw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.4.tgz", + "integrity": "sha512-9n6Xcq7molXWYb680N2Qx+FRW8oT6j/LXF5PZFH3ph9X/Rct0B/BlLAsFI7iL9ySI6LVLuQIVtrLiPT82R7OZw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.3.4", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.4.tgz", + "integrity": "sha512-gEQL9pbJZZHT7lYJBKQCS723v1MGys2IFc94COXbUIyCTWa+qC77a7hUax4Yjd5ggEm35dk4AyYABpKKWC4MLw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/core-downloads-tracker": "^7.3.4", + "@mui/system": "^7.3.3", + "@mui/types": "^7.4.7", + "@mui/utils": "^7.3.3", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.1.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.3.3", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.3.tgz", + "integrity": "sha512-OJM+9nj5JIyPUvsZ5ZjaeC9PfktmK+W5YaVLToLR8L0lB/DGmv1gcKE43ssNLSvpoW71Hct0necfade6+kW3zQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.3.tgz", + "integrity": "sha512-CmFxvRJIBCEaWdilhXMw/5wFJ1+FT9f3xt+m2pPXhHPeVIbBg9MnMvNSJjdALvnQJMPw8jLhrUtXmN7QAZV2fw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.3.tgz", + "integrity": "sha512-Lqq3emZr5IzRLKaHPuMaLBDVaGvxoh6z7HMWd1RPKawBM5uMRaQ4ImsmmgXWtwJdfZux5eugfDhXJUo2mliS8Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/private-theming": "^7.3.3", + "@mui/styled-engine": "^7.3.3", + "@mui/types": "^7.4.7", + "@mui/utils": "^7.3.3", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.7.tgz", + "integrity": "sha512-8vVje9rdEr1rY8oIkYgP+Su5Kwl6ik7O3jQ0wl78JGSmiZhRHV+vkjooGdKD8pbtZbutXFVTWQYshu2b3sG9zw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.3.tgz", + "integrity": "sha512-kwNAUh7bLZ7mRz9JZ+6qfRnnxbE4Zuc+RzXnhSpRSxjTlSTj7b4JxRLXpG+MVtPVtqks5k/XC8No1Vs3x4Z2gg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/types": "^7.4.7", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.1.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -1258,7 +1714,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1272,7 +1727,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1286,7 +1740,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1300,7 +1753,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1314,7 +1766,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1328,7 +1779,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1342,7 +1792,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1356,7 +1805,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1370,7 +1818,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1384,7 +1831,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1398,7 +1844,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1412,7 +1857,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1426,7 +1870,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1440,7 +1883,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1454,7 +1896,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1468,7 +1909,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1482,7 +1922,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1496,7 +1935,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1510,7 +1948,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1524,13 +1961,39 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==" + }, + "node_modules/@sindresorhus/is": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-6.3.1.tgz", + "integrity": "sha512-FX4MfcifwJyFOI2lPoX7PQxCqx8BG1HCho7WdiXwpEQx1Ycij0JxkfYtGK7yqNScrZGSlt6RE6sw8QYoH7eKnQ==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", @@ -1544,21 +2007,23 @@ "license": "MIT" }, "node_modules/@supabase/auth-js": { - "version": "2.71.1", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.71.1.tgz", - "integrity": "sha512-mMIQHBRc+SKpZFRB2qtupuzulaUhFYupNyxqDj5Jp/LyPvcWvjaJzZzObv6URtL/O6lPxkanASnotGtNpS3H2Q==", + "version": "2.76.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.76.1.tgz", + "integrity": "sha512-bxmcgPuyjTUBg7+jAohJ15TDh3ph4hXcv7QkRsQgnIpszurD5LYaJPzX638ETQ8zDL4fvHZRHfGrcmHV8C91jA==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "^2.6.14" + "@supabase/node-fetch": "2.6.15", + "tslib": "2.8.1" } }, "node_modules/@supabase/functions-js": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.5.tgz", - "integrity": "sha512-v5GSqb9zbosquTo6gBwIiq7W9eQ7rE5QazsK/ezNiQXdCbY+bH8D9qEaBIkhVvX4ZRW5rP03gEfw5yw9tiq4EQ==", + "version": "2.76.1", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.76.1.tgz", + "integrity": "sha512-+zJym/GC1sofm5QYKGxHSszCpMW4Ao2dj/WC3YlffAGuIlIhUtWTJvKsv5q7sWaSKUKdDhGpWhZ2OD++fW5BtQ==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "^2.6.14" + "@supabase/node-fetch": "2.6.15", + "tslib": "2.8.1" } }, "node_modules/@supabase/node-fetch": { @@ -1574,47 +2039,50 @@ } }, "node_modules/@supabase/postgrest-js": { - "version": "1.21.3", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.21.3.tgz", - "integrity": "sha512-rg3DmmZQKEVCreXq6Am29hMVe1CzemXyIWVYyyua69y6XubfP+DzGfLxME/1uvdgwqdoaPbtjBDpEBhqxq1ZwA==", + "version": "2.76.1", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.76.1.tgz", + "integrity": "sha512-QJ1Cwim6L9gzWKP8U4Lgw9x/4lMWkZSVMDRYFCH+vVGitVbtfU885swTiioOjjUe4EYGZm+Xktg90twzSVv6IA==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "^2.6.14" + "@supabase/node-fetch": "2.6.15", + "tslib": "2.8.1" } }, "node_modules/@supabase/realtime-js": { - "version": "2.15.4", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.15.4.tgz", - "integrity": "sha512-e/FYIWjvQJHOCNACWehnKvg26zosju3694k0NMUNb+JGLdvHJzEa29ZVVLmawd2kvx4hdbv8mxSqfttRnH3+DA==", + "version": "2.76.1", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.76.1.tgz", + "integrity": "sha512-B5Lfmprea2fx2FS7obp4uAWiRUlEa6j9J3+BvvETGp/2LdkSRBaLEJCBylfcZTXk67ajNPX6ppvKvAZsckqXYg==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "^2.6.13", + "@supabase/node-fetch": "2.6.15", "@types/phoenix": "^1.6.6", "@types/ws": "^8.18.1", + "tslib": "2.8.1", "ws": "^8.18.2" } }, "node_modules/@supabase/storage-js": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.11.0.tgz", - "integrity": "sha512-Y+kx/wDgd4oasAgoAq0bsbQojwQ+ejIif8uczZ9qufRHWFLMU5cODT+ApHsSrDufqUcVKt+eyxtOXSkeh2v9ww==", + "version": "2.76.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.76.1.tgz", + "integrity": "sha512-OJiNT8tocI9tcTjTjv1SBVLabzgEnS1NorZuqivkiJ0gTYmeg2c2PFmqCARhoQ4whF6zR9MVsX/Mtj2oSv4i/w==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "^2.6.14" + "@supabase/node-fetch": "2.6.15", + "tslib": "2.8.1" } }, "node_modules/@supabase/supabase-js": { - "version": "2.57.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.57.0.tgz", - "integrity": "sha512-h9ttcL0MY4h+cGqZl95F/RuqccuRBjHU9B7Qqvw0Da+pPK2sUlU1/UdvyqUGj37UsnSphr9pdGfeXjesYkBcyA==", + "version": "2.76.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.76.1.tgz", + "integrity": "sha512-dYMh9EsTVXZ6WbQ0QmMGIhbXct5+x636tXXaaxUmwjj3kY1jyBTQU8QehxAIfjyRu1mWGV07hoYmTYakkxdSGQ==", "license": "MIT", "dependencies": { - "@supabase/auth-js": "2.71.1", - "@supabase/functions-js": "2.4.5", + "@supabase/auth-js": "2.76.1", + "@supabase/functions-js": "2.76.1", "@supabase/node-fetch": "2.6.15", - "@supabase/postgrest-js": "1.21.3", - "@supabase/realtime-js": "2.15.4", - "@supabase/storage-js": "^2.10.4" + "@supabase/postgrest-js": "2.76.1", + "@supabase/realtime-js": "2.76.1", + "@supabase/storage-js": "2.76.1" } }, "node_modules/@swc/helpers": { @@ -1626,6 +2094,268 @@ "tslib": "^2.8.0" } }, + "node_modules/@tailwindcss/node": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.14.tgz", + "integrity": "sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.0", + "lightningcss": "1.30.1", + "magic-string": "^0.30.19", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.14" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.14.tgz", + "integrity": "sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.5.1" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.14", + "@tailwindcss/oxide-darwin-arm64": "4.1.14", + "@tailwindcss/oxide-darwin-x64": "4.1.14", + "@tailwindcss/oxide-freebsd-x64": "4.1.14", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.14", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.14", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.14", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.14", + "@tailwindcss/oxide-linux-x64-musl": "4.1.14", + "@tailwindcss/oxide-wasm32-wasi": "4.1.14", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.14", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.14" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.14.tgz", + "integrity": "sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.14.tgz", + "integrity": "sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.14.tgz", + "integrity": "sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.14.tgz", + "integrity": "sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.14.tgz", + "integrity": "sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.14.tgz", + "integrity": "sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.14.tgz", + "integrity": "sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.14.tgz", + "integrity": "sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.14.tgz", + "integrity": "sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.14.tgz", + "integrity": "sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.0.5", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.14.tgz", + "integrity": "sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.14.tgz", + "integrity": "sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.14.tgz", + "integrity": "sha512-BoFUoU0XqgCUS1UXWhmDJroKKhNXeDzD7/XwabjkDIAbMnc4ULn5e2FuEuBbhZ6ENZoSYzKlzvZ44Yr6EUDUSA==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.14", + "@tailwindcss/oxide": "4.1.14", + "tailwindcss": "4.1.14" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, "node_modules/@tiptap/core": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.4.2.tgz", @@ -2073,6 +2803,20 @@ "url": "https://github.com/sponsors/ueberdosis" } }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "optional": true, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2185,9 +2929,13 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, + "node_modules/@types/expect": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", + "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2226,6 +2974,12 @@ "undici-types": "~7.10.0" } }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, "node_modules/@types/phoenix": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", @@ -2271,6 +3025,15 @@ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", "license": "MIT" }, + "node_modules/@types/vinyl": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.12.tgz", + "integrity": "sha512-Sr2fYMBUVGYq8kj3UthXFAu5UN6ZW+rYr4NACjZQJvHvj+c8lYv0CahmZ2P/r7iUkN44gGUBwqxZkrKXYPb7cw==", + "dependencies": { + "@types/expect": "^1.20.4", + "@types/node": "*" + } + }, "node_modules/@types/warning": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", @@ -2307,6 +3070,31 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@zegocloud/zego-uikit-prebuilt": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@zegocloud/zego-uikit-prebuilt/-/zego-uikit-prebuilt-2.17.0.tgz", + "integrity": "sha512-yWeMfjKAxNni6hrIP6yVOY5c/TOLizjgGEA+7Lgg/so7WEkdNH2woBx4hV0fx47r2V2puHpKMQrCvk0C4L7+5Q==", + "license": "ISC" + }, + "node_modules/20": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/20/-/20-3.1.9.tgz", + "integrity": "sha512-ODlMClHgW8ziDZa+ypq0XuA+Hzi++qqi8L9T/LZyqyaWjLRm/tTXBPQSNRmUFcgzhrmywbvoVZvu10og1FlhZQ==", + "dependencies": { + "gulp": "*", + "gulp-concat": "*", + "gulp-imagemin": "*", + "gulp-minify-css": "*", + "gulp-minify-html": "*", + "gulp-rename": "*", + "gulp-uglify": "*" + } + }, + "node_modules/22": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/22/-/22-0.0.0.tgz", + "integrity": "sha512-MdBPNDaCFY4fZVpp14n3Mt4isZ2yS1DrIiOig/iMLljr4zDa0g/583xf/lFXNPwhxCfGKYvyWJSrYyS8jNk2mQ==" + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -2347,11 +3135,37 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", + "engines": { + "node": ">=0.4.2" + } + }, + "node_modules/ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==", + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -2363,19 +3177,763 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/archive-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz", + "integrity": "sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA==", + "optional": true, + "dependencies": { + "file-type": "^4.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/archive-type/node_modules/file-type": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz", + "integrity": "sha512-f2UbFQEk7LXgWpi5ntcO86OeA/cC80fuDDDaX/fZ2ZGel+AF7leRQqBBW1eJNiiQkrZlAoM6P+VYP5P6bOlDEQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/argh": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/argh/-/argh-0.1.4.tgz", + "integrity": "sha512-sQN85FUGbEUBLyQiSJp4v8yAHTST2ao1WVXb/L8jkVqQTsypZuJQD0gMVeOLoSZBz21p22izF6HsBQP16QKQtg==" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==" + }, + "node_modules/async-done": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", + "integrity": "sha512-j0s3bzYq9yKIVLKGE/tWlCpa3PfFLcrDZLTSVdnnCTGagXuXBJO4SsY9Xdk/fQBirCkH4evW5xOeJXqlAQFdsw==", + "dependencies": { + "end-of-stream": "^1.4.4", + "once": "^1.4.0", + "stream-exhaust": "^1.0.2" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/async-settle": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-2.0.0.tgz", + "integrity": "sha512-Obu/KE8FurfQRN6ODdHN9LuXqwC+JFIM9NRyZqJJ4ZfLJmIYN9Rg0/kb+wF70VV5+fJusTMQlJ1t5rF7J/ETdg==", + "dependencies": { + "async-done": "^2.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/autoprefix": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/autoprefix/-/autoprefix-1.0.1.tgz", + "integrity": "sha512-uS8sd6wq9i6B/+HY5ysWbddFkS1qEnMLxaYC8jVnNQUOo40lg7Udx5DcO1MxFfZVU6tvOKwlpDiqVrRfAjubhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "autoprefixer-core": "~5.1.1", + "camelcase": "^1.1.0", + "decamelize": "^1.0.0" + } + }, + "node_modules/autoprefixer-core": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/autoprefixer-core/-/autoprefixer-core-5.1.11.tgz", + "integrity": "sha512-9mTI2s93BpYKsYnGIQx6txi2vk24K2+BFuuoGShlk9bSaaL5Dyjm2L3ENB9O9LSGqTethEUacnoZ5aGrOeCvfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "~0.2.0", + "caniuse-db": "^1.0.30000132", + "num2fraction": "~1.1.0", + "postcss": "~4.0.6" + } + }, + "node_modules/autoprefixer-core/node_modules/browserslist": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-0.2.0.tgz", + "integrity": "sha512-i6Sq3Sb/Li23kPHugpBK01cW+NTzifzfc2zjlhZ8NcSBoX64B513eXgcALNgEtDmfLzuVgLosLpaNUdUjkKscg==", + "deprecated": "Browserslist 2 could fail on reading Browserslist >3.0 config used in other tools.", + "dev": true, + "license": "MIT", + "dependencies": { + "caniuse-db": "^1.0.30000054" + } + }, + "node_modules/autoprefixer-core/node_modules/postcss": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-4.0.6.tgz", + "integrity": "sha512-vi0DkoNKHmuMMFzHX6s31D3yms/jWQ1/kVQRcA39hRZVhSmscmpXDVW6dpGIxVy5wqDVkFUpWpOh/wpeCmHGCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-base64": "~2.1.7", + "source-map": "~0.2.0" + } + }, + "node_modules/autoprefixer-core/node_modules/source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", + "dev": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "optional": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/bach": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bach/-/bach-2.0.1.tgz", + "integrity": "sha512-A7bvGMGiTOxGMpNupYl9HQTf0FFDNF4VCmks4PJpFyN1AX2pdKuxuwdvUz2Hu388wcgp+OvGFNsumBfFNkR7eg==", + "dependencies": { + "async-done": "^2.0.0", + "async-settle": "^2.0.0", + "now-and-later": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/balanced-match": { "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/bare-events": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.7.0.tgz", + "integrity": "sha512-b3N5eTW1g7vXkw+0CXh/HazGTcO5KYuu/RCNaJbDMPI6LHDi+7qe8EmxKUVe1sUbY2KZOVZFyj62x0OEz9qyAA==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha512-3vqtKL1N45I5dV0RdssXZG7X6pCqQrWPNOlBPZPrd+QkE2HEhR57Z04m0KtpbsZH73j+a3F8UD1TQnn+ExTvIA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-build": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bin-build/-/bin-build-3.0.0.tgz", + "integrity": "sha512-jcUOof71/TNAI2uM5uoUaDq2ePcVBQ3R/qhxAz1rX7UfvduAL/RXD3jXzvn8cVcDJdGVkiR1shal3OH0ImpuhA==", + "optional": true, + "dependencies": { + "decompress": "^4.0.0", + "download": "^6.2.2", + "execa": "^0.7.0", + "p-map-series": "^1.0.0", + "tempfile": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-build/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "optional": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/bin-build/node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", + "optional": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-build/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-build/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-build/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "optional": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/bin-build/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "optional": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-build/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-build/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/bin-build/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "optional": true + }, + "node_modules/bin-check": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", + "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==", + "optional": true, + "dependencies": { + "execa": "^0.7.0", + "executable": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-check/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "optional": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/bin-check/node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", + "optional": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-check/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-check/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-check/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "optional": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/bin-check/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "optional": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-check/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-check/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/bin-check/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "optional": true + }, + "node_modules/bin-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-3.1.0.tgz", + "integrity": "sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ==", + "optional": true, + "dependencies": { + "execa": "^1.0.0", + "find-versions": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version-check": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-4.0.0.tgz", + "integrity": "sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ==", + "optional": true, + "dependencies": { + "bin-version": "^3.0.0", + "semver": "^5.6.0", + "semver-truncate": "^1.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version-check/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/bin-wrapper": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bin-wrapper/-/bin-wrapper-4.1.0.tgz", + "integrity": "sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q==", + "optional": true, + "dependencies": { + "bin-check": "^4.1.0", + "bin-version-check": "^4.0.0", + "download": "^7.1.0", + "import-lazy": "^3.1.0", + "os-filter-obj": "^2.0.0", + "pify": "^4.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-wrapper/node_modules/@sindresorhus/is": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", + "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/download": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/download/-/download-7.1.0.tgz", + "integrity": "sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ==", + "optional": true, + "dependencies": { + "archive-type": "^4.0.0", + "caw": "^2.0.1", + "content-disposition": "^0.5.2", + "decompress": "^4.2.0", + "ext-name": "^5.0.0", + "file-type": "^8.1.0", + "filenamify": "^2.0.0", + "get-stream": "^3.0.0", + "got": "^8.3.1", + "make-dir": "^1.2.0", + "p-event": "^2.1.0", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-wrapper/node_modules/download/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/file-type": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz", + "integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-wrapper/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/got": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", + "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", + "optional": true, + "dependencies": { + "@sindresorhus/is": "^0.7.0", + "cacheable-request": "^2.1.1", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "into-stream": "^3.1.0", + "is-retry-allowed": "^1.1.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "mimic-response": "^1.0.0", + "p-cancelable": "^0.4.0", + "p-timeout": "^2.0.1", + "pify": "^3.0.0", + "safe-buffer": "^5.1.1", + "timed-out": "^4.0.1", + "url-parse-lax": "^3.0.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/got/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/p-cancelable": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", + "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/p-event": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz", + "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==", + "optional": true, + "dependencies": { + "p-timeout": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-wrapper/node_modules/p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "optional": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "optional": true, + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "optional": true, + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "optional": true + }, "node_modules/bootstrap": { "version": "5.3.8", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", @@ -2399,13 +3957,24 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/browserslist": { "version": "4.25.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", @@ -2439,16 +4008,214 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "optional": true, + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "optional": true + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "optional": true + }, + "node_modules/bufferstreams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-1.0.1.tgz", + "integrity": "sha512-LZmiIfQprMLS6/k42w/PTc7awhU8AdNNcUerxTgr01WlP9agR2SgMv0wjlYYFD6eDOi8WvofrTX8RayjR/AeUQ==", + "dependencies": { + "readable-stream": "^1.0.33" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/bufferstreams/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/bufferstreams/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/bufferstreams/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/cacheable-request": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", + "integrity": "sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==", + "optional": true, + "dependencies": { + "clone-response": "1.0.2", + "get-stream": "3.0.0", + "http-cache-semantics": "3.8.1", + "keyv": "3.0.0", + "lowercase-keys": "1.0.0", + "normalize-url": "2.0.1", + "responselike": "1.0.2" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/cacheable-request/node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", + "optional": true + }, + "node_modules/cacheable-request/node_modules/keyv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", + "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "optional": true, + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "optional": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caniuse-db": { + "version": "1.0.30001751", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001751.tgz", + "integrity": "sha512-8WtVzuKNl7K0O4nCAY3Z0ArbAxwHs74gB1Z35WJ1fi4BO24Qv2dMW91d7LOUaVIpZYK2SnjgyMRe4AIkr0pkog==", + "dev": true, + "license": "CC-BY-4.0" + }, "node_modules/caniuse-lite": { "version": "1.0.30001737", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", @@ -2470,11 +4237,25 @@ ], "license": "CC-BY-4.0" }, + "node_modules/caw": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", + "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", + "optional": true, + "dependencies": { + "get-proxy": "^2.0.0", + "isurl": "^1.0.0-alpha5", + "tunnel-agent": "^0.6.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -2487,12 +4268,167 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/change-file-extension": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/change-file-extension/-/change-file-extension-0.1.1.tgz", + "integrity": "sha512-lB0j9teu8JtDPDHRfU8pNH33w4wMu5bOaKoT4PxH+AKugBrIfpiJMTTKIm0TErNeJPkeQEgvH31YpccTwOKPRg==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", "license": "MIT" }, + "node_modules/clean-css": { + "version": "3.4.28", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz", + "integrity": "sha512-aTWyttSdI2mYi07kWqHi24NUU9YlELFKGOAgFzZjDN1064DMAOy2FBuoyGmkKRlXkbpXd0EVHmiVkbKhKoirTw==", + "dependencies": { + "commander": "2.8.x", + "source-map": "0.4.x" + }, + "bin": { + "cleancss": "bin/cleancss" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha512-Y8nIfcb1s/7DcobUz1yOO1GSp7gyL+D9zLHDehT7iRESqGSxjJ448Sg7rvfgsRJCnKLdSl11uGf0s9X80cH0/A==", + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/cli-color": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.1.0.tgz", + "integrity": "sha512-SzsTUTopL62kJOMbLqBUkaLVbkyw0qKB3uMRFxgy9LrEQ5tdFO9dT8oUhqszpJB9FMpVTIQnZMjb6zn0abilvQ==", + "dependencies": { + "ansi-regex": "2", + "d": "^0.1.1", + "es5-ext": "^0.10.8", + "es6-iterator": "2", + "memoizee": "^0.3.9", + "timers-ext": "0.1" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", + "optional": true, + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==" + }, + "node_modules/cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dependencies": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2502,11 +4438,19 @@ "node": ">=6" } }, + "node_modules/color": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/color/-/color-0.8.0.tgz", + "integrity": "sha512-tKmPx2t+2N4pxZT+P4jXaT3qHMkYqE1ZHe5z6TpRVR/wSONGwHDracgkv//oRsFZ3T1QO6ZBxAxjpDIeNqEQyw==", + "dependencies": { + "color-convert": "^0.5.0", + "color-string": "^0.3.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -2519,21 +4463,106 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, + "node_modules/color-string": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", + "integrity": "sha512-sz29j1bmSDfoAxKIEU6zwoIZXN6BrFbAMIhfYCNyiZXBDuU/aiHlN84lp/xDzL2ubyFhLDobHIlU1X70XRrMDA==", + "dependencies": { + "color-name": "^1.0.0" + } + }, + "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==", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha512-RwBeO/B/vZR3dfKL1ye/vx8MHZ40ugzpyfeVG5GsiuGnrlMWe2o8wxBbLCpw9CsxV+wHuzYlCiWnybrIA0ling==" + }, + "node_modules/colornames": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-0.0.2.tgz", + "integrity": "sha512-aeaoTql364CeoC6VHeRJd8uUiOVZDDtCyTP2dwXPD3WIt8UuPcXzmBB5gEhLDLaJS3MW152O7DfYm1a2HQv11g==" + }, + "node_modules/colorspace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.0.1.tgz", + "integrity": "sha512-rCnzSo6lkArg8rgeLdPQgxC5avqkyFGSpg3Roqn+rGRZfaHSBKgeDMr1YJZ9XTNZAeVoR4KxLjq9SUQ6hMvFlQ==", + "dependencies": { + "color": "0.8.x", + "text-hex": "0.0.x" + } + }, + "node_modules/commander": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "integrity": "sha512-+pJLBFVk+9ZZdlAOB5WuIElVPPth47hILFkmGym57aq8kwxsowvByvB0DHs1vQAhyMZzdcpTtF0VDKGkSDR4ZQ==", + "dependencies": { + "graceful-readlink": ">= 1.0.0" + }, + "engines": { + "node": ">= 0.6.x" + } + }, "node_modules/concat-map": { "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/concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "optional": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "optional": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -2545,6 +4574,57 @@ "node": ">=18" } }, + "node_modules/copy-props": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-4.0.0.tgz", + "integrity": "sha512-bVWtw1wQLzzKiYROtvNlbJgxgBYt2bMJpkCbKmXM3xyijvcjjWXEk5nyrrT3bgJ7ODb19ZohE2T0Y3FgNPyoTw==", + "dependencies": { + "each-props": "^3.0.0", + "is-plain-object": "^5.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "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==" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/crelt": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", @@ -2555,7 +4635,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2566,12 +4646,143 @@ "node": ">= 8" } }, + "node_modules/css-mediaquery": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", + "integrity": "sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==", + "license": "BSD" + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "optional": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-select/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "optional": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/css-select/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "optional": true + }, + "node_modules/css-select/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "optional": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/css-select/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "optional": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/css-select/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "optional": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "optional": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "optional": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "optional": true, + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/d": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz", + "integrity": "sha512-0SdM9V9pd/OXJHoWmTfNPTAeD+lw6ZqHg+isPyBFuJsZLSE0Ygg1cYZ/0l6DrKQXMOqGOu1oWupMoOfoRfMZrQ==", + "dependencies": { + "es5-ext": "~0.10.2" + } + }, "node_modules/d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", @@ -2693,11 +4904,18 @@ "node": ">=12" } }, + "node_modules/dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha512-GODcnWq3YGoTnygPfi02ygEiRxqUxpJwuRHjdhJYuxpcZmDq4rjBiXYmbCCzStxo176ixfLT6i4NPwQooRySnw==", + "engines": { + "node": "*" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2711,12 +4929,215 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/decimal.js-light": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", "license": "MIT" }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "optional": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "optional": true, + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "optional": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "optional": true, + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "optional": true, + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "optional": true, + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "optional": true, + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip/node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "optional": true, + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2724,6 +5145,23 @@ "dev": true, "license": "MIT" }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -2733,6 +5171,33 @@ "node": ">=6" } }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/diagnostics": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.0.1.tgz", + "integrity": "sha512-CRx2wYrfE/5+CpLdQY0Oat5A14C/ntU7BCVeczr4S8WtCDAkhiNAgf7sDy19eIg2byEEJ8UIOPo8frUdQdO/0Q==", + "dependencies": { + "colorspace": "1.0.x", + "enabled": "1.0.x", + "kuler": "0.0.x" + } + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -2743,6 +5208,191 @@ "csstype": "^3.0.2" } }, + "node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/dot-prop": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-8.0.2.tgz", + "integrity": "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ==", + "dependencies": { + "type-fest": "^3.8.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/download": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/download/-/download-6.2.5.tgz", + "integrity": "sha512-DpO9K1sXAST8Cpzb7kmEhogJxymyVUd5qz/vCOSyvwtp2Klj2XcDt5YUuasgxka44SxF0q5RriKIwJmQHG2AuA==", + "optional": true, + "dependencies": { + "caw": "^2.0.0", + "content-disposition": "^0.5.2", + "decompress": "^4.0.0", + "ext-name": "^5.0.0", + "file-type": "5.2.0", + "filenamify": "^2.0.0", + "get-stream": "^3.0.0", + "got": "^7.0.0", + "make-dir": "^1.0.0", + "p-event": "^1.0.0", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha512-+AWBwjGadtksxjOQSFDhPNQbed7icNXApT4+2BNpsXzcCBiInq2H9XW0O8sfHFaPmnQRs7cg/P0fAr2IWQSW0g==", + "dependencies": { + "readable-stream": "~1.1.9" + } + }, + "node_modules/duplexer2/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/duplexer3": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", + "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", + "optional": true + }, + "node_modules/each-props": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-3.0.0.tgz", + "integrity": "sha512-IYf1hpuWrdzse/s/YJOrFmU15lyhSzxelNVAHTEG3DtP4QsLTWZUzcUL3HMXmKQxXpa4EIrBPpwRgj0aehdvAw==", + "dependencies": { + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/easy-transform-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/easy-transform-stream/-/easy-transform-stream-1.0.1.tgz", + "integrity": "sha512-ktkaa6XR7COAR3oj02CF3IOgz2m1hCaY3SfzvKT4Svt2MhHw9XCt+ncJNWfe2TGz31iqzNGZ8spdKQflj+Rlog==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.209", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.209.tgz", @@ -2750,6 +5400,55 @@ "dev": true, "license": "ISC" }, + "node_modules/emailjs-com": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/emailjs-com/-/emailjs-com-3.2.0.tgz", + "integrity": "sha512-Prbz3E1usiAwGjMNYRv6EsJ5c373cX7/AGnZQwOfrpNJrygQJ15+E9OOq4pU8yC977Z5xMetRfc3WmDX6RcjAA==", + "deprecated": "The SDK name changed to @emailjs/browser", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/emits": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emits/-/emits-3.0.0.tgz", + "integrity": "sha512-WJSCMaN/qjIkzWy5Ayu0MDENFltcu4zTPPnWqdFPOVBtsENVTN+A3d76G61yuiVALsMK+76MejdPrwmccv/wag==" + }, + "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==" + }, + "node_modules/enabled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "integrity": "sha512-nnzgVSpB35qKrUN8358SjO1bYAmxoThECTWw9s3J0x5G8A9hokKHVDFzBjVpCoSryo6MhN8woVyascN5jheaNA==", + "dependencies": { + "env-variable": "0.0.x" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -2762,6 +5461,61 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-variable": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz", + "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==" + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "optional": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-toolkit": { "version": "1.39.10", "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.10.tgz", @@ -2772,11 +5526,101 @@ "benchmarks" ] }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-iterator/node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-symbol/node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-0.1.4.tgz", + "integrity": "sha512-P+N5Cd2TXeb7G59euFiM7snORspgbInS29Nbf3KNO2JQp/DyhvMCDWd58nsVAXwYJ6W3Bx7qDdy6QQ3PCJ7jKQ==", + "dependencies": { + "d": "~0.1.1", + "es5-ext": "~0.10.6", + "es6-iterator": "~0.1.3", + "es6-symbol": "~2.0.1" + } + }, + "node_modules/es6-weak-map/node_modules/es6-iterator": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz", + "integrity": "sha512-6TOmbFM6OPWkTe+bQ3ZuUkvqcWUjAnYjKUCLdbvRsAUz2Pr+fYIibwNXNkLNtIK9PPFbNMZZddaRNkyJhlGJhA==", + "dependencies": { + "d": "~0.1.1", + "es5-ext": "~0.10.5", + "es6-symbol": "~2.0.1" + } + }, + "node_modules/es6-weak-map/node_modules/es6-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz", + "integrity": "sha512-wjobO4zO8726HVU7mI2OA/B6QszqwHJuKab7gKHVx+uRfVVYGcWJkCIFxV2Madqb9/RUSrhJ/r6hPfG7FsWtow==", + "dependencies": { + "d": "~0.1.1", + "es5-ext": "~0.10.5" + } + }, "node_modules/esbuild": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -2818,7 +5662,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2950,6 +5793,32 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esniff/node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", @@ -3014,18 +5883,409 @@ "node": ">=0.10.0" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/event-emitter/node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/exec-buffer": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", + "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==", + "optional": true, + "dependencies": { + "execa": "^0.7.0", + "p-finally": "^1.0.0", + "pify": "^3.0.0", + "rimraf": "^2.5.4", + "tempfile": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/exec-buffer/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "optional": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/exec-buffer/node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", + "optional": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/exec-buffer/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/exec-buffer/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/exec-buffer/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "optional": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/exec-buffer/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/exec-buffer/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "optional": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/exec-buffer/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/exec-buffer/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/exec-buffer/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "optional": true + }, + "node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "optional": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "optional": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "optional": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "optional": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "optional": true, + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/executable/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "optional": true, + "dependencies": { + "mime-db": "^1.28.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "optional": true, + "dependencies": { + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dependencies": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-equals": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.2.tgz", + "integrity": "sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -3040,11 +6300,53 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-xml-parser": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", + "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "optional": true, + "dependencies": { + "strnum": "^1.1.1" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "optional": true, + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -3071,6 +6373,63 @@ "node": ">=16.0.0" } }, + "node_modules/file-type": { + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.6.0.tgz", + "integrity": "sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==", + "dependencies": { + "get-stream": "^9.0.1", + "strtok3": "^9.0.1", + "token-types": "^6.0.0", + "uint8array-extras": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", + "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", + "optional": true, + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3088,6 +6447,55 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-versions": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", + "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", + "optional": true, + "dependencies": { + "semver-regex": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/findup-sync": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", + "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.3", + "micromatch": "^4.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/fined": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-2.0.0.tgz", + "integrity": "sha512-OFRzsL6ZMHz5s0JrsEr+TpdGNCtrVtnuG3x1yzGNiQHT0yaDnXAj8V/lWcpJVrnoDpcwXcASxAZYbuXda2Y82A==", + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0", + "object.pick": "^1.3.0", + "parse-filepath": "^1.0.2" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/flagged-respawn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-2.0.0.tgz", + "integrity": "sha512-Gq/a6YCi8zexmGHMuJwahTGzXlAZAOsbCVKduWXC6TlLCjjFRlExMJc4GC2NYPYZ0r/brw9P7CpRgQmlPVeOoA==", + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -3109,11 +6517,78 @@ "dev": true, "license": "ISC" }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "optional": true, + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "optional": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "optional": true + }, + "node_modules/fs-mkdirp-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", + "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", + "dependencies": { + "graceful-fs": "^4.2.8", + "streamx": "^2.12.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "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==", + "optional": true + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3124,6 +6599,25 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function-timeout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", + "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3134,11 +6628,183 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "optional": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-proxy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", + "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", + "optional": true, + "dependencies": { + "npm-conf": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gifsicle": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gifsicle/-/gifsicle-5.3.0.tgz", + "integrity": "sha512-FJTpgdj1Ow/FITB7SVza5HlzXa+/lqEY0tHQazAJbuAdvyJtkH4wIdsR2K414oaTwRXHFLLF+tYbipj+OpYg+Q==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "bin-build": "^3.0.0", + "bin-wrapper": "^4.0.0", + "execa": "^5.0.0" + }, + "bin": { + "gifsicle": "cli.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/imagemin/gisicle-bin?sponsor=1" + } + }, + "node_modules/gifsicle/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "optional": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/gifsicle/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gifsicle/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gifsicle/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "optional": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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", + "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/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -3147,6 +6813,75 @@ "node": ">=10.13.0" } }, + "node_modules/glob-stream": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.3.tgz", + "integrity": "sha512-fqZVj22LtFJkHODT+M4N1RJQ3TjnnQhfE9GwZI8qXscYarnhpip70poMldRnP8ipQ/w0B621kOhfc53/J9bd/A==", + "dependencies": { + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "is-negated-glob": "^1.0.0", + "normalize-path": "^3.0.0", + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-watcher": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-6.0.0.tgz", + "integrity": "sha512-wGM28Ehmcnk2NqRORXFOTOR064L4imSw3EeOqU5bIwUf62eXGwg89WivH6VMahL8zlQHeodzvHpXplrqzrz3Nw==", + "dependencies": { + "async-done": "^2.0.0", + "chokidar": "^3.5.3" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/globals": { "version": "16.3.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", @@ -3160,16 +6895,716 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/glogg": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-2.2.0.tgz", + "integrity": "sha512-eWv1ds/zAlz+M1ioHsyKJomfY7jbDDPpwSkv14KQj89bycx1nvK5/2Cj/T9g7kzJcX5Bc7Yv22FjfBZS/jl94A==", + "dependencies": { + "sparkles": "^2.1.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", + "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", + "optional": true, + "dependencies": { + "decompress-response": "^3.2.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-plain-obj": "^1.1.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "p-cancelable": "^0.3.0", + "p-timeout": "^1.1.1", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "url-parse-lax": "^1.0.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/got/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/got/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==" + }, + "node_modules/gulp": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-5.0.1.tgz", + "integrity": "sha512-PErok3DZSA5WGMd6XXV3IRNO0mlB+wW3OzhFJLEec1jSERg2j1bxJ6e5Fh6N6fn3FH2T9AP4UYNb/pYlADB9sA==", + "dependencies": { + "glob-watcher": "^6.0.0", + "gulp-cli": "^3.1.0", + "undertaker": "^2.0.0", + "vinyl-fs": "^4.0.2" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-cli": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-3.1.0.tgz", + "integrity": "sha512-zZzwlmEsTfXcxRKiCHsdyjZZnFvXWM4v1NqBJSYbuApkvVKivjcmOS2qruAJ+PkEHLFavcDKH40DPc1+t12a9Q==", + "dependencies": { + "@gulpjs/messages": "^1.1.0", + "chalk": "^4.1.2", + "copy-props": "^4.0.0", + "gulplog": "^2.2.0", + "interpret": "^3.1.1", + "liftoff": "^5.0.1", + "mute-stdout": "^2.0.0", + "replace-homedir": "^2.0.0", + "semver-greatest-satisfied-range": "^2.0.0", + "string-width": "^4.2.3", + "v8flags": "^4.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-concat": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", + "integrity": "sha512-a2scActrQrDBpBbR3WUZGyGS1JEPLg5PZJdIa7/Bi3GuKAmPYDK6SFhy/NZq5R8KsKKFvtfR0fakbUCcKGCCjg==", + "dependencies": { + "concat-with-sourcemaps": "^1.0.0", + "through2": "^2.0.0", + "vinyl": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-imagemin": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/gulp-imagemin/-/gulp-imagemin-9.1.0.tgz", + "integrity": "sha512-PmzTWoNrVMYVN4ObRdHyt6oer4mqxV53IbCDi3Q8EHeDZW0OzAuh6RlOtpd/R7PFmbDUk64q5P+L04fD9I5cVA==", + "dependencies": { + "chalk": "^5.3.0", + "gulp-plugin-extras": "^1.0.0", + "imagemin": "^9.0.0", + "plur": "^5.1.0", + "pretty-bytes": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + }, + "optionalDependencies": { + "imagemin-gifsicle": "^7.0.0", + "imagemin-mozjpeg": "^10.0.0", + "imagemin-optipng": "^8.0.0", + "imagemin-svgo": "^10.0.1" + }, + "peerDependencies": { + "gulp": ">=4" + }, + "peerDependenciesMeta": { + "gulp": { + "optional": true + } + } + }, + "node_modules/gulp-imagemin/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/gulp-minify-css": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/gulp-minify-css/-/gulp-minify-css-1.2.4.tgz", + "integrity": "sha512-byBqFQM/HrZoUVYihu/03iYH4m7U5TjSGhr6/7JvpMHh9+woewsCtEp6Noif2VXB+idDoM4ECd9sw+St+KFqsg==", + "deprecated": "Please use gulp-clean-css", + "dependencies": { + "clean-css": "^3.3.3", + "gulp-util": "^3.0.5", + "object-assign": "^4.0.1", + "readable-stream": "^2.0.0", + "vinyl-bufferstream": "^1.0.1", + "vinyl-sourcemaps-apply": "^0.2.0" + } + }, + "node_modules/gulp-minify-html": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/gulp-minify-html/-/gulp-minify-html-1.0.6.tgz", + "integrity": "sha512-toazuTI8XG4BNOY7L/4Da258tgWbtFlfPAyZi0QwrDc+ba11LMMT8dLhGo48SMAil6Wu8PuTcICfkJ6d/dCjLQ==", + "dependencies": { + "gulp-util": "^3.0.3", + "minimize": "^1.5.0", + "through2": "^0.6.1" + } + }, + "node_modules/gulp-minify-html/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/gulp-minify-html/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/gulp-minify-html/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/gulp-minify-html/node_modules/through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg==", + "dependencies": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/gulp-plugin-extras": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gulp-plugin-extras/-/gulp-plugin-extras-1.1.0.tgz", + "integrity": "sha512-T0AXOEVoKYzLIBlwEZ7LtAx2w4ExIozIoxVeYEVLFbdxI7i0sWvFDq0F8mm47djixDF3vAqDPoyGwh3Sg/PWtQ==", + "dependencies": { + "@types/vinyl": "^2.0.12", + "chalk": "^5.3.0", + "easy-transform-stream": "^1.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gulp-plugin-extras/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/gulp-rename": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.1.0.tgz", + "integrity": "sha512-dGuzuH8jQGqCMqC544IEPhs5+O2l+IkdoSZsgd4kY97M1CxQeI3qrmweQBIrxLBbjbe/8uEWK8HHcNBc3OCy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-uglify": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-3.0.2.tgz", + "integrity": "sha512-gk1dhB74AkV2kzqPMQBLA3jPoIAPd/nlNzP2XMDSG8XZrqnlCiDGAqC+rZOumzFvB5zOphlFh6yr3lgcAb/OOg==", + "dependencies": { + "array-each": "^1.0.1", + "extend-shallow": "^3.0.2", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "isobject": "^3.0.1", + "make-error-cause": "^1.1.1", + "safe-buffer": "^5.1.2", + "through2": "^2.0.0", + "uglify-js": "^3.0.5", + "vinyl-sourcemaps-apply": "^0.2.0" + } + }, + "node_modules/gulp-uglify/node_modules/glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-uglify/node_modules/gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha512-hm6N8nrm3Y08jXie48jsC55eCZz9mnb4OirAStEk2deqeyhXU3C1otDVh+ccttMuc1sBi6RX6ZJ720hs9RCvgw==", + "dependencies": { + "glogg": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-uglify/node_modules/sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha512-q5oWPc12lwSFS9h/4VIjG+1NuNDlJ48ywV2JKItY4Ycc/n1fXJeYPVQsfu5ZrhQi7FGSDBalwUCLar/GyHXKGw==", + "deprecated": "gulp-util is deprecated - replace it, following the guidelines at https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5", + "dependencies": { + "array-differ": "^1.0.0", + "array-uniq": "^1.0.2", + "beeper": "^1.0.0", + "chalk": "^1.0.0", + "dateformat": "^2.0.0", + "fancy-log": "^1.1.0", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.0.0", + "minimist": "^1.1.0", + "multipipe": "^0.1.2", + "object-assign": "^3.0.0", + "replace-ext": "0.0.1", + "through2": "^2.0.0", + "vinyl": "^0.5.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/gulp-util/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/gulp-util/node_modules/clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha512-dhUqc57gSMCo6TX85FLfe51eC/s+Im2MLkAgJwfaRRexR2tA4dd3eLEW4L6efzHc2iNorrRRXITifnDLlRrhaA==" + }, + "node_modules/gulp-util/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/gulp-util/node_modules/glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-util/node_modules/gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha512-hm6N8nrm3Y08jXie48jsC55eCZz9mnb4OirAStEk2deqeyhXU3C1otDVh+ccttMuc1sBi6RX6ZJ720hs9RCvgw==", + "dependencies": { + "glogg": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-util/node_modules/object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-util/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/gulp-util/node_modules/vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha512-P5zdf3WB9uzr7IFoVQ2wZTmUwHL8cMZWJGzLBNCHNZ3NB6HTMsYABtt7z8tAGIINLXyAob9B9a1yzVGMFOYKEA==", + "dependencies": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + }, + "engines": { + "node": ">= 0.9" + } + }, + "node_modules/gulplog": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-2.2.0.tgz", + "integrity": "sha512-V2FaKiOhpR3DRXZuYdRLn/qiY0yI5XmqbTKrYbdemJ+xOh2d2MOweI/XFgMzd/9+1twdvMwllnZbWZNJ+BOm4A==", + "dependencies": { + "glogg": "^2.2.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha512-+F4GzLjwHNNDEAJW2DC1xXfEoPkRDmUdJ7CBYw4MpqtDwOnqdImJl7GWlpqx+Wko6//J8uKTnIe4wZSv7yCqmw==", + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/has-gulplog/node_modules/sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbol-support-x": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-to-string-tag-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", + "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "optional": true, + "dependencies": { + "has-symbol-support-x": "^1.4.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "optional": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/htmlparser2": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha512-RSOwLNCnCLDRB9XpSfCzsLzzX8COezhJ3D4kRBNWh0NC/facp1hAMmM8zD7kC01My8vD6lGEbPMlbRW/EwGK5w==", + "dependencies": { + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "node_modules/http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "optional": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "optional": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", + "license": "BSD-3-Clause" + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/identifier-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/identifier-regex/-/identifier-regex-1.0.1.tgz", + "integrity": "sha512-ZrYyM0sozNPZlvBvE7Oq9Bn44n0qKGrYu5sQ0JzMUnjIhpgWYE2JB6aBoFwEYdPjqj7jPyxXTMJiHDOxDfd8yw==", + "dependencies": { + "reserved-identifiers": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3180,6 +7615,226 @@ "node": ">= 4" } }, + "node_modules/image-dimensions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/image-dimensions/-/image-dimensions-2.5.0.tgz", + "integrity": "sha512-CKZPHjAEtSg9lBV9eER0bhNn/yrY7cFEQEhkwjLhqLY+Na8lcP1pEyWsaGMGc8t2qbKWA/tuqbhFQpOKGN72Yw==", + "bin": { + "image-dimensions": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-9.0.1.tgz", + "integrity": "sha512-UoHOfynN8QeqRoUGunn6ilMnLpJ+utbmleP2ufcFqaGal8mY/PeOpV43N31uqtb+CBMFqQ7hxgKzIaAAnmcrdA==", + "dependencies": { + "change-file-extension": "^0.1.1", + "environment": "^1.0.0", + "file-type": "^19.0.0", + "globby": "^14.0.1", + "image-dimensions": "^2.3.0", + "junk": "^4.0.1", + "ow": "^2.0.0", + "p-pipe": "^4.0.0", + "slash": "^5.1.0", + "uint8array-extras": "^1.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-gifsicle": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/imagemin-gifsicle/-/imagemin-gifsicle-7.0.0.tgz", + "integrity": "sha512-LaP38xhxAwS3W8PFh4y5iQ6feoTSF+dTAXFRUEYQWYst6Xd+9L/iPk34QGgK/VO/objmIlmq9TStGfVY2IcHIA==", + "optional": true, + "dependencies": { + "execa": "^1.0.0", + "gifsicle": "^5.0.0", + "is-gif": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/imagemin/imagemin-gifsicle?sponsor=1" + } + }, + "node_modules/imagemin-mozjpeg": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/imagemin-mozjpeg/-/imagemin-mozjpeg-10.0.0.tgz", + "integrity": "sha512-DK85QNOjS3/GzWYfNB3CACMZD10sIQgFDv1+WTOnZljgltQTEyATjdyUVyjKu5q4sCESQdwvwq7WEZzJ5fFjlg==", + "optional": true, + "dependencies": { + "execa": "^6.0.0", + "is-jpg": "^3.0.0", + "mozjpeg": "^8.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/execa": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", + "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", + "optional": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^3.0.1", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/human-signals": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", + "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", + "optional": true, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "optional": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "optional": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "optional": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-mozjpeg/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imagemin-optipng": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/imagemin-optipng/-/imagemin-optipng-8.0.0.tgz", + "integrity": "sha512-CUGfhfwqlPjAC0rm8Fy+R2DJDBGjzy2SkfyT09L8rasnF9jSoHFqJ1xxSZWK6HVPZBMhGPMxCTL70OgTHlLF5A==", + "optional": true, + "dependencies": { + "exec-buffer": "^3.0.0", + "is-png": "^2.0.0", + "optipng-bin": "^7.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/imagemin-svgo": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/imagemin-svgo/-/imagemin-svgo-10.0.1.tgz", + "integrity": "sha512-v27/UTGkb3vrm5jvjsMGQ2oxaDfSOTBfJOgmFO2fYepx05bY1IqWCK13aDytVR+l9w9eOlq0NMCLbxJlghYb2g==", + "optional": true, + "dependencies": { + "is-svg": "^4.3.1", + "svgo": "^2.5.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sindresorhus/imagemin-svgo?sponsor=1" + } + }, "node_modules/immer": { "version": "10.1.3", "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.3.tgz", @@ -3194,7 +7849,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -3207,6 +7861,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-lazy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", + "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3217,6 +7880,27 @@ "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.", + "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==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -3226,6 +7910,27 @@ "node": ">=12" } }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==", + "optional": true, + "dependencies": { + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -3235,21 +7940,133 @@ "loose-envify": "^1.0.0" } }, + "node_modules/irregular-plurals": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", + "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-gif": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-gif/-/is-gif-3.0.0.tgz", + "integrity": "sha512-IqJ/jlbw5WJSNfwQ/lHEDXF8rxhRgF6ythk2oiEvhpG29F704eX9NO6TvPfMiq9DrbwgcEDnETYNcZDPewQoVw==", + "optional": true, + "dependencies": { + "file-type": "^10.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-gif/node_modules/file-type": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.11.0.tgz", + "integrity": "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -3258,13 +8075,226 @@ "node": ">=0.10.0" } }, + "node_modules/is-identifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-identifier/-/is-identifier-1.0.1.tgz", + "integrity": "sha512-HQ5v4rEJ7REUV54bCd2l5FaD299SGDEn2UPoVXaTHAyGviLq2menVUD2udi3trQ32uvB6LdAh/0ck2EuizrtpA==", + "dependencies": { + "identifier-regex": "^1.0.0", + "super-regex": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-jpg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-jpg/-/is-jpg-3.0.0.tgz", + "integrity": "sha512-Vcd67KWHZblEKEBrtP25qLZ8wN9ICoAhl1pKUqD7SM7hf2qtuRl7loDgP5Zigh2oN/+7uj+KVyC0eRJvgOEFeQ==", + "optional": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", + "optional": true + }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-png": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-png/-/is-png-2.0.0.tgz", + "integrity": "sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-svg": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-4.4.0.tgz", + "integrity": "sha512-v+AgVwiK5DsGtT9ng+m4mClp6zDAmwrW8nZi6Gg15qzvBnRWWdfWA1TGaXyCDnWq5g5asofIgMVl3PjKxvk1ug==", + "optional": true, + "dependencies": { + "fast-xml-parser": "^4.1.3" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "optional": true, + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "optional": true, + "dependencies": { + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-base64": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz", + "integrity": "sha512-f+5mYh8iF7FlF7zgmj/yqvvYQUHI0kAxGiLjIfNxZzqJ7RQNc4sjgp8crVJw0Kzv2O6aFGZWgMTnO71I9utHSg==", + "dev": true, + "license": "BSD" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3288,7 +8318,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -3304,6 +8333,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3331,6 +8366,17 @@ "node": ">=6" } }, + "node_modules/junk": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz", + "integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3341,6 +8387,30 @@ "json-buffer": "3.0.1" } }, + "node_modules/kuler": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-0.0.0.tgz", + "integrity": "sha512-5h7OEDPSHedoxB6alJXF4FtFB95QA2OTXGCFaLCutHdkh0VrcSSy/OwH9UHtYqsG2KTrdN7gVEc9KgCBNah/yA==", + "dependencies": { + "colornames": "0.0.2" + } + }, + "node_modules/last-run": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-2.0.0.tgz", + "integrity": "sha512-j+y6WhTLN4Itnf9j5ZQos1BGPCS8DAwmgMroR3OzfxAsBxam0hMw7J8M3KqZl0pLQJ1jNnwIexg5DYpC/ctwEQ==", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/lead": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", + "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3355,6 +8425,257 @@ "node": ">= 0.8.0" } }, + "node_modules/liftoff": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-5.0.1.tgz", + "integrity": "sha512-wwLXMbuxSF8gMvubFcFRp56lkFV69twvbU5vDPbaw+Q+/rF8j0HKjGbIdlSi+LuJm9jf7k9PB+nTxnsLMPcv2Q==", + "dependencies": { + "extend": "^3.0.2", + "findup-sync": "^5.0.0", + "fined": "^2.0.0", + "flagged-respawn": "^2.0.0", + "is-plain-object": "^5.0.0", + "rechoir": "^0.8.0", + "resolve": "^1.20.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", @@ -3386,6 +8707,79 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha512-rFR6Vpm4HeCK1WPGvjZSJ+7yik8d8PVUdCJx5rT2pogG4Ve/2ZS7kfmO5l5T2o5V2mqlNIfSF5MZlr1+xOoYQQ==" + }, + "node_modules/lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha512-mTzAr1aNAv/i7W43vOR/uD/aJ4ngbtsRaCubp2BfZhlGU/eORUjg/7F6X0orNMdv33JOrdgGybtvMN/po3EWrA==" + }, + "node_modules/lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha512-H94wl5P13uEqlCg7OcNNhMQ8KvWSIyqXzOPusRgHC9DK3o54P6P3xtbXlVbRABG4q5gSmp7EDdJ0MSuW9HX6Mg==" + }, + "node_modules/lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA==" + }, + "node_modules/lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha512-De+ZbrMu6eThFti/CSzhRvTKMgQToLxbij58LMfM8JnYDNSOjkjTCIaa8ixglOeGh2nyPlakbt5bJWJ7gvpYlQ==" + }, + "node_modules/lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha512-Sjlavm5y+FUVIF3vF3B75GyXrzsfYV8Dlv3L4mEpuB9leg8N6yf/7rU06iLPx9fY0Mv3khVp9p7Dx0mGV6V5OQ==" + }, + "node_modules/lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha512-OrPwdDc65iJiBeUe5n/LIjd7Viy99bKwDdk7Z5ljfZg0uFRFlfQaCy9tZ4YMAag9WAZmlVpe1iZrkIMMSMHD3w==" + }, + "node_modules/lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==" + }, + "node_modules/lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ==" + }, + "node_modules/lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha512-n1PZMXgaaDWZDSvuNZ/8XOcYO2hOKDqZel5adtR30VKQAtoWs/5AOeFA0vPV8moiPzlqe7F4cP2tzpFewQyelQ==", + "dependencies": { + "lodash._root": "^3.0.0" + } + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, + "node_modules/lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==" + }, + "node_modules/lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha512-CuBsapFjcubOGMn3VD+24HOAPxM79tH+V6ivJL3CHYjtrawauDJHUk//Yew9Hvc6e9rbCrURGk8z6PC+8WJBfQ==", + "dependencies": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3393,6 +8787,37 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha512-L4/arjjuq4noiUJpt3yS6KIKDtJwNe2fIYgMqyYYKoeIfV1iEqvPwhCx23o+R9dzouGihDAPN1dTIRWa7zk8tw==" + }, + "node_modules/lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha512-0B4Y53I0OgHUJkt+7RmlDFWKjVAI/YUpWNiL9GQz5ORDr4ttgfQGo+phBWKFLJbBdtOwgMuUkdOHOnPg45jKmQ==", + "deprecated": "This package is deprecated. Use https://socket.dev/npm/package/eta instead.", + "dependencies": { + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" + } + }, + "node_modules/lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha512-TcrlEr31tDYnWkHFWDCV3dHYroKEXpJZ2YJYvJdhN+y4AkWMDZ5I4I8XDtUKqSAyG81N7w+I1mFEJtcED+tGqQ==", + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3405,6 +8830,15 @@ "loose-envify": "cli.js" } }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3415,6 +8849,74 @@ "yallist": "^3.0.2" } }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "dependencies": { + "es5-ext": "~0.10.2" + } + }, + "node_modules/lucide-react": { + "version": "0.544.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.544.0.tgz", + "integrity": "sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "optional": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "node_modules/make-error-cause": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/make-error-cause/-/make-error-cause-1.2.2.tgz", + "integrity": "sha512-4TO2Y3HkBnis4c0dxhAgD/jprySYLACf7nwN6V0HAHDx59g12WlRpUmFy1bRHamjGUEEBrEvCq6SUpsEE2lhUg==", + "dependencies": { + "make-error": "^1.2.0" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/markdown-it": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", @@ -3432,17 +8934,130 @@ "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/matchmediaquery": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.4.2.tgz", + "integrity": "sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==", + "license": "MIT", + "dependencies": { + "css-mediaquery": "^0.1.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "optional": true + }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "license": "MIT" }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/memoizee": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.3.10.tgz", + "integrity": "sha512-LLzVUuWwGBKK188spgOK/ukrp5zvd9JGsiLDH41pH9vt5jvhZfsu5pxDuAnYAMG8YEGce72KO07sSBy9KkvOfw==", + "dependencies": { + "d": "~0.1.1", + "es5-ext": "~0.10.11", + "es6-weak-map": "~0.1.4", + "event-emitter": "~0.3.4", + "lru-queue": "0.1", + "next-tick": "~0.2.2", + "timers-ext": "0.1" + } + }, + "node_modules/memoizee/node_modules/next-tick": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-0.2.2.tgz", + "integrity": "sha512-f7h4svPtl+QidoBv4taKXUjJ70G2asaZ8G28nS0OkqaalX8dwwrtWtyxEDPK62AC00ur/+/E0pUwBwY5EPn15Q==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "optional": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, "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" @@ -3451,18 +9066,95 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimize": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/minimize/-/minimize-1.8.1.tgz", + "integrity": "sha512-vMXaO5/HgxKi06udiQ4MibwOb0mwxzcpX4DDf6G9k2h6fKNoY1xt8Wrzp82qBm4oZ5aQRP2ry1NzbwEuWBx9Ig==", + "dependencies": { + "argh": "~0.1.4", + "async": "~1.5.2", + "cli-color": "~1.1.0", + "diagnostics": "~1.0.1", + "emits": "~3.0.0", + "htmlparser2": "~3.9.0", + "node-uuid": "~1.4.7" + }, + "bin": { + "minimize": "bin/minimize" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mozjpeg": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/mozjpeg/-/mozjpeg-8.0.0.tgz", + "integrity": "sha512-Ca2Yhah9hG0Iutgsn8MOrAl37P9ThnKsJatjXoWdUO+8X8GeG/6ahvHZrTyqvbs6leMww1SauWUCao/L9qBuFQ==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "bin-build": "^3.0.0", + "bin-wrapper": "^4.0.0" + }, + "bin": { + "mozjpeg": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "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, "license": "MIT" }, + "node_modules/multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha512-7ZxrUybYv9NonoXgwoOqtStIu18D1c3eFZj27hqgf5kBrBF8Q+tE8V0MW8dKM5QLkQPh1JhhbKgHLY9kifov4Q==", + "dependencies": { + "duplexer2": "0.0.2" + } + }, + "node_modules/mute-stdout": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-2.0.0.tgz", + "integrity": "sha512-32GSKM3Wyc8dg/p39lWPKYu8zci9mJFzV1Np9Of0ZEpe6Fhssn/FbI7ywAMd40uX+p3ZKh3T5EeCFv81qS3HmQ==", + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -3484,6 +9176,17 @@ "dev": true, "license": "MIT" }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "optional": true + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -3491,6 +9194,131 @@ "dev": true, "license": "MIT" }, + "node_modules/node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha512-TkCET/3rr9mUuRp+CpO7qfgT++aAxfDRaalQhwPFzI9BY/2rCDn6OfpZOVggi1AXfTPpfkTrg5f5WQx5G1uLxA==", + "deprecated": "Use uuid module instead", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", + "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "optional": true, + "dependencies": { + "prepend-http": "^2.0.0", + "query-string": "^5.0.1", + "sort-keys": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-url/node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-url/node_modules/sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==", + "optional": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", + "dependencies": { + "once": "^1.4.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/npm-conf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", + "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", + "optional": true, + "dependencies": { + "config-chain": "^1.1.11", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-conf/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "optional": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "optional": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/num2fraction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.1.0.tgz", + "integrity": "sha512-xdb2PeSY7+yPt67MgXHQmK5BCMJr/v5yC90mPDlaGceMaPaS72N435F6dLFsRoiLjoJ/FPb7X6PbAU/DWvdaqw==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3500,6 +9328,54 @@ "node": ">=0.10.0" } }, + "node_modules/object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "optional": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3518,12 +9394,110 @@ "node": ">= 0.8.0" } }, + "node_modules/optipng-bin": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/optipng-bin/-/optipng-bin-7.0.1.tgz", + "integrity": "sha512-W99mpdW7Nt2PpFiaO+74pkht7KEqkXkeRomdWXfEz3SALZ6hns81y/pm1dsGZ6ItUIfchiNIP6ORDr1zETU1jA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "bin-build": "^3.0.0", + "bin-wrapper": "^4.0.0" + }, + "bin": { + "optipng": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/orderedmap": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", "license": "MIT" }, + "node_modules/os-filter-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz", + "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==", + "optional": true, + "dependencies": { + "arch": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ow/-/ow-2.0.0.tgz", + "integrity": "sha512-ESUigmGrdhUZ2nQSFNkeKSl6ZRPupXzprMs3yF9DYlNVpJ8XAjM/fI9RUZxA7PI1K9HQDCCvBo1jr/GEIo9joQ==", + "dependencies": { + "@sindresorhus/is": "^6.3.0", + "callsites": "^4.1.0", + "dot-prop": "^8.0.2", + "environment": "^1.0.0", + "fast-equals": "^5.0.1", + "is-identifier": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ow/node_modules/callsites": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.2.0.tgz", + "integrity": "sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", + "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-event": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-1.3.0.tgz", + "integrity": "sha512-hV1zbA7gwqPVFcapfeATaNjQ3J0NuzorHPyG8GPL9g/Y/TplWVBVoCKCXL6Ej2zscrCEv195QNWJXuBH6XZuzA==", + "optional": true, + "dependencies": { + "p-timeout": "^1.1.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-is-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==", + "optional": true, + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3556,11 +9530,54 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz", + "integrity": "sha512-4k9LlvY6Bo/1FcIdV33wqZQES0Py+iKISU9Uc8p8AjWoZPnFKMpVIVD3s0EYn4jzLh1I+WeUZkJ0Yoa4Qfw3Kg==", + "optional": true, + "dependencies": { + "p-reduce": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-pipe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-pipe/-/p-pipe-4.0.0.tgz", + "integrity": "sha512-HkPfFklpZQPUKBFXzKFB6ihLriIHxnmuQdK9WmLDwe4hf2PdhhfWT/FJa+pc3bA1ywvKXtedxIRmd4Y7BTXE4w==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha512-3Tx1T3oM1xO/Y8Gj0sWyE78EIJZ+t+aEmXUdvQgvGmSMri7aPTHoovbXEreWKkL5j21Er60XAWLTzKbAKYOujQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-timeout": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", + "integrity": "sha512-gb0ryzr+K2qFqFv6qi3khoeqMZF/+ajxQipEF6NteZVnvz9tzdsfAVj3lYtn1gAXvH5lfLwfxEII799gt/mRIA==", + "optional": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -3569,6 +9586,53 @@ "node": ">=6" } }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3579,28 +9643,88 @@ "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==", + "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", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/peek-readable": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz", + "integrity": "sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "optional": true + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3609,11 +9733,63 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "optional": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/plur": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz", + "integrity": "sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==", + "dependencies": { + "irregular-plurals": "^3.3.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -3658,6 +9834,31 @@ "node": ">= 0.8.0" } }, + "node_modules/prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -3889,6 +10090,28 @@ "prosemirror-transform": "^1.1.0" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "optional": true + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "optional": true + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3908,6 +10131,39 @@ "node": ">=6" } }, + "node_modules/query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "optional": true, + "dependencies": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/react": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", @@ -3917,6 +10173,19 @@ "node": ">=0.10.0" } }, + "node_modules/react-async-script": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/react-async-script/-/react-async-script-1.2.0.tgz", + "integrity": "sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q==", + "license": "MIT", + "dependencies": { + "hoist-non-react-statics": "^3.3.0", + "prop-types": "^15.5.0" + }, + "peerDependencies": { + "react": ">=16.4.1" + } + }, "node_modules/react-bootstrap": { "version": "2.10.10", "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz", @@ -3948,6 +10217,16 @@ } } }, + "node_modules/react-chartjs-2": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.1.tgz", + "integrity": "sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-dom": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", @@ -3960,6 +10239,19 @@ "react": "^19.1.1" } }, + "node_modules/react-google-recaptcha": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz", + "integrity": "sha512-cYW2/DWas8nEKZGD7SCu9BSuVz8iOcOLHChHyi7upUuVhkpkhYG/6N3KDiTQ3XAiZ2UAZkfvYKMfAHOzBOcGEg==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.5.0", + "react-async-script": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.4.1" + } + }, "node_modules/react-icons": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", @@ -3973,8 +10265,7 @@ "version": "19.1.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", @@ -4015,6 +10306,24 @@ "node": ">=0.10.0" } }, + "node_modules/react-responsive": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-10.0.1.tgz", + "integrity": "sha512-OM5/cRvbtUWEX8le8RCT8scA8y2OPtb0Q/IViEyCEM5FBN8lRrkUOZnu87I88A6njxDldvxG+rLBxWiA7/UM9g==", + "license": "MIT", + "dependencies": { + "hyphenate-style-name": "^1.0.0", + "matchmediaquery": "^0.4.2", + "prop-types": "^15.6.1", + "shallow-equal": "^3.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-router": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.2.tgz", @@ -4053,6 +10362,27 @@ "react-dom": ">=18" } }, + "node_modules/react-select": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz", + "integrity": "sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -4069,10 +10399,51 @@ "react-dom": ">=16.6.0" } }, + "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==", + "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/readable-stream/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==" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/recharts": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.1.2.tgz", - "integrity": "sha512-vhNbYwaxNbk/IATK0Ki29k3qvTkGqwvCgyQAQ9MavvvBwjvKnMTswdbklJpcOAoMPN/qxF3Lyqob0zO+ZXkZ4g==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.3.0.tgz", + "integrity": "sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==", "license": "MIT", "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", @@ -4096,6 +10467,17 @@ "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", @@ -4111,27 +10493,138 @@ "redux": "^5.0.0" } }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==" + }, + "node_modules/replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha512-AFBWBy9EVRTa/LhEcG8QDP3FvpwZqmvN2QFDuJswFeaVhWnZMp8q3E6Zd90SR04PlIwfGdyVjNyLPyen/ek5CQ==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/replace-homedir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-2.0.0.tgz", + "integrity": "sha512-bgEuQQ/BHW0XkkJtawzrfzHFSN70f/3cNOiHa2QsYxqrjaC30X1k74FJ6xswVBP0sr0SpGIdVFuPwfrYziVeyw==", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/reselect": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", "license": "MIT" }, + "node_modules/reserved-identifiers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.0.0.tgz", + "integrity": "sha512-h0bP2Katmvf3hv4Z3WtDl4+6xt/OglQ2Xa6TnhZ/Rm9/7IH1crXQqMwD4J2ngKBonVv+fB55zfGgNDAmsevLVQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, + "node_modules/resolve-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", + "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", + "dependencies": { + "value-or-function": "^4.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "optional": true, + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/rollup": { "version": "4.48.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.48.1.tgz", "integrity": "sha512-jVG20NvbhTYDkGAty2/Yh7HK6/q3DGSRH4o8ALKGArmMuaauM9kLfoMZ+WliPwA5+JHr2lTn3g557FxBV87ifg==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -4173,33 +10666,156 @@ "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", "license": "MIT" }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "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" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", "license": "MIT" }, + "node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "optional": true, + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" } }, + "node_modules/semver-greatest-satisfied-range": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-2.0.0.tgz", + "integrity": "sha512-lH3f6kMbwyANB7HuOWRMlLCa2itaCrZJ+SAqqkSZrZKO/cAsk2EOyaKHUtNkVLFyFW9pct22SFesFp3Z7zpA0g==", + "dependencies": { + "sver": "^1.8.3" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/semver-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", + "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/semver-truncate": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-1.1.2.tgz", + "integrity": "sha512-V1fGg9i4CL3qesB6U0L6XAm4xOJiHmt4QAacazumuasc03BvtFGIMCduv01JWQ69Nv+JST9TqhSCiJoxoY031w==", + "optional": true, + "dependencies": { + "semver": "^5.3.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/semver-truncate/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/set-cookie-parser": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", "license": "MIT" }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "optional": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shallow-equal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -4212,22 +10828,189 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" } }, + "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==", + "optional": true + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "optional": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", + "optional": true, + "dependencies": { + "sort-keys": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/sparkles": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-2.1.0.tgz", + "integrity": "sha512-r7iW1bDw8R/cFifrD3JnQJX0K1jqT0kprL48BiBpLZLJPmAm34zsVBsK5lc7HirZYZqMW65dOXZgbAGt/I6frg==", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "optional": true + }, + "node_modules/stream-composer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", + "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", + "dependencies": { + "streamx": "^2.13.2" + } + }, + "node_modules/stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==" + }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/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==" + }, + "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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "optional": true, + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4241,11 +11024,80 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "optional": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-outer/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "optional": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "optional": true + }, + "node_modules/strtok3": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-9.1.1.tgz", + "integrity": "sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^5.3.1" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/super-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", + "integrity": "sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==", + "dependencies": { + "function-timeout": "^1.0.1", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -4254,6 +11106,228 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sver": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", + "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==", + "optionalDependencies": { + "semver": "^6.3.0" + } + }, + "node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "optional": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sweetalert2": { + "version": "11.23.0", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.23.0.tgz", + "integrity": "sha512-cKzzbC3C1sIs7o9XAMw4E8F9kBtGXsBDUsd2JZ8JM/dqa+nzWwSGM+9LLYILZWzWHzX9W+HJNHyBlbHPVS/krw==", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/limonte" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz", + "integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz", + "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "optional": true, + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/temp-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", + "integrity": "sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tempfile": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-2.0.0.tgz", + "integrity": "sha512-ZOn6nJUgvgC09+doCEF3oB+r3ag7kUvlsXEGX069QRD60p+P3uP7XG9N2/at+EyIRGSN//ZY3LyEotA1YpmjuA==", + "optional": true, + "dependencies": { + "temp-dir": "^1.0.0", + "uuid": "^3.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/text-hex": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-0.0.0.tgz", + "integrity": "sha512-RpZDSt2VIQnsPVDiOySPfi/RTRBbPyJj2fikmH5O2H5Zc/MC6ZPVcc4GYGcnbTS/j2v1HZOmy6F4CimfiLPMRg==" + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "optional": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha512-gLCeArryy2yNTRzTGKbZbloctj64jkZ57hj5zdraXue6aFgd6PmvVtEyiUU+hvU0v7q08oVv8r8ev0tRo6bvgw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/timers-ext": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", + "dependencies": { + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -4264,7 +11338,6 @@ "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.4.4", @@ -4277,18 +11350,115 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "optional": true, + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "optional": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/to-through": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", + "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", + "dependencies": { + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/token-types": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", + "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", + "dependencies": { + "@borewit/text-codec": "^0.1.0", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "optional": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/trim-repeated/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "optional": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4302,12 +11472,77 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "optional": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "license": "MIT" }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "optional": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/uncontrollable": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", @@ -4323,12 +11558,53 @@ "react": ">=15.0.0" } }, + "node_modules/undertaker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-2.0.0.tgz", + "integrity": "sha512-tO/bf30wBbTsJ7go80j0RzA2rcwX6o7XPBpeFcb+jzoeb4pfMM2zUeSDIkY1AWqeZabWxaQZ/h8N9t35QKDLPQ==", + "dependencies": { + "bach": "^2.0.1", + "fast-levenshtein": "^3.0.0", + "last-run": "^2.0.0", + "undertaker-registry": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/undertaker-registry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-2.0.0.tgz", + "integrity": "sha512-+hhVICbnp+rlzZMgxXenpvTxpuvA67Bfgtt+O9WOE5jo7w/dyiF1VmoZVIHvP2EkUjsyKyTwYKlLhA+j47m1Ew==", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/undertaker/node_modules/fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "dependencies": { + "fastest-levenshtein": "^1.0.7" + } + }, "node_modules/undici-types": { "version": "7.10.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", "license": "MIT" }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -4370,6 +11646,41 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==", + "optional": true, + "dependencies": { + "prepend-http": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", + "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/use-mask-input": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/use-mask-input/-/use-mask-input-3.5.0.tgz", @@ -4392,6 +11703,37 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "optional": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/v8flags": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-4.0.1.tgz", + "integrity": "sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg==", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/value-or-function": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", + "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/victory-vendor": { "version": "37.3.6", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", @@ -4414,11 +11756,222 @@ "d3-timer": "^3.0.1" } }, + "node_modules/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-bufferstream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vinyl-bufferstream/-/vinyl-bufferstream-1.0.1.tgz", + "integrity": "sha512-yCCIoTf26Q9SQ0L9cDSavSL7Nt6wgQw8TU1B/bb9b9Z4A3XTypXCGdc5BvXl4ObQvVY8JrDkFnWa/UqBqwM2IA==", + "dependencies": { + "bufferstreams": "1.0.1" + } + }, + "node_modules/vinyl-contents": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", + "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", + "dependencies": { + "bl": "^5.0.0", + "vinyl": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-contents/node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/vinyl-contents/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/vinyl-contents/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==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/vinyl-contents/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/vinyl-contents/node_modules/vinyl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", + "dependencies": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-fs": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.2.tgz", + "integrity": "sha512-XRFwBLLTl8lRAOYiBqxY279wY46tVxLaRhSwo3GzKEuLz1giffsOquWWboD/haGf5lx+JyTigCFfe7DWHoARIA==", + "dependencies": { + "fs-mkdirp-stream": "^2.0.1", + "glob-stream": "^8.0.3", + "graceful-fs": "^4.2.11", + "iconv-lite": "^0.6.3", + "is-valid-glob": "^1.0.0", + "lead": "^4.0.0", + "normalize-path": "3.0.0", + "resolve-options": "^2.0.0", + "stream-composer": "^1.0.2", + "streamx": "^2.14.0", + "to-through": "^3.0.0", + "value-or-function": "^4.0.0", + "vinyl": "^3.0.1", + "vinyl-sourcemap": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-fs/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/vinyl-fs/node_modules/vinyl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", + "dependencies": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-sourcemap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", + "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", + "dependencies": { + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.10", + "now-and-later": "^3.0.0", + "streamx": "^2.12.5", + "vinyl": "^3.0.0", + "vinyl-contents": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-sourcemap/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/vinyl-sourcemap/node_modules/vinyl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", + "dependencies": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==", + "dependencies": { + "source-map": "^0.5.1" + } + }, + "node_modules/vinyl-sourcemaps-apply/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vinyl/node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vite": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.4.tgz", "integrity": "sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -4524,7 +12077,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -4536,6 +12089,27 @@ "node": ">= 8" } }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "optional": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -4546,6 +12120,27 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?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==" + }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -4567,6 +12162,22 @@ } } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -4574,6 +12185,41 @@ "dev": true, "license": "ISC" }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "optional": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 9f36cba..ab529f1 100644 --- a/package.json +++ b/package.json @@ -10,24 +10,40 @@ "preview": "vite preview" }, "dependencies": { + "20": "^3.1.9", + "22": "^0.0.0", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", "@fortawesome/fontawesome-free": "^7.0.0", "@fullcalendar/core": "^6.1.19", "@fullcalendar/daygrid": "^6.1.19", "@fullcalendar/interaction": "^6.1.19", "@fullcalendar/react": "^6.1.19", "@fullcalendar/timegrid": "^6.1.19", - "@supabase/supabase-js": "^2.57.0", + "@mui/icons-material": "^7.3.4", + "@mui/material": "^7.3.4", + "@supabase/supabase-js": "^2.76.1", + "@tailwindcss/vite": "^4.1.14", "@tiptap/extension-image": "^3.4.2", "@tiptap/pm": "^3.4.2", "@tiptap/react": "^3.4.2", "@tiptap/starter-kit": "^3.4.2", + "@zegocloud/zego-uikit-prebuilt": "^2.17.0", "bootstrap": "^5.3.8", + "chart.js": "^4.5.1", + "emailjs-com": "^3.2.0", + "lucide-react": "^0.544.0", "react": "^19.1.1", "react-bootstrap": "^2.10.10", + "react-chartjs-2": "^5.3.1", "react-dom": "^19.1.1", + "react-google-recaptcha": "^3.1.0", "react-icons": "^5.5.0", + "react-responsive": "^10.0.1", "react-router-dom": "^7.8.2", - "recharts": "^3.1.2", + "react-select": "^5.10.2", + "recharts": "^3.3.0", + "sweetalert2": "^11.23.0", "use-mask-input": "^3.5.0" }, "devDependencies": { @@ -35,6 +51,7 @@ "@types/react": "^19.1.9", "@types/react-dom": "^19.1.7", "@vitejs/plugin-react": "^4.7.0", + "autoprefix": "^1.0.1", "eslint": "^9.32.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", diff --git a/public/img/attachment.png b/public/img/AnexoDocumento.png similarity index 100% rename from public/img/attachment.png rename to public/img/AnexoDocumento.png diff --git a/public/img/user.jpg b/public/img/AvatarForm.jpg similarity index 100% rename from public/img/user.jpg rename to public/img/AvatarForm.jpg diff --git a/public/img/ChatGPT Image 12 de nov. de 2025, 14_07_11.png b/public/img/ChatGPT Image 12 de nov. de 2025, 14_07_11.png new file mode 100644 index 0000000..e2e3a4f Binary files /dev/null and b/public/img/ChatGPT Image 12 de nov. de 2025, 14_07_11.png differ diff --git a/public/img/Doctor-Free-PNG-Image.png b/public/img/Doctor-Free-PNG-Image.png new file mode 100644 index 0000000..3d30088 Binary files /dev/null and b/public/img/Doctor-Free-PNG-Image.png differ diff --git a/public/img/banner.png b/public/img/banner.png new file mode 100644 index 0000000..68303fd Binary files /dev/null and b/public/img/banner.png differ diff --git a/public/img/consultion.jpg b/public/img/consultion.jpg new file mode 100644 index 0000000..ab9d204 Binary files /dev/null and b/public/img/consultion.jpg differ diff --git a/public/img/login/medico_container.png b/public/img/login/medico_container.png new file mode 100644 index 0000000..57c9f8d Binary files /dev/null and b/public/img/login/medico_container.png differ diff --git a/public/img/login/paciente_container.png b/public/img/login/paciente_container.png new file mode 100644 index 0000000..8317b74 Binary files /dev/null and b/public/img/login/paciente_container.png differ diff --git a/public/img/logo50.png b/public/img/logo50.png new file mode 100644 index 0000000..18ee819 Binary files /dev/null and b/public/img/logo50.png differ diff --git a/public/img/o-hospital-de-blur_1203-7972.avif b/public/img/o-hospital-de-blur_1203-7972.avif new file mode 100644 index 0000000..b52530d Binary files /dev/null and b/public/img/o-hospital-de-blur_1203-7972.avif differ diff --git a/public/img/pexels-mart-production-7088498.jpg b/public/img/pexels-mart-production-7088498.jpg new file mode 100644 index 0000000..09f3db5 Binary files /dev/null and b/public/img/pexels-mart-production-7088498.jpg differ diff --git a/public/img/specialities-01.png b/public/img/specialities-01.png new file mode 100644 index 0000000..a07f3d0 Binary files /dev/null and b/public/img/specialities-01.png differ diff --git a/public/img/specialities-02.png b/public/img/specialities-02.png new file mode 100644 index 0000000..a2dced0 Binary files /dev/null and b/public/img/specialities-02.png differ diff --git a/public/img/specialities-03.png b/public/img/specialities-03.png new file mode 100644 index 0000000..41eb1af Binary files /dev/null and b/public/img/specialities-03.png differ diff --git a/public/img/specialities-04.png b/public/img/specialities-04.png new file mode 100644 index 0000000..d998415 Binary files /dev/null and b/public/img/specialities-04.png differ diff --git a/public/img/specialities-05.png b/public/img/specialities-05.png new file mode 100644 index 0000000..e37e29c Binary files /dev/null and b/public/img/specialities-05.png differ diff --git a/src/App.jsx b/src/App.jsx deleted file mode 100644 index af3515b..0000000 --- a/src/App.jsx +++ /dev/null @@ -1,17 +0,0 @@ -// src/App.jsx -import './assets/css/index.css' -import Navbar from './components/Navbar' -import Sidebar from './components/Sidebar' -import { Outlet } from 'react-router-dom' - -function App() { - return ( -
- - - -
- ) -} - -export default App diff --git a/src/Company.js b/src/Company.js new file mode 100644 index 0000000..ef3e431 --- /dev/null +++ b/src/Company.js @@ -0,0 +1,242 @@ +export const Company = ` +Introdução: +Olá! Sou o assistente virtual da MediConnect, criado para guiar pacientes, médicos, secretarias e administradores em nossa clínica digital. + +Posso ajudar você a entender como agendar suas consultas, visualizar exames e laudos, gerenciar agendas e esclarecer qualquer dúvida sobre as funcionalidades do sistema. + +Seja bem-vindo à MediConnect: saúde moderna, organizada e digital. + +=============================== Sobre a MediConnect: +A MediConnect é uma plataforma completa de gestão de clínicas e consultórios. + +Nosso objetivo é integrar todos os atores da saúde em um único ambiente: + +Pacientes + +Médicos + +Secretarias + +Administradores + +Com ela, você consegue centralizar consultas, exames, laudos, relatórios e históricos em um sistema seguro, prático e acessível de qualquer lugar. +=============================== Principais funcionalidades: +✨ Marcar e Gerenciar Consultas +✨ Listar Pacientes Cadastrados ("listar pacientes") +✨ Listar Médicos Cadastrados ("listar médicos") +✨ Verificar Horários Disponíveis de Médicos ("listar horários médicos") +✨ Dashboard Interativo por Perfil +✨ Emissão de Laudos Digitais +✨ Acesso a Exames e Histórico Clínico +✨ Relatórios Administrativos +✨ Notificações e lembretes automáticos +=============================== Localização & Contato: +Endereço: 123 Saúde Avenue, São Paulo – SP, Brasil + +Atendimento presencial: + +Segunda a Sexta: 7h00 às 20h00 + +Sábados: 8h00 às 14h00 + +Domingos e feriados: fechado + +Telefone para marcar consultas e dúvidas: +55 (79) 99942-2715 + +E-mail: contato@mediconnect.com + +Site oficial: https://mediconnect-neon.vercel.app + +=============================== Perfis disponíveis e funções: +Paciente (✨ Melhorado) + +Acessa o painel Minhas Consultas para ver suas consultas confirmadas. + +Visualiza e baixa resultados em Meus Exames. + +Consulta o histórico de Meus Laudos médicos emitidos. + +Importante: Para marcar, remarcar ou cancelar uma consulta, o paciente deve entrar em contato com a nossa secretaria por telefone. + +Exemplos de perguntas: + +• "Como faço para agendar uma consulta?" + +• "Onde vejo os resultados dos meus exames?" + +• "Meus laudos ficam disponíveis no sistema?" + +Médico - Gerencia sua agenda em tempo real. + +Consulta compromissos confirmados e pendentes. + +Atende pacientes de forma organizada. + +Emite e disponibiliza laudos digitais. + +Acompanha exames realizados pelos pacientes. + +Visualiza histórico clínico de seus pacientes. + +Exemplos de perguntas: + +• "Como acessar minha agenda?" + +• "Posso emitir laudos pelo sistema?" + +• "Onde vejo exames de meus pacientes?" + +Secretaria - Cadastra novos pacientes. + +Acesso somente a lista de medicos + +Marca, confirma e remarca consultas para pacientes. + +Dá suporte administrativo ao médico e ao paciente. + +Gerencia as agendas e o fluxo de atendimentos. + +Exemplos de perguntas: + +• "A secretaria pode cadastrar novos médicos?" + +• "Como agendar uma consulta para um paciente?" + +Administrador - Possui acesso total ao sistema. + +Gerencia permissões de usuários. + +Controla cadastros de médicos e pacientes. + +Gera relatórios e estatísticas da clínica. + +Supervisiona agendas médicas e fluxo de consultas. + +Exemplos de perguntas: + +• "O que faz o administrador?" + +• "Quem pode alterar permissões?" + +• "Quais relatórios a plataforma oferece?" + +=============================== Principais funcionalidades: +Marcar Consultas (✨ Melhorado): A secretaria marca consultas de forma rápida e digital, e o paciente visualiza a confirmação em seu painel. + +Remarcar/Cancelar Consultas: Flexibilidade para remarcar compromissos através do contato com a secretaria. + +Dashboard Interativo: Cada perfil possui um painel com informações personalizadas. + +Agenda Médica: Médicos e secretarias podem visualizar e organizar compromissos. + +CRUD de Pacientes e Médicos: Cadastro, atualização e gerenciamento completo. + +Laudos Digitais: Médicos emitem documentos eletrônicos, acessíveis ao paciente. + +Exames: Pacientes acessam resultados em Meus Exames. + +Histórico Clínico: Registros permanentes de consultas, exames e laudos. + +Notificações Automáticas: Lembretes de consultas e avisos de novos resultados. + +Relatórios Administrativos: Estatísticas para gestores da clínica. + +=============================== Fluxos explicativos (passo a passo): +Como um paciente marca uma consulta: (✨ Novo) + +Entre em contato com a clínica pelo telefone: +55 (11) 4000-1234. + +Informe seus dados e o médico/especialidade desejada. + +A secretaria irá verificar os horários disponíveis e confirmar o agendamento. + +Após a confirmação, a consulta aparecerá automaticamente no seu painel, em Minhas Consultas. + +Como um médico emite um laudo: - Acesse no painel laudo → selecione o paciente atendido. + +Insira as informações médicas no campo de laudo. + +Salve e finalize. + +O laudo ficará disponível em Meus Laudos no painel do paciente. + +Como a secretaria cadastra um paciente: - Vá até o painel administrativo → Pacientes. + +Clique em "Novo Paciente". + +Preencha dados pessoais, contato e observações. + +Salve para liberar o acesso ao paciente. + +=============================== Respostas rápidas (FAQ do bot): +(✨ Melhorado e Reorganizado) + +"Como marcar uma consulta?" +→ Para marcar uma consulta, você deve ligar para nossa secretaria no número +55 (11) 4000-1234. Nossa equipe fará o agendamento e ele aparecerá no seu portal. + +"Posso cancelar ou remarcar uma consulta?" + +→ Sim, para cancelar ou remarcar, basta entrar em contato com a secretaria com antecedência. + +"Onde vejo meus exames e laudos?" + +→ Você pode acessá-los a qualquer momento nos painéis Meus Exames e Meus Laudos, na sua área de paciente. + +"Quais são os perfis da plataforma?" + +→ São quatro: Paciente, Médico, Secretaria e Administrador, cada um com suas permissões. + +"Quem pode cadastrar novos pacientes ou médicos?" + +→ Apenas os perfis de Secretaria e Administrador. + +"O médico consegue ver meu histórico?" + +→ Sim, para garantir a qualidade do seu atendimento, os médicos têm acesso ao histórico clínico dos pacientes que atendem. + +"Recebo lembretes sobre minhas consultas?" + +→ Sim! O sistema envia lembretes automáticos para você não perder seus compromissos. + +=============================== Diferenciais MediConnect: +Integração completa entre pacientes, médicos e administração. + +Atendimento digital seguro, rápido e acessível. + +Agilidade na entrega de laudos e exames online. + +Dashboard moderno e fácil de usar. + +Redução de falhas administrativas com um sistema centralizado. + +Transparência e organização na comunicação clínica. +=============================== Quando não souber responder: +Se o usuário fizer uma pergunta fora do contexto da MediConnect +ou que não tenha relação com saúde, clínica, consultas, laudos, +ou sistema interno, siga estas instruções: + +1. Seja educado e transparente. +2. Não invente informações. +3. Diga algo como: + + "Desculpe 😅, mas essa pergunta foge um pouco do que posso responder. + Posso te ajudar com informações sobre a MediConnect!" + + +=============================== Políticas gerais (resumidas): +Todos os dados de pacientes são armazenados com segurança e sigilo. + +Apenas perfis autorizados podem acessar laudos e exames. + +Consultas devem ser canceladas ou remarcadas com no mínimo 24h de antecedência. + +O administrador é responsável por revisar permissões e manter a ordem do sistema. + +=============================== Mensagem final: +Na MediConnect, acreditamos que a saúde deve ser prática, organizada e digital. + +Nosso sistema foi feito para facilitar a rotina de todos. + +Sempre que precisar, pode contar comigo para esclarecer suas dúvidas! +`; + diff --git a/src/Supabase.js b/src/Supabase.js index d4aacd3..beb6bc3 100644 --- a/src/Supabase.js +++ b/src/Supabase.js @@ -5,6 +5,20 @@ const supabaseUrl = "https://pxhmxgotbfwypaqwpcmh.supabase.co" const supabaseAnonKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InB4aG14Z290YmZ3eXBhcXdwY21oIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTY1NjU2MjAsImV4cCI6MjA3MjE0MTYyMH0.Yu2C0MZ-f4EaFGeJ03YmDtT7m539Q84JfqULqwe2XUI" const supabase = createClient(supabaseUrl, supabaseAnonKey) +export async function logoutUser() { + const { error } = await supabase.auth.signOut(); + + if (error) { + console.error("Erro ao fazer logout:", error.message); + return false; + } + + // Remove token do localStorage (caso você use) + localStorage.removeItem("access_token"); + + return true; +} + export default supabase \ No newline at end of file diff --git a/src/assets/css/Dashboard.css b/src/assets/css/Dashboard.css new file mode 100644 index 0000000..6fcc08f --- /dev/null +++ b/src/assets/css/Dashboard.css @@ -0,0 +1,167 @@ +* Conteúdo geral */ +.sdc-content { + padding: 20px; + font-family: "Roboto", sans-serif; + background-color: #f5f6fa; +} + +/* Widgets */ +.sdc-dash-widget { + background: #fff; + border-radius: 12px; + padding: 20px; + margin-bottom: 20px; + position: relative; + overflow: hidden; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + min-height: 110px; +} + +.sdc-dash-widget span { + font-size: 30px; + padding: 15px; + border-radius: 50%; + color: #fff; + display: flex; + align-items: center; + justify-content: center; + margin-right: 20px; + width: 60px; + height: 60px; +} + +.sdc-dash-widget-bg1 { background-color: #2196f3; } +.sdc-dash-widget-bg2 { background-color: #4caf50; } +.sdc-dash-widget-bg3 { background-color: #ff9800; } +.sdc-dash-widget-bg4 { background-color: #e91e63; } + +.sdc-dash-widget-info { + flex: 1; + text-align: left; +} + +.sdc-dash-widget-info h3 { + margin: 0; + font-size: 28px; + color: #333; +} + +.sdc-dash-widget-info span { + display: block; + font-size: 16px; + color: #777; + margin-top: 5px; +} + +/* Cards */ +.sdc-card { + background: #fff; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + margin-bottom: 20px; + padding: 15px; +} + +.sdc-card-header h4 { + margin: 0; + color: #333; + font-size: 18px; + font-weight: 500; + display: inline-block; +} + +.sdc-btn { + padding: 6px 12px; + border-radius: 6px; + border: none; + cursor: pointer; + font-size: 14px; + font-weight: 500; +} + +.sdc-btn-primary { background-color: #2196f3; color: #fff; } +.sdc-btn-outline-primary { + background-color: transparent; + border: 1px solid #2196f3; + color: #2196f3; +} + +/* Gráfico de pizza */ +.sdc-pie-chart-wrapper { + width: 200px; + height: 200px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 30px auto; + position: relative; + background: conic-gradient( + #4caf50 0% 40%, + #2196f3 40% 70%, + #ff9800 70% 90%, + #e91e63 90% 100% + ); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.sdc-pie-chart-label { + position: absolute; + text-align: center; + font-size: 16px; + font-weight: 600; + color: #fff; + line-height: 1.4; +} + +/* Gráfico de colunas */ +.sdc-bar-chart-wrapper { + display: flex; + justify-content: space-around; + align-items: flex-end; + margin: 30px 0; + height: 200px; +} + +.sdc-bar { + width: 20%; + background-color: #2196f3; + position: relative; + border-radius: 8px 8px 0 0; + display: flex; + justify-content: center; + align-items: flex-end; + transition: 0.3s ease; +} + +.sdc-bar span { + color: #fff; + font-weight: 600; + margin-bottom: 5px; +} + +.sdc-bar-red { background-color: #e91e63; } +.sdc-bar-orange { background-color: #ff9800; } +.sdc-bar-green { background-color: #4caf50; } + +/* Responsivo */ +@media (max-width: 768px) { + .sdc-dash-widget { + flex-direction: column; + text-align: center; + } + .sdc-dash-widget span { + margin-bottom: 10px; + margin-right: 0; + } + .sdc-bar-chart-wrapper { + flex-direction: column; + height: auto; + } + .sdc-bar { + width: 80%; + margin-bottom: 10px; + } +} diff --git a/src/assets/css/DoctorDashboard.css b/src/assets/css/DoctorDashboard.css new file mode 100644 index 0000000..06a78ee --- /dev/null +++ b/src/assets/css/DoctorDashboard.css @@ -0,0 +1,167 @@ +/* Conteúdo geral */ +.doc-content { + padding: 20px; + font-family: "Roboto", sans-serif; + background-color: #f5f6fa; + } + + /* Widgets */ + .doc-dash-widget { + background: #fff; + border-radius: 12px; + padding: 20px; + margin-bottom: 20px; + position: relative; + overflow: hidden; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + min-height: 110px; + } + + .doc-dash-widget span { + font-size: 30px; + padding: 15px; + border-radius: 50%; + color: #fff; + display: flex; + align-items: center; + justify-content: center; + margin-right: 20px; + width: 60px; + height: 60px; + } + + .doc-dash-widget-bg1 { background-color: #2196f3; } + .doc-dash-widget-bg2 { background-color: #4caf50; } + .doc-dash-widget-bg3 { background-color: #ff9800; } + .doc-dash-widget-bg4 { background-color: #e91e63; } + + .doc-dash-widget-info { + flex: 1; + text-align: left; + } + + .doc-dash-widget-info h3 { + margin: 0; + font-size: 28px; + color: #333; + } + + .doc-dash-widget-info span { + display: block; + font-size: 16px; + color: #777; + margin-top: 5px; + } + + /* Cards */ + .doc-card { + background: #fff; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + margin-bottom: 20px; + padding: 15px; + } + + .doc-card-header h4 { + margin: 0; + color: #333; + font-size: 18px; + font-weight: 500; + display: inline-block; + } + + .doc-btn { + padding: 6px 12px; + border-radius: 6px; + border: none; + cursor: pointer; + font-size: 14px; + font-weight: 500; + } + + .doc-btn-primary { background-color: #2196f3; color: #fff; } + .doc-btn-outline-primary { + background-color: transparent; + border: 1px solid #2196f3; + color: #2196f3; + } + + /* Gráfico de pizza */ + .doc-pie-chart-wrapper { + width: 200px; + height: 200px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 30px auto; + position: relative; + background: conic-gradient( + #4caf50 5% 40%, + #ff9800 40% 80%, + #e91e63 50% 80% + ); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + } + + .doc-pie-chart-label { + position: absolute; + text-align: center; + font-size: 16px; + font-weight: 600; + color: #fff; + line-height: 1.4; + } + + /* Gráfico de colunas */ + .doc-bar-chart-wrapper { + display: flex; + justify-content: space-around; + align-items: flex-end; + margin: 30px 0; + height: 200px; + } + + .doc-bar { + width: 20%; + + position: relative; + border-radius: 8px 8px 0 0; + display: flex; + justify-content: center; + align-items: flex-end; + transition: 0.3s ease; + } + + .doc-bar span { + color: #fff; + font-weight: 600; + margin-bottom: 5px; + } + + .doc-bar-red { background-color: #e91e63; } + .doc-bar-orange { background-color: #ff9800; } + .doc-bar-green { background-color: #4caf50; } + + /* Responsivo */ + @media (max-width: 768px) { + .doc-dash-widget { + flex-direction: column; + text-align: center; + } + .doc-dash-widget span { + margin-bottom: 10px; + margin-right: 0; + } + .doc-bar-chart-wrapper { + flex-direction: column; + height: auto; + } + .doc-bar { + width: 80%; + margin-bottom: 10px; + } + } + \ No newline at end of file diff --git a/src/assets/css/SecretariaDashCss.css b/src/assets/css/SecretariaDashCss.css new file mode 100644 index 0000000..4be5668 --- /dev/null +++ b/src/assets/css/SecretariaDashCss.css @@ -0,0 +1,63 @@ +.dashboard-container { + padding: 2rem; + font-family: Arial, sans-serif; + background-color: #f5f5f5; + min-height: 100vh; + } + + .dashboard-title { + font-size: 2rem; + font-weight: bold; + margin-bottom: 1.5rem; + text-align: center; + } + + .cards-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 1.5rem; + } + + .card { + background-color: #fff; + border: 1px solid #ddd; + border-radius: 12px; + padding: 1.5rem; + box-shadow: 0 4px 8px rgba(0,0,0,0.05); + transition: transform 0.2s, box-shadow 0.2s; + cursor: pointer; + text-align: center; + } + + .card:hover { + transform: translateY(-4px); + box-shadow: 0 6px 12px rgba(0,0,0,0.1); + } + + .card-title { + font-size: 1.25rem; + font-weight: bold; + margin-bottom: 0.5rem; + } + + .card-subtitle { + color: #555; + margin-bottom: 1rem; + font-size: 0.95rem; + } + + .card-button { + width: 100%; + padding: 0.5rem; + background-color: #1976d2; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + } + + .card-button:hover { + background-color: #115293; + } + \ No newline at end of file diff --git a/src/assets/css/chatbot.css b/src/assets/css/chatbot.css new file mode 100644 index 0000000..6081d2e --- /dev/null +++ b/src/assets/css/chatbot.css @@ -0,0 +1,266 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap'); + + +*{ + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Inter", sans-serif; +} + +.body-gpt { + width: 100%; + min-height: 100vh; + background-color: linear-gradient(#F4F0FF, #DACDFF); +} +#chatbot-toggler{ + position: fixed; + bottom: 30px; + right:35px; + border:none; + height: 50px; + width: 50px; + display: flex; + cursor: pointer; + border-radius: 50%; + background: linear-gradient(135deg, #004a99, #0077cc); + align-items: center; + justify-content: center; + transition: all 0.2s ease; +} + +#chatbot-toggler span{ + position: absolute; + color: #fff; +} +.container-chatbox.show-chatbot #chatbot-toggler span{ + transform: rotate(90deg); + +} +#chatbot-toggler span:last-child, +.container-chatbox.show-chatbot #chatbot-toggler span:first-child{ + opacity: 0; +} +.container-chatbox.show-chatbot #chatbot-toggler span:last-child{ + opacity: 1; +} +.chatbot-popup{ + position: fixed; + opacity: 0; + pointer-events: none; + bottom:90px; + right: 35px; + width: 420px; + transform: scale(0.2); + overflow: hidden; + background: #fff; + border-radius: 15px; + transform-origin: bottom right; + box-shadow: 0 0 120px 0 rgba(0, 0, 0, 0.1), + 0 32px 64px -48px rgba(0, 0, 0, 0.5); + transition: all 0.1s ease; +} +.container-chatbox.show-chatbot .chatbot-popup{ + opacity: 1; + transform: scale(1); + pointer-events: auto; +} + +.chatbot-popup .chat-header{ + display: flex; + padding: 15px 22px; + align-items: center; + justify-content: space-between; + background: linear-gradient(135deg, #004a99, #0077cc); +} + +.chat-header .header-info{ + display: flex; + gap: 10px; + align-items: center; +} + +.header-info svg{ + height: 35px; + width: 35px; + padding: 6px; + flex-shrink: 0; + fill: #fff; + background: linear-gradient(135deg, #004a99, #0077cc); + border-radius: 50%; +} + +.header-info .logo-text{ + color: #fff; + font-size: 1.31rem; + font-weight: 600; +} + +.chat-header button { + height: 40px; + width: 40px; + border: none; + outline: none; + color: #fff; + cursor: pointer; + font-size: 1.9rem; + padding-top: 2px; + border-radius: 50%; + margin-right: -10px; + background: none; + transition: 0.2s ease; +} + +.chat-header button:hover{ + background: linear-gradient(135deg, #004a99, #0077cc); +} + +.chat-body{ + display: flex; + flex-direction: column; + height: 460px; + margin-bottom: 82px; + overflow-y: auto; + padding: 25px 22px; + scrollbar-width: thin; + scrollbar-color: #DDD3F9 transparent; +} + +.chat-body .message{ + display: flex; + gap: 11px; + align-items: center; +} +.chat-body .bot-message.error .message-text{ + color: #ff0000 + +} + +.chat-body .bot-message svg{ + height: 35px; + width: 35px; + padding: 6px; + flex-shrink: 0; + fill: #fff; + align-self: flex-end; + background: linear-gradient(135deg, #004a99, #0077cc); + border-radius: 50%; + +} + +.chat-body .message .message-text{ + padding: 12px 16px; + max-width: 75%; + word-wrap: break-word; + white-space: pre-line; + font-size: 0.95rem; +} + +.chat-body .bot-message .message-text{ + background: #F6F2FF; + border-radius: 13px 13px 13px 3px; +} + +.chat-body .user-message{ + flex-direction: column; + align-items: flex-end; +} + +.chat-body .user-message .message-text{ + color: #fff; + background: #03649d; + border-radius: 13px 13px 3px 13px; +} + +.chat-footer{ + position: absolute; + bottom: 0; + width: 100%; + background: #fff; + padding: 15px 22px 20px; +} + +.chat-footer .chat-form{ + display: flex; + align-items: center; + background: #fff; + border-radius: 32px; + outline: 1px solid #CCCCE5; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.06); +} + +.chat-footer .chat-form:focus-within{ + outline: 2px solid #009EFB; +} + +.chat-form .message-input{ + border: none; + outline: none; + width: 100%; + background: none; + height: 47px; + padding: 0 17px; + font-size: 0.95rem; +} + +.chat-form button{ + height: 35px; + width: 32px; + border: none; + display: none; + outline: none; + cursor: pointer; + font-size: 1.15rem; + color: #fff; + flex-shrink: 0; + margin-right: 6px; + border-radius: 50%; + background: #009EFB; + transition: 0.2s ease; +} + +.chat-form button:hover{ + background: #03649d; +} + +.chat-form .message-input:valid ~ button{ + display: block; + +} + +@media (max-width: 520px){ + #chatbot-toggler{ + right: 20px; + bottom: 20px + } + + .chatbot-popup{ + right:0; + bottom: 0; + height: 100%; + border-radius: 0; + width: 100%; + } + .chatbot-popup .chat-header{ + padding: 12px 15px; + } + .chat-body{ + height: calc(90% -55px); + padding: 25px 15px; + } + .chat-footer{ + padding: 10px 15px 15px; + } +}/* Corrige sobreposição do calendário sobre o Chatbot */ +#chatbot-toggler, +.container-chatbox, +.chatbot-popup { + position: fixed !important; + z-index: 9999 !important; /* Fica acima de qualquer componente, incluindo o FullCalendar */ +} + +/* Garante que o popup também não fique atrás de nada */ +.chatbot-popup { + z-index: 9998 !important; +} + diff --git a/src/assets/css/darkmode.css b/src/assets/css/darkmode.css new file mode 100644 index 0000000..906efa3 --- /dev/null +++ b/src/assets/css/darkmode.css @@ -0,0 +1,1841 @@ +/* ============ + RESET GERAL + ============ */ +* { + box-sizing: border-box; +} + +html, body { + margin: 0; + padding: 0; + width: 100%; + min-height: 100vh; + transition: background-color 0.4s ease, color 0.4s ease; +} + +/* ============ + MODO CLARO (PADRÃO) + ============ */ +body { + background: #f5f8ff !important; + color: #111 !important; + font-family: "Inter", sans-serif; +} + +/* ============ + MODO ESCURO + ============ */ +body.dark-mode { + background: #0f172a !important; + color: #e2e8f0 !important; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Containers principais */ +body.dark-mode #root, +body.dark-mode .app, +body.dark-mode .main-container, +body.dark-mode .page-wrapper, +body.dark-mode .content { + background: #0f172a !important; + color: #e2e8f0 !important; +} + +/* ============ + CHATBOT - CORREÇÃO DAS LISTRAS BRANCAS (AJUSTADO) + ============ */ +body.dark-mode .container-chatbox { + background: #0f172a !important; + /* MANTÉM o tamanho original, só muda a cor de fundo */ +} + +body.dark-mode .chatbot-popup { + background: #0f172a !important; + /* MANTÉM o tamanho original, só muda a cor de fundo */ + border: 1px solid #334155 !important; +} + +body.dark-mode .chat-body { + background: #0f172a !important; + color: #e2e8f0 !important; + border-color: #334155 !important; +} + +body.dark-mode .chat-footer { + background: #1e293b !important; + color: #e2e8f0 !important; + border-top: 1px solid #334155 !important; +} + +body.dark-mode .message-text { + color: #e2e8f0 !important; +} + +body.dark-mode .message-input { + background: #0f172a !important; + color: #e2e8f0 !important; + border: 1px solid #334155 !important; +} + +/* ============ + MODAIS - CORREÇÃO DOS MODAIS CLAROS + ============ */ +body.dark-mode .modal-content { + background: #1e293b !important; + color: #e2e8f0 !important; + border: 1px solid #334155 !important; +} + +body.dark-mode .modal-header { + background: #0f172a !important; + color: #e2e8f0 !important; + border-bottom: 1px solid #334155 !important; +} + +body.dark-mode .modal-body { + background: #1e293b !important; + color: #e2e8f0 !important; +} + +body.dark-mode .modal-footer { + background: #0f172a !important; + border-top: 1px solid #334155 !important; +} + +body.dark-mode .btn-close { + filter: invert(1) !important; +} + +/* ============ + FORMULÁRIOS + ============ */ +body.dark-mode .form-control, +body.dark-mode .form-select, +body.dark-mode .form-check-input { + background: #0f172a !important; + color: #e2e8f0 !important; + border: 1px solid #334155 !important; +} + +body.dark-mode .form-control:focus, +body.dark-mode .form-select:focus { + background: #0f172a !important; + color: #e2e8f0 !important; + border-color: #3399ff !important; + box-shadow: 0 0 0 0.2rem rgba(51, 153, 255, 0.25) !important; +} + +body.dark-mode .form-check-label { + color: #e2e8f0 !important; +} + +/* ============ + TABELAS + ============ */ +body.dark-mode .table { + background: #1e293b !important; + color: #e2e8f0 !important; + border-color: #334155 !important; +} + +body.dark-mode .table th { + background: #0f172a !important; + color: #e2e8f0 !important; + border-color: #334155 !important; +} + +body.dark-mode .table td { + background: #1e293b !important; + color: #e2e8f0 !important; + border-color: #334155 !important; +} + +body.dark-mode .table-striped tbody tr:nth-of-type(odd) { + background-color: #192230 !important; +} + +/* ============ + BOTÕES + ============ */ +body.dark-mode .btn-secondary { + background: #334155 !important; + border-color: #334155 !important; + color: #e2e8f0 !important; +} + +body.dark-mode .btn-white { + background: #334155 !important; + border-color: #475569 !important; + color: #e2e8f0 !important; +} + +/* ============ + TEXTOS + ============ */ +body.dark-mode .text-muted { + color: #94a3b8 !important; +} + +/* ============ + CARDS E CONTAINERS + ============ */ +body.dark-mode .card, +body.dark-mode .card-box, +body.dark-mode .profile-widget { + background: #1e293b !important; + color: #e2e8f0 !important; + border: 1px solid #334155 !important; +} + +/* ============ + BOTÃO ACESSIBILIDADE + ============ */ +.acc-btn { + position: fixed; + right: 90px; + bottom: 30px; + z-index: 3000; + width: 50px; + height: 50px; + border-radius: 50%; + border: none; + background-color: #3399ff; + color: white; + font-size: 22px; + cursor: pointer; + transition: all 0.25s ease; +} + +.acc-btn:hover { + background-color: #4da6ff; +} + +.acc-btn.active { + border: 2px solid #fff; +} + +/* ============ + PAINEL ACESSIBILIDADE + ============ */ +.acc-panel { + position: fixed; + bottom: 90px; + right: 30px; + background: #ffffff; + color: #000; + border-radius: 12px; + padding: 16px; + width: 280px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); + transition: all 0.3s ease; + z-index: 4000; +} + +body.dark-mode .acc-panel { + background: #1e293b !important; + color: #f9fafb !important; + border: 1px solid #334155 !important; +} + +/* ADICIONE AQUI O RESTO DO SEU darkmode.css ORIGINAL */ +/* (mantenha todas as outras regras que você já tinha) */ + +/* ============ + ESTILOS BASE + ============ */ +body { + background: #f5f8ff; + color: #111; + font-family: "Inter", sans-serif; + transition: background-color 0.4s, color 0.4s; +} + +/* ============ + MODO ESCURO + ============ */ +body.dark-mode { + background-color: #0f172a; + color: #e2e8f0; +} + +body.dark-mode a { + color: #60a5fa; +} + +body.dark-mode a:hover { + color: #93c5fd; +} + +body.dark-mode .card, +body.dark-mode .card-box, +body.dark-mode .profile-widget { + background-color: #1e293b; + color: #f1f5f9; + border: 1px solid #334155; +} + +/* ============ + BOTÃO ♿ + ============ */ +.acc-btn { + position: fixed; + right: 90px; + bottom: 30px; + z-index: 3000; + width: 50px; + height: 50px; + border-radius: 50%; + border: none; + background-color: #3399ff; + color: white; + font-size: 22px; + cursor: pointer; + transition: all 0.25s ease; +} + +.acc-btn:hover { + background-color: #4da6ff; +} + +.acc-btn.active { + border: 2px solid #fff; +} + +/* ============ + PAINEL + ============ */ +.acc-panel { + position: fixed; + bottom: 90px; + right: 30px; + background: #ffffff; + color: #000; + border-radius: 12px; + padding: 16px; + width: 280px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); + transition: all 0.3s ease; + z-index: 4000; +} + +body.dark-mode .acc-panel { + background: #1e293b; + color: #f9fafb; + border: 1px solid #334155; +} + +/* Header */ +.acc-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.acc-header strong { + font-size: 16px; +} + +.acc-close { + background: none; + border: none; + color: inherit; + font-size: 20px; + cursor: pointer; +} + +/* Conteúdo */ +.acc-row { + display: flex; + align-items: center; + justify-content: space-between; + margin: 8px 0; +} + +.acc-switch { + display: flex; + align-items: center; + gap: 8px; +} + +/* Rodapé */ +.acc-footer { + text-align: center; + font-size: 12px; + opacity: 0.7; + margin-top: 8px; +} + +/* ============ + AJUSTES ADICIONAIS DE MODO ESCURO + ============ */ + +/* Sidebar e rolagem */ +body.dark-mode .sidebar, +body.dark-mode .sidebar-inner, +body.dark-mode .slimscroll { + background-color: #1e293b; + color: #f1f5f9; +} + +body.dark-mode .sidebar a { + color: #e2e8f0; +} + +body.dark-mode .sidebar a:hover { + color: #60a5fa; +} + +body.dark-mode .menu-title { + color: #94a3b8; + font-weight: 500; +} + +/* ======== + Aba "nav-tabs" (como a da aba 'Sobre' dos perfis) + ======== */ +body.dark-mode .nav-tabs.nav-tabs-bottom { + border-bottom: 1px solid #334155; +} + +body.dark-mode .nav-tabs.nav-tabs-bottom .nav-link { + background-color: transparent; + color: #cbd5e1; + border: none; + border-bottom: 2px solid transparent; + transition: all 0.3s ease; +} + +body.dark-mode .nav-tabs.nav-tabs-bottom .nav-link:hover { + color: #60a5fa; + border-bottom-color: #60a5fa; +} + +body.dark-mode .nav-tabs.nav-tabs-bottom .nav-link.active { + color: #ffffff; + border-bottom: 2px solid #3399ff; + font-weight: 600; + background-color: #1e293b; +} + +/* ======== + Conteúdo da aba ativa (ex: Biografia, Sobre, etc) + ======== */ +body.dark-mode .tab-content { + background-color: #0f172a; + color: #e2e8f0; + border-radius: 8px; + padding: 16px; +} + +body.dark-mode .tab-pane { + background-color: #1e293b; + border: 1px solid #334155; + border-radius: 8px; + padding: 16px; +} + +/* ======== + Cards dentro das abas (ex: Biografia, Dados, etc) + ======== */ +body.dark-mode .card-box { + background-color: #1e293b; + border: 1px solid #334155; + color: #f1f5f9; +} + +/* ======== + Scrollbars visuais (modo escuro) + ======== */ +body.dark-mode ::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +body.dark-mode ::-webkit-scrollbar-thumb { + background: #334155; + border-radius: 10px; +} + +body.dark-mode ::-webkit-scrollbar-thumb:hover { + background: #475569; +} + +/* ======== + Links e botões dentro de abas + ======== */ +body.dark-mode .nav-link, +body.dark-mode .btn-link { + color: #93c5fd; +} + +body.dark-mode .nav-link:hover, +body.dark-mode .btn-link:hover { + color: #60a5fa; +} + +/* ============ + AJUSTES COMPLETOS PARA ABA NAV-TABS (INCLUINDO NAV CONTAINER) + ============ */ + +/* Container principal das abas */ +body.dark-mode nav.nav.nav-tabs.nav-tabs-bottom { + background-color: #1e293b; + border-bottom: 1px solid #334155; + border-radius: 8px 8px 0 0; + padding: 6px 10px; +} + +/* Cada aba */ +body.dark-mode nav.nav.nav-tabs.nav-tabs-bottom .nav-link { + background-color: transparent; + color: #cbd5e1; + border: none; + border-bottom: 2px solid transparent; + font-weight: 500; + transition: all 0.3s ease; +} + +/* Hover da aba */ +body.dark-mode nav.nav.nav-tabs.nav-tabs-bottom .nav-link:hover { + color: #60a5fa; + border-bottom-color: #60a5fa; +} + +/* Aba ativa */ +body.dark-mode nav.nav.nav-tabs.nav-tabs-bottom .nav-link.active { + color: #ffffff; + border-bottom: 2px solid #3399ff; + font-weight: 600; + background-color: #1e293b; + border-radius: 4px 4px 0 0; +} + +/* Conteúdo das abas */ +body.dark-mode .tab-content { + background-color: #0f172a; + color: #e2e8f0; + border-radius: 0 0 8px 8px; + padding: 16px; + border: 1px solid #334155; +} + +/* Painel interno (cada aba) */ +body.dark-mode .tab-pane { + background-color: #1e293b; + border: 1px solid #334155; + border-radius: 8px; + padding: 16px; +} + +/* ======== + Sidebar e slimscroll + ======== */ +body.dark-mode .sidebar, +body.dark-mode .sidebar-inner, +body.dark-mode .slimscroll { + background-color: #1e293b; + color: #f1f5f9; +} + +body.dark-mode .sidebar a { + color: #e2e8f0; +} + +body.dark-mode .sidebar a:hover { + color: #60a5fa; +} + +body.dark-mode .menu-title { + color: #94a3b8; + font-weight: 500; +} + +/* ======== + Scrollbars visuais (modo escuro) + ======== */ +body.dark-mode ::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +body.dark-mode ::-webkit-scrollbar-thumb { + background: #334155; + border-radius: 10px; +} + +body.dark-mode ::-webkit-scrollbar-thumb:hover { + background: #475569; +} + +/* ============ + CORREÇÃO DO MODO ESCURO PARA UL.NAV.NAV-TABS.NAV-TABS-BOTTOM + ============ */ + +body.dark-mode ul.nav.nav-tabs.nav-tabs-bottom { + background-color: #1e293b !important; + border-bottom: 1px solid #334155 !important; + border-radius: 8px 8px 0 0 !important; + padding: 6px 10px !important; +} + +body.dark-mode ul.nav.nav-tabs.nav-tabs-bottom .nav-link { + background-color: transparent !important; + color: #cbd5e1 !important; + border: none !important; + border-bottom: 2px solid transparent !important; + font-weight: 500 !important; + transition: all 0.3s ease !important; +} + +body.dark-mode ul.nav.nav-tabs.nav-tabs-bottom .nav-link:hover { + color: #60a5fa !important; + border-bottom-color: #60a5fa !important; +} + +body.dark-mode ul.nav.nav-tabs.nav-tabs-bottom .nav-link.active { + color: #ffffff !important; + border-bottom: 2px solid #3399ff !important; + font-weight: 600 !important; + background-color: #1e293b !important; + border-radius: 4px 4px 0 0 !important; +} + +/* Conteúdo da aba */ +body.dark-mode .tab-content { + background-color: #0f172a !important; + color: #e2e8f0 !important; + border-radius: 0 0 8px 8px !important; + padding: 16px !important; + border: 1px solid #334155 !important; +} + +/* ============ + DASHBOARD (sdc-dash-widget / sdc-card / sdc-card-header) + ============ */ + +/* ===== Widget principal (cards de métricas) ===== */ +body.dark-mode .sdc-dash-widget { + background-color: #1e293b !important; + border: 1px solid #334155 !important; + color: #f1f5f9 !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25) !important; + border-radius: 12px !important; + transition: background-color 0.3s ease, color 0.3s ease; +} + +body.dark-mode .sdc-dash-widget:hover { + border-color: #3399ff !important; + box-shadow: 0 0 0 2px rgba(51, 153, 255, 0.4) !important; +} + +/* ===== Cartões (sdc-card) ===== */ +body.dark-mode .sdc-card { + background-color: #1e293b !important; + border: 1px solid #334155 !important; + color: #e2e8f0 !important; + border-radius: 10px !important; + transition: background-color 0.3s ease, border-color 0.3s ease; +} + +body.dark-mode .sdc-card:hover { + border-color: #3399ff !important; +} + +/* ===== Cabeçalho dos cartões (sdc-card-header) ===== */ +body.dark-mode .sdc-card-header { + background-color: #0f172a !important; + color: #93c5fd !important; + border-bottom: 1px solid #334155 !important; + padding: 10px 16px !important; + font-weight: 600 !important; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +body.dark-mode .sdc-card-header i, +body.dark-mode .sdc-card-header svg { + color: #60a5fa !important; +} + +/* ===== Ícones e textos dentro dos widgets ===== */ +body.dark-mode .sdc-dash-widget h3, +body.dark-mode .sdc-dash-widget span, +body.dark-mode .sdc-dash-widget i { + color: #f1f5f9 !important; +} + +body.dark-mode .sdc-dash-widget .icon { + background: rgba(51, 153, 255, 0.15) !important; + border-radius: 50% !important; + color: #3399ff !important; + padding: 10px; +} + +/* ============ + TABELAS • th / td / thead / DataTables + ============ */ + +/* Base para qualquer tabela (Bootstrap, nativa e DataTables) */ +body.dark-mode table, +body.dark-mode .table { + background-color: #1e293b !important; + /* fundo do corpo */ + color: #e2e8f0 !important; + border-color: #334155 !important; +} + +/* Cabeçalho */ +body.dark-mode thead, +body.dark-mode .table thead { + background-color: #0f172a !important; + color: #e2e8f0 !important; +} + +body.dark-mode .table thead th, +body.dark-mode thead th { + background-color: #0f172a !important; + color: #e2e8f0 !important; + border-color: #334155 !important; +} + +/* Células do corpo */ +body.dark-mode .table td, +body.dark-mode td { + background-color: #1e293b !important; + color: #e2e8f0 !important; + border-color: #334155 !important; +} + +/* Bordas da tabela */ +body.dark-mode .table-bordered, +body.dark-mode .table-bordered th, +body.dark-mode .table-bordered td { + border-color: #334155 !important; +} + +/* Striped (zebrado) */ +body.dark-mode .table-striped tbody tr:nth-of-type(odd) { + background-color: #192230 !important; + /* um tom acima do corpo */ +} + +body.dark-mode .table-striped tbody tr:nth-of-type(even) { + background-color: #1e293b !important; +} + +/* Hover */ +body.dark-mode .table-hover tbody tr:hover { + background-color: #243244 !important; +} + +/* Links dentro da tabela */ +body.dark-mode .table a { + color: #60a5fa !important; +} + +body.dark-mode .table a:hover { + color: #93c5fd !important; +} + +/* Cabeçalho fixo / responsivo */ +body.dark-mode .table-responsive { + background: transparent !important; + border-color: #334155 !important; +} + +/* ===== DataTables (Bootstrap 4) ===== */ +body.dark-mode .dataTables_wrapper .dataTables_length select, +body.dark-mode .dataTables_wrapper .dataTables_filter input { + background-color: #0f172a !important; + color: #e2e8f0 !important; + border: 1px solid #334155 !important; +} + +body.dark-mode .dataTables_wrapper .dataTables_info { + color: #94a3b8 !important; +} + +/* Paginação (Bootstrap) */ +body.dark-mode .pagination .page-link, +body.dark-mode .page-item .page-link { + background-color: #0f172a !important; + color: #e2e8f0 !important; + border: 1px solid #334155 !important; +} + +body.dark-mode .page-item.active .page-link { + background-color: #3399ff !important; + border-color: #3399ff !important; + color: #0b1220 !important; +} + +body.dark-mode .page-item.disabled .page-link { + background-color: #0f172a !important; + color: #64748b !important; + border-color: #334155 !important; +} + +/* Paginação (classe do DataTables puro) */ +body.dark-mode .dataTables_wrapper .dataTables_paginate .paginate_button { + background: #0f172a !important; + color: #e2e8f0 !important; + border: 1px solid #334155 !important; +} + +body.dark-mode .dataTables_wrapper .dataTables_paginate .paginate_button.current, +body.dark-mode .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { + background: #3399ff !important; + border-color: #3399ff !important; + color: #0b1220 !important; +} + +body.dark-mode .dataTables_wrapper .dataTables_paginate .paginate_button:hover { + background: #243244 !important; + color: #e2e8f0 !important; + border-color: #3b4a62 !important; +} + +/* Dropdowns dentro de tabela */ +body.dark-mode .dropdown-menu { + background-color: #0f172a !important; + color: #e2e8f0 !important; + border: 1px solid #334155 !important; +} + +body.dark-mode .dropdown-item { + color: #e2e8f0 !important; +} + +body.dark-mode .dropdown-item:hover { + background-color: #1e293b !important; + color: #93c5fd !important; +} + +/* Badges/labels dentro da tabela (se existirem) */ +body.dark-mode .badge, +body.dark-mode .label { + background: #243244 !important; + color: #e2e8f0 !important; + border: 1px solid #334155 !important; +} + +body.dark-mode h4 { + color: #f8fafc !important; + /* texto branco puro */ + font-weight: 600 !important; +} + +body.dark-mode h4 { + color: #f8fafc !important; + /* texto branco puro */ + font-weight: 600 !important; +} + +/* ============ + BOTÕES PRIMÁRIOS (Bootstrap) + ============ */ + +/* Botão principal */ +body.dark-mode .btn-primary, +body.dark-mode .btn.btn-primary, +body.dark-mode .btn.btn-primary.btn-rounded, +body.dark-mode .btn.btn-primary.float-right { + background-color: #3399ff !important; + /* azul padrão Mediconnect */ + border-color: #3399ff !important; + color: #ffffff !important; + /* texto branco */ + font-weight: 600 !important; + box-shadow: 0 2px 8px rgba(51, 153, 255, 0.3) !important; + transition: all 0.2s ease-in-out; +} + +/* Hover e foco */ +body.dark-mode .btn-primary:hover, +body.dark-mode .btn.btn-primary:hover, +body.dark-mode .btn.btn-primary.btn-rounded:hover, +body.dark-mode .btn.btn-primary.float-right:hover { + background-color: #4da6ff !important; + /* azul mais claro no hover */ + border-color: #4da6ff !important; + color: #ffffff !important; + box-shadow: 0 0 0 3px rgba(51, 153, 255, 0.4) !important; +} + +/* Estado ativo / clicado */ +body.dark-mode .btn-primary:active, +body.dark-mode .btn.btn-primary:active { + background-color: #1d8bff !important; + border-color: #1d8bff !important; + color: #ffffff !important; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2) !important; +} + +/* Estado desabilitado */ +body.dark-mode .btn-primary:disabled, +body.dark-mode .btn.btn-primary:disabled { + background-color: #1e293b !important; + border-color: #334155 !important; + color: #64748b !important; + opacity: 0.8; +} + +/* Corrige contraste em todos botões azuis */ +body.dark-mode .btn-primary span, +body.dark-mode .btn-primary i, +body.dark-mode .btn-primary svg { + color: #ffffff !important; +} + +/* ============ + TÍTULOS, NOMES DE USUÁRIO E TEXTOS IMPORTANTES + ============ */ + +/* Nomes de usuário / headings principais */ +body.dark-mode .user-name, +body.dark-mode .user-name.m-t-0.mb-0, +body.dark-mode .profile-widget .user-name, +body.dark-mode .header-title, +body.dark-mode .page-title { + color: #f8fafc !important; + /* branco puro */ + font-weight: 600 !important; +} + +/* Títulos de cards */ +body.dark-mode .card-title, +body.dark-mode .sdc-card-header .card-title, +body.dark-mode .widget-title, +body.dark-mode .title, +body.dark-mode span.title, +body.dark-mode .page-header .title { + color: #f1f5f9 !important; + font-weight: 600 !important; + text-shadow: 0 0 4px rgba(51, 153, 255, 0.2); +} + +/* Subtítulos e textos menores */ +body.dark-mode .card-subtitle, +body.dark-mode .subtitle, +body.dark-mode .user-role, +body.dark-mode .user-position, +body.dark-mode small, +body.dark-mode .text-muted { + color: #dbeafe !important; + /* azul-claro suave */ +} + +/* Ícones dentro dos títulos */ +body.dark-mode .card-title i, +body.dark-mode .title i, +body.dark-mode .user-name i { + color: #60a5fa !important; +} + +/* Corrige títulos com cor herdada inline */ +body.dark-mode [style*="color: #6c757d"], +body.dark-mode [style*="color:#6c757d"] { + color: #f1f5f9 !important; +} + +/* ============ + MENU TITLE (SIDEBAR) + ============ */ +body.dark-mode .menu-title, +body.dark-mode li.menu-title, +body.dark-mode .sidebar-menu .menu-title { + color: #93c5fd !important; + /* azul-claro sutil */ + font-weight: 600 !important; + letter-spacing: 0.5px; + padding: 16px 16px 8px 16px; +} + +/* Corrige ícones e textos dentro do menu */ +body.dark-mode .sidebar-menu a, +body.dark-mode .sidebar-menu i { + color: #e2e8f0 !important; + transition: all 0.2s ease-in-out; +} + +body.dark-mode .sidebar-menu a:hover, +body.dark-mode .sidebar-menu a:focus, +body.dark-mode .sidebar-menu .active>a { + color: #e2e8f0 !important; + background-color: #0f172a !important; +} + +/* ============ + BOTÕES SDC (FLOAT-RIGHT) + ============ */ +body.dark-mode .sdc-btn.sdc-btn-primary, +body.dark-mode .sdc-btn.sdc-btn-primary.float-right { + background-color: #3399ff !important; + /* azul Mediconnect */ + border-color: #3399ff !important; + color: #ffffff !important; + font-weight: 600 !important; + border-radius: 6px !important; + box-shadow: 0 2px 8px rgba(51, 153, 255, 0.3) !important; + transition: background 0.25s ease, transform 0.15s ease; +} + +body.dark-mode .sdc-btn.sdc-btn-primary:hover, +body.dark-mode .sdc-btn.sdc-btn-primary.float-right:hover { + background-color: #4da6ff !important; + /* azul mais claro no hover */ + transform: scale(1.03); + color: #fff !important; +} + +body.dark-mode .sdc-btn.sdc-btn-primary:active { + background-color: #1d8bff !important; + border-color: #1d8bff !important; + transform: scale(0.98); +} + +body.dark-mode .sdc-btn.sdc-btn-primary:disabled { + background-color: #1e293b !important; + color: #64748b !important; + opacity: 0.7; + box-shadow: none !important; +} + +/* ============ + BOTÕES INFO (btn-info) + ============ */ +body.dark-mode .btn.btn-sm.btn-info, +body.dark-mode .btn.btn-info { + background-color: #3399ff !important; + /* Azul principal */ + border-color: #3399ff !important; + color: #fff !important; + font-weight: 600; + box-shadow: 0 2px 6px rgba(51, 153, 255, 0.3); + transition: all 0.2s ease-in-out; +} + +body.dark-mode .btn.btn-info:hover, +body.dark-mode .btn.btn-sm.btn-info:hover { + background-color: #4da6ff !important; + /* Azul mais claro */ + transform: scale(1.03); +} + +body.dark-mode .btn.btn-info:active { + background-color: #1d8bff !important; + transform: scale(0.98); +} + +/* ============ + DOC ELEMENTS (geral) + ============ */ +body.dark-mode .doc-content, +body.dark-mode .doc-dash-widget, +body.dark-mode .doc-card, +body.dark-mode .doc-pie-chart-wrapper { + background-color: #1e293b !important; + color: #f1f5f9 !important; + border: 1px solid #334155 !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + transition: all 0.3s ease; +} + +/* Header do card */ +/* ============ + CABEÇALHOS DE CARDS E TÍTULOS (h3, h4, span) + ============ */ + +/* Header do card - fundo igual aos demais headers do sistema */ +body.dark-mode .doc-card-header, +body.dark-mode .card-header, +body.dark-mode .profile-header, +body.dark-mode .sdc-card-header { + background-color: #162033 !important; + border-radius: .5rem !important; + /* azul escuro uniforme */ + color: #f1f5f9 !important; + transition: background-color 0.3s ease, color 0.3s ease; +} + +/* Títulos dentro de qualquer header ou card */ +body.dark-mode h1, +body.dark-mode h2, +body.dark-mode h3, +body.dark-mode h4, +body.dark-mode h5, +body.dark-mode h6, +body.dark-mode .card-title, +body.dark-mode .doc-card-header h3, +body.dark-mode .doc-card-header h4, +body.dark-mode .doc-card-header span, +body.dark-mode .sdc-card-header h3, +body.dark-mode .sdc-card-header span { + color: #e8eefc; + /* branco azulado suave */ + font-weight: 600; +} + +/* Subtítulos ou spans dentro de headers */ +body.dark-mode .doc-card-header span, +body.dark-mode .card-header span, +body.dark-mode .sdc-card-header span { + color: #c7d7ff !important; + /* tom mais claro, legível */ + font-weight: 500; +} + +/* Hover em elementos clicáveis do header */ +body.dark-mode .doc-card-header a:hover, +body.dark-mode .card-header a:hover { + color: #93c5fd !important; + /* azul claro do resto do site */ +} + +/* ============ + BOTÕES DOC PRIMARY + ============ */ +body.dark-mode .doc-btn.doc-btn-primary, +body.dark-mode .doc-btn.doc-btn-primary.float-right { + background-color: #3399ff !important; + border: 1px solid #3399ff !important; + color: #ffffff !important; + border-radius: 6px; + font-weight: 600; + box-shadow: 0 3px 8px rgba(51, 153, 255, 0.25); + transition: background 0.25s ease, transform 0.15s ease; +} + +body.dark-mode .doc-btn.doc-btn-primary:hover, +body.dark-mode .doc-btn.doc-btn-primary.float-right:hover { + background-color: #4da6ff !important; + transform: scale(1.03); +} + +body.dark-mode .doc-btn.doc-btn-primary:active { + background-color: #1d8bff !important; + border-color: #1d8bff !important; + transform: scale(0.98); +} + +/* ============ + ELEMENTOS GRÁFICOS (gráficos e painéis) + ============ */ +body.dark-mode canvas, +body.dark-mode .chart, +body.dark-mode .doc-pie-chart-wrapper canvas { + background-color: #1e293b !important; +} + +body.dark-mode .chartjs-render-monitor { + background: transparent !important; +} + +/* ============ + AJUSTE: sdc-card-header sem fundo escuro + ============ */ +body.dark-mode .sdc-card-header { + background: transparent !important; + /* sem fundo */ + border-bottom: 1px solid #334155 !important; + /* linha suave pra separar */ + color: #e8eefc !important; + /* mantém legível */ + box-shadow: none !important; +} + +/* ============ + BADGES CUSTOMIZADAS + ============ */ +body.dark-mode .custom-badge.status-green { + background-color: #22c55e !important; + /* verde vivo (tailwind emerald-500) */ + color: #ffffff !important; + border: 1px solid #16a34a !important; + /* leve contraste */ + box-shadow: 0 0 6px rgba(34, 197, 94, 0.4); + font-weight: 600; + letter-spacing: 0.2px; +} + +/* Ao passar o mouse */ +body.dark-mode .custom-badge.status-green:hover { + background-color: #16a34a !important; + /* tom mais escuro no hover */ + box-shadow: 0 0 8px rgba(22, 163, 74, 0.6); +} + +/* ============ + DASHBOARD WIDGETS (modo escuro - cores vivas) + ============ */ + +/* Container principal */ +body.dark-mode .dash-widget { + background-color: #1b2638 !important; + /* fundo escuro, levemente azulado */ + color: #ffffff !important; + border: 1px solid #2f3c52 !important; + border-radius: 10px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5); + transition: all 0.3s ease; +} + +/* Título do widget */ +body.dark-mode .widget-title2 { + color: #ffffff !important; + /* branco puro */ + font-weight: 700; + font-size: 13px; + letter-spacing: 0.2px; +} + +/* Informações e números à direita */ +body.dark-mode .dash-widget-info.text-right, +body.dark-mode .dash-widget-info.text-right h3, +body.dark-mode .dash-widget-info.text-right span { + color: #ffffff !important; + font-weight: 700; +} + +/* Ícones ou textos auxiliares dentro do card */ +body.dark-mode .dash-widget i, +body.dark-mode .dash-widget small, +body.dark-mode .dash-widget .subtext { + color: #ffffff !important; +} + +/* Hover leve — realce sutil */ +body.dark-mode .dash-widget:hover { + background-color: #22314b !important; + box-shadow: 0 0 15px rgba(96, 165, 250, 0.25); + transform: translateY(-2px); + transition: all 0.25s ease; +} + +/* ============ + DASHBOARD WIDGETS (texto forte e legível) + ============ */ + +/* Container */ +body.dark-mode .dash-widget { + background-color: #1b2638 !important; + border: 1px solid #2f3c52 !important; + border-radius: 10px; + color: #ffffff !important; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6); + transition: all 0.3s ease; +} + +/* Títulos dos widgets */ +/* ============ + AJUSTE FINAL - widget-title2 + ============ */ + +body.dark-mode .widget-title2, +body.dark-mode .widget-title2 *, +body.dark-mode .dash-widget h3, +body.dark-mode h3.widget-title2 { + color: #ffffff !important; + /* branco puro */ + font-weight: 800 !important; + /* texto mais encorpado */ + text-shadow: 0 0 4px rgba(255, 255, 255, 0.08); + /* brilho suave */ + letter-spacing: 0.3px; + opacity: 1 !important; + /* evita cinza translúcido */ +} + +/* Subtítulos e spans */ +body.dark-mode .dash-widget span, +body.dark-mode .dash-widget small { + color: #cbd5e1 !important; + /* cinza claro legível */ +} + +/* Informações à direita */ +body.dark-mode .dash-widget-info.text-right, +body.dark-mode .dash-widget-info.text-right h3, +body.dark-mode .dash-widget-info.text-right span { + color: #ffffff !important; + font-weight: 700 !important; +} + +/* Hover leve */ +body.dark-mode .dash-widget:hover { + background-color: #24324a !important; + box-shadow: 0 0 12px rgba(96, 165, 250, 0.3); + transform: translateY(-2px); +} + +/* ============ + DOC CARD HEADER (modo escuro limpo) + ============ */ +body.dark-mode .doc-card-header { + background: transparent !important; + /* remove o fundo escuro */ + border-bottom: 1px solid #334155 !important; + /* linha discreta */ + color: #ffffff !important; + /* texto branco visível */ + box-shadow: none !important; +} + +/* Ajuste dos títulos dentro do header */ +body.dark-mode .doc-card-header h3, +body.dark-mode .doc-card-header span, +body.dark-mode .doc-card-header strong { + color: #ffffff !important; + /* texto 100% branco */ + font-weight: 700; +} + +/* Se tiver botões dentro do header */ +body.dark-mode .doc-card-header button, +body.dark-mode .doc-card-header a { + background: #2563eb !important; + /* azul consistente */ + color: #fff !important; + border: none !important; + transition: background 0.3s ease; +} + +body.dark-mode .doc-card-header button:hover, +body.dark-mode .doc-card-header a:hover { + background: #3b82f6 !important; + /* azul mais claro ao passar o mouse */ +} + +/* ============ + CHATBOT (modo escuro) + ============ */ + +/* Corpo do chat */ +body.dark-mode .chat-body { + background-color: #0f172a !important; + /* fundo escuro principal */ + color: #f1f5f9 !important; + /* texto claro */ + border-top: 1px solid #334155 !important; + border-bottom: 1px solid #334155 !important; + scrollbar-color: #475569 #1e293b; + /* cores da barra de rolagem */ +} + +/* Mensagens do usuário e do bot */ +body.dark-mode .chat-body .message-text { + color: #f1f5f9 !important; + /* texto claro legível */ +} + +body.dark-mode .chat-body .user-message .message-text { + background-color: #2563eb !important; + /* azul consistente */ + color: #ffffff !important; + border-radius: 10px 10px 0 10px; +} + +body.dark-mode .chat-body .bot-message .message-text { + background-color: #1e293b !important; + /* cinza azulado escuro */ + color: #e2e8f0 !important; + border-radius: 10px 10px 10px 0; +} + +/* Rodapé (área de digitação) */ +body.dark-mode .chat-footer { + background-color: #1e293b !important; + /* mesmo tom do restante */ + border-top: 1px solid #334155 !important; + padding: 10px; +} + +/* Campo de entrada de mensagem */ +body.dark-mode .message-input { + background-color: #0f172a !important; + /* fundo escuro */ + color: #ffffff !important; + /* texto branco */ + border: 1px solid #3b82f6 !important; + /* azul no contorno */ + border-radius: 8px; + padding: 8px 12px; + transition: all 0.2s ease; +} + +body.dark-mode .message-input::placeholder { + color: #94a3b8 !important; + /* placeholder cinza claro */ +} + +body.dark-mode .message-input:focus { + outline: none; + border-color: #60a5fa !important; + /* azul mais claro ao focar */ + box-shadow: 0 0 6px rgba(96, 165, 250, 0.4); +} + +/* Botão de envio */ +body.dark-mode .chat-footer button { + background-color: #2563eb !important; + color: #fff !important; + border: none !important; + border-radius: 8px; + padding: 8px 10px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +body.dark-mode .chat-footer button:hover { + background-color: #3b82f6 !important; +} + +/* Ícone do botão (seta) */ +body.dark-mode .chat-footer button .material-symbols-rounded { + color: #fff !important; + font-size: 20px; + } + +.acc-btn-read { + width: 100%; + padding: 10px 14px; + border: none; + border-radius: 8px; + background-color: #f4f4f4; + color: #222; + font-size: 0.95rem; + cursor: pointer; + transition: all 0.25s ease; + font-weight: 500; +} + +.acc-btn-read:hover { + background-color: #e6e6e6; + +} + +.acc-btn-read { + width: 100%; + padding: 10px 14px; + border: none; + border-radius: 8px; + background-color: #f4f4f4; + color: #222; + font-size: 0.95rem; + cursor: pointer; + transition: all 0.25s ease; + font-weight: 500; +} + +.acc-btn-read:hover { + background-color: #e6e6e6; +} + + +.acc-btn-read.active { + background-color: #009EFB; + color: white; + box-shadow: 0 0 8px rgb(45, 116, 207); + transform: scale(1.02); +} + +/* Calendário da Dashboard */ +.dark-mode .calendar-container{ + background-color: #1e293b !important; +} + +.dark-mode .card-block{ + background-color: #009EFB; + color: white; + box-shadow: 0 0 8px rgb(45, 116, 207); + transform: scale(1.02); +} + +/* Calendário da Dashboard */ +.dark-mode .calendar-container { + background-color: #1e293b !important; +} + +.dark-mode .card-block { + background-color: #0f172a !important; +} + +/* Lista do MuiTabs */ +.dark-mode .MuiTabs-list{ + background-color: #1e293b !important; +} + +.dark-mode .MuiTabs-list{ + background-color: #1e293b !important; +} + +.dark-mode .MuiPaper-root{ + background-color: #1e293b !important; +} + +.dark-mode .MuiTypography-root{ + color: #e8eefc !important; +} + +.dark-mode .form-control{ + background-color: #1e293b; + border-color: #1e293b; + color: #e8eefc; +} + +/* ============ + SWITCH MODERNO (Modo Escuro) + ============ */ +.acc-switch { + position: relative; + display: inline-flex; + align-items: center; + gap: 12px; + cursor: pointer; + user-select: none; +} + +.acc-switch input { + opacity: 0; + width: 0; + height: 0; +} + +/* O trilho do botão */ +.acc-slider { + position: relative; + width: 46px; + height: 26px; + background-color: #d1d5db; + /* cinza claro */ + border-radius: 34px; + transition: all 0.3s ease; + box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.15); +} + +/* A bolinha */ +.acc-slider::before { + content: ""; + position: absolute; + height: 20px; + width: 20px; + left: 3px; + top: 3px; + background-color: white; + border-radius: 50%; + transition: all 0.3s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +/* Quando ativado */ +.acc-switch input:checked+.acc-slider { + background-color: #2563eb; + /* azul principal */ + box-shadow: 0 0 6px rgba(37, 99, 235, 0.6); +} + +.acc-switch input:checked+.acc-slider::before { + transform: translateX(20px); +} + +/* Legenda ao lado */ +.acc-label { + font-size: 15px; + color: #111827; + transition: color 0.3s ease; +} + +/* Modo escuro */ +body.dark-mode .acc-label { + color: #e2e8f0; +} + +/* ============ + MODO DALTÔNICO (versão visual aprimorada) + ============ */ +body.daltonism-mode { + --blue-safe: #1e90ff; + /* Azul forte e seguro */ + --green-safe: #1fab89; + /* Verde água legível */ + --yellow-safe: #f4c542; + /* Amarelo quente */ + --red-safe: #d64550; + /* Vermelho claro e não saturado */ + --bg-safe: #f8fbff; + /* Fundo neutro */ + --text-safe: #0f172a; + /* Texto escuro visível */ + --card-safe: #ffffff; + --border-safe: #cfd9e4; + + background-color: var(--bg-safe); + color: var(--text-safe); + transition: background-color 0.4s ease, color 0.4s ease; +} + +/* 🔹 Mantém cartões e áreas brancas legíveis */ +body.daltonism-mode .card, +body.daltonism-mode .card-box, +body.daltonism-mode .profile-widget, +body.daltonism-mode .acc-panel { + background-color: var(--card-safe); + border: 1px solid var(--border-safe); + color: var(--text-safe); + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.06); +} + +/* 🔹 Links, botões e ações primárias */ +body.daltonism-mode a, +body.daltonism-mode button, +body.daltonism-mode .btn, +body.daltonism-mode .acc-btn { + background-color: var(--blue-safe) !important; + color: #fff !important; + border: none !important; + transition: all 0.25s ease; +} + +body.daltonism-mode a:hover, +body.daltonism-mode button:hover, +body.daltonism-mode .btn:hover { + background-color: #4aa4ff !important; +} + +/* 🔹 Status e alertas */ +body.daltonism-mode .status-green, +body.daltonism-mode .custom-badge.status-green { + background-color: var(--green-safe) !important; + color: #fff !important; +} + +body.daltonism-mode .status-yellow { + background-color: var(--yellow-safe) !important; + color: #000 !important; +} + +body.daltonism-mode .status-red, +body.daltonism-mode .custom-badge.status-red { + background-color: var(--red-safe) !important; + color: #fff !important; +} + +/* 🔹 Títulos e cabeçalhos */ +body.daltonism-mode h1, +body.daltonism-mode h2, +body.daltonism-mode h3, +body.daltonism-mode h4, +body.daltonism-mode h5, +body.daltonism-mode h6, +body.daltonism-mode .widget-title2, +body.daltonism-mode .card-title, +body.daltonism-mode .title, +body.daltonism-mode th { + color: var(--text-safe) !important; + font-weight: 600; +} + +/* 🔹 Tabelas */ +body.daltonism-mode table { + background-color: var(--card-safe); + border-collapse: collapse; + border: 1px solid var(--border-safe); +} + +body.daltonism-mode th, +body.daltonism-mode td { + border: 1px solid var(--border-safe); + color: var(--text-safe); + background-color: #ffffff; +} + +body.daltonism-mode thead { + background-color: #e6f0ff; +} + +/* 🔹 Ícones e detalhes */ +body.daltonism-mode i, +body.daltonism-mode svg { + color: var(--blue-safe); +} + +/* 🔹 Sidebar e menu */ +body.daltonism-mode .sidebar, +body.daltonism-mode .sidebar-inner { + background-color: #f4f8ff !important; +} + +body.daltonism-mode .menu-title, +body.daltonism-mode .nav-link, +body.daltonism-mode .sidebar a { + color: var(--text-safe) !important; +} + +/* 🔹 Inputs e formulários */ +body.daltonism-mode input, +body.daltonism-mode select, +body.daltonism-mode textarea { + background-color: #ffffff; + color: var(--text-safe); + border: 1px solid var(--border-safe); +} + +body.daltonism-mode input:focus, +body.daltonism-mode select:focus, +body.daltonism-mode textarea:focus { + outline: 2px solid var(--blue-safe); +} + +/* 🔹 Chatbot */ +body.daltonism-mode .chat-body, +body.daltonism-mode .chat-footer { + background-color: #f9fbff; + border-top: 1px solid var(--border-safe); +} + +body.daltonism-mode .message.bot-message { + background-color: #e6f2ff; + color: var(--text-safe); +} + +body.daltonism-mode .message.user-message { + background-color: #d0ebff; + color: var(--text-safe); +} + +body.daltonism-mode .message-input { + background-color: #ffffff; + color: var(--text-safe); + border: 1px solid var(--border-safe); +} + +/* ============ + CONTROLE DE FONTE + ============ */ +.acc-font-controls { + display: flex; + align-items: center; + gap: 6px; +} + +.acc-font-controls button { + background-color: #3399ff; + color: #fff; + border: none; + border-radius: 6px; + font-weight: bold; + font-size: 14px; + padding: 5px 9px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.acc-font-controls button:hover { + background-color: #4da6ff; +} + +body.dark-mode .acc-font-controls button { + background-color: #2563eb; +} + +body.dark-mode .acc-font-controls button:hover { + background-color: #3b82f6; +} + +/* ========= SWEETALERT2 - ÍCONES NO MODO ESCURO ========= */ + +/* Base dos ícones */ +body.dark-mode .swal2-icon { + border-color: #60a5fa !important; /* azul suave do tema */ + color: #60a5fa !important; + box-shadow: 0 0 12px rgba(96, 165, 250, 0.4); +} + +/* 🟢 Sucesso */ +body.dark-mode .swal2-icon.swal2-success { + border-color: #22c55e !important; + color: #22c55e !important; + box-shadow: 0 0 12px rgba(34, 197, 94, 0.4); +} +body.dark-mode .swal2-success-ring { + border: 0.25em solid rgba(34, 197, 94, 0.3) !important; +} +body.dark-mode .swal2-success-line-tip, +body.dark-mode .swal2-success-line-long { + background-color: #22c55e !important; +} + +/* 🔴 Erro */ +body.dark-mode .swal2-icon.swal2-error { + border-color: #ef4444 !important; + color: #ef4444 !important; + box-shadow: 0 0 12px rgba(239, 68, 68, 0.4); +} +body.dark-mode .swal2-x-mark-line-left, +body.dark-mode .swal2-x-mark-line-right { + background-color: #ef4444 !important; +} + +/* 🟡 Aviso */ +body.dark-mode .swal2-icon.swal2-warning { + border-color: #facc15 !important; + color: #facc15 !important; + box-shadow: 0 0 10px rgba(250, 204, 21, 0.5); +} + +/* 🔵 Info */ +body.dark-mode .swal2-icon.swal2-info { + border-color: #3b82f6 !important; + color: #3b82f6 !important; + box-shadow: 0 0 10px rgba(59, 130, 246, 0.4); +} + +/* ❓ Pergunta */ +body.dark-mode .swal2-icon.swal2-question { + border-color: #93c5fd !important; + color: #93c5fd !important; + box-shadow: 0 0 10px rgba(147, 197, 253, 0.4); +} + +/* 🔹 Botões refinados */ +body.dark-mode .swal2-confirm { + background-color: #3399ff !important; + border: none !important; + color: #ffffff !important; + font-weight: 600 !important; + text-shadow: 0 0 3px rgba(0, 0, 0, 0.2); +} + +body.dark-mode .swal2-cancel { + background-color: #475569 !important; + border: none !important; + color: #f1f5f9 !important; +} +body.dark-mode .swal2-cancel:hover { + background-color: #64748b !important; +} + +/* 🔹 Fundo e texto */ +body.dark-mode .swal2-popup { + background-color: #1e293b !important; + color: #e2e8f0 !important; + border: 1px solid #334155 !important; + box-shadow: 0 0 25px rgba(51, 153, 255, 0.25); +} +body.dark-mode .swal2-title { + color: #f8fafc !important; +} +body.dark-mode .swal2-html-container { + color: #e2e8f0 !important; +} + +.dark-mode .health-tip{ + background-color: #0f172a !important; +} + +.dark-mode .d-flex{ + background-color: #0f172a !important; +} + diff --git a/src/assets/css/fullcalendar.min.css b/src/assets/css/fullcalendar.min.css index c9a0805..6e5fbb8 100644 --- a/src/assets/css/fullcalendar.min.css +++ b/src/assets/css/fullcalendar.min.css @@ -2,4 +2,4 @@ * FullCalendar v3.7.0 Stylesheet * Docs & License: https://fullcalendar.io/ * (c) 2017 Adam Shaw - */.fc button,.fc table,body .fc{font-size:1em}.fc-bg,.fc-row .fc-bgevent-skeleton,.fc-row .fc-highlight-skeleton{bottom:0}.fc-icon,.fc-unselectable{-webkit-touch-callout:none;-khtml-user-select:none}.fc{direction:ltr;text-align:left}.fc-rtl{text-align:right}.fc th,.fc-basic-view td.fc-week-number,.fc-icon,.fc-toolbar{text-align:center}.fc-highlight{background:#bce8f1;opacity:.3}.fc-bgevent{background:#8fdf82;opacity:.3}.fc-nonbusiness{background:#d7d7d7}.fc button{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;height:2.1em;padding:0 .6em;white-space:nowrap;cursor:pointer}.fc button::-moz-focus-inner{margin:0;padding:0}.fc-state-default{border:1px solid;background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);color:#333;text-shadow:0 1px 1px rgba(255,255,255,.75);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05)}.fc-state-default.fc-corner-left{border-top-left-radius:4px;border-bottom-left-radius:4px}.fc-state-default.fc-corner-right{border-top-right-radius:4px;border-bottom-right-radius:4px}.fc button .fc-icon{position:relative;top:-.05em;margin:0 .2em;vertical-align:middle}.fc-state-active,.fc-state-disabled,.fc-state-down,.fc-state-hover{color:#333;background-color:#e6e6e6}.fc-state-hover{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.fc-state-active,.fc-state-down{background-color:#ccc;background-image:none;box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.fc-state-disabled{cursor:default;background-image:none;opacity:.65;box-shadow:none}.fc-event.fc-draggable,.fc-event[href],.fc-popover .fc-header .fc-close,a[data-goto]{cursor:pointer}.fc-button-group{display:inline-block}.fc .fc-button-group>*{float:left;margin:0 0 0 -1px}.fc .fc-button-group>:first-child{margin-left:0}.fc-popover{position:absolute;box-shadow:0 2px 6px rgba(0,0,0,.15)}.fc-popover .fc-header{padding:2px 4px}.fc-popover .fc-header .fc-title{margin:0 2px}.fc-ltr .fc-popover .fc-header .fc-title,.fc-rtl .fc-popover .fc-header .fc-close{float:left}.fc-ltr .fc-popover .fc-header .fc-close,.fc-rtl .fc-popover .fc-header .fc-title{float:right}.fc-divider{border-style:solid;border-width:1px}hr.fc-divider{height:0;margin:0;padding:0 0 2px;border-width:1px 0}.fc-bg table,.fc-row .fc-bgevent-skeleton table,.fc-row .fc-highlight-skeleton table{height:100%}.fc-clear{clear:both}.fc-bg,.fc-bgevent-skeleton,.fc-helper-skeleton,.fc-highlight-skeleton{position:absolute;top:0;left:0;right:0}.fc table{width:100%;box-sizing:border-box;table-layout:fixed;border-collapse:collapse;border-spacing:0}.fc td,.fc th{border-style:solid;border-width:1px;padding:0;vertical-align:top}.fc td.fc-today{border-style:double}a[data-goto]:hover{text-decoration:underline}.fc .fc-row{border-style:solid;border-width:0}.fc-row table{border-left:0 hidden transparent;border-right:0 hidden transparent;border-bottom:0 hidden transparent}.fc-row:first-child table{border-top:0 hidden transparent}.fc-row{position:relative}.fc-row .fc-bg{z-index:1}.fc-row .fc-bgevent-skeleton td,.fc-row .fc-highlight-skeleton td{border-color:transparent}.fc-row .fc-bgevent-skeleton{z-index:2}.fc-row .fc-highlight-skeleton{z-index:3}.fc-row .fc-content-skeleton{position:relative;z-index:4;padding-bottom:2px}.fc-row .fc-helper-skeleton{z-index:5}.fc .fc-row .fc-content-skeleton table,.fc .fc-row .fc-content-skeleton td,.fc .fc-row .fc-helper-skeleton td{background:0 0;border-color:transparent}.fc-row .fc-content-skeleton td,.fc-row .fc-helper-skeleton td{border-bottom:0}.fc-row .fc-content-skeleton tbody td,.fc-row .fc-helper-skeleton tbody td{border-top:0}.fc-scroller{-webkit-overflow-scrolling:touch}.fc-icon,.fc-row.fc-rigid,.fc-time-grid-event{overflow:hidden}.fc-scroller>.fc-day-grid,.fc-scroller>.fc-time-grid{position:relative;width:100%}.fc-event{position:relative;display:block;font-size:.85em;line-height:1.3;border-radius:3px;border:1px solid #3a87ad}.fc-event,.fc-event-dot{background-color:#3a87ad}.fc-event,.fc-event:hover{color:#fff;text-decoration:none}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc-event .fc-bg{z-index:1;background:#fff;opacity:.25}.fc-event .fc-content{position:relative;z-index:2}.fc-event .fc-resizer{position:absolute;z-index:4;display:none}.fc-event.fc-allow-mouse-resize .fc-resizer,.fc-event.fc-selected .fc-resizer{display:block}.fc-event.fc-selected .fc-resizer:before{content:"";position:absolute;z-index:9999;top:50%;left:50%;width:40px;height:40px;margin-left:-20px;margin-top:-20px}.fc-event.fc-selected{z-index:9999!important;box-shadow:0 2px 5px rgba(0,0,0,.2)}.fc-event.fc-selected.fc-dragging{box-shadow:0 2px 7px rgba(0,0,0,.3)}.fc-h-event.fc-selected:before{content:"";position:absolute;z-index:3;top:-10px;bottom:-10px;left:0;right:0}.fc-ltr .fc-h-event.fc-not-start,.fc-rtl .fc-h-event.fc-not-end{margin-left:0;border-left-width:0;padding-left:1px;border-top-left-radius:0;border-bottom-left-radius:0}.fc-ltr .fc-h-event.fc-not-end,.fc-rtl .fc-h-event.fc-not-start{margin-right:0;border-right-width:0;padding-right:1px;border-top-right-radius:0;border-bottom-right-radius:0}.fc-ltr .fc-h-event .fc-start-resizer,.fc-rtl .fc-h-event .fc-end-resizer{cursor:w-resize;left:-1px}.fc-ltr .fc-h-event .fc-end-resizer,.fc-rtl .fc-h-event .fc-start-resizer{cursor:e-resize;right:-1px}.fc-h-event.fc-allow-mouse-resize .fc-resizer{width:7px;top:-1px;bottom:-1px}.fc-h-event.fc-selected .fc-resizer{border-radius:4px;border-width:1px;width:6px;height:6px;border-style:solid;border-color:inherit;background:#fff;top:50%;margin-top:-4px}.fc-ltr .fc-h-event.fc-selected .fc-start-resizer,.fc-rtl .fc-h-event.fc-selected .fc-end-resizer{margin-left:-4px}.fc-ltr .fc-h-event.fc-selected .fc-end-resizer,.fc-rtl .fc-h-event.fc-selected .fc-start-resizer{margin-right:-4px}.fc-day-grid-event{margin:1px 2px 0;padding:0 1px}tr:first-child>td>.fc-day-grid-event{margin-top:2px}.fc-day-grid-event.fc-selected:after{content:"";position:absolute;z-index:1;top:-1px;right:-1px;bottom:-1px;left:-1px;background:#000;opacity:.25}.fc-day-grid-event .fc-content{white-space:nowrap;overflow:hidden}.fc-day-grid-event .fc-time{font-weight:700}.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer,.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer{margin-left:-2px}.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer,.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer{margin-right:-2px}a.fc-more{margin:1px 3px;font-size:.85em;cursor:pointer;text-decoration:none}a.fc-more:hover{text-decoration:underline}.fc.fc-bootstrap3 a,.ui-widget .fc-event{text-decoration:none}.fc-limited{display:none}.fc-icon,.fc-toolbar .fc-center{display:inline-block}.fc-day-grid .fc-row{z-index:1}.fc-more-popover{z-index:2;width:220px}.fc-more-popover .fc-event-container{padding:10px}.fc-now-indicator{position:absolute;border:0 solid red}.fc-icon:after,.fc-toolbar button{position:relative}.fc-unselectable{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.fc-unthemed .fc-content,.fc-unthemed .fc-divider,.fc-unthemed .fc-list-heading td,.fc-unthemed .fc-list-view,.fc-unthemed .fc-popover,.fc-unthemed .fc-row,.fc-unthemed tbody,.fc-unthemed td,.fc-unthemed th,.fc-unthemed thead{border-color:#ddd}.fc-unthemed .fc-popover{background-color:#fff;border-width:1px;border-style:solid}.fc-unthemed .fc-divider,.fc-unthemed .fc-list-heading td,.fc-unthemed .fc-popover .fc-header{background:#eee}.fc-unthemed td.fc-today{background:#fcf8e3}.fc-unthemed .fc-disabled-day{background:#d7d7d7;opacity:.3}.fc-icon{height:1em;line-height:1em;font-size:1em;font-family:"Courier New",Courier,monospace;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fc-icon-left-single-arrow:after{content:"\02039";font-weight:700;font-size:200%;top:-7%}.fc-icon-right-single-arrow:after{content:"\0203A";font-weight:700;font-size:200%;top:-7%}.fc-icon-left-double-arrow:after{content:"\000AB";font-size:160%;top:-7%}.fc-icon-right-double-arrow:after{content:"\000BB";font-size:160%;top:-7%}.fc-icon-left-triangle:after{content:"\25C4";font-size:125%;top:3%}.fc-icon-right-triangle:after{content:"\25BA";font-size:125%;top:3%}.fc-icon-down-triangle:after{content:"\25BC";font-size:125%;top:2%}.fc-icon-x:after{content:"\000D7";font-size:200%;top:6%}.fc-unthemed .fc-popover .fc-header .fc-close{color:#666;font-size:.9em;margin-top:2px}.fc-unthemed .fc-list-item:hover td{background-color:#f5f5f5}.ui-widget .fc-disabled-day{background-image:none}.fc-bootstrap3 .fc-time-grid .fc-slats table,.fc-time-grid .fc-slats .ui-widget-content{background:0 0}.fc-popover>.ui-widget-header+.ui-widget-content{border-top:0}.ui-widget .fc-event{color:#fff;font-weight:400}.ui-widget td.fc-axis{font-weight:400}.fc.fc-bootstrap3 a[data-goto]:hover{text-decoration:underline}.fc-bootstrap3 hr.fc-divider{border-color:inherit}.fc-bootstrap3 .fc-today.alert{border-radius:0}.fc-bootstrap3 .fc-popover .panel-body{padding:0}.fc-toolbar.fc-header-toolbar{margin-bottom:1em}.fc-toolbar.fc-footer-toolbar{margin-top:1em}.fc-toolbar .fc-left{float:left}.fc-toolbar .fc-right{float:right}.fc .fc-toolbar>*>*{float:left;margin-left:.75em}.fc .fc-toolbar>*>:first-child{margin-left:0}.fc-toolbar h2{margin:0}.fc-toolbar .fc-state-hover,.fc-toolbar .ui-state-hover{z-index:2}.fc-toolbar .fc-state-down{z-index:3}.fc-toolbar .fc-state-active,.fc-toolbar .ui-state-active{z-index:4}.fc-toolbar button:focus{z-index:5}.fc-view-container *,.fc-view-container :after,.fc-view-container :before{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.fc-view,.fc-view>table{position:relative;z-index:1}.fc-basicDay-view .fc-content-skeleton,.fc-basicWeek-view .fc-content-skeleton{padding-bottom:1em}.fc-basic-view .fc-body .fc-row{min-height:4em}.fc-row.fc-rigid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-day-top.fc-other-month{opacity:.3}.fc-basic-view .fc-day-number,.fc-basic-view .fc-week-number{padding:2px}.fc-basic-view th.fc-day-number,.fc-basic-view th.fc-week-number{padding:0 2px}.fc-ltr .fc-basic-view .fc-day-top .fc-day-number{float:right}.fc-rtl .fc-basic-view .fc-day-top .fc-day-number{float:left}.fc-ltr .fc-basic-view .fc-day-top .fc-week-number{float:left;border-radius:0 0 3px}.fc-rtl .fc-basic-view .fc-day-top .fc-week-number{float:right;border-radius:0 0 0 3px}.fc-basic-view .fc-day-top .fc-week-number{min-width:1.5em;text-align:center;background-color:#f2f2f2;color:grey}.fc-basic-view td.fc-week-number>*{display:inline-block;min-width:1.25em}.fc-agenda-view .fc-day-grid{position:relative;z-index:2}.fc-agenda-view .fc-day-grid .fc-row{min-height:3em}.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton{padding-bottom:1em}.fc .fc-axis{vertical-align:middle;padding:0 4px;white-space:nowrap}.fc-ltr .fc-axis{text-align:right}.fc-rtl .fc-axis{text-align:left}.fc-time-grid,.fc-time-grid-container{position:relative;z-index:1}.fc-time-grid{min-height:100%}.fc-time-grid table{border:0 hidden transparent}.fc-time-grid>.fc-bg{z-index:1}.fc-time-grid .fc-slats,.fc-time-grid>hr{position:relative;z-index:2}.fc-time-grid .fc-content-col{position:relative}.fc-time-grid .fc-content-skeleton{position:absolute;z-index:3;top:0;left:0;right:0}.fc-time-grid .fc-business-container{position:relative;z-index:1}.fc-time-grid .fc-bgevent-container{position:relative;z-index:2}.fc-time-grid .fc-highlight-container{z-index:3;position:relative}.fc-time-grid .fc-event-container{position:relative;z-index:4}.fc-time-grid .fc-now-indicator-line{z-index:5}.fc-time-grid .fc-helper-container{position:relative;z-index:6}.fc-time-grid .fc-slats td{height:1.5em;border-bottom:0}.fc-time-grid .fc-slats .fc-minor td{border-top-style:dotted}.fc-time-grid .fc-highlight{position:absolute;left:0;right:0}.fc-ltr .fc-time-grid .fc-event-container{margin:0 2.5% 0 2px}.fc-rtl .fc-time-grid .fc-event-container{margin:0 2px 0 2.5%}.fc-time-grid .fc-bgevent,.fc-time-grid .fc-event{position:absolute;z-index:1}.fc-time-grid .fc-bgevent{left:0;right:0}.fc-v-event.fc-not-start{border-top-width:0;padding-top:1px;border-top-left-radius:0;border-top-right-radius:0}.fc-v-event.fc-not-end{border-bottom-width:0;padding-bottom:1px;border-bottom-left-radius:0;border-bottom-right-radius:0}.fc-time-grid-event.fc-selected{overflow:visible}.fc-time-grid-event.fc-selected .fc-bg{display:none}.fc-time-grid-event .fc-content{overflow:hidden}.fc-time-grid-event .fc-time,.fc-time-grid-event .fc-title{padding:0 1px}.fc-time-grid-event .fc-time{font-size:.85em;white-space:nowrap}.fc-time-grid-event.fc-short .fc-content{white-space:nowrap}.fc-time-grid-event.fc-short .fc-time,.fc-time-grid-event.fc-short .fc-title{display:inline-block;vertical-align:top}.fc-time-grid-event.fc-short .fc-time span{display:none}.fc-time-grid-event.fc-short .fc-time:before{content:attr(data-start)}.fc-time-grid-event.fc-short .fc-time:after{content:"\000A0-\000A0"}.fc-time-grid-event.fc-short .fc-title{font-size:.85em;padding:0}.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer{left:0;right:0;bottom:0;height:8px;overflow:hidden;line-height:8px;font-size:11px;font-family:monospace;text-align:center;cursor:s-resize}.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer:after{content:"="}.fc-time-grid-event.fc-selected .fc-resizer{border-radius:5px;border-width:1px;width:8px;height:8px;border-style:solid;border-color:inherit;background:#fff;left:50%;margin-left:-5px;bottom:-5px}.fc-time-grid .fc-now-indicator-line{border-top-width:1px;left:0;right:0}.fc-time-grid .fc-now-indicator-arrow{margin-top:-5px}.fc-ltr .fc-time-grid .fc-now-indicator-arrow{left:0;border-width:5px 0 5px 6px;border-top-color:transparent;border-bottom-color:transparent}.fc-rtl .fc-time-grid .fc-now-indicator-arrow{right:0;border-width:5px 6px 5px 0;border-top-color:transparent;border-bottom-color:transparent}.fc-event-dot{display:inline-block;width:10px;height:10px;border-radius:5px}.fc-rtl .fc-list-view{direction:rtl}.fc-list-view{border-width:1px;border-style:solid}.fc .fc-list-table{table-layout:auto}.fc-list-table td{border-width:1px 0 0;padding:8px 14px}.fc-list-table tr:first-child td{border-top-width:0}.fc-list-heading{border-bottom-width:1px}.fc-list-heading td{font-weight:700}.fc-ltr .fc-list-heading-main{float:left}.fc-ltr .fc-list-heading-alt,.fc-rtl .fc-list-heading-main{float:right}.fc-rtl .fc-list-heading-alt{float:left}.fc-list-item.fc-has-url{cursor:pointer}.fc-list-item-marker,.fc-list-item-time{white-space:nowrap;width:1px}.fc-ltr .fc-list-item-marker{padding-right:0}.fc-rtl .fc-list-item-marker{padding-left:0}.fc-list-item-title a{text-decoration:none;color:inherit}.fc-list-item-title a[href]:hover{text-decoration:underline}.fc-list-empty-wrap2{position:absolute;top:0;left:0;right:0;bottom:0}.fc-list-empty-wrap1{width:100%;height:100%;display:table}.fc-list-empty{display:table-cell;vertical-align:middle;text-align:center}.fc-unthemed .fc-list-empty{background-color:#eee} + */fc-content-col{position:relative}.fc-time-grid .fc-content-skeleton{position:absolute;z-index:3;top:0;left:0;right:0}.fc-time-grid .fc-business-container{position:relative;z-index:1}.fc-time-grid .fc-bgevent-container{position:relative;z-index:2}.fc-time-grid .fc-highlight-container{z-index:3;position:relative}.fc-time-grid .fc-event-container{position:relative;z-index:4}.fc-time-grid .fc-now-indicator-line{z-index:5}.fc-time-grid .fc-helper-container{position:relative;z-index:6}.fc-time-grid .fc-slats td{ height:1em !important;border-bottom:0;}.fc-time-grid .fc-slats .fc-minor td{border-top-style:dotted}.fc-time-grid .fc-highlight{position:absolute;left:0;right:0}.fc-ltr .fc-time-grid .fc-event-container{margin:0 2.5% 0 2px}.fc-rtl .fc-time-grid .fc-event-container{margin:0 2px 0 2.5%}.fc-time-grid .fc-bgevent,.fc-time-grid .fc-event{position:absolute;z-index:1}.fc-time-grid .fc-bgevent{left:0;right:0}.fc-v-event.fc-not-start{border-top-width:0;padding-top:1px;border-top-left-radius:0;border-top-right-radius:0}.fc-v-event.fc-not-end{border-bottom-width:0;padding-bottom:1px;border-bottom-left-radius:0;border-bottom-right-radius:0}.fc-time-grid-event.fc-selected{overflow:visible}.fc-time-grid-event.fc-selected .fc-bg{display:none}.fc-time-grid-event .fc-content{overflow:hidden}.fc-time-grid-event .fc-time,.fc-time-grid-event .fc-title{padding:0 1px}.fc-time-grid-event .fc-time{font-size:.85em;white-space:nowrap}.fc-time-grid-event.fc-short .fc-content{white-space:nowrap}.fc-time-grid-event.fc-short .fc-time,.fc-time-grid-event.fc-short .fc-title{display:inline-block;vertical-align:top}.fc-time-grid-event.fc-short .fc-time span{display:none}.fc-time-grid-event.fc-short .fc-time:before{content:attr(data-start)}.fc-time-grid-event.fc-short .fc-time:after{content:"\000A0-\000A0"}.fc-time-grid-event.fc-short .fc-title{font-size:.85em;padding:0}.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer{left:0;right:0;bottom:0;height:8px;overflow:hidden;line-height:8px;font-size:11px;font-family:monospace;text-align:center;cursor:s-resize}.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer:after{content:"="}.fc-time-grid-event.fc-selected .fc-resizer{border-radius:5px;border-width:1px;width:8px;height:8px;border-style:solid;border-color:inherit;background:#fff;left:50%;margin-left:-5px;bottom:-5px}.fc-time-grid .fc-now-indicator-line{border-top-width:1px;left:0;right:0}.fc-time-grid .fc-now-indicator-arrow{margin-top:-5px}.fc-ltr .fc-time-grid .fc-now-indicator-arrow{left:0;border-width:5px 0 5px 6px;border-top-color:transparent;border-bottom-color:transparent}.fc-rtl .fc-time-grid .fc-now-indicator-arrow{right:0;border-width:5px 6px 5px 0;border-top-color:transparent;border-bottom-color:transparent}.fc-event-dot{display:inline-block;width:10px;height:10px;border-radius:5px}.fc-rtl .fc-list-view{direction:rtl}.fc-list-view{border-width:1px;border-style:solid}.fc .fc-list-table{table-layout:auto}.fc-list-table td{border-width:1px 0 0;padding:8px 14px}.fc-list-table tr:first-child td{border-top-width:0}.fc-list-heading{border-bottom-width:1px}.fc-list-heading td{font-weight:700}.fc-ltr .fc-list-heading-main{float:left}.fc-ltr .fc-list-heading-alt,.fc-rtl .fc-list-heading-main{float:right}.fc-rtl .fc-list-heading-alt{float:left}.fc-list-item.fc-has-url{cursor:pointer}.fc-list-item-marker,.fc-list-item-time{white-space:nowrap;width:1px}.fc-ltr .fc-list-item-marker{padding-right:0}.fc-rtl .fc-list-item-marker{padding-left:0}.fc-list-item-title a{text-decoration:none;color:inherit}.fc-list-item-title a[href]:hover{text-decoration:underline}.fc-list-empty-wrap2{position:absolute;top:0;left:0;right:0;bottom:0}.fc-list-empty-wrap1{width:100%;height:100%;display:table}.fc-list-empty{display:table-cell;vertical-align:middle;text-align:center}.fc-unthemed .fc-list-empty{background-color:#eee} .fc-timegrid-slot { height: 20px !important;line-height: 20px !important;padding: 0 !important;}.fc-timegrid-slot-label-cushion[data-time$="00"] {border-bottom: 1px solid #fff !important;}.fc-timegrid-slot-label-cushion[data-time$="30"] {border-bottom: none !important;} \ No newline at end of file diff --git a/src/assets/css/hospital.css b/src/assets/css/hospital.css new file mode 100644 index 0000000..2a5b76a --- /dev/null +++ b/src/assets/css/hospital.css @@ -0,0 +1,483 @@ +/* ======== CONFIGURAÇÕES GERAIS ======== */ +body{ + margin: 0; + padding: 0 !important; + font-family: 'Poppins', sans-serif; + scroll-behavior: smooth; + background-color: #f9fbfc; + color: #333; +} + +.hospital-container { + width: 90%; + max-width: 1200px; + margin: 0 auto; +} + +h1, h2, h3, h4 { + color: #003366; + margin-bottom: 0.5rem; +} + +p { + line-height: 1.6; + color: #555; +} + +/* ======== HEADER ======== */ +.hospital-landing-header { + position: fixed; + top: 0; + width: 100%; + background: linear-gradient(135deg, #004a99, #0077cc); + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15); + z-index: 1000; + transition: all 0.3s ease-in-out; +} + +.hospital-header-content { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1rem 0; +} + +.hospital-logo-area { + display: flex; + align-items: center; + gap: 0.6rem; + margin-left: -200px; +} + +.hospital-logo-area img { + width: 44px; + height: 44px; + filter: brightness(0) invert(1); +} + +.hospital-logo-area h1 { + font-size: 1.6rem; + font-weight: 600; + color: #ffffff; +} + +.hospital-nav-links { + display: flex; + align-items: center; + gap: 2rem; +} + +.hospital-nav-links a { + text-decoration: none; + color: #ffffff; + font-weight: 500; + position: relative; + transition: 0.3s ease; +} + +.hospital-nav-links a::after { + content: ""; + position: absolute; + bottom: -4px; + left: 0; + width: 0; + height: 2px; + background: #ffffff; + transition: width 0.3s ease; +} + +.hospital-nav-links a:hover::after { + width: 100%; +} + +.hospital-btn-login { + display: inline-block; + padding: 0.6rem 1.4rem; + background-color: #ffffff !important; + color: #0077cc !important; + border-radius: 30px; + font-weight: 600; + text-decoration: none; + border: 2px solid #0077cc; + transition: all 0.3s ease; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); +} + +.hospital-btn-login:hover { + transform: translateY(-2px); +} + +.hospital-landing-header.scrolled { + background: linear-gradient(135deg, #003d80, #0065b3); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); +} + +/* ======== HERO ======== */ +.hospital-hero-bg-section { + position: relative; + height: 75vh; + background-image: url("/img/pexels-mart-production-7088498.jpg"); + background-repeat: no-repeat; + background-position: center top; + background-size: cover; + display: flex; + align-items: center; + justify-content: center; + width: 100%; +} + +.hospital-hero-bg-content { + position: relative; + text-align: center; + color: white; + background: rgba(0, 0, 0, 0.4); + padding: 2rem 3rem; + border-radius: 12px; + max-width: 700px; +} + +.hospital-hero-bg-content h1 { + font-size: 2.8rem; + font-weight: 700; + margin-bottom: 1rem; +} + +.hospital-hero-bg-content p { + font-size: 1.2rem; + line-height: 1.6; +} + +/* ======== SOBRE ======== */ +.hospital-about-section { + padding: 5rem 0; + background: #fff; +} + +.hospital-about-content { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 2rem; +} + +.hospital-about-image { + flex: 1; +} + +.hospital-about-image img { + width: 100%; + border-radius: 16px; + box-shadow: 0 5px 20px rgba(0,0,0,0.1); +} + +.hospital-about-text { + flex: 1; + min-width: 300px; +} + +.hospital-about-highlights { + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin-top: 1rem; +} + +.hospital-about-highlights div { + background: #e6f2ff; + color: #005fa3; + padding: 0.5rem 1rem; + border-radius: 8px; + font-weight: 500; +} + +/* ======== ESPECIALIDADES ======== */ +.hospital-specialities-section { + background: #f5faff; + padding: 5rem 0; + text-align: center; +} + +.hospital-section-header h3 { + font-size: 2rem; +} + +.hospital-specialities-grid { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 2rem; + margin-top: 3rem; +} + +.hospital-speciality-card { + background: white; + padding: 1.5rem; + border-radius: 12px; + width: 180px; + text-align: center; + transition: 0.3s; + box-shadow: 0 3px 10px rgba(0,0,0,0.1); +} + +.hospital-speciality-card img { + width: 80px; + height: 80px; + margin-bottom: 1rem; +} + +.hospital-speciality-card:hover { + transform: translateY(-5px); + box-shadow: 0 8px 20px rgba(0,0,0,0.15); +} + +/* ======== MÉDICO GIGANTE ======== */ +.hospital-doctor-highlight-section { + background: #fff; + padding: 6rem 0 8rem; + overflow: hidden; +} + +.hospital-doctor-highlight { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 4rem; +} + +.hospital-doctor-image { + flex: 1; + text-align: center; +} + +.hospital-doctor-image img { + width: 100%; + max-width: 520px; + height: auto; + object-fit: contain; + filter: drop-shadow(0 10px 20px rgba(0, 0, 0, 0.1)); +} + +.hospital-doctor-text { + flex: 1; + min-width: 300px; +} + +.hospital-doctor-text h3 { + font-size: 2rem; + color: #003366; + margin-bottom: 1rem; +} + +.hospital-doctor-text p { + color: #555; + margin-bottom: 2rem; + max-width: 500px; +} + +/* ======== CONTATO ======== */ +.hospital-contact-section { + background: linear-gradient(135deg, #e9f5ff, #f5faff); + padding: 6rem 0; +} + +.hospital-contact-wrapper { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + justify-content: space-between; + gap: 3rem; +} + +.hospital-contact-info { + flex: 1; + min-width: 300px; +} + +.hospital-contact-cards { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.hospital-contact-item { + display: flex; + align-items: center; + background: white; + padding: 1rem 1.5rem; + border-radius: 12px; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); + gap: 1rem; +} + +.hospital-contact-form { + flex: 1; + min-width: 300px; + background: white; + padding: 2rem; + border-radius: 16px; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1); +} + +.hospital-contact-form input, +.hospital-contact-form textarea { + width: 100%; + padding: 0.8rem; + margin-bottom: 1rem; + border: 1px solid #d0d7de; + border-radius: 8px; + font-size: 1rem; +} + +.hospital-contact-form button { + width: 100%; + font-weight: 600; +} + +/* ======== FOOTER ======== */ +.hospital-landing-footer { + background: linear-gradient(135deg, #002b5c, #004c99); + color: white; + text-align: center; + padding: 3rem 0 2rem; +} + +.hospital-footer-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; +} + +.hospital-footer-socials { + display: flex; + gap: 1.2rem; +} + +.hospital-footer-socials a { + color: white; + font-size: 1.4rem; + transition: 0.3s; +} + +.hospital-footer-logo img { + width: 36px; + height: 36px; + filter: brightness(0) invert(1); +} + +/* ========================================================== + RESPONSIVIDADE + ========================================================== */ + +/* --- Navbar responsiva --- */ +@media (max-width: 768px) { + + .hospital-header-content { + flex-direction: column; + gap: 1rem; + padding: 1rem 0.5rem; + } + + .hospital-logo-area { + margin-left: 0; + justify-content: center; + } + + .hospital-nav-links { + flex-wrap: wrap; + gap: 1rem; + justify-content: center; + } + + .hospital-btn-login { + padding: 0.5rem 1rem; + } +} + +/* --- Hero responsivo --- */ +@media (max-width: 768px) { + .hospital-hero-bg-section { + height: 55vh; + background-position: center; + } + + .hospital-hero-bg-content h1 { + font-size: 2rem; + } + + .hospital-hero-bg-content p { + font-size: 1rem; + } +} + +/* --- Médico gigante responsivo --- */ +@media (max-width: 768px) { + .hospital-doctor-highlight { + flex-direction: column; + text-align: center; + } + + .hospital-doctor-image img { + max-width: 300px; + } +} + +/* --- Responsividade geral --- */ +@media (max-width: 768px) { + .hospital-about-content, + .hospital-contact-wrapper { + flex-direction: column; + } +} + + +a.hospital-btn-login, +a.hospital-btn-login:link, +a.hospital-btn-login:visited, +button.hospital-btn-login { + display: inline-block !important; + padding: 0.6rem 1.4rem !important; + background-color: #ffffff !important; + color: #0077cc !important; + border-radius: 30px !important; + font-weight: 600 !important; + text-decoration: none !important; + border: 2px solid #0077cc !important; + transition: all 0.3s ease !important; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08) !important; +} + + +a.hospital-btn-login:hover, +button.hospital-btn-login:hover { + transform: translateY(-2px) !important; +} + + +a.hospital-btn-primary, +button.hospital-btn-primary, +input[type="submit"].hospital-btn-primary { + display: inline-block !important; + background: #0077cc !important; + color: #ffffff !important; + padding: 0.8rem 1.5rem !important; + border-radius: 6px !important; + font-weight: 500 !important; + text-decoration: none !important; + border: none !important; +} + +a.hospital-btn-primary:hover, +button.hospital-btn-primary:hover { + background: #005fa3 !important; +} + + +.btn.hospital-btn-login, +.btn.hospital-btn-primary, +button.btn.hospital-btn-login { + + background: unset !important; + color: unset !important; +} \ No newline at end of file diff --git a/src/assets/css/index.css b/src/assets/css/index.css index 5fe60ae..f05330a 100644 --- a/src/assets/css/index.css +++ b/src/assets/css/index.css @@ -22,6 +22,11 @@ @import './tiptap.css'; +@import './chatbot.css'; + +@import './Dashboard.css'; +@import './DoctorDashboard.css'; + /* Estilos para cards de eventos */ .event-card { padding: 6px 10px; @@ -150,3 +155,286 @@ font-size: 0.7rem; min-width: 80px; } + +/* Linha separadora do dropdown */ +.dropdown-divider { + border: none; + border-top: 1px solid #e5e5e5; + margin: 8px 12px; + height: 0; +} + +/* Estilo específico para botão de upload */ +.dropdown-item i { + margin-right: 8px; + width: 14px; + text-align: center; +} + +/* Botão de logout destacado */ +.logout-btn { + color: #e63946 !important; + border: none; + border-radius: 6px; + padding: 8px 12px; + font-weight: 600; + width: 100%; + text-align: left; + margin-top: 6px; + cursor: pointer; + transition: background-color 0.3s ease, transform 0.2s ease; +} + +.logout-btn:hover { + background-color: #fef2f2 !important; + color: #dc2626 !important; +} + +/* Dropdown com leve sombra e padding */ +.dropdown-menu { + position: absolute; + top: 100%; + right: 0; + left: auto; + z-index: 1000; + min-width: 160px; + padding: 5px; + margin: 5px 0 0 0; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.1); + display: none; +} + +.dropdown-menu.show { + display: block; +} + +/* Estilo dos outros itens do dropdown */ +.dropdown-item { + background: none; + border: none; + width: 100%; + text-align: left; + padding: 8px 12px; + border-radius: 4px; + font-size: 14px; + color: #333; + cursor: pointer; + transition: background 0.2s ease; +} + +.dropdown-item:hover { + background-color: #f1f1f1; +} + +.dropdown-header { + padding: 8px 12px; + font-size: 14px; + font-weight: 600; + color: #666; + border-bottom: 1px solid #eee; + margin-bottom: 5px; +} + +/* Estilo para o container do dropdown */ +.dropdown { + position: relative; +} + +/* Estilo específico para o dropdown do perfil */ +.nav-item.dropdown { + position: relative; +} + +/* Estilo específico para o user-menu dropdown */ +.user-menu .nav-item.dropdown .dropdown-menu { + position: absolute !important; + top: 100% !important; + right: 0 !important; + left: auto !important; + transform: translateX(0) !important; + min-width: 180px; + margin-top: 8px; +} + + +.dm-container { + display: flex; + align-items: center; + margin-right: 12px; +} + + +.dm-button { + background: none; + border: none; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: transform 0.2s ease, filter 0.2s ease; + font-size: 20px; +} + + +.dm-button:focus { + outline: none; + box-shadow: none; +} + + +.dm-button:hover { + transform: scale(1.2); + filter: brightness(1.2); +} + +/* Cores */ +.dm-button.light { + color: #fff; +} + +.dm-button.dark { + color: #f1f1f1; +} + +.action-buttons-container { + display: flex; + gap: 5px; + justify-content: flex-end; + align-items: center; +} +.action-btn { + border: none; + background: transparent; + cursor: pointer; + padding: 8px 10px; + margin: 0 2px; + border-radius: 6px; + transition: all 0.3s ease; + position: relative; + overflow: hidden; + display: inline-block; +} + +.action-btn::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); + transition: left 0.5s; +} + +.action-btn:hover::before { + left: 100%; +} + +.action-btn-view { + color: #007bff; +} + +.action-btn-view:hover { + background-color: #007bff; + color: white; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0,123,255,0.3); +} + +.action-btn-edit { + color: #28a745; +} + +.action-btn-edit:hover { + background-color: #28a745; + color: white; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(40,167,69,0.3); +} + +.action-btn-delete { + color: #dc3545; +} + +.action-btn-delete:hover { + background-color: #dc3545; + color: white; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(220,53,69,0.3); +} +.action-btn-print { + color: #17a2b8; +} +.action-btn-print:hover { + background-color: #17a2b8; + color: white; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(23,162,184,0.3); +} + +/* Efeito de tooltip melhorado */ +.action-btn { + position: relative; +} + +.action-btn::after { + content: attr(data-tooltip); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + background-color: #333; + color: white; + padding: 5px 10px; + border-radius: 4px; + font-size: 12px; + white-space: nowrap; + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; + z-index: 1000; +} + +.action-btn:hover::after { + opacity: 1; + visibility: visible; + transform: translateX(-50%) translateY(-5px); +} + +/* Container para os botões de ação */ +.action-buttons-container { + display: flex; + gap: 5px; + justify-content: flex-end; + align-items: center; +} + +/* Responsividade para os botões */ +@media (max-width: 768px) { + .action-btn { + padding: 6px 8px; + margin: 0 1px; + } + + .action-buttons-container { + gap: 3px; + } +} + +/* Efeito focus para acessibilidade */ +.action-btn:focus { + outline: 2px solid transparent; + box-shadow: 0 0 0 2px rgba(0,123,255,0.25); +} + +.action-btn-edit:focus { + box-shadow: 0 0 0 2px rgba(40,167,69,0.25); +} + +.action-btn-delete:focus { + box-shadow: 0 0 0 2px rgba(220,53,69,0.25); +} \ No newline at end of file diff --git a/src/assets/css/login.css b/src/assets/css/login.css new file mode 100644 index 0000000..067d7e6 --- /dev/null +++ b/src/assets/css/login.css @@ -0,0 +1,398 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); + +/* Variáveis de Cores */ +:root { + --primary-blue: #007bff; /* Azul principal do botão */ + --light-blue: #e9f5ff; /* Fundo azul claro para a seção de imagens */ + --dark-green: #38b000; /* Verde escuro para a imagem da paciente */ + --text-dark: #333; + --text-light: #666; + --border-color: #ddd; + --background-grey: #f8f9fa; /* Cinza suave para o fundo */ +} + +/* Reset Básico */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +.login-body { + font-family: 'Inter', sans-serif; + background-color: var(--background-grey); + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; +} + +.container { + display: flex; + width: 100% !important; + max-width: 1200px; /* Largura máxima para a composição */ + background-color: #fff; + border-radius: 12px; + overflow: hidden; + padding-left: 0; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); +} + +/* ------------------- Seção de Imagens (Lado Esquerdo) ------------------- */ +.image-section { + flex: 1; + padding: 50px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background: linear-gradient(#eff3ff, #dde1ea) +} + +.image-box { + width: 250px; + height: 250px; + overflow: hidden; + position: relative; +} + +.content-section { + display: flex; /* Habilita o Flexbox para alinhar imagem e texto lado a lado */ + align-items: center; /* Alinha verticalmente os itens no centro */ + gap: 20px; +} + +.patient-info { + flex-direction: row-reverse; +} + +/* Cores de Fundo (Simulando a imagem) */ +.doctor-box { + margin-right: 40%; + background: transparent; /* Gradiente azul para o médico */ +} + +.patient-box { + margin-left: 40%; + background-color: transparent; /* Verde sólido para a paciente */ +} + +/* Para simular as imagens (substitua por tags reais se tiver os arquivos) */ +.doctor-image, .patient-image { + position: absolute; + width: 100%; + height: 100%; + background-size: contain; + background-position: center; + background-repeat: no-repeat; +} + +.image-box, +.text-box { + align-self: stretch !important; + flex-grow: 1 ; + display: flex ; + flex-direction: column ; + justify-content: center; + margin-left: 0 ; + margin-right: 0 ; +} + +.text-box h3{ + font-size: 24px !important; + color: #003366 !important; +} + +.doctor-image { + /* Esta URL é uma placeholder. Substitua pela URL da sua imagem. */ + background-image: url('/img/login/medico_container.png') !important; +} + +.patient-image { + /* Esta URL é uma placeholder. Substitua pela URL da sua imagem. */ + background-image: url('/img/login/paciente_container.png') !important; +} + +/* ------------------- Seção de Login (Lado Direito) ------------------- */ +.login-section { + width: 450px; /* Largura fixa para a seção de login */ + padding: 60px 40px; + display: flex; + flex-direction: column; + align-items: center; +} + +.app-header { + display: flex; + align-items: center; + margin-bottom: 40px; +} + +.app-icon { + color: var(--primary-blue); + font-size: 24px; + margin-right: 8px; +} + +.app-name { + font-size: 20px; + font-weight: 600; + color: var(--text-dark); +} + +/* Tabs */ +.tab-container { + display: flex; + width: 100%; + border: 1px solid var(--border-color); + border-radius: 8px; + overflow: hidden; + margin-bottom: 40px; +} + +.tab-button { + flex: 1; + padding: 12px 0; + font-size: 14px; + font-weight: 500; + border: none; + background-color: #fff; + cursor: pointer; + color: var(--text-dark); + transition: background-color 0.2s, color 0.2s; +} + +.tab-button:first-child { + border-right: 1px solid var(--border-color); +} + +.tab-button.active { + background-color: var(--primary-blue); + color: #fff; + box-shadow: 0 2px 5px rgba(0, 123, 255, 0.3); +} + +/* Formulário */ +.login-form-container { + width: 100%; +} + +.login-title { + font-size: 24px; + font-weight: 700; + color: var(--text-dark); + margin-bottom: 8px; + color: #003366 !important; +} + +.login-subtitle { + font-size: 14px; + color: var(--text-light); + margin-bottom: 30px; +} + +.input-label { + display: block; + font-size: 14px; + font-weight: 500; + color: var(--text-dark); + margin-bottom: 8px; +} + +.input-group { + display: flex; + align-items: center; + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 10px 15px; + margin-bottom: 20px; + background-color: #f9f9f9; +} + +.input-group input { + flex-grow: 1; + border: none; + outline: none; + font-size: 16px; + background-color: transparent; + color: var(--text-dark); + padding: 0; +} + +.input-group .input-icon, .toggle-password { + color: var(--text-light); + margin-right: 10px; +} + +.password-input .input-icon { + margin-right: 15px; +} + +.password-input .toggle-password { + cursor: pointer; + margin-left: 10px; + margin-right: 0; +} + +/* Input de Telefone */ +.phone-input { + padding-left: 0; +} + +.phone-code { + background-color: #eee; + padding: 0 15px; + margin-right: 15px; + height: 100%; + display: flex; + align-items: center; + font-size: 16px; + color: var(--text-dark); + border-right: 1px solid var(--border-color); + /* Ajuste para alinhar com a altura do input */ + line-height: 1.5; +} + +.phone-input input { + font-family: 'Inter', sans-serif; + padding: 0; + margin-left: 5px; +} + +/* Link de Senha */ +.reset-password { + display: block; + text-align: right; + font-size: 14px; + color: var(--primary-blue); + text-decoration: none; + margin-bottom: 30px; + font-weight: 500; +} + +/* Botão Principal de Login */ +.login-button { + font-family: 'Inter', sans-serif; + width: 100%; + padding: 14px; + background-color: var(--primary-blue); + color: #fff; + border: none; + border-radius: 6px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s; + margin-bottom: 20px; +} + +.login-button:hover { + background-color: #0056b3; +} + +/* Link Login com Código */ +.login-with-code { + display: block; + text-align: center; + font-size: 14px; + color: var(--primary-blue); + text-decoration: none; + font-weight: 500; +} + +/* Responsividade Básica */ +@media (max-width: 992px) { + .container { + flex-direction: column; + } + + .image-section { + display: none; /* Oculta a seção de imagens em telas menores */ + } + + .login-section { + width: 100%; + max-width: 450px; + padding: 40px 20px; + } +} + +/* Estilo para a borda do campo de erro */ +.input-error { + border: 2px solid #e74c3c !important; /* Vermelho forte */ + background-color: #fcebeb; /* Fundo levemente rosado */ + outline: none; +} + +/* Estilo para a mensagem de erro (oculta por padrão) */ +.error-message { + color: #e74c3c; /* Cor do texto de erro */ + font-size: 0.9em; + margin-top: 5px; + display: none; /* Inicia oculta */ +} + +/* Mostra a mensagem de erro quando necessário */ +.error-visible { + display: block; +} + +/* ======================================= */ +/* 📱 RESPONSIVIDADE MOBILE */ +/* ======================================= */ + +/* Aplica regras quando a tela for menor que 768px (tablets e celulares) */ +@media (max-width: 768px) { + + .container { + padding: 10px; + } + + /* 1. Empilha a Imagem e o Texto verticalmente */ + .content-section { + flex-direction: column; + gap: 15px; /* Reduz o espaço entre a imagem e o texto no mobile */ + text-align: center; /* Centraliza o texto */ + } + + /* 2. Remove a inversão da ordem na seção do paciente */ + .patient-info { + flex-direction: column; /* Sobrescreve row-reverse */ + } + + /* 3. Ajusta o tamanho da Imagem para Mobile (menor) */ + .image-box { + width: 280px; /* Reduz o tamanho da imagem para caber melhor na tela */ + height: 280px; + } + + /* 4. Ajusta os tamanhos de fonte no Mobile */ + .text-box h3 { + font-size: 1.5em; /* Título menor */ + color: #003366 !important; + } + + .text-box p { + font-size: 1em; /* Parágrafo menor */ + } + + /* Garante que o texto não tenha largura máxima que o force a centralizar mal */ + .text-box { + max-width: 90%; /* Limita a largura do texto em 90% da tela */ + } +} + + +.recaptcha-wrapper { + margin: 20px 0; + display: flex; + justify-content: center; + width: 100%; +} + + +@media (max-width: 768px) { + .recaptcha-wrapper { + margin: 15px 0; + } +} \ No newline at end of file diff --git a/src/assets/css/modal-details.css b/src/assets/css/modal-details.css new file mode 100644 index 0000000..f490893 --- /dev/null +++ b/src/assets/css/modal-details.css @@ -0,0 +1,91 @@ +/* Fundo do modal */ +.modal.fade.show { + display: block; + background-color: rgba(0, 0, 0, 0.6); /* escurece o fundo */ + backdrop-filter: blur(2px); /* leve desfoque atrás */ + transition: background-color 0.3s; + } + + /* Caixa do modal */ + .modal-dialog { + max-width: 700px; + margin: 1.75rem auto; + } + + .modal-content { + border-radius: 12px; + border: none; + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3); + overflow: hidden; + animation: slideDown 0.3s ease-out; + } + + /* Animação do modal */ + @keyframes slideDown { + from { + transform: translateY(-20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } + } + + /* Cabeçalho */ + .modal-header { + background-color: #007bff; + color: #fff; + border-bottom: none; + padding: 15px 20px; + } + + .modal-header .close { + color: #fff; + opacity: 1; + font-size: 24px; + } + + /* Título */ + .modal-title { + font-weight: 600; + font-size: 18px; + } + + /* Corpo do modal */ + .modal-body { + padding: 20px; + font-size: 14px; + color: #333; + } + + /* Labels e valores */ + .modal-body p strong { + color: #007bff; + } + + /* Colunas */ + .modal-body .col-md-6 { + margin-bottom: 15px; + } + + /* Rodapé */ + .modal-footer { + border-top: none; + padding: 15px 20px; + justify-content: flex-end; + } + + /* Botão fechar */ + .modal-footer .btn-secondary { + background-color: #007bff; + border: none; + border-radius: 5px; + padding: 6px 14px; + transition: all 0.2s; + } + + .modal-footer .btn-secondary:hover { + background-color: #5a6268; + } + \ No newline at end of file diff --git a/src/assets/css/style.css b/src/assets/css/style.css index a64a01b..d594667 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -56,18 +56,23 @@ Version : 1.0 1. General -----------------------*/ -@import url('https://fonts.googleapis.com/css?family=Rubik:400,500,700'); +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); html { - height: 100%; + height: auto; + min-height: 100%; } + body { - font-family: 'Rubik', sans-serif; - font-size: 0.875rem; - color: #666; - background-color: #fafafa; - overflow-x: hidden; - height: 100%; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-size: 0.875rem; + color: #666; + background-color: #fafafa; + overflow-x: hidden; + overflow-y: auto; /* permite rolar */ + min-height: 100vh; /* ocupa toda a tela e cresce conforme o conteúdo */ + height: auto; } + h1, h2, h3, @@ -173,15 +178,36 @@ textarea.form-control { 2. Table -----------------------*/ -.table { - color: #000; - border: 1px solid #f0f0f0; +.table.custom-table > tbody > tr > td { + white-space: nowrap !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + max-width: 150px !important; + } + +table { + color: #000; + + border-radius: 10px ; + + overflow: hidden; + +} + .table.table-white { background-color: #fff; + border-color: #0077cc; +} +.table>thead>tr { + background: linear-gradient(135deg, #004a99, #0077cc); +} +.table>thead>tr>th{ +color: white; } .table > tbody > tr > td { font-weight: 300; + background-color: #fff; } .table-striped > tbody > tr:nth-of-type(2n + 1) { background-color: #f6f6f6; @@ -433,7 +459,8 @@ table.table td h2 span { } .btn-primary { border-color: transparent; - background-color: #009efb; + background: linear-gradient(135deg, #004a99, #0077cc); + } .btn-primary:hover, .btn-primary:focus, @@ -571,12 +598,11 @@ table.table td h2 span { } .pagination > li > a, .pagination > li > span { - color: #009efb; + background: linear-gradient(135deg, #004a99, #0077cc); padding: .5rem .75rem !important; } .page-item.active .page-link { - background-color: #009efb; - border-color: #009efb; + background: linear-gradient(135deg, #004a99, #0077cc); } .dropdown-menu { border: 1px solid rgba(0, 0, 0, 0.1); @@ -665,6 +691,7 @@ textarea.form-control { } .form-control.form-control-sm { padding: 0.25rem 0.5rem; + border-radius: 10px } .card .card-header { background-color: rgba(255, 255, 255, 0.1); @@ -702,7 +729,7 @@ textarea.form-control { -----------------------*/ .header { - background-color: #009efb; + background: linear-gradient(135deg, #004a99, #0077cc); left: 0; position: fixed; right: 0; @@ -730,7 +757,7 @@ textarea.form-control { color: #fff; font-size: 18px; font-weight: 500; - margin-left: 10px; + margin-left: 0px; } .header .navbar-nav .badge { position: absolute; @@ -824,7 +851,7 @@ textarea.form-control { top: 50px; width: 230px; z-index: 1039; - background-color: #fff; + background-color: #FFFFFF; bottom: 0; margin-top: 0px; position: fixed; @@ -844,38 +871,89 @@ textarea.form-control { font-size: 14px; list-style-type: none; margin: 0; - padding: 0; + padding: 8px 0; } .sidebar-menu li a { - color: #888; - display: block; - font-size: 15px; + color: #777; + display: flex; + align-items: center; + font-family: 'Inter', sans-serif; + font-size: 14px; + font-weight: 400; height: auto; - padding: 0 20px; + padding: 12px 16px; + margin: 2px 8px; + border-radius: 10px; + transition: all 0.3s ease; + text-decoration: none; +} +.sidebar-menu li a i { + color: #777; + margin-right: 12px; + width: 16px; + text-align: center; + transition: color 0.3s ease; } .sidebar-menu li a:hover { - color: #009efb; + color: #007BFF; + background-color: #E8F2FF; + transform: translateX(4px); } -.sidebar-menu li.active a { - color: #009efb; - background-color: #f3f3f3; +.sidebar-menu li a:hover i { + color: #007BFF; } +.sidebar-menu li.active a, +.sidebar-menu li a.active { + color: #007BFF; + background-color: #E8F2FF; + font-weight: 500; +} +.sidebar-menu li.active a i, +.sidebar-menu li a.active i { + color: #007BFF; +} + +/* Separadores de seção */ +.sidebar-menu li.separator { + border-top: 1px solid #EAEAEA; + margin: 16px 0; + padding-top: 16px; +} + +/* Melhor espaçamento para o ul */ +.sidebar-menu ul { + font-size: 14px; + list-style-type: none; + margin: 0; + padding: 8px 0; +} + +/* Estilo para span dentro dos links */ +.sidebar-menu li a span { + font-family: 'Inter', sans-serif; + font-weight: inherit; +} + .menu-title { color: #333; - font-size: 15px; - font-weight: 500; - padding: 12px 20px; + font-family: 'Inter', sans-serif; + font-size: 13px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 16px 16px 8px 16px; } .menu-title > i { float: right; line-height: 40px; } .sidebar-menu li.menu-title a { - color: #009efb; - display: inline-block; - float: right; - padding: 0; + color: #009efb; + float: right; + padding: 0; + text-decoration: none; /* opcional */ } + .sidebar-menu li.menu-title a.btn { color: #fff; display: block; @@ -933,6 +1011,36 @@ textarea.form-control { .mobile_btn { display: none; } +@media (max-width: 991.98px) { + + /* Faz o botão de menu (hambúrguer) aparecer */ + .mobile_btn { + display: block; /* Ou 'inline-block', dependendo do seu layout */ + float: left; + color: white; /* Ajuste a cor para combinar com seu header */ + font-size: 24px; + line-height: 50px; /* Centraliza verticalmente (use a altura do header) */ + padding: 0 15px; + cursor: pointer; /* Muda o cursor para indicar que é clicável */ + } + + /* 1. Por padrão, em mobile, a sidebar fica ESCONDIDA para a esquerda */ + .sidebar { + transform: translateX(-100%); /* Move a sidebar totalmente para fora da tela */ + z-index: 1040; /* Garante que a sidebar fique na frente do conteúdo */ + } + + /* 2. REGRA PRINCIPAL: Quando a classe 'sidebar-open' é adicionada ao + 'main-wrapper', a sidebar desliza para dentro da tela */ + .main-wrapper.sidebar-open .sidebar { + transform: translateX(0); + } + + /* 3. Em mobile, o conteúdo principal não deve ser "empurrado" pela sidebar */ + .page-wrapper { + margin-left: 0 !important; + } +} .sidebar .sidebar-menu > ul > li > a span { transition: all 0.2s ease-in-out 0s; display: inline-block; @@ -1266,11 +1374,11 @@ ul.chat-user-total li i.old-users { border-top: 1px solid #000; } .bar-chart > .legend > .item { - position: relative; - display: inline-block; - float: left; - width: 25%; + position: relative; + float: left; + width: 25%; } + .bar-chart > .legend > .item:before { display: block; position: absolute; @@ -2217,12 +2325,12 @@ ul.chat-user-total li i.old-users { margin-right: 12px; } .chat-avatar-sm { - width: 24px; - margin-right: 10px; - display: inline-block; - position: relative; - float: left; + width: 24px; + margin-right: 10px; + position: relative; + float: left; } + .chat-avatar-sm img { width: 24px; } @@ -2579,12 +2687,12 @@ a.fc-event:not([href]) { .fc-toolbar h2 { font-size: 18px; font-weight: 600; - font-family: 'Rubik', sans-serif; + font-family: 'Inter', sans-serif; line-height: 30px; text-transform: uppercase; } .fc-day-grid-event .fc-time { - font-family: 'Rubik', sans-serif; + font-family: 'Inter', sans-serif; } .fc-day { background: #fff; @@ -2658,12 +2766,12 @@ a.fc-event:not([href]) { .fc-basic-view td.fc-week-number span { padding-right: 8px; font-weight: 700; - font-family: 'Rubik', sans-serif; + font-family: 'Inter', sans-serif; } .fc-basic-view td.fc-day-number { padding-right: 8px; font-weight: 700; - font-family: 'Rubik', sans-serif; + font-family: 'Inter', sans-serif; } /*----------------- @@ -3906,11 +4014,11 @@ blockquote p { padding: 0; } .social-share > li { - display: inline-block; - float: left; - margin-left: 10px; - text-align: center; + float: left; + margin-left: 10px; + text-align: center; } + .social-share > li:first-child { margin-left: 0; } @@ -4385,6 +4493,7 @@ blockquote p { box-shadow: 0 6px 15px rgba(36, 37, 38, 0.08); } + /*----------------- 42. Responsive -----------------------*/ @@ -4567,7 +4676,6 @@ blockquote p { display: none; } .sidebar { - margin-left: -225px; width: 225px; } .page-wrapper { @@ -4767,10 +4875,9 @@ blockquote p { height: 120px; } .table-responsive { - display: block; width: 100%; - overflow-x: auto; - -ms-overflow-style: -ms-autohiding-scrollbar; + overflow-x: auto; /* A mágica acontece aqui! */ + -webkit-overflow-scrolling: touch; } .header .has-arrow .dropdown-toggle > span:nth-child(2) { display: none; @@ -4912,4 +5019,113 @@ blockquote p { margin: 0 auto; margin-bottom: 10px; } +} + +/* ================================================= */ +/* ===== GERAL: Telas Médias (max-width: 991px) ===== */ +/* ================================================= */ +@media (max-width: 991px) { + .header .header-left { + width: 180px; + padding: 0 10px; + } + + .logo span { + font-size: 16px; + } + + .page-title-box h3 { + font-size: 16px; + } + + .user-menu.nav > li > a { + padding: 0 10px; + font-size: 15px; + } +} + + +/* ========================================================= */ +/* ===== CORREÇÃO DEFINITIVA MOBILE (max-width: 768px) ==== */ +/* ========================================================= */ + +@media (max-width: 768px) { + + /* Impede rolagem lateral */ + html, body { + overflow-x: hidden; + } + + /* 1. Header como Container Flex */ + .header { + position: fixed; + top: 0; + left: 0; + right: 0; + height: 50px; + z-index: 1050; + + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 10px; + } + + /* 2. Botão da Sidebar (#mobile_btn) - Fica à esquerda */ + #mobile_btn { + position: static; + z-index: 1045; + order: 1; + margin-right: 10px; + } + + /* 3. Container da Logo (.header-left) - REMOVIDO NO MOBILE */ + .header .header-left { + /* A logo será escondida */ + display: none; + } + + /* Ajuste para que o Menu e o Botão fiquem nas extremidades quando a Logo é removida */ + .user-menu { + position: static; + width: auto; + order: 3; + height: 50px; + display: flex; + align-items: center; + float: none; + padding-right: 0; + + /* Centraliza o Menu do Usuário corretamente à direita quando a logo desaparece */ + margin-left: auto; + } + + /* Esconde o título */ + .page-title-box { + display: none; + } + + /* Ajustes finos de tamanho */ + .user-menu.nav > li > a { + padding: 0 8px; + font-size: 14px; + } +} + + +/* ========================================================= */ +/* ===== GERAL: Telas Muito Pequenas (max-width: 480px) ===== */ +/* ========================================================= */ +@media (max-width: 480px) { + /* Não há necessidade de regras para .logo span ou .header-left aqui, + pois o display: none já foi aplicado acima. */ + + .user-menu.nav > li > a { + padding: 0 6px; + font-size: 13px; + } + + .header .user-img img { + width: 20px; + } } \ No newline at end of file diff --git a/src/assets/css/tiptap.css b/src/assets/css/tiptap.css index d6306c3..4f89810 100644 --- a/src/assets/css/tiptap.css +++ b/src/assets/css/tiptap.css @@ -25,6 +25,7 @@ img { max-width: 50%; border-radius: 5px; + display: block; } } diff --git a/src/components/AccessibilityWidget.jsx b/src/components/AccessibilityWidget.jsx new file mode 100644 index 0000000..2d17a6d --- /dev/null +++ b/src/components/AccessibilityWidget.jsx @@ -0,0 +1,154 @@ +import { useEffect, useState } from "react"; +import "../assets/css/darkmode.css"; + + +const LS_KEYS = { + dark: "pref_dark_mode", + daltonism: "pref_daltonism", + font: "pref_font_scale", +}; + +export default function AccessibilityWidget() { + const [open, setOpen] = useState(false); + const [darkMode, setDarkMode] = useState(false); + const [daltonismMode, setDaltonismMode] = useState(false); + const [fontScale, setFontScale] = useState(100); + const [leituraAtiva, setLeituraAtiva] = useState(false); + + // ---------- LEITURA AUTOMÁTICA ---------- + const lerTextoSelecionado = () => { + const texto = window.getSelection().toString().trim(); + if (!texto) return; + window.speechSynthesis.cancel(); + const fala = new SpeechSynthesisUtterance(texto); + fala.lang = "pt-BR"; + fala.rate = 1; + fala.pitch = 1; + window.speechSynthesis.speak(fala); + }; + + useEffect(() => { + const handleSelectionChange = () => { + if (!leituraAtiva) return; + const texto = window.getSelection().toString().trim(); + if (texto.length > 1) lerTextoSelecionado(); + }; + + if (leituraAtiva) { + document.addEventListener("selectionchange", handleSelectionChange); + } else { + document.removeEventListener("selectionchange", handleSelectionChange); + window.speechSynthesis.cancel(); + } + + return () => { + document.removeEventListener("selectionchange", handleSelectionChange); + }; + }, [leituraAtiva]); + + // ---------- CARREGAR PREFERÊNCIAS ---------- + useEffect(() => { + const savedDark = localStorage.getItem(LS_KEYS.dark) === "true"; + const savedDaltonism = localStorage.getItem(LS_KEYS.daltonism) === "true"; + const savedFont = parseInt(localStorage.getItem(LS_KEYS.font) || "100", 10); + + setDarkMode(savedDark); + setDaltonismMode(savedDaltonism); + setFontScale(savedFont); + + document.body.classList.toggle("dark-mode", savedDark); + document.body.classList.toggle("daltonism-mode", savedDaltonism); + document.documentElement.style.fontSize = `${savedFont}%`; + }, []); + + // ---------- FUNÇÕES DE MODO ---------- + const toggleDarkMode = () => { + const next = !darkMode; + setDarkMode(next); + localStorage.setItem(LS_KEYS.dark, String(next)); + document.body.classList.toggle("dark-mode", next); + }; + + const toggleDaltonismMode = () => { + const next = !daltonismMode; + setDaltonismMode(next); + localStorage.setItem(LS_KEYS.daltonism, String(next)); + document.body.classList.toggle("daltonism-mode", next); + }; + + // ---------- CONTROLE DE FONTE ---------- + const applyFontScale = (next) => { + const clamped = Math.max(80, Math.min(180, next)); + setFontScale(clamped); + localStorage.setItem(LS_KEYS.font, String(clamped)); + document.documentElement.style.fontSize = `${clamped}%`; + }; + const incFont = () => applyFontScale(fontScale + 10); + const decFont = () => applyFontScale(fontScale - 10); + const resetFont = () => applyFontScale(100); + + // ---------- JSX ---------- + return ( + <> + {/* Botão flutuante ♿ */} + + + {/* Painel */} + {open && ( +
+
+ Acessibilidade + +
+ + {/* Modo daltônico */} +
+ +
+ + {/* Controle de fonte */} +
+ Tamanho da fonte +
+ + + +
+
+ + {/* Leitura automática */} +
+ +
+ +
+ Atalho: Ctrl + Alt + A +
+
+ )} + + ); +} \ No newline at end of file diff --git a/src/components/LaudoConsulta.jsx b/src/components/LaudoConsulta.jsx new file mode 100644 index 0000000..2d47e58 --- /dev/null +++ b/src/components/LaudoConsulta.jsx @@ -0,0 +1,456 @@ +import { useEditor, EditorContent } from '@tiptap/react' +import StarterKit from '@tiptap/starter-kit' +import Image from '@tiptap/extension-image'; +import { useState, useEffect, useRef} from 'react'; +import { Card, Collapse } from "react-bootstrap"; // <-- IMPORT CORRETO +import { ChevronDown, ChevronUp } from "lucide-react"; +import { getAccessToken } from '../utils/auth'; +import Select from 'react-select'; +import Swal from 'sweetalert2'; +import { useNavigate } from 'react-router-dom'; +import { FaMicrophone } from "react-icons/fa"; +import { InterimMark } from '../utils/InterimMark'; // <-- Verifique se esse caminho está certo! +import { getUserRole } from '../utils/userInfo'; +import { useLocation } from 'react-router-dom'; +import { getDoctorId } from '../utils/userInfo'; + + + +function Bar({ comandos, handleSubmit, toggleRecording, isRecording }) { + const inputRef = useRef(null); + + const handleAbrirExplorador = () => { + inputRef.current.click(); // abre o explorador + }; + + const handleArquivoSelecionado = (event) => { + const arquivo = event.target.files[0]; + if (arquivo) { + const imageUrl = URL.createObjectURL(arquivo); + comandos.agregarImagen(imageUrl); + event.target.value = null; + } + }; + + return ( + <> +
+
+ + + + {/* */} + + + + + + + <> + + + + + + + {/* */} +
+
+ +
+
+ + ); +}; + +function LaudoConsulta() { + const location = useLocation(); + const patient_id = location.state?.pacienteId; + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + + const navigate = useNavigate(); + const [paciente, setPaciente] = useState([]); + const tokenUsuario = getAccessToken() + var myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + useEffect(() => { + fetch(`${supabaseUrl}/rest/v1/patients`, requestOptions) + .then(response => response.json()) + .then(result => setPaciente(Array.isArray(result) ? result : [])) + .catch(error => console.log('error', error)); + }, []) + const options = paciente.map(p => ({ + value: p.id, + label: p.full_name + })); + function gerarOrderNumber() { + const prefixo = "REL"; + + const agora = new Date(); + const ano = agora.getFullYear(); + const mes = String(agora.getMonth() + 1).padStart(2, "0"); // adiciona 0 à esquerda se necessário + + // Gerar um código aleatório de 6 caracteres (letras maiúsculas + números) + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + let codigo = ""; + for (let i = 0; i < 6; i++) { + codigo += chars.charAt(Math.floor(Math.random() * chars.length)); + } + + return `${prefixo}-${ano}-${mes}-${codigo}`; + } + + // Exemplo de uso: + const orderNumber = gerarOrderNumber(); + const [laudos, setLaudos] = useState({ + patient_id: patient_id || "", + order_number: "", + exam: "", + diagnosis: "", + conclusion: "", + cid_code: "", + content_html: "", + status: "draft", + requested_by: getDoctorId(), + }); + const handlePacienteChange = (selected) => { + setLaudos(prev => ({ + ...prev, + patient_id: selected ? selected.value : "" + })); + }; + const handleChange = (e) => { + const { name, value } = e.target; + setLaudos((prev) => ({ + ...prev, + [name]: value + })); + }; + + const handleSubmit = (e) => { + const role = getUserRole(); + e.preventDefault(); + if (!laudos.patient_id || !laudos.diagnosis || !laudos.exam || !laudos.conclusion) { + Swal.fire({ + title: "Por favor, preencha todos os campos obrigatórios.", + icon: "warning", + draggable: true + }); + return; + } + var myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + myHeaders.append("Content-Type", "application/json"); + + var raw = JSON.stringify(laudos); + + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: raw, + redirect: 'follow' + }; + fetch(`${supabaseUrl}/rest/v1/reports`, requestOptions) + .then(response => response.text()) + .then(async result => { + console.log(result); + // Atualiza o status da consulta para completed + if (laudos.patient_id) { + await fetch(`${supabaseUrl}/rest/v1/appointments?id=eq.${location.state?.consultaId}`, { + method: 'PATCH', + headers: myHeaders, + body: JSON.stringify({ status: 'completed' }) + }); + } + Swal.fire({ + title: "Laudo adicionado!", + icon: "success", + draggable: true + }); + navigate(`/${role}/laudolist`); + }) + .catch(error => console.log('error', error)); + }; + + const [open, setOpen] = useState(false); + const editor = useEditor({ + extensions: [StarterKit, Image, InterimMark ], + content: "", + onUpdate: ({ editor }) => { + setLaudos(prev => ({ + ...prev, + content_html: editor.getHTML() + })); + } + }) + const [isRecording, setIsRecording] = useState(false); + const recognitionRef = useRef(null); + const lastInsertedRef = useRef({ from: -1, to: -1, text: '' }); + + + useEffect(() => { + const SpeechRecognition = + window.SpeechRecognition || window.webkitSpeechRecognition; + + if (!SpeechRecognition) { + alert("Seu navegador não suporta reconhecimento de voz 😢"); + return; + } + + const recognition = new SpeechRecognition(); + recognition.lang = "pt-BR"; + recognition.continuous = false; + recognition.interimResults = true; + + recognition.onresult = (event) => { + if (!editor) return; + + const result = event.results[0]; + const transcript = result[0].transcript; + const last = lastInsertedRef.current; + + // --- CORREÇÃO DE LÓGICA --- + // Vamos rodar a deleção como um comando SEPARADO primeiro. + if (last.from !== -1 && + editor.state.doc.textBetween(last.from, last.to) === last.text) + { + // Roda a deleção e PARA. + editor.chain().focus() + .deleteRange({ from: last.from, to: last.to }) + .run(); + } + + // Pega a posição ATUAL (depois da deleção) + const currentPos = editor.state.selection.from; + + if (result.isFinal) { + // --- RESULTADO FINAL (PRETO) --- + // Roda a inserção final como um comando SEPARADO. + editor.chain().focus() + .insertContent(transcript + ' ') + .run(); + + // Reseta a Ref + lastInsertedRef.current = { from: -1, to: -1, text: '' }; + + } else { + // --- RESULTADO PROVISÓRIO (CINZA) --- + // Esta é a nova estratégia: "Ligar" a mark, inserir, "Desligar" a mark. + // Roda tudo como um comando SEPARADO. + editor.chain() + .focus() + .setMark('interimMark') // <-- "Pincel cinza" LIGADO + .insertContent(transcript) // <-- Insere o texto + .unsetMark('interimMark') // <-- "Pincel cinza" DESLIGADO + .run(); + + // Atualiza a Ref com a posição do texto cinza + lastInsertedRef.current = { + from: currentPos, + to: currentPos + transcript.length, + text: transcript + }; + } + // Não precisamos mais do 'editorChain.run()' aqui embaixo + }; + + recognition.onerror = (err) => { + // ... (código do onerror sem mudanças) + }; + + recognition.onend = () => { + // ... (código do onend sem mudanças) + }; + + recognitionRef.current = recognition; + + return () => { + recognition.stop(); + }; + + }, [editor, isRecording]); + + const toggleRecording = () => { + if (!recognitionRef.current) return; + + if (isRecording) { + // Usuário clicou para PARAR + setIsRecording(false); // <-- Seta o estado + recognitionRef.current.stop(); // <-- Para a API + // O 'onend' será chamado e fará a limpeza/confirmação. + } else { + // Usuário clicou para COMEÇAR + editor?.chain().focus().run(); + setIsRecording(true); // <-- Seta o estado + recognitionRef.current.start(); // <-- Inicia a API + } + }; + + const comandos = { + toggleBold: () => editor.chain().focus().toggleBold().run(), + toggleItalic: () => editor.chain().focus().toggleItalic().run(), + toggleUnderline: () => editor.chain().focus().toggleUnderline().run(), + toggleCodeBlock: () => editor.chain().focus().toggleCodeBlock().run(), + toggleH1: () => editor.chain().focus().toggleHeading({ level: 1 }).run(), + toggleH2: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), + toggleH3: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), + toggleParrafo: () => editor.chain().focus().setParagraph().run(), + toggleListaOrdenada: () => editor.chain().focus().toggleOrderedList().run(), + toggleListaPontos: () => editor.chain().focus().toggleBulletList().run(), + agregarImagen: (url) => { + if (!url) return; + editor.chain().focus().setImage({ src: url }).run(); + }, + agregarLink: () => { + const url = window.prompt('URL do link') + if (url) { + editor.chain().focus().setLink({ href: url }).run() + } + } + } + + return ( +
+
+

Laudo Médico

+
+ + setOpen(!open)} + aria-controls="paciente-content" + aria-expanded={open} + className="d-flex justify-content-between align-items-center" + style={{ + cursor: "pointer", + borderRadius: "25px", + padding: "12px 20px", + }} + > + Informações do paciente + {open ? : } + + + +
+ + + + + +
+
+
+
+ + +
+
+ ); +} + +export default LaudoConsulta; \ No newline at end of file diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx deleted file mode 100644 index 09050e8..0000000 --- a/src/components/Navbar.jsx +++ /dev/null @@ -1,117 +0,0 @@ -// src/components/Navbar.jsx -import "../assets/css/index.css"; -import { Link, useLocation, useNavigate } from "react-router-dom"; -import { useEffect, useRef, useState } from "react"; - -function Navbar() { - const location = useLocation(); - const navigate = useNavigate(); - - const [openNotif, setOpenNotif] = useState(false); - const [openProfile, setOpenProfile] = useState(false); - - const notifRef = useRef(null); - const profileRef = useRef(null); - - const isDoctor = location.pathname.startsWith("/doctor"); - const profileName = isDoctor ? "Médico" : "Admin"; - - // Fecha dropdowns ao clicar fora - useEffect(() => { - function handleClickOutside(e) { - if (notifRef.current && !notifRef.current.contains(e.target)) { - setOpenNotif(false); - } - if (profileRef.current && !profileRef.current.contains(e.target)) { - setOpenProfile(false); - } - } - document.addEventListener("click", handleClickOutside); - return () => document.removeEventListener("click", handleClickOutside); - }, []); - - const goToOtherRole = () => { - if (isDoctor) navigate("/"); - else navigate("/doctor"); - setOpenProfile(false); - }; - - return ( -
-
- {/* Logo dinâmica */} - - {" "} - MediConnect - -
- - - - - - -
- ); -} - -export default Navbar; - diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx deleted file mode 100644 index bb6a02d..0000000 --- a/src/components/Sidebar.jsx +++ /dev/null @@ -1,68 +0,0 @@ -import '../assets/css/index.css' -import { Link } from 'react-router-dom'; - -function Sidebar() { - return ( -
- -
- ); -} -export default Sidebar; - diff --git a/src/components/VerLaudo.jsx b/src/components/VerLaudo.jsx new file mode 100644 index 0000000..3af9eb6 --- /dev/null +++ b/src/components/VerLaudo.jsx @@ -0,0 +1,446 @@ +// PatientList.jsx +import { useEditor, EditorContent } from '@tiptap/react' +import StarterKit from '@tiptap/starter-kit' +import Image from '@tiptap/extension-image'; +import { Link } from 'react-router-dom'; +import { useEffect, useState } from 'react'; +import { getAccessToken } from '../utils/auth'; +import { useParams } from 'react-router-dom'; +import { Card, Collapse } from "react-bootstrap"; // <-- IMPORT CORRETO +import { ChevronDown, ChevronUp } from "lucide-react"; +import Select from 'react-select'; +import Swal from 'sweetalert2'; +import { useNavigate } from 'react-router-dom'; +import { useRef } from 'react'; +import { InterimMark } from '../utils/InterimMark'; +import { FaMicrophone } from "react-icons/fa"; +import { getUserRole } from '../utils/userInfo'; +function Bar({ comandos, toggleRecording, isRecording, editor, pacientesMap, Laudos }) { + const inputRef = useRef(null); + + const handleAbrirExplorador = () => { + inputRef.current.click(); // abre o explorador + }; + + const handleArquivoSelecionado = (event) => { + const arquivo = event.target.files[0]; + if (arquivo) { + const imageUrl = URL.createObjectURL(arquivo); + comandos.agregarImagen(imageUrl); + event.target.value = null; + } + }; + const navigate = useNavigate(); + const role = getUserRole(); + return ( + <> +
+
+ + + + {/* */} + + + + + + + + <> + + + + + + + + {/* */} +
+
+ +
+
+ + ); +}; + +function VerLaudo() { + const navigate = useNavigate(); + const [open, setOpen] = useState(false); + const [Laudos, setLaudos] = useState({}) + const tokenUsuario = getAccessToken() + const { id } = useParams() + const role = getUserRole(); + var myHeaders = new Headers(); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || "https://yuanqfswhberkoevtmfr.supabase.co"; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + myHeaders.append("Content-Type", "application/json"); + + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + useEffect(() => { + fetch(`${supabaseUrl}/rest/v1/reports?id=eq.${id}`, requestOptions) + .then(response => response.json()) + .then(result => { + // result é um array, pegue o primeiro laudo + const laudo = Array.isArray(result) ? result[0] : null; + setLaudos(laudo || {}); + }) + .catch(error => console.log('error', error)); + }, [id]); + const editor = useEditor({ + extensions: [StarterKit, Image, InterimMark], + content: Laudos?.content_html || "

Escreva o laudo aqui...

", + onUpdate: ({ editor }) => { + setLaudos({ + ...Laudos, + content_html: editor.getHTML() + }); + } + }); + const [isRecording, setIsRecording] = useState(false); + const recognitionRef = useRef(null); + const lastInsertedRef = useRef({ from: -1, to: -1, text: '' }); + + useEffect(() => { + const SpeechRecognition = + window.SpeechRecognition || window.webkitSpeechRecognition; + + if (!SpeechRecognition) { + alert("Seu navegador não suporta reconhecimento de voz 😢"); + return; + } + + const recognition = new SpeechRecognition(); + recognition.lang = "pt-BR"; + recognition.continuous = false; + recognition.interimResults = true; + + recognition.onresult = (event) => { + if (!editor) return; + + const result = event.results[0]; + const transcript = result[0].transcript; + const last = lastInsertedRef.current; + + // --- CORREÇÃO DE LÓGICA --- + // Vamos rodar a deleção como um comando SEPARADO primeiro. + if (last.from !== -1 && + editor.state.doc.textBetween(last.from, last.to) === last.text) + { + // Roda a deleção e PARA. + editor.chain().focus() + .deleteRange({ from: last.from, to: last.to }) + .run(); + } + + // Pega a posição ATUAL (depois da deleção) + const currentPos = editor.state.selection.from; + + if (result.isFinal) { + // --- RESULTADO FINAL (PRETO) --- + // Roda a inserção final como um comando SEPARADO. + editor.chain().focus() + .insertContent(transcript + ' ') + .run(); + + // Reseta a Ref + lastInsertedRef.current = { from: -1, to: -1, text: '' }; + + } else { + // --- RESULTADO PROVISÓRIO (CINZA) --- + // Esta é a nova estratégia: "Ligar" a mark, inserir, "Desligar" a mark. + // Roda tudo como um comando SEPARADO. + editor.chain() + .focus() + .setMark('interimMark') // <-- "Pincel cinza" LIGADO + .insertContent(transcript) // <-- Insere o texto + .unsetMark('interimMark') // <-- "Pincel cinza" DESLIGADO + .run(); + + // Atualiza a Ref com a posição do texto cinza + lastInsertedRef.current = { + from: currentPos, + to: currentPos + transcript.length, + text: transcript + }; + } + // Não precisamos mais do 'editorChain.run()' aqui embaixo + }; + + recognition.onerror = (err) => { + // ... (código do onerror sem mudanças) + }; + + recognition.onend = () => { + // ... (código do onend sem mudanças) + }; + + recognitionRef.current = recognition; + + return () => { + recognition.stop(); + }; + + }, [editor, isRecording]); + + const toggleRecording = () => { + if (!recognitionRef.current) return; + + if (isRecording) { + // Usuário clicou para PARAR + setIsRecording(false); // <-- Seta o estado + recognitionRef.current.stop(); // <-- Para a API + // O 'onend' será chamado e fará a limpeza/confirmação. + } else { + // Usuário clicou para COMEÇAR + editor?.chain().focus().run(); + setIsRecording(true); // <-- Seta o estado + recognitionRef.current.start(); // <-- Inicia a API + } + }; + useEffect(() => { + if (editor && Laudos?.content_html) { + editor.commands.setContent(Laudos.content_html); + } + }, [editor, Laudos]); + const comandos = { + toggleBold: () => editor.chain().focus().toggleBold().run(), + toggleItalic: () => editor.chain().focus().toggleItalic().run(), + toggleUnderline: () => editor.chain().focus().toggleUnderline().run(), + toggleCodeBlock: () => editor.chain().focus().toggleCodeBlock().run(), + toggleH1: () => editor.chain().focus().toggleHeading({ level: 1 }).run(), + toggleH2: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), + toggleH3: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), + toggleParrafo: () => editor.chain().focus().setParagraph().run(), + toggleListaOrdenada: () => editor.chain().focus().toggleOrderedList().run(), + toggleListaPontos: () => editor.chain().focus().toggleBulletList().run(), + agregarImagen: (url) => { + if (!url) return; + editor.chain().focus().setImage({ src: url }).run(); + }, + + agregarLink: () => { + const url = window.prompt('URL do link') + if (url) { + editor.chain().focus().setLink({ href: url }).run() + } + } + } + const handleChange = (e) => { + const { name, value } = e.target; + setLaudos((prev) => ({ + ...prev, + [name]: value + })); + }; + const [pacientesMap, setPacientesMap] = useState({}); + useEffect(() => { + if (!Laudos || !Laudos.patient_id) return; + const buscarPacientes = async () => { + try { + // Pega IDs únicos de pacientes + const idsUnicos = [Laudos.patient_id]; + + // Faz apenas 1 fetch por paciente + const promises = idsUnicos.map(async (id) => { + try { + const res = await fetch( + `${supabaseUrl}/rest/v1/patients?id=eq.${id}`, + { + method: "GET", + headers: { + apikey: + supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + }, + } + ); + const data = await res.json(); + return { id, full_name: data[0]?.full_name || "Nome não encontrado" }; + } catch (err) { + return { id, full_name: "Nome não encontrado" }; + } + }); + + const results = await Promise.all(promises); + + const map = {}; + results.forEach((r) => (map[r.id] = r.full_name)); + setPacientesMap(map); + } catch (err) { + console.error("Erro ao buscar pacientes:", err); + } + }; + + buscarPacientes(); + }, [Laudos]); + return ( +
+
+

Laudo Médico

+
+ + setOpen(!open)} + aria-controls="paciente-content" + aria-expanded={open} + className="d-flex justify-content-between align-items-center" + style={{ + cursor: "pointer", + borderRadius: "25px", + padding: "12px 20px", + }} + > + Informações do paciente + {open ? : } + + + +
+ + + + + + + + + +
+
+
+
+ + +
+
+ ); +} +export default VerLaudo; diff --git a/src/components/call/RoomPage.jsx b/src/components/call/RoomPage.jsx new file mode 100644 index 0000000..736733e --- /dev/null +++ b/src/components/call/RoomPage.jsx @@ -0,0 +1,206 @@ +import React, { useRef, useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import { ZegoUIKitPrebuilt } from "@zegocloud/zego-uikit-prebuilt"; +import { getFullName } from "../../utils/userInfo"; +import { getPatientId } from "../../utils/userInfo"; +import { getAccessToken } from "../../utils/auth"; +import { getDoctorId } from "../../utils/userInfo"; +import TranscriptBlock from "./TranscriptBlock"; // Componente da barra lateral + +const RoomPage = () => { +  const { roomId } = useParams(); +  const meetingRef = useRef(null); +  const [userInfo, setUserInfo] = useState(null); +  const [kitToken, setKitToken] = useState(null); +  const [canJoin, setCanJoin] = useState(false); +  const [consulta, setConsulta] = useState(null); +  const [windowWidth, setWindowWidth] = useState(window.innerWidth); +  +  // Variáveis de ambiente e token +  const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; +  const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; +  const tokenUsuario = getAccessToken(); +  +  // Variável para determinar o modo (Mobile/Desktop) +  const isMobile = windowWidth < 1024; + +  // --- Efeitos e Lógica (Mantidos) --- + +  useEffect(() => { +    const handleResize = () => setWindowWidth(window.innerWidth); +    window.addEventListener('resize', handleResize); +    return () => window.removeEventListener('resize', handleResize); +  }, []); + +  useEffect(() => { +    async function fetchUserAndValidate() { +      var myHeaders = new Headers(); +      myHeaders.append("apikey", supabaseAK); +      myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); +      var requestOptions = { +        method: 'GET', +        headers: myHeaders, +        redirect: 'follow' +      }; +      // Busca a consulta específica pelo roomId +      const response = await fetch(`${supabaseUrl}/rest/v1/appointments?id=eq.${roomId}`, requestOptions); +      const result = await response.json(); +      const consultaData = Array.isArray(result) ? result[0] : null; +      setConsulta(consultaData); +      +      const userPatient = { id: getPatientId(), name: getFullName() }; +      const userDoctor = { id: getDoctorId(), name: getFullName() }; +      const user = consultaData?.patient_id === userPatient.id ? userPatient : userDoctor; +      +      const isMedico = user.id === consultaData?.doctor_id; +      const uniqueId = (user.id || "") + '-' + Date.now(); +      const isResponsavel = user.id === consultaData?.doctor_id || user.id === consultaData?.patient_id; +      +      if (isResponsavel) { +        setUserInfo({ name: user.name, id: uniqueId, isMedico }); +      } else { +        alert('Você não tem permissão para acessar esta sala.'); +      } +    } +    fetchUserAndValidate(); +  }, [roomId, supabaseAK, supabaseUrl, tokenUsuario]); + +  useEffect(() => { +    if (!userInfo) return; +    const appID = 1934403252; +    const serverSecret = "7290704fc5dca533b3633cf22b1a2635"; +    const token = ZegoUIKitPrebuilt.generateKitTokenForTest( +      appID, +      serverSecret, +      roomId, +      userInfo.id, +      userInfo.name +    ); +    setKitToken(token); +  }, [userInfo, roomId]); + +  const handleJoin = () => { +    navigator.mediaDevices.getUserMedia({ video: true, audio: true }) +      .then(() => { +        setCanJoin(true); +      }) +      .catch(() => { +        alert('Permita o acesso à câmera e microfone para usar a videoconferência.'); +      }); +  }; + +  useEffect(() => { +    if (!kitToken || !meetingRef.current || !canJoin) return; +    const zp = ZegoUIKitPrebuilt.create(kitToken); +    zp.joinRoom({ +      container: meetingRef.current, +      scenario: { +        mode: ZegoUIKitPrebuilt.VideoConference, +      }, +      showPreJoinView: false, +      showRemoveUserButton: userInfo?.isMedico === true, +    }); +  }, [kitToken, canJoin, userInfo]); + + +  // --- ESTILOS RESPONSIVOS E DE CORES --- + +  const fullScreenStyle = { +    width: "100vw", +    height: "100vh", +    margin: 0, +    padding: 0, +    overflow: "hidden", +    boxSizing: 'border-box', +  }; + +  const callContainerStyle = isMobile ? +    { +      display: "flex", +      flexDirection: "column", +      height: "100%", +      width: "100%", +      boxSizing: 'border-box', +    } : +    { +      display: "flex", +      height: "100%", +      width: "100%", +      boxSizing: 'border-box', +    }; + +  // Ajuste AQUI +  const videoAreaStyle = isMobile ? +    { +      flex: 'none', +      width: "100%", +      height: "40vh", // <<< NOVO E ÚLTIMO AJUSTE: Reduzido para 40vh (dá mais folga) +      minHeight: "300px", +      boxSizing: 'border-box', +    } : +    { +      flex: 2, +      height: "100%", +      boxSizing: 'border-box', +    }; + +  // Ajuste AQUI +  const transcriptAreaStyle = isMobile ? +    { +      flex: 'none', +      width: "100%", +      height: "calc(100vh - 40vh)", // <<< NOVO AJUSTE: Calculado com base em 40vh +      background: "#1c1f2e", +      overflowY: "auto", +      boxSizing: 'border-box', +      padding: '16px', +    } : +    { +      flex: 1, +      minWidth: 350, +      maxWidth: 500, +      background: "#1c1f2e", +      height: "100%", +      overflowY: "auto", +      boxSizing: 'border-box', +      padding: '16px', +    }; + +  // --- RENDERIZAÇÃO (Mantida) --- + +  return ( +   
+     
+        {!canJoin ? ( +          // Tela de Permissão de Câmera/Microfone +         
+           

Permita o acesso à câmera e microfone para entrar na chamada

+            +         
+        ) : ( +          // Tela da Chamada com Layout Responsivo +         
+            {/* Área de Vídeo (Zego Cloud) */} +           
+            +            {/* Área de Transcrição/Bloco de Texto */} +           
+              {consulta && ( +                +              )} +           
+         
+        )} +     
+   
+  ); +}; + +export default RoomPage; \ No newline at end of file diff --git a/src/components/call/TranscriptBlock.jsx b/src/components/call/TranscriptBlock.jsx new file mode 100644 index 0000000..d58f1e4 --- /dev/null +++ b/src/components/call/TranscriptBlock.jsx @@ -0,0 +1,268 @@ +import React, { useState, useRef, useEffect } from "react"; +import { getAccessToken } from "../../utils/auth"; +import { useNavigate } from "react-router-dom"; +import { getDoctorId } from "../../utils/userInfo"; +import Swal from "sweetalert2"; +import { getUserRole } from "../../utils/userInfo"; + +const TranscriptBlock = ({ appointmentId, doctor_id, patient_id, exam }) => { + const [transcript, setTranscript] = useState(""); + const [isEditing, setIsEditing] = useState(false); + const [isRecording, setIsRecording] = useState(false); + const [isSaving, setIsSaving] = useState(false); + const recognitionRef = useRef(null); + const navigate = useNavigate(); + const [laudos, setLaudos] = useState({ + patient_id: patient_id, + order_number: "", + exam: exam || "", + diagnosis: "", + conclusion: "", + cid_code: "", + content_html: transcript, + status: "draft", + requested_by: doctor_id, + }); + const isMedico = getDoctorId() === doctor_id; + + const Change = (e) => { + const { name, value } = e.target; + setLaudos((prev) => ({ ...prev, [name]: value })); + }; + const handleChange = (event) => { + setTranscript(event.target.value); + }; + useEffect(() => { + setLaudos((prev) => ({ + ...prev, + content_html: transcript + })); + }, [transcript]); + + const handleStartStop = () => { + if (!isRecording) { + const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + if (!SpeechRecognition) { + alert("Seu navegador não suporta reconhecimento de voz."); + return; + } + const recognition = new SpeechRecognition(); + recognition.lang = "pt-BR"; + recognition.continuous = true; + recognition.interimResults = true; + recognition.onresult = (event) => { + let interim = ""; + let final = ""; + for (let i = 0; i < event.results.length; ++i) { + if (event.results[i].isFinal) { + final += event.results[i][0].transcript; + } else { + interim += event.results[i][0].transcript; + } + } + setTranscript((prev) => final + interim); + }; + recognition.onend = () => setIsRecording(false); + recognition.onerror = () => setIsRecording(false); + recognitionRef.current = recognition; + recognition.start(); + setIsRecording(true); + } else { + recognitionRef.current && recognitionRef.current.stop(); + setIsRecording(false); + } + }; + + const handleSaveAsLaudo = async () => { + setIsSaving(true); + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + const tokenUsuario = getAccessToken(); + try { + const response = await fetch(`${supabaseUrl}/rest/v1/reports`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "apikey": supabaseAK, + "Authorization": `Bearer ${tokenUsuario}` + }, + body: JSON.stringify({ ...laudos }) + }); + if (response.ok) { + await Swal.fire({ + icon: 'success', + title: 'Laudo salvo com sucesso!', + showConfirmButton: false, + timer: 1800 + }); + } else { + const error = await response.text(); + await Swal.fire({ + icon: 'error', + title: 'Erro ao salvar o laudo', + text: error, + }); + console.log("Payload enviado:", laudos); + } + } catch (err) { + await Swal.fire({ + icon: 'error', + title: 'Erro ao salvar o laudo', + text: err.message || '', + }); + } + setIsSaving(false); + }; + + const handleCorrigirComIA = async () => { + setIsSaving(true); + try { + const response = await fetch(import.meta.env.VITE_API_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + contents: [ + { role: "user", parts: [{ text: `Corrija erros de português, termos médicos e pontuação neste texto de transcrição de consulta médica, mantendo o sentido original e sem inventar informações. Responda apenas com o texto corrigido.\n\n${transcript}` }] } + ] + }) + }); + const data = await response.json(); + if (!response.ok) throw new Error(data.error?.message || "Erro ao corrigir com IA"); + const textoCorrigido = data.candidates?.[0]?.content?.parts?.[0]?.text || transcript; + setTranscript(textoCorrigido.trim()); + await Swal.fire({ + icon: 'success', + title: 'Texto corrigido com IA!', + showConfirmButton: false, + timer: 1500 + }); + } catch (err) { + await Swal.fire({ + icon: 'error', + title: 'Erro ao corrigir com IA', + text: err.message || '', + }); + } + setIsSaving(false); + }; + + // NOVO: Finalizar consulta + const handleFinalizarConsulta = async () => { + setIsSaving(true); + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + const tokenUsuario = getAccessToken(); + try { + await fetch(`${supabaseUrl}/rest/v1/appointments?id=eq.${appointmentId}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + "apikey": supabaseAK, + "Authorization": `Bearer ${tokenUsuario}` + }, + body: JSON.stringify({ status: "completed" }) + }); + await Swal.fire({ + icon: 'success', + title: 'Consulta finalizada!', + showConfirmButton: false, + timer: 1800 + }); + navigate(`/${getUserRole()}/consultalist`); // Altere para o caminho do seu menu/dash se necessário + } catch (err) { + await Swal.fire({ + icon: 'error', + title: 'Erro ao finalizar consulta', + text: err.message || '', + }); + } + setIsSaving(false); + }; + + const buttonStyle = { + padding: "10px 24px", + fontSize: "1rem", + borderRadius: "8px", + background: isRecording ? '#e74c3c' : '#1976d2', + color: "#fff", + border: "none", + cursor: "pointer", + marginTop: "12px", + marginRight: "8px", + transition: "background 0.2s" + }; + + const secondaryButtonStyle = { + ...buttonStyle, + background: "#fff", + color: "#1976d2", + border: "1px solid #1976d2" + }; + + return ( +
+

+ Transcrição / Bloco de Texto +

+

+ Você pode digitar, colar, editar manualmente, usar o microfone para transcrever ou corrigir com IA. +

+ + + +
+ + + +
+ + Voltar + + +
+ +
+ + + + ); +} + +export default ConsultaEdit; \ No newline at end of file diff --git a/src/components/edits/DoctorEdit.jsx b/src/components/edits/DoctorEdit.jsx new file mode 100644 index 0000000..a3629a2 --- /dev/null +++ b/src/components/edits/DoctorEdit.jsx @@ -0,0 +1,446 @@ +import "../../assets/css/index.css"; +import { withMask } from "use-mask-input"; +import { useState, useEffect } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import Swal from "sweetalert2"; +import { getAccessToken } from "../../utils/auth.js"; +import { useResponsive } from '../../utils/useResponsive.js'; +import { getUserRole } from "../../utils/userInfo.js"; + + +function EditDoctor() { + const [doctorData, setDoctorData] = useState({}); + const { id } = useParams(); + const navigate = useNavigate(); + const role = getUserRole(); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + + // Buscar médico pelo ID + useEffect(() => { + const fetchDoctor = async () => { + try { + const tokenUsuario = getAccessToken(); + + const response = await fetch( + `${supabaseUrl}/rest/v1/doctors?id=eq.${id}`, + { + method: "GET", + headers: { + apikey: supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + }, + } + ); + + const data = await response.json(); + if (data && data.length > 0) { + setDoctorData(data[0]); + } + } catch (err) { + console.error("Erro ao buscar médico:", err); + } + }; + + fetchDoctor(); + }, [id]); + + // Atualizar campos + const handleChange = (e) => { + const { name, value } = e.target; + setDoctorData((prev) => ({ + ...prev, + [name]: value, + })); + }; + + // Buscar CEP + const buscarCep = () => { + const cep = doctorData.cep?.replace(/\D/g, ""); + if (!cep) return; + + fetch(`https://viacep.com.br/ws/${cep}/json/`) + .then((response) => response.json()) + .then((data) => { + setDoctorData((prev) => ({ + ...prev, + city: data.localidade || "", + state: data.uf || "", + street: data.logradouro || "", + neighborhood: data.bairro || "", + })); + }) + .catch((err) => console.error("Erro ao buscar CEP:", err)); + }; + + // Salvar alterações + const handleEdit = async (e) => { + e.preventDefault(); + + const result = await Swal.fire({ + title: "Deseja salvar as alterações?", + text: "As modificações serão salvas permanentemente.", + icon: "question", + showDenyButton: true, + showCancelButton: true, + confirmButtonText: "Salvar", + denyButtonText: `Não salvar`, + cancelButtonText: "Cancelar", + }); + + if (result.isConfirmed) { + try { + const tokenUsuario = getAccessToken(); + + const response = await fetch( + `${supabaseUrl}/rest/v1/doctors?id=eq.${id}`, + { + method: "PATCH", + headers: { + apikey: supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(doctorData), + } + ); + + if (!response.ok) { + const err = await response.json(); + Swal.fire("Erro!", err.message || "Não foi possível salvar.", "error"); + return; + } + + await Swal.fire("Sucesso!", "As alterações foram salvas.", "success"); + navigate(`/${role}/doctorlist`); + } catch (err) { + console.error("Erro inesperado:", err); + Swal.fire("Erro!", "Não foi possível salvar as alterações.", "error"); + } + } + }; + + return ( +
+
+
+
+
+

Editar Médico

+
+
+ +
+
+
+
+ {/* Nome completo */} +
+
+ + +
+
+ + {/* CPF */} +
+
+ + +
+
+ + {/* Email */} +
+
+ + +
+
+ + {/* Telefone */} +
+
+ + +
+
+ + {/* CRM */} +
+
+ + +
+
+ + {/* CRM UF */} +
+
+ + +
+
+ + {/* Especialidade */} +
+
+ + +
+
+ + {/* Data de nascimento */} +
+
+ + +
+
+ + {/* CEP */} +
+
+ + +
+
+ + {/* Rua */} +
+
+ + +
+
+ + {/* Número */} +
+
+ + +
+
+ + {/* Complemento */} +
+
+ + +
+
+ + {/* Bairro */} +
+
+ + +
+
+ + {/* Cidade */} +
+
+ + +
+
+ + {/* Estado */} +
+
+ + +
+
+ + {/* Ativo/Inativo */} +
+
+ +
+ + setDoctorData((prev) => ({ ...prev, active: true })) + } + /> + +
+
+ + setDoctorData((prev) => ({ ...prev, active: false })) + } + /> + +
+
+
+
+ +
+ +
+
+
+
+
+
+
+ ); +} + +export default EditDoctor; \ No newline at end of file diff --git a/src/components/edits/LaudoEdit.jsx b/src/components/edits/LaudoEdit.jsx new file mode 100644 index 0000000..328d6db --- /dev/null +++ b/src/components/edits/LaudoEdit.jsx @@ -0,0 +1,478 @@ +// PatientList.jsx +import { useEditor, EditorContent } from '@tiptap/react' +import StarterKit from '@tiptap/starter-kit' +import Image from '@tiptap/extension-image'; +import { Link } from 'react-router-dom'; +import { useEffect, useState } from 'react'; +import { getAccessToken } from '../../utils/auth'; +import { useParams } from 'react-router-dom'; +import { Card, Collapse } from "react-bootstrap"; // <-- IMPORT CORRETO +import { ChevronDown, ChevronUp } from "lucide-react"; +import Select from 'react-select'; +import Swal from 'sweetalert2'; +import { useNavigate } from 'react-router-dom'; +import { useRef } from 'react'; +import { InterimMark } from '../../utils/InterimMark'; +import { FaMicrophone } from "react-icons/fa"; +import { getUserRole } from '../../utils/userInfo'; +function Bar({ comandos, handleSubmit, toggleRecording, isRecording }) { + const inputRef = useRef(null); + + const handleAbrirExplorador = () => { + inputRef.current.click(); // abre o explorador + }; + + const handleArquivoSelecionado = (event) => { + const arquivo = event.target.files[0]; + if (arquivo) { + const imageUrl = URL.createObjectURL(arquivo); + comandos.agregarImagen(imageUrl); + event.target.value = null; + } + }; + + return ( + <> +
+
+ + + + {/* */} + + + + + + + + <> + + + + + + + {/* */} +
+
+ +
+
+ + ); +}; + +function LaudoAdmEdit() { + const navigate = useNavigate(); + const [open, setOpen] = useState(false); + const [Laudos, setLaudos] = useState({}) + const tokenUsuario = getAccessToken() + const { id } = useParams() + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + const role = getUserRole(); + var myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + myHeaders.append("Content-Type", "application/json"); + + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + useEffect(() => { + fetch(`${supabaseUrl}/rest/v1/reports?id=eq.${id}`, requestOptions) + .then(response => response.json()) + .then(result => { + // result é um array, pegue o primeiro laudo + const laudo = Array.isArray(result) ? result[0] : null; + setLaudos(laudo || {}); + }) + .catch(error => console.log('error', error)); + }, [id]); + const editor = useEditor({ + extensions: [StarterKit, Image, InterimMark], + content: Laudos?.content_html || "

Escreva o laudo aqui...

", + onUpdate: ({ editor }) => { + setLaudos({ + ...Laudos, + content_html: editor.getHTML() + }); + } + }); + const [isRecording, setIsRecording] = useState(false); + const recognitionRef = useRef(null); + const lastInsertedRef = useRef({ from: -1, to: -1, text: '' }); + + useEffect(() => { + const SpeechRecognition = + window.SpeechRecognition || window.webkitSpeechRecognition; + + if (!SpeechRecognition) { + alert("Seu navegador não suporta reconhecimento de voz 😢"); + return; + } + + const recognition = new SpeechRecognition(); + recognition.lang = "pt-BR"; + recognition.continuous = false; + recognition.interimResults = true; + + recognition.onresult = (event) => { + if (!editor) return; + + const result = event.results[0]; + const transcript = result[0].transcript; + const last = lastInsertedRef.current; + + // --- CORREÇÃO DE LÓGICA --- + // Vamos rodar a deleção como um comando SEPARADO primeiro. + if (last.from !== -1 && + editor.state.doc.textBetween(last.from, last.to) === last.text) + { + // Roda a deleção e PARA. + editor.chain().focus() + .deleteRange({ from: last.from, to: last.to }) + .run(); + } + + // Pega a posição ATUAL (depois da deleção) + const currentPos = editor.state.selection.from; + + if (result.isFinal) { + // --- RESULTADO FINAL (PRETO) --- + // Roda a inserção final como um comando SEPARADO. + editor.chain().focus() + .insertContent(transcript + ' ') + .run(); + + // Reseta a Ref + lastInsertedRef.current = { from: -1, to: -1, text: '' }; + + } else { + // --- RESULTADO PROVISÓRIO (CINZA) --- + // Esta é a nova estratégia: "Ligar" a mark, inserir, "Desligar" a mark. + // Roda tudo como um comando SEPARADO. + editor.chain() + .focus() + .setMark('interimMark') // <-- "Pincel cinza" LIGADO + .insertContent(transcript) // <-- Insere o texto + .unsetMark('interimMark') // <-- "Pincel cinza" DESLIGADO + .run(); + + // Atualiza a Ref com a posição do texto cinza + lastInsertedRef.current = { + from: currentPos, + to: currentPos + transcript.length, + text: transcript + }; + } + // Não precisamos mais do 'editorChain.run()' aqui embaixo + }; + + recognition.onerror = (err) => { + // ... (código do onerror sem mudanças) + }; + + recognition.onend = () => { + // ... (código do onend sem mudanças) + }; + + recognitionRef.current = recognition; + + return () => { + recognition.stop(); + }; + + }, [editor, isRecording]); + + const toggleRecording = () => { + if (!recognitionRef.current) return; + + if (isRecording) { + // Usuário clicou para PARAR + setIsRecording(false); // <-- Seta o estado + recognitionRef.current.stop(); // <-- Para a API + // O 'onend' será chamado e fará a limpeza/confirmação. + } else { + // Usuário clicou para COMEÇAR + editor?.chain().focus().run(); + setIsRecording(true); // <-- Seta o estado + recognitionRef.current.start(); // <-- Inicia a API + } + }; + useEffect(() => { + if (editor && Laudos?.content_html) { + editor.commands.setContent(Laudos.content_html); + } + }, [editor, Laudos]); + const comandos = { + toggleBold: () => editor.chain().focus().toggleBold().run(), + toggleItalic: () => editor.chain().focus().toggleItalic().run(), + toggleUnderline: () => editor.chain().focus().toggleUnderline().run(), + toggleCodeBlock: () => editor.chain().focus().toggleCodeBlock().run(), + toggleH1: () => editor.chain().focus().toggleHeading({ level: 1 }).run(), + toggleH2: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), + toggleH3: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), + toggleParrafo: () => editor.chain().focus().setParagraph().run(), + toggleListaOrdenada: () => editor.chain().focus().toggleOrderedList().run(), + toggleListaPuntos: () => editor.chain().focus().toggleBulletList().run(), + agregarImagen: (url) => { + if (!url) return; + editor.chain().focus().setImage({ src: url }).run(); + }, + + agregarLink: () => { + const url = window.prompt('URL do link') + if (url) { + editor.chain().focus().setLink({ href: url }).run() + } + } + } + const handleChange = (e) => { + const { name, value } = e.target; + setLaudos((prev) => ({ + ...prev, + [name]: value + })); + }; + const [pacientesMap, setPacientesMap] = useState({}); + useEffect(() => { + if (!Laudos || !Laudos.patient_id) return; + const buscarPacientes = async () => { + try { + // Pega IDs únicos de pacientes + const idsUnicos = [Laudos.patient_id]; + + // Faz apenas 1 fetch por paciente + const promises = idsUnicos.map(async (id) => { + try { + const res = await fetch( + `${supabaseUrl}/rest/v1/patients?id=eq.${id}`, + { + method: "GET", + headers: { + apikey: + supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + }, + } + ); + const data = await res.json(); + return { id, full_name: data[0]?.full_name || "Nome não encontrado" }; + } catch (err) { + return { id, full_name: "Nome não encontrado" }; + } + }); + + const results = await Promise.all(promises); + + const map = {}; + results.forEach((r) => (map[r.id] = r.full_name)); + setPacientesMap(map); + } catch (err) { + console.error("Erro ao buscar pacientes:", err); + } + }; + + buscarPacientes(); + }, [Laudos]); + + const handleSubmit = (e) => { + e.preventDefault() + Swal.fire({ + title: "Você deseja salvar as alterações?", + text: "As modificações serão salvas permanentemente.", + icon: "question", + showDenyButton: true, + showCancelButton: true, + cancelButtonText: "Cancelar", + confirmButtonText: "Salvar", + denyButtonText: "Não salvar", + }).then(async (result) => { + if (result.isConfirmed) { + try { + var myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + myHeaders.append("Content-Type", "application/json"); + var raw = JSON.stringify({ + patient_id: Laudos.patient_id, + exam: Laudos.exam, + diagnosis: Laudos.diagnosis, + conclusion: Laudos.conclusion, + content_html: Laudos.content_html, + status: "draft", + }); + + var requestOptions = { + method: "PATCH", + headers: myHeaders, + body: raw, + redirect: "follow", + }; + + const response = await fetch( + `${supabaseUrl}/rest/v1/reports?id=eq.${id}`, + requestOptions + ); + + if (response.ok) { + Swal.fire("Salvo!", "", "success").then(() => { + navigate(`/${role}/laudolist`); + }) + } else { + Swal.fire("Error saving changes", "", "error"); + } + } catch (error) { + Swal.fire("Something went wrong", "", "error"); + console.error(error); + } + } else if (result.isDenied) { + Swal.fire("As alterações não foram salvas", "", "info"); + } + }); + } + + return ( +
+
+

Laudo Médico

+
+ + setOpen(!open)} + aria-controls="paciente-content" + aria-expanded={open} + className="d-flex justify-content-between align-items-center" + style={{ + cursor: "pointer", + borderRadius: "25px", + padding: "12px 20px", + }} + > + Informações do paciente + {open ? : } + + + +
+ + + + + + + + + +
+
+
+
+ + +
+
+ ); +} +export default LaudoAdmEdit; diff --git a/src/pages/Patient/PatientEdit.jsx b/src/components/edits/PatientEdit.jsx similarity index 79% rename from src/pages/Patient/PatientEdit.jsx rename to src/components/edits/PatientEdit.jsx index 035b88c..4f4b919 100644 --- a/src/pages/Patient/PatientEdit.jsx +++ b/src/components/edits/PatientEdit.jsx @@ -1,23 +1,47 @@ import { useState } from "react"; import { useEffect } from "react"; -import "../../assets/css/index.css" +import "../../assets/css/index.css"; import { withMask } from "use-mask-input"; -import supabase from "../../Supabase" -import { Link } from "react-router-dom"; -import { useParams } from "react-router-dom"; +import { Link, useNavigate, useParams } from "react-router-dom"; +import Swal from "sweetalert2"; +import { getAccessToken } from "../../utils/auth.js"; +const AvatarForm = "/img/AvatarForm.jpg"; +const AnexoDocumento = "/img/AnexoDocumento.png"; +import { useResponsive } from '../../utils/useResponsive.js'; +import { getUserRole } from "../../utils/userInfo.js"; + + function PatientEdit() { + //testando + const role = getUserRole(); + const tokenUsuario = getAccessToken() const [patients, setpatients] = useState([""]) - const{id} = useParams() + const { id } = useParams() + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; // carregando a lista e adicionando no usestate + var myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + // eh carregda a lista useEffect(() => { - fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${id}`) - .then((response) => response.json()) - .then((result) => setpatients(result.data || {})) - .catch((error) => console.log("error", error)); - }, [id]); + fetch(`${supabaseUrl}/rest/v1/patients`, requestOptions) + .then(response => response.json()) + .then(result => { + const paciente = result.find(p => p.id == id); + setpatients(paciente || []); + console.log(paciente) + }) + .catch(error => console.log('error', error)); + }, []) const [preview, setPreview] = useState(null); - + const navigate = useNavigate(); useEffect(() => { if (patients.foto_url) { setPreview('https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSr6OBNqnFlVKC6fAk-mzSuzmOKgjWMYq9y0g&s'); @@ -25,20 +49,53 @@ function PatientEdit() { }, [patients.foto_url]); const handleEdit = async (e) => { - const requestOptions = { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(patients), - redirect: "follow", - }; - fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${id}`, requestOptions) - .then((response) => response.json()) - .then((result) => { - alert("Paciente editado com sucesso!"); - // redireciona após editar - }) - .catch((error) => console.log("error", error)); - }; + e.preventDefault() + Swal.fire({ + title: "Você deseja salvar as alterações?", + text: "As modificações serão salvas permanentemente.", + icon: "question", + showDenyButton: true, + showCancelButton: true, + cancelButtonText: "Cancelar", + confirmButtonText: "Salvar", + denyButtonText: "Não salvar", + }).then(async (result) => { + if (result.isConfirmed) { + try { + var myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + myHeaders.append("Content-Type", "application/json"); + var raw = JSON.stringify(patients); + + var requestOptions = { + method: "PATCH", + headers: myHeaders, + body: raw, + redirect: "follow", + }; + + const response = await fetch( + `${supabaseUrl}/rest/v1/patients?id=eq.${id}`, + requestOptions + ); + + if (response.ok) { + Swal.fire("Salvo!", "", "success").then(() => { + navigate(`/${role}/patientlist`); + }) + } else { + Swal.fire("Error saving changes", "", "error"); + } + } catch (error) { + Swal.fire("Something went wrong", "", "error"); + console.error(error); + } + } else if (result.isDenied) { + Swal.fire("As alterações não foram salvas", "", "info"); + } + }); + } // aqui eu fiz uma funçao onde atualiza o estado do paciente, eu poderia ir mudando com o onchange em cada input mas assim ficou melhor // e como se fosse 'onChange={(e) => setpatientData({ ...patientData, rg: e.target.value })}' // prev= pega o valor anterio @@ -48,35 +105,36 @@ function PatientEdit() { ...prev, [name]: value })); - }; - const buscarCep = (e) => { + }; + const buscarCep = (e) => { const cep = patients.cep.replace(/\D/g, ''); console.log(cep); fetch(`https://viacep.com.br/ws/${cep}/json/`) .then(response => response.json()) .then(data => { - console.log(data) - // salvando os valores para depois colocar nos inputs - setValuesFromCep(data) - // estou salvando os valoeres no patientData - setpatients((prev) => ({ - ...prev, - cidade: data.localidade || '', - logradouro: data.logradouro || '', - bairro: data.bairro || '', - estado: data.estado || '' - })); + console.log(data) + // salvando os valores para depois colocar nos inputs + setValuesFromCep(data) + // estou salvando os valoeres no patientData + setpatients((prev) => ({ + ...prev, + city: data.localidade || '', + street: data.logradouro || '', + neighborhood: data.bairro || '', + state: data.estado || '' + })); }) - } + } // aqui esta sentando os valores nos inputs const setValuesFromCep = (data) => { - document.getElementById('logradouro').value = data.logradouro || ''; - document.getElementById('bairro').value = data.bairro || ''; - document.getElementById('cidade').value = data.localidade || ''; - document.getElementById('estado').value = data.uf || ''; + document.getElementById('street').value = data.logradouro || ''; + document.getElementById('neighborhood').value = data.bairro || ''; + document.getElementById('city').value = data.localidade || ''; + document.getElementById('state').value = data.uf || ''; } - - + // aqui eu fiz uma funçao onde atualiza o estado do paciente, eu poderia ir mudando com o onchange em cada input mas assim ficou melhor + // e como se fosse 'onChange={(e) => setpatientData({ ...patientData, rg: e.target.value })}' + // prev= pega o valor anterio const validarCpf = async (cpf) => { const cpfLimpo = cpf.replace(/\D/g, ""); try { @@ -89,7 +147,7 @@ function PatientEdit() { }); const data = await response.json(); if (data.valido === false) { - + alert("CPF inválido!"); return false; } else if (data.valido === true) { @@ -106,7 +164,7 @@ function PatientEdit() { const handleSubmit = async (e) => { e.preventDefault(); - + const cpfValido = await validarCpf(patientData.cpf); // Calcula idade a partir da data de nascimento @@ -152,7 +210,7 @@ function PatientEdit() {
- +
@@ -160,8 +218,8 @@ function PatientEdit() { { - handleChange(e); - setPreview(URL.createObjectURL(e.target.files[0])); + handleChange(e); + setPreview(URL.createObjectURL(e.target.files[0])); }} type="file" accept="image/png, image/jpeg" @@ -193,9 +251,9 @@ function PatientEdit() { console.log("Erro ao remover foto:", error); } }} - > + > Limpar -
+
@@ -205,11 +263,11 @@ function PatientEdit() { - @@ -238,10 +296,10 @@ function PatientEdit() {
@@ -272,8 +330,8 @@ function PatientEdit() {
@@ -281,8 +339,8 @@ function PatientEdit() {
@@ -303,8 +361,8 @@ function PatientEdit() {
@@ -320,15 +378,15 @@ function PatientEdit() {
-
@@ -350,31 +408,31 @@ function PatientEdit() {
@@ -382,27 +440,27 @@ function PatientEdit() {
@@ -417,15 +475,15 @@ function PatientEdit() {
@@ -433,7 +491,7 @@ function PatientEdit() {
-
@@ -467,27 +525,27 @@ function PatientEdit() {
@@ -495,35 +553,35 @@ function PatientEdit() {
-
@@ -578,7 +636,7 @@ function PatientEdit() {
- +
@@ -589,12 +647,11 @@ function PatientEdit() {
- - - +
@@ -603,6 +660,6 @@ function PatientEdit() {
); -}; +} export default PatientEdit; diff --git a/src/components/forms/AgendaForm.jsx b/src/components/forms/AgendaForm.jsx new file mode 100644 index 0000000..bc38983 --- /dev/null +++ b/src/components/forms/AgendaForm.jsx @@ -0,0 +1,250 @@ +import "../../assets/css/index.css" +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { getAccessToken } from "../../utils/auth.js"; +import { getUserRole } from "../../utils/userInfo.js"; + +function AgendaForm() { + const [doctors, setDoctors] = useState([]); + const [doctorId, setDoctorId] = useState(""); + const [weekday, setWeekday] = useState(""); + const [startTime, setStartTime] = useState(""); + const [endTime, setEndTime] = useState(""); + const [appointmentType, setAppointmentType] = useState("presencial"); + const [active, setActive] = useState(true); + const [loading, setLoading] = useState(true); + const role = getUserRole(); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || "https://yuanqfswhberkoevtmfr.supabase.co"; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + + const navigate = useNavigate(); + const tokenUsuario = getAccessToken(); + + const headers = { + apikey: + supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + "Content-Type": "application/json", + }; + + // Buscar médicos + useEffect(() => { + setLoading(true); + fetch(`${supabaseUrl}/rest/v1/doctors`, { + headers, + }) + .then(async (res) => { + if (!res.ok) { + const text = await res.text(); + throw new Error(`Erro ${res.status}: ${text}`); + } + return res.json(); + }) + .then((data) => { + console.log("Médicos carregados:", data); + setDoctors(Array.isArray(data) ? data : []); + }) + .catch((err) => { + console.error("Erro ao carregar médicos:", err); + setDoctors([]); + }) + .finally(() => setLoading(false)); + }, []); + + // Criar disponibilidade + const handleSubmit = async (e) => { + e.preventDefault(); + + if (!doctorId || !weekday || !startTime || !endTime) { + alert("Preencha todos os campos obrigatórios!"); + return; + } + + // ✅ Tenta pegar o ID do usuário logado, se existir + // (caso o tokenUsuario contenha JWT com o UUID do usuário) + let createdBy = null; + try { + const payload = JSON.parse(atob(tokenUsuario.split(".")[1])); + createdBy = payload?.sub || null; + } catch (error) { + console.warn("Token inválido ou sem UUID. Usando null para created_by."); + } + + const body = { + doctor_id: doctorId, + weekday, + start_time: startTime, + end_time: endTime, + slot_minutes: 30, + appointment_type: appointmentType, + active, + created_by: createdBy, // ✅ Envia null se não houver UUID válido + }; + + console.log("Enviando agenda:", body); + + fetch( + `${supabaseUrl}/rest/v1/doctor_availability`, + { + method: "POST", + headers, + body: JSON.stringify(body), + } + ) + .then(async (res) => { + const text = await res.text(); + if (!res.ok) { + throw new Error(`Erro ${res.status}: ${text}`); + } + return text ? JSON.parse(text) : {}; + }) + .then(() => { + alert("✅ Agenda criada com sucesso!"); + navigate(`/${role}/agendadoctor`); + }) + .catch((err) => { + console.error("❌ Erro ao criar agenda:", err); + alert("Erro ao criar agenda. Veja o console para mais detalhes."); + }); + }; + + return ( +
+
+
+
+

Adicionar Agenda

+
+
+ +
+
+
+ {/* Médico */} +
+
+
+ + +
+
+ + {/* Dias */} +
+
+ + +
+
+
+ + {/* Horários */} +
+
+ + setStartTime(e.target.value)} + required + /> +
+ +
+ + setEndTime(e.target.value)} + required + /> +
+
+ + {/* Tipo e Status */} +
+ + +
+ +
+ +
+ setActive(true)} + /> + +
+
+ setActive(false)} + /> + +
+
+ +
+ +
+
+
+
+
+
+ ); +} + +export default AgendaForm; diff --git a/src/components/forms/ConsultaForm.jsx b/src/components/forms/ConsultaForm.jsx new file mode 100644 index 0000000..48a157b --- /dev/null +++ b/src/components/forms/ConsultaForm.jsx @@ -0,0 +1,477 @@ +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import Swal from "sweetalert2"; +import "../../assets/css/index.css"; +import { getAccessToken } from "../../utils/auth"; +import { getUserId } from "../../utils/userInfo"; +import { sendSMS } from "../../utils/sendSMS"; +import { getUserRole } from "../../utils/userInfo"; +import emailjs from 'emailjs-com'; + +function ConsultaForm() { + const role = getUserRole(); + const [minDate, setMinDate] = useState(""); + const [pacientes, setPacientes] = useState([]); + const [medicos, setMedicos] = useState([]); + const [horariosDisponiveis, setHorariosDisponiveis] = useState([]); + const [apiResponse, setApiResponse] = useState(null); + const [carregandoHorarios, setCarregandoHorarios] = useState(false); + const tokenUsuario = getAccessToken(); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || "https://yuanqfswhberkoevtmfr.supabase.co"; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + const servicekey = import.meta.env.VITE_SERVICE_KEY + const templatekey = import.meta.env.VITE_TEMPLATE_KEY + const publickey = import.meta.env.VITE_PUBLIC_KEY + + const [formData, setFormData] = useState({ + appointment_type: "presencial", + chief_complaint: "", + doctor_id: "", + duration_minutes: 30, + insurance_provider: "", + patient_id: "", + patient_notes: "", + scheduled_date: "", + scheduled_time: "", + }); + + const navigate = useNavigate(); + + // 🔹 Define a data mínima + useEffect(() => { + const today = new Date(); + const offset = today.getTimezoneOffset(); + today.setMinutes(today.getMinutes() - offset); + setMinDate(today.toISOString().split("T")[0]); + }, []); + + // 🔹 Buscar pacientes + useEffect(() => { + const fetchPacientes = async () => { + try { + const response = await fetch( + `${supabaseUrl}/rest/v1/patients`, + { + headers: { + apikey: supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + }, + } + ); + + if (response.ok) { + const data = await response.json(); + setPacientes(data); + } else { + console.error("Erro ao buscar pacientes"); + } + } catch (error) { + console.error("Erro:", error); + } + }; + + fetchPacientes(); + }, []); + + // 🔹 Buscar médicos + useEffect(() => { + const fetchMedicos = async () => { + try { + const response = await fetch( + `${supabaseUrl}/rest/v1/doctors`, + { + headers: { + apikey: supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + }, + } + ); + + if (response.ok) { + const data = await response.json(); + setMedicos(data); + } else { + console.error("Erro ao buscar médicos"); + } + } catch (error) { + console.error("Erro:", error); + } + }; + + fetchMedicos(); + }, []); + + // 🔹 Buscar horários disponíveis + const fetchHorariosDisponiveis = async (doctorId, date, appointmentType) => { + if (!doctorId || !date) { + setHorariosDisponiveis([]); + setApiResponse(null); + return; + } + + setCarregandoHorarios(true); + + const startDate = `${date}T00:00:00.000Z`; + const endDate = `${date}T23:59:59.999Z`; + + const payload = { + doctor_id: doctorId, + start_date: startDate, + end_date: endDate, + appointment_type: appointmentType || "presencial", + }; + + console.log("🚀 AgendaForm - Payload enviado para get-available-slots:", payload); + console.log("🔑 AgendaForm - Token do usuário:", tokenUsuario ? "EXISTS" : "NULL"); + + try { + const response = await fetch( + `${supabaseUrl}/functions/v1/get-available-slots`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + apikey: supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + }, + body: JSON.stringify(payload), + } + ); + + const data = await response.json(); + setApiResponse(data); + + console.log("🔍 AgendaForm (Admin) - Resposta da Edge Function:", data); + + if (!response.ok) throw new Error(data.error || "Erro ao buscar horários"); + + const slotsDisponiveis = (data?.slots || []).filter((s) => s.available); + + console.log("✅ AgendaForm (Admin) - Slots disponíveis após filtro:", slotsDisponiveis); + console.log("🔍 AgendaForm (Admin) - Todos os slots (antes do filtro):", data?.slots); + console.log("❌ AgendaForm (Admin) - Slots NÃO disponíveis:", (data?.slots || []).filter((s) => !s.available)); + + setHorariosDisponiveis(slotsDisponiveis); + + if (slotsDisponiveis.length === 0) + Swal.fire("Atenção", "Nenhum horário disponível para este dia.", "info"); + } catch (error) { + console.error("Erro ao buscar horários disponíveis:", error); + setHorariosDisponiveis([]); + setApiResponse(null); + Swal.fire("Erro", "Não foi possível obter os horários disponíveis.", "error"); + } finally { + setCarregandoHorarios(false); + } + }; + + // 🔹 Atualiza campos + const handleChange = (e) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + }; + + // 🔹 Atualiza horários quando médico ou data mudam + useEffect(() => { + if (formData.doctor_id && formData.scheduled_date) { + fetchHorariosDisponiveis( + formData.doctor_id, + formData.scheduled_date, + formData.appointment_type + ); + } + }, [formData.doctor_id, formData.scheduled_date, formData.appointment_type]); + + // 🔹 Envia formulário + const handleSubmit = async (e) => { + e.preventDefault(); + + if (!formData.scheduled_date || !formData.scheduled_time) { + Swal.fire("Atenção", "Selecione uma data e horário válidos", "warning"); + return; + } + + const scheduled_at = `${formData.scheduled_date}T${formData.scheduled_time}:00Z`; + + const payload = { + patient_id: formData.patient_id, + doctor_id: formData.doctor_id, + scheduled_at, + duration_minutes: formData.duration_minutes, + appointment_type: formData.appointment_type, + chief_complaint: formData.chief_complaint, + patient_notes: formData.patient_notes, + insurance_provider: formData.insurance_provider, + created_by: getUserId(), + }; + + try { + const response = await fetch( + `${supabaseUrl}/rest/v1/appointments`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + apikey: supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + Prefer: "return=representation", + }, + body: JSON.stringify(payload), + } + ); + + if (response.ok) { + const consultaCriada = await response.json(); + + // 🔹 Busca o telefone do paciente selecionado + const pacienteSelecionado = pacientes.find( + (p) => String(p.id) === String(formData.patient_id) + ); + const email = + pacienteSelecionado?.email || + null; + + if (email){ + try { + await emailjs.send( + `${servicekey}`, + `${templatekey}`, + { + nome_do_paciente: pacienteSelecionado.name || pacienteSelecionado.full_name || `Paciente #${pacienteSelecionado.id}`, + date: formData.scheduled_date, + time: formData.scheduled_time, + doctor_name: medicos.find((m) => String(m.id) === String(formData.doctor_id))?.full_name || + medicos.find((m) => String(m.id) === String(formData.doctor_id))?.full_name || + medicos.find((m) => String(m.id) === String(formData.doctor_id))?.doctor_name || + `Médico #${formData.doctor_id}`, + email: email, + }, + `${publickey}` + ); + console.log("Email de confirmação enviado com sucesso!"); + } catch (error) { + console.error("Erro ao enviar email de confirmação:", error); + } + } + Swal.fire({ + title: "Sucesso!", + text: "Consulta criada com sucesso!", + icon: "success", + confirmButtonText: "OK", + }).then(() => { + navigate(`/${role}/consultalist`); + }); + } else { + const error = await response.json(); + console.error(error); + Swal.fire("Erro", "Não foi possível criar a consulta", "error"); + } + } catch (error) { + console.error(error); + Swal.fire("Erro", "Erro de conexão com o servidor", "error"); + } + }; + + return ( +
+
+
+
+

Nova consulta

+
+

Informações do paciente

+
+
+ +
+
+
+
+ {/* Paciente */} +
+
+ + +
+
+ + {/* Tipo da consulta */} +
+
+ + +
+
+
+ +
+

Informações do atendimento

+ + {/* Médico e Convênio */} +
+
+
+ + +
+
+ +
+
+ + +
+
+
+ + {/* Motivo */} +
+ + +
+ + {/* Data e hora */} +
+
+
+ + +
+
+ +
+
+ + + +
+
+
+ + +
+ + +
+ +
+ +
+
+
+
+
+
+ ); +} + +export default ConsultaForm; \ No newline at end of file diff --git a/src/components/forms/DoctorForm.jsx b/src/components/forms/DoctorForm.jsx new file mode 100644 index 0000000..e92e2c2 --- /dev/null +++ b/src/components/forms/DoctorForm.jsx @@ -0,0 +1,488 @@ +import "../../assets/css/index.css"; +import { withMask } from "use-mask-input"; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import Swal from "sweetalert2"; +import { getAccessToken } from "../../utils/auth.js"; +import { getUserRole } from "../../utils/userInfo.js"; +const role = getUserRole(); + + +function DoctorForm() { + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || "https://yuanqfswhberkoevtmfr.supabase.co"; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + + const role = getUserRole(); + const [doctorData, setDoctorData] = useState({ + full_name: "", + cpf: "", + email: "", + crm: "", + crm_uf: "", + specialty: "", + birth_date: "", + phone_mobile: "", + cep: "", + street: "", + number: "", + complement: "", + neighborhood: "", + city: "", + state: "", + active: false, + }); + + const tokenUsuario = getAccessToken(); + const navigate = useNavigate(); + + const estados = { + AC: "Acre", AL: "Alagoas", AP: "Amapá", AM: "Amazonas", + BA: "Bahia", CE: "Ceará", DF: "Distrito Federal", ES: "Espírito Santo", + GO: "Goiás", MA: "Maranhão", MT: "Mato Grosso", MS: "Mato Grosso do Sul", + MG: "Minas Gerais", PA: "Pará", PB: "Paraíba", PR: "Paraná", + PE: "Pernambuco", PI: "Piauí", RJ: "Rio de Janeiro", RN: "Rio Grande do Norte", + RS: "Rio Grande do Sul", RO: "Rondônia", RR: "Roraima", SC: "Santa Catarina", + SP: "São Paulo", SE: "Sergipe", TO: "Tocantins" + }; + + const buscarCep = () => { + const cep = doctorData.cep.replace(/\D/g, ""); + if (cep.length === 8) { + fetch(`https://brasilapi.com.br/api/cep/v2/${cep}`) + .then((response) => response.json()) + .then((data) => { + setDoctorData((prev) => ({ + ...prev, + city: data.city || '', + street: data.street || '', + neighborhood: data.neighborhood || '', + state: estados[data.state] || data.state + })); + }) + .catch(() => { + Swal.fire({ title: "Erro ao buscar CEP", icon: "error" }); + }); + } + }; + + const handleChange = (e) => { + const { name, value, type, checked } = e.target; + setDoctorData((prev) => ({ + ...prev, + [name]: type === "checkbox" ? checked : value, + })); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + const requiredFields = [ + "full_name", "cpf", "email", "phone_mobile", "crm", "crm_uf", + "specialty", "birth_date", "cep", "street", "number", + "neighborhood", "city", "state" + ]; + + const missingFields = requiredFields.filter( + (field) => !doctorData[field] || doctorData[field].toString().trim() === "" + ); + + if (missingFields.length > 0) { + Swal.fire({ + title: "Campos obrigatórios faltando", + text: "Por favor, preencha todos os campos antes de continuar.", + icon: "warning" + }); + return; + } + + try { + // === 2️⃣ ETAPA 1: CRIAR O USUÁRIO NO AUTH (CHAMANDO A FUNCTION) === + const authHeaders = new Headers(); + authHeaders.append("apikey", supabaseAK); + authHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + authHeaders.append("Content-Type", "application/json"); + const authRaw = JSON.stringify({ + ...doctorData, + password: "12345678", // Usando CRM como senha + role: "medico" + }); + + console.log("📤 Body enviado para Auth:", authRaw); + console.log("🌐 Endpoint:", `${supabaseUrl}/functions/v1/create-user-with-password`); + + const authResponse = await fetch( + `${supabaseUrl}/functions/v1/create-user-with-password`, + { + method: 'POST', + headers: authHeaders, + body: authRaw, + redirect: 'follow' + } + ); + console.log("📥 Status da resposta:", authResponse.status, authResponse.statusText); + if (!authResponse.ok) { + console.log("❌ Resposta não OK de criação de usuário no Auth"); + }else{ + navigate(`/${role}/doctorlist`) + } + } catch (error) { + console.error("❌ Erro no cadastro em duas etapas:", error); + Swal.fire({ + title: "Erro ao cadastrar", + text: error.message, // Exibe a mensagem de erro específica + icon: "error" + }); + } + }; + + return ( +
+
+
+
+
+
+
+ {/* Nome completo */} +
+
+

Dados pessoais

+
+
+
+ + +
+
+ + {/* CPF */} +
+
+ + +
+
+ + {/* Email */} +
+
+ + +
+
+ + {/* Telefone */} +
+
+ + +
+
+ + {/* CRM */} +
+
+ + +
+
+ + {/* CRM UF */} +
+
+ + +
+
+ + {/* Especialidade */} +
+
+ + +
+
+ + {/* Data de nascimento */} +
+
+ + +
+
+ {/*
+
+ +
+ +
+
+ +
+
+ +
+
+
*/} +
+
+

Endereço

+
+ + + {/* CEP */} +
+
+ + +
+
+ + {/* Rua */} +
+
+ + +
+
+ + {/* Número */} +
+
+ + +
+
+ + {/* Complemento */} +
+
+ + +
+
+ + {/* Bairro */} +
+
+ + +
+
+ + {/* Cidade */} +
+
+ + +
+
+ + {/* Estado */} +
+
+ + +
+
+ + {/* Ativo/Inativo */} +
+
+ +
+ setDoctorData((prev) => ({ ...prev, active: true }))} + /> + +
+
+ setDoctorData((prev) => ({ ...prev, active: false }))} + /> + +
+
+
+
+ +
+ +
+
+
+
+
+
+
+ ); +} + +export default DoctorForm; \ No newline at end of file diff --git a/src/components/forms/LaudoForm.jsx b/src/components/forms/LaudoForm.jsx new file mode 100644 index 0000000..0e9d506 --- /dev/null +++ b/src/components/forms/LaudoForm.jsx @@ -0,0 +1,445 @@ + +import { useEditor, EditorContent } from '@tiptap/react' +import StarterKit from '@tiptap/starter-kit' +import Image from '@tiptap/extension-image'; +import { useState, useEffect, useRef } from 'react'; +import { Card, Collapse } from "react-bootstrap"; // <-- IMPORT CORRETO +import { ChevronDown, ChevronUp } from "lucide-react"; +import { getAccessToken } from '../../utils/auth'; +import Select from 'react-select'; +import Swal from 'sweetalert2'; +import { useNavigate } from 'react-router-dom'; +import { FaMicrophone } from "react-icons/fa"; +import { InterimMark } from '../../utils/InterimMark'; // <-- Verifique se esse caminho está certo! +import { getUserRole, getDoctorId } from '../../utils/userInfo'; + + +function Bar({ comandos, handleSubmit, toggleRecording, isRecording }) { + const inputRef = useRef(null); + + const handleAbrirExplorador = () => { + inputRef.current.click(); // abre o explorador + }; + + const handleArquivoSelecionado = (event) => { + const arquivo = event.target.files[0]; + if (arquivo) { + const imageUrl = URL.createObjectURL(arquivo); + comandos.agregarImagen(imageUrl); + event.target.value = null; + } + }; + + return ( + <> +
+
+ + + + {/* */} + + + + + + + <> + + + + + + + {/* */} +
+
+ +
+
+ + ); +}; + +function LaudoForm() { + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || "https://yuanqfswhberkoevtmfr.supabase.co"; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + + const navigate = useNavigate(); + const [paciente, setPaciente] = useState([]); + const tokenUsuario = getAccessToken() + var myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + useEffect(() => { + fetch(`${supabaseUrl}/rest/v1/patients`, requestOptions) + .then(response => response.json()) + .then(result => setPaciente(Array.isArray(result) ? result : [])) + .catch(error => console.log('error', error)); + }, []) + const options = paciente.map(p => ({ + value: p.id, + label: p.full_name + })); + function gerarOrderNumber() { + const prefixo = "REL"; + + const agora = new Date(); + const ano = agora.getFullYear(); + const mes = String(agora.getMonth() + 1).padStart(2, "0"); // adiciona 0 à esquerda se necessário + + // Gerar um código aleatório de 6 caracteres (letras maiúsculas + números) + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + let codigo = ""; + for (let i = 0; i < 6; i++) { + codigo += chars.charAt(Math.floor(Math.random() * chars.length)); + } + + return `${prefixo}-${ano}-${mes}-${codigo}`; + } + + // Exemplo de uso: + const orderNumber = gerarOrderNumber(); + const [laudos, setLaudos] = useState({ + patient_id: "", + order_number: "", + exam: "", + diagnosis: "", + conclusion: "", + cid_code: "", + content_html: "", + status: "draft", + requested_by: getDoctorId(), + }); + const handlePacienteChange = (selected) => { + setLaudos(prev => ({ + ...prev, + patient_id: selected ? selected.value : "" + })); + }; + const handleChange = (e) => { + const { name, value } = e.target; + setLaudos((prev) => ({ + ...prev, + [name]: value + })); + }; + + const handleSubmit = (e) => { + const role = getUserRole(); + e.preventDefault(); + if (!laudos.patient_id || !laudos.diagnosis || !laudos.exam || !laudos.conclusion) { + Swal.fire({ + title: "Por favor, preencha todos os campos obrigatórios.", + icon: "warning", + draggable: true + }); + return; + } + var myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + myHeaders.append("Content-Type", "application/json"); + + var raw = JSON.stringify(laudos); + + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: raw, + redirect: 'follow' + }; + fetch(`${supabaseUrl}/rest/v1/reports`, requestOptions) + .then(response => response.text()) + .then(result => { + console.log(result); + Swal.fire({ + title: "Laudo adicionado!", + icon: "success", + draggable: true + }); + navigate(`/${role}/laudolist`); + }) + .catch(error => console.log('error', error)); + }; + + const [open, setOpen] = useState(false); + const editor = useEditor({ + extensions: [StarterKit, Image, InterimMark ], + content: "", + onUpdate: ({ editor }) => { + setLaudos(prev => ({ + ...prev, + content_html: editor.getHTML() + })); + } + }) + const [isRecording, setIsRecording] = useState(false); + const recognitionRef = useRef(null); + const lastInsertedRef = useRef({ from: -1, to: -1, text: '' }); + + + useEffect(() => { + const SpeechRecognition = + window.SpeechRecognition || window.webkitSpeechRecognition; + + if (!SpeechRecognition) { + alert("Seu navegador não suporta reconhecimento de voz 😢"); + return; + } + + const recognition = new SpeechRecognition(); + recognition.lang = "pt-BR"; + recognition.continuous = false; + recognition.interimResults = true; + + recognition.onresult = (event) => { + if (!editor) return; + + const result = event.results[0]; + const transcript = result[0].transcript; + const last = lastInsertedRef.current; + + // --- CORREÇÃO DE LÓGICA --- + // Vamos rodar a deleção como um comando SEPARADO primeiro. + if (last.from !== -1 && + editor.state.doc.textBetween(last.from, last.to) === last.text) + { + // Roda a deleção e PARA. + editor.chain().focus() + .deleteRange({ from: last.from, to: last.to }) + .run(); + } + + // Pega a posição ATUAL (depois da deleção) + const currentPos = editor.state.selection.from; + + if (result.isFinal) { + // --- RESULTADO FINAL (PRETO) --- + // Roda a inserção final como um comando SEPARADO. + editor.chain().focus() + .insertContent(transcript + ' ') + .run(); + + // Reseta a Ref + lastInsertedRef.current = { from: -1, to: -1, text: '' }; + + } else { + // --- RESULTADO PROVISÓRIO (CINZA) --- + // Esta é a nova estratégia: "Ligar" a mark, inserir, "Desligar" a mark. + // Roda tudo como um comando SEPARADO. + editor.chain() + .focus() + .setMark('interimMark') // <-- "Pincel cinza" LIGADO + .insertContent(transcript) // <-- Insere o texto + .unsetMark('interimMark') // <-- "Pincel cinza" DESLIGADO + .run(); + + // Atualiza a Ref com a posição do texto cinza + lastInsertedRef.current = { + from: currentPos, + to: currentPos + transcript.length, + text: transcript + }; + } + // Não precisamos mais do 'editorChain.run()' aqui embaixo + }; + + recognition.onerror = (err) => { + // ... (código do onerror sem mudanças) + }; + + recognition.onend = () => { + // ... (código do onend sem mudanças) + }; + + recognitionRef.current = recognition; + + return () => { + recognition.stop(); + }; + + }, [editor, isRecording]); + + const toggleRecording = () => { + if (!recognitionRef.current) return; + + if (isRecording) { + // Usuário clicou para PARAR + setIsRecording(false); // <-- Seta o estado + recognitionRef.current.stop(); // <-- Para a API + // O 'onend' será chamado e fará a limpeza/confirmação. + } else { + // Usuário clicou para COMEÇAR + editor?.chain().focus().run(); + setIsRecording(true); // <-- Seta o estado + recognitionRef.current.start(); // <-- Inicia a API + } + }; + + const comandos = { + toggleBold: () => editor.chain().focus().toggleBold().run(), + toggleItalic: () => editor.chain().focus().toggleItalic().run(), + toggleUnderline: () => editor.chain().focus().toggleUnderline().run(), + toggleCodeBlock: () => editor.chain().focus().toggleCodeBlock().run(), + toggleH1: () => editor.chain().focus().toggleHeading({ level: 1 }).run(), + toggleH2: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), + toggleH3: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), + toggleParrafo: () => editor.chain().focus().setParagraph().run(), + toggleListaOrdenada: () => editor.chain().focus().toggleOrderedList().run(), + toggleListaPuntos: () => editor.chain().focus().toggleBulletList().run(), + agregarImagen: (url) => { + if (!url) return; + editor.chain().focus().setImage({ src: url }).run(); + }, + agregarLink: () => { + const url = window.prompt('URL do link') + if (url) { + editor.chain().focus().setLink({ href: url }).run() + } + } + } + + return ( +
+
+

Laudo Médico

+
+ + setOpen(!open)} + aria-controls="paciente-content" + aria-expanded={open} + className="d-flex justify-content-between align-items-center" + style={{ + cursor: "pointer", + borderRadius: "25px", + padding: "12px 20px", + }} + > + Informações do paciente + {open ? : } + + + +
+ + + + + +
+
+
+
+ + +
+
+ ); +} + +export default LaudoForm; +; \ No newline at end of file diff --git a/src/pages/Patient/Patientform.jsx b/src/components/forms/PatientForm.jsx similarity index 51% rename from src/pages/Patient/Patientform.jsx rename to src/components/forms/PatientForm.jsx index eeb8a1f..bb7a554 100644 --- a/src/pages/Patient/Patientform.jsx +++ b/src/components/forms/PatientForm.jsx @@ -1,274 +1,179 @@ -import { useState, useEffect,useRef } from "react"; -import "../../assets/css/index.css" +import { useState, useEffect, useRef } from "react"; +import "../../assets/css/index.css"; import { withMask } from "use-mask-input"; -import supabase from "../../Supabase" +import supabase from "../../Supabase.js"; import { useNavigate } from "react-router-dom"; +import { getAccessToken } from "../../utils/auth.js"; +const AvatarForm = "/img/AvatarForm.jpg"; +const AnexoDocumento = "/img/AnexoDocumento.png"; +import Swal from "sweetalert2"; +import { getUserRole } from "../../utils/userInfo.js"; + function Patientform() { + const role = getUserRole(); + const tokenUsuario = getAccessToken(); const [patientData, setpatientData] = useState({ - nome: "", - nome_social: "", + full_name: "", cpf: "", - rg: "", - outros_documentos: "", - numero_documento: "", - estado_civil: "", - raça: "", - data_nascimento: null, - profissao: "", - nome_pai: "", - profissao_pai: "", - nome_mae: "", - profissao_mae: "", - nome_responsavel: "", - codigo_legado: "", - foto_url: "", - rn: "false", - sexo: "", - celular: "", email: "", - telefone1: "", - telefone2: "", + birth_date: "", + social_name: "", + sex: "", + street: "", + number: "", + neighborhood: "", + city: "", + state: "", cep: "", - estado: "", - logradouro: "", - bairro: "", - numero: "", - complemento: "", - referencia: "", - status: "inativo", - observaçao: "" - - - }) - + ethnicity: "", + father_name: "", + father_profession: "", + legacy_code: "", + marital_status: "", + mother_name: "", + mother_profession: "", + phone1: "", + phone2: "", + phone_mobile: "", + profession: "", + reference: "", + guardian_cpf: "", + guardian_name: "", + complement: "", + }); + const [previewUrl, setPreviewUrl] = useState(AvatarForm); const [fotoFile, setFotoFile] = useState(null); const fileRef = useRef(null); + const navigate = useNavigate(); useEffect(() => { - console.log("Estado atualizado:", patientData); - }, [patientData]); - - // aqui eu fiz uma funçao onde atualiza o estado do paciente, eu poderia ir mudando com o onchange em cada input mas assim ficou melhor - // e como se fosse 'onChange={(e) => setpatientData({ ...patientData, rg: e.target.value })}' - // prev= pega o valor anterior + console.log("Estado atualizado:", patientData); + }, [patientData]); + const handleChange = (e) => { const { name, value } = e.target; setpatientData((prev) => ({ ...prev, - [name]: value + [name]: value, })); }; - // aqui esta sentando os valores nos inputs - const setValuesFromCep = (data) => { - document.getElementById('logradouro').value = data.logradouro || ''; - document.getElementById('bairro').value = data.bairro || ''; - document.getElementById('cidade').value = data.localidade || ''; - document.getElementById('estado').value = data.uf || ''; - } - const buscarCep = (e) => { - const cep = patientData.cep.replace(/\D/g, ''); - console.log(cep); - fetch(`https://viacep.com.br/ws/${cep}/json/`) - .then(response => response.json()) - .then(data => { - console.log(data) - // salvando os valores para depois colocar nos inputs - setValuesFromCep(data) - // estou salvando os valoeres no patientData - setpatientData((prev) => ({ - ...prev, - cidade: data.localidade || '', - logradouro: data.logradouro || '', - bairro: data.bairro || '', - estado: data.estado || '' - })); - }) - } - const navigate = useNavigate(); - // enviando para o supabase + const estados = { + AC: "Acre", AL: "Alagoas", AP: "Amapá", AM: "Amazonas", BA: "Bahia", + CE: "Ceará", DF: "Distrito Federal", ES: "Espírito Santo", GO: "Goiás", + MA: "Maranhão", MT: "Mato Grosso", MS: "Mato Grosso do Sul", + MG: "Minas Gerais", PA: "Pará", PB: "Paraíba", PR: "Paraná", + PE: "Pernambuco", PI: "Piauí", RJ: "Rio de Janeiro", RN: "Rio Grande do Norte", + RS: "Rio Grande do Sul", RO: "Rondônia", RR: "Roraima", SC: "Santa Catarina", + SP: "São Paulo", SE: "Sergipe", TO: "Tocantins" + }; + + const setValuesFromCep = (data) => { + setpatientData((prev) => ({ + ...prev, + city: data.city || '', + street: data.street || '', + neighborhood: data.neighborhood || '', + state: estados[data.state] || data.state || '', + })); + }; + + const buscarCep = (e) => { + const cep = patientData.cep.replace(/\D/g, ""); + if (cep.length !== 8) return; + + fetch(`https://brasilapi.com.br/api/cep/v2/${cep}`) + .then((response) => response.json()) + .then((data) => { + if (data && !data.errors) { + setValuesFromCep(data); + } else { + console.log("CEP não encontrado"); + } + }) + .catch(err => console.error("Erro ao buscar CEP:", err)); + }; + + const handleSubmit = async (e) => { e.preventDefault(); - //const cpfValido = await validarCpf(patientData.cpf); - - /* - // Verifica se já existe paciente com o CPF - const cpfExiste = await verificarCpfExistente(patientData.cpf); - if (cpfExiste) { - alert("Já existe um paciente cadastrado com este CPF!"); - return; - } - */ - - // Calcula idade a partir da data de nascimento - /* - const hoje = new Date(); - const nascimento = new Date(patientData.data_nascimento); - let idade = hoje.getFullYear() - nascimento.getFullYear(); - const m = hoje.getMonth() - nascimento.getMonth(); - if (m < 0 || (m === 0 && hoje.getDate() < nascimento.getDate())) { - idade--; - } - - let cpfRespValido = true; - if (idade < 18) { - cpfRespValido = await validarCpf(patientData.cpf_responsavel); - } - - if (!cpfValido || !cpfRespValido) { - console.log("CPF inválido. Não enviando o formulário."); - // Não envia se algum CPF for inválido - return; - }*/ - - // Campos obrigatórios const requiredFields = [ - "nome", - "cpf", - "data_nascimento", - "sexo", - "celular", - "cep", - "logradouro", - "numero", - "bairro", - "estado", - "status", - "email" + "full_name", "cpf", "birth_date", "sex", "phone_mobile", + "cep", "street", "number", "neighborhood", "state", "email" ]; - const missingFields = requiredFields.filter( - (field) => !patientData[field] || patientData[field].toString().trim() === "" - ); + const missingFields = requiredFields.filter( + (field) => !patientData[field] || patientData[field].toString().trim() === "" + ); - if (missingFields.length > 0) { - alert("Por favor, preencha todos os campos obrigatórios."); - return; - } + if (missingFields.length > 0) { + Swal.fire("Atenção", `Campos obrigatórios faltando: ${missingFields.join(", ")}`, "warning"); + return; + } - const myHeaders = new Headers(); - myHeaders.append("Authorization", "Bearer "); - myHeaders.append("Content-Type", "application/json"); - const raw = JSON.stringify(patientData); + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || "https://yuanqfswhberkoevtmfr.supabase.co"; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; - var requestOptions = { - method: 'POST', - headers: myHeaders, - body: raw, - redirect: 'follow' - }; - - fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes", requestOptions) - .then(response => response.json()) - .then(async (result) => { - setpatientData(result) - console.log(result) - - if (fotoFile && result?.id) { - const formData = new FormData(); - formData.append("foto", fotoFile); - - try { - const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${result.id}/foto`, { - method: "POST", - headers: { - "Authorization": "Bearer " - }, - body: formData - }); - const uploadResult = await res.json(); - console.log("Foto enviada com sucesso:", uploadResult); - } catch (error) { - console.error("Erro no upload da foto:", error); - } - } - - if (patientData.documentoFile && result?.id) { - const formData = new FormData(); - formData.append("anexo", patientData.documentoFile); - - try { - const resAnexo = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${result.id}/anexos`, { - method: "POST", - headers: { - "Authorization": "Bearer " - }, - body: formData - }); - const novoAnexo = await resAnexo.json(); - console.log("Anexo enviado com sucesso:", novoAnexo); - - - setpatientData((prev) => ({ - ...prev, - anexos: [...(prev.anexos || []), novoAnexo] - })); - } catch (error) { - console.error("Erro no upload do anexo:", error); - } - } - - alert("paciente cadastrado") - navigate("/patientlist") - }) - .catch(error => console.log('error', error)); - /*console.log(patientData); - const{data, error} = await supabase - .from("Patient") - .insert([patientData]) - .select() - if(error){ - console.log("Erro ao inserir paciente:", error); - }else{ - console.log("Paciente inserido com sucesso:", data); - }*/ - }; - - const validarCpf = async (cpf) => { - const cpfLimpo = cpf.replace(/\D/g, ""); try { - const response = await fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes/validar-cpf", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ cpf: cpfLimpo }) + const headers = new Headers(); + headers.append("apikey", + supabaseAK + ); + headers.append("Authorization", `Bearer ${tokenUsuario}`); + headers.append("Content-Type", "application/json"); + + const raw = JSON.stringify({ + ...patientData, + password: "12345678", // senha padrão + role: "paciente", // role padrão + create_patient_record: true }); - const data = await response.json(); - if (data.valido === false) { - alert("CPF inválido!"); - return false; - } else if (data.valido === true) { - return true; - } else { - return false; + + console.log("📤 Enviando dados:", raw); + + const response = await fetch( + `${supabaseUrl}/functions/v1/create-user-with-password`, + { + method: "POST", + headers, + body: raw, + redirect: "follow" + } + ); + + console.log("📥 Status:", response.status, response.statusText); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(errorText || "Erro ao cadastrar paciente"); } + + Swal.fire({ + title: "Paciente cadastrado com sucesso!", + icon: "success", + }); + + navigate(`/${role}/patientlist`); + } catch (error) { - alert("Erro ao validar CPF."); - return false; + console.error("❌ Erro no cadastro:", error); + Swal.fire({ + title: "Erro ao cadastrar", + text: error.message, + icon: "error", + }); } }; - - const verificarCpfExistente = async (cpf) => { - const cpfLimpo = cpf.replace(/\D/g, ""); - try { - const response = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes?cpf=${cpfLimpo}`); - const data = await response.json(); - // Ajuste conforme o formato de resposta da sua API - if (data && data.data && data.data.length > 0) { - return true; // Já existe paciente com esse CPF - } - return false; - } catch (error) { - console.log("Erro ao verificar CPF existente."); - return false; + const handleFileChange = (e) => { + const file = e.target.files[0]; + setFotoFile(file); + if (file) { + setPreviewUrl(URL.createObjectURL(file)); } }; - return (
@@ -289,50 +194,44 @@ function Patientform() {
- +
- setFotoFile(e.target.files[0])} /> + onChange={handleFileChange} + />
{ - // Remove no frontend - setpatientData(prev => ({ ...prev, foto_url: "" })); - setFotoFile(null); // Limpa no preview + setpatientData((prev) => ({ + ...prev, + foto_url: "", + })); + setFotoFile(null); + setPreviewUrl(AvatarForm); if (fileRef.current) fileRef.current.value = null; - - // Remove no backend e mostra resposta no console - if (patientData.id) { - try { - const response = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientData.id}/foto`, { - method: "DELETE", - }); - const data = await response.json(); - console.log("Resposta da API ao remover foto:", data); - if (response.ok || response.status === 200) { - console.log("Foto removida com sucesso na API."); - } - } catch (error) { - console.log("Erro ao remover foto:", error); - } - } else { - console.log("Ainda não existe paciente cadastrado para remover foto na API."); - } }} - > + > Limpar -
+
@@ -342,27 +241,34 @@ function Patientform() { -
+
-
+
- - @@ -370,205 +276,280 @@ function Patientform() {
+
+
-
+
-
+
-
+
-
+
- -
+
-
+
- * - CPF* +
+
- +
+
-
+
- * - Data de Nascimento* +
+
- +
+
-
+
-
+
-
+
- +
+ + {/* Contato */}

Contato

+
- * - + +
-
+
- * - Email* +
-
+ {/* Endereço */}

Endereço

@@ -591,8 +576,11 @@ function Patientform() {
- * - CEP* +
- * - Cidade* +
- - Rua* +
-
+
-
-
- - Bairro* +
- - Referência +
+

- +
- -
+
-
-
- -
-
-
-
- { - const file = e.target.files[0]; - if (file) { - setpatientData((prev) => ({ ...prev, documentoFile: file })); - } - }} - /> -
-
-
- {patientData.anexos?.length > 0 && ( - - )} -
-
- - {/* Lista anexos */} - {patientData.anexos?.length > 0 && - patientData.anexos.map((anexo) => ( - - ))}
-
- -
- - - +
@@ -788,7 +732,9 @@ function Patientform() {
- ); -}; -export default Patientform; \ No newline at end of file + ); +} + +export default Patientform; + diff --git a/src/components/layouts/Navbar.jsx b/src/components/layouts/Navbar.jsx new file mode 100644 index 0000000..752af05 --- /dev/null +++ b/src/components/layouts/Navbar.jsx @@ -0,0 +1,465 @@ +import { useEffect, useRef, useState } from "react"; +import { useNavigate, useLocation, Link } from "react-router-dom"; +import "../../assets/css/index.css"; +import { logoutUser } from "../../Supabase"; +import Swal from "sweetalert2"; +import { getUserRole, clearUserInfo, getUserId } from "../../utils/userInfo"; +import { getAccessToken } from "../../utils/auth"; + +const AvatarForm = "/img/AvatarForm.jpg"; + +const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || "https://yuanqfswhberkoevtmfr.supabase.co"; +const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + +var myHeaders = new Headers(); +const tokenUsuario = getAccessToken(); + +const LS_KEYS = { + dark: "pref_dark_mode", + daltonism: "pref_daltonism", + font: "pref_font_scale", +}; + +myHeaders.append("apikey", supabaseAK); +myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + +function Navbar({ onMenuClick }) { + const location = useLocation(); + const navigate = useNavigate(); + + // Estados + const [openNotif, setOpenNotif] = useState(false); + const [openProfile, setOpenProfile] = useState(false); + const [previewUrl, setPreviewUrl] = useState(AvatarForm); + const [darkMode, setDarkMode] = useState(false); + + // Estado para dados do utilizador + const [userData, setUserData] = useState({ + email: "Carregando...", + role: "", + lastSignIn: "" + }); + + const notifRef = useRef(null); + const profileRef = useRef(null); + const fileRef = useRef(null); + + const isDoctor = location.pathname.startsWith("/doctor"); + const userId = getUserId(); + const extensions = ["png", "jpg", "jpeg", "gif"]; + + // --- EFEITOS --- + + // 1. Dark Mode + useEffect(() => { + const saved = localStorage.getItem(LS_KEYS.dark) === "true"; + setDarkMode(saved); + document.body.classList.toggle("dark-mode", saved); + }, []); + + // 2. Fechar ao clicar fora + useEffect(() => { + function handleClickOutside(e) { + if (notifRef.current && !notifRef.current.contains(e.target)) setOpenNotif(false); + if (profileRef.current && !profileRef.current.contains(e.target)) setOpenProfile(false); + } + document.addEventListener("click", handleClickOutside); + return () => document.removeEventListener("click", handleClickOutside); + }, []); + + // 3. Carregar Avatar + useEffect(() => { + const loadAvatar = async () => { + if (!userId) return; + + var requestOptions = { + headers: myHeaders, + method: 'GET', + redirect: 'follow' + }; + + const possibleNames = ['avatar', 'secretario', 'profile', 'user']; + for (const name of possibleNames) { + for (const ext of extensions) { + try { + const response = await fetch(`${supabaseUrl}/storage/v1/object/avatars/${userId}/${name}.${ext}`, requestOptions); + if (response.ok) { + const blob = await response.blob(); + const imageUrl = URL.createObjectURL(blob); + setPreviewUrl(imageUrl); + return; + } + } catch (error) { } + } + } + }; + loadAvatar(); + }, [userId]); + + // 4. Determinar Nome Amigável da Role (Função auxiliar restaurada) + const getFriendlyRole = () => { + const role = getUserRole(); // Tenta pegar do localStorage/utils primeiro + + if (role) { + switch (role) { + case "medico": return "Médico"; + case "paciente": return "Paciente"; + case "admin": return "Administrador"; + case "secretaria": return "Secretária"; + default: return role; + } + } + + // Fallback baseado na URL se não tiver role salva + if (location.pathname.startsWith("/doctor")) return "Médico"; + if (location.pathname.startsWith("/patientapp")) return "Paciente"; + if (location.pathname.startsWith("/admin")) return "Administrador"; + if (location.pathname.startsWith("/secretaria")) return "Secretária"; + + return "Usuário"; + }; + + // 5. Buscar dados do utilizador e combinar com a Role Amigável + useEffect(() => { + const fetchUserDetails = async () => { + // Define a role inicial baseada na lógica local (mais confiável para exibição) + const friendlyRole = getFriendlyRole(); + + if (!userId) { + setUserData(prev => ({ ...prev, role: friendlyRole })); + return; + } + + try { + const response = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/auth/v1/user", { + method: 'GET', + headers: { + 'apikey': supabaseAK, + 'Authorization': `Bearer ${getAccessToken()}` + } + }); + + if (response.ok) { + const data = await response.json(); + + const date = data.last_sign_in_at + ? new Date(data.last_sign_in_at).toLocaleString('pt-PT', { + day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' + }) + : "Primeiro acesso"; + + // Se a API retornar metadados com role, usamos, senão usamos a friendlyRole calculada + const apiRole = data.user_metadata?.role || friendlyRole; + + setUserData({ + email: data.email, + role: apiRole, // Aqui agora usamos a role tratada + lastSignIn: date + }); + } else { + // Se a API falhar, garante que a role local seja mostrada + setUserData(prev => ({ ...prev, role: friendlyRole })); + } + } catch (error) { + console.error("Erro ao buscar detalhes do utilizador", error); + setUserData(prev => ({ ...prev, role: friendlyRole })); + } + }; + + fetchUserDetails(); + }, [userId, location.pathname]); // Atualiza se mudar de rota também + + // --- FUNÇÕES --- + + const toggleDarkMode = () => { + const next = !darkMode; + setDarkMode(next); + localStorage.setItem(LS_KEYS.dark, String(next)); + document.body.classList.toggle("dark-mode", next); + }; + + const handleLogout = async () => { + Swal.fire({ + title: "Tem a certeza que deseja sair?", + text: "Precisará de fazer login novamente para aceder ao sistema.", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#e63946", + cancelButtonColor: "#6c757d", + confirmButtonText: "Sair", + cancelButtonText: "Cancelar", + }).then(async (result) => { + if (result.isConfirmed) { + const success = await logoutUser(); + if (success) { + clearUserInfo(); + navigate("/login"); + } + } + }); + }; + + const handleAvatarUpload = () => { + setOpenProfile(false); + Swal.fire({ + title: 'Alterar Foto de Perfil', + html: ` +
+
+ +
+ +
`, + showCancelButton: true, + confirmButtonText: 'Salvar', + preConfirm: () => { + const fileInput = document.getElementById('avatar-input'); + const file = fileInput.files[0]; + if (!file) { Swal.showValidationMessage('Selecione uma imagem'); return false; } + return file; + }, + didOpen: () => { + const fileInput = document.getElementById('avatar-input'); + const previewImg = document.getElementById('preview-avatar'); + fileInput.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => previewImg.src = e.target.result; + reader.readAsDataURL(file); + } + }); + } + }).then((result) => { + if (result.isConfirmed && result.value) { + uploadToSupabase(result.value); + } + }); + }; + + const uploadToSupabase = async (file) => { + console.log("Upload simulado:", file.name); + }; + + const handleAccessibilitySettings = () => { + setOpenProfile(false); + + let daltonismMode = localStorage.getItem(LS_KEYS.daltonism) === "true"; + let fontScale = parseInt(localStorage.getItem(LS_KEYS.font) || "100", 10); + let leituraAtiva = false; + + const applyFontScale = (next) => { + const clamped = Math.max(80, Math.min(180, next)); + fontScale = clamped; + localStorage.setItem(LS_KEYS.font, String(clamped)); + document.documentElement.style.fontSize = `${clamped}%`; + const fontSpan = document.getElementById('modal-font-size'); + if (fontSpan) fontSpan.textContent = `${clamped}%`; + }; + + const toggleDaltonismMode = () => { + daltonismMode = !daltonismMode; + localStorage.setItem(LS_KEYS.daltonism, String(daltonismMode)); + document.body.classList.toggle("daltonism-mode", daltonismMode); + }; + + let selectionChangeListener = null; + const lerTextoSelecionado = () => { + const texto = window.getSelection().toString().trim(); + if (!texto) return; + window.speechSynthesis.cancel(); + const fala = new SpeechSynthesisUtterance(texto); + fala.lang = "pt-PT"; + fala.rate = 1; + window.speechSynthesis.speak(fala); + }; + + const toggleLeituraAtiva = () => { + leituraAtiva = !leituraAtiva; + const btn = document.getElementById('modal-toggle-leitura'); + if (btn) { + btn.textContent = leituraAtiva ? "🟢 Leitura automática ativada" : "🔊 Ativar leitura automática"; + btn.classList.toggle("active", leituraAtiva); + } + if (selectionChangeListener) { + document.removeEventListener("selectionchange", selectionChangeListener); + selectionChangeListener = null; + } + if (leituraAtiva) { + selectionChangeListener = () => { + const texto = window.getSelection().toString().trim(); + if (texto.length > 1) lerTextoSelecionado(); + }; + document.addEventListener("selectionchange", selectionChangeListener); + } else { + window.speechSynthesis.cancel(); + } + }; + + Swal.fire({ + title: 'Configurações de Acessibilidade', + html: ` + +
+
+ + Modo daltônico +
+
+
+ Tamanho da fonte: ${fontScale}% +
+ + + +
+
+
+
+ +
+
+ `, + showConfirmButton: false, + showCancelButton: true, + cancelButtonText: 'Fechar', + cancelButtonColor: '#6c757d', + width: '450px', + didOpen: () => { + const popup = Swal.getPopup(); + if (!popup) return; + const daltonismToggle = popup.querySelector('#daltonism-toggle'); + const decFontBtn = popup.querySelector('#dec-font'); + const resetFontBtn = popup.querySelector('#reset-font'); + const incFontBtn = popup.querySelector('#inc-font'); + const toggleLeituraBtn = popup.querySelector('#modal-toggle-leitura'); + + if (daltonismToggle) daltonismToggle.addEventListener('change', toggleDaltonismMode); + if (decFontBtn) decFontBtn.addEventListener('click', () => applyFontScale(fontScale - 10)); + if (resetFontBtn) resetFontBtn.addEventListener('click', () => applyFontScale(100)); + if (incFontBtn) incFontBtn.addEventListener('click', () => applyFontScale(fontScale + 10)); + if (toggleLeituraBtn) toggleLeituraBtn.addEventListener('click', toggleLeituraAtiva); + }, + willClose: () => { + if (selectionChangeListener) document.removeEventListener("selectionchange", selectionChangeListener); + window.speechSynthesis.cancel(); + } + }); + }; + + return ( +
+
+ + logo{" "} + MediConnect + +
+ + { e.preventDefault(); onMenuClick(); }}> + + + +
    +
  • + +
  • + +
  • +
    setOpenProfile(!openProfile)} style={{ cursor: "pointer" }}> + +
    + +
    + + {/* --- CORREÇÃO MODO ESCURO --- + Adicionei estilos condicionais baseados no estado `darkMode`. + Fundo: #f8f9fa (claro) / #2c2c2c (escuro) + Texto: #333 (claro) / #e0e0e0 (escuro) + Bordas: #eaeaea (claro) / #444 (escuro) + */} +
    +
    + {/* --- CORREÇÃO ROLE DO USUÁRIO --- */} + {userData.role} +
    +
    + {userData.email} +
    +
    + Login: {userData.lastSignIn} +
    +
    + + + + + +
    + + +
    +
  • +
+
+ ); +} + +export default Navbar; \ No newline at end of file diff --git a/src/components/layouts/Sidebar.jsx b/src/components/layouts/Sidebar.jsx new file mode 100644 index 0000000..826ff0a --- /dev/null +++ b/src/components/layouts/Sidebar.jsx @@ -0,0 +1,120 @@ +import { getUserRole } from "../../utils/userInfo"; +import { Link, useLocation } from "react-router-dom"; +import { useState } from "react"; +import AccessibilityWidget from "../AccessibilityWidget.jsx"; +import Chatbox from "../chat/Chatbox.jsx"; +import Navbar from "./Navbar.jsx"; +function Sidebar() { + const [isSidebarOpen, setSidebarOpen] = useState(false); + const location = useLocation(); + const role = getUserRole(); + + // 2. Adicione a função para alternar o estado + const toggleSidebar = () => { + setSidebarOpen(!isSidebarOpen); + }; + + // 3. Crie a string de classe que será aplicada dinamicamente + const mainWrapperClass = isSidebarOpen ? 'main-wrapper sidebar-open' : 'main-wrapper'; + + // Função para verificar se a rota está ativa + const isActive = (path) => { + const currentPath = location.pathname; + + // Verificação exata primeiro + if (currentPath === path) return true; + + // Verificação de subrotas (ex: /admin/doctorlist/edit/123) + if (currentPath.startsWith(path + '/')) return true; + + // Verificações específicas para páginas de edição/criação + if (path === `/${role}/doctorlist` && ( + currentPath.includes(`/${role}/editdoctor/`) || + currentPath.includes(`/${role}/doctorform`) + )) return true; + + if (path === `/${role}/patientlist` && ( + currentPath.includes(`/${role}/editpatient/`) || + currentPath.includes(`/${role}/patientform`) + )) return true; + + if (path === `/${role}/consultalist` && ( + currentPath.includes(`/${role}/consultaform`) || + currentPath.includes(`/${role}/editconsulta/`) + )) return true; + + if (path === `/${role}/createuser` && ( + currentPath.includes(`/${role}/createuser/`) + )) return true; + if (path === `/${role}/doctor-exceptions` && ( + currentPath.includes(`/${role}/doctor-exceptions/`) + )) return true; + if (path === `/${role}/agendadoctor` && ( + currentPath.includes(`/${role}/editdoctorschedule/`) || + currentPath.includes(`/${role}/agendaform`) + )) return true; + if (path === `/${role}/laudolist` && ( + currentPath.includes(`/${role}/laudoedit/`) || + currentPath.includes(`/${role}/laudo`) || + currentPath.includes(`/${role}/laudolist/`) + )) return true; + + return false; + }; + const permissoes = { + admin: ['dashboard', 'consultalist', 'laudolist', 'patientlist', 'doctorlist', 'agendadoctor', 'createuser', 'excecao'], + medico: ['consultalist', 'dashboard', 'patientlist', 'prontuariolist', 'laudolist', 'excecao', 'agendadoctor', 'doctorcalendar'], + secretaria: ['dashboard', 'agendadoctor', 'consultalist', 'patientlist', 'doctorlist',], + paciente: ['dashboard', 'medicosdisponiveis', 'consultalist', 'laudolist', 'agendarconsulta'], + + }; + function temPermissao(role, acao) { + return permissoes[role]?.includes(acao); + } + const menuItems = [ + { key: 'dashboard', label: 'Dashboard', icon: 'fa-bar-chart-o', path: 'dashboard' }, + { key: 'doctorlist', label: 'Médicos', icon: 'fa-user-md', path: 'doctorlist' }, + { key: 'patientlist', label: 'Pacientes', icon: 'fa-wheelchair', path: 'patientlist' }, + { key: 'calendar', label: 'Calendario', icon: 'fa-calendar', path: 'calendar' }, + { key: 'agendadoctor', label: 'Agenda Médica', icon: 'fa-clock-o', path: 'agendadoctor' }, + { key: 'consultalist', label: 'Consultas', icon: 'fa-stethoscope', path: 'consultalist' }, + { key: 'laudolist', label: 'Laudos', icon: 'fa-file-text-o', path: 'laudolist' }, + { key: 'createuser', label: 'Usuários', icon: 'fa-users', path: 'createuser' }, + { key: 'excecao', label: 'Exceções do Médico', icon: 'fa-calendar-times-o', path: 'excecao' }, + { key: 'medicosdisponiveis', label: 'Agendar Consultas', icon: 'fa fa-calendar-plus-o', path: 'medicosdisponiveis' }, + { key: 'doctorcalendar', label: 'Calendário', icon: 'fa fa-calendar', path: 'doctorcalendar' }, + ]; + return ( +
+
+ + +
+
+ + ); +} +export default Sidebar; \ No newline at end of file diff --git a/src/components/lists/AgendaDoctor.jsx b/src/components/lists/AgendaDoctor.jsx new file mode 100644 index 0000000..383ca72 --- /dev/null +++ b/src/components/lists/AgendaDoctor.jsx @@ -0,0 +1,665 @@ +import "../../assets/css/index.css" +import { Link } from "react-router-dom"; +import { useState, useEffect, useRef, useLayoutEffect } from "react"; +import { createPortal } from "react-dom"; +import { getAccessToken } from "../../utils/auth.js"; +import { getUserRole } from "../../utils/userInfo.js"; +import { getDoctorId } from "../../utils/userInfo.js"; + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + +// Componente para o dropdown portal +function DropdownPortal({ anchorEl, isOpen, onClose, className, children }) { + const menuRef = useRef(null); + const [stylePos, setStylePos] = useState({ + position: "absolute", + top: 0, + left: 0, + visibility: "hidden", + zIndex: 1000, + }); + + useLayoutEffect(() => { + if (!isOpen || !anchorEl || !menuRef.current) return; + + const anchorRect = anchorEl.getBoundingClientRect(); + const menuRect = menuRef.current.getBoundingClientRect(); + const scrollY = window.scrollY || window.pageYOffset; + const scrollX = window.scrollX || window.pageXOffset; + + let left = anchorRect.right + scrollX - menuRect.width; + let top = anchorRect.bottom + scrollY; + + if (left < 0) left = scrollX + 4; + if (top + menuRect.height > window.innerHeight + scrollY) { + top = anchorRect.top + scrollY - menuRect.height; + } + + setStylePos({ + position: "absolute", + top: `${Math.round(top)}px`, + left: `${Math.round(left)}px`, + visibility: "visible", + zIndex: 1000, + }); + }, [isOpen, anchorEl, children]); + + useEffect(() => { + if (!isOpen) return; + + function handleDocClick(e) { + if ( + menuRef.current && + !menuRef.current.contains(e.target) && + anchorEl && + !anchorEl.contains(e.target) + ) { + onClose(); + } + } + + function handleScroll() { + onClose(); + } + + document.addEventListener("mousedown", handleDocClick); + document.addEventListener("scroll", handleScroll, true); + + return () => { + document.removeEventListener("mousedown", handleDocClick); + document.removeEventListener("scroll", handleScroll, true); + }; + }, [isOpen, onClose, anchorEl]); + + if (!isOpen) return null; + + return createPortal( +
e.stopPropagation()} + > + {children} +
, + document.body + ); +} + +function AgendaDoctor() { + const [agenda, setAgenda] = useState([]); + const [medicos, setMedicos] = useState([]); + const [openDropdown, setOpenDropdown] = useState(null); + const [search, setSearch] = useState(""); + const [dayFilter, setDayFilter] = useState(""); + const [typeFilter, setTypeFilter] = useState(""); + const [deleteId, setDeleteId] = useState(null); + const [editId, setEditId] = useState(null); + const [editData, setEditData] = useState({ + doctor_id: "", + weekday: "", + start_time: "", + end_time: "", + slot_minutes: 30, + appointment_type: "", + active: true, + }); + const anchorRefs = useRef({}); + const role = getUserRole(); + const tokenUsuario = getAccessToken(); + + const requestOptions = { + method: "GET", + headers: { + apikey: + supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + }, + redirect: "follow", + }; + + // Fetch agenda + useEffect(() => { + if (getUserRole() === 'medico') { + fetch( + `${supabaseUrl}/rest/v1/doctor_availability?doctor_id=eq.${getDoctorId()}`, + requestOptions + ) + .then((res) => res.json()) + .then((result) => setAgenda(Array.isArray(result) ? result : [])) + .catch((err) => console.log(err)); + } else { + fetch( + `${supabaseUrl}/rest/v1/doctor_availability`, + requestOptions + ) + .then((res) => res.json()) + .then((result) => setAgenda(Array.isArray(result) ? result : [])) + .catch((err) => console.log(err)); + } + }, []); + + // Fetch médicos + useEffect(() => { + fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors", requestOptions) + .then((res) => res.json()) + .then((result) => setMedicos(Array.isArray(result) ? result : [])) + .catch((err) => console.log(err)); + }, []); + + const getDoctorName = (id) => { + if (!id) return ""; + const medico = medicos.find((m) => m.id === id); + return medico ? medico.full_name || medico.name || "" : id; + }; + + // DELETE + const handleDelete = (id) => setDeleteId(id); + + const confirmDelete = () => { + if (!deleteId) return; + + fetch( + `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctor_availability?id=eq.${deleteId}`, + { + method: "DELETE", + headers: { + apikey: + supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + }, + } + ) + .then((res) => { + if (!res.ok) throw new Error("Erro ao deletar a agenda"); + setAgenda((prev) => prev.filter((a) => a.id !== deleteId)); + setDeleteId(null); + }) + .catch((err) => console.log(err)); + }; + + // EDIT + const handleEditClick = (id) => { + const agendaItem = agenda.find((a) => a.id === id); + if (!agendaItem) return; + + setEditData({ + doctor_id: agendaItem.doctor_id || "", + weekday: agendaItem.weekday || "", + start_time: agendaItem.start_time || "", + end_time: agendaItem.end_time || "", + slot_minutes: agendaItem.slot_minutes || 30, + appointment_type: agendaItem.appointment_type || "", + active: agendaItem.active ?? true, + }); + setEditId(id); + setOpenDropdown(null); + }; + + const handleEditChange = (e) => { + const { name, value, type, checked } = e.target; + setEditData((prev) => ({ + ...prev, + [name]: type === "checkbox" ? checked : value, + })); + }; + + const submitEdit = () => { + if (!editId) return; + + if (!editData.doctor_id) { + alert("Selecione um médico válido."); + return; + } + if (!editData.weekday || !editData.start_time || !editData.end_time || !editData.appointment_type) { + alert("Preencha todos os campos obrigatórios."); + return; + } + + fetch( + `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctor_availability?id=eq.${editId}`, + { + method: "PATCH", + headers: { + apikey: + supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + "Content-Type": "application/json", + "Prefer": "return=representation", // ESSENCIAL + }, + body: JSON.stringify(editData), + } + ) + .then((res) => { + if (!res.ok) throw new Error("Erro ao salvar alterações"); + return res.json(); + }) + .then((updated) => { + setAgenda((prev) => + prev.map((a) => (a.id === editId ? { ...a, ...updated[0] } : a)) + ); + setEditId(null); + }) + .catch((err) => { + console.error(err); + alert("Erro ao salvar alterações. Verifique os campos e tente novamente."); + }); + }; + + const filteredAgenda = agenda.filter((a) => { + if (!a) return false; + const q = search.toLowerCase(); + + // Filtro por texto (nome do médico, dia, tipo) + const matchesText = ( + (getDoctorName(a.doctor_id) || "").toLowerCase().includes(q) || + (a.weekday || "").toLowerCase().includes(q) || + (a.appointment_type || "").toLowerCase().includes(q) + ); + + // Filtro por dia da semana + const matchesDay = !dayFilter || a.weekday === dayFilter; + + // Filtro por tipo de consulta + const matchesType = !typeFilter || a.appointment_type === typeFilter; + + return matchesText && matchesDay && matchesType; + }); + + // Paginação + const [itemsPerPage1, setItemsPerPage1] = useState(15); + const [currentPage1, setCurrentPage1] = useState(1); + const indexOfLastAgenda = currentPage1 * itemsPerPage1; + const indexOfFirstAgenda = indexOfLastAgenda - itemsPerPage1; + const currentAgenda = filteredAgenda.slice(indexOfFirstAgenda, indexOfLastAgenda); + const totalPages1 = Math.ceil(filteredAgenda.length / itemsPerPage1); + + // Reset da paginação quando filtros mudam + useEffect(() => { + setCurrentPage1(1); + }, [search, dayFilter, typeFilter]); + const permissoes = { + admin: ['nome'], + secretaria: ['nome'], + medico: [''] + }; + const pode = (acao) => permissoes[role]?.includes(acao); + return ( +
+
+ {/* Header com título e botão */} +
+

Agenda Médica

+ + Adicionar agenda + +
+ + {/* Todos os filtros em uma única linha */} +
+ {/* Campo de busca */} + setSearch(e.target.value)} + style={{ minWidth: "300px", maxWidth: "450px", }} + /> + + {/* Filtro por dia da semana */} + + + {/* Filtro por tipo de consulta */} + +
+ + {/* Tabela */} +
+ + + + {pode('nome') && } + + + + + + + + + + {currentAgenda.length > 0 ? ( + currentAgenda.map((a) => ( + + {pode('nome') && } + + + + + + + + )) + ) : ( + + + + )} + +
NomeDias disponíveisHorário disponívelDuração (min)TipoStatusAção
{getDoctorName(a.doctor_id)} + + + {a.weekday === 'monday' ? 'Segunda' : + a.weekday === 'tuesday' ? 'Terça' : + a.weekday === 'wednesday' ? 'Quarta' : + a.weekday === 'thursday' ? 'Quinta' : + a.weekday === 'friday' ? 'Sexta' : + a.weekday === 'saturday' ? 'Sábado' : + a.weekday === 'sunday' ? 'Domingo' : + a.weekday} + + + {a.start_time || ""} ás {a.end_time || ""} + {a.slot_minutes || 30} + + {a.appointment_type === 'presencial' ? ( + <> + + Presencial + + ) : a.appointment_type === 'telemedicina' ? ( + <> + + Telemedicina + + ) : ( + a.appointment_type + )} + + + + {a.active ? ( + <> + + Ativo + + ) : ( + <> + + Inativo + + )} + + +
+ + +
+
+ Nenhuma agenda encontrada +
+
+ + {/* Paginação */} +
+
+ Total encontrados: {filteredAgenda.length} +
+
+ +
+
+
+ +
+ + {/* Modal de Delete */} + {deleteId && ( +
+
+
+
+ +

Tem certeza que deseja deletar esta agenda?

+
+ + +
+
+
+
+
+ )} + + {/* Modal de Edit */} + {editId && ( +
+
+
+
+
Editar Disponibilidade
+ +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+
+
+
+ )} +
+
+ ); + } +export default AgendaDoctor; diff --git a/src/components/lists/ConsultaList.jsx b/src/components/lists/ConsultaList.jsx new file mode 100644 index 0000000..ce351c9 --- /dev/null +++ b/src/components/lists/ConsultaList.jsx @@ -0,0 +1,813 @@ +import "../../assets/css/index.css" +import { Link } from "react-router-dom"; +import { useState, useEffect, useRef, useLayoutEffect } from "react"; +import { createPortal } from "react-dom"; +import { getAccessToken} from "../../utils/auth.js"; +import Swal from "sweetalert2"; +import { useResponsive } from '../../utils/useResponsive.js'; +import { useNavigate } from "react-router-dom"; +import { getUserRole } from "../../utils/userInfo.js"; + + +function DropdownPortal({ anchorEl, isOpen, onClose, className, children }) { + const menuRef = useRef(null); + const [stylePos, setStylePos] = useState({ + position: "absolute", + top: 0, + left: 0, + visibility: "hidden", + zIndex: 1000, + }); + + // Posiciona o menu após renderar (medir tamanho do menu) + useLayoutEffect(() => { + if (!isOpen) return; + if (!anchorEl || !menuRef.current) return; + + const anchorRect = anchorEl.getBoundingClientRect(); + const menuRect = menuRef.current.getBoundingClientRect(); + const scrollY = window.scrollY || window.pageYOffset; + const scrollX = window.scrollX || window.pageXOffset; + + // tenta alinhar à direita do botão (como dropdown-menu-right) + let left = anchorRect.right + scrollX - menuRect.width; + let top = anchorRect.bottom + scrollY; + + // evita sair da esquerda da tela + if (left < 0) left = scrollX + 4; + // se extrapolar bottom, abre para cima + if (top + menuRect.height > window.innerHeight + scrollY) { + top = anchorRect.top + scrollY - menuRect.height; + } + setStylePos({ + position: "absolute", + top: `${Math.round(top)}px`, + left: `${Math.round(left)}px`, + visibility: "visible", + zIndex: 1000, + }); + }, [isOpen, anchorEl, children]); + + // fecha ao clicar fora / ao rolar + useEffect(() => { + if (!isOpen) return; + function handleDocClick(e) { + const menu = menuRef.current; + if (menu && !menu.contains(e.target) && anchorEl && !anchorEl.contains(e.target)) { + onClose(); + } + } + function handleScroll() { + onClose(); + } + document.addEventListener("mousedown", handleDocClick); + // captura scroll em qualquer elemento (true) + document.addEventListener("scroll", handleScroll, true); + return () => { + document.removeEventListener("mousedown", handleDocClick); + document.removeEventListener("scroll", handleScroll, true); + }; + }, [isOpen, onClose, anchorEl]); + + if (!isOpen) return null; + return createPortal( +
e.stopPropagation()} + > + {children} +
, + document.body + ); +} + +function ConsultaList() { + const [openDropdown, setOpenDropdown] = useState(null); + const anchorRefs = useRef({}); + const [consulta, setConsultas] = useState([]); + const [search, setSearch] = useState(""); + const [statusFilter, setStatusFilter] = useState(""); + const [typeFilter, setTypeFilter] = useState(""); + const tokenUsuario = getAccessToken() + const [pacientesMap, setPacientesMap] = useState({}); + const [medicosMap, setMedicosMap] = useState({}); + const [period, setPeriod] = useState(""); + const [startDate, setStartDate] = useState(""); + const [endDate, setEndDate] = useState(""); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + + const headers = { + apikey: supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + "Content-Type": "application/json", + }; + + var myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + useEffect(() => { + fetch(`${supabaseUrl}/rest/v1/appointments`, requestOptions) + .then(response => response.json()) + .then(result => setConsultas(Array.isArray(result) ? result : [])) + .catch(error => console.log('error', error)); + }, []) + + const handleDelete = async (id) => { + if (getUserRole() === 'paciente') { + Swal.fire("Ação não permitida", "Pacientes não podem excluir consultas. Por favor, entre em contato com a secretaria.", "warning"); + return; + } + const confirm = await Swal.fire({ + title: "Tem certeza?", + text: "Deseja realmente excluir esta consulta? Essa ação não poderá ser desfeita.", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#e63946", + cancelButtonColor: "#6c757d", + confirmButtonText: "Excluir!", + cancelButtonText: "Cancelar", + }); + + if (!confirm.isConfirmed) return; + + try { + const response = await fetch( + `${supabaseUrl}/rest/v1/appointments?id=eq.${id}`, + { + method: "DELETE", + headers: myHeaders, + } + ); + console.log("Resposta do delete:", response); + if (response.ok) { + setConsultas((prev) => prev.filter((c) => c.id !== id)); + setOpenDropdown(null); + + Swal.fire({ + title: "Excluída!", + text: "A consulta foi removida com sucesso.", + icon: "success", + timer: 2000, + showConfirmButton: false, + }); + } else { + Swal.fire("Erro", "Falha ao excluir a consulta. Tente novamente.", "error"); + } + } catch (error) { + console.error("Erro ao deletar:", error); + Swal.fire("Erro", "Não foi possível conectar ao servidor.", "error"); + } + }; + + const filteredConsultas = consulta.filter(p => { + if (!p) return false; + const nome = (pacientesMap[p.patient_id] || "").toLowerCase(); + const médicoNome = (medicosMap[p.doctor_id] || "").toLowerCase(); + const cpf = (p.cpf || "").toLowerCase(); + const email = (p.email || "").toLowerCase(); + const q = search.toLowerCase(); + + + // Filtro por texto (nome, cpf, email) + const matchesText = nome.includes(q) || cpf.includes(q) || email.includes(q) || médicoNome.includes(q); + + // Filtro por status + const matchesStatus = !statusFilter || p.status === statusFilter; + + // Filtro por tipo de consulta + const matchesType = !typeFilter || p.appointment_type === typeFilter; + + + let dateMatch = true; + if (p.scheduled_at) { + const consultaDate = new Date(p.scheduled_at); + const today = new Date(); + + // Filtros por período rápido + if (period === "today") { + const todayStr = today.toDateString(); + dateMatch = consultaDate.toDateString() === todayStr; + } else if (period === "week") { + const startOfWeek = new Date(today); + startOfWeek.setDate(today.getDate() - today.getDay()); + startOfWeek.setHours(0, 0, 0, 0); + const endOfWeek = new Date(startOfWeek); + endOfWeek.setDate(startOfWeek.getDate() + 6); + endOfWeek.setHours(23, 59, 59, 999); + dateMatch = consultaDate >= startOfWeek && consultaDate <= endOfWeek; + } else if (period === "month") { + dateMatch = consultaDate.getMonth() === today.getMonth() && + consultaDate.getFullYear() === today.getFullYear(); + } + + // Filtros por data específica + if (startDate && endDate) { + const start = new Date(startDate); + const end = new Date(endDate); + end.setHours(23, 59, 59, 999); // Inclui o dia inteiro + dateMatch = dateMatch && consultaDate >= start && consultaDate <= end; + } else if (startDate) { + const start = new Date(startDate); + dateMatch = dateMatch && consultaDate >= start; + } else if (endDate) { + const end = new Date(endDate); + end.setHours(23, 59, 59, 999); + dateMatch = dateMatch && consultaDate <= end; + } + } + + return matchesText && matchesStatus && matchesType && dateMatch; + }).sort((a, b) => { + // Priorizar consultas "requested" (solicitadas) primeiro + if (a.status === 'requested' && b.status !== 'requested') { + return -1; // 'a' vem antes de 'b' + } + if (b.status === 'requested' && a.status !== 'requested') { + return 1; // 'b' vem antes de 'a' + } + + // Se ambos têm o mesmo status de prioridade, ordena por data (mais recente primeiro) + const dateA = new Date(a.scheduled_at || 0); + const dateB = new Date(b.scheduled_at || 0); + return dateB - dateA; + }); + const [itemsPerPage1, setItemsPerPage1] = useState(15); + const [currentPage1, setCurrentPage1] = useState(1); + const indexOfLastPatient = currentPage1 * itemsPerPage1; + const indexOfFirstPatient = indexOfLastPatient - itemsPerPage1; + const currentConsultas = filteredConsultas.slice(indexOfFirstPatient, indexOfLastPatient); + const totalPages1 = Math.ceil(filteredConsultas.length / itemsPerPage1); + useEffect(() => { + setCurrentPage1(1); + }, [search, statusFilter, typeFilter, period, startDate, endDate]); + + // Função para definir períodos e limpar datas + const handlePeriodChange = (newPeriod) => { + // Se clicar no mesmo período, limpa o filtro + if (period === newPeriod) { + setPeriod(""); + } else { + setPeriod(newPeriod); + } + + // Sempre limpa as datas específicas + setStartDate(""); + setEndDate(""); + }; +useEffect(() => { + if (!consulta || consulta.length === 0) return; + + const buscarPacientes = async () => { + try { + // Pega IDs únicos de pacientes + const idsUnicos = [...new Set(consulta.map((c) => c.patient_id))]; + + // Faz apenas 1 fetch por paciente + const promises = idsUnicos.map(async (id) => { + try { + const res = await fetch( + `${supabaseUrl}/rest/v1/patients?id=eq.${id}`, + { + method: "GET", + headers: { + apikey: + supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + }, + } + ); + const data = await res.json(); + return { id, full_name: data[0]?.full_name || "Nome não encontrado" }; + } catch (err) { + return { id, full_name: "Nome não encontrado" }; + } + }); + + const results = await Promise.all(promises); + + const map = {}; + results.forEach((r) => (map[r.id] = r.full_name)); + setPacientesMap(map); + } catch (err) { + console.error("Erro ao buscar pacientes:", err); + } + }; + + buscarPacientes(); + }, [consulta]); + useEffect(() => { + if (!Array.isArray(consulta) || consulta.length === 0) return; + + const buscarMedicos = async () => { + try { + const idsUnicos = [...new Set(consulta.map((c) => c.doctor_id).filter(Boolean))]; + if (idsUnicos.length === 0) return; + + const headers = { + "Content-Type": "application/json", + Authorization: `Bearer ${tokenUsuario}`, + apikey: supabaseAK, + }; + + const promises = idsUnicos.map(async (id) => { + try { + const res = await fetch(`${supabaseUrl}/rest/v1/doctors?id=eq.${id}`, { + method: "GET", + headers, + }); + if (!res.ok) return { id, full_name: "Nome não encontrado" }; + const data = await res.json(); + return { id, full_name: data?.[0]?.full_name || "Nome não encontrado" }; + } catch { + return { id, full_name: "Nome não encontrado" }; + } + }); + + const results = await Promise.all(promises); + const map = {}; + results.forEach((r) => (map[r.id] = r.full_name)); + setMedicosMap(map); + } catch (err) { + console.error("Erro ao buscar nomes dos médicos:", err); + } + }; + + buscarMedicos(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [consulta]); + const formatDate = (dateString) => { + if (!dateString) return 'N/A'; + try { + // Extrai data e hora diretamente da string ISO sem conversão de timezone + const [datePart, timePart] = dateString.split('T'); + const [year, month, day] = datePart.split('-'); + const [hour, minute] = timePart.split(':'); + + return `${day}/${month}/${year} ${hour}:${minute}`; + } catch { + return dateString; + } + }; + const handleConfirm = async (id) => { + if (getUserRole() === 'paciente') { + Swal.fire("Ação não permitida", "Pacientes não podem confirmar consultas diretamente. Por favor, entre em contato com a secretaria.", "warning"); + return; + } + const confirm = await Swal.fire({ + title: "Confirmar consulta?", + text: "Esta ação irá confirmar a consulta.", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#4caf50", + cancelButtonColor: "#6c757d", + confirmButtonText: "Confirmar consulta", + cancelButtonText: "Voltar", + }); + + if (!confirm.isConfirmed) return; + + try { + const response = await fetch( + `${supabaseUrl}/rest/v1/appointments?id=eq.${id}`, + { + method: "PATCH", + headers: { + ...headers, + "Prefer": "return=minimal" + }, + body: JSON.stringify({ status: "confirmed" }) + } + ); + + if (response.ok) { + // Atualiza o estado local + setConsultas((prev) => + prev.map((c) => + c.id === id ? { ...c, status: "confirmed" } : c + ) + ); + + Swal.fire({ + title: "Confirmado!", + text: "Consulta confirmada com sucesso.", + icon: "success", + timer: 2000, + showConfirmButton: false, + }); + } else { + throw new Error('Falha na confirmação'); + } + } catch (error) { + console.error("Erro ao confirmar:", error); + Swal.fire("Erro", "Não foi possível confirmar a consulta.", "error"); + } +}; + +const handleCancel = async (id) => { + if (getUserRole() === 'paciente') { + Swal.fire("Ação não permitida", "Pacientes não podem cancelar consultas diretamente. Por favor, entre em contato com a secretaria.", "warning"); + return; + } + const confirm = await Swal.fire({ + title: "Cancelar consulta?", + text: "Esta ação irá cancelar a consulta.", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#e63946", + cancelButtonColor: "#6c757d", + confirmButtonText: "Cancelar consulta", + cancelButtonText: "Voltar", + }); + + if (!confirm.isConfirmed) return; + + try { + const response = await fetch( + `${supabaseUrl}/rest/v1/appointments?id=eq.${id}`, + { + method: "PATCH", + headers: { + ...headers, + }, + body: JSON.stringify({ status: "cancelled" }) + } + ); + + if (response.ok) { + setConsultas((prev) => + prev.map((c) => + c.id === id ? { ...c, status: "cancelled" } : c + ) + ); + + Swal.fire({ + title: "Cancelado!", + text: "Consulta cancelada com sucesso.", + icon: "success", + timer: 2000, + showConfirmButton: false, + }); + } else { + throw new Error('Falha no cancelamento'); + } + } catch (error) { + console.error("Erro ao cancelar:", error); + Swal.fire("Erro", "Não foi possível cancelar a consulta.", "error"); + } +}; + + const navigate = useNavigate(); + const role = getUserRole(); + const permissoes = { + admin: ['editconsulta', 'deletarconsulta', 'consultaform', 'viewactionconsultas' , 'nomepaciente'], + medico: ['editconsulta', 'deletarconsulta', 'consultaform', 'viewactionconsultas', 'nomepaciente'], + secretaria: ['editconsulta', 'deletarconsulta', 'consultaform', 'viewactionconsultas', 'nomepaciente'], + paciente: [''] +}; + const pode = (acao) => permissoes[role]?.includes(acao); + function hasAnyAction(c) { + return ( + pode('editconsulta') || + pode('deletarconsulta') || + (c.status === 'confirmed' && pode('viewactionconsultas')) || + (c.appointment_type === 'telemedicina' && c.status === 'confirmed') || + (c.status === 'requested' && pode('viewactionconsultas')) + ); +} + return ( +
+
+
+
+
+ {pode('consultaform') && ( +
+

Lista de consultas

+ + Adicionar consulta + +
+ )} + + {/* Todos os filtros em uma única linha */} +
+ {/* Campo de busca */} + setSearch(e.target.value)} + /> + + {/* Filtro de status */} + + + {/* Filtro De */} +
+ + { + setStartDate(e.target.value); + if (e.target.value) setPeriod(""); + }} + /> +
+ + {/* Filtro Até */} +
+ + { + setEndDate(e.target.value); + if (e.target.value) setPeriod(""); + }} + /> +
+ + {/* Botões rápidos */} + + + +
+
+
+ +
+
+
+ + + + + {pode('nomepaciente') && } + + + + + + {currentConsultas.some(hasAnyAction) && } + + + + {currentConsultas.length > 0 ? ( + currentConsultas.map((c) => ( + + + {pode('nomepaciente') && } + + + + + + {currentConsultas.some(hasAnyAction) && ( + hasAnyAction(c) ? ( + + ) : ( + + ) + )} + + )) + ) : ( + + + + )} + +
PedidoNome do PacienteNome do MédicoAgendadoDuraçãoModoStatusAção
{c.order_number}{pacientesMap[c.patient_id] || "Carregando..."}{medicosMap[c.doctor_id] || "Carregando..."}{formatDate(c.scheduled_at)}{c.duration_minutes} min + + {c.appointment_type === 'presencial' ? ( + <> + + Presencial + + ) : c.appointment_type === 'telemedicina' ? ( + <> + + Telemedicina + + ) : ( + c.appointment_type + )} + + + + + {c.status === 'requested' ? ( + <> + + Solicitado + + ) : c.status === 'confirmed' ? ( + <> + + Confirmado + + ) : c.status === 'completed' ? ( + <> + + Concluído + + ) : c.status === 'cancelled' ? ( + <> + + Cancelado + + ) : ( + <> + + {c.status} + + )} + + +
+ {c.appointment_type !== 'telemedicina' && c.status === 'confirmed' && ( + + )} + {c.appointment_type === 'telemedicina' && c.status === 'confirmed' && ( + + )} + {pode('editconsulta') && ( + + )} + + {c.status === 'requested' && pode('viewactionconsultas') && ( + <> + + + + )} + +
+
-
+ Nenhuma consulta encontrada. +
+
+
+
+ Total encontrados: {filteredConsultas.length} +
+
+ +
+
+
+ +
+
+
+
+
+
+ ); +} + +export default ConsultaList; \ No newline at end of file diff --git a/src/components/lists/DoctorList.jsx b/src/components/lists/DoctorList.jsx new file mode 100644 index 0000000..714e5f5 --- /dev/null +++ b/src/components/lists/DoctorList.jsx @@ -0,0 +1,405 @@ +import "../../assets/css/index.css"; +import { useState, useEffect } from "react"; +import { Link } from "react-router-dom"; +import supabase from "../../Supabase.js"; +import Swal from "sweetalert2"; +import { getAccessToken } from "../../utils/auth.js"; +import { getUserRole } from "../../utils/userInfo.js"; + + +const AvatarForm = "/img/AvatarForm.jpg"; + + +function DoctorList() { + const [search, setSearch] = useState(""); + const [specialtyFilter, setSpecialtyFilter] = useState(""); // Filtro por especialidade + const [doctors, setDoctors] = useState([]); + const [openDropdown, setOpenDropdown] = useState(null); + const tokenUsuario = getAccessToken() + const role = getUserRole(); + var myHeaders = new Headers(); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + + myHeaders.append( + "apikey", + supabaseAK + ); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + + var requestOptions = { + method: "GET", + headers: myHeaders, + redirect: "follow", + }; + + // buscar médicos + useEffect(() => { + fetch(`${supabaseUrl}/rest/v1/doctors`, requestOptions) + .then((response) => response.json()) + .then((result) => setDoctors(Array.isArray(result) ? result : [])) + .catch((error) => console.log("error", error)); + }, []); + + + const handleDelete = async (id) => { + Swal.fire({ + title: "Tem certeza?", + text: "Tem certeza que deseja excluir este registro?", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Sim, excluir", + cancelButtonText: "Cancelar" + }).then(async (result) => { + if (result.isConfirmed) { + try { + const tokenUsuario = getAccessToken(); // pega o token do usuário (mesmo que usa no form) + + var myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + myHeaders.append("Content-Type", "application/json"); + + const response = await fetch( + `${supabaseUrl}/rest/v1/doctors?id=eq.${id}`, + { + method: "DELETE", + headers: myHeaders, + } + ); + + if (!response.ok) { + const err = await response.json(); + console.error("Erro ao deletar médico:", err); + Swal.fire("Erro!", err.message || "Não foi possível excluir o registro.", "error"); + return; + } + + // Atualiza a lista local + setDoctors((prev) => prev.filter((doc) => doc.id !== id)); + + Swal.fire("Excluído!", "O registro foi removido com sucesso.", "success"); + } catch (error) { + console.error("Erro inesperado:", error); + Swal.fire("Erro!", "Algo deu errado ao excluir.", "error"); + } + } + }); + }; + + const handleViewDetails = async (id) => { + try { + const tokenUsuario = getAccessToken(); + + const response = await fetch( + `${supabaseUrl}/rest/v1/doctors?id=eq.${id}`, + { + method: "GET", + headers: { + apikey: + supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + }, + } + ); + + const data = await response.json(); + const doctor = data[0]; + + if (!doctor) { + Swal.fire("Erro", "Não foi possível carregar os detalhes do médico.", "error"); + return; + } + + Swal.fire({ + width: "800px", + showConfirmButton: true, + confirmButtonText: "Fechar", + confirmButtonColor: "#4dabf7", + background: document.body.classList.contains("dark-mode") ? "#1e1e2f" : "#fff", + color: document.body.classList.contains("dark-mode") ? "#f5f5f5" : "#000", + html: ` +
+ +
+
Perfil Médico
+ +
+ + +
+ ${doctor.full_name} +
${doctor.full_name}
+

${doctor.specialty || "Especialidade não informada"}

+
+ + +
+
+

Telefone: ${doctor.phone_mobile || "—"}

+

Email: ${doctor.email || "—"}

+

Data de nascimento: ${doctor.birth_date || "—"}

+

Sexo: ${doctor.gender || "—"}

+
+
+

Região: ${doctor.city || "—"}, ${doctor.state || "—"}, Brasil

+

CRM: ${doctor.crm || "—"}

+

Especialidade: ${doctor.specialty || "—"}

+

Experiência: ${doctor.experience_years || "—"} anos

+
+
+ + +
+
Biografia
+

+ ${doctor.biografia || "Este médico ainda não possui biografia cadastrada."} +

+
+
+ `, + didOpen: () => { + document + .getElementById("btn-close-modal") + ?.addEventListener("click", () => Swal.close()); + }, + }); + } catch (err) { + console.error("Erro ao buscar médico:", err); + Swal.fire("Erro!", err.message || "Erro ao buscar médico.", "error"); + } + }; + + // Função de filtragem (mesmo padrão do PatientList) + const filteredDoctors = doctors.filter(doctor => { + if (!doctor) return false; + + // Filtro por texto (nome, especialidade, CRM, email) + const nome = (doctor.full_name || "").toLowerCase(); + const crm = (doctor.crm || "").toLowerCase(); + const email = (doctor.email || "").toLowerCase(); + const cidade = (doctor.city || "").toLowerCase(); + const q = search.toLowerCase(); + const matchesSearch = nome.includes(q) || crm.includes(q) || email.includes(q) || cidade.includes(q); + + // Filtro por especialidade + let matchesSpecialty = true; + if (specialtyFilter) { + const doctorSpecialty = (doctor.specialty || "").toLowerCase().trim(); + matchesSpecialty = doctorSpecialty.includes(specialtyFilter.toLowerCase()); + } + + return matchesSearch && matchesSpecialty; + }); + const permissoes = { + admin: ['adddoctor'], + secretaria: [""], + paciente: [''] +}; + const pode = (acao) => permissoes[role]?.includes(acao); + return ( +
+
+
+
+
+

Lista de Médicos

+ {pode('adddoctor') && ( + Adicionar Médico + + )} +
+ + {/* Filtros em uma única linha (mesmo padrão do PatientList) */} +
+ {/* Campo de busca */} + setSearch(e.target.value)} + style={{ minWidth: "300px", maxWidth: "450px" }} + /> + + {/* Filtro por especialidade */} + + + {/* Contador de resultados */} + + {filteredDoctors.length} médico(s) encontrado(s) + +
+
+
+ +
+ {filteredDoctors.length > 0 ? ( + filteredDoctors.map((doctor) => ( +
+
+
+
+ +
+
+ + {/* Dropdown estilizado */} +
+ + + {openDropdown === doctor.id && ( +
+ {/* Ver Detalhes */} + { + e.stopPropagation(); + setOpenDropdown(null); + handleViewDetails(doctor.id); + }} + > + Ver Detalhes + + + {/* Edit */} + + Editar + + + {/* Delete */} + +
+ )} +
+ +

+ + {doctor.full_name} + +

+
{doctor.specialty || 'Não informado'}
+
+ {doctor.city || 'Não informado'} +
+
+
+ )) + ) : ( +
+
+ +
Nenhum médico encontrado
+

+ {search || specialtyFilter + ? "Tente ajustar os filtros de busca" + : "Nenhum médico cadastrado no sistema"} +

+
+
+ )} +
+
+ + {/* Modal delete (não alterado) */} + +
+ ); +} + +export default DoctorList; diff --git a/src/components/lists/LaudoList.jsx b/src/components/lists/LaudoList.jsx new file mode 100644 index 0000000..0a2d62e --- /dev/null +++ b/src/components/lists/LaudoList.jsx @@ -0,0 +1,755 @@ +import { Link } from "react-router-dom"; +import React, { useState, useRef, useLayoutEffect, useEffect } from "react"; +import { createPortal } from "react-dom"; +import { getAccessToken } from "../../utils/auth.js"; +import Swal from 'sweetalert2'; +import { useResponsive } from '../../utils/useResponsive'; +import { useNavigate } from "react-router-dom"; +import { getUserRole } from "../../utils/userInfo.js"; + + +function DropdownPortal({ anchorEl, isOpen, onClose, className, children }) { + const menuRef = useRef(null); + const [stylePos, setStylePos] = useState({ + position: "absolute", + top: 0, + left: 0, + visibility: "hidden", + zIndex: 1000, + }); + + useLayoutEffect(() => { + if (!isOpen || !anchorEl || !menuRef.current) return; + + const anchorRect = anchorEl.getBoundingClientRect(); + const menuRect = menuRef.current.getBoundingClientRect(); + const scrollY = window.scrollY || window.pageYOffset; + const scrollX = window.scrollX || window.pageXOffset; + + let left = anchorRect.right + scrollX - menuRect.width; + let top = anchorRect.bottom + scrollY; + + if (left < 0) left = scrollX + 4; + if (top + menuRect.height > window.innerHeight + scrollY) { + top = anchorRect.top + scrollY - menuRect.height; + } + + setStylePos({ + position: "absolute", + top: `${Math.round(top)}px`, + left: `${Math.round(left)}px`, + visibility: "visible", + zIndex: 1000, + }); + }, [isOpen, anchorEl, children]); + + useEffect(() => { + if (!isOpen) return; + + const handleDocClick = (e) => { + if (menuRef.current && !menuRef.current.contains(e.target) && + anchorEl && !anchorEl.contains(e.target)) { + onClose(); + } + }; + const handleScroll = () => onClose(); + + document.addEventListener("mousedown", handleDocClick); + document.addEventListener("scroll", handleScroll, true); + + return () => { + document.removeEventListener("mousedown", handleDocClick); + document.removeEventListener("scroll", handleScroll, true); + }; + }, [isOpen, onClose, anchorEl]); + + if (!isOpen) return null; + return createPortal( +
e.stopPropagation()}> + {children} +
, + document.body + ); +} + +function LaudoList() { + const [search, setSearch] = useState(""); + const [period, setPeriod] = useState(""); // "", "today", "week", "month" + const [startDate, setStartDate] = useState(""); + const [endDate, setEndDate] = useState(""); + const [statusFilter, setStatusFilter] = useState(""); + const [laudos, setLaudos] = useState([]) + const [openDropdown, setOpenDropdown] = useState(null); + const anchorRefs = useRef({}); + const tokenUsuario = getAccessToken() + const role = getUserRole(); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || "https://yuanqfswhberkoevtmfr.supabase.co"; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + + var myHeaders = new Headers(); + myHeaders.append( + "apikey", + supabaseAK + ); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + + useEffect(() => { + fetch(`${supabaseUrl}/rest/v1/reports`, requestOptions) + .then(response => response.json()) + .then(result => setLaudos(Array.isArray(result) ? result : [])) + .catch(error => console.log('error', error)); + }, []) + + + + const handleVerDetalhes = (laudo) => { + Swal.fire({ + title: "Detalhes do Laudo", + html: ` +
+
+
Informações do Pedido
+

Nº Pedido: ${laudo.order_number || 'N/A'}

+

Paciente ID: ${laudo.patient_id || 'N/A'}

+

Tipo: ${laudo.tipo || 'N/A'}

+
+ +
+
Detalhes do Exame
+

Exame: ${laudo.exam || 'N/A'}

+

Diagnóstico: ${laudo.diagnosis || 'Nenhum diagnóstico'}

+

Conclusão: ${laudo.conclusion || 'Nenhuma conclusão'}

+
+ +
+
Responsáveis
+

Executante: ${laudo.requested_by || 'N/A'}

+
+ +
+
Datas
+

Criado em: ${formatDate(laudo.created_at) || 'N/A'}

+
+
+ `, + showCancelButton: true, + confirmButtonText: "Abrir Laudo", + cancelButtonText: "Fechar", + confirmButtonColor: "#3085d6", + cancelButtonColor: "#6c757d", + icon: "info", + width: "600px", + draggable: true + }).then((result) => { + if (result.isConfirmed) { + // Abrir o form de laudo + abrirFormLaudo(laudo.id); + } + }); + }; + + const abrirFormLaudo = (laudoId) => { + // Navega para o form de laudo com o ID + window.location.href = `/${role}/laudoform?id=${laudoId}`; + }; + + const getStatusBadgeClass = (status) => { + switch (status?.toLowerCase()) { + case 'concluído': + case 'finalizado': + return 'bg-success'; + case 'pendente': + return 'bg-warning'; + case 'cancelado': + return 'bg-danger'; + default: + return 'bg-secondary'; + } + }; + + const formatDate = (dateString) => { + if (!dateString) return 'N/A'; + try { + const date = new Date(dateString); + return date.toLocaleDateString('pt-BR', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit' + }).replace(',', ' às'); + } catch { + return dateString; + } + }; + + const handleDelete = (id) => { + Swal.fire({ + title: "Tem certeza?", + text: "Tem certeza que deseja excluir este laudo?", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#e63946", + cancelButtonColor: "#6c757d", + confirmButtonText: "Excluir!", + cancelButtonText: "Cancelar", + }).then((result) => { + if (result.isConfirmed) { + + var requestOptions = { + method: 'DELETE', + headers: myHeaders, + redirect: 'follow' + }; + + fetch(`${supabaseUrl}/rest/v1/reports?id=eq.${id}`, requestOptions) + .then(response => response.text()) + .then(result => console.log(result)) + .catch(error => console.log('error', error)); + setLaudos(prev => prev.filter(l => l.id !== id)); + setOpenDropdown(null); + Swal.fire({ + title: "Excluído!", + text: "Laudo excluído com sucesso.", + icon: "success", + draggable: true + }); + } + }); + }; + + const mascararCPF = (cpf = "") => { + if (cpf.length < 5) return cpf; + return `${cpf.slice(0, 3)}.***.***-${cpf.slice(-2)}`; + }; + const [pacientesMap, setPacientesMap] = useState({}); + // useEffect para atualizar todos os nomes + useEffect(() => { + if (!laudos || laudos.length === 0) return; + + const buscarPacientes = async () => { + try { + // Pega IDs únicos de pacientes + const idsUnicos = [...new Set(laudos.map((l) => l.patient_id))]; + + // Faz apenas 1 fetch por paciente + const promises = idsUnicos.map(async (id) => { + try { + const res = await fetch( + `${supabaseUrl}/rest/v1/patients?id=eq.${id}`, + { + method: "GET", + headers: { + apikey: + supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + }, + } + ); + const data = await res.json(); + return { id, full_name: data[0]?.full_name || "Nome não encontrado" }; + } catch (err) { + return { id, full_name: "Nome não encontrado" }; + } + }); + + const results = await Promise.all(promises); + + const map = {}; + results.forEach((r) => (map[r.id] = r.full_name)); + setPacientesMap(map); + } catch (err) { + console.error("Erro ao buscar pacientes:", err); + } + }; + + buscarPacientes(); + }, [laudos]); + const filteredLaudos = laudos.filter(l => { + const q = search.toLowerCase(); + const textMatch = + (pacientesMap[l.patient_id]?.toLowerCase() || "").includes(q) || + (l.status || "").toLowerCase().includes(q) || + (l.order_number || "").toString().toLowerCase().includes(q) || + (l.exam || "").toLowerCase().includes(q) || + (l.diagnosis || "").toLowerCase().includes(q) || + (l.conclusion || "").toLowerCase().includes(q); + + // Filtro por status + const matchesStatus = !statusFilter || l.status === statusFilter; + + let dateMatch = true; + if (l.created_at) { + const laudoDate = new Date(l.created_at); + const today = new Date(); + + // Filtros por período rápido + if (period === "today") { + const todayStr = today.toDateString(); + dateMatch = laudoDate.toDateString() === todayStr; + } else if (period === "week") { + const startOfWeek = new Date(today); + startOfWeek.setDate(today.getDate() - today.getDay()); + startOfWeek.setHours(0, 0, 0, 0); + const endOfWeek = new Date(startOfWeek); + endOfWeek.setDate(startOfWeek.getDate() + 6); + endOfWeek.setHours(23, 59, 59, 999); + dateMatch = laudoDate >= startOfWeek && laudoDate <= endOfWeek; + } else if (period === "month") { + dateMatch = laudoDate.getMonth() === today.getMonth() && + laudoDate.getFullYear() === today.getFullYear(); + } + + // Filtros por data específica + if (startDate && endDate) { + const start = new Date(startDate); + const end = new Date(endDate); + end.setHours(23, 59, 59, 999); // Inclui o dia inteiro + dateMatch = dateMatch && laudoDate >= start && laudoDate <= end; + } else if (startDate) { + const start = new Date(startDate); + dateMatch = dateMatch && laudoDate >= start; + } else if (endDate) { + const end = new Date(endDate); + end.setHours(23, 59, 59, 999); + dateMatch = dateMatch && laudoDate <= end; + } + } + + return textMatch && matchesStatus && dateMatch; + }); + + const [itemsPerPage1, setItemsPerPage1] = useState(15); + const [currentPage1, setCurrentPage1] = useState(1); + const indexOfLastLaudos = currentPage1 * itemsPerPage1; + const indexOfFirstLaudos = indexOfLastLaudos - itemsPerPage1; + const currentLaudos = filteredLaudos.slice(indexOfFirstLaudos, indexOfLastLaudos); + const totalPages1 = Math.ceil(filteredLaudos.length / itemsPerPage1); + const navigate = useNavigate(); + const [medicosMap, setMedicosMap] = useState({}); + // Função para definir períodos e limpar datas + const handlePeriodChange = (newPeriod) => { + // Se clicar no mesmo período, limpa o filtro + if (period === newPeriod) { + setPeriod(""); + } else { + setPeriod(newPeriod); + } + + // Sempre limpa as datas específicas + setStartDate(""); + setEndDate(""); + }; + + useEffect(() => { + setCurrentPage1(1); + }, [search, statusFilter, period, startDate, endDate]); + + useEffect(() => { + if (!Array.isArray(laudos) || laudos.length === 0) return; + + const buscarMedicos = async () => { + try { + const idsUnicos = [...new Set(laudos.map((c) => c.requested_by).filter(Boolean))]; + if (idsUnicos.length === 0) return; + + const headers = { + "Content-Type": "application/json", + Authorization: `Bearer ${tokenUsuario}`, + apikey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ", + }; + + const promises = idsUnicos.map(async (id) => { + try { + const res = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${id}`, { + method: "GET", + headers, + }); + if (!res.ok) return { id, full_name: "Nome não encontrado" }; + const data = await res.json(); + return { id, full_name: data?.[0]?.full_name || "Nome não encontrado" }; + } catch { + return { id, full_name: "Nome não encontrado" }; + } + }); + + const results = await Promise.all(promises); + const map = {}; + results.forEach((r) => (map[r.id] = r.full_name)); + setMedicosMap(map); + } catch (err) { + console.error("Erro ao buscar nomes dos médicos:", err); + } + }; + + buscarMedicos(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [laudos]); + const permissoes = { + admin: ['editlaudo', 'deletarlaudo', 'viewlaudo', 'viewpatientlaudos', 'createlaudo', 'executantelaudo'], + medico: ['editlaudo', 'deletarlaudo', 'viewlaudo', 'viewpatientlaudos', 'createlaudo'], + paciente: ['viewlaudo'] +}; + const pode = (acao) => permissoes[role]?.includes(acao); + // Função para imprimir o laudo (content_html) + const handlePrint = async (laudoId) => { + try { + const res = await fetch(`${supabaseUrl}/rest/v1/reports?id=eq.${laudoId}`, { + method: 'GET', + headers: myHeaders, + }); + if (!res.ok) { + Swal.fire({ + icon: 'error', + title: 'Erro de autenticação', + text: 'Não foi possível acessar o laudo. Faça login novamente.' + }); + return; + } + const data = await res.json(); + const contentHtml = data[0]?.content_html; + if (!contentHtml) { + Swal.fire({ + icon: 'warning', + title: 'Sem conteúdo', + text: 'Este laudo não possui conteúdo para impressão.' + }); + return; + } + // Caminho da logo (ajuste se necessário) + const logoUrl = '/public/img/logomedconnect.png'; + const pacienteNome = pacientesMap[data[0]?.patient_id] || 'Paciente'; + const pedido = data[0]?.order_number || 'N/A'; + const exame = data[0]?.exam || 'N/A'; + const dataCriacao = formatDate(data[0]?.created_at); + const medicoNome = medicosMap[data[0]?.requested_by] || data[0]?.requested_by || ''; + const printWindow = window.open('', '', 'width=900,height=700'); + printWindow.document.write(` + + + + Laudo Médico - ${pacienteNome} + + + +
+ +
+
MedConnect - Sistema de Laudos
+
${new Date().toLocaleDateString('pt-BR')}
+
+
+
+ Paciente: ${pacienteNome}
+ Pedido: ${pedido}
+ Exame: ${exame}
+ Médico: ${medicoNome}
+ Data: ${dataCriacao} +
+

Laudo Médico

+
${contentHtml}
+ + + + `); + printWindow.document.close(); + printWindow.focus(); + printWindow.print(); + } catch (err) { + Swal.fire({ + icon: 'error', + title: 'Erro ao imprimir', + text: 'Não foi possível imprimir o laudo.' + }); + } + }; + return ( +
+
+ {/* Header com título e botão */} +
+
+

Laudos

+ {pode('createlaudo') && ( + { + e.stopPropagation(); + setOpenDropdown(null); + }} + className="btn btn-primary btn-rounded" + > + Adicionar Laudo + + )} +
+
+ + {/* Todos os filtros em uma única linha */} +
+ {/* Campo de busca */} + setSearch(e.target.value)} + style={{ minWidth: "300px", maxWidth: "450px", }} + /> + + {/* Filtro de status */} + + + {/* Filtro De */} +
+ + { + setStartDate(e.target.value); + if (e.target.value) setPeriod(""); + }} + /> +
+ + {/* Filtro Até */} +
+ + { + setEndDate(e.target.value); + if (e.target.value) setPeriod(""); + }} + /> +
+ + {/* Botões rápidos */} + + + +
+ + {/* Tabela */} +
+
+
+ + + + + {pode('viewpatientlaudos') && ( + + )} + + + + + {pode('executantelaudo') && ( + + )} + + + + + + {currentLaudos.length > 0 ? currentLaudos.map(l => ( + + + {pode('viewpatientlaudos') && ( + + )} + + + + + {pode('executantelaudo') && ( + + )} + + + + )) : ( + + + + )} + +
PedidoPacienteProcedimentoDiagnósticoConclusãoStatusExecutanteCriado emAções
{l.order_number}{pacientesMap[l.patient_id] || "Carregando..."}{l.exam}{l.diagnosis}{l.conclusion} + + {l.status === 'draft' ? ( + <> + + Rascunho + + ) : l.status === 'completed' ? ( + <> + + Concluído + + ) : ( + l.status + )} + + {medicosMap[l.requested_by] || l.requested_by}{formatDate(l.created_at)} +
+ {pode('editlaudo') && ( + )} + + {/* Botão de imprimir */} + + {pode('deletarlaudo') && ( + + )} +
+
Nenhum laudo encontrado
+
+
+
+ Total encontrados: {filteredLaudos.length} +
+
+ +
+
+
+ +
+
+
+
+
+ ); +} + +export default LaudoList; \ No newline at end of file diff --git a/src/components/lists/PatientList.jsx b/src/components/lists/PatientList.jsx new file mode 100644 index 0000000..daf26cd --- /dev/null +++ b/src/components/lists/PatientList.jsx @@ -0,0 +1,616 @@ +import { Link } from "react-router-dom"; +import "../../assets/css/index.css"; +import React, { useState, useEffect, useRef, useLayoutEffect } from "react"; +import { createPortal } from "react-dom"; +import supabase from "../../Supabase.js"; +import { getAccessToken } from "../../utils/auth.js"; +import Swal from "sweetalert2"; +import '../../assets/css/modal-details.css'; +const AvatarForm = "/img/AvatarForm.jpg"; +import { useNavigate } from "react-router-dom"; +import { getUserRole } from "../../utils/userInfo.js"; +// Componente que renderiza o menu em um portal (document.body) e posiciona em relação ao botão +function DropdownPortal({ anchorEl, isOpen, onClose, className, children }) { + const menuRef = useRef(null); + const [stylePos, setStylePos] = useState({ + position: "absolute", + top: 0, + left: 0, + visibility: "hidden", + zIndex: 1000, + }); + + // Posiciona o menu após renderar (medir tamanho do menu) + useLayoutEffect(() => { + if (!isOpen) return; + if (!anchorEl || !menuRef.current) return; + + const anchorRect = anchorEl.getBoundingClientRect(); + const menuRect = menuRef.current.getBoundingClientRect(); + const scrollY = window.scrollY || window.pageYOffset; + const scrollX = window.scrollX || window.pageXOffset; + + // tenta alinhar à direita do botão (como dropdown-menu-right) + let left = anchorRect.right + scrollX - menuRect.width; + let top = anchorRect.bottom + scrollY; + + // evita sair da esquerda da tela + if (left < 0) left = scrollX + 4; + // se extrapolar bottom, abre para cima + if (top + menuRect.height > window.innerHeight + scrollY) { + top = anchorRect.top + scrollY - menuRect.height; + } + setStylePos({ + position: "absolute", + top: `${Math.round(top)}px`, + left: `${Math.round(left)}px`, + visibility: "visible", + zIndex: 1000, + }); + }, [isOpen, anchorEl, children]); + + // fecha ao clicar fora / ao rolar + useEffect(() => { + if (!isOpen) return; + function handleDocClick(e) { + const menu = menuRef.current; + if (menu && !menu.contains(e.target) && anchorEl && !anchorEl.contains(e.target)) { + onClose(); + } + } + function handleScroll() { + onClose(); + } + document.addEventListener("mousedown", handleDocClick); + // captura scroll em qualquer elemento (true) + document.addEventListener("scroll", handleScroll, true); + return () => { + document.removeEventListener("mousedown", handleDocClick); + document.removeEventListener("scroll", handleScroll, true); + }; + }, [isOpen, onClose, anchorEl]); + + if (!isOpen) return null; + return createPortal( +
e.stopPropagation()} + > + {children} +
, + document.body + ); +} + +function PatientList() { + const [search, setSearch] = useState(""); + const [sexFilter, setSexFilter] = useState(""); // Filtro por sexo + const [patients, setPatients] = useState([]); + const [openDropdown, setOpenDropdown] = useState(null); + const anchorRefs = useRef({}); // guarda referência do botão de cada linha + // 🟢 ADICIONADO — controla o modal e o paciente selecionado + const [selectedPatient, setSelectedPatient] = useState(null); + const [showModal, setShowModal] = useState(false); + const role = getUserRole(); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + + const handleViewDetails = (patient) => { + const mascararCPF = (cpf = "") => { + if (cpf.length < 5) return cpf; + const inicio = cpf.slice(0, 3); + const fim = cpf.slice(-2); + return `${inicio}.***.***-${fim}`; + }; + + Swal.fire({ + title: `

Detalhes do Paciente

`, + html: ` +
+ ${patient.full_name} +
${patient.full_name}
+

Informações detalhadas sobre o paciente.

+
+ +
+
+
+

Nome Completo: ${patient.full_name}

+

Telefone: ${patient.phone_mobile}

+

CPF: ${mascararCPF(patient.cpf)}

+

Peso (kg): ${patient.weight || "—"}

+

Endereço: ${patient.address || "—"}

+
+
+

Email: ${patient.email}

+

Data de Nascimento: ${patient.birth_date}

+

Tipo Sanguíneo: ${patient.blood_type || "—"}

+

Altura (m): ${patient.height || "—"}

+
+
+
+ `, + width: "800px", + showConfirmButton: true, + confirmButtonText: "Fechar", + confirmButtonColor: "#4dabf7", + background: document.body.classList.contains("dark-mode") + ? "#1e1e2f" + : "#fff", + color: document.body.classList.contains("dark-mode") + ? "#f5f5f5" + : "#000", + customClass: { + popup: 'swal2-modal-patient' + } + }); + }; + + + const tokenUsuario = getAccessToken() + var myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + useEffect(() => { + fetch(`${supabaseUrl}/rest/v1/patients`, requestOptions) + .then(response => response.json()) + .then(result => { + setPatients(Array.isArray(result) ? result : []) + console.log(result); + }) + .catch(error => console.log('error', error)); + }, []) + + const handleDelete = async (id) => { + if (getUserRole() === 'paciente' || getUserRole() === 'medico') { + Swal.fire("Ação não permitida", "Pacientes e médicos não podem excluir pacientes. Por favor, entre em contato com a secretaria.", "warning"); + return; + } + Swal.fire({ + title: "Tem certeza?", + text: "Tem certeza que deseja excluir este paciente?", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#e63946", + cancelButtonColor: "#6c757d", + confirmButtonText: "Excluir!", + cancelButtonText: "Cancelar", + }).then(async (result) => { + if (result.isConfirmed) { + try { + var myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + + var requestOptions = { + method: 'DELETE', + headers: myHeaders, + redirect: 'follow' + }; + + const response = await fetch(`${supabaseUrl}/rest/v1/patients?id=eq.${id}`, requestOptions) + + if (response.ok) { + setPatients(prev => prev.filter(l => l.id !== id)); + setOpenDropdown(null); + Swal.fire({ + title: "Registro Excluído", + text: "Registro excluído com sucesso", + icon: "success" + }) + + } else { + Swal.fire("Error saving changes", "", "error"); + } + } + catch (error) { + Swal.fire("Something went wrong", "", "error"); + console.error(error); + } + } + }); + }; + + + const [birthDateStart, setBirthDateStart] = useState(""); + const [birthDateEnd, setBirthDateEnd] = useState(""); + const [ageRange, setAgeRange] = useState(""); + const [bloodType, setBloodType] = useState(""); + + const filteredPatients = patients.filter(p => { + if (!p) return false; + + // Filtro por texto (nome, cpf, email) + const nome = (p.full_name || "").toLowerCase(); + const cpf = (p.cpf || "").toLowerCase(); + const email = (p.email || "").toLowerCase(); + const data = (p.birth_date || "").toLowerCase(); + const q = search.toLowerCase(); + const matchesSearch = nome.includes(q) || cpf.includes(q) || email.includes(q) || data.includes(q); + + // Filtro por sexo (flexível - aceita diferentes variações) + let matchesSex = true; + if (sexFilter) { + const patientSex = (p.sex || "").toLowerCase().trim(); + + if (sexFilter === "masculino") { + matchesSex = patientSex === "masculino" || patientSex === "m" || patientSex === "male"; + } else if (sexFilter === "feminino") { + matchesSex = patientSex === "feminino" || patientSex === "f" || patientSex === "female"; + } else if (sexFilter === "outros") { + matchesSex = !["masculino", "m", "male", "feminino", "f", "female", ""].includes(patientSex); + } + } + + // Filtro por data de nascimento + let matchesBirthDate = true; + if (birthDateStart) { + matchesBirthDate = p.birth_date >= birthDateStart; + } + if (matchesBirthDate && birthDateEnd) { + matchesBirthDate = p.birth_date <= birthDateEnd; + } + + // Filtro por faixa etária + let matchesAge = true; + if (ageRange && p.birth_date) { + const today = new Date(); + const birth = new Date(p.birth_date); + let age = today.getFullYear() - birth.getFullYear(); + const m = today.getMonth() - birth.getMonth(); + if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) { + age--; + } + if (ageRange === "0-18") matchesAge = age >= 0 && age <= 18; + else if (ageRange === "19-40") matchesAge = age >= 19 && age <= 40; + else if (ageRange === "41-60") matchesAge = age >= 41 && age <= 60; + else if (ageRange === "60+") matchesAge = age > 60; + } + + // Filtro por tipo sanguíneo + let matchesBlood = true; + if (bloodType) { + matchesBlood = (p.blood_type || "").toUpperCase() === bloodType; + } + return matchesSearch && matchesSex && matchesBirthDate && matchesAge && matchesBlood; + }); + const [itemsPerPage1, setItemsPerPage1] = useState(15); + const [currentPage1, setCurrentPage1] = useState(1); + const indexOfLastPatient = currentPage1 * itemsPerPage1; + const indexOfFirstPatient = indexOfLastPatient - itemsPerPage1; + const currentPatients = filteredPatients.slice(indexOfFirstPatient, indexOfLastPatient); + const totalPages1 = Math.ceil(filteredPatients.length / itemsPerPage1); + useEffect(() => { + setCurrentPage1(1); + }, [search, sexFilter]); + + const mascararCPF = (cpf = "") => { + if (cpf.length < 5) return cpf; + const inicio = cpf.slice(0, 3); + const fim = cpf.slice(-2); + return `${inicio}.***.***-${fim}`; + }; + + const renderSexBadge = (sex) => { + const sexo = (sex || "").toLowerCase().trim(); + + if (sexo === "masculino" || sexo === "m" || sexo === "male") { + return ( + + + Masculino + + ); + } else if (sexo === "feminino" || sexo === "f" || sexo === "female") { + return ( + + + Feminino + + ); + } else if (sexo === "") { + return ( + + + Em branco + + ); + } else { + return ( + + + {sexo || "Outros"} + + ); + } + }; + + const navigate = useNavigate(); + const permissoes = { + admin: ['editpatient', 'deletepatient'], + medico: [''], + secretaria: ['editpatient', 'deletepatient'], + }; + const pode = (acao) => permissoes[role]?.includes(acao); + return ( +
+
+
+
+
+
+

Lista de Pacientes

+ + Adicionar Paciente + +
+ + {/* Todos os filtros em uma única linha */} +
+ {/* Campo de busca */} + setSearch(e.target.value)} + style={{ minWidth: "300px", maxWidth: "450px" }} + /> + + {/* Filtro por sexo */} + + {/* Filtro por data de nascimento (de) */} + De: + setBirthDateStart(e.target.value)} + style={{ minWidth: "150px", maxWidth: "180px" }} + placeholder="Nascimento de" + title="Nascimento de" + /> + {/* Filtro por data de nascimento (até) */} + Até: + setBirthDateEnd(e.target.value)} + style={{ minWidth: "150px", maxWidth: "180px" }} + placeholder="Nascimento até" + title="Nascimento até" + /> + {/* Filtro por faixa etária */} + + {/* Filtro por tipo sanguíneo */} + +
+
+
+ +
+
+
+ + + + + + + + + + + + + + {currentPatients.length > 0 ? ( + currentPatients.map((p) => ( + + + + + + + + + + )) + ) : ( + + + + )} + +
NomeCpfData de NascimentoTelefoneEmailSexoAções
+
+
+ {p.full_name} { + e.target.src = AvatarForm; // Fallback se a imagem não carregar + }} + /> + {p.full_name} +
+
+
{mascararCPF(p.cpf)}{p.birth_date}{p.phone_mobile}{p.email}{renderSexBadge(p.sex)} +
+ + + {pode('editpatient') && ( + + )} + {pode('deletepatient') && ( + + )} +
+ +
+ Nenhum paciente encontrado +
+
+ {/* Linha de controles abaixo da tabela */} +
+
+ Total encontrados: {filteredPatients.length} +
+
+ +
+
+
+ +
+
+
+
+
+
+ ); +} + +export default PatientList; \ No newline at end of file diff --git a/src/main.jsx b/src/main.jsx index b509f92..0765cc9 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,81 +1,13 @@ -// src/main.jsx import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; - -import "./assets/css/index.css"; - -// Layouts -import App from "./App.jsx"; // Layout Admin -import DoctorApp from "./pages/DoctorApp/DoctorApp.jsx"; // Layout Médico - -// Páginas Admin -import Patientform from "./pages/Patient/Patientform.jsx"; -import PatientList from "./pages/Patient/PatientList.jsx"; -import Doctorlist from "./pages/Doctor/DoctorList.jsx"; -import DoctorForm from "./pages/Doctor/DoctorForm.jsx"; -import Doctorschedule from "./pages/Schedule/DoctorSchedule.jsx"; -import AddSchedule from "./pages/Schedule/AddSchedule.jsx"; -import Calendar from "./pages/calendar/Calendar.jsx"; -import EditDoctor from "./pages/Doctor/DoctorEdit.jsx"; -import PatientEdit from "./pages/Patient/PatientEdit.jsx"; -import DoctorProfile from "./pages/Doctor/DoctorProfile.jsx"; - - -// Páginas Médico -import DoctorDashboard from "./pages/DoctorApp/DoctorDashboard.jsx"; -import DoctorCalendar from "./pages/DoctorApp/DoctorCalendar.jsx"; -import DoctorPatientList from "./pages/DoctorApp/DoctorPatientList.jsx"; -import AgendaList from "./pages/Agendar/AgendaList.jsx"; -import AgendaForm from "./pages/Agendar/AgendaForm.jsx"; -import AgendaEdit from "./pages/Agendar/AgendaEdit.jsx"; -import LaudoList from "./pages/laudos/LaudosList.jsx" -import Laudo from "./pages/laudos/Laudo.jsx"; - - - +import { router } from "./routes/RoutesApp.jsx"; // Criando o router com todas as rotas -const router = createBrowserRouter([ - // Rotas Admin - { - path: "/", - element: , - children: [ - // Rota inicial do Admin: apenas mostra layout com Navbar e Sidebar - { path: "patient", element: }, - { path: "patientlist", element: }, - { path: "doctorlist", element: }, - { path: "doctorform", element: }, - { path: "doctorschedule", element: }, - { path: "addschedule", element: }, - { path: "calendar", element: }, - { path: "profiledoctor/:id", element: }, - { path: "editdoctor/:id", element: }, - { path: "editpatient/:id", element: }, - { path: "agendaform", element: }, - { path: "agendaedit", element: }, - { path: "agendalist", element: }, - { path: "laudolist", element: }, - { path: "laudo", element: } - ], - }, - // Rotas Médico - { - path: "/doctor", - element: , - children: [ - { index: true, element: }, // Rota inicial médico - { path: "dashboard", element: }, - { path: "calendar", element: }, - { path: "patients", element: }, - ], - }, -]); -// Renderizando a aplicação + createRoot(document.getElementById("root")).render( -); +); \ No newline at end of file diff --git a/src/pages/AdminApp/AdminApp.jsx b/src/pages/AdminApp/AdminApp.jsx new file mode 100644 index 0000000..c72984b --- /dev/null +++ b/src/pages/AdminApp/AdminApp.jsx @@ -0,0 +1,49 @@ +import '../../assets/css/index.css' +import { Link, useLocation } from 'react-router-dom'; +import { useState } from 'react'; +import { Outlet } from 'react-router-dom'; +import { getAccessToken } from '../../utils/auth.js'; +import { getUserRole } from '../../utils/userInfo.js'; +import Sidebar from '../../components/layouts/Sidebar.jsx'; + + + + +function AdminApp() { + const token = getAccessToken(); + const user = getUserRole(); + // Verificação de autenticação + if (!token) { + return ; + } + + // Verificação de role + if (user !== 'admin') { + return ( +
+
+
+

❌ Acesso Negado

+

Apenas administradores podem acessar esta área.

+ +
+
+
+ ); + } + return ( +
+ + +
+ ); +} + +export { AdminApp }; +export default AdminApp; + diff --git a/src/pages/AdminApp/AdminDashboard.jsx b/src/pages/AdminApp/AdminDashboard.jsx new file mode 100644 index 0000000..02c4e77 --- /dev/null +++ b/src/pages/AdminApp/AdminDashboard.jsx @@ -0,0 +1,874 @@ +import React, { useState, useEffect } from "react"; +import { Link } from "react-router-dom"; +import { getAccessToken } from "../../utils/auth.js"; +import "../../assets/css/index.css"; +import Tabs from '@mui/material/Tabs'; +import Tab from '@mui/material/Tab'; +import Box from '@mui/material/Box'; +import { styled } from '@mui/material/styles'; +import { getFullName, getUserId } from "../../utils/userInfo.js"; +// Usando URLs das imagens no public +const AvatarForm = "/img/AvatarForm.jpg"; +const banner = "/img/banner.png"; +import { + BarChart, + Bar, + PieChart, + Pie, + Cell, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer +} from 'recharts'; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip as ChartTooltip, + Legend as ChartLegend, +} from 'chart.js'; +import { Bar as ChartJSBar } from 'react-chartjs-2'; + +// Registrar componentes do Chart.js +ChartJS.register( + CategoryScale, + LinearScale, + BarElement, + Title, + ChartTooltip, + ChartLegend +); + +// Componente do gráfico de consultas mensais +const ConsultasMensaisChart = ({ data }) => ( + + + + + + [`${value} consultas`, 'Total']} + /> + + + + +); + +// Componente do gráfico de pacientes ativos/inativos +const AtivosInativosChart = ({ data }) => ( + + + `${name} ${(percent * 100).toFixed(0)}%`} + outerRadius={120} + fill="#8884d8" + dataKey="value" + > + {data.map((entry, index) => ( + + ))} + + [`${value} pacientes`, name]} + /> + + + +); + +// Componente do gráfico de taxa de cancelamentos +const TaxaCancelamentosChart = ({ data }) => { + + + if (!data || data.length === 0) { + return ( +
+
+ +

Nenhum dado de cancelamentos encontrado

+
+
+ ); + } + + // Preparar dados para Chart.js (gráfico empilhado) + const chartData = { + labels: data.map(item => item.mes), + datasets: [ + { + label: 'Realizadas', + data: data.map(item => item.realizadas), + backgroundColor: '#dee2e6', + borderColor: '#adb5bd', + borderWidth: 1, + borderRadius: 4, + borderSkipped: false, + }, + { + label: 'Canceladas', + data: data.map(item => item.canceladas), + backgroundColor: '#dc3545', + borderColor: '#c82333', + borderWidth: 1, + borderRadius: 4, + borderSkipped: false, + } + ] + }; + + const options = { + responsive: true, + maintainAspectRatio: false, + scales: { + x: { + stacked: true, + grid: { + display: false, + }, + ticks: { + color: '#6c757d', + font: { + size: 12 + } + } + }, + y: { + stacked: true, + beginAtZero: true, + max: 100, + grid: { + color: '#e9ecef', + drawBorder: false, + }, + ticks: { + color: '#6c757d', + font: { + size: 12 + }, + callback: function(value) { + return value + '%'; + } + } + } + }, + plugins: { + legend: { + display: true, + position: 'top', + labels: { + color: '#495057', + font: { + size: 12 + }, + usePointStyle: true, + pointStyle: 'rect' + } + }, + tooltip: { + backgroundColor: '#f8f9fa', + titleColor: '#343a40', + bodyColor: '#343a40', + borderColor: '#dee2e6', + borderWidth: 1, + callbacks: { + label: function(context) { + const datasetLabel = context.dataset.label; + const value = context.parsed.y; + const dataIndex = context.dataIndex; + const monthData = data[dataIndex]; + + if (datasetLabel === 'Canceladas') { + const numConsultas = Math.round(monthData.total * value / 100); + return `${datasetLabel}: ${value}% (${numConsultas} de ${monthData.total} consultas)`; + } else { + const numConsultas = Math.round(monthData.total * value / 100); + return `${datasetLabel}: ${value}% (${numConsultas} consultas)`; + } + }, + title: function(context) { + const monthData = data[context[0].dataIndex]; + return `${context[0].label} ${new Date().getFullYear()} - Total: ${monthData.total} consultas`; + }, + afterBody: function(context) { + const monthData = data[context[0].dataIndex]; + if (monthData.total === 0) { + return ['Nenhuma consulta registrada neste mês']; + } + return []; + } + } + } + }, + animation: { + duration: 1000, + easing: 'easeInOutQuart' + }, + layout: { + padding: { + left: 10, + right: 10, + top: 10, + bottom: 10 + } + } + }; + + return ( +
+ +
+ ); +}; + +// Componente do gráfico de consultas por médico com Chart.js (horizontal) +const ConsultasPorMedicoChart = ({ data }) => { + + + if (!data || data.length === 0) { + return ( +
+
+ +

Nenhum dado de médicos encontrado

+
+
+ ); + } + + // Preparar dados para Chart.js + const chartData = { + labels: data.map(item => item.medico), + datasets: [ + { + label: 'Consultas', + data: data.map(item => item.consultas), + backgroundColor: '#28a745', + borderColor: '#1e7e34', + borderWidth: 1, + borderRadius: 4, + borderSkipped: false, + } + ] + }; + + const options = { + indexAxis: 'y', // Torna o gráfico horizontal + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false, // Ocultar legenda pois é óbvio + }, + tooltip: { + backgroundColor: '#f8f9fa', + titleColor: '#343a40', + bodyColor: '#343a40', + borderColor: '#dee2e6', + borderWidth: 1, + callbacks: { + label: function(context) { + return `${context.parsed.x} consultas`; + } + } + } + }, + scales: { + x: { + beginAtZero: true, + grid: { + color: '#e9ecef', + drawBorder: false, + }, + ticks: { + color: '#6c757d', + font: { + size: 12 + } + } + }, + y: { + grid: { + display: false, + }, + ticks: { + color: '#6c757d', + font: { + size: 11 + }, + maxRotation: 0, + // Remover callback que truncava os nomes - mostrar nomes completos + } + } + }, + animation: { + duration: 1000, + easing: 'easeInOutQuart' + }, + layout: { + padding: { + left: 20, + right: 30, + top: 10, + bottom: 10 + } + }, + elements: { + bar: { + borderRadius: 4, + } + } + }; + + return ( +
+ +
+ ); +}; + +function AdminDashboard() { + const [patients, setPatients] = useState([]); + const [doctors, setDoctors] = useState([]); + const [consulta, setConsulta] = useState([]); + const [countPaciente, setCountPaciente] = useState(0); + const [countMedico, setCountMedico] = useState(0); + // Estados para os gráficos + const [consultasMensaisDataReal, setConsultasMensaisDataReal] = useState([]); + const [pacientesStatusDataReal, setPacientesStatusDataReal] = useState([]); + const [consultasPorMedicoData, setConsultasPorMedicoData] = useState([]); + const [taxaCancelamentosData, setTaxaCancelamentosData] = useState([]); + const [appointments, setAppointments] = useState([]); + const [loading, setLoading] = useState(true); + const [currentTime, setCurrentTime] = useState(new Date()); + const [previewUrl, setPreviewUrl] = useState(AvatarForm); + + const tokenUsuario = getAccessToken(); + const userId = getUserId(); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + + const requestOptions = { + method: "GET", + headers: { + apikey: + supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + }, + redirect: "follow", + }; + + useEffect(() => { + const loadData = async () => { + try { + setLoading(true); + + // Buscar pacientes + const patientsResponse = await fetch( + `${supabaseUrl}/rest/v1/patients`, + requestOptions + ); + const patientsData = await patientsResponse.json(); + const patientsArr = Array.isArray(patientsData) ? patientsData : []; + setPatients(patientsArr); + setConsulta(patientsArr); + setCountPaciente(patientsArr.length); + + // Processar status dos pacientes + if (patientsArr.length > 0) { + const ativos = patientsArr.filter(p => p.active !== false).length; + const inativos = patientsArr.length - ativos; + + const statusData = [ + { name: 'Ativos', value: ativos, color: '#007bff' }, + { name: 'Inativos', value: inativos, color: '#ffa500' } + ]; + + setPacientesStatusDataReal(statusData); + } + + // Buscar médicos + const doctorsResponse = await fetch( + `${supabaseUrl}/rest/v1/doctors`, + requestOptions + ); + const doctorsData = await doctorsResponse.json(); + const doctorsArr = Array.isArray(doctorsData) ? doctorsData : []; + setDoctors(doctorsArr); + setCountMedico(doctorsArr.length); + + // Buscar consultas + const appointmentsResponse = await fetch( + `${supabaseUrl}/rest/v1/appointments`, + requestOptions + ); + const appointmentsData = await appointmentsResponse.json(); + const appointmentsArr = Array.isArray(appointmentsData) ? appointmentsData : []; + setAppointments(appointmentsArr); + + // Processar dados dos gráficos + processConsultasMensais(appointmentsArr); + await processConsultasPorMedico(appointmentsArr, doctorsArr); + processTaxaCancelamentos(appointmentsArr); + + + } catch (error) { + + } finally { + setLoading(false); + } + }; + + loadData(); + }, []); + + // useEffect para atualizar o relógio em tempo real + useEffect(() => { + const timer = setInterval(() => { + setCurrentTime(new Date()); + }, 1000); // Atualiza a cada segundo + + return () => clearInterval(timer); // Limpa o timer quando o componente é desmontado + }, []); + + // useEffect para carregar avatar do usuário (mesma lógica da navbar) + useEffect(() => { + const loadAvatar = async () => { + if (!userId) return; + + const myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + + const requestOptions = { + headers: myHeaders, + method: 'GET', + redirect: 'follow' + }; + + try { + const response = await fetch(`${supabaseUrl}/storage/v1/object/avatars/${userId}/avatar.png`, requestOptions); + + if (response.ok) { + const blob = await response.blob(); + const imageUrl = URL.createObjectURL(blob); + setPreviewUrl(imageUrl); + return; // Avatar encontrado + } + } catch (error) { + + } + + // Se chegou até aqui, não encontrou avatar - mantém o padrão + + }; + + loadAvatar(); + }, [userId]); + + // Processar dados das consultas mensais + const processConsultasMensais = (appointmentsData) => { + const meses = [ + 'Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', + 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez' + ]; + + const consultasPorMes = meses.map(mes => ({ mes, consultas: 0 })); + + if (appointmentsData && appointmentsData.length > 0) { + appointmentsData.forEach(appointment => { + if (appointment.scheduled_at) { + const data = new Date(appointment.scheduled_at); + const mesIndex = data.getMonth(); + if (mesIndex >= 0 && mesIndex < 12) { + consultasPorMes[mesIndex].consultas++; + } + } + }); + } + + + setConsultasMensaisDataReal(consultasPorMes); + }; + + // Processar dados da taxa de cancelamentos + const processTaxaCancelamentos = (appointmentsData) => { + const meses = [ + 'Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', + 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez' + ]; + + const cancelamentosPorMes = meses.map(mes => ({ + mes, + realizadas: 0, + canceladas: 0, + total: 0 + })); + + if (appointmentsData && appointmentsData.length > 0) { + + + appointmentsData.forEach(appointment => { + if (appointment.scheduled_at) { + const data = new Date(appointment.scheduled_at); + const mesIndex = data.getMonth(); + const anoAtual = new Date().getFullYear(); + const anoConsulta = data.getFullYear(); + + // Processar apenas consultas do ano atual + if (mesIndex >= 0 && mesIndex < 12 && anoConsulta === anoAtual) { + cancelamentosPorMes[mesIndex].total++; + + // Verificar diferentes possíveis campos de status de cancelamento + const isCancelled = + appointment.status === 'cancelled' || + appointment.status === 'canceled' || + appointment.cancelled === true || + appointment.is_cancelled === true || + appointment.appointment_status === 'cancelled' || + appointment.appointment_status === 'canceled'; + + if (isCancelled) { + cancelamentosPorMes[mesIndex].canceladas++; + } else { + cancelamentosPorMes[mesIndex].realizadas++; + } + } + } + }); + + // Calcular porcentagens e manter valores absolutos para tooltip + cancelamentosPorMes.forEach(mes => { + if (mes.total > 0) { + const realizadasCount = mes.realizadas; + const canceladasCount = mes.canceladas; + + mes.realizadas = Math.round((realizadasCount / mes.total) * 100); + mes.canceladas = Math.round((canceladasCount / mes.total) * 100); + + // Garantir que soma seja 100% + if (mes.realizadas + mes.canceladas !== 100 && mes.total > 0) { + mes.realizadas = 100 - mes.canceladas; + } + } else { + // Se não há dados, mostrar 100% realizadas + mes.realizadas = 100; + mes.canceladas = 0; + } + }); + + + setTaxaCancelamentosData(cancelamentosPorMes); + } else { + + setTaxaCancelamentosData([]); + } + }; + + // Processar dados das consultas por médico + const processConsultasPorMedico = async (appointmentsData, doctorsData) => { + try { + + + // Criar mapa de médicos + const doctorsMap = {}; + doctorsData.forEach(doctor => { + let doctorName = doctor.full_name || doctor.name || `Médico ${doctor.id}`; + + // Apenas limpar espaços em branco, manter nome completo + doctorName = doctorName.trim(); + + doctorsMap[doctor.id] = doctorName; + }); + + + + // Contar consultas por médico + const consultasPorMedico = {}; + appointmentsData.forEach(appointment => { + + if (appointment.doctor_id) { + const doctorName = doctorsMap[appointment.doctor_id] || `Médico ${appointment.doctor_id}`; + consultasPorMedico[doctorName] = (consultasPorMedico[doctorName] || 0) + 1; + } + }); + + + // Converter para array e ordenar por número de consultas (maior para menor) + const chartData = Object.entries(consultasPorMedico) + .map(([medico, consultas]) => ({ medico, consultas })) + .sort((a, b) => b.consultas - a.consultas) + .slice(0, 10); // Mostrar apenas os top 10 médicos + + + setConsultasPorMedicoData(chartData); + } catch (error) { + setConsultasPorMedicoData([]); + } + }; + + + + + + + return ( +
+
+ {/* Header com informações do admin */} +
+
+
+
+
+
+

👨‍💼 Olá, {getFullName()}!

+

É ótimo tê-lo novamente no MediConnect. Acompanhe o desempenho da sua clínica, mantenha o controle de tudo em um só lugar e continue fazendo-a crescer todos os dias! +

+ + + 🕒 {currentTime.toLocaleString('pt-BR')} + +
+
+ Avatar +
+
+
+
+
+
+ + {/* Cards de estatísticas */} +
+
+
+ + + +
+

{countPaciente}

+ Total Pacientes +
+
+
+ +
+
+ + + +
+

{countMedico}

+ Total Médicos +
+
+
+ +
+
+ + + +
+

{appointments.length}

+ Total Consultas +
+
+
+ +
+
+ + + +
+

80

+ Atendidos +
+
+
+
+ + {/* Seção dos Gráficos */} +
+ {/* Consultas por Mês */} +
+
+
+

📊 Consultas por Mês ({new Date().getFullYear()})

+
+
+ {loading ? ( +
+
+ +

Carregando dados...

+
+
+ ) : consultasMensaisDataReal.length > 0 ? ( + + ) : ( +
+
+ +

Nenhum dado encontrado

+
+
+ )} +
+
+
+ + {/* Top 10 Médicos */} +
+
+
+

🏆 Top 10 Médicos (Consultas)

+
+
+ {loading ? ( +
+
+ +

Carregando dados...

+
+
+ ) : ( + + )} +
+
+
+
+ +
+ {/* Pacientes Ativos/Inativos */} +
+
+
+

👥 Pacientes Ativos x Inativos

+
+
+ {loading ? ( +
+
+ +

Carregando dados...

+
+
+ ) : pacientesStatusDataReal.length > 0 ? ( + + ) : ( +
+
+ +

Nenhum dado encontrado

+
+
+ )} +
+
+
+ + {/* Taxa de Cancelamentos */} +
+
+
+

📉 Taxa de Cancelamentos

+
+
+ {loading ? ( +
+
+ +

Carregando dados...

+
+
+ ) : ( + + )} +
+
+
+
+
+
+ ); +} + +// CSS customizado para o AdminDashboard (mesmo estilo do PatientDashboard) +const style = document.createElement('style'); +style.textContent = ` + .user-info-banner { + position: relative; + overflow: hidden; + } + + .user-info-banner::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: url('data:image/svg+xml,'); + pointer-events: none; + } + + .dash-widget { + transition: transform 0.2s ease; + } + + .dash-widget:hover { + transform: translateY(-3px); + } + + .card { + transition: transform 0.2s ease, box-shadow 0.2s ease; + } + + .card:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0,0,0,0.15) !important; + } +`; + +if (!document.head.querySelector('[data-admin-dashboard-styles]')) { + style.setAttribute('data-admin-dashboard-styles', 'true'); + document.head.appendChild(style); +} + +export default AdminDashboard; \ No newline at end of file diff --git a/src/pages/AdminApp/CreateUser.jsx b/src/pages/AdminApp/CreateUser.jsx new file mode 100644 index 0000000..35cbfd8 --- /dev/null +++ b/src/pages/AdminApp/CreateUser.jsx @@ -0,0 +1,711 @@ +import { useEffect, useState } from "react"; +import { getAccessToken } from "../../utils/auth"; +import Swal from 'sweetalert2'; + + +function CreateUser() { + const tokenUsuario = getAccessToken() + const [search, setSearch] = useState(""); + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + const [showModal, setShowModal] = useState(false); + const [roleFilter, setRoleFilter] = useState(""); + const [period, setPeriod] = useState(""); + const [startDate, setStartDate] = useState(""); + const [endDate, setEndDate] = useState(""); + const [formData, setFormData] = useState({ + full_name: "", + email: "", + phone: "", + cpf: "", + role: "secretaria", + password: "" + }); + const [submitting, setSubmitting] = useState(false); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + + const getHeaders = () => { + const token = getAccessToken(); + return { + "apikey": supabaseAK, + "Authorization": `Bearer ${token}`, + "Content-Type": "application/json", + }; + }; + + const formatDate = (isoString) => { + if (!isoString) return "-"; + const date = new Date(isoString); + return date.toLocaleString("pt-BR", { + day: "2-digit", + month: "2-digit", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }); + }; + + const fetchUsersAndRoles = async () => { + try { + const headers = getHeaders(); + + // Buscar perfis + const resProfiles = await fetch( + `${supabaseUrl}/rest/v1/profiles`, + { method: "GET", headers } + ); + if (!resProfiles.ok) throw new Error("Erro ao buscar perfis"); + const profiles = await resProfiles.json(); + + // Buscar roles dos usuários + const resRoles = await fetch( + `${supabaseUrl}/rest/v1/user_roles`, + { method: "GET", headers } + ); + if (!resRoles.ok) throw new Error("Erro ao buscar roles"); + const roles = await resRoles.json(); + + // Merge profiles com roles + const merged = profiles.map((profile) => { + const userRoles = roles.filter((r) => r.user_id === profile.id); + const cargos = userRoles.length > 0 ? userRoles.map(r => r.role).join(", ") : "Sem cargo"; + + return { + ...profile, + role: cargos, + }; + }); + + setUsers(merged); + } catch (err) { + console.error("Erro ao carregar usuários e roles:", err); + Swal.fire({ + title: "Erro!", + text: "Erro ao carregar usuários", + icon: "error", + }); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchUsersAndRoles(); + }, []); + + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData(prev => ({ + ...prev, + [name]: value + })); + }; + + const handleCreateUser = async (e) => { + e.preventDefault(); + setSubmitting(true); + + try { + // headers + const myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + myHeaders.append("Content-Type", "application/json"); + + // validações básicas + if (!formData.email || !formData.password || !formData.full_name) { + Swal.fire("Campos obrigatórios", "Preencha nome, e-mail e senha.", "warning"); + setSubmitting(false); + return; + } + if (formData.password.length < 6) { + Swal.fire("Senha inválida", "A senha deve ter pelo menos 6 caracteres.", "warning"); + setSubmitting(false); + return; + } + if (!formData.role || formData.role.trim() === "") { + Swal.fire("Cargo obrigatório", "Selecione um cargo para o usuário.", "warning"); + setSubmitting(false); + return; + } + + const role = (formData.role || "").toString().trim(); // garante string exata + + // payload 1: role como string (conforme docs) + const payload1 = { + email: formData.email.trim(), + password: formData.password, + full_name: formData.full_name.trim(), + phone: formData.phone || "", + cpf: formData.cpf || "", + role: role, + ...(role === "paciente" && { + create_patient_record: true, + phone_mobile: formData.phone || "" + }) + }; + + console.log("Tentando criar usuário (payload1):", payload1); + + let response = await fetch( + `${supabaseUrl}/functions/v1/create-user-with-password`, + { + method: "POST", + headers: myHeaders, + body: JSON.stringify(payload1) + } + ); + + // tenta ler resposta (json se possível, senão texto) + let result; + try { + result = await response.json(); + } catch (err) { + result = await response.text(); + } + console.log("Resposta (payload1):", response.status, result); + + // Se OK, finaliza + if (response.ok) { + Swal.fire({ + title: "Sucesso!", + html: ` +
+

Usuário criado com sucesso!

+

Nome: ${result.user?.full_name || formData.full_name}

+

Email: ${result.user?.email || formData.email}

+

Cargo: ${role}

+

Telefone: ${formData.phone || "Não informado"}

+
+ `, + icon: "success", + }); + + setShowModal(false); + setFormData({ full_name: "", email: "", phone: "", role: "secretaria", password: "" }); + await fetchUsersAndRoles(); + return; + } + + // Se 400 e menciona role ou resposta indicar role inválida, tenta reenviar usando roles: [role] + const errMsg = typeof result === "string" ? result : JSON.stringify(result); + const mentionsRole = /role|roles|invalid role|role inválida|role not allowed/i.test(errMsg); + + if ((response.status === 400 || response.status === 422) && mentionsRole) { + const payload2 = { + email: formData.email.trim(), + password: formData.password, + full_name: formData.full_name.trim(), + phone: formData.phone || "", + cpf: formData.cpf || "", + roles: [role], // tentativa alternativa + ...(role === "paciente" && { + create_patient_record: true, + phone_mobile: formData.phone || "" + }) + }; + + console.log("Servidor rejeitou role. Tentando payload2 (roles array):", payload2); + + const response2 = await fetch( + `${supabaseUrl}/functions/v1/create-user-with-password`, + { + method: "POST", + headers: myHeaders, + body: JSON.stringify(payload2) + } + ); + + let result2; + try { + result2 = await response2.json(); + } catch (err) { + result2 = await response2.text(); + } + console.log("Resposta (payload2):", response2.status, result2); + + if (response2.ok) { + Swal.fire("Sucesso!", result2.message || "Usuário criado com sucesso!", "success"); + setShowModal(false); + setFormData({ full_name: "", email: "", phone: "", role: "secretaria", password: "" }); + await fetchUsersAndRoles(); + return; + } else { + // falha na segunda tentativa — mostra detalhe + const detail = typeof result2 === "string" ? result2 : JSON.stringify(result2); + throw new Error(detail || "Erro ao criar usuário (tentativa com roles array falhou)"); + } + } + + // Se não é erro de role ou tentativas falharam, lança o erro original + throw new Error(errMsg || "Erro ao criar usuário"); + } catch (err) { + console.error("Erro ao criar usuário:", err); + Swal.fire({ + title: "Erro!", + text: err.message || "Falha ao criar usuário", + icon: "error", + }); + } finally { + setSubmitting(false); + } + }; + + + // Função para definir períodos e limpar datas + const handlePeriodChange = (newPeriod) => { + // Se clicar no mesmo período, limpa o filtro + if (period === newPeriod) { + setPeriod(""); + } else { + setPeriod(newPeriod); + } + + // Sempre limpa as datas específicas + setStartDate(""); + setEndDate(""); + }; + + const openCreateModal = () => setShowModal(true); + const closeModal = () => { + setShowModal(false); + setFormData({ + full_name: "", + email: "", + phone: "", + cpf: "", + role: "secretaria", + password: "" + }); + }; + + const filteredUsers = users.filter(p => { + if (!p) return false; + const nome = (p.full_name || "").toLowerCase(); + const cpf = (p.cpf || "").toLowerCase(); + const email = (p.email || "").toLowerCase(); + const q = search.toLowerCase(); + + // Filtro por texto (nome, cpf, email) + const matchesText = nome.includes(q) || cpf.includes(q) || email.includes(q); + + // Filtro por cargo + const matchesRole = !roleFilter || (p.role || "").toLowerCase().includes(roleFilter.toLowerCase()); + + let dateMatch = true; + if (p.created_at) { + const userDate = new Date(p.created_at); + const now = new Date(); + + // Filtros por período + if (period === "today") { + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const tomorrow = new Date(today); + tomorrow.setDate(tomorrow.getDate() + 1); + dateMatch = userDate >= today && userDate < tomorrow; + } else if (period === "week") { + const weekStart = new Date(now); + weekStart.setDate(now.getDate() - now.getDay()); + weekStart.setHours(0, 0, 0, 0); + dateMatch = userDate >= weekStart; + } else if (period === "month") { + const monthStart = new Date(now.getFullYear(), now.getMonth(), 1); + dateMatch = userDate >= monthStart; + } + + // Filtros por data específica + if (startDate && endDate) { + const start = new Date(startDate); + const end = new Date(endDate); + end.setHours(23, 59, 59, 999); // Inclui o dia inteiro + dateMatch = dateMatch && userDate >= start && userDate <= end; + } else if (startDate) { + const start = new Date(startDate); + dateMatch = dateMatch && userDate >= start; + } else if (endDate) { + const end = new Date(endDate); + end.setHours(23, 59, 59, 999); + dateMatch = dateMatch && userDate <= end; + } + } + + + return matchesText && matchesRole && dateMatch; + }); + + const [itemsPerPage1, setItemsPerPage1] = useState(15); + const [currentPage1, setCurrentPage1] = useState(1); + const indexOfLastPatient = currentPage1 * itemsPerPage1; + const indexOfFirstPatient = indexOfLastPatient - itemsPerPage1; + const currentUsers = filteredUsers.slice(indexOfFirstPatient, indexOfLastPatient); + const totalPages1 = Math.ceil(filteredUsers.length / itemsPerPage1); + + + useEffect(() => { + setCurrentPage1(1); + }, [search, roleFilter, period, startDate, endDate]); + + if (loading) return

Carregando usuários...

; + return ( +
+
+
+

Lista de Usuários

+ +
+ {/* Todos os filtros em uma única linha */} +
+ {/* Campo de busca */} + setSearch(e.target.value)} + style={{ minWidth: "300px", maxWidth: "450px", }} + /> + + {/* Filtro por cargo */} + + + {/* Filtro De */} +
+ + { + setStartDate(e.target.value); + if (e.target.value) setPeriod(""); + }} + /> +
+ + {/* Filtro Até */} +
+ + { + setEndDate(e.target.value); + if (e.target.value) setPeriod(""); + }} + /> +
+ + {/* Botões rápidos */} + + + +
+ +
+
+
+ + + + + + + + + + + + + {currentUsers.length > 0 ? ( + currentUsers.map((user) => ( + + + + + + + + + )) + ) : ( + + + + )} + +
NomeEmailTelefoneCargosUser IDCriado em
{user.full_name || "-"}{user.email || "-"}{user.phone || "-"} + {(() => { + if (!user.role || user.role === "Sem cargo") { + return ( + + + Sem cargo + + ); + } + + const rolesArray = user.role.split(', ').map(r => r.trim()); + const roleMap = { + 'admin': { icon: 'fa fa-shield', label: 'Admin', color: 'status-red' }, + 'medico': { icon: 'fa fa-stethoscope', label: 'Médico', color: 'status-purple' }, + 'gestor': { icon: 'fa fa-briefcase', label: 'Gestor', color: 'status-blue' }, + 'secretaria': { icon: 'fa fa-phone', label: 'Secretaria', color: 'status-orange' }, + 'paciente': { icon: 'fa fa-user', label: 'Paciente', color: 'status-green' }, + 'user': { icon: 'fa fa-user-circle', label: 'User', color: 'status-pink' } + }; + + return ( +
+ {rolesArray.map((role, index) => { + const roleInfo = roleMap[role.toLowerCase()] || { icon: 'fa-question-circle', label: role, color: 'status-gray' }; + return ( + + + {roleInfo.label} + + ); + })} +
+ ); + })()} +
{user.id}{formatDate(user.created_at)}
Nenhum usuário encontrado
+
+
+
+ Total encontrados: {filteredUsers.length} +
+
+ +
+
+
+ +
+
+
+
+ + {showModal && ( +
+
+
+
+
Criar Novo Usuário
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ + +
+
+
+
+
+ )} +
+ ); +} + +export default CreateUser; diff --git a/src/pages/AdminApp/Doctorexceçao.jsx b/src/pages/AdminApp/Doctorexceçao.jsx new file mode 100644 index 0000000..395c475 --- /dev/null +++ b/src/pages/AdminApp/Doctorexceçao.jsx @@ -0,0 +1,339 @@ +import React, { useEffect, useMemo, useState } from "react"; +import FullCalendar from "@fullcalendar/react"; +import dayGridPlugin from "@fullcalendar/daygrid"; +import interactionPlugin from "@fullcalendar/interaction"; +import ptBrLocale from "@fullcalendar/core/locales/pt-br"; +import Swal from "sweetalert2"; +import { getAccessToken } from "../../utils/auth.js"; + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + +const API_ROOT = `${supabaseUrl}/rest/v1`; +const API_URL = `${API_ROOT}/doctor_exceptions`; +const API_DOCTORS = `${API_ROOT}/doctors?select=id,full_name`; +const API_KEY = supabaseAK; + +export default function Doctorexceçao() { + const token = getAccessToken(); + + const [exceptions, setExceptions] = useState([]); + const [doctors, setDoctors] = useState([]); + const [loading, setLoading] = useState(true); + const [err, setErr] = useState(""); + + // ---------- CONFIGURAÇÕES COMUNS ---------- + const commonHeaders = { + apikey: API_KEY, + Authorization: `Bearer ${token}`, + }; + + // ---------- CARREGAR DADOS ---------- + const loadExceptions = async () => { + try { + setLoading(true); + setErr(""); + const res = await fetch(`${API_URL}?select=*`, { headers: commonHeaders }); + if (!res.ok) throw new Error(await res.text()); + const data = await res.json(); + setExceptions(Array.isArray(data) ? data : []); + } catch (e) { + setErr(e.message || "Erro ao carregar exceções"); + } finally { + setLoading(false); + } + }; + + const loadDoctors = async () => { + try { + const res = await fetch(API_DOCTORS, { headers: commonHeaders }); + if (!res.ok) throw new Error(await res.text()); + const data = await res.json(); + setDoctors(Array.isArray(data) ? data : []); + } catch { + setDoctors([]); + } + }; + + useEffect(() => { + loadDoctors(); + loadExceptions(); + }, [token]); + + // ---------- CRIAR EXCEÇÃO ---------- + const createException = async (payload) => { + try { + const body = { + ...payload, + created_by: payload.created_by || payload.doctor_id, + }; + + const res = await fetch(API_URL, { + method: "POST", + headers: { + ...commonHeaders, + "Content-Type": "application/json", + Prefer: "return=representation", + }, + body: JSON.stringify(body), + }); + + if (!res.ok) throw new Error(await res.text()); + await res.json(); + await loadExceptions(); + Swal.fire("Sucesso!", "Exceção criada com sucesso.", "success"); + } catch (e) { + Swal.fire("Erro ao criar", e.message || "Falha ao criar exceção", "error"); + } + }; + + // ---------- DELETAR EXCEÇÃO ---------- + const deleteException = async (id) => { + const confirm = await Swal.fire({ + title: "Excluir exceção?", + text: "Essa ação não pode ser desfeita.", + icon: "warning", + showCancelButton: true, + confirmButtonText: "Sim, excluir", + cancelButtonText: "Cancelar", + }); + if (!confirm.isConfirmed) return; + + try { + const res = await fetch(`${API_URL}?id=eq.${id}`, { + method: "DELETE", + headers: commonHeaders, + }); + if (!res.ok) throw new Error(await res.text()); + await loadExceptions(); + Swal.fire("Removida!", "Exceção excluída com sucesso.", "success"); + } catch (e) { + Swal.fire("Erro ao excluir", e.message || "Falha ao excluir", "error"); + } + }; + + // ---------- EVENTOS DO CALENDÁRIO ---------- + const events = useMemo(() => { + return exceptions.map((ex) => { + const isBlock = ex.kind === "bloqueio"; + return { + id: ex.id, + title: isBlock ? "Bloqueio" : "Liberação", + start: ex.date, + allDay: true, + backgroundColor: isBlock ? "#ef4444" : "#22c55e", + borderColor: isBlock ? "#b91c1c" : "#15803d", + textColor: "#fff", + }; + }); + }, [exceptions]); + + // ---------- HANDLERS ---------- + const handleDateClick = async (info) => { + if (!doctors.length) { + Swal.fire("Sem médicos", "Cadastre médicos antes de criar exceções.", "info"); + return; + } + + // 1️⃣ Selecionar médico + const doctorOptions = doctors.reduce((acc, d) => { + acc[d.id] = d.full_name || d.id; + return acc; + }, {}); + const s1 = await Swal.fire({ + title: `Nova exceção — ${info.dateStr}`, + input: "select", + inputOptions: doctorOptions, + inputPlaceholder: "Selecione o médico", + showCancelButton: true, + confirmButtonText: "Continuar", + didOpen: (popup) => { + popup.style.position = "fixed"; + popup.style.top = "230px"; + } + }); + if (!s1.isConfirmed || !s1.value) return; + const doctor_id = s1.value; + + // 2️⃣ Tipo da exceção + const s2 = await Swal.fire({ + title: "Tipo de exceção", + input: "select", + inputOptions: { + bloqueio: "Bloqueio (remover horários)", + liberacao: "Liberação (adicionar horários extras)", + }, + inputPlaceholder: "Selecione o tipo", + showCancelButton: true, + confirmButtonText: "Continuar", + didOpen: (popup) => { + popup.style.position = "fixed"; + popup.style.top = "230px"; + } + }); + if (!s2.isConfirmed || !s2.value) return; + const kind = s2.value; + + // 3️⃣ Motivo + const form = await Swal.fire({ + title: "Motivo (opcional)", + input: "text", + inputPlaceholder: "Ex: Congresso, folga, manutenção...", + showCancelButton: true, + confirmButtonText: "Criar exceção", + didOpen: (popup) => { + popup.style.position = "fixed"; + popup.style.top = "230px"; + } + }); + if (!form.isConfirmed) return; + + const payload = { + doctor_id, + created_by: doctor_id, + date: info.dateStr, + kind, + reason: form.value || null, + }; + + await createException(payload); + }; + + const handleEventClick = async (info) => { + const e = exceptions.find((x) => x.id === info.event.id); + if (!e) return; + await Swal.fire({ + title: e.kind === "bloqueio" ? "Bloqueio" : "Liberação", + html: `Médico: ${ + doctors.find((d) => d.id === e.doctor_id)?.full_name || e.doctor_id + }
+ Data: ${e.date}
+ Motivo: ${e.reason || "-"}`, + icon: "info", + showCancelButton: true, + confirmButtonText: "Excluir", + cancelButtonText: "Fechar", + }).then((r) => { + if (r.isConfirmed) deleteException(e.id); + }); + }; + + // ---------- UI ---------- + return ( +
+
+
+
+

Exceções (Bloqueios / Liberações)

+ + Clique numa data para adicionar exceções por médico + +
+
+
+ + {/* Calendário */} +
+
+
+ {loading ? ( +

Carregando calendário…

+ ) : err ? ( +

Erro: {err}

+ ) : ( + + )} +
+
+
+ + {/* Lista de exceções */} +
+
+
+
+
Lista de Exceções
+ {exceptions.length} registro(s) +
+ +
+ {loading ? ( +

Carregando lista…

+ ) : err ? ( +

Erro: {err}

+ ) : exceptions.length === 0 ? ( +

Nenhuma exceção encontrada.

+ ) : ( +
+ + + + + + + + + + + + {exceptions.map((ex) => ( + + + + + + + + ))} + +
MédicoDataTipoMotivoAções
+ {doctors.find((d) => d.id === ex.doctor_id)?.full_name || + ex.doctor_id} + {ex.date} + {ex.kind === "bloqueio" ? ( + Bloqueio + ) : ( + Liberação + )} + {ex.reason || "-"} + +
+
+ )} +
+
+

+ * Vermelho = Bloqueio,  + Verde = Liberação. +

+
+
+
+ + ); +} \ No newline at end of file diff --git a/src/pages/calendar/Calendar.jsx b/src/pages/AdminApp/calendar/Calendar.jsx similarity index 100% rename from src/pages/calendar/Calendar.jsx rename to src/pages/AdminApp/calendar/Calendar.jsx diff --git a/src/pages/Agendar/AgendaEdit.jsx b/src/pages/Agendar/AgendaEdit.jsx deleted file mode 100644 index e9a0803..0000000 --- a/src/pages/Agendar/AgendaEdit.jsx +++ /dev/null @@ -1,213 +0,0 @@ -import { useState, useEffect } from "react"; -import { withMask } from "use-mask-input"; -import { Link } from "react-router-dom"; -import "../../assets/css/index.css"; - -function AgendaEdit() { - const [minDate, setMinDate] = useState(""); - - useEffect(() => { - const getToday = () => { - const today = new Date(); - const offset = today.getTimezoneOffset(); - today.setMinutes(today.getMinutes() - offset); - return today.toISOString().split("T")[0]; - }; - - setMinDate(getToday()); - }, []); - - return ( -
-
-
-
-
-

Editar consulta

-
-

Informações do paciente

-
-
-
-
-
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
- -
- -
-
- -
-
- -
-
-
-
-

Informações do atendimento

-
-
-
- - -
-
- -
-
- - -
-
-
- -
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
- - -
- -
- -
- - -
-
- - -
-
- -
- - - -
-
-
-
-
-
-
- ); -} -export default AgendaEdit; \ No newline at end of file diff --git a/src/pages/Agendar/AgendaForm.jsx b/src/pages/Agendar/AgendaForm.jsx deleted file mode 100644 index 1d365ff..0000000 --- a/src/pages/Agendar/AgendaForm.jsx +++ /dev/null @@ -1,213 +0,0 @@ -import { useState, useEffect } from "react"; -import { withMask } from "use-mask-input"; -import { Link } from "react-router-dom"; -import "../../assets/css/index.css"; - -function AgendaForm() { - const [minDate, setMinDate] = useState(""); - - useEffect(() => { - const getToday = () => { - const today = new Date(); - const offset = today.getTimezoneOffset(); - today.setMinutes(today.getMinutes() - offset); - return today.toISOString().split("T")[0]; - }; - - setMinDate(getToday()); - }, []); - - return ( -
-
-
-
-
-

Nova consulta

-
-

Informações do paciente

-
-
-
-
-
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
- -
- -
-
- -
-
- -
-
-
-
-

Informações do atendimento

-
-
-
- - -
-
- -
-
- - -
-
-
- -
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
- - -
- -
- -
- - -
-
- - -
-
- -
- - - -
-
-
-
-
-
-
- ); -} -export default AgendaForm; \ No newline at end of file diff --git a/src/pages/Agendar/AgendaList.jsx b/src/pages/Agendar/AgendaList.jsx deleted file mode 100644 index 604af3b..0000000 --- a/src/pages/Agendar/AgendaList.jsx +++ /dev/null @@ -1,231 +0,0 @@ -import "../../assets/css/index.css" -import { Link } from "react-router-dom"; -import { useState, useEffect, useRef, useLayoutEffect } from "react"; -import { createPortal } from "react-dom"; - -function DropdownPortal({ anchorEl, isOpen, onClose, className, children }) { - const menuRef = useRef(null); - const [stylePos, setStylePos] = useState({ - position: "absolute", - top: 0, - left: 0, - visibility: "hidden", - zIndex: 1000, - }); - - // Posiciona o menu após renderar (medir tamanho do menu) - useLayoutEffect(() => { - if (!isOpen) return; - if (!anchorEl || !menuRef.current) return; - - const anchorRect = anchorEl.getBoundingClientRect(); - const menuRect = menuRef.current.getBoundingClientRect(); - const scrollY = window.scrollY || window.pageYOffset; - const scrollX = window.scrollX || window.pageXOffset; - - // tenta alinhar à direita do botão (como dropdown-menu-right) - let left = anchorRect.right + scrollX - menuRect.width; - let top = anchorRect.bottom + scrollY; - - // evita sair da esquerda da tela - if (left < 0) left = scrollX + 4; - // se extrapolar bottom, abre para cima - if (top + menuRect.height > window.innerHeight + scrollY) { - top = anchorRect.top + scrollY - menuRect.height; - } - setStylePos({ - position: "absolute", - top: `${Math.round(top)}px`, - left: `${Math.round(left)}px`, - visibility: "visible", - zIndex: 1000, - }); - }, [isOpen, anchorEl, children]); - - // fecha ao clicar fora / ao rolar - useEffect(() => { - if (!isOpen) return; - function handleDocClick(e) { - const menu = menuRef.current; - if (menu && !menu.contains(e.target) && anchorEl && !anchorEl.contains(e.target)) { - onClose(); - } - } - function handleScroll() { - onClose(); - } - document.addEventListener("mousedown", handleDocClick); - // captura scroll em qualquer elemento (true) - document.addEventListener("scroll", handleScroll, true); - return () => { - document.removeEventListener("mousedown", handleDocClick); - document.removeEventListener("scroll", handleScroll, true); - }; - }, [isOpen, onClose, anchorEl]); - - if (!isOpen) return null; - return createPortal( -
e.stopPropagation()} - > - {children} -
, - document.body - ); -} - -function AgendaList() { - const [openDropdown, setOpenDropdown] = useState(null); - const anchorRefs = useRef({}); - - return ( -
-
-
-
-
-

Lista de consultas

- -
-
-
- - Adicionar consulta - -
-
- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
ID da cosultaNome do PacienteIdadeNome do médicoEspecialidadeData da consultaHora da consultaStatusAção
APT0001João Miguel18Davi AndradeCardiologista25 Set 202510:00am - 11:00am - - Ativo - - -
- - - setOpenDropdown(null)} - className="dropdown-menu dropdown-menu-right show" - > - {/* { - e.stopPropagation(); - setOpenDropdown(null); - }} - > - Ver Detalhes - */} - - { - e.stopPropagation(); - setOpenDropdown(null); - }} - > - Editar - - - - -
- -
-
-
-
-
- - {/* Modal delete */} - - -
-
- ); -} - -export default AgendaList; \ No newline at end of file diff --git a/src/pages/Doctor/DoctorEdit.jsx b/src/pages/Doctor/DoctorEdit.jsx deleted file mode 100644 index bd118eb..0000000 --- a/src/pages/Doctor/DoctorEdit.jsx +++ /dev/null @@ -1,422 +0,0 @@ -import "../../assets/css/index.css" -import { withMask } from "use-mask-input"; -import { useState } from "react"; -import supabase from "../../Supabase" -import { Link } from "react-router-dom"; -import { useParams } from "react-router-dom"; -import { useEffect } from "react"; - -function EditDoctor() { - const [doctors, setdoctors] = useState([]); - - const {id} = useParams() - useEffect(() => { - const fetchDoctors = async () => { - const {data, error} = await supabase - .from('Doctor') - .select('*') - .eq ('id', id) - .single() - if(error){ - console.error("Erro ao buscar pacientes:", error); - }else{ - setdoctors(data); - } - }; - fetchDoctors(); - } , []); - - - const handleChange = (e) => { - const { name, value } = e.target; - setdoctors((prev) => ({ - ...prev, - [name]: value - })); - } - const handleEdit = async (e) => { - const { data, error } = await supabase - .from("Doctor") - .update([doctors]) - .eq ('id', id) - .single() - - }; - - const buscarCep = (e) => { - const cep = doctors.cep.replace(/\D/g, ''); - console.log(cep); - fetch(`https://viacep.com.br/ws/${cep}/json/`) - .then(response => response.json()) - .then(data => { - console.log(data) - // salvando os valores para depois colocar nos inputs - setValuesFromCep(data) - // estou salvando os valoeres no patientData - setdoctors((prev) => ({ - ...prev, - cidade: data.localidade || '', - estado: data.estado || '', - logradouro: data.logradouro || "", - bairro: data.bairro || '', - })); - }) - } - const setValuesFromCep = (data) => { - document.getElementById('cidade').value = data.localidade || ''; - document.getElementById('estado').value = data.uf || ''; - document.getElementById('logradouro').value= data.logradouro || ''; - document.getElementById('bairro').value= data.bairro || ''; - } - return ( -
- {/* FORMULÁRIO*/} -
-
-
-
-

Editar Médico

-
-
-
-
-
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
- - -
-
-
- - -
-
- -
-
- - -
-
-
-
- - -
-
-
-
- -
- -
-
-
-
-
- - -
-
-
-
- -
- -
-
- -
-
- -
-
-
-
-
-

Endereço

-
-
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
-
-
-
- -
-
- -
-
- -
-
-
-
-
-
- -
-
- -
-
- -
-
-
-
-
- - -
-
- -
- - -
-
- - -
-
-
- -
-
-
-
-
-
-
- ); -} -export default EditDoctor diff --git a/src/pages/Doctor/DoctorForm.jsx b/src/pages/Doctor/DoctorForm.jsx deleted file mode 100644 index 70cbbdf..0000000 --- a/src/pages/Doctor/DoctorForm.jsx +++ /dev/null @@ -1,448 +0,0 @@ -import "../../assets/css/index.css" -import { withMask } from "use-mask-input"; -import { useState } from "react"; -import supabase from "../../Supabase" -import { Link } from "react-router-dom"; -import { useNavigate } from "react-router-dom"; - -function DoctorForm() { - - const [doctorData, setdoctorData] = useState({ - nome: "", - sobrenome: "", - cpf: "", - crm: "", - senha: "", - confirmarsenha: "", - email: "", - data_nascimento: "", - telefone: "", - sexo: "", - endereco: "", - numero: "", - cidade: "", - estado: "", - cep: "", - biografia: "", - status: "inativo", - especialidade: "", - bairro:"", - referencia:"", - logradouro:"", - complemento:"" - }); - - const handleChange = (e) => { - const { name, value } = e.target; - setdoctorData((prev) => ({ - ...prev, - [name]: value - })); - } - const navigate = useNavigate(); - - const handleSubmit = async (e) => { - e.preventDefault(); - const requiredFields = ["nome","cpf","crm","senha","confirmarsenha","data_nascimento","sexo","cep","logradouro","numero","bairro","cidade","estado","especialidade","email","telefone","data_nascimento"]; - const missing = requiredFields.filter(f => !doctorData[f] || doctorData[f].toString().trim() === ""); - if (missing.length > 0) { - alert("Preencha todos os campos obrigatórios."); - return; - } - - // Verificar se senha e confirmarSenha são iguais - if (doctorData.senha !== doctorData.confirmarsenha) { - alert("Senha e Confirmar Senha não coincidem."); - return; - } - const { data, error } = await supabase - .from("Doctor") - .insert([doctorData]); - - if (error) { - console.error("Erro ao cadastrar doutor:", error); - alert(`Erro ao cadastrar doutor: ${error.message}`); - } else { - alert("Doutor cadastrado com sucesso!"); - navigate("/doctorlist"); - } - }; - - const buscarCep = (e) => { - const cep = doctorData.cep.replace(/\D/g, ''); - console.log(cep); - fetch(`https://viacep.com.br/ws/${cep}/json/`) - .then(response => response.json()) - .then(data => { - console.log(data) - // salvando os valores para depois colocar nos inputs - setValuesFromCep(data) - // estou salvando os valoeres no patientData - setdoctorData((prev) => ({ - ...prev, - cidade: data.localidade || '', - estado: data.estado || '', - logradouro: data.logradouro || "", - bairro: data.bairro || '', - })); - }) - } - const setValuesFromCep = (data) => { - document.getElementById('cidade').value = data.localidade || ''; - document.getElementById('estado').value = data.uf || ''; - document.getElementById('logradouro').value= data.logradouro || ''; - document.getElementById('bairro').value= data.bairro || ''; - } - return ( -
- {/* FORMULÁRIO*/} -
-
-
-
-

Cadastrar Médico

-
-
-
-
-
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
- - -
-
-
- - -
-
- -
-
- - -
-
-
-
- - -
-
-
-
- -
- -
-
-
-
-
- - -
-
-
-
- -
- -
-
- -
-
- -
-
-
-
-
-

Endereço

-
-
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
-
-
-
- -
-
- -
-
- -
-
-
-
-
-
- -
-
- -
-
- -
-
-
-
-
- - -
-
- -
- - -
-
- - -
-
-
- -
-
-
-
-
-
-
- ); -} -export default DoctorForm diff --git a/src/pages/Doctor/DoctorList.jsx b/src/pages/Doctor/DoctorList.jsx deleted file mode 100644 index 74d3472..0000000 --- a/src/pages/Doctor/DoctorList.jsx +++ /dev/null @@ -1,142 +0,0 @@ -import "../../assets/css/index.css"; -import { useState, useEffect } from "react"; -import { Link } from "react-router-dom"; -import supabase from "../../Supabase"; - -function Doctors() { - const [doctors, setDoctors] = useState([]); - const [openDropdown, setOpenDropdown] = useState(null); - - useEffect(() => { - const fetchDoctors = async () => { - const { data, error } = await supabase - .from("Doctor") - .select("*"); - if (error) { - console.error("Erro ao buscar pacientes:", error); - } else { - setDoctors(data); - } - }; - fetchDoctors(); - }, []); - - const handleDelete = async (id) => { - if (window.confirm("Tem certeza que deseja excluir este médico?")) { - const { error } = await supabase.from("Doctor").delete().eq("id", id); - if (error) console.error("Erro ao deletar médico:", error); - else setDoctors(doctors.filter((doc) => doc.id !== id)); - } - }; - - return ( -
-
-
-
-

Médicos

-
-
- - Adicionar Médico - -
-
- -
- {doctors.map((doctor) => ( -
-
-
-
- -
-
- - {/* Dropdown estilizado */} -
- - - {openDropdown === doctor.id && ( -
- {/* Ver Detalhes */} - e.stopPropagation()} - > - Ver Detalhes - - {/* Edit */} - - Editar - - - {/* Delete */} - -
- )} -
- -

- - {doctor.nome} {doctor.sobrenome} - -

-
{doctor.especialidade}
-
- {doctor.cidade} -
-
-
- ))} -
-
- - {/* Modal delete (não alterado) */} - -
- ); -} - -export default Doctors; diff --git a/src/pages/Doctor/DoctorProfile.jsx b/src/pages/Doctor/DoctorProfile.jsx deleted file mode 100644 index c5851b2..0000000 --- a/src/pages/Doctor/DoctorProfile.jsx +++ /dev/null @@ -1,127 +0,0 @@ -import "../../assets/css/index.css"; -import supabase from "../../Supabase"; -import { useState } from "react"; -import { useEffect } from "react"; -import { useParams } from "react-router-dom"; -function DoctorProfile() { - const [doctorData, setdoctorData] = useState([]); - const {id} = useParams() - useEffect(() => { - const fetchDoctors = async () => { - const {data, error} = await supabase - .from('Doctor') - .select('*') - .eq ('id', id) - .single() - if(error){ - console.error("Erro ao buscar pacientes:", error); - }else{ - setdoctorData(data); - } - }; - fetchDoctors(); - } , []); - - return ( -
- {/* Page Content */} -
-
-
-
-

Perfil Médico

-
- -
- - {/* Profile Header */} -
-
-
-
-
- -
-
-
-
-
-

{doctorData.nome} {doctorData.sobrenome}

- {doctorData.especialidade} -
-
-
-
-
    -
  • - Phone: - {doctorData.telefone} -
  • -
  • - Email: - {doctorData.email} -
  • -
  • - Data de nascimento: - {doctorData.data_nascimento} -
  • -
  • - Região - {doctorData.cidade}, {doctorData.estado}, Brasil -
  • -
  • - Sexo - {doctorData.sexo} -
  • -
-
-
-
-
-
-
- -
- - {/* Tabs */} -
- - -
-
-
-
-
-

Biografia

-
- -
- -

{doctorData.biografia}

- -
- -
-
- - -
-
-
-
-
-
-
-
- ); -} -export default DoctorProfile \ No newline at end of file diff --git a/src/pages/DoctorApp/DoctorApp.jsx b/src/pages/DoctorApp/DoctorApp.jsx index ce85bdd..d055cba 100644 --- a/src/pages/DoctorApp/DoctorApp.jsx +++ b/src/pages/DoctorApp/DoctorApp.jsx @@ -1,100 +1,97 @@ -import { Outlet, NavLink } from "react-router-dom"; -import "../../assets/css/index.css"; +import { Outlet, NavLink, useLocation } from "react-router-dom"; +import './../../assets/css/index.css' +import Navbar from '../../components/layouts/Navbar' +import { useState } from "react"; +import Chatbox from '../../components/chat/Chatbox'; +import AccessibilityWidget from '../../components/AccessibilityWidget'; +import { Link } from "react-router-dom"; +import { useResponsive } from '../../utils/useResponsive'; +import { getAccessToken } from '../../utils/auth.js'; +import { getUserRole } from '../../utils/userInfo.js'; +import { Navigate } from 'react-router-dom'; +import Sidebar from "../../components/layouts/Sidebar.jsx"; function DoctorApp() { + const [isSidebarOpen, setSidebarOpen] = useState(false); + const location = useLocation(); + + // 2. Adicione a função para alternar o estado + const toggleSidebar = () => { + setSidebarOpen(!isSidebarOpen); + }; + + // 3. Crie a string de classe que será aplicada dinamicamente + const mainWrapperClass = isSidebarOpen ? 'main-wrapper sidebar-open' : 'main-wrapper'; + + // Função para verificar se a rota está ativa + const isActive = (path) => { + const currentPath = location.pathname; + + // Verificação exata primeiro + if (currentPath === path) return true; + + // Verificação de subrotas (ex: /doctor/patients/edit/123) + if (currentPath.startsWith(path + '/')) return true; + + // Verificações específicas para páginas de edição/criação + if (path === '/doctor/patients' && ( + currentPath.includes('/doctor/editpatient/') || + currentPath.includes('/doctor/patientform') || + currentPath.includes('/doctor/patient/') + )) return true; + + if (path === '/doctor/prontuariolist' && ( + currentPath.includes('/doctor/prontuario/') || + currentPath.includes('/doctor/editprontuario/') || + currentPath.includes('/doctor/prontuarioform') + )) return true; + + if (path === '/doctor/consultas' && ( + currentPath.includes('/doctor/consulta/') || + currentPath.includes('/doctor/editconsulta/') || + currentPath.includes('/doctor/consultaform') + )) return true; + + if (path === '/doctor/laudolist' && ( + currentPath.includes('/doctor/laudo/') || + currentPath.includes('/doctor/editlaudo/') || + currentPath.includes('/doctor/laudoform') + )) return true; + + return false; + }; + const token = getAccessToken(); + const user = getUserRole(); + // Verificação de autenticação + if (!token) { + return ; + } + + // Verificação de role + if (user !== 'medico') { + return ( +
+
+
+

❌ Acesso Negado

+

Apenas médicos podem acessar esta área.

+ +
+
+
+ ); + } return ( -
- {/* Header */} - - - {/* Sidebar */} - - - {/* Conteúdo */} -
-
+
+
-
-
); } -export default DoctorApp; +export default DoctorApp; \ No newline at end of file diff --git a/src/pages/DoctorApp/DoctorCalendar.jsx b/src/pages/DoctorApp/DoctorCalendar.jsx index 87572f0..62ec53f 100644 --- a/src/pages/DoctorApp/DoctorCalendar.jsx +++ b/src/pages/DoctorApp/DoctorCalendar.jsx @@ -1,55 +1,256 @@ +// --- SEU JSX COMPLETO --- + +import React, { useState, useEffect } from "react"; import FullCalendar from "@fullcalendar/react"; import dayGridPlugin from "@fullcalendar/daygrid"; import timeGridPlugin from "@fullcalendar/timegrid"; import interactionPlugin from "@fullcalendar/interaction"; import ptBrLocale from "@fullcalendar/core/locales/pt-br"; +import "../../assets/css/index.css"; +import { getAccessToken } from '../../utils/auth'; +import { getDoctorId } from "../../utils/userInfo"; -function DoctorCalendar() { - return ( -
-

Calendário do Médico

-
- +// Função para formatar data/hora igual ao ConsultaList +function formatDateTime(dateString) { + if (!dateString) return ''; + try { + const [datePart, timePart] = dateString.split('T'); + const [year, month, day] = datePart.split('-'); + const [hour, minute] = timePart.split(':'); + return `${day}/${month}/${year} ${hour}:${minute}`; + } catch { + return dateString; + } +} + +export default function DoctorCalendar() { + const [events, setEvents] = useState([]); + const [editingEvent, setEditingEvent] = useState(null); + const [showPopup, setShowPopup] = useState(false); + const [showActionModal, setShowActionModal] = useState(false); + const [step, setStep] = useState(1); + const [newEvent, setNewEvent] = useState({ title: "", time: "" }); + const [selectedDate, setSelectedDate] = useState(null); + const [selectedEvent, setSelectedEvent] = useState(null); + const [pacientesMap, setPacientesMap] = useState({}); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + const colorsByType = { + presencial: "#4dabf7", + online: "#f76c6c", + Rotina: "#4dabf7", + Cardiologia: "#f76c6c", + Otorrino: "#f7b84d", + Pediatria: "#6cf78b" + }; + + const doctor_id = getDoctorId(); + const tokenUsuario = getAccessToken(); + + useEffect(() => { + const fetchAppointments = async () => { + try { + const requestOptions = { + method: "GET", + headers: { apikey: supabaseAK, Authorization: `Bearer ${tokenUsuario}` }, + }; + + const response = await fetch( + `${supabaseUrl}/rest/v1/appointments?doctor_id=eq.${doctor_id}`, requestOptions + ); + + const result = await response.json(); + const consultas = Array.isArray(result) ? result : []; + + const idsUnicos = [...new Set(consultas.map((c) => c.patient_id))]; + const promises = idsUnicos.map(async (id) => { + try { + const res = await fetch( + `${supabaseUrl}/rest/v1/patients?id=eq.${id}`, + { + method: "GET", + headers: { apikey: supabaseAK, Authorization: `Bearer ${tokenUsuario}` }, + } + ); + const data = await res.json(); + return { id, full_name: data?.[0]?.full_name || "Nome não encontrado" }; + } catch { + return { id, full_name: "Nome não encontrado" }; + } + }); + + const pacientes = await Promise.all(promises); + const map = {}; + pacientes.forEach((p) => (map[p.id] = p.full_name)); + setPacientesMap(map); + + const calendarEvents = consultas.map((consulta) => { + const [date, timeFull] = consulta.scheduled_at.split('T'); + const time = timeFull ? timeFull.substring(0, 5) : ''; + return { + id: consulta.id, + title: map[consulta.patient_id] || "Paciente", + date: date, + time: time, + start: `${date}T${time}:00`, + type: consulta.appointment_type || "presencial", + color: colorsByType[consulta.appointment_type] || "#4dabf7", + appointmentData: consulta, + }; + }); + + setEvents(calendarEvents); + } catch (error) { + console.error("Erro ao buscar consultas:", error); + } + }; + + if (doctor_id) fetchAppointments(); + }, [doctor_id, tokenUsuario]); + + const handleDateClick = (arg) => { + setSelectedDate(arg.dateStr); + setNewEvent({ title: "", time: "" }); + setStep(1); + setEditingEvent(null); + setShowPopup(true); + }; + + const handleAddEvent = () => { + const eventToAdd = { + id: Date.now(), + title: newEvent.title, + time: newEvent.time, + date: selectedDate, + start: `${selectedDate}T${newEvent.time}:00`, + color: colorsByType[newEvent.type] || "#4dabf7" + }; + setEvents((prev) => [...prev, eventToAdd]); + setShowPopup(false); + }; + + const handleEditEvent = () => { + setEvents((prevEvents) => + prevEvents.map((ev) => + ev.id.toString() === editingEvent.id.toString() + ? { + ...ev, + title: newEvent.title, + time: newEvent.time, + start: `${ev.date}T${newEvent.time}:00`, + color: colorsByType[newEvent.type] || "#4dabf7" + } + : ev + ) + ); + setEditingEvent(null); + setShowPopup(false); + setShowActionModal(false); + }; + + const handleNextStep = () => { + if (step < 2) setStep(step + 1); + else editingEvent ? handleEditEvent() : handleAddEvent(); + }; + + const handleEventClick = (clickInfo) => { + setSelectedEvent(clickInfo.event); + setShowActionModal(true); + }; + + const renderEventContent = (eventInfo) => { + const bg = + eventInfo.event.backgroundColor || + eventInfo.event.extendedProps?.color || + "#4dabf7"; + + const appointmentType = eventInfo.event.extendedProps?.type || "presencial"; + const typeLabel = appointmentType === "presencial" ? "Presencial" : "Online"; + + return ( +
+ + {eventInfo.event.title} + + + {eventInfo.event.extendedProps.time}
+ ); + }; - {/* CSS inline para centralizar */} - + /* ADICIONADO PARA DEIXAR A PARTE AZUL ESTILIZÁVEL */ + slotLabelContent={(arg) => { + return { + html: `${arg.text}` + }; + }} + + events={events.map((ev) => ({ + id: ev.id, + title: ev.title, + start: `${ev.date}T${ev.time}:00`, + color: ev.color, + extendedProps: { + type: ev.type, + time: ev.time, + color: ev.color, + appointmentData: ev.appointmentData + } + }))} + + eventContent={renderEventContent} + eventClick={handleEventClick} + dayCellClassNames="calendar-day-cell" + /> + +
+
); } - -export default DoctorCalendar; - diff --git a/src/pages/DoctorApp/DoctorDashboard.jsx b/src/pages/DoctorApp/DoctorDashboard.jsx index 296b3cb..ed9d1fe 100644 --- a/src/pages/DoctorApp/DoctorDashboard.jsx +++ b/src/pages/DoctorApp/DoctorDashboard.jsx @@ -1,68 +1,526 @@ -import { PieChart, Pie, Cell, ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, Legend } from "recharts"; +import React, { useState, useEffect } from "react"; +import { Link } from "react-router-dom"; +import { getAccessToken } from "../../utils/auth.js"; +import "../../assets/css/index.css"; +import { getFullName, getUserId } from "../../utils/userInfo"; +import { getUserRole } from "../../utils/userInfo"; +const AvatarForm = "/img/AvatarForm.jpg"; +const banner = "/img/banner.png"; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip as ChartTooltip, + Legend as ChartLegend, +} from 'chart.js'; +import { Bar as ChartJSBar } from 'react-chartjs-2'; +import { withTheme } from "@emotion/react"; -const consultsData = [ - { name: "Consultas", value: 45 }, - { name: "Exames", value: 20 }, - { name: "Laudos", value: 15 }, - { name: "Receitas", value: 25 }, -]; - -const COLORS = ["#0088FE", "#00C49F", "#FFBB28", "#FF8042"]; +// Registrar componentes do Chart.js +ChartJS.register( + CategoryScale, + LinearScale, + BarElement, + Title, + ChartTooltip, + ChartLegend +); function DoctorDashboard() { + const [patients, setPatients] = useState([]); + const [appointments, setAppointments] = useState([]); + const [todayAppointments, setTodayAppointments] = useState([]); + const [recentConsults, setRecentConsults] = useState([]); + const [followUpPatients, setFollowUpPatients] = useState([]); + const [alerts, setAlerts] = useState([]); + const [draftReports, setDraftReports] = useState([]); + // Estados para os gráficos médicos + + const [loading, setLoading] = useState(true); + const [currentTime, setCurrentTime] = useState(new Date()); + const [previewUrl, setPreviewUrl] = useState(AvatarForm); + + const tokenUsuario = getAccessToken(); + const userId = getUserId(); + const role = getUserRole(); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + + const requestOptions = { + method: "GET", + headers: { + apikey: supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + }, + redirect: "follow", + }; + + useEffect(() => { + const loadDoctorData = async () => { + try { + setLoading(true); + + // Buscar pacientes + const patientsResponse = await fetch( + `${supabaseUrl}/rest/v1/patients`, + requestOptions + ); + const patientsData = await patientsResponse.json(); + const patientsArr = Array.isArray(patientsData) ? patientsData : []; + setPatients(patientsArr); + + // Buscar consultas do médico (filtrar pelo doctor_id se disponível) + const appointmentsResponse = await fetch( + `${supabaseUrl}/rest/v1/appointments`, + requestOptions + ); + const appointmentsData = await appointmentsResponse.json(); + const appointmentsArr = Array.isArray(appointmentsData) ? appointmentsData : []; + setAppointments(appointmentsArr); + + // Buscar laudos em draft + const reportsResponse = await fetch( + `${supabaseUrl}/rest/v1/reports?status=eq.draft`, + requestOptions + ); + const reportsData = await reportsResponse.json(); + const reportsArr = Array.isArray(reportsData) ? reportsData : []; + setDraftReports(reportsArr); + + // Processar dados específicos do médico + processTodayAppointments(appointmentsArr, patientsArr); + processRecentConsults(appointmentsArr, patientsArr); + processFollowUpPatients(appointmentsArr, patientsArr); + processConsultasMensais(appointmentsArr); + processComparecimentoData(appointmentsArr); + processAlerts(appointmentsArr, reportsArr); + + } catch (error) { + console.error('Erro ao carregar dados do médico:', error); + } finally { + setLoading(false); + } + }; + + // Inject custom CSS for DoctorDashboard + const styleId = 'doctor-dashboard-styles'; + if (!document.getElementById(styleId)) { + const style = document.createElement('style'); + style.id = styleId; + style.textContent = ` + [data-dashboard="doctor"] .custom-badge { + padding: 4px 12px; + border-radius: 20px; + font-size: 11px; + font-weight: 500; + text-transform: uppercase; + } + [data-dashboard="doctor"] .status-green { + background-color: #e8f5e8; + color: #2e7d32; + border: 1px solid #c8e6c9; + } + [data-dashboard="doctor"] .status-yellow { + background-color: #fff8e1; + color: #f57f17; + border: 1px solid #ffecb3; + } + [data-dashboard="doctor"] .status-red { + background-color: #ffebee; + color: #c62828; + border: 1px solid #ffcdd2; + } + `; + document.head.appendChild(style); + } + + loadDoctorData(); + }, []); + + // useEffect para atualizar o relógio em tempo real + useEffect(() => { + const timer = setInterval(() => { + setCurrentTime(new Date()); + }, 1000); // Atualiza a cada segundo + + return () => clearInterval(timer); // Limpa o timer quando o componente é desmontado + }, []); + + // useEffect para carregar avatar do usuário (mesma lógica da navbar) + useEffect(() => { + const loadAvatar = async () => { + if (!userId) return; + + const myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + + const requestOptions = { + headers: myHeaders, + method: 'GET', + redirect: 'follow' + }; + + try { + const response = await fetch(`${supabaseUrl}/storage/v1/object/avatars/${userId}/avatar.png`, requestOptions); + + if (response.ok) { + const blob = await response.blob(); + const imageUrl = URL.createObjectURL(blob); + setPreviewUrl(imageUrl); + return; // Avatar encontrado + } + } catch (error) { + + } + + // Se chegou até aqui, não encontrou avatar - mantém o padrão + + }; + + loadAvatar(); + }, [userId]); + + // Processar agenda do dia + const processTodayAppointments = (appointmentsData, patientsData) => { + const today = new Date().toISOString().split('T')[0]; + const todayAppts = appointmentsData.filter(apt => { + if (!apt.scheduled_at) return false; + const aptDate = apt.scheduled_at.split('T')[0]; + return aptDate === today; + }); + + const todayWithPatients = todayAppts.map(apt => { + const patient = patientsData.find(p => p.id === apt.patient_id); + return { + ...apt, + patient_name: patient?.name || patient?.full_name || 'Paciente não encontrado', + time: apt.scheduled_at ? apt.scheduled_at.split('T')[1].substring(0, 5) : '' + }; + }).sort((a, b) => a.time.localeCompare(b.time)); + + setTodayAppointments(todayWithPatients); + }; + + // Processar consultas recentes + const processRecentConsults = (appointmentsData, patientsData) => { + const recent = appointmentsData + .filter(apt => apt.scheduled_at && new Date(apt.scheduled_at) < new Date()) + .sort((a, b) => new Date(b.scheduled_at) - new Date(a.scheduled_at)) + .slice(0, 5) + .map(apt => { + const patient = patientsData.find(p => p.id === apt.patient_id); + return { + ...apt, + patient_name: patient?.name || patient?.full_name || 'Paciente não encontrado', + date: apt.scheduled_at ? new Date(apt.scheduled_at).toLocaleDateString('pt-BR') : '' + }; + }); + + setRecentConsults(recent); + }; + + // Processar pacientes em acompanhamento + const processFollowUpPatients = (appointmentsData, patientsData) => { + // Selecionar pacientes com consultas recorrentes ou em tratamento + const followUp = patientsData.slice(0, 10); + setFollowUpPatients(followUp); + }; + + // Processar dados de consultas mensais + const processConsultasMensais = (appointmentsData) => { + // Lógica para processar consultas mensais para gráficos + // Implementar conforme necessário + }; + + // Processar dados de comparecimento + const processComparecimentoData = (appointmentsData) => { + // Lógica para processar dados de comparecimento + // Implementar conforme necessário + }; + + // Processar alertas + const processAlerts = (appointmentsData, reportsData = []) => { + const alertsArray = []; + + // Verificar laudos em draft + if (reportsData.length > 0) { + alertsArray.push({ + message: `${reportsData.length} laudo${reportsData.length > 1 ? 's' : ''} pendente${reportsData.length > 1 ? 's' : ''} de confirmação`, + type: 'warning', + icon: 'fa-file-text-o', + action: 'Confirmar', + link: '/doctor/reports' + }); + } + + // Verificar consultas próximas (próximas 2 horas) + const now = new Date(); + const twoHoursLater = new Date(now.getTime() + 2 * 60 * 60 * 1000); + + appointmentsData.forEach(apt => { + if (apt.scheduled_at) { + const aptDate = new Date(apt.scheduled_at); + if (aptDate > now && aptDate <= twoHoursLater) { + alertsArray.push({ + message: `Consulta em ${Math.round((aptDate - now) / (1000 * 60))} minutos`, + type: 'warning', + icon: 'fa-clock-o', + action: 'Ver', + link: '/doctor/appointments' + }); + } + } + }); + + // Adicionar outros alertas conforme necessário + if (alertsArray.length === 0) { + alertsArray.push({ + message: 'Nenhum alerta no momento', + type: 'info', + icon: 'fa-info-circle', + action: 'OK' + }); + } + + setAlerts(alertsArray); + }; return ( -
-
-
-

📊 Dashboard do Médico

- +
+
+ {/* Header com informações do médico */} +
- {/* Gráfico de Pizza */} -
-
Distribuição de Atividades
- - - - {consultsData.map((entry, index) => ( - - ))} - - - - -
- - {/* Gráfico de Barras */} -
-
Consultas por Semana
- - - - - - - - - +
+
+
+
+

👨‍⚕️ Olá, Dr. {getFullName()}!

+

Hoje é mais um dia para transformar vidas. Revise sua agenda, acompanhe seus pacientes e siga fazendo a diferença com o MediConnect. 💙!

+ + 🕒 {currentTime.toLocaleString('pt-BR')} + +
+
+ Avatar +
+
+
+ + {/* Cards de estatísticas */} +
+
+
+ + + +
+

{todayAppointments.length}

+ Consultas Hoje +
+
+
+ +
+
+ + + +
+

{patients.length}

+ Total Pacientes +
+
+
+ +
+
+ + + +
+

{draftReports.length}

+ Laudos Pendentes +
+
+
+ +
+
+ + + +
+

{alerts.length}

+ Notificações +
+
+
+
+ + {/* Ações rápidas */} +
+
+
+
+
+
+ + + Nova Consulta + +
+
+ + + Meus Pacientes + +
+
+ + + Laudos + +
+
+ + + Exceções + +
+
+
+
+
+
+ + {/* Alertas Médicos */} + {alerts.length > 0 && ( +
+
+
+
+

🔔 Notificações Importantes

+
+
+ {alerts.map((alert, index) => ( +
+ + {alert.message} + +
+ ))} +
+
+
+
+ )} + + {/* Seções Específicas do Médico */} + {/* Primeira linha - Agenda do Dia e Consultas Recentes */} +
+
+
+

+ Consultas de Hoje +

+ {loading ? ( +
+
+ +

Carregando agenda...

+
+
+ ) : todayAppointments.length > 0 ? ( +
+ {todayAppointments.map((apt, index) => ( +
+
+ {apt.time} +
+
+
{apt.patient_name}
+ {apt.chief_complaint || 'Consulta de rotina'} +
+
+ + {apt.status === 'completed' ? 'Realizada' : 'Agendada'} + +
+
+ ))} +
+ ) : ( +
+ +

Nenhuma consulta agendada para hoje

+
+ )} +
+
+ +
+
+

+ Atendimentos Recentes +

+ {loading ? ( +
+
+ +

Carregando consultas...

+
+
+ ) : recentConsults.length > 0 ? ( +
+ {recentConsults.map((consult, index) => ( +
+
+ {consult.patient_name.charAt(0)} +
+
+
{consult.patient_name}
+ {consult.date} +
+ +
+ ))} +
+ ) : ( +
+ +

Nenhuma consulta recente

+
+ )} +
+
+
+ + {/* Charts Section */} + + +
); } -export default DoctorDashboard; +export default DoctorDashboard; \ No newline at end of file diff --git a/src/pages/DoctorApp/DoctorPatientList.jsx b/src/pages/DoctorApp/DoctorPatientList.jsx deleted file mode 100644 index eadeb81..0000000 --- a/src/pages/DoctorApp/DoctorPatientList.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import React, { useState } from "react"; - -function PatientList() { - const [searchTerm, setSearchTerm] = useState(""); - - const patients = [ - { - nome: "João Miguel", - cpf: "091.959.495-69", - telefone: "+55 (75) 99961-7296", - email: "Joaomiguel80@gmail.com", - }, - ]; - - const filteredPatients = patients.filter( - (p) => - p.nome.toLowerCase().includes(searchTerm.toLowerCase()) || - p.cpf.toLowerCase().includes(searchTerm.toLowerCase()) || - p.email.toLowerCase().includes(searchTerm.toLowerCase()) - ); - - return ( -
- {/* Barra de Pesquisa */} - setSearchTerm(e.target.value)} - className="search-bar" - /> - - {/* Tabela */} -
-

Lista de Pacientes

- - - - - - - - - - - - {filteredPatients.map((p, idx) => ( - - - - - - - - ))} - -
NomeCPFTelefoneEmailAções
{p.nome}{p.cpf}{p.telefone}{p.email} - - -
-
-
- ); -} - -export default PatientList; diff --git a/src/pages/DoctorApp/Doctorexceçao.jsx b/src/pages/DoctorApp/Doctorexceçao.jsx new file mode 100644 index 0000000..5f95f01 --- /dev/null +++ b/src/pages/DoctorApp/Doctorexceçao.jsx @@ -0,0 +1,356 @@ +import React, { useEffect, useMemo, useState } from "react"; +import FullCalendar from "@fullcalendar/react"; +import dayGridPlugin from "@fullcalendar/daygrid"; +import interactionPlugin from "@fullcalendar/interaction"; +import ptBrLocale from "@fullcalendar/core/locales/pt-br"; +import Swal from "sweetalert2"; +import { getAccessToken } from "../../utils/auth.js"; +// Adicione uma função para pegar o papel do usuário +import { getUserRole } from '../../utils/userInfo.js'; + +const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; +const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + +const API_ROOT = `${supabaseUrl}/rest/v1`; +const AUTH_URL = `${supabaseUrl}/auth/v1/user`; // Endpoint para pegar dados do user logado +const API_URL = `${API_ROOT}/doctor_exceptions`; +const API_DOCTORS = `${API_ROOT}/doctors`; +const API_KEY = supabaseAK; + +export default function Doctorexceçao() { + const token = getAccessToken(); + // Verifica o papel do usuário (admin ou médico) + const userRole = getUserRole(); + + const [exceptions, setExceptions] = useState([]); + const [currentDoctor, setCurrentDoctor] = useState(null); // Estado para o médico logado + const [loading, setLoading] = useState(true); + const [err, setErr] = useState(""); + + // ---------- CONFIGURAÇÕES COMUNS ---------- + const commonHeaders = { + apikey: API_KEY, + Authorization: `Bearer ${token}`, + }; + + // ---------- 1. IDENTIFICAR USUÁRIO LOGADO ---------- + const loadCurrentUser = async () => { + try { + setLoading(true); + if (userRole === 'admin') { + // Se for admin, carrega todas as exceções e define um médico padrão + await loadExceptions(); + setCurrentDoctor({ full_name: 'Administrador' }); + return; + } + + // 1. Pega os dados da autenticação (Auth User) + const resAuth = await fetch(AUTH_URL, { headers: commonHeaders }); + if (!resAuth.ok) throw new Error("Falha ao autenticar usuário"); + const user = await resAuth.json(); + + // 2. Busca o perfil do médico correspondente na tabela 'doctors' + // Assumindo que o ID do médico na tabela é igual ao ID do Auth (UUID) + const resDoc = await fetch(`${API_DOCTORS}?user_id=eq.${user.id}`, { headers: commonHeaders }); + if (!resDoc.ok) throw new Error("Perfil de médico não encontrado"); + + const docsData = await resDoc.json(); + + if (docsData.length > 0) { + const doc = docsData[0]; + setCurrentDoctor(doc); + // Só carrega as exceções depois de saber quem é o médico + loadExceptions(doc.id); + } else { + setErr("Seu usuário não está cadastrado como médico."); + } + } catch (e) { + setErr(e.message || "Erro ao carregar perfil do usuário"); + } finally { + setLoading(false); + } + }; + + // ---------- 2. CARREGAR DADOS FILTRADOS ---------- + // Agora recebe o doctorId como argumento para filtrar na API + const loadExceptions = async (doctorId) => { + try { + let url; + if (userRole === 'admin') { + url = `${API_URL}?select=*,doctor:doctor_id(id,full_name)`; // join para trazer nome do médico + } else { + url = `${API_URL}?select=*&doctor_id=eq.${doctorId}`; + } + const res = await fetch(url, { headers: commonHeaders }); + if (!res.ok) throw new Error(await res.text()); + const data = await res.json(); + setExceptions(Array.isArray(data) ? data : []); + } catch (e) { + console.error(e); + } + }; + + useEffect(() => { + if (token) { + loadCurrentUser(); + } + }, [token]); + + // ---------- CRIAR EXCEÇÃO ---------- + const createException = async (payload) => { + try { + const body = { + ...payload, + // Garante que quem cria é o próprio médico + created_by: currentDoctor.id, + doctor_id: currentDoctor.id + }; + + const res = await fetch(API_URL, { + method: "POST", + headers: { + ...commonHeaders, + "Content-Type": "application/json", + Prefer: "return=representation", + }, + body: JSON.stringify(body), + }); + + if (!res.ok) throw new Error(await res.text()); + await res.json(); + // Recarrega usando o ID do médico logado + await loadExceptions(currentDoctor.id); + Swal.fire("Sucesso!", "Exceção criada com sucesso.", "success"); + } catch (e) { + Swal.fire("Erro ao criar", e.message || "Falha ao criar exceção", "error"); + } + }; + + // ---------- DELETAR EXCEÇÃO ---------- + const deleteException = async (id) => { + const confirm = await Swal.fire({ + title: "Excluir exceção?", + text: "Essa ação não pode ser desfeita.", + icon: "warning", + showCancelButton: true, + confirmButtonText: "Sim, excluir", + cancelButtonText: "Cancelar", + }); + if (!confirm.isConfirmed) return; + + try { + const res = await fetch(`${API_URL}?id=eq.${id}`, { + method: "DELETE", + headers: commonHeaders, + }); + if (!res.ok) throw new Error(await res.text()); + + // Recarrega usando o ID do médico logado + await loadExceptions(currentDoctor.id); + Swal.fire("Removida!", "Exceção excluída com sucesso.", "success"); + } catch (e) { + Swal.fire("Erro ao excluir", e.message || "Falha ao excluir", "error"); + } + }; + + // ---------- EVENTOS DO CALENDÁRIO ---------- + const events = useMemo(() => { + return exceptions.map((ex) => { + const isBlock = ex.kind === "bloqueio"; + return { + id: ex.id, + title: isBlock ? "Bloqueio" : "Liberação", + start: ex.date, + allDay: true, + backgroundColor: isBlock ? "#ef4444" : "#22c55e", + borderColor: isBlock ? "#b91c1c" : "#15803d", + textColor: "#fff", + }; + }); + }, [exceptions]); + + // ---------- HANDLERS ---------- + const handleDateClick = async (info) => { + if (!currentDoctor) { + Swal.fire("Erro", "Perfil de médico não identificado.", "error"); + return; + } + + // REMOVIDA A ETAPA 1 (Seleção de médico) + // Agora vai direto para a seleção do tipo de exceção + + // 1️⃣ Tipo da exceção + const s2 = await Swal.fire({ + title: `Nova exceção — ${info.dateStr}`, + text: "O que deseja fazer nesta data?", + input: "select", + inputOptions: { + bloqueio: "Bloqueio (Não atender)", + liberacao: "Liberação (Atender extra)", + }, + inputPlaceholder: "Selecione o tipo", + showCancelButton: true, + confirmButtonText: "Continuar", + didOpen: (popup) => { + popup.style.position = "fixed"; + popup.style.top = "230px"; + } + }); + if (!s2.isConfirmed || !s2.value) return; + const kind = s2.value; + + // 2️⃣ Motivo + const form = await Swal.fire({ + title: "Motivo (opcional)", + input: "text", + inputPlaceholder: "Ex: Congresso, folga, manutenção...", + showCancelButton: true, + confirmButtonText: "Criar exceção", + didOpen: (popup) => { + popup.style.position = "fixed"; + popup.style.top = "230px"; + } + }); + if (!form.isConfirmed) return; + + const payload = { + // O ID vem do estado global do componente + doctor_id: currentDoctor.id, + date: info.dateStr, + kind, + reason: form.value || null, + }; + + await createException(payload); + }; + + const handleEventClick = async (info) => { + const e = exceptions.find((x) => x.id === info.event.id); + if (!e) return; + + await Swal.fire({ + title: e.kind === "bloqueio" ? "Bloqueio" : "Liberação", + html: `Data: ${e.date}
+ Motivo: ${e.reason || "-"}`, + icon: "info", + showCancelButton: true, + confirmButtonText: "Excluir", + cancelButtonText: "Fechar", + }).then((r) => { + if (r.isConfirmed) deleteException(e.id); + }); + }; + + // ---------- UI ---------- + return ( +
+
+
+
+
+

+ Minhas Exceções {currentDoctor ? `(${currentDoctor.full_name})` : ""} +

+ + Clique numa data para bloquear ou liberar sua agenda + +
+
+
+ + {/* Calendário */} +
+
+
+ {loading ? ( +

Identificando médico e carregando agenda...

+ ) : err ? ( +
{err}
+ ) : ( + + )} +
+
+
+ + {/* Lista de exceções */} +
+
+
+
+
Minhas Exceções Registradas
+ {exceptions.length} registro(s) +
+ +
+ {loading ? ( +

Carregando...

+ ) : !err && exceptions.length === 0 ? ( +

Nenhuma exceção encontrada para você.

+ ) : ( +
+ + + + + + + {userRole === 'admin' && } + + + + + {exceptions.map((ex) => ( + + + + + {userRole === 'admin' && } + + + ))} + +
DataTipoMotivoMédicoAções
{ex.date} + {ex.kind === "bloqueio" ? ( + Bloqueio + ) : ( + Liberação + )} + {ex.reason || "-"}{ex.doctor?.full_name || '-'} + +
+
+ )} +
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/pages/DoctorApp/Prontuario/DoctorProntuario.jsx b/src/pages/DoctorApp/Prontuario/DoctorProntuario.jsx new file mode 100644 index 0000000..1a39075 --- /dev/null +++ b/src/pages/DoctorApp/Prontuario/DoctorProntuario.jsx @@ -0,0 +1,790 @@ +// src/pages/DoctorApp/DoctorProntuario.jsx +import React, { useState, useEffect } from "react"; +import { useParams, useNavigate, useLocation } from "react-router-dom"; + + +function DoctorProntuario() { + const { id } = useParams(); + const navigate = useNavigate(); + const location = useLocation(); + + // Receber paciente via state da navegação ou buscar por ID + const [paciente, setPaciente] = useState(location.state?.paciente || null); + const [prontuario, setProntuario] = useState(""); + const [historico, setHistorico] = useState([]); + const [retorno, setRetorno] = useState("3 meses"); + const [peso, setPeso] = useState(""); + const [altura, setAltura] = useState(""); + const [imc, setImc] = useState(""); + const [pressaoArterial, setPressaoArterial] = useState(""); + const [temperatura, setTemperatura] = useState(""); + const [observacoes, setObservacoes] = useState(""); + + // Estados para anexos + const [anexos, setAnexos] = useState([]); + const [arquivoSelecionado, setArquivoSelecionado] = useState(null); + + // Estados para o cronômetro + const [atendimentoIniciado, setAtendimentoIniciado] = useState(false); + const [atendimentoPausado, setAtendimentoPausado] = useState(false); + const [atendimentoFinalizado, setAtendimentoFinalizado] = useState(false); + const [tempoDecorrido, setTempoDecorrido] = useState(0); + const [cronometroAtivo, setCronometroAtivo] = useState(false); + + // Dados mockados dos pacientes (igual ao da lista) + const pacientesMock = [ + { + id: 1, + nome: "João Silva Santos", + cpf: "123.456.789-00", + data_nascimento: "15/03/1985", + telefone: "(11) 99999-9999", + email: "joao.silva@email.com", + status: "ativo", + endereco: "Rua das Flores, 123 - São Paulo/SP", + idade: "39 anos", + primeiraConsulta: "15/01/2024", + convenio: "Unimed", + atendimentos: 3, + faltas: 0 + }, + { + id: 2, + nome: "Maria Oliveira Costa", + cpf: "987.654.321-00", + data_nascimento: "22/07/1990", + telefone: "(11) 88888-8888", + email: "maria.oliveira@email.com", + status: "ativo", + endereco: "Av. Paulista, 1000 - São Paulo/SP", + idade: "33 anos", + primeiraConsulta: "20/02/2024", + convenio: "Amil", + atendimentos: 2, + faltas: 1 + }, + { + id: 3, + nome: "Pedro Almeida Souza", + cpf: "456.789.123-00", + data_nascimento: "10/12/1978", + telefone: "(11) 77777-7777", + email: "pedro.almeida@email.com", + status: "inativo", + endereco: "Rua Augusta, 500 - São Paulo/SP", + idade: "45 anos", + primeiraConsulta: "05/03/2024", + convenio: "Bradesco Saúde", + atendimentos: 1, + faltas: 0 + }, + { + id: 4, + nome: "Ana Pereira Lima", + cpf: "789.123.456-00", + data_nascimento: "05/09/1995", + telefone: "(11) 66666-6666", + email: "ana.pereira@email.com", + status: "ativo", + endereco: "Rua Consolação, 200 - São Paulo/SP", + idade: "28 anos", + primeiraConsulta: "10/04/2024", + convenio: "SulAmérica", + atendimentos: 4, + faltas: 0 + }, + { + id: 5, + nome: "Carlos Rodrigues Ferreira", + cpf: "321.654.987-00", + data_nascimento: "30/01/1982", + telefone: "(11) 55555-5555", + email: "carlos.rodrigues@email.com", + status: "arquivado", + endereco: "Alameda Santos, 800 - São Paulo/SP", + idade: "42 anos", + primeiraConsulta: "25/05/2024", + convenio: "NotreDame Intermédica", + atendimentos: 2, + faltas: 1 + } + ]; + + // Histórico médico mockado específico para cada paciente + const historicoPorPaciente = { + 1: [ + { + id: 1, + data: "15/01/2024", + tipo: "Consulta Inicial", + diagnostico: "Paciente em bom estado geral. Realizado check-up preventivo.", + medico: "Dr. José Rodrigues", + duracao: "45 minutos", + retorno: "6 meses", + dadosAntropometricos: { + peso: "78.5 kg", + altura: "175 cm", + imc: "25.6", + pressaoArterial: "120/80 mmHg", + temperatura: "36.5°C" + }, + observacoes: "Paciente sem queixas. Exames dentro da normalidade.", + anexos: [ + { id: 1, nome: "raio-x-torax.pdf", tipo: "pdf", tamanho: "2.4 MB" } + ] + } + ], + 2: [ + { + id: 1, + data: "20/02/2024", + tipo: "Consulta de Rotina", + diagnostico: "Controle de pressão arterial.", + medico: "Dr. José Rodrigues", + duracao: "30 minutos", + retorno: "3 meses", + dadosAntropometricos: { + peso: "65.2 kg", + altura: "165 cm", + imc: "23.9", + pressaoArterial: "118/78 mmHg", + temperatura: "36.7°C" + }, + observacoes: "Paciente com pressão controlada.", + anexos: [] + } + ], + // Adicione históricos para os outros IDs... + }; + + useEffect(() => { + // Se o paciente foi passado via state, usa ele + if (location.state?.paciente) { + setPaciente(location.state.paciente); + const historicoPaciente = historicoPorPaciente[location.state.paciente.id] || []; + setHistorico(historicoPaciente); + } + // Se não, busca pelo ID na URL + else if (id) { + const pacienteEncontrado = pacientesMock.find(p => p.id === parseInt(id)); + setPaciente(pacienteEncontrado); + const historicoPaciente = historicoPorPaciente[parseInt(id)] || []; + setHistorico(historicoPaciente); + } + }, [id, location.state]); + + // Resto do código do cronômetro permanece igual... + useEffect(() => { + let intervalo = null; + + if (cronometroAtivo && !atendimentoPausado && !atendimentoFinalizado) { + intervalo = setInterval(() => { + setTempoDecorrido(tempo => tempo + 1); + }, 1000); + } else { + clearInterval(intervalo); + } + + return () => clearInterval(intervalo); + }, [cronometroAtivo, atendimentoPausado, atendimentoFinalizado]); + + const formatarTempo = (segundos) => { + const minutos = Math.floor(segundos / 60); + const segs = segundos % 60; + return `${minutos.toString().padStart(2, '0')}:${segs.toString().padStart(2, '0')}`; + }; + + const formatarTempoExtenso = (segundos) => { + const horas = Math.floor(segundos / 3600); + const minutos = Math.floor((segundos % 3600) / 60); + const segs = segundos % 60; + + if (horas > 0) { + return `${horas}h ${minutos}m ${segs}s`; + } + return `${minutos}m ${segs}s`; + }; + + const iniciarAtendimento = () => { + setAtendimentoIniciado(true); + setAtendimentoPausado(false); + setAtendimentoFinalizado(false); + setCronometroAtivo(true); + setTempoDecorrido(0); + }; + + const pausarAtendimento = () => { + setAtendimentoPausado(true); + setCronometroAtivo(false); + }; + + const retomarAtendimento = () => { + setAtendimentoPausado(false); + setCronometroAtivo(true); + }; + + const finalizarAtendimento = () => { + const confirmacao = window.confirm( + `Tem certeza que deseja finalizar o atendimento?\n\n` + + `Paciente: ${paciente.nome}\n` + + `Duração: ${formatarTempoExtenso(tempoDecorrido)}\n\n` + + `Após finalizar, o tempo será salvo e não poderá ser alterado.` + ); + + if (confirmacao) { + setAtendimentoFinalizado(true); + setCronometroAtivo(false); + setAtendimentoIniciado(false); + setAtendimentoPausado(false); + } + }; + + const calcularIMC = () => { + if (peso && altura) { + const alturaMetros = parseInt(altura) / 100; + const imcCalculado = (parseFloat(peso) / (alturaMetros * alturaMetros)).toFixed(1); + setImc(imcCalculado); + } + }; + + useEffect(() => { + calcularIMC(); + }, [peso, altura]); + + const handleArquivoSelecionado = (event) => { + const file = event.target.files[0]; + if (file) { + setArquivoSelecionado(file); + } + }; + + const adicionarAnexo = () => { + if (arquivoSelecionado) { + const novoAnexo = { + id: anexos.length + 1, + nome: arquivoSelecionado.name, + tipo: arquivoSelecionado.type, + tamanho: (arquivoSelecionado.size / 1024 / 1024).toFixed(1) + " MB", + arquivo: arquivoSelecionado, + data: new Date().toLocaleString('pt-BR') + }; + + setAnexos([...anexos, novoAnexo]); + setArquivoSelecionado(null); + document.getElementById('fileInput').value = ''; + } + }; + + const removerAnexo = (id) => { + setAnexos(anexos.filter(anexo => anexo.id !== id)); + }; + + const handleSalvarProntuario = () => { + if (prontuario.trim()) { + const duracaoFormatada = atendimentoFinalizado ? formatarTempoExtenso(tempoDecorrido) : "Não registrado"; + + const dadosAntropometricosText = ` +DADOS ANTROPOMÉTRICOS: +- Peso: ${peso || "Não informado"} +- Altura: ${altura || "Não informado"} +- IMC: ${imc || "Não calculado"} +- Pressão Arterial: ${pressaoArterial || "Não informada"} +- Temperatura: ${temperatura || "Não informada"} +`; + + const observacoesText = observacoes ? `\nOBSERVAÇÕES:\n${observacoes}` : ""; + + const textoCompleto = `${dadosAntropometricosText}${observacoesText}\n\nDIAGNÓSTICO E CONDUTA:\n${prontuario}`; + + const novoRegistro = { + id: historico.length + 1, + data: new Date().toLocaleDateString('pt-BR'), + tipo: "Consulta de Rotina", + diagnostico: textoCompleto, + medico: "Dr. Médico Atual", + duracao: duracaoFormatada, + retorno: retorno, + dadosAntropometricos: { + peso: peso || "Não informado", + altura: altura || "Não informado", + imc: imc || "Não calculado", + pressaoArterial: pressaoArterial || "Não informada", + temperatura: temperatura || "Não informada" + }, + observacoes: observacoes, + anexos: [...anexos] + }; + + setHistorico([novoRegistro, ...historico]); + + // Limpar formulário + setProntuario(""); + setPeso(""); + setAltura(""); + setImc(""); + setPressaoArterial(""); + setTemperatura(""); + setObservacoes(""); + setAnexos([]); + setAtendimentoIniciado(false); + setAtendimentoPausado(false); + setAtendimentoFinalizado(false); + setCronometroAtivo(false); + setTempoDecorrido(0); + + alert(`Prontuário salvo com sucesso! Duração da consulta: ${duracaoFormatada}`); + } + }; + + const visualizarCadastro = () => { + if (paciente) { + alert(`Cadastro Completo de ${paciente.nome}\n\nCPF: ${paciente.cpf}\nTelefone: ${paciente.telefone}\nEmail: ${paciente.email}\nEndereço: ${paciente.endereco}\nData Nasc.: ${paciente.data_nascimento}\nConvênio: ${paciente.convenio || "Não informado"}\nStatus: ${paciente.status}`); + } + }; + + const getIconePorTipo = (tipo) => { + if (tipo.includes('image')) return '🖼️'; + if (tipo.includes('pdf')) return '📄'; + if (tipo.includes('word')) return '📝'; + return '📎'; + }; + + if (!paciente) { + return ( +
+

Paciente não encontrado

+ +
+ ); + } + + return ( +
+ {/* Header com informações do paciente específico */} +
+
+ +
+

Prontuário Médico

+ Paciente ID: {paciente.id} • {paciente.nome} +
+
+ +
+ + + {/* Controle do atendimento */} + {!atendimentoIniciado && !atendimentoFinalizado ? ( + + ) : atendimentoPausado ? ( + + ) : atendimentoIniciado ? ( + + ) : null} + + {(atendimentoIniciado || atendimentoPausado) && ( + + )} +
+
+ + {/* Cronômetro */} + {(atendimentoIniciado || atendimentoPausado || atendimentoFinalizado) && ( +
+
+ + {atendimentoFinalizado ? '✅ Atendimento Finalizado' : + atendimentoPausado ? '⏸️ Atendimento Pausado' : '▶️ Atendimento em Andamento'} + + {atendimentoPausado && ( + (Tempo pausado) + )} +
+
+ + ⏱️ {formatarTempo(tempoDecorrido)} + {atendimentoFinalizado && ` (${formatarTempoExtenso(tempoDecorrido)})`} + +
+
+ )} + + {/* Resumo do Paciente ESPECÍFICO */} +
+
+
Resumo do Paciente
+ + {paciente.status} + +
+
+
+

{paciente.nome}

+
+ ID: {paciente.id} + CPF: {paciente.cpf} +
+
+ +
+
+
+
IDADE
+
{paciente.idade || "N/A"}
+
Nasc: {paciente.data_nascimento}
+
+
+ +
+
+
PRIMEIRA CONSULTA
+
{paciente.primeiraConsulta || "N/A"}
+
Convênio: {paciente.convenio || "N/A"}
+
+
+ +
+
+
ATENDIMENTOS
+
{paciente.atendimentos || 0}
+
realizados
+
+
+ +
+
+
FALTAS
+
{paciente.faltas || 0}
+
registradas
+
+
+
+
+
+ + {/* Resto do código do prontuário permanece igual... */} +
+ {/* Coluna 1: Dados da Consulta */} +
+
+
+
⏱️ Duração da Consulta
+
+
+ {atendimentoFinalizado ? ( +
+
+ {formatarTempo(tempoDecorrido)} +
+ Tempo final: {formatarTempoExtenso(tempoDecorrido)} +
+ ) : atendimentoIniciado ? ( +
+
+ {formatarTempo(tempoDecorrido)} +
+ + {atendimentoPausado ? 'Tempo pausado' : 'Tempo decorrido'} + +
+ ) : ( +
+
+ 00:00 +
+ Inicie o atendimento +
+ )} +
+
+ +
+
+
📊 Dados Antropométricos
+
+
+
+
+ + setPeso(e.target.value)} + className="form-control form-control-sm" + placeholder="70.5" + /> +
+
+ + setAltura(e.target.value)} + className="form-control form-control-sm" + placeholder="175" + /> +
+
+ + +
+
+ + setPressaoArterial(e.target.value)} + className="form-control form-control-sm" + placeholder="120/80 mmHg" + /> +
+
+ + setTemperatura(e.target.value)} + className="form-control form-control-sm" + placeholder="36.5°C" + /> +
+
+ + + + +
+ + + {/* FOOTER */} +
+
+
+ Medi-Connect + Medi-Connect +
+ +
+ + + +
+ +
+ © {new Date().getFullYear()} Medi-Connect — Cuidando da sua saúde com excelência. +
+
+
+
+ + ); +} diff --git a/src/pages/Login/Acessounico.jsx b/src/pages/Login/Acessounico.jsx new file mode 100644 index 0000000..c7fbdb5 --- /dev/null +++ b/src/pages/Login/Acessounico.jsx @@ -0,0 +1,147 @@ +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { getAccessToken } from "../../utils/auth.js"; +import { useResponsive } from '../../utils/useResponsive'; + +export default function MagicLink() { + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + + const navigate = useNavigate(); + const [email, setEmail] = useState(""); + const [emailError, setEmailError] = useState(''); + const [isTouched, setIsTouched] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + const [serverError, setServerError] = useState(''); + const [serverSucsess, setServerSucsess] = useState(''); + const tokenUsuario =getAccessToken() + const handleSubmit = async (e) => { + e.preventDefault(); + + const emailValidation = validateEmail(email); + + if (emailValidation) { + // Se houver erros locais, para a execução antes do fetch + return; + } + + try { + const myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + myHeaders.append("Content-Type", "application/json"); + var raw = JSON.stringify({ + email: email, + options: { + emailRedirectTo: "https://mediconnect-neon.vercel.app/" + } + }); + + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: raw, + redirect: 'follow' + }; + const response = await fetch( + `${supabaseUrl}/auth/v1/otp`, requestOptions + ); + + const result = await response.json(); + console.log("🔗 Retorno da API de acesso único:", result); + + serverSucsess("Se o e-mail estiver cadastrado, enviamos um link de acesso!"); + setEmail(""); + + } catch (error) { + console.error("❌ Erro ao enviar magic link:", error); + serverError("Erro ao enviar o link de acesso. Tente novamente."); + } + }; + + const validateEmail = (emailValue) => { + let error = ''; + + if (emailValue.trim() === '') { + error = 'O e-mail não pode ficar vazio.'; + } else if (!emailValue.includes('@') || !emailValue.includes('.')) { + error = 'O e-mail deve conter o símbolo "@" e um ponto (".") seguido por uma extensão.'; + } + + // Atualiza o estado de erro específico para o email + setEmailError(error); + return error; +}; + + const handleEmailChange = (e) => { + const newValue = e.target.value; + setEmail(newValue); + if (isTouched) { + validateEmail(newValue); // Valida em tempo real + } + + const { name, value } = e.target; + setConta((prev) => ({ + ...prev, + [name]: value + })); +}; + + const handleEmailBlur = (e) => { + setIsTouched(true); + validateEmail(e.target.value); // Valida ao perder o foco +}; + + return ( + + ); +}; \ No newline at end of file diff --git a/src/pages/Login/Login.jsx b/src/pages/Login/Login.jsx new file mode 100644 index 0000000..d7884fb --- /dev/null +++ b/src/pages/Login/Login.jsx @@ -0,0 +1,234 @@ +import { useState, useRef } from "react"; +import { useNavigate } from "react-router-dom"; +import ReCAPTCHA from "react-google-recaptcha"; +import { setUserId, setUserEmail, setUserRole, setDoctorId, setPatientId, setFullName } from "../../utils/userInfo"; +import "../../assets/css/login.css"; + +export default function Login() { + const [email, setEmail] = useState(''); + const [emailError, setEmailError] = useState(''); + const [password, setPassword] = useState(''); + const [passwordError, setPasswordError] = useState(''); + const [isTouched, setIsTouched] = useState(false); + const [serverError, setServerError] = useState(''); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + + const navigate = useNavigate(); + const [conta, setConta] = useState({ email: "", password: "" }); + const [showPassword, setShowPassword] = useState(false); + + const recaptchaRef = useRef(); + + const togglePasswordVisibility = () => setShowPassword(prev => !prev); + + const validateEmail = (emailValue) => { + let error = ''; + if (emailValue.trim() === '') error = 'O e-mail não pode ficar vazio.'; + else if (!emailValue.includes('@') || !emailValue.includes('.')) error = 'O e-mail deve conter "@" e um ponto.'; + setEmailError(error); + return error; + }; + + const validatePassword = (passwordValue) => { + let error = ''; + const MIN_LENGTH = 3; + if (passwordValue.trim() === '') error = 'A senha não pode ficar vazia.'; + else if (passwordValue.length < MIN_LENGTH) error = `A senha deve ter pelo menos ${MIN_LENGTH} caracteres.`; + setPasswordError(error); + return error; + }; + + const handleEmailChange = (e) => { + const { name, value } = e.target; + setConta(prev => ({ ...prev, [name]: value })); + setEmail(value); + if (isTouched) validateEmail(value); + }; + + const handleEmailBlur = (e) => { + setIsTouched(true); + validateEmail(e.target.value); + }; + + const handlePasswordChange = (e) => { + const { name, value } = e.target; + setConta(prev => ({ ...prev, [name]: value })); + setPassword(value); + if (isTouched) validatePassword(value); + }; + + const handlePasswordBlur = (e) => { + setIsTouched(true); + validatePassword(e.target.value); + }; + + const handleLogin = async (e) => { + e.preventDefault(); + + const recaptchaValue = recaptchaRef.current.getValue(); + if (!recaptchaValue) { + setServerError("Por favor, confirme que você não é um robô."); + return; + } + + setServerError(''); + setEmailError(''); + setPasswordError(''); + + if (validateEmail(conta.email) || validatePassword(conta.password)) return; + + try { + const loginResp = await fetch(`${supabaseUrl}/auth/v1/token?grant_type=password`, { + method: "POST", + headers: { "apikey": supabaseAK, "Content-Type": "application/json" }, + body: JSON.stringify({ email: conta.email, password: conta.password, grant_type: "password" }), + redirect: "follow" + }); + const loginResult = await loginResp.json(); + + if (!loginResult.access_token) { + setServerError("Credenciais inválidas. Verifique seu e-mail e senha."); + return; + } + + localStorage.setItem("access_token", loginResult.access_token); + localStorage.setItem("refresh_token", loginResult.refresh_token); + + const userInfoRes = await fetch(`${supabaseUrl}/functions/v1/user-info`, { + method: "GET", + headers: { "Authorization": `Bearer ${loginResult.access_token}`, "apikey": supabaseAK, "Content-Type": "application/json" }, + redirect: "follow" + }); + const userInfo = await userInfoRes.json(); + + const userData = { + id: userInfo.profile?.id, + email: userInfo.user?.email, + role: userInfo.roles || [], + doctor_id: userInfo.profile?.doctor_id || userInfo.doctor_id || null, + patient_id: userInfo.profile?.patient_id || userInfo.patient_id || null, + full_name: userInfo.profile?.full_name || userInfo.user?.user_metadata?.full_name || userInfo.user?.email?.split('@')[0] || null + }; + + if (userData.id) { + setUserId(userData.id); + setUserEmail(userData.email); + if (userData.doctor_id) setDoctorId(userData.doctor_id); + if (userData.patient_id) setPatientId(userData.patient_id); + if (userData.full_name) setFullName(userData.full_name); + } + + const rolePriority = [ + { role: "admin", path: "/admin/dashboard" }, + { role: "secretaria", path: "/secretaria/" }, + { role: "medico", path: "/medico/dashboard" }, + { role: "user", path: "/patientapp" }, + { role: "paciente", path: "/paciente" }, + ]; + + const matchedRole = rolePriority.find(r => userData.role.includes(r.role)); + if (matchedRole) { + setUserRole(matchedRole.role); + navigate(matchedRole.path); + } else { + setServerError("Usuário sem função atribuída. Contate o administrador."); + } + + } catch (error) { + setServerError("Erro ao conectar ao servidor. Tente novamente."); + } + }; + + return ( +
+
+
+
+
+
+
+
+

Você mais próximo de seu médico

+

Consultas online e acompanhamento em tempo real.

+
+
+
+
+
+
+
+

Agende sem sair de casa

+

O seu atendimento, na medida da sua agenda.

+
+
+
+ +
+
+ MediConnect +
+ +
+

Entre para iniciar a sessão.

+ +
+ +
+ +
+ {emailError &&

{emailError}

} + + +
+ + + +
+ {passwordError &&

{passwordError}

} + {serverError &&

{serverError}

} + + Esqueceu a senha? + + {/* ✅ RECAPTCHA */} +
+ +
+ + + + navigate("/AcessoUnico")}> + Entrar com Link de Acessso Único + +
+
+
+
+
+ ); +} diff --git a/src/pages/Patient/PatientList.jsx b/src/pages/Patient/PatientList.jsx deleted file mode 100644 index 561c30d..0000000 --- a/src/pages/Patient/PatientList.jsx +++ /dev/null @@ -1,264 +0,0 @@ -// PatientList.jsx -import { Link } from "react-router-dom"; -import "../../assets/css/index.css"; -import React, { useState, useEffect, useRef, useLayoutEffect } from "react"; -import { createPortal } from "react-dom"; -import supabase from "../../Supabase"; // se for usar supabase para delete, senão pode remover - -// Componente que renderiza o menu em um portal (document.body) e posiciona em relação ao botão -function DropdownPortal({ anchorEl, isOpen, onClose, className, children }) { - const menuRef = useRef(null); - const [stylePos, setStylePos] = useState({ - position: "absolute", - top: 0, - left: 0, - visibility: "hidden", - zIndex: 1000, - }); - - // Posiciona o menu após renderar (medir tamanho do menu) - useLayoutEffect(() => { - if (!isOpen) return; - if (!anchorEl || !menuRef.current) return; - - const anchorRect = anchorEl.getBoundingClientRect(); - const menuRect = menuRef.current.getBoundingClientRect(); - const scrollY = window.scrollY || window.pageYOffset; - const scrollX = window.scrollX || window.pageXOffset; - - // tenta alinhar à direita do botão (como dropdown-menu-right) - let left = anchorRect.right + scrollX - menuRect.width; - let top = anchorRect.bottom + scrollY; - - // evita sair da esquerda da tela - if (left < 0) left = scrollX + 4; - // se extrapolar bottom, abre para cima - if (top + menuRect.height > window.innerHeight + scrollY) { - top = anchorRect.top + scrollY - menuRect.height; - } - setStylePos({ - position: "absolute", - top: `${Math.round(top)}px`, - left: `${Math.round(left)}px`, - visibility: "visible", - zIndex: 1000, - }); - }, [isOpen, anchorEl, children]); - - // fecha ao clicar fora / ao rolar - useEffect(() => { - if (!isOpen) return; - function handleDocClick(e) { - const menu = menuRef.current; - if (menu && !menu.contains(e.target) && anchorEl && !anchorEl.contains(e.target)) { - onClose(); - } - } - function handleScroll() { - onClose(); - } - document.addEventListener("mousedown", handleDocClick); - // captura scroll em qualquer elemento (true) - document.addEventListener("scroll", handleScroll, true); - return () => { - document.removeEventListener("mousedown", handleDocClick); - document.removeEventListener("scroll", handleScroll, true); - }; - }, [isOpen, onClose, anchorEl]); - - if (!isOpen) return null; - return createPortal( -
e.stopPropagation()} - > - {children} -
, - document.body - ); -} - -function PatientList() { - const [search, setSearch] = useState(""); - const [patients, setPatients] = useState([]); - const [openDropdown, setOpenDropdown] = useState(null); - const anchorRefs = useRef({}); // guarda referência do botão de cada linha - - var requestOptions = { - method: "GET", - redirect: "follow", - }; - - useEffect(() => { - fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes", requestOptions) - .then((response) => response.json()) - .then((result) => { - console.log("API result:", result); - setPatients(result.data || []); - }) - .catch((error) => console.log("error", error)); - }, []); - - // Exemplo simples de delete local (confirmação + remove do state) - const handleDelete = async (id) => { - const confirmDel = window.confirm("Tem certeza que deseja excluir este paciente?"); - if (!confirmDel) return; - - const requestOptions = { - method: 'DELETE', - redirect: 'follow' - }; - - fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes/", requestOptions) - .then(response => response.text()) - .then(result => console.log(result)) - .catch(error => console.log('error', error)); - - // Se quiser apagar no supabase, faça a chamada aqui. - // const { error } = await supabase.from("Patient").delete().eq("id", id); - // if (error) { console.error(error); return; } - - setPatients((prev) => prev.filter((p) => p.id !== id)); - setOpenDropdown(null); - }; - - const filteredPatients = patients.filter((p) => { - if (!p) return false; - const nome = (p.nome || "").toLowerCase(); - const cpf = (p.cpf || "").toLowerCase(); - const email = (p.email || "").toLowerCase(); - const q = search.toLowerCase(); - return nome.includes(q) || cpf.includes(q) || email.includes(q); - }); - - const mascararCPF = (cpf = "") => { - if (cpf.length < 5) return cpf; - const inicio = cpf.slice(0, 3); - const fim = cpf.slice(-2); - return `${inicio}.***.***-${fim}`; - }; - - return ( -
-
-
-
-
-

Lista de Pacientes

- setSearch(e.target.value)} - /> -
-
-
- - Adicionar Paciente - -
-
- -
-
-
- - - - - - - - - - - - - - {filteredPatients.length > 0 ? ( - filteredPatients.map((p) => ( - - - - - - - - - - )) - ) : ( - - - - )} - -
NomeCpfData de NascimentoTelefoneEmailStatusAções
{p.nome}{mascararCPF(p.cpf)}{p.data_nascimento}{p.telefone}{p.email}{p.status} -
- - - setOpenDropdown(null)} - className="dropdown-menu dropdown-menu-right show" - > - {/* { - e.stopPropagation(); - setOpenDropdown(null); - }} - > - Ver Detalhes - */} - - { - e.stopPropagation(); - setOpenDropdown(null); - }} - > - Editar - - - - -
-
- Nenhum paciente encontrado -
-
-
-
-
-
-
- ); -} - -export default PatientList; -; \ No newline at end of file diff --git a/src/pages/PatientApp/AgendarConsultas.jsx b/src/pages/PatientApp/AgendarConsultas.jsx new file mode 100644 index 0000000..5b76490 --- /dev/null +++ b/src/pages/PatientApp/AgendarConsultas.jsx @@ -0,0 +1,561 @@ +import React, { useState, useEffect } from 'react'; +import { + Container, + Grid, + Card, + CardContent, + Typography, + Button, + Avatar, + Box, + Chip, + CircularProgress, + Alert, + Paper, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Stepper, + Step, + StepLabel, + Checkbox, + FormControlLabel +} from '@mui/material'; +import { + ArrowBack, + CalendarToday, + AccessTime, + Person, + LocalHospital, + CheckCircle, + Email, + Sms +} from '@mui/icons-material'; +import { useParams, useNavigate } from 'react-router-dom'; +import { Link } from "react-router-dom"; +import Swal from "sweetalert2"; +import { getAccessToken } from "../../utils/auth.js"; +import { getPatientId } from "../../utils/userInfo"; +import { getUserRole } from '../../utils/userInfo'; + +const AgendarConsulta = () => { + const { medicoId } = useParams(); + const navigate = useNavigate(); + const [medico, setMedico] = useState(null); + const [horariosDisponiveis, setHorariosDisponiveis] = useState([]); + const [loading, setLoading] = useState(true); + const [dataSelecionada, setDataSelecionada] = useState(''); + const [horarioSelecionado, setHorarioSelecionado] = useState(null); + const [modalConfirmacao, setModalConfirmacao] = useState(false); + const [agendando, setAgendando] = useState(false); + const [activeStep, setActiveStep] = useState(0); + const [enviarEmail, setEnviarEmail] = useState(true); + const [enviarSMS, setEnviarSMS] = useState(true); + const [minDate, setMinDate] = useState(""); + const [carregandoHorarios, setCarregandoHorarios] = useState(false); + const [formData, setFormData] = useState({ + scheduled_date: "", + scheduled_time: "", + chief_complaint: "", + patient_notes: "" + }); + let [confirmationModal, setConfirmationModal] = useState(false); + const role = getUserRole(); + const tokenUsuario = getAccessToken(); + const patientId = getPatientId(); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + + const headers = { + "Content-Type": "application/json", + apikey: supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + }; + + const handleConfirmationModal = async () => { + if (!dataSelecionada || !horarioSelecionado) { + alert("Selecione uma data e horário válidos"); + return; + } + + const confirm = window.confirm(` + Confirmar agendamento: + + Médico: Dr. ${medico?.nome} + Especialidade: ${medico?.especialidade} + Data: ${new Date(dataSelecionada).toLocaleDateString('pt-BR')} + Horário: ${horarioSelecionado ? horarioSelecionado.datetime.split("T")[1].substring(0, 5) : ''} + Valor: R$ ${medico?.valorConsulta} + + Deseja confirmar? + `); + + if (confirm) { + await confirmarAgendamento(); + + alert(`Consulta marcada com sucesso! Sua consulta com Dr. ${medico.nome} foi agendada.`); + navigate(`/${role}/consultalist`); + } + }; + + useEffect(() => { + const getToday = () => { + const today = new Date(); + const offset = today.getTimezoneOffset(); + today.setMinutes(today.getMinutes() - offset); + return today.toISOString().split("T")[0]; + }; + + setMinDate(getToday()); + }, []); + useEffect(() => { + carregarMedicoEHorarios(); + }, [medicoId]); + + const carregarMedicoEHorarios = async () => { + setLoading(true); + try { + // Buscar dados do médico + const medicoResponse = await fetch( + `${supabaseUrl}/rest/v1/doctors?id=eq.${medicoId}`, + { headers } + ); + + if (medicoResponse.ok) { + const medicoData = await medicoResponse.json(); + if (medicoData.length > 0) { + const doctorData = medicoData[0]; + setMedico({ + id: doctorData.id, + nome: doctorData.full_name, + especialidade: doctorData.specialty, + valorConsulta: 250, // Valor fixo por enquanto + foto: '', + biografia: doctorData.bio || 'Especialista em ' + doctorData.specialty + }); + } else { + throw new Error('Médico não encontrado'); + } + } else { + throw new Error('Erro ao carregar dados do médico'); + } + + setLoading(false); + } catch (error) { + console.error('Erro ao carregar dados:', error); + setMedico(medicoMock); + setLoading(false); + } + }; + + // Função para buscar horários disponíveis + const fetchHorariosDisponiveis = async (date) => { + if (!medicoId || !date) { + setHorariosDisponiveis([]); + return; + } + + setCarregandoHorarios(true); + + const startDate = `${date}T00:00:00.000Z`; + const endDate = `${date}T23:59:59.999Z`; + + const payload = { + doctor_id: medicoId, + start_date: startDate, + end_date: endDate, + appointment_type: "presencial", + }; + + try { + const response = await fetch( + `${supabaseUrl}/functions/v1/get-available-slots`, + { + method: "POST", + headers, + body: JSON.stringify(payload), + } + ); + + const data = await response.json(); + + console.log("🔍 AgendarConsultas - Resposta da Edge Function:", data); + + if (!response.ok) throw new Error(data.error || "Erro ao buscar horários"); + + // Usar exatamente o mesmo formato do AgendaForm + const slotsDisponiveis = (data?.slots || []).filter((s) => s.available); + + console.log("✅ Slots disponíveis após filtro:", slotsDisponiveis); + console.log("🔍 Todos os slots (antes do filtro):", data?.slots); + console.log("❌ Slots NÃO disponíveis:", (data?.slots || []).filter((s) => !s.available)); + + console.log("✅ AgendarConsultas - Slots disponíveis após filtro:", slotsDisponiveis); + + setHorariosDisponiveis(slotsDisponiveis); + + if (slotsDisponiveis.length === 0) { + alert("Nenhum horário disponível para este dia."); + } + } catch (error) { + console.error("Erro ao buscar horários disponíveis:", error); + setHorariosDisponiveis([]); + alert("Não foi possível obter os horários disponíveis."); + } finally { + setCarregandoHorarios(false); + } + }; + + // Atualizar horários quando a data muda + useEffect(() => { + if (dataSelecionada && medicoId) { + fetchHorariosDisponiveis(dataSelecionada); + } + }, [dataSelecionada, medicoId]); + + const selecionarHorario = (horario) => { + setHorarioSelecionado(horario); + setModalConfirmacao(true); + setActiveStep(0); + }; + + const confirmarAgendamento = async () => { + setAgendando(true); + + try { + if (!horarioSelecionado || !horarioSelecionado.datetime) { + throw new Error("Horário não selecionado corretamente"); + } + + // Usar exatamente o mesmo formato que o AgendaForm + const scheduled_at = horarioSelecionado.datetime; + + const payload = { + patient_id: patientId, + doctor_id: medicoId, + scheduled_at, + duration_minutes: 30, + appointment_type: "presencial", + chief_complaint: formData.chief_complaint || "Consulta agendada pelo paciente", + patient_notes: formData.patient_notes || "", + created_by: patientId, + }; + + const response = await fetch( + `${supabaseUrl}/rest/v1/appointments`, + { + method: "POST", + headers: { + ...headers, + Prefer: "return=representation", + }, + body: JSON.stringify(payload), + } + ); + + if (response.ok) { + const consultaCriada = await response.json(); + console.log("Consulta criada:", consultaCriada); + + setActiveStep(2); + setAgendando(false); + + // Aqui você pode adicionar envio de SMS se necessário + // if (enviarSMS) { + // await sendSMS(telefone, mensagem, patientId); + // } + + } else { + const error = await response.json(); + console.error("Erro da API:", error); + throw new Error("Não foi possível criar a consulta"); + } + + } catch (error) { + console.error('Erro no agendamento:', error); + alert(error.message || "Erro ao realizar agendamento. Tente novamente."); + setAgendando(false); + } + }; + + const finalizarAgendamento = () => { + setModalConfirmacao(false); + navigate(`/${role}/consultalist`); + }; + + // Não precisamos mais da linha datasDisponiveis, pois usamos a Edge Function + const horariosDaData = horariosDisponiveis.filter(h => h.data === dataSelecionada); + + const renderStepContent = (step) => { + switch (step) { + case 0: + return ( + + + Confirme os dados da consulta: + + + + + + Médico: Dr. {medico.nome} + + + + + Especialidade: {medico.especialidade} + + + + + Data: {new Date(dataSelecionada).toLocaleDateString('pt-BR')} + + + + + Horário: {horarioSelecionado ? horarioSelecionado.datetime.split("T")[1].substring(0, 5) : ''} + + + + Valor: R$ {medico.valorConsulta} + + + + + Chegue com 15 minutos de antecedência para o atendimento. + + + ); + + case 1: + return ( + + + Escolha como deseja receber as confirmações: + + + setEnviarEmail(e.target.checked)} + icon={} + checkedIcon={} + /> + } + label="Receber confirmação por E-mail" + sx={{ mb: 2, display: 'block' }} + /> + + setEnviarSMS(e.target.checked)} + icon={} + checkedIcon={} + /> + } + label="Receber confirmação por SMS" + sx={{ display: 'block' }} + /> + + + Você também receberá um lembrete 24 horas antes da consulta. + + + ); + + case 2: + return ( + + + + Consulta Agendada com Sucesso! + + + Sua consulta foi agendada para {new Date(dataSelecionada).toLocaleDateString('pt-BR')} às {horarioSelecionado ? horarioSelecionado.datetime.split("T")[1].substring(0, 5) : ''} + + + A consulta foi adicionada à agenda do Dr. {medico.nome} e as confirmações foram enviadas. + + + ); + + default: + return null; + } + }; + + if (loading) { + return ( + + + Carregando horários... + + ); + } + + if (!medico) { + return ( + + Médico não encontrado + + ); + } + + return ( +
+ + + + {/* Cabeçalho do Médico */} + + + + + {medico.nome.split(' ').map(n => n[0]).join('')} + + + + Dr. {medico.nome} + + + + {medico.biografia} + + + + + +
+
+

Informações do atendimento

+
+
+
+ +
+ setDataSelecionada(e.target.value)} + /> +
+
+
+
+
+ +
+ +
+
+
+
+
+ + setFormData(prev => ({ ...prev, chief_complaint: e.target.value }))} + placeholder="Ex: Dor no peito, consulta de rotina..." + /> +
+
+ + +
+ +
+ +
+ + +
+
+ +
+ +
+
+
+
+ ); +}; + +export default AgendarConsulta; \ No newline at end of file diff --git a/src/pages/PatientApp/MedicosDisponiveis.jsx b/src/pages/PatientApp/MedicosDisponiveis.jsx new file mode 100644 index 0000000..e11a4b7 --- /dev/null +++ b/src/pages/PatientApp/MedicosDisponiveis.jsx @@ -0,0 +1,306 @@ +import React, { useState, useEffect } from 'react'; +import { getAccessToken } from '../../utils/auth'; +import { + Container, + Grid, + Card, + CardContent, + Typography, + Button, + Chip, + Avatar, + Box, + Rating, + CircularProgress, + TextField, + FormControl, + InputLabel, + Select, + MenuItem, + Paper, + InputAdornment, + Divider, + Alert +} from '@mui/material'; +import { + ArrowBack, + CalendarToday, + AccessTime, + MedicalServices, + Search, + FilterList +} from '@mui/icons-material'; +import { useNavigate } from 'react-router-dom'; +const AvatarForm = "/img/AvatarForm.jpg"; +import { getUserRole } from '../../utils/userInfo'; + +const MedicosDisponiveis = () => { + const [medicos, setMedicos] = useState([]); + const [medicosFiltrados, setMedicosFiltrados] = useState([]); + const [loading, setLoading] = useState(true); + const [searchTerm, setSearchTerm] = useState(''); + const [specialtyFilter, setSpecialtyFilter] = useState(''); + const [avaliacaoFilter, setAvaliacaoFilter] = useState(''); + const [valorFilter, setValorFilter] = useState(''); + const navigate = useNavigate(); + const role = getUserRole(); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + + const tokenUsuario = getAccessToken() + var myHeaders = new Headers(); + myHeaders.append( + "apikey", + supabaseAK + ); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + + var requestOptions = { + method: "GET", + headers: myHeaders, + redirect: "follow", + }; + + // buscar médicos + useEffect(() => { + fetch(`${supabaseUrl}/rest/v1/doctors`, requestOptions) + .then((response) => response.json()) + .then((result) => setMedicos(Array.isArray(result) ? result : [])) + .catch((error) => console.log("error", error)); + }, []); + + useEffect(() => { + carregarMedicos(); + }, []); + + useEffect(() => { + aplicarFiltros(); + }, [medicos, searchTerm, specialtyFilter, avaliacaoFilter, valorFilter]); + + const carregarMedicos = async () => { + setLoading(true); + try { + setTimeout(() => { + setLoading(false); + }, 1000); + + } catch (error) { + console.error('Erro ao carregar médicos:', error); + // Fallback para dados mock em caso de erro + setLoading(false); + } + }; + + const specialty = Array.from(new Set(medicos.map(m => m.specialty).filter(Boolean))); + + + const aplicarFiltros = () => { + let filtrados = [...medicos]; + + if (searchTerm) { + filtrados = filtrados.filter(medico => { + const nome = medico.full_name ? medico.full_name.toLowerCase() : ""; + const especialidade = medico.specialty ? medico.specialty.toLowerCase() : ""; + return ( + nome.includes(searchTerm.toLowerCase()) || + especialidade.includes(searchTerm.toLowerCase()) + ); + }); + } + + if (specialtyFilter) { + filtrados = filtrados.filter(medico => + medico.specialty === specialtyFilter + ); + } + + setMedicosFiltrados(filtrados); + }; + + const limparFiltros = () => { + setSearchTerm(''); + setSpecialtyFilter(''); + setAvaliacaoFilter(''); + setValorFilter(''); + }; + + const verHorariosDisponiveis = (medicoId) => { + navigate(`/${role}/agendarconsulta/${medicoId}`); + }; + + if (loading) { + return ( + + + Carregando médicos... + + ); + } + + return ( +
+ + + Médicos Disponíveis + + + Encontre o médico perfeito para sua necessidade - {medicosFiltrados.length} médico(s) encontrado(s) + + + {/* Filtros e Busca */} + + + + Filtros e Busca + + + + + setSearchTerm(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + + + + + Especialidade + + + + + + + + + + + {/* Lista de Médicos */} + + {medicosFiltrados.map((medico) => ( + + + + {/* Parte superior: avatar + texto */} + + + {medico.full_name.split(' n[0]).join('')} + src={AvatarForm} + style={{ width: '80px', height: '80px', objectFit: 'cover' }}> + + + + + {medico.full_name} + + + } + label={medico.specialty} + color="primary" + variant="outlined" + size="small" + sx={{ mt: 1 }} + /> + + + {/* Botão na parte inferior */} + + + + + ))} + + + {medicosFiltrados.length === 0 && !loading && ( + + + Nenhum médico encontrado com os filtros selecionados + + + + )} + +
+ ); +}; + +export default MedicosDisponiveis; \ No newline at end of file diff --git a/src/pages/PatientApp/PatientApp.jsx b/src/pages/PatientApp/PatientApp.jsx new file mode 100644 index 0000000..d363392 --- /dev/null +++ b/src/pages/PatientApp/PatientApp.jsx @@ -0,0 +1,80 @@ +import { Outlet, NavLink, useLocation } from "react-router-dom"; +import './../../assets/css/index.css' +import { useState } from "react"; +import { getAccessToken} from "../../utils/auth"; +import { getUserRole } from '../../utils/userInfo.js'; +import Sidebar from "../../components/layouts/Sidebar.jsx"; + +export default function PatientApp() { + const [isSidebarOpen, setSidebarOpen] = useState(false); + const location = useLocation(); + + // 2. Adicione a função para alternar o estado + const toggleSidebar = () => { + setSidebarOpen(!isSidebarOpen); + }; + + // 3. Crie a string de classe que será aplicada dinamicamente + const mainWrapperClass = isSidebarOpen ? 'main-wrapper sidebar-open' : 'main-wrapper'; + + // Função para verificar se a rota está ativa + const isActive = (path) => { + const currentPath = location.pathname; + + // Verificação exata primeiro + if (currentPath === path) return true; + + // Verificação de subrotas (ex: /patientapp/meuslaudos/view/123) + if (currentPath.startsWith(path + '/')) return true; + + // Verificações específicas para páginas de edição/criação + if (path === '/patientapp/medicosdisponiveis' && ( + currentPath.includes('/patientapp/agendar/') || + currentPath.includes('/patientapp/consultaform') + )) return true; + + if (path === '/patientapp/minhasconsultas' && ( + currentPath.includes('/patientapp/consulta/') || + currentPath.includes('/patientapp/editconsulta/') + )) return true; + + if (path === '/patientapp/meuslaudos' && ( + currentPath.includes('/patientapp/laudo/') || + currentPath.includes('/patientapp/viewlaudo/') + )) return true; + + return false; + }; + const token = getAccessToken(); + const user = getUserRole(); + // Verificação de autenticação + if (!token) { + return ; + } + + // Verificação de role + if (user !== 'paciente') { + return ( +
+
+
+

❌ Acesso Negado

+

Apenas administradores podem acessar esta área.

+ +
+
+
+ ); + } + return ( +
+ + +
+ ); +} \ No newline at end of file diff --git a/src/pages/PatientApp/PatientDashboard.jsx b/src/pages/PatientApp/PatientDashboard.jsx new file mode 100644 index 0000000..6ab6530 --- /dev/null +++ b/src/pages/PatientApp/PatientDashboard.jsx @@ -0,0 +1,887 @@ +import React, { useState, useEffect } from "react"; +import { Link } from "react-router-dom"; +import { getAccessToken } from "../../utils/auth.js"; +import { getFullName, getUserId } from "../../utils/userInfo"; +import "../../assets/css/index.css"; +import { getUserRole } from "../../utils/userInfo"; + +const AvatarForm = "/img/AvatarForm.jpg"; +const banner = "/img/banner.png"; + +export default function PatientDashboard() { + const [appointments, setAppointments] = useState([]); + const [reports, setReports] = useState([]); + const [nextConsultations, setNextConsultations] = useState([]); + const [recentExams, setRecentExams] = useState([]); + const [loading, setLoading] = useState(true); + const [currentTime, setCurrentTime] = useState(new Date()); + const role = getUserRole(); + const tokenUsuario = getAccessToken(); + const userId = getUserId(); + const patientName = getFullName() || "Paciente"; + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + + const API_KEY = supabaseAK; + + const requestOptions = { + method: "GET", + headers: { + apikey: API_KEY, + Authorization: `Bearer ${tokenUsuario}`, + }, + redirect: "follow", + }; + + useEffect(() => { + const loadPatientData = async () => { + try { + setLoading(true); + console.log("🔄 Carregando dados do paciente...", { userId, tokenUsuario: !!tokenUsuario }); + + // Buscar todas as consultas primeiro (sem filtrar por patient_id se não existir na tabela) + const appointmentsResponse = await fetch( + `${supabaseUrl}/rest/v1/appointments`, + requestOptions + ); + + const reportsResponse = await fetch( + `${supabaseUrl}/rest/v1/reports`, + requestOptions + ); + + const doctorsResponse = await fetch( + `${supabaseUrl}/rest/v1/doctors?select=id,full_name`, + requestOptions + ); + + console.log("📡 Status das respostas:", { + appointments: appointmentsResponse.status, + reports: reportsResponse.status, + doctors: doctorsResponse.status + }); + + const [appointmentsData, reportsData, doctorsData] = await Promise.all([ + appointmentsResponse.json(), + reportsResponse.json(), + doctorsResponse.json() + ]); + + console.log("📊 Dados recebidos:", { + appointments: appointmentsData, + reports: reportsData, + doctors: doctorsData + }); + + const appointmentsArr = Array.isArray(appointmentsData) ? appointmentsData : []; + const reportsArr = Array.isArray(reportsData) ? reportsData : []; + const doctorsArr = Array.isArray(doctorsData) ? doctorsData : []; + + // Filtrar consultas por patient_id (se o campo existir) + const patientAppointments = appointmentsArr.filter(apt => + apt.patient_id === userId || + apt.patient_id === parseInt(userId) || + // Se não tiver patient_id, mostrar algumas para demonstração + !apt.patient_id + ); + + // Filtrar relatórios por patient_id (se o campo existir) + const patientReports = reportsArr.filter(rep => + rep.patient_id === userId || + rep.patient_id === parseInt(userId) || + // Se não tiver patient_id, mostrar alguns para demonstração + !rep.patient_id + ); + + // Enriquecer consultas com nomes dos médicos + const enrichedAppointments = patientAppointments.map(appointment => { + const doctor = doctorsArr.find(doc => doc.id === appointment.doctor_id); + return { + ...appointment, + doctor_name: doctor ? doctor.full_name : 'Médico não informado' + }; + }); + + console.log("✅ Dados processados:", { + enrichedAppointments, + patientReports, + totalDoctors: doctorsArr.length + }); + + setAppointments(enrichedAppointments); + setReports(patientReports); + + // Processar dados + console.log("🔥 TESTE: Chamando processNextConsultations com:", enrichedAppointments.length, "consultas"); + + // FORÇAR para teste + if (enrichedAppointments.length === 0) { + forceShowConsultations(); + } else { + // Filtrar consultas não canceladas + const nonCancelledConsultations = enrichedAppointments.filter(apt => + apt.status !== 'cancelled' && + apt.status !== 'cancelada' && + apt.status !== 'canceled' + ); + + console.log("📋 Consultas não canceladas:", nonCancelledConsultations.length, "de", enrichedAppointments.length); + + if (nonCancelledConsultations.length > 0) { + // Ordenar por proximidade da data atual (mais próximas primeiro) + const today = new Date(); + today.setHours(0, 0, 0, 0); + + const sortedByProximity = nonCancelledConsultations + .map(apt => { + const dateField = apt.scheduled_at || apt.date; + const timeField = apt.time; + + if (dateField) { + let consultationDateTime; + + if (dateField.includes('T')) { + // Data já inclui horário + consultationDateTime = new Date(dateField); + } else { + // Combinar data com horário + consultationDateTime = new Date(dateField); + if (timeField) { + const [hours, minutes] = timeField.split(':'); + consultationDateTime.setHours(parseInt(hours), parseInt(minutes), 0, 0); + } else { + consultationDateTime.setHours(12, 0, 0, 0); // Default meio-dia se não houver horário + } + } + + const now = new Date(); + const diffInMinutes = Math.abs((consultationDateTime - now) / (1000 * 60)); + return { ...apt, proximityScore: diffInMinutes, consultationDateTime }; + } + return { ...apt, proximityScore: 999999 }; // Consultas sem data vão para o final + }) + .sort((a, b) => a.proximityScore - b.proximityScore) + .slice(0, 2); + + console.log("✅ Mostrando 2 consultas mais próximas da data atual:", sortedByProximity); + setNextConsultations(sortedByProximity); + } else { + console.log("⚠️ Todas as consultas estão canceladas - usando dados de teste"); + forceShowConsultations(); + } + } + + processRecentExams(patientReports); + + } catch (error) { + console.error("❌ Erro ao carregar dados do paciente:", error); + } finally { + setLoading(false); + } + }; + + if (tokenUsuario) { + loadPatientData(); + } + }, [userId, tokenUsuario]); + + // Processar próximas consultas + const processNextConsultations = (appointments) => { + console.log("🔄 Processando consultas:", appointments); + console.log("📊 Total de consultas recebidas:", appointments.length); + + // Análise detalhada de cada consulta + appointments.forEach((apt, index) => { + console.log(`📋 Consulta ${index + 1}:`, { + id: apt.id, + scheduled_at: apt.scheduled_at, + date: apt.date, + time: apt.time, + doctor_name: apt.doctor_name, + status: apt.status + }); + }); + + // Data de hoje em formato string para comparação + const today = new Date(); + const todayString = today.toISOString().split('T')[0]; // YYYY-MM-DD + + console.log("� Data de hoje (string):", todayString); + + // Filtrar consultas futuras (incluindo hoje) + const futureConsultations = appointments.filter(apt => { + // Usar scheduled_at como data principal + const dateField = apt.scheduled_at || apt.date; + + if (!dateField) { + console.log("⚠️ Consulta sem data:", apt.id); + return false; + } + + // Normalizar a data da consulta + let consultationDate = dateField; + + // Se a data contém horário, pegar apenas a parte da data + if (consultationDate.includes('T')) { + consultationDate = consultationDate.split('T')[0]; + } + + const isFuture = consultationDate >= todayString; + console.log(`📅 Consulta ${apt.id}: ${consultationDate} >= ${todayString} = ${isFuture}`); + + return isFuture; + }); + + console.log("🔮 Consultas futuras encontradas:", futureConsultations.length); + console.log("📋 Lista de consultas futuras:", futureConsultations); + + // Mostrar as 2 consultas mais próximas do horário atual (futuras ou passadas) + const consultationsWithProximity = appointments + .map(apt => { + const dateField = apt.scheduled_at || apt.date; + const timeField = apt.time; + + if (dateField) { + let consultationDateTime; + + if (dateField.includes('T')) { + // Data já inclui horário + consultationDateTime = new Date(dateField); + } else { + // Combinar data com horário + consultationDateTime = new Date(dateField); + if (timeField) { + const [hours, minutes] = timeField.split(':'); + consultationDateTime.setHours(parseInt(hours), parseInt(minutes), 0, 0); + } else { + consultationDateTime.setHours(12, 0, 0, 0); // Default meio-dia se não houver horário + } + } + + const now = new Date(); + const diffInMinutes = Math.abs((consultationDateTime - now) / (1000 * 60)); + return { ...apt, proximityScore: diffInMinutes, consultationDateTime }; + } + return { ...apt, proximityScore: 999999 }; // Consultas sem data vão para o final + }) + .sort((a, b) => a.proximityScore - b.proximityScore) + .slice(0, 2); + + console.log("✅ 2 consultas mais próximas da data atual:", consultationsWithProximity); + setNextConsultations(consultationsWithProximity); + }; + + // FUNÇÃO DE TESTE - FORÇAR EXIBIÇÃO + + // Processar exames recentes + const processRecentExams = (reports) => { + console.log("🔬 Processando exames:", reports); + + // Ordenar por data de criação (mais recentes primeiro) + const recent = reports + .filter(report => report.created_at) // Apenas com data válida + .sort((a, b) => new Date(b.created_at) - new Date(a.created_at)) + .slice(0, 5); + + console.log("✅ Exames recentes processados:", recent); + setRecentExams(recent); + }; + + // Atualizar relógio + useEffect(() => { + const timer = setInterval(() => { + setCurrentTime(new Date()); + }, 1000); + return () => clearInterval(timer); + }, []); + + // Funções auxiliares para status das consultas + const getStatusColor = (status) => { + switch (status) { + case 'confirmed': case 'confirmada': return 'bg-success'; + case 'pending': case 'pendente': return 'bg-warning'; + case 'cancelled': case 'cancelada': return 'bg-danger'; + case 'completed': case 'finalizada': return 'bg-info'; + default: return 'bg-primary'; + } + }; + + const getStatusBorderColor = (status) => { + switch (status) { + case 'confirmed': case 'confirmada': return '#28a745'; + case 'pending': case 'pendente': return '#ffc107'; + case 'cancelled': case 'cancelada': return '#dc3545'; + case 'completed': case 'finalizada': return '#17a2b8'; + default: return '#007bff'; + } + }; + + const getStatusIcon = (status) => { + switch (status) { + case 'confirmed': case 'confirmada': return 'fa-check'; + case 'pending': case 'pendente': return 'fa-clock-o'; + case 'cancelled': case 'cancelada': return 'fa-times'; + case 'completed': case 'finalizada': return 'fa-check-circle'; + default: return 'fa-calendar'; + } + }; + + + + + + // Funções auxiliares para status dos exames + const getExamBorderColor = (status) => { + switch (status) { + case 'completed': case 'finalizado': return '#28a745'; + case 'draft': case 'rascunho': return '#ffc107'; + case 'pending': case 'pendente': return '#17a2b8'; + default: return '#6c757d'; + } + }; + + const getExamIconColor = (status) => { + switch (status) { + case 'completed': case 'finalizado': return 'bg-success'; + case 'draft': case 'rascunho': return 'bg-warning'; + case 'pending': case 'pendente': return 'bg-info'; + default: return 'bg-secondary'; + } + }; + + const getExamIcon = (status) => { + switch (status) { + case 'completed': case 'finalizado': return 'fa-check'; + case 'draft': case 'rascunho': return 'fa-clock-o'; + case 'pending': case 'pendente': return 'fa-file-text'; + default: return 'fa-file-o'; + } + }; + + const getExamBadgeClass = (status) => { + switch (status) { + case 'completed': case 'finalizado': return 'bg-success'; + case 'draft': case 'rascunho': return 'bg-warning'; + case 'pending': case 'pendente': return 'bg-info'; + default: return 'bg-secondary'; + } + }; + + const getExamStatusText = (status) => { + switch (status) { + case 'completed': case 'finalizado': return 'Concluído'; + case 'draft': case 'rascunho': return 'Em análise'; + case 'pending': case 'pendente': return 'Disponível'; + default: return 'Processando'; + } + }; + + // Estatísticas calculadas baseadas nos dados reais da API + demonstração (apenas consultas não canceladas) + const nonCancelledAppointments = appointments.filter(apt => + apt.status !== 'cancelled' && + apt.status !== 'cancelada' && + apt.status !== 'canceled' + ); + + const totalConsultas = nonCancelledAppointments.length > 0 ? nonCancelledAppointments.length : 5; + const consultasRealizadas = nonCancelledAppointments.length > 0 + ? nonCancelledAppointments.filter(apt => apt.status === 'completed' || apt.status === 'finalizada').length + : 3; + const proximasConsultas = nextConsultations.length; + const examesRealizados = reports.length > 0 ? reports.length : 3; + const [previewUrl, setPreviewUrl] = useState(AvatarForm); + useEffect(() => { + const loadAvatar = async () => { + if (!userId) return; + + const myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + + const requestOptions = { + headers: myHeaders, + method: 'GET', + redirect: 'follow' + }; + + try { + const response = await fetch(`${supabaseUrl}/storage/v1/object/avatars/${userId}/avatar.png`, requestOptions); + + if (response.ok) { + const blob = await response.blob(); + const imageUrl = URL.createObjectURL(blob); + setPreviewUrl(imageUrl); + return; // Avatar encontrado + } + } catch (error) { + + } + + // Se chegou até aqui, não encontrou avatar - mantém o padrão + + }; + + loadAvatar(); + }, [userId]); + + + if (loading) { + return ( +
+
+
+
+
+ Carregando... +
+

Carregando dashboard...

+
+
+
+
+ ); + } + + return ( +
+
+ {/* Header com informações do paciente */} +
+
+
+
+
+
+

👋 Olá, {patientName}!

+

Acompanhe suas consultas, resultados e tudo o que precisa em um só lugar. +Cuide-se, e deixe o resto com a gente 💙

+ + 🕒 {currentTime.toLocaleString('pt-BR')} + +
+
+ Avatar +
+
+
+
+
+
+ + {/* Cards de estatísticas */} +
+
+
+ + + +
+

{totalConsultas}

+ Total Consultas +
+
+
+ +
+
+ + + +
+

{proximasConsultas}

+ Próximas Consultas +
+
+
+ +
+
+ + + +
+

{examesRealizados}

+ Laudos +
+
+
+ +
+
+ + + +
+

{consultasRealizadas}

+ Consultas Realizadas +
+
+
+
+ + {/* Ações rápidas */} +
+
+
+ +
+
+
+ + + Agendar Consulta + +
+
+ + + Minhas Consultas + +
+
+ + + Meus Laudos + +
+
+ +
+
+
+
+
+
+ +
+ {/* Próximas Consultas */} +
+
+
+

📅 Próximas Consultas

+ + Ver todas + +
+
+ {nextConsultations.length > 0 ? ( +
+ {nextConsultations.map((consultation, index) => ( +
+
+
+
+ +
+
+
+
{consultation.doctor_name || 'Médico não informado'}
+ + {(() => { + const dateToShow = consultation.scheduled_at || consultation.date; + if (dateToShow) { + // Extrair data e hora sem conversão de fuso horário + let dateStr = ''; + let timeStr = ''; + + if (dateToShow.includes('T')) { + // Formato ISO: 2025-11-15T14:30:00 + const [datePart, timePart] = dateToShow.split('T'); + const [year, month, day] = datePart.split('-'); + dateStr = `${day}/${month}/${year}`; + + if (timePart) { + const [hour, minute] = timePart.split(':'); + timeStr = `${hour}:${minute}`; + } + } else { + // Formato simples: 2025-11-15 + const [year, month, day] = dateToShow.split('-'); + dateStr = `${day}/${month}/${year}`; + } + + // Usar horário do campo time se existir, senão usar o extraído + const finalTime = consultation.time || timeStr || 'Horário a confirmar'; + + return `${dateStr} às ${finalTime}`; + } + return 'Data a confirmar'; + })()} + +
+ + {consultation.status === 'requested' ? ( + <> + + Solicitado + + ) : consultation.status === 'confirmed' ? ( + <> + + Confirmado + + ) : consultation.status === 'completed' ? ( + <> + + Concluído + + ) : consultation.status === 'cancelled' ? ( + <> + + Cancelado + + ) : ( + <> + + {consultation.status} + + )} + +
+
+ ))} +
+ ) : ( +
+ +

Nenhuma consulta agendada

+ + Agendar primeira consulta + +
+ )} + + {/* Botão para ver mais */} +
+ + + Agendar nova consulta + +
+
+
+
+ + {/* Exames Recentes */} +
+
+
+

🔬 Laudos Recentes

+ + Ver todos + +
+
+ {recentExams.length > 0 ? ( +
+ {recentExams.map((exam) => ( +
+
+
+
+ +
+
+
+
{exam.exam || 'Exame'}
+ + {new Date(exam.created_at).toLocaleDateString('pt-BR')} - {exam.requested_by || 'Médico não informado'} + +
+ + {exam.status === 'draft' ? ( + <> + + Rascunho + + ) : exam.status === 'completed' ? ( + <> + + Concluído + + ) : ( + exam.status + )} + +
+
+ ))} +
+ ) : ( +
+ +

Nenhum laudo realizado ainda

+
+ )} + +
+ + + Ver todos os laudos + +
+
+
+
+
+ + {/* Informações de saúde */} +
+
+
+
+

💡 Dicas de Saúde

+
+
+
+
+
+ +
Exercite-se Regularmente
+

30 minutos de atividade física por dia fazem a diferença

+
+
+
+
+ +
Alimentação Saudável
+

Consuma frutas e vegetais todos os dias

+
+
+
+
+ +
Durma Bem
+

7-8 horas de sono são essenciais para sua saúde

+
+
+
+
+
+
+
+
+
+ ); +} + +// CSS customizado para o PatientDashboard +const style = document.createElement('style'); +style.textContent = ` + .timeline { + position: relative; + } + + .timeline-item { + position: relative; + } + + .timeline-marker { + width: 12px; + height: 12px; + border-radius: 50%; + margin-top: 6px; + flex-shrink: 0; + } + + .timeline-content { + background: #f8f9fa; + padding: 15px; + border-radius: 10px; + border-left: 3px solid #007bff; + width: 100%; + } + + .exam-icon { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + } + + .health-tip { + transition: transform 0.2s ease, box-shadow 0.2s ease; + } + + .health-tip:hover { + transform: translateY(-5px); + box-shadow: 0 8px 25px rgba(0,0,0,0.15); + } + + .dash-widget { + transition: transform 0.2s ease; + } + + .dash-widget:hover { + transform: translateY(-3px); + } + + .user-info-banner { + position: relative; + overflow: hidden; + } + + .user-info-banner::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: url('data:image/svg+xml,'); + pointer-events: none; + } +`; + +if (!document.head.querySelector('[data-patient-dashboard-styles]')) { + style.setAttribute('data-patient-dashboard-styles', 'true'); + document.head.appendChild(style); +} \ No newline at end of file diff --git a/src/pages/Schedule/AddSchedule.jsx b/src/pages/Schedule/AddSchedule.jsx deleted file mode 100644 index 900cdfa..0000000 --- a/src/pages/Schedule/AddSchedule.jsx +++ /dev/null @@ -1,242 +0,0 @@ -import "../../assets/css/index.css" - -function AddSchedule(){ - return ( -
- - - {/* Conteúdo da página */} -
-
-
-
-

Adicionar Agenda

-
-
- -
-
-
-
-
-
- - -
-
- -
-
- - -
-
-
- -
-
-
- - -
-
- -
-
- - -
-
-
- -
- - -
- -
- -
- - -
-
- - -
-
- -
- -
-
-
-
-
-
- -
-
- ); -}; - -export default AddSchedule; diff --git a/src/pages/Schedule/DoctorSchedule.jsx b/src/pages/Schedule/DoctorSchedule.jsx deleted file mode 100644 index 4334806..0000000 --- a/src/pages/Schedule/DoctorSchedule.jsx +++ /dev/null @@ -1,107 +0,0 @@ -import "../../assets/css/index.css" -import { Link } from "react-router-dom"; - -function Doctorschedule() { - return ( -
-
-
-
-
-

Agenda médica

-
-
- - Adicionar agenda - -
-
- -
-
-
- - - - - - - - - - - - - - - - - - - - - -
NomeDepartamentoDias disponíveisHorário disponívelStatusAção
- user{" "} - Henry Daniels - CardiologistaSegunda-feira, Terça-feira, Quinta-feira10:00 AM - 7:00 PM - Ativo - - -
-
-
-
-
-
- - {/* Modal de exclusão */} - -
- ); -} -export default Doctorschedule diff --git a/src/pages/SecretariaApp/SecretariaApp.jsx b/src/pages/SecretariaApp/SecretariaApp.jsx new file mode 100644 index 0000000..926eea0 --- /dev/null +++ b/src/pages/SecretariaApp/SecretariaApp.jsx @@ -0,0 +1,93 @@ +import { Outlet, NavLink, useLocation } from "react-router-dom"; +import './../../assets/css/index.css' +import Navbar from '../../components/layouts/Navbar' +import React, { useState } from 'react'; +import { Link } from "react-router-dom"; +import Chatbox from '../../components/chat/Chatbox'; +import AccessibilityWidget from '../../components/AccessibilityWidget'; +import { useResponsive } from '../../utils/useResponsive'; +import { getAccessToken } from '../../utils/auth.js'; +import { getUserRole } from '../../utils/userInfo.js'; +import { Navigate } from 'react-router-dom'; +import Sidebar from "../../components/layouts/Sidebar.jsx"; + + +function SecretariaApp() { + // 1. Adicione o estado para controlar a sidebar + const [isSidebarOpen, setSidebarOpen] = useState(false); + const location = useLocation(); + + // 2. Adicione a função para alternar o estado + const toggleSidebar = () => { + setSidebarOpen(!isSidebarOpen); + }; + + // 3. Crie a string de classe que será aplicada dinamicamente + const mainWrapperClass = isSidebarOpen ? 'main-wrapper sidebar-open' : 'main-wrapper'; + + // Função para verificar se a rota está ativa + const isActive = (path) => { + const currentPath = location.pathname; + + // Verificação exata primeiro + if (currentPath === path) return true; + + // Verificação de subrotas (ex: /secretaria/pacientelista/edit/123) + if (currentPath.startsWith(path + '/')) return true; + + // Verificações específicas para páginas de edição/criação + if (path === '/secretaria/pacientelista' && ( + currentPath.includes('/secretaria/pacienteeditar/') || + currentPath.includes('/secretaria/pacienteform') + )) return true; + + if (path === '/secretaria/medicoslista' && ( + currentPath.includes('/secretaria/medicoseditar/') + )) return true; + + if (path === '/secretaria/secretariaconsultalist' && ( + currentPath.includes('/secretaria/editarconsulta/') || + currentPath.includes('/secretaria/adicionarconsulta') || + currentPath.includes('/secretaria/consulta/') + )) return true; + if (path === '/secretaria/agendamedica' && ( + currentPath.includes('/secretaria/adicionaragenda') + )) return true; + return false; + }; + const token = getAccessToken(); + const user = getUserRole(); + // Verificação de autenticação + if (!token) { + return ; + } + + // Verificação de role + if (user !== 'secretaria') { + return ( +
+
+
+

❌ Acesso Negado

+

Apenas secretárias podem acessar esta área.

+ +
+
+
+ ); + } + return ( +
+ + + +
+ ); +} + +export default SecretariaApp; \ No newline at end of file diff --git a/src/pages/SecretariaApp/SecretariaDashboard.jsx b/src/pages/SecretariaApp/SecretariaDashboard.jsx new file mode 100644 index 0000000..f67da59 --- /dev/null +++ b/src/pages/SecretariaApp/SecretariaDashboard.jsx @@ -0,0 +1,845 @@ +import React, { useState, useEffect } from "react"; +import { Link } from "react-router-dom"; +import { getAccessToken } from "../../utils/auth.js"; +import "../../assets/css/index.css"; +import { getFullName, getUserId } from "../../utils/userInfo"; +const AvatarForm = "/img/AvatarForm.jpg"; +const banner = "/img/banner.png"; +import { + BarChart, + Bar, + PieChart, + Pie, + Cell, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer +} from 'recharts'; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip as ChartTooltip, + Legend as ChartLegend, +} from 'chart.js'; +import { Bar as ChartJSBar } from 'react-chartjs-2'; + +// Registrar componentes do Chart.js +ChartJS.register( + CategoryScale, + LinearScale, + BarElement, + Title, + ChartTooltip, + ChartLegend +); + +// Componente do gráfico de consultas mensais +const ConsultasMensaisChart = ({ data }) => ( + + + + + + [`${value} consultas`, 'Total']} + /> + + + + +); + +// Componente do gráfico de pacientes ativos/inativos +const AtivosInativosChart = ({ data }) => ( + + + `${name} ${(percent * 100).toFixed(0)}%`} + outerRadius={120} + fill="#8884d8" + dataKey="value" + > + {data.map((entry, index) => ( + + ))} + + [`${value} pacientes`, name]} + /> + + + +); + +// Componente do gráfico de consultas por médico com Chart.js (horizontal) +const ConsultasPorMedicoChart = ({ data }) => { + if (!data || data.length === 0) { + return ( +
+
+ +

Nenhum dado de médicos encontrado

+
+
+ ); + } + + const chartData = { + labels: data.map(item => item.medico), + datasets: [ + { + label: 'Consultas', + data: data.map(item => item.consultas), + backgroundColor: '#28a745', + borderColor: '#1e7e34', + borderWidth: 1, + borderRadius: 4, + borderSkipped: false, + } + ] + }; + + const options = { + indexAxis: 'y', + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false, + }, + tooltip: { + backgroundColor: '#f8f9fa', + titleColor: '#343a40', + bodyColor: '#343a40', + borderColor: '#dee2e6', + borderWidth: 1, + callbacks: { + label: function (context) { + return `${context.parsed.x} consultas`; + } + } + } + }, + scales: { + x: { + beginAtZero: true, + grid: { + color: '#e9ecef', + drawBorder: false, + }, + ticks: { + color: '#6c757d', + font: { + size: 12 + } + } + }, + y: { + grid: { + display: false, + }, + ticks: { + color: '#6c757d', + font: { + size: 11 + }, + maxRotation: 0, + } + } + }, + animation: { + duration: 1000, + easing: 'easeInOutQuart' + }, + layout: { + padding: { + left: 20, + right: 30, + top: 10, + bottom: 10 + } + }, + elements: { + bar: { + borderRadius: 4, + } + } + }; + + return ( +
+ +
+ ); +}; + +// Componente do gráfico de taxa de cancelamentos +const TaxaCancelamentosChart = ({ data }) => { + + if (!data || data.length === 0) { + return ( +
+
+ +

Nenhum dado de cancelamentos encontrado

+
+
+ ); + } + + // Preparar dados para Chart.js (gráfico empilhado) + const chartData = { + labels: data.map(item => item.mes), + datasets: [ + { + label: 'Realizadas', + data: data.map(item => item.realizadas), + backgroundColor: '#dee2e6', + borderColor: '#adb5bd', + borderWidth: 1, + borderRadius: 4, + borderSkipped: false, + }, + { + label: 'Canceladas', + data: data.map(item => item.canceladas), + backgroundColor: '#dc3545', + borderColor: '#c82333', + borderWidth: 1, + borderRadius: 4, + borderSkipped: false, + } + ] + }; + + const options = { + responsive: true, + maintainAspectRatio: false, + scales: { + x: { + stacked: true, + grid: { + display: false, + }, + ticks: { + color: '#6c757d', + font: { + size: 12 + } + } + }, + y: { + stacked: true, + beginAtZero: true, + max: 100, + grid: { + color: '#e9ecef', + drawBorder: false, + }, + ticks: { + color: '#6c757d', + font: { + size: 12 + }, + callback: function (value) { + return value + '%'; + } + } + } + }, + plugins: { + legend: { + display: true, + position: 'top', + labels: { + color: '#495057', + font: { + size: 12 + }, + usePointStyle: true, + pointStyle: 'rect' + } + }, + tooltip: { + backgroundColor: '#f8f9fa', + titleColor: '#343a40', + bodyColor: '#343a40', + borderColor: '#dee2e6', + borderWidth: 1, + callbacks: { + label: function (context) { + const datasetLabel = context.dataset.label; + const value = context.parsed.y; + const dataIndex = context.dataIndex; + const monthData = data[dataIndex]; + + if (datasetLabel === 'Canceladas') { + const numConsultas = Math.round(monthData.total * value / 100); + return `${datasetLabel}: ${value}% (${numConsultas} de ${monthData.total} consultas)`; + } else { + const numConsultas = Math.round(monthData.total * value / 100); + return `${datasetLabel}: ${value}% (${numConsultas} consultas)`; + } + }, + title: function (context) { + const monthData = data[context[0].dataIndex]; + return `${context[0].label} ${new Date().getFullYear()} - Total: ${monthData.total} consultas`; + }, + afterBody: function (context) { + const monthData = data[context[0].dataIndex]; + if (monthData.total === 0) { + return ['Nenhuma consulta registrada neste mês']; + } + return []; + } + } + } + }, + animation: { + duration: 1000, + easing: 'easeInOutQuart' + }, + layout: { + padding: { + left: 10, + right: 10, + top: 10, + bottom: 10 + } + } + }; + + return ( +
+ +
+ ); +}; + +function SecretariaDashboard() { + const [patients, setPatients] = useState([]); + const [doctors, setDoctors] = useState([]); + const [consulta, setConsulta] = useState([]); + const [countPaciente, setCountPaciente] = useState(0); + const [countMedico, setCountMedico] = useState(0); + // Estados para os gráficos + const [consultasMensaisDataReal, setConsultasMensaisDataReal] = useState([]); + const [pacientesStatusDataReal, setPacientesStatusDataReal] = useState([]); + const [consultasPorMedicoData, setConsultasPorMedicoData] = useState([]); + const [taxaCancelamentosData, setTaxaCancelamentosData] = useState([]); + const [appointments, setAppointments] = useState([]); + const [loading, setLoading] = useState(true); + const [currentTime, setCurrentTime] = useState(new Date()); + const [previewUrl, setPreviewUrl] = useState(AvatarForm); + + const tokenUsuario = getAccessToken(); + const userId = getUserId(); + + const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; + const supabaseAK = import.meta.env.VITE_SUPABASE_ANON_KEY; + + const requestOptions = { + method: "GET", + headers: { + apikey: + supabaseAK, + Authorization: `Bearer ${tokenUsuario}`, + }, + redirect: "follow", + }; + + useEffect(() => { + const loadData = async () => { + try { + setLoading(true); + + // Buscar pacientes + const patientsResponse = await fetch( + `${supabaseUrl}/rest/v1/patients`, + requestOptions + ); + const patientsData = await patientsResponse.json(); + const patientsArr = Array.isArray(patientsData) ? patientsData : []; + setPatients(patientsArr); + setConsulta(patientsArr); + setCountPaciente(patientsArr.length); + + // Processar status dos pacientes + if (patientsArr.length > 0) { + const ativos = patientsArr.filter(p => p.active !== false).length; + const inativos = patientsArr.length - ativos; + + const statusData = [ + { name: 'Ativos', value: ativos, color: '#007bff' }, + { name: 'Inativos', value: inativos, color: '#ffa500' } + ]; + + setPacientesStatusDataReal(statusData); + } + + // Buscar médicos + const doctorsResponse = await fetch( + `${supabaseUrl}/rest/v1/doctors`, + requestOptions + ); + const doctorsData = await doctorsResponse.json(); + const doctorsArr = Array.isArray(doctorsData) ? doctorsData : []; + setDoctors(doctorsArr); + setCountMedico(doctorsArr.length); + + // Buscar consultas + const appointmentsResponse = await fetch( + `${supabaseUrl}/rest/v1/appointments`, + requestOptions + ); + const appointmentsData = await appointmentsResponse.json(); + const appointmentsArr = Array.isArray(appointmentsData) ? appointmentsData : []; + setAppointments(appointmentsArr); + + // Processar dados dos gráficos + processConsultasMensais(appointmentsArr); + await processConsultasPorMedico(appointmentsArr, doctorsArr); + processTaxaCancelamentos(appointmentsArr); + + + } catch (error) { + + } finally { + setLoading(false); + } + }; + + loadData(); + }, []); + + // useEffect para atualizar o relógio em tempo real + useEffect(() => { + const timer = setInterval(() => { + setCurrentTime(new Date()); + }, 1000); + + return () => clearInterval(timer); + }, []); + + // useEffect para carregar avatar do usuário (mesma lógica da navbar) + useEffect(() => { + const loadAvatar = async () => { + if (!userId) return; + + const myHeaders = new Headers(); + myHeaders.append("apikey", supabaseAK); + myHeaders.append("Authorization", `Bearer ${tokenUsuario}`); + + const requestOptions = { + headers: myHeaders, + method: 'GET', + redirect: 'follow' + }; + + try { + const response = await fetch(`${supabaseUrl}/storage/v1/object/avatars/${userId}/avatar.png`, requestOptions); + + if (response.ok) { + const blob = await response.blob(); + const imageUrl = URL.createObjectURL(blob); + setPreviewUrl(imageUrl); + return; + } + } catch (error) { + + } + + }; + + loadAvatar(); + }, [userId]); + + // Processar dados das consultas mensais + const processConsultasMensais = (appointmentsData) => { + const meses = [ + 'Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', + 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez' + ]; + + const consultasPorMes = meses.map(mes => ({ mes, consultas: 0 })); + + if (appointmentsData && appointmentsData.length > 0) { + appointmentsData.forEach(appointment => { + if (appointment.scheduled_at) { + const data = new Date(appointment.scheduled_at); + const mesIndex = data.getMonth(); + if (mesIndex >= 0 && mesIndex < 12) { + consultasPorMes[mesIndex].consultas++; + } + } + }); + } + + setConsultasMensaisDataReal(consultasPorMes); + }; + + // Processar dados das consultas por médico + const processConsultasPorMedico = async (appointmentsData, doctorsData) => { + try { + // Criar mapa de médicos + const doctorsMap = {}; + doctorsData.forEach(doctor => { + let doctorName = doctor.full_name || doctor.name || `Médico ${doctor.id}`; + doctorName = doctorName.trim(); + doctorsMap[doctor.id] = doctorName; + }); + + // Contar consultas por médico + const consultasPorMedico = {}; + appointmentsData.forEach(appointment => { + if (appointment.doctor_id) { + const doctorName = doctorsMap[appointment.doctor_id] || `Médico ${appointment.doctor_id}`; + consultasPorMedico[doctorName] = (consultasPorMedico[doctorName] || 0) + 1; + } + }); + + // Converter para array e ordenar por número de consultas (maior para menor) + const chartData = Object.entries(consultasPorMedico) + .map(([medico, consultas]) => ({ medico, consultas })) + .sort((a, b) => b.consultas - a.consultas) + .slice(0, 10); // Mostrar apenas os top 10 médicos + + setConsultasPorMedicoData(chartData); + } catch (error) { + setConsultasPorMedicoData([]); + } + }; + + // Processar dados da taxa de cancelamentos + const processTaxaCancelamentos = (appointmentsData) => { + const meses = [ + 'Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', + 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez' + ]; + + const cancelamentosPorMes = meses.map(mes => ({ + mes, + realizadas: 0, + canceladas: 0, + total: 0 + })); + + if (appointmentsData && appointmentsData.length > 0) { + + appointmentsData.forEach(appointment => { + if (appointment.scheduled_at) { + const data = new Date(appointment.scheduled_at); + const mesIndex = data.getMonth(); + const anoAtual = new Date().getFullYear(); + const anoConsulta = data.getFullYear(); + + // Processar apenas consultas do ano atual + if (mesIndex >= 0 && mesIndex < 12 && anoConsulta === anoAtual) { + cancelamentosPorMes[mesIndex].total++; + + // Verificar diferentes possíveis campos de status de cancelamento + const isCancelled = + appointment.status === 'cancelled' || + appointment.status === 'canceled' || + appointment.cancelled === true || + appointment.is_cancelled === true || + appointment.appointment_status === 'cancelled' || + appointment.appointment_status === 'canceled'; + + if (isCancelled) { + cancelamentosPorMes[mesIndex].canceladas++; + } else { + cancelamentosPorMes[mesIndex].realizadas++; + } + } + } + }); + + // Calcular porcentagens e manter valores absolutos para tooltip + cancelamentosPorMes.forEach(mes => { + if (mes.total > 0) { + const realizadasCount = mes.realizadas; + const canceladasCount = mes.canceladas; + + mes.realizadas = Math.round((realizadasCount / mes.total) * 100); + mes.canceladas = Math.round((canceladasCount / mes.total) * 100); + + // Garantir que soma seja 100% + if (mes.realizadas + mes.canceladas !== 100 && mes.total > 0) { + mes.realizadas = 100 - mes.canceladas; + } + } else { + // Se não há dados, mostrar 100% realizadas + mes.realizadas = 100; + mes.canceladas = 0; + } + }); + + + setTaxaCancelamentosData(cancelamentosPorMes); + } else { + // Se não há dados da API, deixar vazio + + setTaxaCancelamentosData([]); + } + }; + + + + return ( +
+
+ {/* Header com informações da secretária */} +
+
+
+
+
+
+

📋 Olá, {getFullName()}!

+

O MediConnect está pronto para mais um dia de organização e cuidado. Continue ajudando nossa clínica a funcionar de forma leve, eficiente e acolhedora!

+ + 🕒 {currentTime.toLocaleString('pt-BR')} + +
+
+ Avatar +
+
+
+
+
+
+ + {/* Cards de estatísticas */} +
+
+
+ + + +
+

{countPaciente}

+ Total Pacientes +
+
+
+ +
+
+ + + +
+

{countMedico}

+ Total Médicos +
+
+
+ +
+
+ + + +
+

{appointments.length}

+ Total Consultas +
+
+
+ +
+
+ + + +
+

80

+ Atendidos +
+
+
+
+ {/* Seção dos Gráficos */} +
+ {/* Consultas por Mês */} +
+
+
+

📊 Consultas por Mês ({new Date().getFullYear()})

+
+
+ {loading ? ( +
+
+ +

Carregando dados...

+
+
+ ) : consultasMensaisDataReal.length > 0 ? ( + + ) : ( +
+
+ +

Nenhum dado encontrado

+
+
+ )} +
+
+
+ + {/* Top 10 Médicos */} +
+
+
+

🏆 Top 10 Médicos (Consultas)

+
+
+ {loading ? ( +
+
+ +

Carregando dados...

+
+
+ ) : ( + + )} +
+
+
+
+ +
+ {/* Pacientes Ativos/Inativos */} +
+
+
+

👥 Pacientes Ativos x Inativos

+
+
+ {loading ? ( +
+
+ +

Carregando dados...

+
+
+ ) : pacientesStatusDataReal.length > 0 ? ( + + ) : ( +
+
+ +

Nenhum dado encontrado

+
+
+ )} +
+
+
+ + {/* Taxa de Cancelamentos */} +
+
+
+

📉 Taxa de Cancelamentos

+
+
+ {loading ? ( +
+
+ +

Carregando dados...

+
+
+ ) : ( + + )} +
+
+
+
+
+
+ ); +} + +// CSS customizado para o SecretariaDashboard (mesmo estilo do AdminDashboard) +const style = document.createElement('style'); +style.textContent = ` + .user-info-banner { + position: relative; + overflow: hidden; + } + + .user-info-banner::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: url('data:image/svg+xml,'); + pointer-events: none; + } + + .dash-widget { + transition: transform 0.2s ease; + } + + .dash-widget:hover { + transform: translateY(-3px); + } + + .card { + transition: transform 0.2s ease, box-shadow 0.2s ease; + } + + .card:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0,0,0,0.15) !important; + } +`; + +if (!document.head.querySelector('[data-secretaria-dashboard-styles]')) { + style.setAttribute('data-secretaria-dashboard-styles', 'true'); + document.head.appendChild(style); +} + +export default SecretariaDashboard; \ No newline at end of file diff --git a/src/pages/laudos/Laudo.jsx b/src/pages/laudos/Laudo.jsx deleted file mode 100644 index 9c82b9d..0000000 --- a/src/pages/laudos/Laudo.jsx +++ /dev/null @@ -1,50 +0,0 @@ -// PatientList.jsx -import { useEditor, EditorContent } from '@tiptap/react' -import StarterKit from '@tiptap/starter-kit' -import Bar from '../../components/Bar'; -import Image from '@tiptap/extension-image'; - -function Laudo() { - const editor = useEditor({ - extensions: [StarterKit, Image], - content: "" - }) - const comandos = { - toggleBold: () => editor.chain().focus().toggleBold().run(), - toggleItalic: () => editor.chain().focus().toggleItalic().run(), - toggleUnderline: () => editor.chain().focus().toggleUnderline().run(), - toggleCodeBlock: () => editor.chain().focus().toggleCodeBlock().run(), - toggleH1: () => editor.chain().focus().toggleHeading({ level: 1 }).run(), - toggleH2: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), - toggleH3: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), - toggleParrafo: () => editor.chain().focus().setParagraph().run(), - toggleListaOrdenada: () => editor.chain().focus().toggleOrderedList().run(), - toggleListaPuntos: () => editor.chain().focus().toggleBulletList().run(), - agregarImagen: () => { - const url = window.prompt('URL da imagem') - editor.chain().focus().setImage({ src: url }).run(); - - }, - agregarLink: () => { - const url = window.prompt('URL do link') - if (url) { - editor.chain().focus().setLink({ href: url }).run() - } - } - } - - return ( -
-
-
-

Laudo Médico

- - -
-
-
- ); -} - -export default Laudo; -; \ No newline at end of file diff --git a/src/pages/laudos/LaudosList.jsx b/src/pages/laudos/LaudosList.jsx deleted file mode 100644 index bee3797..0000000 --- a/src/pages/laudos/LaudosList.jsx +++ /dev/null @@ -1,288 +0,0 @@ -import { Link } from "react-router-dom"; -import "../../assets/css/index.css"; -import React, { useState, useRef, useLayoutEffect, useEffect } from "react"; -import { createPortal } from "react-dom"; - -function DropdownPortal({ anchorEl, isOpen, onClose, className, children }) { - const menuRef = useRef(null); - const [stylePos, setStylePos] = useState({ - position: "absolute", - top: 0, - left: 0, - visibility: "hidden", - zIndex: 1000, - }); - - useLayoutEffect(() => { - if (!isOpen || !anchorEl || !menuRef.current) return; - - const anchorRect = anchorEl.getBoundingClientRect(); - const menuRect = menuRef.current.getBoundingClientRect(); - const scrollY = window.scrollY || window.pageYOffset; - const scrollX = window.scrollX || window.pageXOffset; - - let left = anchorRect.right + scrollX - menuRect.width; - let top = anchorRect.bottom + scrollY; - - if (left < 0) left = scrollX + 4; - if (top + menuRect.height > window.innerHeight + scrollY) { - top = anchorRect.top + scrollY - menuRect.height; - } - - setStylePos({ - position: "absolute", - top: `${Math.round(top)}px`, - left: `${Math.round(left)}px`, - visibility: "visible", - zIndex: 1000, - }); - }, [isOpen, anchorEl, children]); - - useEffect(() => { - if (!isOpen) return; - - const handleDocClick = (e) => { - if (menuRef.current && !menuRef.current.contains(e.target) && - anchorEl && !anchorEl.contains(e.target)) { - onClose(); - } - }; - const handleScroll = () => onClose(); - - document.addEventListener("mousedown", handleDocClick); - document.addEventListener("scroll", handleScroll, true); - - return () => { - document.removeEventListener("mousedown", handleDocClick); - document.removeEventListener("scroll", handleScroll, true); - }; - }, [isOpen, onClose, anchorEl]); - - if (!isOpen) return null; - return createPortal( -
e.stopPropagation()}> - {children} -
, - document.body - ); -} - -function LaudoList() { - const [search, setSearch] = useState(""); - const [period, setPeriod] = useState(""); // "", "today", "week", "month" - const [startDate, setStartDate] = useState(""); - const [endDate, setEndDate] = useState(""); - const [laudos, setLaudos] = useState([ - { - id: 1, - pedido: 12345, - data: "2024-10-01", - prazo: "2024-10-05", - paciente: "Davi Andrade", - cpf: "12345678900", - tipo: "Radiologia", - status: "Pendente", - executante: "Dr. Silva", - exame: "Raio-X de Tórax" - }, - { - id: 2, - pedido: 12346, - data: "2024-10-02", - prazo: "2024-10-06", - paciente: "Maria Souza", - cpf: "98765432100", - tipo: "Cardiologia", - status: "Concluído", - executante: "Dra. Lima", - exame: "Eletrocardiograma" - }, - { - id: 3, - pedido: 12347, - data: "2024-10-03", - prazo: "2024-10-07", - paciente: "João Pereira", - cpf: "45678912300", - tipo: "Neurologia", - status: "Em Andamento", - executante: "Dr. Costa", - exame: "Ressonância Magnética" - }, - { - id: 4, - pedido: 12348, - data: "2024-10-04", - prazo: "2024-10-08", - paciente: "Ana Oliveira", - cpf: "32165498700", - tipo: "Ortopedia", - status: "Pendente", - executante: "Dra. Fernandes", - exame: "Tomografia Computadorizada" - }, - ]); - const [openDropdown, setOpenDropdown] = useState(null); - const anchorRefs = useRef({}); - - const handleDelete = (id) => { - if (!window.confirm("Tem certeza que deseja excluir este laudo?")) return; - setLaudos(prev => prev.filter(l => l.id !== id)); - setOpenDropdown(null); - }; - - const filteredLaudos = laudos.filter(l => { - const q = search.toLowerCase(); - const textMatch = - (l.paciente || "").toLowerCase().includes(q) || - (l.cpf || "").toLowerCase().includes(q) || - (l.tipo || "").toLowerCase().includes(q) || - (l.status || "").toLowerCase().includes(q) || - (l.pedido || "").toString().toLowerCase().includes(q) || - (l.prazo || "").toLowerCase().includes(q) || - (l.executante || "").toLowerCase().includes(q) || - (l.exame || "").toLowerCase().includes(q) || - (l.data || "").toLowerCase().includes(q); - - let dateMatch = true; - const today = new Date(); - const laudoDate = new Date(l.data); - - if (period === "today") { - dateMatch = laudoDate.toDateString() === today.toDateString(); - } else if (period === "week") { - const startOfWeek = new Date(today); - startOfWeek.setDate(today.getDate() - today.getDay()); - const endOfWeek = new Date(startOfWeek); - endOfWeek.setDate(startOfWeek.getDate() + 6); - dateMatch = laudoDate >= startOfWeek && laudoDate <= endOfWeek; - } else if (period === "month") { - dateMatch = laudoDate.getMonth() === today.getMonth() && laudoDate.getFullYear() === today.getFullYear(); - } - - if (startDate && endDate) { - dateMatch = dateMatch && l.data >= startDate && l.data <= endDate; - } else if (startDate) { - dateMatch = dateMatch && l.data >= startDate; - } else if (endDate) { - dateMatch = dateMatch && l.data <= endDate; - } - - return textMatch && dateMatch; - }); - - const mascararCPF = (cpf = "") => { - if (cpf.length < 5) return cpf; - return `${cpf.slice(0,3)}.***.***-${cpf.slice(-2)}`; - }; - - return ( -
-
-
-

Laudos

- - {/* Linha de pesquisa e filtros */} -
- {/* Esquerda: pesquisa */} -
- setSearch(e.target.value)} - style={{ minWidth: "200px" }} - /> -
- - {/* Direita: filtros de data + botões */} -
- - {/* Filtros de data primeiro */} -
- - setStartDate(e.target.value)} /> - - setEndDate(e.target.value)} /> -
- - {/* Botões rápidos */} -
- - - -
- - -
-
- - {/* Tabela */} -
-
-
- - - - - - - - - - - - - - - - - {filteredLaudos.length>0 ? filteredLaudos.map(l=>( - - - - - - - - - - - - - )) : ( - - - - )} - -
PedidoDataPrazoPacienteCPFTipoStatusExecutanteExameAções
{l.pedido}{l.data}{l.prazo}{l.paciente}{mascararCPF(l.cpf)}{l.tipo}{l.status}{l.executante}{l.exame} -
- - setOpenDropdown(null)} className="dropdown-menu dropdown-menu-right show"> - {e.stopPropagation(); setOpenDropdown(null);}}> - Laudo - - - -
-
Nenhum laudo encontrado
-
-
-
- -
-
-
- ); -} - -export default LaudoList; - diff --git a/src/routes/AdminRoutes.jsx b/src/routes/AdminRoutes.jsx new file mode 100644 index 0000000..c281e97 --- /dev/null +++ b/src/routes/AdminRoutes.jsx @@ -0,0 +1,60 @@ +import { AdminApp } from "../pages/AdminApp/AdminApp"; +import AdminDashboard from "../pages/AdminApp/AdminDashboard" +import CreateUser from "../pages/AdminApp/CreateUser"; +//listas +import AgendaDoctor from "../components/lists/AgendaDoctor" +import ConsultaList from "../components/lists/ConsultaList"; +import DoctorList from "../components/lists/DoctorList"; +import PatientList from "../components/lists/PatientList"; +import LaudoList from "../components/lists/LaudoList"; +//forms +import AgendaForm from "../components/forms/AgendaForm"; +import ConsultaForm from "../components/forms/ConsultaForm"; +import PatientForm from "../components/forms/PatientForm"; +import DoctorForm from "../components/forms/DoctorForm"; +import LaudoForm from "../components/forms/LaudoForm"; +//edits +import DoctorEdit from "../components/edits/DoctorEdit"; +import PatientEdit from "../components/edits/PatientEdit"; +import ConsultaEdit from "../components/edits/ConsultaEdit"; +import LaudoEdit from "../components/edits/LaudoEdit"; +import VerLaudo from "../components/VerLaudo"; + +import Doctorexcecao from "../pages/DoctorApp/Doctorexceçao"; +import LaudoConsulta from "../components/LaudoConsulta"; + + +export const AdminRoutes = + { + path: "/admin", + element: , + children: [ + {index: true, element: }, + {path: "dashboard", element: }, + //listas + {path: "agendadoctor", element: }, + {path: "consultalist", element: }, + {path: "doctorlist", element: }, + {path: "patientlist", element: }, + {path: "laudolist", element: }, + //forms + {path: "agendaform", element: }, + {path: "consultaform", element: }, + {path: "patientform", element: }, + {path: "doctorform", element: }, + {path: "laudoform", element: }, + {path: "laudoconsulta", element: }, + + + + //edits + {path: "editdoctor/:id", element: }, + {path: "editpatient/:id", element: }, + {path: "editconsulta/:id", element: }, + {path: "editlaudo/:id", element: }, + //create user + {path: "createuser", element: }, + {path: "excecao", element: }, + {path: "verlaudo/:id", element: }, + ] +}; diff --git a/src/routes/DoctorRoutes.jsx b/src/routes/DoctorRoutes.jsx new file mode 100644 index 0000000..8ea3679 --- /dev/null +++ b/src/routes/DoctorRoutes.jsx @@ -0,0 +1,50 @@ +import DoctorDashBoard from "../pages/DoctorApp/DoctorDashboard"; +import DoctorApp from "../pages/DoctorApp/DoctorApp"; +import DoctorProntuario from "../pages/DoctorApp/Prontuario/DoctorProntuario"; +import DoctorProntuarioList from "../pages/DoctorApp/Prontuario/DoctorProntuarioList" +import Doctorexeceçao from "../pages/DoctorApp/Doctorexceçao"; + +import AgendaDoctor from "../components/lists/AgendaDoctor" +import ConsultaList from "../components/lists/ConsultaList"; +import PatientList from "../components/lists/PatientList"; +import LaudoList from "../components/lists/LaudoList"; +//forms +import AgendaForm from "../components/forms/AgendaForm"; +import ConsultaForm from "../components/forms/ConsultaForm"; +import LaudoForm from "../components/forms/LaudoForm"; +//edits +import ConsultaEdit from "../components/edits/ConsultaEdit"; +import LaudoEdit from "../components/edits/LaudoEdit"; +import VerLaudo from "../components/VerLaudo"; +import DoctorCalendar from "../pages/DoctorApp/DoctorCalendar"; +import LaudoConsulta from "../components/LaudoConsulta"; + +export const DoctorRoutes = + {path : "/medico", + element: , + children: [ + {index: true, element: }, + {path: "dashboard", element: }, + {path: "prontuario", element: }, + {path: "prontuariolist", element: }, + {path: "calendar", element: }, + {path: "excecao", element: }, + //listas + {path: "agendadoctor", element: }, + {path: "consultalist", element: }, + {path: "patientlist", element: }, + {path: "laudolist", element: }, + //forms + {path: "agendaform", element: }, + {path: "consultaform", element: }, + {path: "laudoform", element: }, + {path: "laudoconsulta", element: }, + + //edits + {path: "editconsulta/:id", element: }, + {path: "editlaudo/:id", element: }, + {path: "verlaudo/:id", element: }, + {path: "doctorcalendar", element: }, + + ] +}; \ No newline at end of file diff --git a/src/routes/PatientRoutes.jsx b/src/routes/PatientRoutes.jsx new file mode 100644 index 0000000..b30bd79 --- /dev/null +++ b/src/routes/PatientRoutes.jsx @@ -0,0 +1,29 @@ +import PatientDashboard from "../pages/PatientApp/PatientDashboard"; +import PatientApp from "../pages/PatientApp/PatientApp"; + +//listas +import LaudoList from "../components/lists/LaudoList"; +import ConsultaList from "../components/lists/ConsultaList"; + +//forms +import MedicosDisponiveis from "../pages/PatientApp/MedicosDisponiveis"; +import AgendarConsulta from "../pages/PatientApp/AgendarConsultas"; +import VerLaudo from "../components/VerLaudo"; + + +export const PatientRoutes = + { + path: "/paciente", + element: , + children: [ + {index: true, element: }, + {path: "dashboard", element: }, + //listas + {path: "laudolist", element: }, + {path: "consultalist", element: }, + //forms + {path: "medicosdisponiveis", element: }, + {path: "agendarconsulta/:medicoId", element: }, + {path: "verlaudo/:id", element: }, + ] + }; diff --git a/src/routes/RoutesApp.jsx b/src/routes/RoutesApp.jsx new file mode 100644 index 0000000..fe4dc79 --- /dev/null +++ b/src/routes/RoutesApp.jsx @@ -0,0 +1,45 @@ +import { createBrowserRouter } from "react-router-dom"; +import { AdminRoutes } from "./AdminRoutes"; +import { DoctorRoutes } from "./DoctorRoutes"; +import { PatientRoutes } from "./PatientRoutes"; +import { SecretariaRoutes } from "./SecretariaRoutes"; +import Login from "../pages/Login/Login.jsx"; +import MagicLink from "../pages/Login/Acessounico.jsx"; +import HospitalLanding from "../pages/LandingPage/HospitalLanding.jsx" +import RoomPage from "../components/call/RoomPage.jsx"; +import Chat from "../components/chat.jsx"; +export const router = createBrowserRouter([ + { + path: "/", + element: + }, + + { + path: "/login", + element: + }, + { + path: "/AcessoUnico", + element: + }, + { + path: "/call/:roomId", + element: + }, + { + path: "/chat", + element: + }, + + // ✅ Se AdminRoutes for função: + AdminRoutes, + DoctorRoutes, + PatientRoutes, + SecretariaRoutes, + + // ✅ OU se AdminRoutes for objeto: + // AdminRoutes, + // DoctorRoutes, + // PatientRoutes, + // SecretariaRoutes, +]); \ No newline at end of file diff --git a/src/routes/SecretariaRoutes.jsx b/src/routes/SecretariaRoutes.jsx new file mode 100644 index 0000000..64e930b --- /dev/null +++ b/src/routes/SecretariaRoutes.jsx @@ -0,0 +1,37 @@ +import SecretariaApp from "../pages/SecretariaApp/SecretariaApp"; +import SecretariaDashboard from "../pages/SecretariaApp/SecretariaDashboard"; + +import AgendaDoctor from "../components/lists/AgendaDoctor" +import ConsultaList from "../components/lists/ConsultaList"; +import DoctorList from "../components/lists/DoctorList"; +import PatientList from "../components/lists/PatientList"; +//forms +import AgendaForm from "../components/forms/AgendaForm"; +import ConsultaForm from "../components/forms/ConsultaForm"; +import PatientForm from "../components/forms/PatientForm"; + +//edits +import PatientEdit from "../components/edits/PatientEdit"; +import ConsultaEdit from "../components/edits/ConsultaEdit"; + +export const SecretariaRoutes = + { + path: "/secretaria", + element: , + children: [ + {index: true, element: }, + {path: "dashboard", element: }, + //listas + {path: "agendadoctor", element: }, + {path: "consultalist", element: }, + {path: "doctorlist", element: }, + {path: "patientlist", element: }, + //forms + {path: "agendaform", element: }, + {path: "consultaform", element: }, + {path: "patientform", element: }, + //edits + {path: "editpatient/:id", element: }, + {path: "editconsulta/:id", element: }, + ] + }; diff --git a/src/utils/InterimMark.js b/src/utils/InterimMark.js new file mode 100644 index 0000000..29798a0 --- /dev/null +++ b/src/utils/InterimMark.js @@ -0,0 +1,34 @@ +import { Mark, mergeAttributes } from '@tiptap/core'; + +export const InterimMark = Mark.create({ + name: 'interimMark', + + // Isso faz com que a marca seja "exclusiva". + // Se você aplicar 'bold' e 'interimMark', o Tiptap + // saberá como lidar com isso. + inclusive: false, + + // Isso define como a marca será renderizada no HTML + renderHTML({ HTMLAttributes }) { + return [ + 'span', + mergeAttributes(HTMLAttributes, { + 'data-interim': 'true', + // O estilo "provisório" que você queria. + // Cinza e itálico dão uma boa ideia de "pre-confirm". + 'style': 'color: #888; font-style: italic;', + }), + 0, // 0 significa "aqui vai o conteúdo (texto)" + ]; + }, + + // Isso define como o Tiptap deve ler essa marca do HTML + // (útil se você for carregar conteúdo salvo) + parseHTML() { + return [ + { + tag: 'span[data-interim]', + }, + ]; + }, +}); \ No newline at end of file diff --git a/src/utils/auth.js b/src/utils/auth.js new file mode 100644 index 0000000..0963db9 --- /dev/null +++ b/src/utils/auth.js @@ -0,0 +1,39 @@ +export function getAccessToken() { + return localStorage.getItem("access_token"); +} + +export function getRefreshToken() { + return localStorage.getItem("refresh_token"); +} + +export async function refreshAccessToken() { + const refresh_token = getRefreshToken(); + if (!refresh_token) return null; + + const response = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/auth/v1/token", { + method: "POST", + headers: { + "apikey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ", // substitua pela sua chave real + "Content-Type": "application/json" + }, + body: JSON.stringify({ + grant_type: "refresh_token", + refresh_token + }) + }); + + if (!response.ok) { + console.error("Erro ao atualizar token:", response.status, await response.text()); + return null; + } + + const data = await response.json(); + + if (data.access_token) { + localStorage.setItem("access_token", data.access_token); + if (data.refresh_token) localStorage.setItem("refresh_token", data.refresh_token); + return data.access_token; + } + + return null; +} diff --git a/src/utils/permissions.js b/src/utils/permissions.js new file mode 100644 index 0000000..1d1036f --- /dev/null +++ b/src/utils/permissions.js @@ -0,0 +1,13 @@ +function permissions(role) { + const permissoes = { + admin: ['dashboard', 'consultas', 'usuarios', 'consultaform'], + medico: ['dashboard', 'consultas', ''], + recepcionista: ['dashboard'] + }; + return permissoes[role] || []; +} + +function pode(role, acao) { + const permissoesRole = permissions(role); + return permissoesRole.includes(acao); +} \ No newline at end of file diff --git a/src/utils/sendSMS.js b/src/utils/sendSMS.js new file mode 100644 index 0000000..398e8a0 --- /dev/null +++ b/src/utils/sendSMS.js @@ -0,0 +1,33 @@ +import { getAccessToken } from "../utils/auth"; + +export async function sendSMS(phoneNumber, message, patientId) { + const token = getAccessToken(); + + const headers = new Headers(); + headers.append("Authorization", `Bearer ${token}`); + headers.append("apikey", "SUA_ANON_KEY_REAL_DO_SUPABASE"); // substitua pela sua anon key real + headers.append("Content-Type", "application/json"); + + // 🔹 garante formato internacional (+55) + const formattedNumber = phoneNumber.startsWith("+") + ? phoneNumber + : `+55${phoneNumber.replace(/\D/g, "")}`; + + const body = JSON.stringify({ + phone_number: formattedNumber, + message, + patient_id: patientId, + }); + + const response = await fetch( + "https://yuanqfswhberkoevtmfr.supabase.co/functions/v1/send-sms", + { method: "POST", headers, body } + ); + + if (!response.ok) { + const text = await response.text().catch(() => ""); + throw new Error(`Falha ao enviar SMS (${response.status}) ${text}`); + } + + return response.json(); +} \ No newline at end of file diff --git a/src/utils/sidebar.js b/src/utils/sidebar.js new file mode 100644 index 0000000..e0c16f7 --- /dev/null +++ b/src/utils/sidebar.js @@ -0,0 +1,120 @@ + var Sidemenu = function() { + this.$menuItem = $('#sidebar-menu a'); + }; + + function init() { + var $this = Sidemenu; + $('#sidebar-menu a').on('click', function(e) { + if($(this).parent().hasClass('submenu')) { + e.preventDefault(); + } + if(!$(this).hasClass('subdrop')) { + $('ul', $(this).parents('ul:first')).slideUp(350); + $('a', $(this).parents('ul:first')).removeClass('subdrop'); + $(this).next('ul').slideDown(350); + $(this).addClass('subdrop'); + } else if($(this).hasClass('subdrop')) { + $(this).removeClass('subdrop'); + $(this).next('ul').slideUp(350); + } + }); + $('#sidebar-menu ul li.submenu a.active').parents('li:last').children('a:first').addClass('active').trigger('click'); + } + // Sidebar Initiate + init(); + + // Sidebar overlay + function sidebar_overlay($target) { + if($target.length) { + $target.toggleClass('opened'); + $sidebarOverlay.toggleClass('opened'); + $('html').toggleClass('menu-opened'); + $sidebarOverlay.attr('data-reff', '#' + $target[0].id); + } + } + + // Mobile menu sidebar overlay + $(document).on('click', '#mobile_btn', function() { + var $target = $($(this).attr('href')); + sidebar_overlay($target); + $wrapper.toggleClass('slide-nav'); + $('#chat_sidebar').removeClass('opened'); + return false; + }); + + // Chat sidebar overlay + $(document).on('click', '#task_chat', function() { + var $target = $($(this).attr('href')); + console.log($target); + sidebar_overlay($target); + return false; + }); + + // Sidebar overlay reset + $sidebarOverlay.on('click', function() { + var $target = $($(this).attr('data-reff')); + if($target.length) { + $target.removeClass('opened'); + $('html').removeClass('menu-opened'); + $(this).removeClass('opened'); + $wrapper.removeClass('slide-nav'); + } + return false; + }); + + // Select 2 + if($('.select').length > 0) { + $('.select').select2({ + minimumResultsForSearch: -1, + width: '100%' + }); + } + + // Floating Label + if($('.floating').length > 0) { + $('.floating').on('focus blur', function(e) { + $(this).parents('.form-focus').toggleClass('focused', (e.type === 'focus' || this.value.length > 0)); + }).trigger('blur'); + } + + // Right Sidebar Scroll + if($('#msg_list').length > 0) { + $('#msg_list').slimscroll({ + height: '100%', + color: '#878787', + disableFadeOut: true, + borderRadius: 0, + size: '4px', + alwaysVisible: false, + touchScrollStep: 100 + }); + var msgHeight = $(window).height() - 124; + $('#msg_list').height(msgHeight); + $('.msg-sidebar .slimScrollDiv').height(msgHeight); + $(window).resize(function() { + var msgrHeight = $(window).height() - 124; + $('#msg_list').height(msgrHeight); + $('.msg-sidebar .slimScrollDiv').height(msgrHeight); + }); + } + + // Left Sidebar Scroll + if($slimScrolls.length > 0) { + $slimScrolls.slimScroll({ + height: 'auto', + width: '100%', + position: 'right', + size: '7px', + color: '#ccc', + wheelStep: 10, + touchScrollStep: 100 + }); + var wHeight = $(window).height() - 60; + $slimScrolls.height(wHeight); + $('.sidebar .slimScrollDiv').height(wHeight); + $(window).resize(function() { + var rHeight = $(window).height() - 60; + $slimScrolls.height(rHeight); + $('.sidebar .slimScrollDiv').height(rHeight); + }); + } \ No newline at end of file diff --git a/src/utils/sweetalertTheme.js b/src/utils/sweetalertTheme.js new file mode 100644 index 0000000..33bcae1 --- /dev/null +++ b/src/utils/sweetalertTheme.js @@ -0,0 +1,24 @@ +// src/utils/sweetalertTheme.js +import Swal from "sweetalert2"; + +export function themedSwal(options) { + const isDark = document.body.classList.contains("dark-mode"); + + const baseOptions = { + background: isDark ? "#1e293b" : "#ffffff", + color: isDark ? "#e2e8f0" : "#111827", + confirmButtonColor: "#3399ff", + cancelButtonColor: isDark ? "#475569" : "#6c757d", + customClass: { + popup: isDark ? "swal2-dark-popup" : "", + title: "swal2-title", + confirmButton: "swal2-confirm", + cancelButton: "swal2-cancel", + }, + }; + + return Swal.fire({ + ...baseOptions, + ...options, + }); +} diff --git a/src/utils/useResponsive.js b/src/utils/useResponsive.js new file mode 100644 index 0000000..55352ff --- /dev/null +++ b/src/utils/useResponsive.js @@ -0,0 +1,199 @@ +import { useMediaQuery } from 'react-responsive'; +import { useMemo, useEffect, useState } from 'react'; + +// Breakpoints mais abrangentes e modernos +export const BREAKPOINTS = { + // Mobile First Approach + xs: '(max-width: 479px)', // Mobile pequeno + sm: '(min-width: 480px) and (max-width: 767px)', // Mobile grande + md: '(min-width: 768px) and (max-width: 1023px)', // Tablet + lg: '(min-width: 1024px) and (max-width: 1279px)', // Desktop pequeno + xl: '(min-width: 1280px) and (max-width: 1439px)', // Desktop médio + xxl: '(min-width: 1440px)', // Desktop grande + '2xl': '(min-width: 1920px)', // Desktop extra grande + + // Breakpoints úteis para comportamentos específicos + touchDevice: '(hover: none) and (pointer: coarse)', + stylusDevice: '(hover: none) and (pointer: fine)', + mouseDevice: '(hover: hover) and (pointer: fine)', + + // Para modo alto contraste/dark mode preferido + prefersDark: '(prefers-color-scheme: dark)', + prefersLight: '(prefers-color-scheme: light)', + prefersReducedMotion: '(prefers-reduced-motion: reduce)' +}; + +// Sistema de breakpoints para mobile-first +export const BREAKPOINTS_MOBILE_FIRST = { + xs: 480, // > 480px + sm: 768, // > 768px + md: 1024, // > 1024px + lg: 1280, // > 1280px + xl: 1440, // > 1440px + xxl: 1920 // > 1920px +}; + +export const useResponsive = () => { + // Breakpoints principais + const isXS = useMediaQuery({ query: BREAKPOINTS.xs }); + const isSM = useMediaQuery({ query: BREAKPOINTS.sm }); + const isMD = useMediaQuery({ query: BREAKPOINTS.md }); + const isLG = useMediaQuery({ query: BREAKPOINTS.lg }); + const isXL = useMediaQuery({ query: BREAKPOINTS.xl }); + const isXXL = useMediaQuery({ query: BREAKPOINTS.xxl }); + const is2XL = useMediaQuery({ query: BREAKPOINTS['2xl'] }); + + // Dispositivos e capacidades + const isTouchDevice = useMediaQuery({ query: BREAKPOINTS.touchDevice }); + const isStylusDevice = useMediaQuery({ query: BREAKPOINTS.stylusDevice }); + const isMouseDevice = useMediaQuery({ query: BREAKPOINTS.mouseDevice }); + + // Preferências do usuário + const prefersDark = useMediaQuery({ query: BREAKPOINTS.prefersDark }); + const prefersLight = useMediaQuery({ query: BREAKPOINTS.prefersLight }); + const prefersReducedMotion = useMediaQuery({ query: BREAKPOINTS.prefersReducedMotion }); + + // Estado para evitar hidratação inconsistente no SSR + const [isClient, setIsClient] = useState(false); + + useEffect(() => { + setIsClient(true); + }, []); + + // Device type com lógica mais robusta + const deviceType = useMemo(() => { + if (!isClient) return 'ssr'; // Para SSR + + if (isXS) return 'xs'; + if (isSM) return 'sm'; + if (isMD) return 'md'; + if (isLG) return 'lg'; + if (isXL) return 'xl'; + if (isXXL) return 'xxl'; + if (is2XL) return '2xl'; + + return 'unknown'; + }, [isClient, isXS, isSM, isMD, isLG, isXL, isXXL, is2XL]); + + // Agrupamentos úteis + const isMobile = useMemo(() => isXS || isSM, [isXS, isSM]); + const isTablet = useMemo(() => isMD, [isMD]); + const isDesktop = useMemo(() => isLG || isXL || isXXL || is2XL, [isLG, isXL, isXXL, is2XL]); + const isLargeScreen = useMemo(() => isXL || isXXL || is2XL, [isXL, isXXL, is2XL]); + + // Helpers para orientação de layout + const layoutType = useMemo(() => { + if (isMobile) return 'mobile'; + if (isTablet) return 'tablet'; + if (isDesktop) return 'desktop'; + return 'unknown'; + }, [isMobile, isTablet, isDesktop]); + + // Retorno memoizado para performance + return useMemo(() => ({ + // Breakpoints individuais + isXS, + isSM, + isMD, + isLG, + isXL, + isXXL, + is2XL, + + // Agrupamentos + isMobile, + isTablet, + isDesktop, + isLargeScreen, + + // Tipo de dispositivo e layout + deviceType, + layoutType, + + // Capacidades do dispositivo + isTouchDevice, + isStylusDevice, + isMouseDevice, + + // Preferências do usuário + prefersDark, + prefersLight, + prefersReducedMotion, + + // Helpers para condições comuns + isPortrait: typeof window !== 'undefined' ? window.innerHeight > window.innerWidth : false, + isLandscape: typeof window !== 'undefined' ? window.innerWidth > window.innerHeight : false, + + // Métodos úteis + breakpoint: deviceType, + isAbove: (breakpoint) => { + const breakpoints = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl', '2xl']; + const currentIndex = breakpoints.indexOf(deviceType); + const targetIndex = breakpoints.indexOf(breakpoint); + return currentIndex > targetIndex; + }, + isBelow: (breakpoint) => { + const breakpoints = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl', '2xl']; + const currentIndex = breakpoints.indexOf(deviceType); + const targetIndex = breakpoints.indexOf(breakpoint); + return currentIndex < targetIndex; + } + }), [ + isXS, isSM, isMD, isLG, isXL, isXXL, is2XL, + isMobile, isTablet, isDesktop, isLargeScreen, + deviceType, layoutType, + isTouchDevice, isStylusDevice, isMouseDevice, + prefersDark, prefersLight, prefersReducedMotion + ]); +}; + +// Hook específico para orientação melhorado +export const useOrientation = () => { + const [orientation, setOrientation] = useState({ + isPortrait: false, + isLandscape: false, + angle: 0 + }); + + useEffect(() => { + const updateOrientation = () => { + const isPortrait = window.innerHeight > window.innerWidth; + const screenOrientation = window.screen?.orientation || {}; + + setOrientation({ + isPortrait, + isLandscape: !isPortrait, + angle: screenOrientation.angle || 0, + type: screenOrientation.type || (isPortrait ? 'portrait' : 'landscape') + }); + }; + + updateOrientation(); + + window.addEventListener('resize', updateOrientation); + window.addEventListener('orientationchange', updateOrientation); + + return () => { + window.removeEventListener('resize', updateOrientation); + window.removeEventListener('orientationchange', updateOrientation); + }; + }, []); + + return orientation; +}; + +// Hook para debug (apenas desenvolvimento) +export const useResponsiveDebug = () => { + const responsive = useResponsive(); + + useEffect(() => { + if (process.env.NODE_ENV === 'development') { + console.log('📱 Responsive Debug:', responsive); + } + }, [responsive]); + + return responsive; +}; + +// Export default para compatibilidade +export default useResponsive; \ No newline at end of file diff --git a/src/utils/userInfo.js b/src/utils/userInfo.js new file mode 100644 index 0000000..8ecf874 --- /dev/null +++ b/src/utils/userInfo.js @@ -0,0 +1,69 @@ +// userInfo.js +export function setUserId(id) { + localStorage.setItem("user_id", id); +} + + +export function getUserId() { + return localStorage.getItem("user_id"); +} + + +export function setUserEmail(email) { + localStorage.setItem("user_email", email); +} + + +export function getUserEmail() { + return localStorage.getItem("user_email"); +} + + +export function setUserRole(role) { + localStorage.setItem("user_role", role); +} + + +export function getUserRole() { + return localStorage.getItem("user_role"); +} + + +export function setDoctorId(doctorId) { + localStorage.setItem("doctor_id", doctorId); +} + + +export function getDoctorId() { + return localStorage.getItem("doctor_id"); +} + + +export function setPatientId(patientId) { + localStorage.setItem("patient_id", patientId); +} + + +export function getPatientId() { + return localStorage.getItem("patient_id"); +} + + +export function setFullName(fullName) { + localStorage.setItem("full_name", fullName); +} + + +export function getFullName() { + return localStorage.getItem("full_name"); +} + + +export function clearUserInfo() { + localStorage.removeItem("user_id"); + localStorage.removeItem("user_email"); + localStorage.removeItem("user_role"); + localStorage.removeItem("doctor_id"); + localStorage.removeItem("patient_id"); + localStorage.removeItem("full_name"); +} \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index 8b0f57b..e46ae86 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,7 +1,11 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [ + react(), + tailwindcss(), + ] })