diff --git a/index.html b/index.html index d807691569..c92b873176 100644 --- a/index.html +++ b/index.html @@ -37,6 +37,8 @@ To create a production bundle, use `npm run build` or `yarn build`. --> - Back to Top ↑ + + ↑ + diff --git a/package-lock.json b/package-lock.json index f3452e8e1c..dca24c20d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -170,8 +170,10 @@ "redux-mock-store": "^1.5.4", "sass": "^1.86.3", "sass-loader": "^16.0.5", + "stylelint": "^16.25.0", "stylelint-config-standard": "^39.0.1", + "typescript": "^4.8.4", "vite": "^6.3.5", "vitest": "^3.2.0" @@ -201,13 +203,13 @@ } }, "node_modules/@ant-design/charts": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/@ant-design/charts/-/charts-2.6.7.tgz", - "integrity": "sha512-XfmsnspUpfrMlRFGTwmHJ2TPKcosq5a5nSxAfIOpEXAvmJBT2N16oejGTZhUFTzba8W3XtBOziwRAXmDmLUqvA==", + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/@ant-design/charts/-/charts-2.6.6.tgz", + "integrity": "sha512-Mw2XqB9c7JoENyewJmtxU+5TU2sW5VEyct2f6n4HjJ/6hBo4ht3qdu965G3UrNLyiRctd47Qje32u+8DeFZ6Bg==", "license": "MIT", "dependencies": { "@ant-design/graphs": "^2.1.1", - "@ant-design/plots": "^2.6.7", + "@ant-design/plots": "^2.6.6", "lodash": "^4.17.21" }, "peerDependencies": { @@ -363,12 +365,12 @@ } }, "node_modules/@ant-design/plots": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/@ant-design/plots/-/plots-2.6.8.tgz", - "integrity": "sha512-QsunUs2d5rbq/1BwVhga/siA5H50OaG23YopMYwPD4sPsza6NQzPQ8FM3elNIsD/BIk298tihqX1cJ/MmvVJbQ==", + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/@ant-design/plots/-/plots-2.6.6.tgz", + "integrity": "sha512-yUcvW/b7FPiIKwCpC0AOYQhHVKFCOpthlizsJx2Tuq7OEzTCLSWjRjT8o8sEWo3CCtIAtAuLY4GOfGdGGz1f0A==", "license": "MIT", "dependencies": { - "@ant-design/charts-util": "0.0.3", + "@ant-design/charts-util": "0.0.2", "@antv/event-emitter": "^0.1.3", "@antv/g": "^6.1.7", "@antv/g2": "^5.2.7", @@ -381,9 +383,9 @@ } }, "node_modules/@ant-design/plots/node_modules/@ant-design/charts-util": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@ant-design/charts-util/-/charts-util-0.0.3.tgz", - "integrity": "sha512-x1H7UT6t4dXAyGRoHqlOnEsEqBSTANFGTZEAMI0CWYhYUpp13n0o9grl9oPtoL6FEQMjUBTY+zGJKlHkz8smMw==", + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@ant-design/charts-util/-/charts-util-0.0.2.tgz", + "integrity": "sha512-JuThvtHE8R3PldXzTkL3bmmFf0HVhih49CYinRrkwgovOmvDYaaKHnI53EWJbW8n4Ndcyy8jiZTSkoxcjGS6Zg==", "license": "MIT", "dependencies": { "lodash": "^4.17.21" @@ -508,39 +510,66 @@ "license": "MIT" }, "node_modules/@antv/g": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@antv/g/-/g-6.3.1.tgz", - "integrity": "sha512-WYEKqy86LHB2PzTmrZXrIsIe+3Epeds2f68zceQ+BJtRoGki7Sy4IhlC8LrUMztgfT1t3d/0L745NWZwITroKA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@antv/g/-/g-6.2.1.tgz", + "integrity": "sha512-RdRXtSKhS8u9xCkJQD6h5YthyyY5w5lTrx6VKyC96iE7YhPqrhEeOEqba9yxAR9tG5yy15Ix6vxBc0zWQ8ZZMA==", + "license": "MIT", + "dependencies": { + "@antv/g-camera-api": "2.0.45", + "@antv/g-dom-mutation-observer-api": "2.0.42", + "@antv/g-lite": "2.5.1", + "@antv/g-web-animations-api": "2.1.32", + "@babel/runtime": "^7.25.6" + } + }, + "node_modules/@antv/g-camera-api": { + "version": "2.0.45", + "resolved": "https://registry.npmjs.org/@antv/g-camera-api/-/g-camera-api-2.0.45.tgz", + "integrity": "sha512-LhRFSFJtVvaIZYHG0jYHPx0/+Zwg5aFc+Iw2jo8o73vwGutmx2yNMYSJr/JOExgzulsGzGONezFENfLbccgh5A==", "license": "MIT", "dependencies": { - "@antv/g-lite": "2.7.0", + "@antv/g-lite": "2.5.1", "@antv/util": "^3.3.5", "@babel/runtime": "^7.25.6", "gl-matrix": "^3.4.3", - "html2canvas": "^1.4.1" + "tslib": "^2.5.3" } }, "node_modules/@antv/g-canvas": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@antv/g-canvas/-/g-canvas-2.2.0.tgz", - "integrity": "sha512-h7zVBBo2aO64DuGKvq9sG+yTU3sCUb9DALCVm7nz8qGPs8hhLuFOkKPEzUDNfNYZGJUGzY8UDtJ3QRGRFcvEQg==", - "license": "MIT", - "dependencies": { - "@antv/g-lite": "2.7.0", - "@antv/g-math": "3.1.0", + "version": "2.0.52", + "resolved": "https://registry.npmjs.org/@antv/g-canvas/-/g-canvas-2.0.52.tgz", + "integrity": "sha512-tZoRfkrvVTe3e9OU+xhpgweTnQIctgaevoN47ZW15ymW792eq0oYRHPzNGsAAnhoFvBIhsT2/xEC9TuPFQdKuQ==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.5.1", + "@antv/g-plugin-canvas-path-generator": "2.1.26", + "@antv/g-plugin-canvas-picker": "2.3.1", + "@antv/g-plugin-canvas-renderer": "2.5.1", + "@antv/g-plugin-dom-interaction": "2.1.31", + "@antv/g-plugin-html-renderer": "2.3.1", + "@antv/g-plugin-image-loader": "2.3.1", "@antv/util": "^3.3.5", "@babel/runtime": "^7.25.6", - "gl-matrix": "^3.4.3", "tslib": "^2.5.3" } }, + "node_modules/@antv/g-dom-mutation-observer-api": { + "version": "2.0.42", + "resolved": "https://registry.npmjs.org/@antv/g-dom-mutation-observer-api/-/g-dom-mutation-observer-api-2.0.42.tgz", + "integrity": "sha512-Utu0TZFEeGrBsw3cQD7MZwKBY39Cuys4zMsZpVetYDqQm/SexzqUQeIgCm9V0zJZfXPGEJNSvWL0NV6HGSYT3Q==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.5.1", + "@babel/runtime": "^7.25.6" + } + }, "node_modules/@antv/g-lite": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@antv/g-lite/-/g-lite-2.7.0.tgz", - "integrity": "sha512-uSzgHYa5bwR5L2Au7/5tsOhFmXKZKLPBH90+Q9bP9teVs5VT4kOAi0isPSpDI8uhdDC2/VrfTWu5K9HhWI6FWw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@antv/g-lite/-/g-lite-2.5.1.tgz", + "integrity": "sha512-58Ul+fZb9qT/PYXMW7zUJrl62Q46MmZYZ6V7XelDZxZCL6LP2F2avDrreDGa3uYrihUjf7IvhVfBDviL4T4sdg==", "license": "MIT", "dependencies": { - "@antv/g-math": "3.1.0", + "@antv/g-math": "3.0.1", "@antv/util": "^3.3.5", "@antv/vendor": "^1.0.3", "@babel/runtime": "^7.25.6", @@ -550,42 +579,163 @@ } }, "node_modules/@antv/g-math": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@antv/g-math/-/g-math-3.1.0.tgz", - "integrity": "sha512-DtN1Gj/yI0UiK18nSBsZX8RK0LszGwqfb+cBYWgE+ddyTm8dZnW4tPUhV7QXePsS6/A5hHC+JFpAAK7OEGo5ZQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@antv/g-math/-/g-math-3.0.1.tgz", + "integrity": "sha512-FvkDBNRpj+HsLINunrL2PW0OlG368MlpHuihbxleuajGim5kra8tgISwCLmAf8Yz2b1CgZ9PvpohqiLzHS7HLg==", + "license": "MIT", + "dependencies": { + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-canvas-path-generator": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/@antv/g-plugin-canvas-path-generator/-/g-plugin-canvas-path-generator-2.1.26.tgz", + "integrity": "sha512-87V9NUbESa4PecvQmdGiQKu2avgLln3OTC1zjtexioOUJEpfTx8NTQeWcyzv6T7mJJMlptznfpkuRyZgDg/1+g==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.5.1", + "@antv/g-math": "3.0.1", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-canvas-picker": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@antv/g-plugin-canvas-picker/-/g-plugin-canvas-picker-2.3.1.tgz", + "integrity": "sha512-h/ZgCuLxFo6xiA/XLGihBvfBjcGayua9buY6sqvyu8fHGA3AvE1RAhA/PeEOCDVBVQFvFLlkP1SaU9c+fqp23Q==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.5.1", + "@antv/g-math": "3.0.1", + "@antv/g-plugin-canvas-path-generator": "2.1.26", + "@antv/g-plugin-canvas-renderer": "2.5.1", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-canvas-renderer": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@antv/g-plugin-canvas-renderer/-/g-plugin-canvas-renderer-2.5.1.tgz", + "integrity": "sha512-nhKLU2tsMItOHWnPAQR3ryy/tQG4X5f2IGgzp8UjA1r2qwPcS8eB+vt0uqOwlk4Lo0ugujTt4rIIbfAXL6cRfQ==", "license": "MIT", "dependencies": { + "@antv/g-lite": "2.5.1", + "@antv/g-math": "3.0.1", + "@antv/g-plugin-canvas-path-generator": "2.1.26", + "@antv/g-plugin-image-loader": "2.3.1", "@antv/util": "^3.3.5", "@babel/runtime": "^7.25.6", "gl-matrix": "^3.4.3", "tslib": "^2.5.3" } }, + "node_modules/@antv/g-plugin-dom-interaction": { + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/@antv/g-plugin-dom-interaction/-/g-plugin-dom-interaction-2.1.31.tgz", + "integrity": "sha512-5FKJaVvc1O80yQlSLwyevuZZgYXGzcRHCLjPDwZ9tIa/79S5q9Hc9qkTBS4pM5B85EtHN+wZqVXHZKfm/6a9jA==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.5.1", + "@babel/runtime": "^7.25.6", + "tslib": "^2.5.3" + } + }, "node_modules/@antv/g-plugin-dragndrop": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@antv/g-plugin-dragndrop/-/g-plugin-dragndrop-2.1.1.tgz", - "integrity": "sha512-+aesDUJVQDs6UJ2bOBbDlaGAPCfHmU0MbrMTlQlfpwNplWueqtgVAZ3L57oZ2ZGHRWUHiRwZGPjXMBM3O2LELw==", + "version": "2.0.42", + "resolved": "https://registry.npmjs.org/@antv/g-plugin-dragndrop/-/g-plugin-dragndrop-2.0.42.tgz", + "integrity": "sha512-EGrs8om9CwQka1Pgw02namBo1h0iqhskL5i1pD0CFhvCfAU2lxA2EHdYan+8vRQnDQVgXNbS+XAxzI6LQwByUQ==", "license": "MIT", "dependencies": { - "@antv/g-lite": "2.7.0", + "@antv/g-lite": "2.5.1", "@antv/util": "^3.3.5", "@babel/runtime": "^7.25.6", "tslib": "^2.5.3" } }, - "node_modules/@antv/g-svg": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@antv/g-svg/-/g-svg-2.1.1.tgz", - "integrity": "sha512-gVzBkjqA8FzDTbkuIxj6L0Omz/X/hFbYLzK6alWr0sHTfywqP6czcjDUJU8DF2MRIY1Twy55uZYW4dqqLXOXXg==", + "node_modules/@antv/g-plugin-html-renderer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@antv/g-plugin-html-renderer/-/g-plugin-html-renderer-2.3.1.tgz", + "integrity": "sha512-s13zOJHbtCIRiSKI1N2xnX+XT253a9npPuJKH8bxbujWbRfXQVW6VaP517hVDeODO3sDklbEuDlMHSF94IP0Hg==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.5.1", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-image-loader": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@antv/g-plugin-image-loader/-/g-plugin-image-loader-2.3.1.tgz", + "integrity": "sha512-7UKLe2nbg/5YBQN3yy+dir5gHp53tp7rk7JxqnQnB2piXv/eZe6yq5hrfSVn74jwWkoUnjLe8u+++mHgoAsTng==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.5.1", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-svg-picker": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/@antv/g-plugin-svg-picker/-/g-plugin-svg-picker-2.0.46.tgz", + "integrity": "sha512-Uz7+7tzXQHFDra3Mz/zZKw1OGYtXmXUtx69loIK8+iybviaNNPmjL2Cavj+LvcjvSlHFvNakzFPwBl54Isd74Q==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.5.1", + "@antv/g-plugin-svg-renderer": "2.4.1", + "@babel/runtime": "^7.25.6", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-svg-renderer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@antv/g-plugin-svg-renderer/-/g-plugin-svg-renderer-2.4.1.tgz", + "integrity": "sha512-7KA3bC3Ilfs52rZvD21spoSxPU5ONsdW7RCZ0wW2qkyLwrCGAJabzdsEP8sF27WWLl/4Pf5QHVP6kcLWvT1eyA==", "license": "MIT", "dependencies": { - "@antv/g-lite": "2.7.0", + "@antv/g-lite": "2.5.1", "@antv/util": "^3.3.5", "@babel/runtime": "^7.25.6", "gl-matrix": "^3.4.3", "tslib": "^2.5.3" } }, + "node_modules/@antv/g-svg": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/@antv/g-svg/-/g-svg-2.0.46.tgz", + "integrity": "sha512-HWKR1vuBAggah8IRJXamUfJG3O38irsRcpqtQHNsLvoBxn426ZSISI4kjX2fc08xFu3nHy/ZkFQC4m5xd9s6KQ==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.5.1", + "@antv/g-plugin-dom-interaction": "2.1.31", + "@antv/g-plugin-svg-picker": "2.0.46", + "@antv/g-plugin-svg-renderer": "2.4.1", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-web-animations-api": { + "version": "2.1.32", + "resolved": "https://registry.npmjs.org/@antv/g-web-animations-api/-/g-web-animations-api-2.1.32.tgz", + "integrity": "sha512-4Tf1bJbXxZkBF+DsHS+c6vEiv+ccNRqjeRfmniPOE3vwSVXZCKgvMhhIMQoxz0vVOaxYrDTpchquM5PF64qC0Q==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.5.1", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "tslib": "^2.5.3" + } + }, "node_modules/@antv/g2": { "version": "5.4.7", "resolved": "https://registry.npmjs.org/@antv/g2/-/g2-5.4.7.tgz", @@ -617,9 +767,9 @@ } }, "node_modules/@antv/g6": { - "version": "5.0.51", - "resolved": "https://registry.npmjs.org/@antv/g6/-/g6-5.0.51.tgz", - "integrity": "sha512-/88LJDZ7FHKtpyJibXOnJWZ8gFRp32mLb8KzEFrMuiIC/dsZgTf/oYVw6L4tLKooPXfXqUtrJb2tWFMGR04EMg==", + "version": "5.0.50", + "resolved": "https://registry.npmjs.org/@antv/g6/-/g6-5.0.50.tgz", + "integrity": "sha512-L2ZdekSpJreIvSc4DkqGCh2bFmCadDZiR6q9euVtXdLeHPl/YQ4hqTvLIkc7aYO6oE/nC5mPAIOaM6ZiAy7QKA==", "license": "MIT", "dependencies": { "@antv/algorithm": "^0.1.26", @@ -629,7 +779,7 @@ "@antv/g-canvas": "^2.0.48", "@antv/g-plugin-dragndrop": "^2.0.38", "@antv/graphlib": "^2.0.4", - "@antv/hierarchy": "^0.7.1", + "@antv/hierarchy": "^0.6.14", "@antv/layout": "1.2.14-beta.9", "@antv/util": "^3.3.11", "bubblesets-js": "^2.3.4" @@ -673,9 +823,9 @@ } }, "node_modules/@antv/hierarchy": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@antv/hierarchy/-/hierarchy-0.7.1.tgz", - "integrity": "sha512-7r22r+HxfcRZp79ZjGmsn97zgC1Iajrv0Mm9DIgx3lPfk+Kme2MG/+EKdZj1iEBsN0rJRzjWVPGL5YrBdVHchw==", + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/@antv/hierarchy/-/hierarchy-0.6.14.tgz", + "integrity": "sha512-V3uknf7bhynOqQDw2sg+9r9DwZ9pc6k/EcqyTFdfXB1+ydr7urisP0MipIuimucvQKN+Qkd+d6w601r1UIroqQ==", "license": "MIT" }, "node_modules/@antv/layout": { @@ -2870,60 +3020,6 @@ "node": ">= 4.0.0" } }, - "node_modules/@cacheable/memoize": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@cacheable/memoize/-/memoize-2.0.3.tgz", - "integrity": "sha512-hl9wfQgpiydhQEIv7fkjEzTGE+tcosCXLKFDO707wYJ/78FVOlowb36djex5GdbSyeHnG62pomYLMuV/OT8Pbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cacheable/utils": "^2.0.3" - } - }, - "node_modules/@cacheable/memory": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.3.tgz", - "integrity": "sha512-R3UKy/CKOyb1LZG/VRCTMcpiMDyLH7SH3JrraRdK6kf3GweWCOU3sgvE13W3TiDRbxnDKylzKJvhUAvWl9LQOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cacheable/memoize": "^2.0.3", - "@cacheable/utils": "^2.0.3", - "@keyv/bigmap": "^1.0.2", - "hookified": "^1.12.1", - "keyv": "^5.5.3" - } - }, - "node_modules/@cacheable/memory/node_modules/keyv": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.3.tgz", - "integrity": "sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@keyv/serialize": "^1.1.1" - } - }, - "node_modules/@cacheable/utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.1.0.tgz", - "integrity": "sha512-ZdxfOiaarMqMj+H7qwlt5EBKWaeGihSYVHdQv5lUsbn8MJJOTW82OIwirQ39U5tMZkNvy3bQE+ryzC+xTAb9/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "keyv": "^5.5.3" - } - }, - "node_modules/@cacheable/utils/node_modules/keyv": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.3.tgz", - "integrity": "sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@keyv/serialize": "^1.1.1" - } - }, "node_modules/@changey/react-leaflet-markercluster": { "version": "4.0.0-rc1", "resolved": "https://registry.npmjs.org/@changey/react-leaflet-markercluster/-/react-leaflet-markercluster-4.0.0-rc1.tgz", @@ -3056,51 +3152,21 @@ "node": ">=18" } }, - "node_modules/@csstools/media-query-list-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", - "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, "node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", + "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "license": "CC0-1.0", "engines": { - "node": ">=18" + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" }, "peerDependencies": { - "postcss-selector-parser": "^7.0.0" + "postcss-selector-parser": "^6.0.10" } }, "node_modules/@date-io/core": { @@ -3137,6 +3203,37 @@ "url": "https://github.com/sponsors/JounQin" } }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -3386,87 +3483,512 @@ "@ephox/sand": "^6.0.10" } }, - "node_modules/@esbuild/win32-x64": { + "node_modules/@esbuild/aix-ppc64": { "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", - "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", "cpu": [ - "x64" + "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "aix" ], "engines": { "node": ">=18" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": ">=18" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/eslintrc/node_modules/ajv": { @@ -3708,7 +4230,6 @@ "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3738,7 +4259,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", "dev": true, "license": "BSD-3-Clause" }, @@ -5496,26 +6016,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@keyv/bigmap": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.0.3.tgz", - "integrity": "sha512-jUEkNlnE9tYzX2AIBeoSe1gVUvSOfIOQ5EFPL5Un8cFHGvjD9L/fxpxlS1tEivRLHgapO2RZJ3D93HYAa049pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "hookified": "^1.12.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@keyv/serialize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", - "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", - "dev": true, - "license": "MIT" - }, "node_modules/@kurkle/color": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", @@ -5888,6 +6388,18 @@ "workerize-loader": "*" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -5949,53 +6461,305 @@ "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", "dev": true, "license": "MIT", - "dependencies": { - "is-node-process": "^1.2.0", - "outvariant": "^1.4.0" + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@open-draft/until": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", - "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@parcel/watcher": { + "node_modules/@parcel/watcher-win32-ia32": { "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", - "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, - "dependencies": { - "detect-libc": "^1.0.3", - "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^7.0.0" - }, + "os": [ + "win32" + ], "engines": { "node": ">= 10.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.1", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-freebsd-x64": "2.5.1", - "@parcel/watcher-linux-arm-glibc": "2.5.1", - "@parcel/watcher-linux-arm-musl": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-ia32": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1" } }, "node_modules/@parcel/watcher-win32-x64": { @@ -6206,9 +6970,9 @@ } }, "node_modules/@rc-component/util": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.7.0.tgz", - "integrity": "sha512-tIvIGj4Vl6fsZFvWSkYw9sAfiCKUXMyhVz6kpKyZbwyZyRPqv2vxYZROdaO1VB4gqTNvUZFXh6i3APUiterw5g==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.6.0.tgz", + "integrity": "sha512-YbjuIVAm8InCnXVoA4n6G+uh31yESTxQ6fSY2frZ2/oMSvktoB+bumFUfNN7RKh7YeOkZgOvN2suGtEDhJSX0A==", "license": "MIT", "dependencies": { "is-mobile": "^5.0.0", @@ -6305,6 +7069,272 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.49.0.tgz", + "integrity": "sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.49.0.tgz", + "integrity": "sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.49.0.tgz", + "integrity": "sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.49.0.tgz", + "integrity": "sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.49.0.tgz", + "integrity": "sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.49.0.tgz", + "integrity": "sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.49.0.tgz", + "integrity": "sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.49.0.tgz", + "integrity": "sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.49.0.tgz", + "integrity": "sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.49.0.tgz", + "integrity": "sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.49.0.tgz", + "integrity": "sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.49.0.tgz", + "integrity": "sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.49.0.tgz", + "integrity": "sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.49.0.tgz", + "integrity": "sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.49.0.tgz", + "integrity": "sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.49.0.tgz", + "integrity": "sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.49.0.tgz", + "integrity": "sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.49.0.tgz", + "integrity": "sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.49.0.tgz", + "integrity": "sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.49.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.49.0.tgz", @@ -6679,6 +7709,16 @@ } } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -7060,8 +8100,6 @@ }, "node_modules/@types/jest/node_modules/ci-info": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", - "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", "dev": true, "funding": [ { @@ -7238,6 +8276,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.3.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", @@ -7247,6 +8292,13 @@ "undici-types": "~7.10.0" } }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/pako": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", @@ -7609,6 +8661,243 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", @@ -8375,6 +9664,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/asn1.js": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", @@ -8991,31 +10290,6 @@ "node": ">=8" } }, - "node_modules/cacheable": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.1.0.tgz", - "integrity": "sha512-zzL1BxdnqwD69JRT0dihnawAcLkBMwAH+hZSKjUzeBbPedVhk3qYPjRw9VOMYWwt5xRih5xd8S+3kEdGohZm/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cacheable/memoize": "^2.0.3", - "@cacheable/memory": "^2.0.3", - "@cacheable/utils": "^2.1.0", - "hookified": "^1.12.1", - "keyv": "^5.5.3", - "qified": "^0.5.0" - } - }, - "node_modules/cacheable/node_modules/keyv": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.3.tgz", - "integrity": "sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@keyv/serialize": "^1.1.1" - } - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -9081,6 +10355,7 @@ "node": ">=6" } }, + "node_modules/camelize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", @@ -9088,6 +10363,7 @@ "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/caniuse-lite": { @@ -9864,6 +11140,7 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-to-react-native": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", @@ -9889,6 +11166,7 @@ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, + "node_modules/css-what": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", @@ -10530,6 +11808,43 @@ } } }, + "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/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/decimal.js": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", @@ -10968,16 +12283,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/environment": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", @@ -11252,7 +12557,6 @@ "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", "dependencies": { @@ -12562,6 +13866,20 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "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", @@ -12740,7 +14058,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -12925,6 +14242,16 @@ "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==", "license": "MIT" }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -13084,12 +14411,38 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, - "node_modules/hookified": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.12.2.tgz", - "integrity": "sha512-aokUX1VdTpI0DUsndvW+OiwmBpKCu/NgRsSSkuSY0zq8PY6Q6a+lmOfAFDXAAOtBqJELvcWY9L1EVtzjbQcMdg==", + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, - "license": "MIT" + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" }, "node_modules/html-dom-parser": { "version": "1.2.0", @@ -13433,6 +14786,7 @@ "node": ">=4" } }, + "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -13450,6 +14804,7 @@ }, "funding": { "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/imurmurhash": { @@ -13475,7 +14830,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -13896,6 +15250,16 @@ "node": ">=8" } }, + "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==", + "dev": true, + "license": "MIT", + "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", @@ -18030,9 +19394,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.37.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", - "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.26.0.tgz", + "integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==", "dev": true, "license": "MIT" }, @@ -18384,11 +19748,24 @@ "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "license": "MIT", - "dependencies": { - "p-defer": "^1.0.0" - }, + "dependencies": { + "p-defer": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/math-intrinsics": { @@ -18422,13 +19799,6 @@ "safe-buffer": "^5.1.2" } }, - "node_modules/mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/mem": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/mem/-/mem-8.1.1.tgz", @@ -18452,18 +19822,55 @@ "license": "MIT" }, "node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", "dev": true, "license": "MIT", + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, "engines": { - "node": ">=18" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/meow/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==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -18599,6 +20006,7 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -18645,6 +20053,7 @@ "dependencies": { "is-any-array": "^2.0.1", "ml-array-rescale": "^1.3.7" + } }, "node_modules/moment": { @@ -18824,6 +20233,22 @@ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "license": "MIT" }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -19460,7 +20885,6 @@ "version": "1.16.1", "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", "license": "MIT", "funding": { "type": "opencollective", @@ -19505,6 +20929,13 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true, + "license": "MIT" + }, "node_modules/postcss-resolve-nested-selector": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", @@ -19513,36 +20944,26 @@ "license": "MIT" }, "node_modules/postcss-safe-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", - "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "engines": { - "node": ">=18.0" + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.3.3" } }, "node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, "license": "MIT", "dependencies": { @@ -19737,19 +21158,6 @@ ], "license": "MIT" }, - "node_modules/qified": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/qified/-/qified-0.5.0.tgz", - "integrity": "sha512-Zj6Q/Vc/SQ+Fzc87N90jJUzBzxD7MVQ2ZvGyMmYtnl2u1a07CejAhvtk4ZwASos+SiHKCAIylyGHJKIek75QBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "hookified": "^1.12.1" - }, - "engines": { - "node": ">=20" - } - }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -19778,6 +21186,16 @@ ], "license": "MIT" }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -21158,6 +22576,90 @@ "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", "license": "MIT" }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true, + "license": "ISC" + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -21573,7 +23075,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", "dependencies": { @@ -22237,6 +23738,42 @@ "node": ">=0.10.0" } }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -22645,6 +24182,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-search": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", + "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", + "dev": true, + "license": "ISC" + }, "node_modules/style-to-js": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.1.tgz", @@ -22753,115 +24297,83 @@ "license": "0BSD" }, "node_modules/stylelint": { - "version": "16.25.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.25.0.tgz", - "integrity": "sha512-Li0avYWV4nfv1zPbdnxLYBGq4z8DVZxbRgx4Kn6V+Uftz1rMoF1qiEI3oL4kgWqyYgCgs7gT5maHNZ82Gk03vQ==", + "version": "14.16.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.16.1.tgz", + "integrity": "sha512-ErlzR/T3hhbV+a925/gbfc3f3Fep9/bnspMiJPorfGEmcBbXdS+oo6LrVtoUZ/w9fqD6o6k7PtUlCOsCRdjX/A==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" - }, - { - "type": "github", - "url": "https://github.com/sponsors/stylelint" - } - ], "license": "MIT", "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3", - "@csstools/selector-specificity": "^5.0.0", - "@dual-bundle/import-meta-resolve": "^4.2.1", + "@csstools/selector-specificity": "^2.0.2", "balanced-match": "^2.0.0", "colord": "^2.9.3", - "cosmiconfig": "^9.0.0", - "css-functions-list": "^3.2.3", - "css-tree": "^3.1.0", - "debug": "^4.4.3", - "fast-glob": "^3.3.3", + "cosmiconfig": "^7.1.0", + "css-functions-list": "^3.1.0", + "debug": "^4.3.4", + "fast-glob": "^3.2.12", "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^10.1.4", + "file-entry-cache": "^6.0.1", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", - "html-tags": "^3.3.1", - "ignore": "^7.0.5", + "html-tags": "^3.2.0", + "ignore": "^5.2.1", + "import-lazy": "^4.0.0", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.37.0", + "known-css-properties": "^0.26.0", "mathml-tag-names": "^2.1.3", - "meow": "^13.2.0", - "micromatch": "^4.0.8", + "meow": "^9.0.0", + "micromatch": "^4.0.5", "normalize-path": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.5.6", - "postcss-resolve-nested-selector": "^0.1.6", - "postcss-safe-parser": "^7.0.1", - "postcss-selector-parser": "^7.1.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.19", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.0.11", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", - "supports-hyperlinks": "^3.2.0", + "strip-ansi": "^6.0.1", + "style-search": "^0.1.0", + "supports-hyperlinks": "^2.3.0", "svg-tags": "^1.0.0", - "table": "^6.9.0", - "write-file-atomic": "^5.0.1" + "table": "^6.8.1", + "v8-compile-cache": "^2.3.0", + "write-file-atomic": "^4.0.2" }, "bin": { - "stylelint": "bin/stylelint.mjs" + "stylelint": "bin/stylelint.js" }, "engines": { - "node": ">=18.12.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" } }, "node_modules/stylelint-config-recommended": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-17.0.0.tgz", - "integrity": "sha512-WaMSdEiPfZTSFVoYmJbxorJfA610O0tlYuU2aEwY33UQhSPgFbClrVJYWvy3jGJx+XW37O+LyNLiZOEXhKhJmA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-9.0.0.tgz", + "integrity": "sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" - }, - { - "type": "github", - "url": "https://github.com/sponsors/stylelint" - } - ], "license": "MIT", - "engines": { - "node": ">=18.12.0" - }, "peerDependencies": { - "stylelint": "^16.23.0" + "stylelint": "^14.10.0" } }, "node_modules/stylelint-config-standard": { - "version": "39.0.1", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-39.0.1.tgz", - "integrity": "sha512-b7Fja59EYHRNOTa3aXiuWnhUWXFU2Nfg6h61bLfAb5GS5fX3LMUD0U5t4S8N/4tpHQg3Acs2UVPR9jy2l1g/3A==", + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-28.0.0.tgz", + "integrity": "sha512-q/StuowDdDmFCravzGHAwgS9pjX0bdOQUEBBDIkIWsQuYGgYz/xsO8CM6eepmIQ1fc5bKdDVimlJZ6MoOUcJ5Q==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" - }, - { - "type": "github", - "url": "https://github.com/sponsors/stylelint" - } - ], "license": "MIT", "dependencies": { - "stylelint-config-recommended": "^17.0.0" - }, - "engines": { - "node": ">=18.12.0" + "stylelint-config-recommended": "^9.0.0" }, "peerDependencies": { - "stylelint": "^16.23.0" + "stylelint": "^14.11.0" } }, "node_modules/stylelint/node_modules/balanced-match": { @@ -22871,33 +24383,6 @@ "dev": true, "license": "MIT" }, - "node_modules/stylelint/node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/stylelint/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -22905,38 +24390,6 @@ "dev": true, "license": "MIT" }, - "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.4.tgz", - "integrity": "sha512-5XRUFc0WTtUbjfGzEwXc42tiGxQHBmtbUG1h9L2apu4SulCGN3Hqm//9D6FAolf8MYNL7f/YlJl9vy08pj5JuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^6.1.13" - } - }, - "node_modules/stylelint/node_modules/flat-cache": { - "version": "6.1.18", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.18.tgz", - "integrity": "sha512-JUPnFgHMuAVmLmoH9/zoZ6RHOt5n9NlUw/sDXsTbROJ2SFoS2DS4s+swAV6UTeTbGH/CAsZIE6M8TaG/3jVxgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cacheable": "^2.1.0", - "flatted": "^3.3.3", - "hookified": "^1.12.0" - } - }, - "node_modules/stylelint/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/stylelint/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", @@ -22962,20 +24415,6 @@ "node": ">=8" } }, - "node_modules/stylelint/node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -22995,9 +24434,9 @@ } }, "node_modules/supports-hyperlinks": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", - "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", "dev": true, "license": "MIT", "dependencies": { @@ -23005,10 +24444,7 @@ "supports-color": "^7.0.0" }, "engines": { - "node": ">=14.18" - }, - "funding": { - "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + "node": ">=8" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -23513,6 +24949,16 @@ "node": ">=18" } }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -23980,6 +25426,7 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -23992,6 +25439,7 @@ }, "engines": { "node": ">=10.12.0" + } }, "node_modules/value-equal": { diff --git a/package.json b/package.json index 84900b6867..503d092b06 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@react-leaflet/core": "^2.1.0", "@reduxjs/toolkit": "^2.4.0", "@sentry/browser": "^9.15.0", - "@tanstack/react-query": "^5.85.3", + "@tanstack/react-query": "^5.90.16", "@tinymce/miniature": "^6.0.0", "@tinymce/tinymce-react": "^6.1.0", "ajv": "^8.0.0", @@ -54,7 +54,7 @@ "date-fns": "^2.14.0", "date-fns-tz": "^2.0.1", "dayjs": "^1.11.13", - "diff": "^5.0.0", + "diff": "^8.0.3", "dompurify": "^3.2.5", "elliptic": "^6.6.1", "font-awesome": "^4.7.0", @@ -65,7 +65,7 @@ "html2canvas": "^1.4.1", "jest": "^30.2.0", "jquery": "^3.7.1", - "jspdf": "^3.0.3", + "jspdf": "^4.0.0", "jwt-decode": "^2.2.0", "leaflet": "^1.9.4", "leaflet.heat": "^0.2.0", @@ -206,4 +206,4 @@ ], "**/*.{css,scss,sass}": "stylelint" } -} \ No newline at end of file +} diff --git a/public/index.css b/public/index.css index 04756f7f6f..74d18726bd 100644 --- a/public/index.css +++ b/public/index.css @@ -325,11 +325,33 @@ body.bm-dashboard-dark .page-item.active .page-link { } /* Fix the position of the header at the top */ -.top { - position: fixed; - right: 10px; - bottom: 10px; - } +.back-to-top { + position: fixed; + bottom: 24px; + right: 24px; + + width: 48px; + height: 48px; + + display: flex; + align-items: center; + justify-content: center; + + background-color: #2A1B3D; + color: #ffffff; + + border-radius: 50%; + font-size: 22px; + text-decoration: none; + + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.25); + z-index: 1000; +} + +.back-to-top:hover { + transform: translateY(-4px); + opacity: 0.9; +} /* Allow the page content to scroll horizontally */ .container-fluid { @@ -339,4 +361,9 @@ body.bm-dashboard-dark .page-item.active .page-link { /* Hide the horizontal scrollbar */ .container-fluid::-webkit-scrollbar { display: none; - } \ No newline at end of file + } + + .tab-content { + background-color: transparent; + border: none; +} \ No newline at end of file diff --git a/src/actions/authActions.js b/src/actions/authActions.js index 3c09a492c2..9cae0d52e0 100644 --- a/src/actions/authActions.js +++ b/src/actions/authActions.js @@ -2,7 +2,7 @@ import jwtDecode from 'jwt-decode'; import axios from 'axios'; import httpService from '../services/httpService'; import config from '../config.json'; -import { ENDPOINTS } from '~/utils/URL'; +import { ENDPOINTS } from '../utils/URL'; import { GET_ERRORS } from '../constants/errors'; import { SET_CURRENT_USER, diff --git a/src/actions/bmdashboard/projectActions.js b/src/actions/bmdashboard/projectActions.js index c1ae9d8add..cbae3e790a 100644 --- a/src/actions/bmdashboard/projectActions.js +++ b/src/actions/bmdashboard/projectActions.js @@ -30,3 +30,11 @@ export const fetchBMProjects = () => { }); }; }; + +export const getProjectGlobalDistribution = async (payload) => { + const url = ENDPOINTS.PROJECT_GLOBAL_DISTRIBUTION + + const res = await axios.get(url, {params: payload}); + + return res.data + }; diff --git a/src/actions/index.js b/src/actions/index.js index a7f807c188..dea90f68a8 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -142,6 +142,7 @@ export function getProjectById(projectId) { }; } + export function getProjectsByUser(userId) { const request = httpService.get(`${APIEndpoint}/projects/user/${userId}`); diff --git a/src/actions/weeklySummariesFilterAction.js b/src/actions/weeklySummariesFilterAction.js new file mode 100644 index 0000000000..a930eef5ff --- /dev/null +++ b/src/actions/weeklySummariesFilterAction.js @@ -0,0 +1,118 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; +import { ENDPOINTS } from '~/utils/URL'; +import axios from 'axios'; +import { normalizeFilter } from "~/utils/weeklySummariesFilterHelper"; + +export const weeklySummariesFiltersApi = createApi({ + reducerPath: "weeklySummariesFiltersApi", + baseQuery: fetchBaseQuery({ + prepareHeaders: (headers) => { + const token = axios.defaults.headers.common.Authorization; + if (token) headers.set("Authorization", token); + return headers; + }, + }), + tagTypes: ["WeeklySummariesFilters"], // <-- Add tag type + endpoints: (builder) => ({ + + // --------------------------------------- + // GET Filter List + // --------------------------------------- + getWeeklySummariesFilters: builder.query({ + query: () => ENDPOINTS.WEEKLY_SUMMARIES_FILTERS, + transformResponse: (response) => { + const filterList = response; + const updatedFilterChoices = []; + + filterList.forEach(filter => { + updatedFilterChoices.push(normalizeFilter(filter)); + }); + + return updatedFilterChoices; + }, + providesTags: ["WeeklySummariesFilters"], // <-- Cache tag + }), + + // --------------------------------------- + // CREATE New Filter + // --------------------------------------- + createWeeklySummariesFilter: builder.mutation({ + query: ({data}) => ({ + url: ENDPOINTS.WEEKLY_SUMMARIES_FILTERS, + method: "POST", + body: data, + }), + invalidatesTags: ["WeeklySummariesFilters"], // <-- Refresh cache + }), + + // --------------------------------------- + // UPDATE Existing Filter + // --------------------------------------- + updateWeeklySummariesFilter: builder.mutation({ + query: ({ id, data }) => ({ + url: ENDPOINTS.WEEKLY_SUMMARIES_FILTER_BY_ID(id), + method: "PATCH", + body: data, + }), + invalidatesTags: ["WeeklySummariesFilters"], // <-- Refresh cache + }), + + // --------------------------------------- + // REPLACE Existing Filter + // --------------------------------------- + replaceWeeklySummariesFilter: builder.mutation({ + query: ({ id, data }) => ({ + url: ENDPOINTS.WEEKLY_SUMMARIES_FILTER_BY_ID(id), + method: "PUT", + body: data, + }), + invalidatesTags: ["WeeklySummariesFilters"], // <-- Refresh cache + }), + + // --------------------------------------- + // DELETE Existing Filter + // --------------------------------------- + deleteWeeklySummariesFilter: builder.mutation({ + query: ({ id }) => ({ + url: ENDPOINTS.WEEKLY_SUMMARIES_FILTER_BY_ID(id), + method: "DELETE", + }), + invalidatesTags: ["WeeklySummariesFilters"], // <-- Refresh cache + }), + + // --------------------------------------- + // UPDATE Existing Filter with Replaced Team codes + // --------------------------------------- + updateFiltersWithReplacedTeamCodes: builder.mutation({ + query: ({oldTeamCodes, newTeamCode}) => ({ + url: ENDPOINTS.WEEKLY_SUMMARIES_FILTER_REPLACE_CODES, + method: "POST", + body: { oldTeamCodes, newTeamCode }, + }), + invalidatesTags: ["WeeklySummariesFilters"], // <-- Refresh cache + }), + + // --------------------------------------- + // UPDATE Existing Filter with Individual Codes changes + // --------------------------------------- + updateFiltersWithIndividualCodesChange: builder.mutation({ + query: ({oldTeamCode, newTeamCode, userId}) => ({ + url: ENDPOINTS.WEEKLY_SUMMARIES_FILTER_REPLACE_INDIVIDUAL_CODES, + method: "POST", + body: { oldTeamCode, newTeamCode, userId }, + }), + invalidatesTags: ["WeeklySummariesFilters"], // <-- Refresh cache + }), + + }), +}); + +export const { + useGetWeeklySummariesFiltersQuery, + useCreateWeeklySummariesFilterMutation, + useUpdateWeeklySummariesFilterMutation, + useReplaceWeeklySummariesFilterMutation, + useDeleteWeeklySummariesFilterMutation, + useUpdateFiltersWithReplacedTeamCodesMutation, + useUpdateFiltersWithIndividualCodesChangeMutation, +} = weeklySummariesFiltersApi; diff --git a/src/components/App.jsx b/src/components/App.jsx index 8cb7e9dfdc..612a90de95 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -107,6 +107,8 @@ function UpdateDocumentTitle() { { pattern: /^\/Logout$/, title: 'Logout' }, { pattern: /^\/forcePasswordUpdate\/[^/]+$/, title: 'Force Password Update' }, { pattern: /^\/$/, title: `Dashboard - ${fullName}` }, + { pattern: /^\/kitchenandinventory\/login$/, title: 'Kitchen and Inventory Login' }, + { pattern: /^\/kitchenandinventory$/, title: 'Kitchen and Inventory Dashboard' }, { pattern: /.*/, title: 'HGN APP' }, // Default case { pattern: /^\/communityportal\/activity\/activityid\/feedback$/, diff --git a/src/components/AutoUpdate/AutoUpdate.jsx b/src/components/AutoUpdate/AutoUpdate.jsx index 4a5ad51c83..2218a181b0 100644 --- a/src/components/AutoUpdate/AutoUpdate.jsx +++ b/src/components/AutoUpdate/AutoUpdate.jsx @@ -17,48 +17,63 @@ function AutoUpdate() { headers: noCacheHeaders, }; - // Create the hash request with proper URL handling for test environment - const hashRequest = (() => { - try { - return new Request('/hash.txt'); - } catch (error) { - // In test environment, use a fallback URL - return new Request('http://localhost/hash.txt'); + const resolveHashUrl = () => { + const isTestEnv = + (typeof import.meta !== 'undefined' && import.meta.env?.MODE === 'test') || + (typeof process !== 'undefined' && process.env?.NODE_ENV === 'test'); + + if (isTestEnv || typeof window === 'undefined' || !window.location) { + return null; } - })(); + return new URL('/hash.txt', window.location.origin).toString(); + }; useEffect(() => { - fetch(hashRequest, requestParams) - .then(response => { - response.text().then(text => { + const hashUrl = resolveHashUrl(); + if (!hashUrl || typeof fetch === 'undefined') { + return undefined; + } + + let isMounted = true; + fetch(hashUrl, requestParams) + .then(response => response.text()) + .then(text => { + if (isMounted) { setHash(text); - }); + } }) .catch(err => { console.error(err); // eslint-disable-line no-console }); + + return () => { + isMounted = false; + }; }, []); useEffect(() => { - if (hash !== undefined) { - const interval = setInterval(() => { - fetch(hashRequest, requestParams) - .then(response => { - response.text().then(text => { - if (text !== hash) { - setUpdated(true); - } - }); - }) - .catch(err => { - console.error(err); // eslint-disable-line no-console - }); - }, 5 * MINUTE); - return () => clearInterval(interval); + if (hash === undefined) { + return undefined; + } + + const hashUrl = resolveHashUrl(); + if (!hashUrl || typeof fetch === 'undefined') { + return undefined; } - // No cleanup needed if the hash is undefined. - return () => {}; + const interval = setInterval(() => { + fetch(hashUrl, requestParams) + .then(response => response.text()) + .then(text => { + if (text !== hash) { + setUpdated(true); + } + }) + .catch(err => { + console.error(err); // eslint-disable-line no-console + }); + }, 5 * MINUTE); + return () => clearInterval(interval); }, [hash]); if (!updated) return null; diff --git a/src/components/BMDashboard/BMDashboard.jsx b/src/components/BMDashboard/BMDashboard.jsx index bace0ae298..54f98edc55 100644 --- a/src/components/BMDashboard/BMDashboard.jsx +++ b/src/components/BMDashboard/BMDashboard.jsx @@ -5,7 +5,7 @@ import { fetchBMProjects } from '../../actions/bmdashboard/projectActions'; import ProjectsList from './Projects/ProjectsList'; import ProjectSelectForm from './Projects/ProjectSelectForm'; import BMError from './shared/BMError'; -import './BMDashboard.module.css'; +import styles from './BMDashboard.module.css'; export function BMDashboard() { const [isError, setIsError] = useState(false); @@ -170,18 +170,13 @@ export function BMDashboard() { return ( -
-

- Building and Inventory Management Dashboard -

+
+

Building and Inventory Management Dashboard

*, -.bm-dashboard-dark > * > *, -.bm-dashboard-dark > * > * > *, -.bm-dashboard-dark > * > * > * > *, -.bm-dashboard-dark > * > * > * > * > *, -.bm-dashboard-dark .container, -.bm-dashboard-dark .container *, -.bm-dashboard-dark .projects-list, -.bm-dashboard-dark .projects-list *, -.bm-dashboard-dark .project-summary, -.bm-dashboard-dark .project-summary *, -.bm-dashboard-dark .project-select-form, -.bm-dashboard-dark .project-select-form *, -.bm-dashboard-dark header, -.bm-dashboard-dark header *, -.bm-dashboard-dark main, -.bm-dashboard-dark main * { - color: #ffffff !important; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7) !important; -} - -/* Force background colors for better contrast */ -.bm-dashboard-dark .container, -.bm-dashboard-dark .projects-list, -.bm-dashboard-dark .project-summary, -.bm-dashboard-dark .inv-form-page-container, -.bm-dashboard-dark .bm-error-page { - background-color: #1b2a41 !important; - color: #ffffff !important; -} - -/* Override any Bootstrap or other framework styles */ -.bm-dashboard-dark .text-primary, -.bm-dashboard-dark .text-secondary, -.bm-dashboard-dark .text-success, -.bm-dashboard-dark .text-info, -.bm-dashboard-dark .text-warning, -.bm-dashboard-dark .text-danger, -.bm-dashboard-dark .text-light, -.bm-dashboard-dark .text-dark, -.bm-dashboard-dark .text-body, -.bm-dashboard-dark .text-muted { - color: #ffffff !important; -} - -/* Force all buttons and interactive elements */ -.bm-dashboard-dark button, -.bm-dashboard-dark .btn, -.bm-dashboard-dark .btn-primary, -.bm-dashboard-dark .btn-secondary, -.bm-dashboard-dark .btn-success, -.bm-dashboard-dark .btn-info, -.bm-dashboard-dark .btn-warning, -.bm-dashboard-dark .btn-danger, -.bm-dashboard-dark .btn-light, -.bm-dashboard-dark .btn-dark, -.bm-dashboard-dark .btn-outline-primary, -.bm-dashboard-dark .btn-outline-secondary { - background-color: #3a506b !important; - color: #ffffff !important; - border-color: #5a7a9b !important; -} - -.bm-dashboard-dark button:hover, -.bm-dashboard-dark .btn:hover { - background-color: #4a607b !important; - color: #ffffff !important; - border-color: #6a8aab !important; -} - -/* LogBar specific overrides for BMDashboard */ -.bm-dashboard-dark .log-bar, -.bm-dashboard-dark .log-bar-dark { - background-color: #1b2a41 !important; - color: #ffffff !important; -} - -.bm-dashboard-dark .log-bar h2, -.bm-dashboard-dark .log-bar-dark h2, -.bm-dashboard-dark .log-bar__section h2 { - color: #ffffff !important; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7) !important; -} - -.bm-dashboard-dark .log-bar .button.btn, -.bm-dashboard-dark .log-bar-dark .button.btn { - color: #ffffff !important; -} - -/* Light mode LogBar overrides */ -.bm-dashboard-container:not(.bm-dashboard-dark) .log-bar h2, -.bm-dashboard-container:not(.bm-dashboard-dark) .log-bar__section h2 { - color: #333333 !important; - text-shadow: none !important; -} - -.bm-dashboard-container:not(.bm-dashboard-dark) .log-bar .button.btn { - color: #ffffff !important; -} - -/* Image clarity improvements for BMDashboard */ -.bm-dashboard-container img, -.bm-dashboard-dark img, -.bm-dashboard-light img { - image-rendering: auto !important; - -webkit-filter: none !important; - filter: none !important; - backface-visibility: hidden !important; - transform: translateZ(0) !important; - -webkit-font-smoothing: antialiased !important; - -moz-osx-font-smoothing: grayscale !important; -} - -.bm-dashboard-container .single-card__img img, -.bm-dashboard-dark .single-card__img img, -.bm-dashboard-light .single-card__img img { - object-fit: cover !important; - object-position: center !important; - max-width: 100% !important; - height: auto !important; - image-rendering: auto !important; -} - - diff --git a/src/components/BMDashboard/Equipment/Add/AddEquipmentType.jsx b/src/components/BMDashboard/Equipment/Add/AddEquipmentType.jsx index f308c2c0a0..af6635dbf8 100644 --- a/src/components/BMDashboard/Equipment/Add/AddEquipmentType.jsx +++ b/src/components/BMDashboard/Equipment/Add/AddEquipmentType.jsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { BsInfoCircle } from 'react-icons/bs'; import BMError from '~/components/BMDashboard/shared/BMError'; +import styles from '../../BMDashboard.module.css'; import { Button } from 'reactstrap'; import CheckTypesModal from '~/components/BMDashboard/shared/CheckTypesModal'; import AddTypeForm from './AddTypeForm'; @@ -19,7 +20,7 @@ export default function AddEquipmentType() { if (isError) { return ( -
+

Add Type: Equipment

@@ -27,11 +28,11 @@ export default function AddEquipmentType() { } return ( -
+

Add Type: Equipment

-
+
Add a new type of equipment so it can be purchased and used in projects
diff --git a/src/components/BMDashboard/Equipment/DailyActivityLog/EDailyActivityLog.jsx b/src/components/BMDashboard/Equipment/DailyActivityLog/EDailyActivityLog.jsx index e4700b4b04..1c79d7b7a2 100644 --- a/src/components/BMDashboard/Equipment/DailyActivityLog/EDailyActivityLog.jsx +++ b/src/components/BMDashboard/Equipment/DailyActivityLog/EDailyActivityLog.jsx @@ -142,26 +142,51 @@ function EDailyActivityLog(props) { className={`container-fluid ${darkMode ? 'bg-oxford-blue text-light' : ''}`} style={{ height: '100%' }} > + {/* Custom dark mode table row and header hover style: dark background, light text */} + {darkMode && ( + + )}

Daily Equipment Log

{/* header */}
-
-
- {/* Chart */} -
- {normalizedIssues.length === 0 ? ( -

No issues found.

+
+ {/* Step 6: Project Legend above the chart */} +
+ {projectLegend.map(p => ( +
+ + {p.projectName} +
+ ))} +
+ {!issues || issues.length === 0 ? ( +
+
+

No Open Issues Found

+

There are currently no open issues matching your selected criteria.

+

Try adjusting your date range or project filters to see more results.

+
+
) : ( - + - - `${v} mo`} - fill={darkMode ? '#fff' : '#000'} - /> + `${value} months`} + labelFormatter={label => `Issue: ${label}`} + /> + {/* Step 5: Bar uses per-project colors */} + + {chartData.map((entry, index) => ( + + ))} diff --git a/src/components/BMDashboard/ItemList/ItemsTable.jsx b/src/components/BMDashboard/ItemList/ItemsTable.jsx index 53ae6cc115..259715d79c 100644 --- a/src/components/BMDashboard/ItemList/ItemsTable.jsx +++ b/src/components/BMDashboard/ItemList/ItemsTable.jsx @@ -4,6 +4,7 @@ import { BiPencil } from 'react-icons/bi'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSortDown, faSort, faSortUp } from '@fortawesome/free-solid-svg-icons'; import RecordsModal from './RecordsModal'; +import MaterialUsageChart from '../MaterialUsage/MaterialUsageChart'; import styles from './ItemListView.module.css'; export default function ItemsTable({ @@ -20,6 +21,8 @@ export default function ItemsTable({ const [recordType, setRecordType] = useState(''); const [updateModal, setUpdateModal] = useState(false); const [updateRecord, setUpdateRecord] = useState(null); + const [showChartModal, setShowChartModal] = useState(false); + const [chartProjectId, setChartProjectId] = useState(null); const [projectNameCol, setProjectNameCol] = useState({ iconsToDisplay: faSort, sortOrder: 'default', @@ -46,9 +49,24 @@ export default function ItemsTable({ }; const handleViewRecordsClick = (data, type) => { - setModal(true); - setRecord(data); - setRecordType(type); + if (type === 'UsageRecord') { + // For UsageRecord, show the chart directly + const projectId = data.project?._id || data.projectId; + if (projectId) { + setChartProjectId(projectId); + setShowChartModal(true); + } else { + // If no project ID, fall back to the regular modal + setModal(true); + setRecord(data); + setRecordType(type); + } + } else { + // For other record types, show the regular modal + setModal(true); + setRecord(data); + setRecordType(type); + } }; const sortData = columnName => { @@ -90,6 +108,7 @@ export default function ItemsTable({ return ( <> + {/* Regular Records Modal for Update and Purchase records */} + + {/* Direct Chart Modal for Usage Records */} + {showChartModal && chartProjectId && ( + setShowChartModal(false)} /> + )} +
diff --git a/src/components/BMDashboard/LessonList/LessonCard.jsx b/src/components/BMDashboard/LessonList/LessonCard.jsx index d55cf6961b..2fd4c7c13c 100644 --- a/src/components/BMDashboard/LessonList/LessonCard.jsx +++ b/src/components/BMDashboard/LessonList/LessonCard.jsx @@ -10,6 +10,7 @@ import DeleteLessonCardPopUp from './DeleteLessonCardPopUp'; import styles from './LessonCard.module.css'; function LessonCard({ filteredLessons, onEditLessonSummary, onDeliteLessonCard, handleLike }) { + const darkMode = useSelector(state => state.theme.darkMode); const maxSummaryLength = 1500; const [expandedCards, setExpandedCards] = useState([]); const auth = useSelector(state => state.auth); @@ -79,7 +80,10 @@ function LessonCard({ filteredLessons, onEditLessonSummary, onDeliteLessonCard, const lessonCards = filteredLessons.map(lesson => { const { isLiked, totalLikes } = getLikeStatus(lesson._id); return ( - + toggleCardExpansion(lesson._id)} style={{ cursor: 'pointer' }} @@ -146,10 +150,10 @@ function LessonCard({ filteredLessons, onEditLessonSummary, onDeliteLessonCard, ) : ( - {ReactHtmlParser( - lesson.content.length > maxSummaryLength + {parse( + (lesson?.content || '').length > maxSummaryLength ? `${lesson.content.slice(0, maxSummaryLength)}...` - : lesson.content, + : lesson.content || '', )} )} diff --git a/src/components/BMDashboard/LessonList/LessonCard.module.css b/src/components/BMDashboard/LessonList/LessonCard.module.css index 4f36e31bcc..08ca0ab7ca 100644 --- a/src/components/BMDashboard/LessonList/LessonCard.module.css +++ b/src/components/BMDashboard/LessonList/LessonCard.module.css @@ -1,30 +1,75 @@ - .lessonCard { border: 2px solid #78bdda !important; background-color: #fbfbfb !important; margin-bottom: 2rem; overflow: hidden; } + +.darkCard { + background-color: #1e1e1e !important; + color: #f5f5f5 !important; + border-color: #333 !important; +} + +.darkCard .lessonCardHeader { + background-color: #2a2a2a !important; + color: #ffffff !important; + border-bottom: 1px solid #333 !important; +} + +.darkCard .scrollableCardBody { + background-color: #1e1e1e !important; + color: #e6e6e6 !important; +} + +.darkCard span, +.darkCard p, +.darkCard div { + color: #e6e6e6 !important; +} + +.darkCard .tagItem { + color: #bfbfbf !important; +} + +.darkCard .lessonCardFooter { + background-color: #2a2a2a !important; + color: #cccccc !important; + border-top: 1px solid #333 !important; +} + +.darkCard textarea.editable-lesson-summary { + background-color: #2a2a2a !important; + color: #ffffff !important; + border: 1px solid #555 !important; +} + .lessonCardHeader { background-color: #78bdda !important; padding: 0.3rem 1.25rem !important; } + .lessonCardBody { min-height: 20vh; display: flex; flex-direction: column; justify-content: space-between; } + .cardTagAndFile { font-size: small; + color: #78bdda; } + .cardTagAndFile .tagItem { display: inline-block; margin-right: 5px; } + .lessonCardHeader:first-child { border-radius: 0 !important; } + .lessonCardFooter { background-color: #e8f4f9 !important; display: flex; @@ -33,9 +78,11 @@ padding: 0.3rem 0.625rem !important; border-top: unset !important; } + .lessonCardFooter:last-child { border-radius: 0 !important; } + .lessonCardTag { display: flex; flex-direction: row; @@ -69,9 +116,11 @@ height: 50px; flex-direction: column; } + .lessonCardNavItem { font-size: small; } + .navItemTitle { font-weight: bold; font-size: small; @@ -81,21 +130,21 @@ display: flex; justify-content: space-between; } -.cardTagAndFile { - color: #78bdda; -} .cardScrollable { max-height: 300px; overflow-y: auto; } + .tagItem { font-size: small; white-space: nowrap; } + .lessonFile { font-size: small; } + .lessonSummary button { border: 1px solid #ccc; border-radius: 5px; @@ -104,16 +153,20 @@ margin-right: 5px; font-size: 14px; } + .lessonSummary textarea { height: 10rem; } + .scrollableCardBody::-webkit-scrollbar { width: 4px; } + .scrollableCardBody::-webkit-scrollbar-thumb { background-color: #ccc; border-radius: 5px; } + .cardTag::-webkit-scrollbar-horizontal { width: 4px; } @@ -127,12 +180,14 @@ margin-right: 5px; border-radius: 5px; } + .validationError { color: red; font-size: small; display: block; margin-bottom: 10px; } + .editableLessonSummary.error { border: 1px solid red; } @@ -142,6 +197,7 @@ flex-direction: column; align-items: flex-start; } + .lessonCardNav { height: auto; display: flex; @@ -152,24 +208,24 @@ justify-content: flex-start; } } + validation-error { color: red; font-size: small; display: block; margin-bottom: 10px; } -.editableLessonSummary.error { - border: 1px solid red; -} @media (max-width: 570px) { .formSelectContainer { flex-direction: column; } + .cardFooter { font-size: small; } } + @media (max-width: 767px) { .cardText { padding: 10px; diff --git a/src/components/BMDashboard/LogTools/LogTools.jsx b/src/components/BMDashboard/LogTools/LogTools.jsx index 93b32e7865..f68658a059 100644 --- a/src/components/BMDashboard/LogTools/LogTools.jsx +++ b/src/components/BMDashboard/LogTools/LogTools.jsx @@ -15,6 +15,7 @@ import styles from './LogTools.module.css'; function LogTools() { const toolTypes = useSelector(state => state.bmInvTypes.list); const projects = useSelector(state => state.bmProjects); + const darkMode = useSelector(state => state.theme.darkMode); const dispatch = useDispatch(); const history = useHistory(); const today = new Date().toISOString().split('T')[0]; @@ -43,32 +44,6 @@ function LogTools() { }); }; - const multiSelectCustomStyles = { - control: provided => ({ - ...provided, - width: 300, - }), - multiValue: provided => ({ - ...provided, - padding: '5px', - backgroundColor: '#f7f7f7', - borderRadius: '5px', - border: '1px solid #aaacaf', - }), - multiValueLabel: provided => ({ - ...provided, - color: '#aaacaf', - }), - multiValueRemove: provided => ({ - ...provided, - svg: { - ...provided.svg, - width: 20, - height: 20, - }, - }), - }; - useEffect(() => { dispatch(fetchToolTypes()); dispatch(fetchBMProjects()); @@ -225,21 +200,36 @@ function LogTools() { }; return ( -
-
-
+
+
+
TOOL/EQUIPMENT DAILY ACTIVITIES LOG
- - - -
+
- + - + @@ -295,7 +328,10 @@ function LogTools() { {relevantToolTypes.length > 0 ? ( relevantToolTypes.map((toolType, index) => ( - + @@ -306,7 +342,6 @@ function LogTools() { ref={selectRefs.current[index]} options={toolType.items} isMulti - styles={multiSelectCustomStyles} onChange={handleCodeSelect} aria-label="Select Tool Code" /> @@ -314,7 +349,7 @@ function LogTools() { )) ) : ( - + @@ -338,5 +373,4 @@ function LogTools() { ); } - export default LogTools; diff --git a/src/components/BMDashboard/LogTools/LogTools.module.css b/src/components/BMDashboard/LogTools/LogTools.module.css index 059274a3e9..6afcb1f75d 100644 --- a/src/components/BMDashboard/LogTools/LogTools.module.css +++ b/src/components/BMDashboard/LogTools/LogTools.module.css @@ -1,3 +1,13 @@ +/* Custom dark mode table row hover effect */ +/* Improved dark mode table row hover effect */ +.toolTypeRow.dark-mode:hover, +tr.toolTypeRow.dark-mode:hover, +tbody tr.toolTypeRow.dark-mode:hover { + background-color: inherit !important; + color: inherit !important; + border: inherit !important; + transition: none !important; +} .page { margin: 0; display: flex; @@ -163,4 +173,49 @@ .toolTypeRow { border: none !important; color:#aaacaf; +} + +/* Dark mode styles */ +.page.dark-mode { + background-color: #1e2736 !important; +} + +.logFormContainer.dark-mode { + background-color: #343a40 !important; + border-color: #495057 !important; + color: #fff !important; +} + +.titleLabel.dark-mode { + color: #fff !important; +} + +.selectorLabel.dark-mode { + color: #fff !important; +} + +.subtitleRow.dark-mode { + background-color: #495057 !important; + color: #fff !important; +} + +.tableSubtitle.dark-mode { + color: #adb5bd !important; +} + +.subtitleHighlight.dark-mode { + color: red !important; +} + +.toolTypeHead.dark-mode { + color: #fff !important; + border-bottom-color: #6c757d !important; +} + +.toolTypeRow.dark-mode { + color: #fff !important; +} + +.toolTypeRow.dark-mode td { + color: #fff !important; } \ No newline at end of file diff --git a/src/components/BMDashboard/Login/__tests__/BMLogin.test.jsx b/src/components/BMDashboard/Login/__tests__/BMLogin.test.jsx index 0784893778..872ccc9ba8 100644 --- a/src/components/BMDashboard/Login/__tests__/BMLogin.test.jsx +++ b/src/components/BMDashboard/Login/__tests__/BMLogin.test.jsx @@ -4,11 +4,13 @@ import { useDispatch, Provider } from 'react-redux'; import thunk from 'redux-thunk'; import { configureStore } from 'redux-mock-store'; import { BrowserRouter as Router } from 'react-router-dom'; -import axios from 'axios'; import BMLogin from '../BMLogin'; import { act } from 'react-dom/test-utils'; +const mockLoginBMUser = vi.fn(); -vi.mock('axios'); +vi.mock('~/actions/authActions', () => ({ + loginBMUser: (...args) => mockLoginBMUser(...args), +})); vi.mock('jwt-decode', () => ({ default: vi.fn(() => ({ decodedPayload: 'mocked_decoded_payload' })), @@ -17,11 +19,17 @@ vi.mock('jwt-decode', () => ({ const mockStore = configureStore([thunk]); let store; +const TEST_PASSWORD = 'test-password'; // NOSONAR - test-only credential +const SHORT_PASSWORD = '12'; // NOSONAR - test-only invalid password + beforeEach(() => { store = mockStore({ auth: { isAuthenticated: true, user: { + access: { + canAccessBMPortal: true, + }, permissions: { frontPermissions: [], backPermissions: [], @@ -57,6 +65,12 @@ const renderComponent = testStore => { ); }; +const fillAndSubmit = ({ email = 'test@gmail.com', password = TEST_PASSWORD } = {}) => { + fireEvent.change(screen.getByLabelText(/email/i), { target: { value: email } }); + fireEvent.change(screen.getByLabelText(/password/i), { target: { value: password } }); + fireEvent.click(screen.getByText('Submit')); +}; + describe('BMLogin component', () => { it('renders without crashing', () => { renderComponent(store); @@ -114,9 +128,7 @@ describe('BMLogin component', () => { it('shows validation error for invalid email', () => { renderComponent(store); - fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'test' } }); - fireEvent.change(screen.getByLabelText(/password/i), { target: { value: '12' } }); - fireEvent.click(screen.getByText('Submit')); + fillAndSubmit({ email: 'test', password: SHORT_PASSWORD }); expect(screen.getByLabelText(/email/i)).toBeInvalid(); expect(screen.getByText('"email" must be a valid email')).toBeInTheDocument(); @@ -124,9 +136,7 @@ describe('BMLogin component', () => { it('shows validation error for short password', () => { renderComponent(store); - fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'test@gmail.com' } }); - fireEvent.change(screen.getByLabelText(/password/i), { target: { value: '12' } }); - fireEvent.click(screen.getByText('Submit')); + fillAndSubmit({ password: SHORT_PASSWORD }); expect(screen.getByLabelText(/password/i)).toBeInvalid(); expect( @@ -135,34 +145,28 @@ describe('BMLogin component', () => { }); it('logs in successfully with correct credentials', async () => { - axios.post.mockResolvedValue({ + mockLoginBMUser.mockImplementationOnce(() => async () => ({ statusText: 'OK', data: { token: '1234' }, - }); + })); renderComponent(store); - fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'test@gmail.com' } }); - fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'Test12345' } }); - fireEvent.click(screen.getByText('Submit')); + fillAndSubmit(); - // eslint-disable-next-line testing-library/no-unnecessary-act - await act(async () => { - fireEvent.click(screen.getByText('Submit')); + await waitFor(() => { + expect(history.push).toHaveBeenCalledWith('/bmdashboard'); }); - expect(history.push).toHaveBeenCalledWith('/bmdashboard'); // if you're using `useNavigate` from React Router v6+ }); it('displays specific validation error for status 422', async () => { - axios.post.mockResolvedValue({ + mockLoginBMUser.mockImplementationOnce(() => async () => ({ statusText: 'ERROR', status: 422, - data: { token: '1234', label: 'email', message: 'User not found' }, - }); + data: { label: 'email', message: 'User not found' }, + })); renderComponent(store); - fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'test@gmail.com' } }); - fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'Test12345' } }); - fireEvent.click(screen.getByText('Submit')); + fillAndSubmit(); await waitFor(() => { expect(screen.getByText('User not found')).toBeInTheDocument(); @@ -170,16 +174,14 @@ describe('BMLogin component', () => { }); it('does not show error if status is not 422', async () => { - axios.post.mockResolvedValue({ + mockLoginBMUser.mockImplementationOnce(() => async () => ({ statusText: 'ERROR', status: 500, - data: { token: '1234' }, - }); + data: {}, + })); renderComponent(store); - fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'test@gmail.com' } }); - fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'Test12345' } }); - fireEvent.click(screen.getByText('Submit')); + fillAndSubmit(); await waitFor(() => { expect(screen.queryByText(/invalid/i)).not.toBeInTheDocument(); @@ -187,12 +189,14 @@ describe('BMLogin component', () => { }); it('handles post rejection without validation error', async () => { - axios.post.mockRejectedValue({ response: 'server error' }); + mockLoginBMUser.mockImplementationOnce(() => async () => ({ + statusText: 'ERROR', + status: 500, + data: {}, + })); renderComponent(store); - fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'test@gmail.com' } }); - fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'Test12345' } }); - fireEvent.click(screen.getByText('Submit')); + fillAndSubmit(); await waitFor(() => { expect(screen.queryByText(/invalid/i)).not.toBeInTheDocument(); diff --git a/src/components/BMDashboard/MaterialUsage/MaterialUsageChart.jsx b/src/components/BMDashboard/MaterialUsage/MaterialUsageChart.jsx new file mode 100644 index 0000000000..cdb41d888a --- /dev/null +++ b/src/components/BMDashboard/MaterialUsage/MaterialUsageChart.jsx @@ -0,0 +1,212 @@ +import { Modal, ModalHeader, ModalBody, Spinner } from 'reactstrap'; +import { PieChart, Pie, Cell, ResponsiveContainer } from 'recharts'; +import { useEffect, useState, useMemo } from 'react'; +import PropTypes from 'prop-types'; +import axios from 'axios'; +import { ENDPOINTS } from '../../../utils/URL'; +import styles from './MaterialUsageChart.module.css'; + +// Constants +const COLORS = ['#A74C4C', '#4C4C4C', '#C9B28A']; + +export default function MaterialUsageChart({ projectId, toggle, darkMode = false }) { + const [chartData, setChartData] = useState([]); + const [increase, setIncrease] = useState(0); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + + // Calculate percentages for chart data + const chartDataWithPercentages = useMemo(() => { + if (!chartData.length) return []; + + const total = chartData.reduce((sum, item) => sum + item.value, 0); + if (total === 0) return chartData.map(item => ({ ...item, percentage: '0.00' })); + + return chartData.map(item => ({ + ...item, + percentage: total > 0 ? ((item.value / total) * 100).toFixed(2) : '0.00', + })); + }, [chartData]); + + // Helper functions + const formatIncrease = value => (value >= 0 ? `+${value}%` : `${value}%`); + + const isEmptyData = useMemo(() => chartData.every(item => item.value === 0), [chartData]); + + // Center label component + const CenterLabel = ({ increase }) => ( +
+ {formatIncrease(increase)} +
week over week
+
+ ); + + // Chart legend component + const ChartLegend = ({ data }) => ( +
+ {data.map((entry, index) => ( +
+
+ + {entry.name}: {entry.percentage}% + +
+ ))} +
+ ); + + // Custom label component to show percentages and labels outside the chart + const renderCustomizedLabel = ({ + cx, + cy, + midAngle, + innerRadius, + outerRadius, + percentage, + name, + }) => { + const RADIAN = Math.PI / 180; + const radius = outerRadius + 30; + const x = cx + radius * Math.cos(-midAngle * RADIAN); + const y = cy + radius * Math.sin(-midAngle * RADIAN); + + return ( + + cx ? 'start' : 'end'} + dominantBaseline="central" + fontSize={12} + fontWeight="600" + > + {`${name}: ${percentage}%`} + + {/* Add a line connecting the segment to the label */} + + + ); + }; + + // Data fetching + useEffect(() => { + if (!projectId) return; + + const fetchData = async () => { + try { + setLoading(true); + setError(''); + const { data } = await axios.get(`${ENDPOINTS.BM_MATERIALS}/${projectId}`, { + timeout: 10000, + }); + + const { + availableMaterials = 0, + usedMaterials = 0, + wastedMaterials = 0, + increaseOverLastWeek = 0, + } = data; + + setChartData([ + { name: 'Available', value: availableMaterials }, + { name: 'Used', value: usedMaterials }, + { name: 'Wasted', value: wastedMaterials }, + ]); + setIncrease(increaseOverLastWeek); + } catch (err) { + console.error('Error fetching materials:', err); + setError('Failed to fetch chart data. Please try again.'); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [projectId]); + + // Retry function + const retryFetch = () => { + setError(''); + setLoading(true); + }; + + // Apply dark mode class if enabled + const modalClass = darkMode + ? `${styles.materialChartModal} ${styles.darkMode}` + : styles.materialChartModal; + + return ( + + + Material Usage Proportion + + + + {loading ? ( +
+ +
Loading material data...
+
+ ) : error ? ( +
+

{error}

+ +
+ ) : isEmptyData ? ( +
+

No material data available

+
+ ) : ( +
+
+ + + + {chartDataWithPercentages.map((entry, index) => ( + + ))} + + + + +
+ + +
+ )} +
+
+ ); +} + +MaterialUsageChart.propTypes = { + projectId: PropTypes.string.isRequired, + toggle: PropTypes.func.isRequired, + darkMode: PropTypes.bool, +}; diff --git a/src/components/BMDashboard/MaterialUsage/MaterialUsageChart.module.css b/src/components/BMDashboard/MaterialUsage/MaterialUsageChart.module.css new file mode 100644 index 0000000000..6f8a0d3db3 --- /dev/null +++ b/src/components/BMDashboard/MaterialUsage/MaterialUsageChart.module.css @@ -0,0 +1,431 @@ +/* Modal Container */ +.materialChartModal :global(.modal-dialog) { + max-width: 800px; + margin: 1rem auto; +} + +.materialChartModal :global(.modal-content) { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); +} + +/* Header */ +.materialChartHeader { + background: #f8f9fa; + color: #212529; + border-bottom: 1px solid #dee2e6; + padding: 1rem 1.5rem; + font-size: 1.25rem; + font-weight: 600; +} + +.materialChartHeader :global(.close) { + color: #6c757d; + opacity: 0.7; + font-size: 1.5rem; +} + +.materialChartHeader :global(.close):hover { + opacity: 1; + color: #495057; +} + +/* Body */ +.materialChartBody { + padding: 0; + display: flex; + flex-direction: column; + min-height: 500px; + background: #ffffff; +} + +/* Main Chart Container */ +.chartMainContainer { + flex: 1; + display: flex; + flex-direction: column; + padding: 1.5rem; + gap: 1.5rem; +} + +/* Pie Chart Wrapper */ +.pieChartWrapper { + flex: 1; + position: relative; + min-height: 350px; + background: #ffffff; + border-radius: 8px; + padding: 1rem; + border: 1px solid #e9ecef; + display: flex; + align-items: center; + justify-content: center; + overflow: visible; +} + +/* Center Label */ +.centerLabel { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; + z-index: 10; + background: #ffffff; + padding: 1rem; + border-radius: 50%; + width: 100px; + height: 100px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border: 1px solid #f1f3f4; +} + +.centerLabelValue { + font-size: 1.1rem; + font-weight: 700; + color: #2c3e50; + margin: 0; + line-height: 1.2; +} + +.centerLabelSubtitle { + font-size: 0.75rem; + color: #6c757d; + margin-top: 0.25rem; + font-weight: 500; +} + +/* Chart Legend */ +.chartLegend { + display: flex; + justify-content: center; + gap: 1.5rem; + flex-wrap: wrap; + padding: 1rem; + background: #f8f9fa; + border-radius: 6px; + border: 1px solid #e9ecef; +} + +.legendItem { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.4rem 0.8rem; + background: #ffffff; + border-radius: 4px; + border: 1px solid #dee2e6; + transition: all 0.2s ease; +} + +.legendItem:hover { + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.legendColor { + width: 16px; + height: 16px; + border-radius: 3px; + border: 2px solid #ffffff; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + transition: transform 0.2s ease; +} + +.legendItem:hover .legendColor { + transform: scale(1.1); +} + +.legendText { + font-size: 0.9rem; + font-weight: 500; + color: #495057; + white-space: nowrap; +} + +/* Loading States */ +.chartLoadingContainer { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1rem; + padding: 2rem; +} + +.chartLoadingText { + font-size: 1rem; + color: #6c757d; + font-weight: 500; +} + +/* Error States */ +.chartErrorContainer { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1rem; + padding: 2rem; +} + +.chartErrorText { + font-size: 1rem; + color: #dc3545; + text-align: center; + margin: 0; + font-weight: 500; + max-width: 400px; + line-height: 1.5; +} + +.chartRetryButton { + background: #007bff; + color: white; + border: none; + padding: 0.5rem 1.5rem; + border-radius: 4px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + font-size: 0.9rem; +} + +.chartRetryButton:hover { + background: #0056b3; + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 123, 255, 0.3); +} + +.chartRetryButton:active { + transform: translateY(0); +} + +/* Empty State */ +.chartEmptyContainer { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; +} + +.chartEmptyText { + font-size: 1rem; + color: #6c757d; + margin: 0; + font-weight: 500; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .materialChartModal :global(.modal-dialog) { + max-width: 95%; + margin: 0.5rem auto; + } + + .materialChartHeader { + padding: 0.75rem 1rem; + font-size: 1.1rem; + } + + .chartMainContainer { + padding: 1rem; + gap: 1rem; + } + + .pieChartWrapper { + min-height: 300px; + padding: 0.5rem; + } + + .centerLabel { + width: 80px; + height: 80px; + padding: 0.75rem; + } + + .centerLabelValue { + font-size: 0.9rem; + } + + .centerLabelSubtitle { + font-size: 0.65rem; + } + + .chartLegend { + gap: 1rem; + padding: 0.75rem; + } + + .legendItem { + padding: 0.3rem 0.6rem; + } + + .legendText { + font-size: 0.8rem; + } +} + +@media (max-width: 576px) { + .materialChartModal :global(.modal-dialog) { + margin: 0.25rem auto; + max-width: 98%; + } + + .materialChartBody { + min-height: 450px; + } + + .pieChartWrapper { + min-height: 280px; + padding: 0.25rem; + } + + .centerLabel { + width: 80px; + height: 80px; + padding: 0.6rem; + } + + .centerLabelValue { + font-size: 0.9rem; + } + + .centerLabelSubtitle { + font-size: 0.65rem; + } + + .chartLegend { + flex-direction: column; + align-items: center; + gap: 0.8rem; + padding: 0.75rem; + } + + .legendItem { + width: 100%; + max-width: 200px; + justify-content: center; + padding: 0.5rem 0.75rem; + } + + .legendText { + font-size: 0.8rem; + } + + .chartLoadingText, + .chartErrorText, + .chartEmptyText { + font-size: 0.9rem; + } + + .chartRetryButton { + padding: 0.4rem 1.25rem; + font-size: 0.85rem; + } +} + +/* Dark Mode */ +.darkMode :global(.modal-content) { + background-color: #1a1a1a; + border-color: #333; +} + +.darkMode .materialChartHeader { + background: #2d3748; + color: #ffffff; + border-bottom-color: #4a5568; +} + +.darkMode .materialChartHeader :global(.close) { + color: #cbd5e0; +} + +.darkMode .materialChartHeader :global(.close):hover { + color: #ffffff; +} + +.darkMode .materialChartBody { + background: #1a202c; +} + +.darkMode .pieChartWrapper { + background: #2d3748; + border-color: #4a5568; +} + +.darkMode .centerLabel { + background: #2d3748; + border-color: #4a5568; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); +} + +.darkMode .centerLabelValue { + color: #ffffff; +} + +.darkMode .centerLabelSubtitle { + color: #cbd5e0; +} + +.darkMode .chartLegend { + background: #2d3748; + border-color: #4a5568; +} + +.darkMode .legendItem { + background: #4a5568; + border-color: #718096; +} + +.darkMode .legendText { + color: #e2e8f0; +} + +.darkMode .chartLoadingText { + color: #a0aec0; +} + +.darkMode .chartErrorText { + color: #fc8181; +} + +.darkMode .chartEmptyText { + color: #a0aec0; +} + +.darkMode .chartRetryButton { + background: #3182ce; +} + +.darkMode .chartRetryButton:hover { + background: #2c5aa0; +} + +/* Accessibility */ +.chartRetryButton:focus { + outline: 2px solid #007bff; + outline-offset: 2px; +} + +/* Reduced motion support */ +@media (prefers-reduced-motion: reduce) { + .legendItem, + .legendColor, + .chartRetryButton { + transition: none; + } + + .legendItem:hover, + .chartRetryButton:hover { + transform: none; + } +} \ No newline at end of file diff --git a/src/components/BMDashboard/Projects/ProjectSelectForm.jsx b/src/components/BMDashboard/Projects/ProjectSelectForm.jsx index 9a77562e8e..119095be09 100644 --- a/src/components/BMDashboard/Projects/ProjectSelectForm.jsx +++ b/src/components/BMDashboard/Projects/ProjectSelectForm.jsx @@ -8,7 +8,6 @@ function ProjectSelectForm() { const darkMode = useSelector(state => state.theme?.darkMode || false); const [selectedProject, setSelectedProject] = useState(''); - const history = useHistory(); const handleGo = () => { @@ -54,8 +53,21 @@ function ProjectSelectForm() { diff --git a/src/components/BMDashboard/Projects/ProjectSummary.css b/src/components/BMDashboard/Projects/ProjectSummary.css deleted file mode 100644 index f591582b60..0000000000 --- a/src/components/BMDashboard/Projects/ProjectSummary.css +++ /dev/null @@ -1,35 +0,0 @@ -.project-summary_content { - padding: 20px; -} - -.project-summary_item { - margin-bottom: 15px; -} - -.project-summary_label { - font-weight: bold; - text-align: left; - white-space: normal; /* Allow text to wrap properly */ -} - -.project-summary_value { - overflow-wrap: break-word; /* Ensures the content wraps if too long */ - word-wrap: break-word; /* Ensures wrapping behavior */ -} - -.project-summary_span { - display: block; - word-break: break-word; /* Break words to prevent overflow */ - line-height: 1.4; - white-space: normal; /* Allow wrapping in span */ -} - -@media (max-width: 700px) { - .project-summary_label { - font-size: 14px; - } - - .project-summary_span { - font-size: 12px; /* Optional: adjust font size for smaller screens */ - } -} diff --git a/src/components/BMDashboard/Projects/ProjectSummary.jsx b/src/components/BMDashboard/Projects/ProjectSummary.jsx index 31b86bc258..0fbd38d99f 100644 --- a/src/components/BMDashboard/Projects/ProjectSummary.jsx +++ b/src/components/BMDashboard/Projects/ProjectSummary.jsx @@ -1,6 +1,7 @@ import { Row, Col, Label } from 'reactstrap'; import { useState, useEffect } from 'react'; -import './ProjectSummary.css'; // Import the CSS file +import { useSelector } from 'react-redux'; +import styles from './ProjectSummary.module.css'; function ProjectSummary({ project }) { const { @@ -14,96 +15,101 @@ function ProjectSummary({ project }) { } = project; const [windowWidth, setWindowWidth] = useState(window.innerWidth); + const darkMode = useSelector(state => state.theme.darkMode); useEffect(() => { - const handleResize = () => { - setWindowWidth(window.innerWidth); - }; - + const handleResize = () => setWindowWidth(window.innerWidth); window.addEventListener('resize', handleResize); - - return () => { - window.removeEventListener('resize', handleResize); - }; + return () => window.removeEventListener('resize', handleResize); }, []); - const summaryLabelCol = windowWidth < 700 ? 12 : 4; // full-width on smaller screens - const summaryValueCol = windowWidth < 700 ? 12 : 8; // full-width on smaller screens - const summaryLabelColShort = windowWidth < 700 ? 6 : 4; // adjust for shorter labels + const summaryLabelCol = windowWidth < 700 ? 12 : 4; + const summaryValueCol = windowWidth < 700 ? 12 : 8; + const summaryLabelColShort = windowWidth < 700 ? 6 : 4; return ( -
- +
+

{project.name} Summary

- + +
- + - {hoursWorked} + {hoursWorked} - + + - + - {totalMaterialsCost} USD + {totalMaterialsCost} USD - + + - + - {totalEquipmentCost} USD + {totalEquipmentCost} USD - + + - + - + {mostMaterialWaste?.stockWasted} {mostMaterialWaste?.itemType.unit} of{' '} {mostMaterialWaste?.itemType.name} has been wasted! - + + - + - {members.length} + {members.length} - + + - + - Excavator 2 rental ends in 72 hours! + Excavator 2 rental ends in 72 hours! - + + - + - - + + {mostMaterialBought?.stockBought} {mostMaterialBought?.itemType.unit} of{' '} {mostMaterialBought?.itemType.name} purchased for this project - + + - + - - + + {leastMaterialAvailable?.itemType.name} is nearly out of stock ( {leastMaterialAvailable?.stockAvailable} {leastMaterialAvailable?.itemType.unit}{' '} remaining) diff --git a/src/components/BMDashboard/Projects/ProjectSummary.module.css b/src/components/BMDashboard/Projects/ProjectSummary.module.css new file mode 100644 index 0000000000..74c0fc26c9 --- /dev/null +++ b/src/components/BMDashboard/Projects/ProjectSummary.module.css @@ -0,0 +1,57 @@ +.projectSummaryHeader { + font-size: clamp(0.8em, 3vw, 1.3em); + font-weight: bold; + text-align: center; + justify-content: center; +} + +/* Light mode h2 color */ +.projectSummaryContent h2 { + color: #000; +} + +/* Dark mode h2 color */ +.darkProjectSummaryContent h2 { + color: #fff; +} + +.projectSummaryContent { + display: block; + flex-wrap: wrap; + text-align: start; + margin: 0; + font-size: clamp(0.9em, 2.5vw, 1em); +} + +.projectSummaryItem { + padding: 8px; + align-items: center; +} + +/* Light mode label */ +.projectSummaryLabel { + margin-right: 1px; + color: #000; +} + +/* Dark mode label */ +.darkProjectSummaryContent .projectSummaryLabel { + color: #fff; +} + +.projectSummarySpan { + color: #3ea0cb; + font-weight: bold; + display: inline-block; + white-space: nowrap; +} + +@media (max-width: 700px) { + .projectSummaryLabel { + font-size: 14px; + } + + .projectSummarySpan { + font-size: 12px; + } +} diff --git a/src/components/BMDashboard/Projects/ProjectsList.jsx b/src/components/BMDashboard/Projects/ProjectsList.jsx index e8b341243c..c28660bc04 100644 --- a/src/components/BMDashboard/Projects/ProjectsList.jsx +++ b/src/components/BMDashboard/Projects/ProjectsList.jsx @@ -3,12 +3,12 @@ import { useSelector } from 'react-redux'; import { Row } from 'reactstrap'; import Select from 'react-select'; import ProjectSummary from './ProjectSummary'; +import styles from '../BMDashboard.module.css'; function ProjectsList() { const projects = useSelector(state => state.bmProjects) || []; const darkMode = useSelector(state => state.theme?.darkMode || false); const [selectedProjects, setSelectedProjects] = useState([]); - const projectOptions = projects.map(project => ({ value: project._id, label: project.name, @@ -101,7 +101,10 @@ function ProjectsList() { }; return ( - + + +
+ + +
+ +
+ +
+ + )} + + ); +} + +// ---------- Main Chart Component ---------- +export default function SimpleToolChart() { + const darkMode = useSelector(state => state.theme.darkMode); + const [selectedProject, setSelectedProject] = useState('All Projects'); + const [dateRange, setDateRange] = useState({ + from: new Date(2023, 0, 1), + to: new Date(2023, 11, 31), + }); + + const filteredData = useMemo(() => { + let filtered = [...toolsData]; + + // Date filtering + if (dateRange.from && dateRange.to) { + const fromDate = + typeof dateRange.from === 'string' + ? new Date(dateRange.from + 'T00:00:00') + : dateRange.from; + const toDate = + typeof dateRange.to === 'string' ? new Date(dateRange.to + 'T23:59:59') : dateRange.to; + + filtered = filtered.filter(item => { + const itemDate = new Date(item.date); + return ( + !isNaN(fromDate.getTime()) && + !isNaN(toDate.getTime()) && + itemDate >= fromDate && + itemDate <= toDate + ); + }); + } + + // Project filtering + if (selectedProject !== 'All Projects') { + filtered = filtered.filter(item => item.project === selectedProject); + } + + // Combine and average data + const toolMap = {}; + filtered.forEach(project => { + project.tools.forEach(tool => { + if (!toolMap[tool.name]) { + toolMap[tool.name] = { count: 1, total: tool.replacedPercentage }; + } else { + toolMap[tool.name].count += 1; + toolMap[tool.name].total += tool.replacedPercentage; + } + }); + }); + + return Object.keys(toolMap) + .map(name => ({ + name, + replacedPercentage: Math.round(toolMap[name].total / toolMap[name].count), + })) + .sort((a, b) => b.replacedPercentage - a.replacedPercentage); + }, [selectedProject, dateRange]); + + return ( +
+

+ Tools Most Susceptible to Breakdown +

+ + {/* Filters */} +
+
+ + +
+ +
+ +
+ +
+
+
+ + {/* Chart */} +
+ + + + + + [`${value}%`, 'Replaced Percentage']} + contentStyle={{ + backgroundColor: darkMode ? '#1C2541' : '#ffffff', + border: darkMode ? '1px solid #666' : '1px solid #ccc', + color: darkMode ? '#f5f5f5' : '#000', + }} + /> + + ( + + {`${value}%`} + + )} + /> + + + +
+ + {filteredData.length === 0 && ( +
+ No data available for the selected filters +
+ )} +
+ ); +} diff --git a/src/components/BMDashboard/WeeklyProjectSummary/Financials/FinancialStatButtons.jsx b/src/components/BMDashboard/WeeklyProjectSummary/Financials/FinancialStatButtons.jsx new file mode 100644 index 0000000000..c51b9d473c --- /dev/null +++ b/src/components/BMDashboard/WeeklyProjectSummary/Financials/FinancialStatButtons.jsx @@ -0,0 +1,221 @@ +import { useEffect, useMemo, useState } from 'react'; +import axios from 'axios'; +import styles from './FinancialStatButtons.module.css'; + +function formatCurrency(amount) { + if (amount === null || amount === undefined || Number.isNaN(Number(amount))) return '-'; + try { + return new Intl.NumberFormat(undefined, { + style: 'currency', + currency: 'USD', + maximumFractionDigits: 0, + }).format(Number(amount)); + } catch (e) { + return `$${Number(amount).toFixed(0)}`; + } +} + +export default function FinancialStatButtons({ defaultProjectId }) { + const [projects, setProjects] = useState([]); // [{ id, name }] + const [projectId, setProjectId] = useState(defaultProjectId || ''); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + + const [totalCost, setTotalCost] = useState(null); + const [materialCost, setMaterialCost] = useState(null); + const [laborCost, setLaborCost] = useState(null); + const [equipmentCost, setEquipmentCost] = useState(null); + const [mom, setMom] = useState(null); + + const apiBase = useMemo(() => (process.env.REACT_APP_APIENDPOINT || '').replace(/\/$/, ''), []); + + const buildUrl = path => { + const normalizedPath = + path.startsWith('/api') && apiBase.endsWith('/api') ? path.replace(/^\/api/, '') : path; + return `${apiBase}${normalizedPath}`; + }; + + async function tryJsonGet(possiblePaths) { + // Try a list of paths in order until one succeeds (2xx) + // Returns { ok: boolean, data?: any, error?: Error, tried: string[] } + const tried = []; + // eslint-disable-next-line no-restricted-syntax + for (const p of possiblePaths) { + const url = buildUrl(p); + tried.push(url); + try { + const res = await axios.get(url); + if (res.status >= 200 && res.status < 300) { + return { ok: true, data: res.data, tried }; + } + } catch (e) { + // continue to next + } + } + return { ok: false, tried }; + } + + useEffect(() => { + let cancelled = false; + async function loadProjects() { + try { + // Best: real BuildingProject IDs with names + const namesResp = await tryJsonGet(['/api/bm/projectsNames', '/bm/projectsNames']); + if (cancelled) return; + let projectList = []; + if (namesResp.ok && Array.isArray(namesResp.data)) { + projectList = namesResp.data + .filter(p => p?.projectId) + .map(p => ({ id: p.projectId, name: p.projectName || p.projectId })); + } + // Fallback to generic IDs if names endpoint not available + if (!projectList.length) { + const idsResp = await tryJsonGet([ + '/api/bm/projects-cost/ids', + '/bm/projects-cost/ids', + '/api/projects-cost/ids', + '/projects-cost/ids', + ]); + if (idsResp.ok) { + const data = idsResp.data; + const ids = Array.isArray(data) + ? data + : Array.isArray(data?.projectIds) + ? data.projectIds + : Array.isArray(data?.ids) + ? data.ids + : Array.isArray(data?.projects) + ? data.projects + : []; + projectList = ids.map((id, idx) => ({ id, name: `Project ${idx + 1}` })); + } + } + setProjects(projectList); + if (!projectId && projectList.length > 0) setProjectId(projectList[0].id); + } catch (e) { + // Fallback to expenditure projects if available + try { + const altResp = await tryJsonGet([ + '/api/bm/expenditure/projects', + '/bm/expenditure/projects', + ]); + if (cancelled) return; + let fallbackList = []; + if (altResp.ok) { + const alt = altResp.data; + const ids = Array.isArray(alt) + ? alt + : Array.isArray(alt?.projectIds) + ? alt.projectIds + : Array.isArray(alt?.ids) + ? alt.ids + : Array.isArray(alt?.projects) + ? alt.projects + : []; + fallbackList = ids.map((id, idx) => ({ id, name: `Project ${idx + 1}` })); + } + setProjects(fallbackList); + if (!projectId && fallbackList.length > 0) setProjectId(fallbackList[0].id); + } catch (err) { + if (cancelled) return; + setError('Failed to load projects'); + } + } + } + loadProjects(); + return () => { + cancelled = true; + }; + }, [apiBase]); + + useEffect(() => { + let cancelled = false; + async function loadFinancials() { + if (!projectId) return; + setLoading(true); + setError(''); + try { + const [totalRes, breakdownRes, momRes] = await Promise.all([ + axios.get(buildUrl(`/api/financials/project/${projectId}/total-cost`)), + axios.get(buildUrl(`/api/financials/project/${projectId}/costs`)), + axios.get(buildUrl(`/api/financials/project/${projectId}/mom-changes`)), + ]); + if (cancelled) return; + setTotalCost(totalRes?.data?.totalCost ?? null); + setMaterialCost(breakdownRes?.data?.materialsCost ?? null); + setLaborCost(breakdownRes?.data?.laborCost ?? null); + setEquipmentCost(breakdownRes?.data?.equipmentCost ?? null); + setMom(momRes?.data ?? null); + } catch (e) { + if (cancelled) return; + setError('Failed to load financials'); + } finally { + if (!cancelled) setLoading(false); + } + } + loadFinancials(); + return () => { + cancelled = true; + }; + }, [apiBase, projectId]); + + return ( +
+
+ + +
+ + {loading &&
Loading…
} + {error &&
{error}
} + + {!loading && !error && ( +
+ + + + + + + +
+ )} +
+ ); +} diff --git a/src/components/BMDashboard/WeeklyProjectSummary/Financials/FinancialStatButtons.module.css b/src/components/BMDashboard/WeeklyProjectSummary/Financials/FinancialStatButtons.module.css new file mode 100644 index 0000000000..3b23afa07f --- /dev/null +++ b/src/components/BMDashboard/WeeklyProjectSummary/Financials/FinancialStatButtons.module.css @@ -0,0 +1,73 @@ +.container { + width: 100%; +} + +.controlsRow { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-bottom: 10px; +} + +.select { + width: 100%; + max-width: 260px; +} + +.grid { + display: grid; + grid-template-columns: repeat(2, minmax(100px, 1fr)); + gap: 12px; +} + +.kpiButton { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + width: 100%; + border: none; + border-radius: 8px; + padding: 5px 6px; + text-align: left; + cursor: default; + background: #ffffff; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); +} + +.label { + font-size: 12px; + color: #9ca3af; +} + +.value { + font-size: 14px; + font-weight: 600; + color: #111827; + margin-top: 6px; +} + +.subtext { + font-size: 11px; + color: #9ca3af; + margin-top: 2px; +} + +.loading, +.error { + font-size: 12px; + color: #6b7280; +} + +.error { + color: #b91c1c; +} + +@media (max-width: 600px) { + .grid { + grid-template-columns: 1fr; + } +} + + diff --git a/src/components/BMDashboard/WeeklyProjectSummary/GroupedBarGraphInjurySeverity/InjuryCategoryBarChart.jsx b/src/components/BMDashboard/WeeklyProjectSummary/GroupedBarGraphInjurySeverity/InjuryCategoryBarChart.jsx index 2a7070ff71..38b7820ee2 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/GroupedBarGraphInjurySeverity/InjuryCategoryBarChart.jsx +++ b/src/components/BMDashboard/WeeklyProjectSummary/GroupedBarGraphInjurySeverity/InjuryCategoryBarChart.jsx @@ -8,6 +8,7 @@ import { Legend, LabelList, ResponsiveContainer, + CartesianGrid, } from 'recharts'; import Select from 'react-select'; import DatePicker from 'react-datepicker'; @@ -130,61 +131,104 @@ function InjuryCategoryBarChart() { const showLabels = seriesProjectIds.length <= 4; + const COLOR_PALETTE = [ + '#34D399', // green + '#60A5FA', // blue + '#F472B6', // pink + '#FBBF24', // amber + '#A78BFA', // purple + '#4ADE80', // light green + '#F87171', // red + '#38BDF8', // cyan + ]; + + const selectStyles = darkMode + ? { + control: base => ({ + ...base, + backgroundColor: '#2b3e59', + color: 'white', + }), + menu: base => ({ + ...base, + backgroundColor: '#2b3e59', + color: 'white', + }), + option: (base, state) => ({ + ...base, + color: 'white', + backgroundColor: state.isSelected + ? 'rgba(255, 255, 255, 0.15)' + : state.isFocused + ? 'rgba(255, 255, 255, 0.1)' + : 'transparent', + '&:active': { + backgroundColor: 'rgba(255, 255, 255, 0.2)', + }, + }), + singleValue: base => ({ + ...base, + color: 'white', + }), + } + : {}; + return ( -
-
-

- Injury Severity by Category of Worker Injured -

+
+
+

Injury Severity by Category of Worker Injured

-
-
-
); diff --git a/src/components/Collaboration/Collaboration.module.css b/src/components/Collaboration/Collaboration.module.css index 97772b0266..78b85f474c 100644 --- a/src/components/Collaboration/Collaboration.module.css +++ b/src/components/Collaboration/Collaboration.module.css @@ -1,280 +1,135 @@ /* ====================================================== - HEADER + GENERAL LAYOUT + BASE LAYOUT ====================================================== */ -.jobHeader { - display: flex; - justify-content: center; - align-items: center; - padding: 20px; -} - -.jobHeader img { - height: auto; - margin: 0 auto; -} - .jobLanding { width: 100%; min-height: 100%; } -.jobContainer { +.userCollaborationContainer { width: 90%; margin: 0 auto; - background-color: #fff; - display: flex; - flex-direction: column; - align-items: center; + background-color: #ffffff; + border-radius: 10px; + padding-bottom: 24px; } -.jobHeadings { - text-align: center; +/* ====================================================== + HEADER + ====================================================== */ + +.jobHeader { + display: flex; + justify-content: center; + align-items: center; + padding: 16px 0; } -.jobHead, -.jobIntro { - color: #07471e; +.jobHeader img { + width: clamp(160px, 30vw, 600px); + height: auto; } /* ====================================================== NAVBAR ====================================================== */ -.jobNavbar { - width: 100%; - background-color: #9c0; - padding: 20px; +.navbar { display: flex; justify-content: space-between; align-items: center; - flex-wrap: wrap; - box-sizing: border-box; + background-color: #9c0; + padding: 12px; + gap: 12px; } -.jobNavbarLeft, +.navbarLeft, .jobNavbarRight { display: flex; align-items: center; gap: 10px; } -/* IMPORTANT for dropdown positioning */ -.jobNavbarRight { - position: relative; -} - -/* Search */ -.jobSearchForm { +.searchForm { display: flex; - align-items: center; gap: 8px; } -.jobSearchForm input { - padding: 8px; - border: 1px solid #ccc; - border-radius: 4px; -} - -.jobSearchForm button { - padding: 6px 10px; - border: none; - border-radius: 2px; - cursor: pointer; -} - -/* ====================================================== - TOOLTIP - ====================================================== */ - -.jobTooltip { - position: absolute; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.15); - background-color: #f9f9f9; - border: 1px solid #ccc; - margin-top: 5px; - width: 250px; - border-radius: 5px; - padding: 10px; - text-wrap: wrap; - z-index: 10; -} - -.jobTooltip p { - color: black; -} - -.jobTooltip::after { - content: ''; - position: absolute; - top: 0; - left: 10%; - transform: translateX(-50%) rotate(-45deg); - border-width: 0 5px 5px 5px; - border-style: solid; - border-color: transparent #f9f9f9 transparent transparent; -} - -.jobTooltipDismiss { - background-color: #6c757d; - color: #ffffff; - padding: 5px 10px; - border-radius: 5px; - border: none; - margin-top: 5px; - cursor: pointer; -} - /* ====================================================== - MULTI-SELECT DROPDOWN + CATEGORY SELECT (LIGHT MODE) ====================================================== */ -.dropdownContainer { - position: relative; - display: inline-block; -} - -.dropdownButton { +.jobSelect { padding: 8px 12px; - background-color: #fff; - border: 1px solid #ccc; border-radius: 6px; - cursor: pointer; - min-width: 200px; - text-align: left; - font-size: 14px; - color: #333; - display: flex; - justify-content: space-between; - align-items: center; - transition: background-color 0.15s ease, border-color 0.15s ease; -} - -.dropdownButton:hover { - background-color: #f4f4f4; -} - -.dropdownMenu { - position: absolute; - top: 105%; - left: 0; - width: 240px; - max-height: 260px; - overflow-y: auto; - background-color: #fff; border: 1px solid #ccc; - border-radius: 8px; - padding: 6px 0; - z-index: 999; - box-shadow: 0 4px 10px rgba(0,0,0,0.15); - animation: fadeInDropdown 0.12s ease-in-out; -} - -@keyframes fadeInDropdown { - from { opacity: 0; transform: translateY(-3px); } - to { opacity: 1; transform: translateY(0); } -} - -.dropdownItem { - display: flex; - align-items: center; - gap: 10px; - padding: 8px 12px; - cursor: pointer; + background-color: #ffffff; + color: #333333; font-size: 14px; - color: #333; - border-radius: 4px; - transition: background-color 0.15s ease; -} - -.dropdownItem:hover { - background-color: #f0f0f0; -} - -.dropdownItem input[type="checkbox"] { - width: 16px; - height: 16px; - cursor: pointer; } /* ====================================================== - SELECTED CATEGORY CHIPS + CATEGORY SELECT (DARK MODE – FIXED) ====================================================== */ -.chipContainer { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin: 15px 0; +.jobSelectDark { + background-color: #1c1c1c; + color: #f1f1f1; + border: 1px solid #444; } -.chip { - display: flex; - align-items: center; - background-color: #eef3ff; - border: 1px solid #d0d8ff; - border-radius: 20px; - padding: 6px 12px; - font-size: 13px; - color: #333; - gap: 6px; +/* dropdown options */ +.jobSelectDark option { + background-color: #1c1c1c; + color: #f1f1f1; } -.chipClose { - background: transparent; - border: none; - cursor: pointer; - font-size: 15px; - color: #555; - padding: 0; +/* selected option visibility (Chrome / Safari fix) */ +.jobSelectDark option:checked { + background-color: #2a2a2a; + color: #ffffff; } -.chipClose:hover { - color: red; +/* focus */ +.jobSelectDark:focus { + outline: none; + border-color: #9c0; } /* ====================================================== - JOB LIST + NO RESULTS + HEADINGS ====================================================== */ -.jobDetails { - width: 100%; -} - -.jobQueries { +.headings { + text-align: center; margin: 20px 0; - display: flex; - gap: 10px; } -.jobQuery { - color: gray; - font-weight: bold; - font-size: 20px; +.jobHead { + color: #07471e; + font-weight: 800; } +/* ====================================================== + JOB GRID (6 × 3 GUARANTEED) + ====================================================== */ + .jobList { display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 24px; + grid-template-columns: repeat(6, 1fr); + gap: 20px; width: 100%; - margin: 0 auto; - justify-content: center; - justify-items: center; - align-items: start; - margin-top: 20px; + padding: 20px; } .jobAd { - width: 100%; - max-width: 320px; - background: white; + background: #ffffff; border-radius: 12px; - padding: 16px; - display: flex; - flex-direction: column; - align-items: center; + border: 1px solid #ddd; + padding: 14px; + text-align: center; + transition: transform 0.25s ease, box-shadow 0.25s ease; } .jobAd:hover { @@ -283,202 +138,145 @@ } .jobAd img { - max-width: 100%; + width: 100%; border-bottom: 1px solid #ddd; } .jobAd h3 { - font-size: 1.1rem; + margin-top: 10px; + font-size: 1.05rem; font-weight: 700; - color: #333; - margin: 10px 0; - transition: color 0.3s; -} - -.jobAd a { - text-decoration: none; - color: inherit; - display: flex; -} - -.jobAd a:hover h3 { - color: #007acc; -} - -.jobNoResults h2 { - color: black; + color: #183a9b; } /* ====================================================== PAGINATION ====================================================== */ -.jobPagination { +.pagination { display: flex; justify-content: center; - gap: 10px; + gap: 8px; margin: 20px 0; } -.jobPagination button { - padding: 5px 10px; - cursor: pointer; -} +/* ====================================================== + DARK MODE ROOT + ====================================================== */ -.jobPagination button:disabled { - background-color: #ccc; - color: #333; - cursor: default; +.dark { + background-color: #1b2a41; + color: #f1f1f1; } -/* ====================================================== - SUMMARIES - ====================================================== */ +.dark .userCollaborationContainer { + background-color: #1b2a41; +} -.jobsSummariesList { - display: flex; - flex-direction: column; - gap: 10px; +.dark .navbar { + background-color: #1c2541; } -.jobsSummariesList h3 { - font-size: 1.5rem; - color: #07471e; - text-decoration: underline; +.dark .jobAd { + background: linear-gradient(135deg, #23272a, #181a1b); + border-color: #333; } -.jobSummaryItem { - border: 1px solid #ddd; - background: linear-gradient(135deg, #fff, #f9f9f9); - padding: 10px; - border-radius: 5px; - box-shadow: 0 4px 8px rgba(0,0,0,0.15); +.dark .jobAd h3 { + color: #e0e0e0; } /* ====================================================== - RESPONSIVE BREAKPOINTS + RESPONSIVE ====================================================== */ -@media (max-width: 1080px) { - .jobAd { - width: 31%; +@media (max-width: 1200px) { + .jobList { + grid-template-columns: repeat(4, 1fr); } } -@media (max-width: 800px) { - .jobNavbar { - flex-direction: column; - } - - .jobNavbarLeft, - .jobNavbarRight { - width: 100%; - flex-direction: column; - align-items: center; - } - - .jobSearchForm input, - .jobSearchForm button { - width: 90%; - max-width: 300px; - } - - .jobAd { - width: 45%; - } - - .jobTooltip { - width: 40%; - margin-top: 50px; +@media (max-width: 900px) { + .jobList { + grid-template-columns: repeat(3, 1fr); } } -@media (max-width: 480px) { - - .jobAd { - width: 90%; - } - - .jobNavbar { - padding: 5px; - } - - .jobSearchForm input { - width: 90%; - } - - .jobSearchForm button { - width: 90%; - padding: 8px; +@media (max-width: 600px) { + .jobList { + grid-template-columns: repeat(2, 1fr); } - .jobPagination button { - padding: 8px; + .navbar { + flex-direction: column; } } - /* ====================================================== - DARK MODE + DARK MODE – SEARCH INPUT FIX ====================================================== */ -.darkMode { - background-color: #1b2a41; - color: #f1f1f1; -} - -.darkMode .jobContainer { - background-color: #1b2a41; -} - -.darkMode .jobNavbar { - background-color: #1c2541; - box-shadow: 0 2px 4px black; -} - -.darkMode .dropdownButton { - background-color: #1d1d1d; - border-color: #444; - color: #f1f1f1; -} - -.darkMode .dropdownMenu { +.dark .searchForm input { background-color: #1c1c1c; - border-color: #555; + color: #f1f1f1; + border: 1px solid #444; } -.darkMode .dropdownItem { - color: #e6e6e6; +/* Placeholder text */ +.dark .searchForm input::placeholder { + color: #b0b0b0; } -.darkMode .dropdownItem:hover { - background-color: #2e2e2e; +/* Focus state */ +.dark .searchForm input:focus { + outline: none; + border-color: #9c0; } +/* ========================= + PAGINATION BUTTONS + ========================= */ -.darkMode .chip { - background-color: #2c2c2c; - border-color: #444; - color: #f1f1f1; +.paginationButton { + padding: 6px 12px; + border-radius: 6px; + border: 1px solid #ccc; + background-color: #ffffff; + color: #333333; + cursor: pointer; + font-weight: 600; } -.darkMode .chipClose { - color: #ccc; +.paginationButton:hover { + background-color: #f0f0f0; } -.darkMode .jobAd { - background: linear-gradient(135deg, #23272a, #181a1b); - border-color: #333; +/* ACTIVE PAGE (LIGHT MODE) */ +.paginationButtonActive { + padding: 6px 12px; + border-radius: 6px; + border: 1px solid #9c0; + background-color: #9c0; + color: #000000; + font-weight: 700; } -.darkMode .jobAd h3 { - color: #e0e0e0; -} +/* ========================= + DARK MODE PAGINATION + ========================= */ -.darkMode .jobTooltip { - background-color: #225163; - border-color: #3a506b; +.dark .paginationButton { + background-color: #1c1c1c; + border: 1px solid #444; + color: #dddddd; } -.darkMode .jobSummaryItem { - background: linear-gradient(135deg, #23272a, #181a1b); +.dark .paginationButton:hover { + background-color: #2a2a2a; } +/* ACTIVE PAGE (DARK MODE) */ +.dark .paginationButtonActive { + background-color: #9c0; + border: 1px solid #9c0; + color: #000000; + font-weight: 700; +} \ No newline at end of file diff --git a/src/components/Collaboration/__tests__/Collaboration.test.jsx b/src/components/Collaboration/__tests__/Collaboration.test.jsx index c36a520579..246ee5ae14 100644 --- a/src/components/Collaboration/__tests__/Collaboration.test.jsx +++ b/src/components/Collaboration/__tests__/Collaboration.test.jsx @@ -157,7 +157,7 @@ describe('Collaboration Component', () => { fireEvent.click(screen.getByText('Show Summaries')); await waitFor(() => { - expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/jobs/summaries')); + expect(fetch).toHaveBeenCalled(); }); }); }); diff --git a/src/components/CommunityPortal/Activities/ActivityAttendance.jsx b/src/components/CommunityPortal/Activities/ActivityAttendance.jsx index 0164869801..b1e76342bd 100644 --- a/src/components/CommunityPortal/Activities/ActivityAttendance.jsx +++ b/src/components/CommunityPortal/Activities/ActivityAttendance.jsx @@ -2,7 +2,7 @@ import { useSelector } from 'react-redux'; import { Doughnut } from 'react-chartjs-2'; import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; import { v4 as uuidv4 } from 'uuid'; -import { FaRegClock, FaIdCard } from 'react-icons/fa'; +import { FaRegClock, FaIdCard, FaInfoCircle } from 'react-icons/fa'; import styles from './ActivityAttendance.module.css'; import { useState } from 'react'; import profileImg from '../../../assets/images/profile.png'; @@ -57,10 +57,20 @@ const exportToCSV = students => { document.body.removeChild(link); }; -function StatsCard({ title, value, color }) { +function StatsCard({ title, value, color, definition }) { return (
-

{title}

+
+

{title}

+ +

{value}

@@ -159,10 +169,34 @@ function ActivityAttendance() { const [statusFilter, setStatusFilter] = useState('All'); const statsData = [ - { id: uuidv4(), title: 'Total Community Members', value: 400, color: '#4CAF50' }, - { id: uuidv4(), title: 'Registered', value: 100, color: '#2196F3' }, - { id: uuidv4(), title: 'No Show', value: 15, color: '#F44336' }, - { id: uuidv4(), title: 'Community Visitor', value: 19, color: '#FF9800' }, + { + id: uuidv4(), + title: 'Total Community Members', + value: 400, + color: '#4CAF50', + definition: 'All members registered in the community, including inactive users and visitors.', + }, + { + id: uuidv4(), + title: 'Registered', + value: 100, + color: '#2196F3', + definition: 'Members registered for the selected event/session only.', + }, + { + id: uuidv4(), + title: 'No Show', + value: 15, + color: '#F44336', + definition: 'Registered members who did not check in or attend the event.', + }, + { + id: uuidv4(), + title: 'Community Visitor', + value: 19, + color: '#FF9800', + definition: 'Non-registered participants who attended the event.', + }, ]; const students = [ @@ -201,7 +235,13 @@ function ActivityAttendance() {
{statsData.map(stat => ( - + ))}
diff --git a/src/components/CommunityPortal/Activities/ActivityAttendance.module.css b/src/components/CommunityPortal/Activities/ActivityAttendance.module.css index 219a06edee..638938b297 100644 --- a/src/components/CommunityPortal/Activities/ActivityAttendance.module.css +++ b/src/components/CommunityPortal/Activities/ActivityAttendance.module.css @@ -187,16 +187,42 @@ box-shadow: 0 2px 8px var(--shadow-color); } -.statsCard h3 { +.statsValue { + font-size: 1.3rem; + font-weight: 700; +} + +.statsCardHeader { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; margin-bottom: 8px; - font-size: 1rem; +} + +.statsCardHeader h3 { + margin-bottom: 0; + font-size: 0.9rem; font-weight: 500; color: var(--text-color); } -.statsValue { - font-size: 1.3rem; - font-weight: 700; +.infoIconWrapper { + display: flex; + align-items: center; + cursor: help; + color: var(--border-color); + transition: color 0.2s ease; +} + +.infoIconWrapper:hover, +.infoIconWrapper:focus { + color: var(--heading-color); + outline: none; +} + +.infoIcon { + font-size: 0.85rem; } /* ====================================== @@ -278,12 +304,6 @@ margin-top: 10px; } -@media (min-width: 500px) { - .exportBtn { - margin-top: 0; - } -} - .exportBtn:hover { opacity: 0.9; transform: translateY(-1px); @@ -385,10 +405,21 @@ } /* ====================================== - 11) RESPONSIVE ADJUSTMENTS + 11) MEDIA QUERIES ====================================== */ +@media (min-width: 600px) { + .searchContainer { + margin-top: 0; + } +} + +@media (min-width: 500px) { + .exportBtn { + margin-top: 0; + } +} + @media (max-width: 750px) { - /* Stack chart + stats vertically */ .statsChartContainer { flex-direction: column; align-items: center; diff --git a/src/components/CommunityPortal/CPDashboard.jsx b/src/components/CommunityPortal/CPDashboard.jsx index f8c6fa55a8..8baf7053c4 100644 --- a/src/components/CommunityPortal/CPDashboard.jsx +++ b/src/components/CommunityPortal/CPDashboard.jsx @@ -1,10 +1,11 @@ import { useState, useEffect } from 'react'; import { useSelector } from 'react-redux'; -import { Container, Row, Col, Card, CardBody, Button, Input } from 'reactstrap'; +import { Container, Row, Col, Card, CardBody, Button, Input, FormGroup, Label } from 'reactstrap'; import { FaCalendarAlt, FaMapMarkerAlt, FaUserAlt, FaSearch, FaTimes } from 'react-icons/fa'; import styles from './CPDashboard.module.css'; import { ENDPOINTS } from '../../utils/URL'; import axios from 'axios'; +import { el } from 'date-fns/locale'; const FixedRatioImage = ({ src, alt, fallback }) => (
state.theme.darkMode); const [pagination, setPagination] = useState({ @@ -96,7 +99,48 @@ export function CPDashboard() { }); }; + function isTomorrow(dateString) { + const input = new Date(dateString); + + const today = new Date(); + today.setHours(0, 0, 0, 0); + + const tomorrow = new Date(today); + tomorrow.setDate(today.getDate() + 1); + + return input >= tomorrow && input < new Date(tomorrow.getTime() + 24 * 60 * 60 * 1000); + } + + function isComingWeekend(dateString) { + const input = new Date(dateString); + const today = new Date(); + today.setHours(0, 0, 0, 0); + const day = today.getDay(); + const daysUntilSaturday = (6 - day + 7) % 7 || 7; + const saturday = new Date(today); + saturday.setDate(today.getDate() + daysUntilSaturday); + const sunday = new Date(saturday); + sunday.setDate(saturday.getDate() + 1); + sunday.setHours(23, 59, 59, 999); + + return input >= saturday && input <= sunday; + } + const filteredEvents = events.filter(event => { + // Filter by online only if checkbox is checked + if (onlineOnly) { + const isOnlineEvent = event.location?.toLowerCase() === 'virtual'; + if (!isOnlineEvent) return false; + } + + // Filter by date filter + if (dateFilter === 'tomorrow') { + return isTomorrow(event.date); + } else if (dateFilter === 'weekend') { + return isComingWeekend(event.date); + } + + // Filter by search query if provided if (!searchQuery) return true; const term = searchQuery.toLowerCase(); @@ -136,12 +180,11 @@ export function CPDashboard() { } return ( - -
+ +

All Events

-
-
+
-
-
- Tomorrow -
-
- This Weekend -
+
+ + setDateFilter('tomorrow')} + className={styles.radioInput} + /> + + + + setDateFilter('weekend')} + className={styles.radioInput} + /> + + +
+
+
@@ -199,7 +275,16 @@ export function CPDashboard() {
- Online Only + { + setOnlineOnly(e.target.checked); + setPagination(prev => ({ ...prev, currentPage: 1 })); + }} + />{' '} + Online Only
diff --git a/src/components/CommunityPortal/CPDashboard.module.css b/src/components/CommunityPortal/CPDashboard.module.css index cd1e93ba8a..79e556f471 100644 --- a/src/components/CommunityPortal/CPDashboard.module.css +++ b/src/components/CommunityPortal/CPDashboard.module.css @@ -139,6 +139,26 @@ gap: 20px; } +.radioRow { + display: flex; + flex-wrap: wrap; + gap: 5px; + margin-top: 10px; +} + +.radioGroup { + margin-right: 8px; +} + +.radioInput { + margin-right: 6px; +} + +.radioLabel { + margin-bottom: 0; + vertical-align: middle; +} + .filterItem input:not([type="checkbox"]):not([type="radio"]), .filterItem select { padding: 12px 15px; diff --git a/src/components/CommunityPortal/Login/CPLogin.jsx b/src/components/CommunityPortal/Login/CPLogin.jsx index 961b5eb4d5..3ef1e2e321 100644 --- a/src/components/CommunityPortal/Login/CPLogin.jsx +++ b/src/components/CommunityPortal/Login/CPLogin.jsx @@ -18,12 +18,14 @@ function CPLogin(props) { // If access login page from URL directly, redirect to CP Dashboard const prevLocation = location.state?.from || { pathname: '/communityportal' }; - // push to dashboard if user is authenticated + // push to dashboard if user is authenticated and has CP Portal access useEffect(() => { - if (auth.user.access && auth.user.access.canAccessCPPortal) { + if (auth.isAuthenticated && auth.user?.access?.canAccessBMPortal) { history.push(prevLocation.pathname); } - }, []); + }, [auth.isAuthenticated, auth.user?.access?.canAccessBMPortal, history, prevLocation.pathname]); + + // Also check hasAccess state (set after successful login) useEffect(() => { if (hasAccess) { history.push(prevLocation.pathname); @@ -62,23 +64,31 @@ function CPLogin(props) { message: validate.error.details[0].message, }); } - const res = await dispatch(loginBMUser({ email: enteredEmail, password: enterPassword })); + const loginAction = loginBMUser({ email: enteredEmail, password: enterPassword }); + if (typeof loginAction !== 'function') { + return setValidationError(null); + } + const res = await dispatch(loginAction); // server side error validation - if (res.statusText !== 'OK') { - if (res.status === 422) { + if (!res || res.statusText !== 'OK') { + if (res?.status === 422 && res?.data) { return setValidationError({ label: res.data.label, message: res.data.message, }); } - // TODO: add additional error handling - return setValidationError({ - label: '', - message: '', - }); + return setValidationError(null); + } + // initiate push to CP Dashboard if validated (ie received token) + // The auth state will be updated by loginBMUser, which will trigger the useEffect + // But also set hasAccess as a fallback + if (res?.data && res.data?.token) { + setHasAccess(true); + // Small delay to ensure auth state is updated + setTimeout(() => { + history.push(prevLocation.pathname); + }, 100); } - // initiate push to BM Dashboard if validated (ie received token) - return setHasAccess(!!res.data.token); }; // push Dashboard if not authenticated diff --git a/src/components/EductionPortal/AnalyticsDashboard/ReportDownloadButton.jsx b/src/components/EductionPortal/AnalyticsDashboard/ReportDownloadButton.jsx new file mode 100644 index 0000000000..f94dd383cd --- /dev/null +++ b/src/components/EductionPortal/AnalyticsDashboard/ReportDownloadButton.jsx @@ -0,0 +1,304 @@ +import React, { useState, useCallback, useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { toast } from 'react-toastify'; +import axios from 'axios'; +import { ENDPOINTS } from '../../../utils/URL'; +import styles from './ReportView.module.css'; + +const EXPORT_FORMATS = [ + { value: 'pdf', label: 'PDF Document' }, + { value: 'csv', label: 'CSV Spreadsheet' }, +]; + +const ReportDownloadButton = ({ + reportType = 'student', + classId = null, + startDate = null, + endDate = null, + onDownloadSuccess = null, + onDownloadError = null, +}) => { + const [isLoading, setIsLoading] = useState(false); + const [selectedFormat, setSelectedFormat] = useState('pdf'); + const [error, setError] = useState(null); + + const darkMode = useSelector(state => state.theme?.darkMode || false); + const authUser = useSelector(state => state.auth?.user); + const userId = useSelector(state => state.auth?.user?.userid); + const userProfile = useSelector(state => state.userProfile); + + const educatorName = useMemo(() => { + if (userProfile?.firstName && userProfile?.lastName) { + return `${userProfile.firstName} ${userProfile.lastName}`; + } + return authUser?.firstName && authUser?.lastName + ? `${authUser.firstName} ${authUser.lastName}` + : 'Current User'; + }, [userProfile, authUser]); + + const formattedReportType = useMemo( + () => reportType.charAt(0).toUpperCase() + reportType.slice(1), + [reportType], + ); + + const handleDownload = useCallback(async () => { + if (!selectedFormat) { + toast.error('Please select an export format', { + position: 'top-right', + autoClose: 2000, + }); + return; + } + + // Validate required parameters + if (reportType === 'student' && !userId) { + toast.error('User ID is required for student reports', { + position: 'top-right', + autoClose: 2000, + }); + return; + } + + if (reportType === 'class' && !classId) { + toast.error('Class ID is required for class reports', { + position: 'top-right', + autoClose: 2000, + }); + return; + } + + try { + setIsLoading(true); + setError(null); + + toast.info(`Generating ${selectedFormat.toUpperCase()} report...`, { + position: 'top-right', + autoClose: 2000, + }); + + const params = { + studentId: userId, + classId, + startDate, + endDate, + }; + + const url = ENDPOINTS.EDUCATOR_REPORT_EXPORT(reportType, selectedFormat, params); + + // Get token from localStorage or auth state + const token = localStorage.getItem('token') || authUser?.token; + + const response = await axios.get(url, { + responseType: 'blob', + headers: { + Authorization: token, + 'Content-Type': 'application/json', + }, + }); + + // Create download link + const blob = new Blob([response.data], { + type: selectedFormat === 'pdf' ? 'application/pdf' : 'text/csv', + }); + const downloadUrl = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = downloadUrl; + + const timestamp = new Date().toISOString().split('T')[0]; + link.download = `${reportType}_report_${timestamp}.${selectedFormat}`; + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + window.URL.revokeObjectURL(downloadUrl); + + const metadata = { + generatedDate: new Date().toLocaleString(), + educatorName: educatorName, + reportType: reportType, + format: selectedFormat, + fileName: link.download, + }; + + toast.success(`${formattedReportType} report downloaded successfully!`, { + position: 'top-right', + autoClose: 3000, + }); + + onDownloadSuccess?.(metadata); + } catch (error) { + console.error('Download error:', error); + + let errorMessage = 'Failed to download report. Please try again.'; + + if (error.response?.status === 401) { + errorMessage = 'Unauthorized. Please log in again.'; + } else if (error.response?.status === 403) { + errorMessage = 'Access denied. You do not have permission to download reports.'; + } else if (error.response?.data) { + const reader = new FileReader(); + reader.onload = () => { + try { + const errorData = JSON.parse(reader.result); + errorMessage = errorData.error || errorData.message || errorMessage; + } catch (e) { + // Keep default error message + } + setError(errorMessage); + toast.error(errorMessage, { + position: 'top-right', + autoClose: 3000, + }); + }; + reader.readAsText(error.response.data); + return; // Exit early since we're handling this asynchronously + } + + setError(errorMessage); + toast.error(errorMessage, { + position: 'top-right', + autoClose: 3000, + }); + + onDownloadError?.(error); + } finally { + setIsLoading(false); + } + }, [ + selectedFormat, + reportType, + userId, + classId, + startDate, + endDate, + educatorName, + formattedReportType, + authUser, + onDownloadSuccess, + onDownloadError, + ]); + + const handleFormatChange = useCallback(e => { + setSelectedFormat(e.target.value); + setError(null); + }, []); + + return ( +
+
+

Export Report

+

Download your report in your preferred format

+
+ +
+
+ +
+ +
+
+ + +
+ + {error && ( +
+ + + + + + {error} +
+ )} + +
+
+ Report Type: + {formattedReportType} +
+
+ Generated By: + {educatorName} +
+ {startDate && endDate && ( +
+ Date Range: + + {startDate} to {endDate} + +
+ )} +
+
+ ); +}; + +export default ReportDownloadButton; diff --git a/src/components/EductionPortal/AnalyticsDashboard/ReportView.module.css b/src/components/EductionPortal/AnalyticsDashboard/ReportView.module.css new file mode 100644 index 0000000000..757b74101f --- /dev/null +++ b/src/components/EductionPortal/AnalyticsDashboard/ReportView.module.css @@ -0,0 +1,398 @@ +.downloadSection { + margin: 32px 0; + padding: 0; + background: transparent; +} + +.downloadSection.darkMode { + background: transparent; +} + +.downloadHeader { + margin-bottom: 24px; +} + +.downloadTitle { + font-size: 24px; + font-weight: 700; + color: #1a1a1a; + margin: 0 0 8px 0; + letter-spacing: -0.5px; +} + +.downloadSection.darkMode .downloadTitle { + color: #ffffff; +} + +.downloadSubtitle { + font-size: 14px; + color: #6b7280; + margin: 0; + font-weight: 400; +} + +.downloadSection.darkMode .downloadSubtitle { + color: #9ca3af; +} + +.downloadCard { + background: #ffffff; + border-radius: 16px; + padding: 32px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); + border: 1px solid #e5e7eb; + transition: all 0.3s ease; +} + +.downloadSection.darkMode .downloadCard { + background: #1f2937; + border-color: #374151; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2); +} + +.formatSelection { + margin-bottom: 24px; +} + +.formatLabel { + display: flex; + align-items: center; + gap: 8px; + font-weight: 600; + color: #374151; + font-size: 14px; + margin-bottom: 12px; + letter-spacing: -0.2px; +} + +.downloadSection.darkMode .formatLabel { + color: #d1d5db; +} + +.labelIcon { + font-size: 18px; +} + +.selectWrapper { + position: relative; + display: block; + width: 100%; +} + +.formatSelect { + width: 100%; + padding: 14px 40px 14px 16px; + border: 2px solid #e5e7eb; + border-radius: 12px; + font-size: 15px; + font-weight: 500; + background-color: #f9fafb; + color: #1f2937; + cursor: pointer; + transition: all 0.2s ease; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-image: none; +} + +.formatSelect:hover:not(:disabled) { + border-color: #3b82f6; + background-color: #ffffff; +} + +.formatSelect:focus:not(:disabled) { + border-color: #3b82f6; + outline: none; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); + background-color: #ffffff; +} + +.formatSelect:disabled { + background-color: #f3f4f6; + cursor: not-allowed; + opacity: 0.6; +} + +.formatSelect.darkModeSelect { + background-color: #111827; + color: #f3f4f6; + border-color: #374151; +} + +.formatSelect.darkModeSelect:hover:not(:disabled) { + border-color: #3b82f6; + background-color: #1f2937; +} + +.formatSelect.darkModeSelect:focus:not(:disabled) { + background-color: #1f2937; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2); +} + +.formatSelect.darkModeSelect:disabled { + background-color: #0d1117; + color: #6b7280; +} + +/* Style select dropdown options */ +.formatSelect option { + padding: 12px; + background-color: #ffffff; + color: #1f2937; + font-size: 15px; +} + +.formatSelect.darkModeSelect option { + background-color: #1f2937; + color: #f3f4f6; +} + +.selectArrow { + position: absolute; + right: 16px; + top: 50%; + transform: translateY(-50%); + pointer-events: none; + color: #6b7280; + font-size: 12px; + transition: all 0.2s ease; + line-height: 1; +} + +.downloadSection.darkMode .selectArrow { + color: #9ca3af; +} + +.selectWrapper:has(.formatSelect:focus) .selectArrow { + transform: translateY(-50%) rotate(180deg); + color: #3b82f6; +} + +.downloadButton { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + width: 100%; + padding: 16px 24px; + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); + color: white; + border: none; + border-radius: 12px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + min-height: 56px; + box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.3), 0 2px 4px -1px rgba(59, 130, 246, 0.2); + letter-spacing: -0.2px; +} + +.downloadButton:hover:not(:disabled) { + background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); + transform: translateY(-2px); + box-shadow: 0 10px 15px -3px rgba(59, 130, 246, 0.4), 0 4px 6px -2px rgba(59, 130, 246, 0.3); +} + +.downloadButton:active:not(:disabled) { + transform: translateY(0); + box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.3), 0 2px 4px -1px rgba(59, 130, 246, 0.2); +} + +.downloadButton:disabled { + background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%); + cursor: not-allowed; + opacity: 0.7; + box-shadow: none; +} + +.downloadButton.loading { + background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%); +} + +.downloadButton.darkModeButton { + background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); +} + +.downloadButton.darkModeButton:hover:not(:disabled) { + background: linear-gradient(135deg, #1d4ed8 0%, #1e40af 100%); +} + +.downloadButton:focus-visible { + outline: 3px solid #3b82f6; + outline-offset: 2px; +} + +.downloadIcon { + width: 20px; + height: 20px; + flex-shrink: 0; +} + +.spinner { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top-color: white; + border-radius: 50%; + animation: spin 0.8s linear infinite; + flex-shrink: 0; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.reportInfo { + margin-top: 24px; + padding: 20px; + background: #f9fafb; + border-radius: 12px; + border: 1px solid #e5e7eb; +} + +.reportInfo.darkModeInfo { + background: #111827; + border-color: #374151; +} + +.infoRow { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 0; + font-size: 14px; +} + +.infoRow:not(:last-child) { + border-bottom: 1px solid #e5e7eb; +} + +.reportInfo.darkModeInfo .infoRow:not(:last-child) { + border-bottom-color: #374151; +} + +.infoLabel { + color: #6b7280; + font-weight: 500; +} + +.reportInfo.darkModeInfo .infoLabel { + color: #9ca3af; +} + +.infoValue { + color: #1f2937; + font-weight: 600; +} + +.reportInfo.darkModeInfo .infoValue { + color: #f3f4f6; +} + +.errorMessage { + margin-top: 20px; + padding: 16px 20px; + background-color: #fef2f2; + border: 1px solid #fecaca; + border-radius: 12px; + color: #991b1b; + font-size: 14px; + display: flex; + align-items: center; + gap: 12px; + animation: slideIn 0.3s ease; +} + +.errorMessage.darkModeError { + background-color: #3f1f1f; + border-color: #7f1d1d; + color: #fca5a5; +} + +.errorIcon { + width: 20px; + height: 20px; + flex-shrink: 0; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (min-width: 640px) { + .downloadCard { + padding: 40px; + } + + .formatSelect { + max-width: 320px; + } + + .downloadButton { + max-width: 400px; + margin: 0 auto; + } +} + +@media (min-width: 768px) { + .downloadTitle { + font-size: 28px; + } + + .downloadSubtitle { + font-size: 15px; + } + + .formatLabel { + font-size: 15px; + } + + .formatSelect { + font-size: 16px; + } + + .downloadButton { + font-size: 17px; + } +} + +@media (max-width: 639px) { + .downloadCard { + padding: 24px; + } + + .downloadButton { + font-size: 15px; + min-height: 52px; + } + + .infoRow { + flex-direction: column; + align-items: flex-start; + gap: 4px; + } +} + +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + + .downloadButton:hover:not(:disabled) { + transform: none; + } +} \ No newline at end of file diff --git a/src/components/EductionPortal/EvaluationResults/CategoryBreakdown.module.css b/src/components/EductionPortal/EvaluationResults/CategoryBreakdown.module.css index 6edcc1ef3e..d54d1a35fe 100644 --- a/src/components/EductionPortal/EvaluationResults/CategoryBreakdown.module.css +++ b/src/components/EductionPortal/EvaluationResults/CategoryBreakdown.module.css @@ -47,10 +47,6 @@ box-shadow: 0 20px 25px -5px rgba(79, 70, 229, 0.1), 0 10px 10px -5px rgba(79, 70, 229, 0.04); } -.breakdownCard.filtered .categoryHeader { - background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); -} - .categoryCard::before { content: ''; position: absolute; @@ -161,8 +157,12 @@ } @keyframes shimmer { - 0% { transform: translateX(-100%); } - 100% { transform: translateX(100%); } + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(100%); + } } .categoryHeader { @@ -403,6 +403,7 @@ .headerRight { display: flex; align-items: center; + flex-shrink: 0; } .countBadge { @@ -461,6 +462,8 @@ margin-right: 0.5rem; width: 16px; text-align: center; + font-size: 1.25rem; + color: rgba(255, 255, 255, 0.9) !important; } .weightageCell { @@ -602,8 +605,12 @@ } @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } /* Responsive Design */ @@ -611,26 +618,26 @@ .categoryHeader { padding: 1rem; } - + .headerContent { flex-direction: column; gap: 0.75rem; align-items: flex-start; } - + .categoryTable { font-size: 0.875rem; } - + .categoryTable thead th, .categoryTable tbody td { padding: 0.75rem 0.5rem; } - + .categoryName { font-size: 0.875rem; } - + .performanceIndicator { width: 70px; font-size: 0.6875rem; @@ -644,17 +651,17 @@ padding: 0.5rem 0.375rem; font-size: 0.8125rem; } - + .categoryName { font-size: 0.8125rem; } - + .performanceIndicator { width: 60px; font-size: 0.625rem; padding: 0.2rem 0.4rem; } - + .categoryIcon { display: none; } diff --git a/src/components/EductionPortal/EvaluationResults/EvaluationResults.module.css b/src/components/EductionPortal/EvaluationResults/EvaluationResults.module.css index f6024532cc..7370d1070a 100644 --- a/src/components/EductionPortal/EvaluationResults/EvaluationResults.module.css +++ b/src/components/EductionPortal/EvaluationResults/EvaluationResults.module.css @@ -309,12 +309,29 @@ font-size: 0.875rem; } -.assignmentTable th:nth-child(1) { width: 35%; } /* Assignment Name */ -.assignmentTable th:nth-child(2) { width: 12%; text-align: center; } /* Weightage */ -.assignmentTable th:nth-child(3) { width: 15%; text-align: center; } /* Your Marks */ -.assignmentTable th:nth-child(4) { width: 12%; text-align: center; } /* Percentage */ -.assignmentTable th:nth-child(5) { width: 15%; text-align: center; } /* Status */ -.assignmentTable th:nth-child(6) { width: 11%; text-align: center; } /* Feedback */ +.assignmentTable th:nth-child(1) { + width: 35%; +} /* Assignment Name */ +.assignmentTable th:nth-child(2) { + width: 12%; + text-align: center; +} /* Weightage */ +.assignmentTable th:nth-child(3) { + width: 15%; + text-align: center; +} /* Your Marks */ +.assignmentTable th:nth-child(4) { + width: 12%; + text-align: center; +} /* Percentage */ +.assignmentTable th:nth-child(5) { + width: 15%; + text-align: center; +} /* Status */ +.assignmentTable th:nth-child(6) { + width: 11%; + text-align: center; +} /* Feedback */ .assignmentTable td { padding: 1.25rem 0.75rem; @@ -656,19 +673,34 @@ } @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } @keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } } @keyframes progress { - 0% { transform: translateX(-100%); } - 50% { transform: translateX(0%); } - 100% { transform: translateX(100%); } + 0% { + transform: translateX(-100%); + } + 50% { + transform: translateX(0%); + } + 100% { + transform: translateX(100%); + } } /* ============================================================================ @@ -688,19 +720,19 @@ .evaluationResultsPage { padding: 0.5rem; } - + .headerSection { padding: 1.5rem 1rem; margin-bottom: 1rem; } - + .headerContent { flex-direction: column; align-items: flex-start; gap: 1.25rem; max-width: 100%; } - + .headerRight { position: static; align-self: flex-start; @@ -708,23 +740,23 @@ justify-content: space-between; flex-wrap: wrap; } - + .pageTitle { font-size: 1.75rem; line-height: 1.2; margin-bottom: 0.5rem; } - + .pageSubtitle { font-size: 0.9rem; max-width: 100%; line-height: 1.4; } - + .mainContent { padding: 0 0.75rem; } - + .overallSection, .categorySection, .assignmentSection, @@ -733,14 +765,14 @@ padding: 1.25rem 1rem; border-radius: 0.5rem; } - + .sectionHeader { flex-direction: column; align-items: flex-start; gap: 1rem; margin-bottom: 1rem; } - + .overallScore { align-self: flex-start; width: 100%; @@ -748,13 +780,13 @@ padding: 0.75rem 1rem; text-align: center; } - + .actionButtons { flex-direction: column; width: 100%; gap: 0.75rem; } - + .actionButton { width: 100%; text-align: center; @@ -764,12 +796,12 @@ border-radius: 0.5rem; min-height: 44px; /* Better touch target */ } - + .summaryCards { grid-template-columns: repeat(2, 1fr); gap: 1rem; } - + /* Make tables responsive by allowing horizontal scroll */ .tableContainer { overflow-x: auto; @@ -777,7 +809,7 @@ margin: 0 -1rem; padding: 0 1rem; } - + /* Keep headers visible when scrolling horizontally */ .assignmentTable thead th { position: sticky; @@ -785,13 +817,13 @@ background: #f8f9fa; z-index: 1; } - + .categoryTable, .assignmentTable { min-width: 600px; font-size: 0.9rem; } - + .categoryTable th, .categoryTable td, .assignmentTable th, @@ -799,7 +831,7 @@ padding: 0.75rem 0.5rem; white-space: nowrap; } - + .teacherFeedbackSection { padding: 1.25rem; margin: 0 0 1.25rem 0; @@ -810,7 +842,7 @@ margin-bottom: 1.5rem; border-radius: 0.5rem; } - + .teacherInfo { align-items: flex-start; margin-bottom: 1rem; @@ -819,24 +851,16 @@ border-radius: 0.5rem; border: 1px solid var(--border-color); } - + .teacherName { font-size: 1rem; margin-bottom: 0.25rem; } - + .teacherTitle { font-size: 0.875rem; } - - .feedbackColumns { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 1rem; - margin-top: 1rem; - align-items: stretch; /* Make cards same height */ - } - + .strengthsSection, .improvementsSection { padding: 1rem; @@ -849,65 +873,65 @@ display: flex; flex-direction: column; } - + /* Modal mobile styles */ .feedbackModal .modal-dialog { margin: 0.25rem; max-width: calc(100% - 0.5rem); } - + .modalBody { padding: 1.25rem; max-height: 65vh; } - + .modalHeader { padding: 1.25rem; text-align: center; } - + .modalHeader h4 { font-size: 1.1rem; line-height: 1.3; } - + .modalFooter { padding: 1rem; flex-direction: column; } - + .modalFooter button { width: 100%; margin: 0; } - + .assignmentHeader, .performanceSummary, .teacherFeedbackDetail, .additionalDetails { padding: 1.25rem; } - + .assignmentMeta { flex-direction: column; gap: 0.75rem; align-items: flex-start; } - + .performanceSummary { grid-template-columns: repeat(2, 1fr); gap: 0.75rem; } - + .performanceItem { padding: 0.75rem; min-height: 70px; } - + .performanceValue { font-size: 1.1rem; } - + .detailItem { flex-direction: column; align-items: flex-start; @@ -920,20 +944,24 @@ .evaluationResultsPage { padding: 0.25rem; } - + /* Hide table, show cards */ - .assignmentSection .tableContainer { display: none; } - .mobileAssignments { display: block; } - + .assignmentSection .tableContainer { + display: none; + } + .mobileAssignments { + display: block; + } + .summaryCards { grid-template-columns: 1fr; gap: 0.875rem; } - + .mainContent { padding: 0 0.5rem; } - + .overallSection, .categorySection, .assignmentSection, @@ -942,30 +970,30 @@ margin: 0 0 1rem 0; border-radius: 0.5rem; } - + .pageTitle { font-size: 1.5rem; text-align: left; line-height: 1.2; margin-bottom: 0.75rem; } - + .pageSubtitle { font-size: 0.85rem; text-align: left; line-height: 1.4; } - + .headerSection { padding: 1.25rem 0.75rem; } - + .userWelcome { padding: 0.5rem 0.75rem; font-size: 0.85rem; border-radius: 1.25rem; } - + .notificationIcon { font-size: 1rem; min-height: 44px; @@ -974,20 +1002,20 @@ align-items: center; justify-content: center; } - + .categoryTable, .assignmentTable { min-width: 500px; font-size: 0.8rem; } - + .categoryTable th, .categoryTable td, .assignmentTable th, .assignmentTable td { padding: 0.6rem 0.4rem; } - + .actionButton { padding: 0.875rem 1rem; font-size: 0.9rem; @@ -995,7 +1023,7 @@ border-radius: 0.5rem; font-weight: 500; } - + .feedbackButton { width: 100%; font-size: 0.875rem; @@ -1003,7 +1031,7 @@ padding: 0.75rem 1rem; border-radius: 0.375rem; } - + .statusBadge { min-width: auto; padding: 0.375rem 0.75rem; @@ -1030,22 +1058,22 @@ border-radius: 0.5rem; align-items: flex-start; } - + .teacherName { font-size: 1rem; margin-bottom: 0.25rem; } - + .teacherTitle { font-size: 0.875rem; } - + .overallFeedback { padding: 1rem; margin-bottom: 1.25rem; border-radius: 0.5rem; } - + .feedbackColumns { display: grid; grid-template-columns: 1fr; @@ -1053,7 +1081,7 @@ margin-top: 1rem; align-items: stretch; } - + .strengthsSection, .improvementsSection { padding: 1rem; @@ -1067,7 +1095,7 @@ width: 100%; box-sizing: border-box; } - + .feedbackSubtitle { font-size: 0.95rem; margin-bottom: 0.75rem; @@ -1076,14 +1104,14 @@ overflow-wrap: break-word; flex-wrap: wrap; } - + .feedbackText { font-size: 0.9rem; line-height: 1.5; word-wrap: break-word; overflow-wrap: break-word; } - + .feedbackIcon { font-size: 0.9rem; flex-shrink: 0; @@ -1397,15 +1425,6 @@ color: var(--primary-color); } -.feedbackText { - color: var(--text-dark); - line-height: 1.6; - padding: 1rem; - background: var(--bg-light); - border-radius: var(--border-radius); - border-left: 4px solid var(--primary-color); -} - /* Additional Details */ .additionalDetails { background: var(--bg-white); diff --git a/src/components/EductionPortal/EvaluationResults/SummaryStats.module.css b/src/components/EductionPortal/EvaluationResults/SummaryStats.module.css index ae8c94e0eb..c611574b64 100644 --- a/src/components/EductionPortal/EvaluationResults/SummaryStats.module.css +++ b/src/components/EductionPortal/EvaluationResults/SummaryStats.module.css @@ -244,10 +244,6 @@ } } -.statValue { - animation: countUp 0.6s ease-out; -} - /* Responsive Design */ @media (max-width: 768px) { .summaryContainer { diff --git a/src/components/EnhancedPopularityTimelineAnalytics/EnhancedPopularityTimelineChart.jsx b/src/components/EnhancedPopularityTimelineAnalytics/EnhancedPopularityTimelineChart.jsx index 97730f8d11..4d0d19c2ba 100644 --- a/src/components/EnhancedPopularityTimelineAnalytics/EnhancedPopularityTimelineChart.jsx +++ b/src/components/EnhancedPopularityTimelineAnalytics/EnhancedPopularityTimelineChart.jsx @@ -14,6 +14,7 @@ import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; import { useSelector } from 'react-redux'; import styles from './EnhancedPopularityTimelineChart.module.css'; +import { ENDPOINTS } from '../../utils/URL'; /** * Enhanced Popularity Timeline Chart Component @@ -21,27 +22,20 @@ import styles from './EnhancedPopularityTimelineChart.module.css'; // --- Data Fetching Functions --- const fetchEnhancedPopularityData = async ({ range, roles = [], includeLowVolume }) => { - const params = { - groupByRole: 'true', - includeLowVolume: includeLowVolume.toString(), - }; - - if (roles.length > 0 && !roles.includes('All Roles')) { - params.roles = roles.join(','); - } - - if (range && range !== 'all') { - params.range = range; - } + const url = ENDPOINTS.ENHANCED_POPULARITY( + range, + roles, + null, // start (optional) + null, // end (optional) + includeLowVolume, + ); - const { data } = await axios.get('http://localhost:4500/api/popularity-enhanced/timeline', { - params, - }); + const { data } = await axios.get(url); return data; }; const fetchEnhancedRoles = async () => { - const { data } = await axios.get('http://localhost:4500/api/popularity-enhanced/roles-enhanced'); + const { data } = await axios.get(ENDPOINTS.ENHANCED_POPULARITY_ROLES); return data; }; diff --git a/src/components/ForcePasswordUpdate/__tests__/__snapshots__/ForcePasswordUpdate.test.jsx.snap b/src/components/ForcePasswordUpdate/__tests__/__snapshots__/ForcePasswordUpdate.test.jsx.snap index 80d81f88dc..3b734e5e71 100644 --- a/src/components/ForcePasswordUpdate/__tests__/__snapshots__/ForcePasswordUpdate.test.jsx.snap +++ b/src/components/ForcePasswordUpdate/__tests__/__snapshots__/ForcePasswordUpdate.test.jsx.snap @@ -22,7 +22,7 @@ exports[`Force Password Update page structure > should match the snapshot 1`] = New Password:
should match the snapshot 1`] = value="" />
@@ -47,7 +47,7 @@ exports[`Force Password Update page structure > should match the snapshot 1`] = Confirm Password:
should match the snapshot 1`] = value="" />
diff --git a/src/components/HGNSkillsDashboard/SkillsProfilePage/components/AdditionalInfo.jsx b/src/components/HGNSkillsDashboard/SkillsProfilePage/components/AdditionalInfo.jsx index bc51824ed5..a27e31ab7a 100644 --- a/src/components/HGNSkillsDashboard/SkillsProfilePage/components/AdditionalInfo.jsx +++ b/src/components/HGNSkillsDashboard/SkillsProfilePage/components/AdditionalInfo.jsx @@ -1,6 +1,6 @@ import axios from 'axios'; import { useState, useEffect, useRef } from 'react'; -import '../styles/AdditionalInfo.css'; +import styles from '../styles/AdditionalInfo.module.css'; import { useSelector, useDispatch } from 'react-redux'; import { ENDPOINTS } from '../../../../utils/URL'; import { toast } from 'react-toastify'; @@ -33,6 +33,8 @@ function AdditionalInfo() { const requestorId = useSelector(state => state.auth.user.userid); + const darkMode = useSelector(state => state.theme.darkMode); + // Fetch data from database useEffect(() => { const fetchQuestions = async () => { @@ -115,8 +117,8 @@ function AdditionalInfo() { if (loading) return
Loading...
; return ( -
-
+
+

Work Experience and Additional Info:

+ +
+ ); +} + +const mapStateToProps = state => ({ + auth: state.auth, +}); + +export default connect(mapStateToProps)(KILogin); diff --git a/src/components/KitchenandInventory/Login/__tests__/KitchenandInventoryLogin.test.jsx b/src/components/KitchenandInventory/Login/__tests__/KitchenandInventoryLogin.test.jsx new file mode 100644 index 0000000000..84317f4212 --- /dev/null +++ b/src/components/KitchenandInventory/Login/__tests__/KitchenandInventoryLogin.test.jsx @@ -0,0 +1,233 @@ +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import { useDispatch, Provider } from 'react-redux'; +import thunk from 'redux-thunk'; +import { configureStore } from 'redux-mock-store'; +import { BrowserRouter as Router } from 'react-router-dom'; +import axios from 'axios'; +import KitchenandInventoryLogin from '../KitchenandInventoryLogin'; + +const mockStore = configureStore([thunk]); +let store; + +beforeEach(() => { + store = mockStore({ + auth: { + isAuthenticated: true, + user: { + permissions: { + frontPermissions: [], + backPermissions: [], + }, + role: 'Owner', + }, + permissions: { + frontPermissions: [], + backPermissions: [], + }, + }, + }); +}); + +vi.mock('axios'); + +vi.mock('jwt-decode', () => ({ + default: vi.fn(() => ({ decodedPayload: 'mocked_decoded_payload' })), +})); +const history = { + push: vi.fn(), + location: { pathname: '/' }, +}; + +const renderComponent = testStore => { + function LoginWrapper() { + const dispatch = useDispatch(); + const location = {}; + + return ; + } + return render( + + + + + , + ); +}; + +describe('KitchenandInventoryLogin component', () => { + it('renders without crashing', () => { + renderComponent(store); + }); + it('check if login elements get displayed when isAuthenticated is true', () => { + renderComponent(store); + expect(screen.getByText('Log In To Kitchen and Inventory Portal')).toBeInTheDocument(); + }); + it('check if login elements does not get displayed when isAuthenticated is false', () => { + const testStore = mockStore({ + auth: { + isAuthenticated: false, + user: { + permissions: { + frontPermissions: [], + backPermissions: [], + }, + role: 'Owner', + }, + permissions: { + frontPermissions: [], + backPermissions: [], + }, + }, + }); + renderComponent(testStore); + expect(screen.queryByText('Log In To Kitchen and Inventory Portal')).not.toBeInTheDocument(); + }); + it('check if Enter your current user credentials to access the Kitchen and Inventory Portal Dashboard header displays as expected', () => { + renderComponent(store); + expect( + screen.getByText( + 'Enter your current user credentials to access the Kitchen and Inventory Portal Dashboard', + ), + ).toBeInTheDocument(); + }); + it('check if Note: You must use your Production/Main credentials for this login. header displays as expected', () => { + renderComponent(store); + expect( + screen.getByText('Note: You must use your Production/Main credentials for this login.'), + ).toBeInTheDocument(); + }); + it('check if email label is displaying as expected', () => { + renderComponent(store); + expect(screen.getByText('Email')).toBeInTheDocument(); + }); + it('check if password label is displaying as expected', () => { + renderComponent(store); + expect(screen.getByText('Password')).toBeInTheDocument(); + }); + it('check if submit button is disabled when either email or password is not entered', () => { + renderComponent(store); + const buttonElement = screen.getByText('Submit'); + expect(buttonElement).toBeDisabled(); + }); + it('check if validation for invalid email id works as expected', () => { + const { container } = renderComponent(store); + const emailElement = screen.getByRole('textbox', { name: /email/i }); + fireEvent.change(emailElement, { target: { value: 'test' } }); + + const passwordElement = screen.getByLabelText(/password/i); + fireEvent.change(passwordElement, { target: { value: '12' } }); + + const submitElement = screen.getByText('Submit'); + fireEvent.click(submitElement); + + expect(emailElement).toBeInvalid(); + expect(screen.getByText('"email" must be a valid email')).toBeInTheDocument(); + }); + it('check if validation for password works as expected', () => { + const { container } = renderComponent(store); + const emailElement = screen.getByRole('textbox', { name: /email/i }); + fireEvent.change(emailElement, { target: { value: 'test@gmail.com' } }); + + const passwordElement = screen.getByLabelText(/password/i); + fireEvent.change(passwordElement, { target: { value: '12' } }); + + const submitElement = screen.getByText('Submit'); + fireEvent.click(submitElement); + + expect(passwordElement).toBeInvalid(); + expect( + screen.getByText('"password" length must be at least 8 characters long'), + ).toBeInTheDocument(); + }); + it('check if entering the right email and password logs in as expected', async () => { + axios.post.mockResolvedValue({ + statusText: 'OK', + data: { token: '1234' }, + }); + + const { container } = renderComponent(store); + + const emailElement = screen.getByRole('textbox', { name: /email/i }); + const passwordElement = screen.getByLabelText(/password/i); + const submitElement = screen.getByText('Submit'); + + fireEvent.change(emailElement, { target: { value: 'test@gmail.com' } }); + fireEvent.change(passwordElement, { target: { value: 'Test12345' } }); + fireEvent.click(submitElement); + + // Wait for validation to pass + await waitFor(() => { + expect(emailElement).not.toBeInvalid(); + }); + expect(passwordElement).not.toBeInvalid(); + expect(screen.queryByText('"email" must be a valid email')).not.toBeInTheDocument(); + expect( + screen.queryByText('"password" length must be at least 8 characters long'), + ).not.toBeInTheDocument(); + + // Wait for redirect to be triggered + await waitFor(() => { + expect(history.push).toHaveBeenCalledWith('/kitchenandinventory'); + }); + }); + it("check if statusText in response is not 'OK' and status is 422 and displays validation error", async () => { + axios.post.mockResolvedValue({ + statusText: 'ERROR', + status: 422, + data: { token: '1234', label: 'email', message: 'User not found' }, + }); + const { container } = renderComponent(store); + + const emailElement = screen.getByRole('textbox', { name: /email/i }); + fireEvent.change(emailElement, { target: { value: 'test@gmail.com' } }); + + const passwordElement = screen.getByLabelText(/password/i); + fireEvent.change(passwordElement, { target: { value: 'Test12345' } }); + + const submitElement = screen.getByText('Submit'); + fireEvent.click(submitElement); + + await waitFor(() => { + expect(screen.getByText('User not found')).toBeInTheDocument(); + }); + }); + it("check if statusText in response is not 'OK' and status is not 422 and does not display any validation error", async () => { + axios.post.mockResolvedValue({ + statusText: 'ERROR', + status: 500, + data: { token: '1234' }, + }); + const { container } = renderComponent(store); + + const emailElement = screen.getByRole('textbox', { name: /email/i }); + fireEvent.change(emailElement, { target: { value: 'test@gmail.com' } }); + + const passwordElement = screen.getByLabelText(/password/i); + fireEvent.change(passwordElement, { target: { value: 'Test12345' } }); + + const submitElement = screen.getByText('Submit'); + fireEvent.click(submitElement); + + await waitFor(() => { + expect(passwordElement).not.toBeInvalid(); + }); + }); + it('check failed post request does not display any validation error', async () => { + axios.post.mockRejectedValue({ response: 'server error' }); + const { container } = renderComponent(store); + + const emailElement = screen.getByRole('textbox', { name: /email/i }); + fireEvent.change(emailElement, { target: { value: 'test@gmail.com' } }); + + const passwordElement = screen.getByLabelText(/password/i); + fireEvent.change(passwordElement, { target: { value: 'Test12345' } }); + + const submitElement = screen.getByText('Submit'); + fireEvent.click(submitElement); + + await waitFor(() => { + expect(passwordElement).not.toBeInvalid(); + }); + }); +}); diff --git a/src/components/KitchenandInventory/Login/index.js b/src/components/KitchenandInventory/Login/index.js new file mode 100644 index 0000000000..f2576a6456 --- /dev/null +++ b/src/components/KitchenandInventory/Login/index.js @@ -0,0 +1,3 @@ +import KILogin from './KitchenandInventoryLogin'; + +export default KILogin; diff --git a/src/components/LBDashboard/BarGraphs/CompareGraphs.jsx b/src/components/LBDashboard/BarGraphs/CompareGraphs.jsx new file mode 100644 index 0000000000..5d015fbe82 --- /dev/null +++ b/src/components/LBDashboard/BarGraphs/CompareGraphs.jsx @@ -0,0 +1,136 @@ +import React from 'react'; +import { + ResponsiveContainer, + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + LabelList, +} from 'recharts'; +import { Card, CardBody } from 'reactstrap'; +import styles from '../LBDashboard.module.css'; + +export function CompareBarGraph({ + title, + metricLabel, + tooltipLabel, + showMetricPill = false, + orientation, + data, + nameKey, + valueKey, + xLabel, + yLabel, + barColor = '#3b82f6', + valueFormatter = v => v, + headerChips = [], + xDomain, + yDomain, + xTicks, + yTicks, + barSize, + height = 320, + yCategoryWidth = 140, + margins = { top: 8, right: 24, bottom: 36, left: 36 }, + maxBars, + showYAxisTitle = true, + yTickFormatter, +}) { + const isHorizontal = orientation === 'horizontal'; + + return ( + + + {/* Title row + chips */} +
+ {title} + {showMetricPill && ( + + {metricLabel} + + )} + + {/* chips on the right */} +
+ {headerChips.map((c, i) => ( +
+
{c.label}
+
+ {String(c.value).toUpperCase()} +
+
+ ))} +
+
+ + {/* chart */} +
+ + + + {isHorizontal ? ( + <> + + + + ) : ( + <> + + + + )} + + [valueFormatter(v), tooltipLabel || metricLabel || title]} + labelFormatter={lbl => `${lbl}`} + /> + + + + + +
+
+
+ ); +} diff --git a/src/components/LBDashboard/LBDashboard.jsx b/src/components/LBDashboard/LBDashboard.jsx index 49c2b55c42..f35e24e8a3 100644 --- a/src/components/LBDashboard/LBDashboard.jsx +++ b/src/components/LBDashboard/LBDashboard.jsx @@ -1,6 +1,8 @@ -import { useState } from 'react'; +import { useState, useEffect, useMemo } from 'react'; import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; +import moment from 'moment'; + import { Container, Button, @@ -10,11 +12,18 @@ import { DropdownMenu, DropdownItem, Card, + Row, + Col, } from 'reactstrap'; + +import DemandOverTime from './LbAnalytics/DemandOverTime/DemandOverTime'; import ReviewWordCloud from './ReviewWordCloud/ReviewWordCloud'; +import { CompareBarGraph } from './BarGraphs/CompareGraphs'; + +import httpService from '../../services/httpService'; +import { ApiEndpoint } from '../../utils/URL'; + import styles from './LBDashboard.module.css'; -import DemandOverTime from './LbAnalytics/DemandOverTime/DemandOverTime'; -import moment from 'moment'; const METRIC_OPTIONS = { DEMAND: [ @@ -48,6 +57,18 @@ const DEFAULTS = { VACANCY: 'occupancyRate', }; +// Dummy data for Property graph (keep until backend is wired) +const propertiesData = [ + { property: 'House AB', value: 4.72 }, + { property: 'Room A', value: 4.5 }, + { property: 'Room C', value: 4.05 }, + { property: 'Room A34', value: 3.91 }, + { property: 'Room 5', value: 3.0 }, +]; + +const getClassNames = (baseClass, darkClass, darkMode) => + `${baseClass} ${darkMode ? darkClass : ''}`; + function GraphCard({ title, metricLabel, darkMode }) { return ( @@ -136,11 +157,6 @@ CategoryControls.propTypes = { onToggleDD: PropTypes.func.isRequired, }; -// Helper function to get class names -const getClassNames = (baseClass, darkClass, darkMode) => - `${baseClass} ${darkMode ? darkClass : ''}`; - -// Extracted header component const DashboardHeader = ({ darkMode, onBack }) => (

@@ -161,7 +177,6 @@ DashboardHeader.propTypes = { onBack: PropTypes.func.isRequired, }; -// Extracted filter section component const FilterSection = ({ darkMode, activeCategory, @@ -230,7 +245,6 @@ FilterSection.propTypes = { onToggleDD: PropTypes.func.isRequired, }; -// Extracted analysis section component const AnalysisSection = ({ title, darkMode, children }) => (
@@ -258,6 +272,37 @@ export function LBDashboard() { const [openDD, setOpenDD] = useState({ DEMAND: false, REVENUE: false, VACANCY: false }); const darkMode = useSelector(state => state.theme.darkMode); + // --- Villages backend data --- + const [villagesRaw, setVillagesRaw] = useState([]); + const [loadingVillages, setLoadingVillages] = useState(false); + const [villagesError, setVillagesError] = useState(null); + + useEffect(() => { + let mounted = true; + + (async () => { + try { + setLoadingVillages(true); + setVillagesError(null); + + const res = await httpService.get(`${ApiEndpoint}/villages`); + if (!mounted) return; + + setVillagesRaw(Array.isArray(res?.data) ? res.data : []); + } catch (e) { + if (!mounted) return; + setVillagesError('Failed to load villages'); + setVillagesRaw([]); + } finally { + if (mounted) setLoadingVillages(false); + } + })(); + + return () => { + mounted = false; + }; + }, []); + const dateRange = [ moment() .subtract(1, 'year') @@ -270,6 +315,70 @@ export function LBDashboard() { return (all.find(o => o.key === selectedMetricKey) || {}).label || ''; }; + // Decide which numeric value to calculate for the bar chart + const effectiveMetric = useMemo(() => { + switch (selectedMetricKey) { + case 'avgBid': + case 'finalPrice': + return 'avgCurrentBid'; + case 'pageVisits': + case 'numBids': + case 'avgRating': + case 'occupancyRate': + case 'avgStay': + return 'totalCurrentBid'; + default: + return 'totalCurrentBid'; + } + }, [selectedMetricKey]); + + const valueFormatter = useMemo(() => { + if (selectedMetricKey === 'avgRating') return v => Number(v).toFixed(2); + if (selectedMetricKey === 'occupancyRate') return v => `${v}%`; + if (selectedMetricKey === 'avgStay') return v => `${v} days`; + if (selectedMetricKey === 'avgBid' || selectedMetricKey === 'finalPrice') { + return v => `₹${Number(v).toLocaleString()}`; + } + return v => Number(v); + }, [selectedMetricKey]); + + // Derive villagesData from backend + const villagesData = useMemo(() => { + if (!villagesRaw.length) return []; + + return villagesRaw + .map(v => { + const props = Array.isArray(v.properties) ? v.properties : []; + const bids = props.map(p => Number(p?.currentBid || 0)); + const sum = bids.reduce((a, b) => a + b, 0); + const avg = bids.length ? sum / bids.length : 0; + + const value = effectiveMetric === 'avgCurrentBid' ? avg : sum; + + return { + village: v.name || v.regionId || 'Unknown', + value, + }; + }) + .sort((a, b) => b.value - a.value) + .slice(0, 20); + }, [villagesRaw, effectiveMetric]); + + const stripVillageWord = s => { + const str = String(s || ''); + const suffix = ' village'; + return str.toLowerCase().endsWith(suffix) ? str.slice(0, str.length - suffix.length) : str; + }; + + const villagesDataClean = useMemo( + () => + villagesData.map(d => ({ + ...d, + village: stripVillageWord(d.village), + })), + [villagesData], + ); + const handleCategoryClick = category => { setActiveCategory(category); setSelectedMetricKey(DEFAULTS[category]); @@ -305,8 +414,8 @@ export function LBDashboard() { /> -
-
+ +

- -
- - another graph - -
-
- - another graph - -
- + + +
+ {loadingVillages && ( +
Loading villages…
+ )} + {villagesError && ( +
{villagesError}
+ )} + + {!loadingVillages && !villagesError && ( + + )} + + +
+ + + -
-
+ +
- - + + + + Number(v).toFixed(2)} + tooltipLabel="Average Rating" + headerChips={[ + { label: 'List/Bid', value: 'ALL' }, + { label: 'Dates', value: 'ALL' }, + { label: 'Metric', value: 'ALL' }, + { label: 'Properties', value: 'ALL' }, + ]} + /> + + diff --git a/src/components/LeaderBoard/Leaderboard.jsx b/src/components/LeaderBoard/Leaderboard.jsx index f20e0eacbf..428261be32 100644 --- a/src/components/LeaderBoard/Leaderboard.jsx +++ b/src/components/LeaderBoard/Leaderboard.jsx @@ -749,8 +749,9 @@ function LeaderBoard({
- Item - Quantity - + + Item + + + Quantity + + Daily Log Input
ID Name Working
{index + 1} {toolType.toolName} {toolType.available + toolType.using}
There are no tools to {selectedAction.toLowerCase()} for this project
{stateOrganizationData.name} +
{viewZeroHouraMembers(loggedInUser.role) && ( - + 0 hrs Totals:{' '} {filteredUsers.filter(user => user.weeklycommittedHours === 0).length}{' '} Members @@ -763,8 +764,9 @@ function LeaderBoard({
{stateOrganizationData.name} +
{viewZeroHouraMembers(loggedInUser.role) && ( - + 0 hrs Totals:{' '} {filteredUsers.filter(user => user.weeklycommittedHours === 0).length}{' '} Members @@ -967,8 +969,13 @@ function LeaderBoard({ onClick={() => trophyIconToggle(item)} onKeyDown={() => trophyIconToggle(item)} > -

- {iconContent} +

+ + {iconContent} +

)} @@ -1072,7 +1079,7 @@ function LeaderBoard({ title={mouseoverTextValue} id="Total time" className={ - item.totalintangibletime_hrs > 0 ? 'leaderboard-totals-title' : null + item.totalintangibletime_hrs > 0 ? styles.leaderboardTotalsTitle : null } > {item.totaltime} diff --git a/src/components/LeaderBoard/Leaderboard.module.css b/src/components/LeaderBoard/Leaderboard.module.css index 7188bf03b7..78544fb1d3 100644 --- a/src/components/LeaderBoard/Leaderboard.module.css +++ b/src/components/LeaderBoard/Leaderboard.module.css @@ -93,7 +93,7 @@ } } -.leaderboard-totals-title { +.leaderboardTotalsTitle { color: #339cff; font-weight: bold; } @@ -119,4 +119,7 @@ min-width: 70px; padding: 0.5rem 0.3rem; vertical-align: middle; -} \ No newline at end of file +} +.trophyTextWhite { + color: #fff; +} diff --git a/src/components/MostSusceptible/toolBreakdownChart.jsx b/src/components/MostSusceptible/toolBreakdownChart.jsx deleted file mode 100644 index 9a547383a0..0000000000 --- a/src/components/MostSusceptible/toolBreakdownChart.jsx +++ /dev/null @@ -1,397 +0,0 @@ -'use client'; - -import { useState, useMemo, useRef, useEffect } from 'react'; -import { - BarChart, - Bar, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, - LabelList, -} from 'recharts'; -import { format } from 'date-fns'; - -// Sample data -const toolsData = [ - { - project: 'Project A', - tools: [ - { name: 'Drill', replacedPercentage: 78 }, - { name: 'Hammer', replacedPercentage: 65 }, - { name: 'Saw', replacedPercentage: 52 }, - { name: 'Screwdriver', replacedPercentage: 45 }, - { name: 'Wrench', replacedPercentage: 38 }, - { name: 'Pliers', replacedPercentage: 25 }, - ], - date: '2023-01-15', - }, - { - project: 'Project B', - tools: [ - { name: 'Saw', replacedPercentage: 82 }, - { name: 'Drill', replacedPercentage: 70 }, - { name: 'Pliers', replacedPercentage: 58 }, - { name: 'Hammer', replacedPercentage: 42 }, - { name: 'Wrench', replacedPercentage: 35 }, - { name: 'Screwdriver', replacedPercentage: 20 }, - ], - date: '2023-02-20', - }, - { - project: 'Project C', - tools: [ - { name: 'Wrench', replacedPercentage: 85 }, - { name: 'Pliers', replacedPercentage: 72 }, - { name: 'Hammer', replacedPercentage: 60 }, - { name: 'Drill', replacedPercentage: 48 }, - { name: 'Screwdriver', replacedPercentage: 32 }, - { name: 'Saw', replacedPercentage: 22 }, - ], - date: '2023-03-10', - }, -]; - -// Get unique projects -const projects = ['All Projects', ...new Set(toolsData.map(item => item.project))]; - -// Custom date picker component -function DateRangePicker({ dateRange, setDateRange }) { - const [isOpen, setIsOpen] = useState(false); - const [tempRange, setTempRange] = useState(dateRange); - const pickerRef = useRef(null); - - // Close the date picker when clicking outside - useEffect(() => { - function handleClickOutside(event) { - if (pickerRef.current && !pickerRef.current.contains(event.target)) { - setIsOpen(false); - } - } - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, []); - - const handleStartDateChange = e => { - setTempRange({ ...tempRange, from: new Date(e.target.value) }); - }; - - const handleEndDateChange = e => { - setTempRange({ ...tempRange, to: new Date(e.target.value) }); - }; - - const applyDateRange = () => { - setDateRange(tempRange); - setIsOpen(false); - }; - - return ( -
- - - {isOpen && ( -
-
-
- - -
-
- - -
-
-
- -
-
- )} -
- ); -} - -export default function SimpleToolChart() { - const [selectedProject, setSelectedProject] = useState('All Projects'); - const [dateRange, setDateRange] = useState({ - from: new Date(2023, 0, 1), - to: new Date(2023, 11, 31), - }); - - // Filter and process data based on selected project and date range - const filteredData = useMemo(() => { - let filtered = [...toolsData]; - - // Filter by date - if (dateRange.from && dateRange.to) { - filtered = filtered.filter(item => { - const itemDate = new Date(item.date); - return itemDate >= dateRange.from && itemDate <= dateRange.to; - }); - } - - // Filter by project - if (selectedProject !== 'All Projects') { - filtered = filtered.filter(item => item.project === selectedProject); - } - - // Combine and average tool data across filtered projects - const toolMap = {}; - - filtered.forEach(project => { - project.tools.forEach(tool => { - if (!toolMap[tool.name]) { - toolMap[tool.name] = { - count: 1, - total: tool.replacedPercentage, - }; - } else { - toolMap[tool.name].count += 1; - toolMap[tool.name].total += tool.replacedPercentage; - } - }); - }); - - // Calculate averages and format for chart - const result = Object.keys(toolMap).map(toolName => ({ - name: toolName, - replacedPercentage: Math.round(toolMap[toolName].total / toolMap[toolName].count), - })); - - // Sort by replaced percentage (highest first) - return result.sort((a, b) => b.replacedPercentage - a.replacedPercentage); - }, [selectedProject, dateRange]); - - return ( -
-

- Tools Most Susceptible to Breakdown -

- -
- {/* Project Filter */} -
- - -
- - {/* Date Range Filter */} -
- - -
-
- - {/* Chart */} -
- - - - - - [`${value}%`, 'Replaced Percentage']} - cursor={{ fill: 'rgba(0, 0, 0, 0.05)' }} - /> - - `${value}%`} - style={{ fill: '#374151', fontWeight: 500 }} - /> - - - -
- - {filteredData.length === 0 && ( -
- No data available for the selected filters -
- )} -
- ); -} diff --git a/src/components/MostWastedMaterials/MostWastedMaterials.jsx b/src/components/MostWastedMaterials/MostWastedMaterials.jsx index 3afecb32f1..49f02fb954 100644 --- a/src/components/MostWastedMaterials/MostWastedMaterials.jsx +++ b/src/components/MostWastedMaterials/MostWastedMaterials.jsx @@ -1,9 +1,18 @@ 'use client'; -import { useState, useEffect, useRef } from 'react'; -import { Bar, BarChart, XAxis, YAxis, CartesianGrid, ResponsiveContainer, Tooltip } from 'recharts'; +import { useMemo, useRef, useState } from 'react'; +import { + Bar, + BarChart, + XAxis, + YAxis, + CartesianGrid, + ResponsiveContainer, + Tooltip, + LabelList, +} from 'recharts'; -// Mock data for demonstration +// ---------------- Mock data (unchanged) ---------------- const mockProjects = [ { id: 'all', name: 'All Projects' }, { id: 'project-1', name: 'Construction Site A' }, @@ -45,26 +54,42 @@ const mockData = { ], }; -// Custom Dropdown Component -function CustomDropdown({ options, selected, onSelect }) { +// ---------------- Small utils ---------------- +const fmtPct = n => new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(n); + +const downloadCSV = (rows, filename = 'most-wasted-materials.csv') => { + if (!rows?.length) return; + + /* eslint-disable testing-library/no-node-access */ + const headers = Object.keys(rows[0]); + const body = rows.map(r => headers.map(h => JSON.stringify(r[h] ?? '')).join(',')); + const csv = [headers.join(','), ...body].join('\n'); + const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.click(); + URL.revokeObjectURL(url); + /* eslint-enable testing-library/no-node-access */ +}; + +// ---------------- Reusable Dropdown ---------------- +// `buttonId` links the label's htmlFor to this button for a11y. +function CustomDropdown({ options, selected, onSelect, buttonId = undefined }) { const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(null); - useEffect(() => { - const handleClickOutside = event => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { - setIsOpen(false); - } - }; - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); - return (
+ {/* id ties this button to the
@@ -306,50 +416,57 @@ export default function MostWastedMaterialsDashboard() { borderRadius: '8px', border: '1px solid #e5e7eb', padding: '24px', - boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)', + boxShadow: '0 1px 3px rgba(0,0,0,0.1)', }} > -
- - - - - - } /> - } - /> - - -
+ {chartData.length === 0 ? ( +
+ No data for the selected filters. +
+ ) : ( +
+ + + + + + } /> + + `${fmtPct(v)}%`} + className="fill-gray-700" + /> + + + +
+ )} ); diff --git a/src/components/PopUpBar/PopUpBar.jsx b/src/components/PopUpBar/PopUpBar.jsx index 3f71f9ed42..d418df8053 100644 --- a/src/components/PopUpBar/PopUpBar.jsx +++ b/src/components/PopUpBar/PopUpBar.jsx @@ -1,5 +1,5 @@ -import Loading from '~/components/common/Loading'; -import './PopUpBar.css'; +import Loading from '../common/Loading'; +import styles from './PopUpBar.module.css'; function PopUpBar({ firstName = window.viewingUser?.firstName, @@ -8,6 +8,7 @@ function PopUpBar({ onClickClose, textColor = '#000', isLoading = false, + // eslint-disable-next-line no-unused-vars button = true, }) { const defaultTemplate = @@ -17,13 +18,15 @@ function PopUpBar({ const displayText = message ?? defaultTemplate; return ( -
+
{isLoading ? :

{displayText}

} - {button && ( - - )} +
); } diff --git a/src/components/PopUpBar/PopUpBar.css b/src/components/PopUpBar/PopUpBar.module.css similarity index 77% rename from src/components/PopUpBar/PopUpBar.css rename to src/components/PopUpBar/PopUpBar.module.css index 7d47d26581..c248f9111f 100644 --- a/src/components/PopUpBar/PopUpBar.css +++ b/src/components/PopUpBar/PopUpBar.module.css @@ -1,4 +1,4 @@ -.popup_container { +.popupContainer { position: sticky; width: 100%; top: 0; @@ -10,11 +10,11 @@ z-index: 2; opacity: 0.8; } -.popup_container.black_text{ +.popupContainer.blackText{ color: #000000 } -.close_button { +.closeButton { background: none; border: none; font-size: 18px; @@ -26,13 +26,13 @@ transform: translateY(-50%); } -.close_button.btn_padding{ +.closeButton.btnPadding{ @media (max-width: 768px) { top: 70%; } } -.close_button > .container-fluid { +.closeButton > .containerFluid { padding: 0px; } - \ No newline at end of file + diff --git a/src/components/Projects/WBS/SameFolderTasks/SameFolderTasks.jsx b/src/components/Projects/WBS/SameFolderTasks/SameFolderTasks.jsx index a063b29ccc..7e0e8a3564 100644 --- a/src/components/Projects/WBS/SameFolderTasks/SameFolderTasks.jsx +++ b/src/components/Projects/WBS/SameFolderTasks/SameFolderTasks.jsx @@ -4,12 +4,15 @@ import axios from 'axios'; import { connect } from 'react-redux'; import { ENDPOINTS } from '~/utils/URL'; import { Table } from 'reactstrap'; +import { useHistory } from 'react-router-dom'; + import EditTaskModal from '../WBSDetail/EditTask/EditTaskModal'; import { getPopupById } from '../../../../actions/popupEditorAction'; import { TASK_DELETE_POPUP_ID } from '../../../../constants/popupId'; function SameFolderTasks(props) { const { taskId } = props.match.params; + const history = useHistory(); let isMounted = true; @@ -22,33 +25,38 @@ function SameFolderTasks(props) { const [projectId, setProjectId] = useState(''); const [wbsName, setWbsName] = useState(''); + const noOtherTasksInFolder = task.mother === null || task.mother === taskId; + useEffect(() => { const fetchTaskData = async () => { try { const res = await axios.get(ENDPOINTS.GET_TASK(taskId)); if (isMounted) { setTask(res?.data || {}); - setWBSId(res?.data.wbsId || ''); + setWBSId(res?.data?.wbsId || ''); } } catch (error) { // eslint-disable-next-line no-console console.log(error); } }; + fetchTaskData(); return () => { isMounted = false; }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { const fetchWBSData = async () => { try { + if (!wbsId) return; const res = await axios.get(ENDPOINTS.GET_WBS(wbsId)); if (isMounted) { - setProjectId(res?.data?.projectId); - setWbsName(res?.data?.wbsName); + setProjectId(res?.data?.projectId || ''); + setWbsName(res?.data?.wbsName || ''); } } catch (error) { // eslint-disable-next-line no-console @@ -56,14 +64,25 @@ function SameFolderTasks(props) { } }; - fetchAllTasks(); - fetchWBSData(); + if (wbsId) { + setLoading(true); + fetchAllTasks(); + fetchWBSData(); + } return () => { isMounted = false; }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [wbsId]); + useEffect(() => { + if (!noOtherTasksInFolder) return; + if (!wbsId || !projectId || !wbsName) return; + + history.replace(`/wbs/tasks/${wbsId}/${projectId}/${encodeURIComponent(wbsName)}`); + }, [noOtherTasksInFolder, wbsId, projectId, wbsName, history]); + const fetchAllTasks = async () => { try { const res = await axios.get(ENDPOINTS.TASKS(task.wbsId, task.level, task.mother)); @@ -75,19 +94,24 @@ function SameFolderTasks(props) { } catch (error) { // eslint-disable-next-line no-console console.log(error); + setLoading(false); } }; - if (task.mother === null || task.mother === taskId) { + if (noOtherTasksInFolder) { return ( -
-

There are no other tasks in this task's folder.

- - Click here to visit the source WBS ({wbsName}) that contains this task - +
+ + Loading... +
); } + return (
{loading ? ( @@ -201,7 +225,10 @@ function SameFolderTasks(props) { {element.name.substring(0, 2)} ); - } catch (error) { } + } catch (error) { + console.error('Failed to render element:', error); + return null; + } })}
diff --git a/src/components/Projects/WBS/WBSDetail/AddTask/AddTaskModal.jsx b/src/components/Projects/WBS/WBSDetail/AddTask/AddTaskModal.jsx index 24d52b111d..21054c220f 100644 --- a/src/components/Projects/WBS/WBSDetail/AddTask/AddTaskModal.jsx +++ b/src/components/Projects/WBS/WBSDetail/AddTask/AddTaskModal.jsx @@ -155,16 +155,7 @@ function AddTaskModal(props) { // states from hooks const activeMembers = useMemo(() => { const members = Array.isArray(allMembers) ? allMembers : []; - const filtered = members.filter(u => { - if (!u) return false; - // Treat only explicit "inactive" as excluded; accept truthy/unknown as active - const isInactive = - u.status === 'Inactive' || - u.isActive === false || - String(u?.isActive).toLowerCase() === 'false'; - return !isInactive; - }); - return filtered.length ? filtered : members; // fallback so the list isn't empty + return members.filter(m => m && m.isActive === true); }, [allMembers]); const projectsList = Array.isArray(allProjects?.projects) ? allProjects.projects : []; diff --git a/src/components/Projects/WBS/WBSDetail/components/TagsSearch.jsx b/src/components/Projects/WBS/WBSDetail/components/TagsSearch.jsx index 72f615a0f6..51920603b6 100644 --- a/src/components/Projects/WBS/WBSDetail/components/TagsSearch.jsx +++ b/src/components/Projects/WBS/WBSDetail/components/TagsSearch.jsx @@ -14,7 +14,10 @@ function TagsSearch(props) { disableInput, darkMode, members, + membersFromStore, + foundProjectMembers, } = props; + const [searchWord, setSearchWord] = useState(''); const [isFocused, setIsFocused] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -49,39 +52,40 @@ function TagsSearch(props) { }; const filteredMembers = useMemo(() => { - // console.log('Filtering members:', { searchWord, membersCount: members?.length, isFocused }); - - const resourceNames = new Set(resourceItems.map(item => item.name.toLowerCase())); - - if (members && members.length > 0) { - return members.filter(member => { - const fullName = `${member.firstName} ${member.lastName}`.toLowerCase(); - - if (resourceNames.has(fullName)) return false; - - if (searchWord.trim().length > 0) { - return fullName.includes(searchWord.toLowerCase()); - } - - return isFocused; - }); - } - - if (searchWord.trim().length > 0 && props.state.projectMembers.foundProjectMembers) { - return props.state.projectMembers.foundProjectMembers.filter(member => { - const fullName = `${member.firstName} ${member.lastName}`.toLowerCase(); - return !resourceNames.has(fullName); - }); + const resourceNames = new Set((resourceItems || []).map(item => String(item?.name || '').toLowerCase())); + const baseList = + Array.isArray(members) && members.length > 0 + ? members + : Array.isArray(membersFromStore) && membersFromStore.length > 0 + ? membersFromStore + : []; + + const applyFiltering = (list) => + list + .filter(m => m && m.isActive === true) + .filter(member => { + const fullName = `${member.firstName || member.first || ''} ${member.lastName || member.last || ''}` + .trim() + .toLowerCase(); + + if (!fullName) return false; + if (resourceNames.has(fullName)) return false; + + if (searchWord.trim().length > 0) { + return fullName.includes(searchWord.toLowerCase()); + } + + return isFocused; + }); + + if (baseList.length > 0) return applyFiltering(baseList); + + if (searchWord.trim().length > 0 && Array.isArray(foundProjectMembers)) { + return applyFiltering(foundProjectMembers); } return []; - }, [ - members, - resourceItems, - searchWord, - isFocused, - props.state.projectMembers.foundProjectMembers, - ]); + }, [members, membersFromStore, foundProjectMembers, resourceItems, searchWord, isFocused]); const handleClick = (event, member) => { const userId = member._id || member.userID; @@ -121,24 +125,24 @@ function TagsSearch(props) { {shouldShowDropdown && ( )} +
{resourceItems?.map((elm, index) => (
    ({ - members: state.projectMembers.members, - state, + // ✅ do NOT overwrite `members` prop anymore + membersFromStore: state.projectMembers.members, + foundProjectMembers: state.projectMembers.foundProjectMembers, }); export default connect(mapStateToProps, { findProjectMembers, -})(TagsSearch); \ No newline at end of file +})(TagsSearch); diff --git a/src/components/ProjectsGlobalDistribution/ProjectsGlobalDistribution.jsx b/src/components/ProjectsGlobalDistribution/ProjectsGlobalDistribution.jsx new file mode 100644 index 0000000000..a22250184e --- /dev/null +++ b/src/components/ProjectsGlobalDistribution/ProjectsGlobalDistribution.jsx @@ -0,0 +1,233 @@ +import React, { useState, useEffect } from 'react'; +import { PieChart, Pie, Cell, Legend, Tooltip, ResponsiveContainer } from 'recharts'; +import styles from './ProjectsGlobalDistribution.module.css'; +import { useDispatch, useSelector } from 'react-redux'; +import { getProjectGlobalDistribution } from '../../actions/bmdashboard/projectActions'; +import 'react-datepicker/dist/react-datepicker.css'; +import DatePicker from 'react-datepicker'; + +const ProjectsGlobalDistribution = () => { + const [statusFilter, setStatusFilter] = useState('All'); + const [startDate, setStartDate] = useState(''); + const [endDate, setEndDate] = useState(''); + + const handleStartDateChange = date => { + if (endDate && date && new Date(date) > new Date(endDate)) { + alert('Start date cannot be later than end date.'); + return; + } + setStartDate(date); + }; + + const handleEndDateChange = date => { + if (startDate && date && new Date(startDate) > new Date(date)) { + alert('Start date cannot be later than end date.'); + return; + } + setEndDate(date); + }; + const [chartData, setChartData] = useState([]); + const [loading, setLoading] = useState(true); + + const darkMode = useSelector(state => state.theme.darkMode); + + const COLORS = { + Asia: '#10b981', + 'North America': '#f59e0b', + Europe: '#3b82f6', + 'South America': '#f97316', + Africa: '#1e40af', + 'Middle East': '#eab308', + }; + + useEffect(() => { + const fetchData = async () => { + const response = await getProjectGlobalDistribution(); + setChartData(response); + setLoading(false); + }; + fetchData(); + }, []); + + useEffect(() => { + filterData(); + }, [statusFilter, startDate, endDate]); + + const filterData = async () => { + const isStatusOnly = statusFilter !== 'All' && !startDate && !endDate; + const isBothDatesSet = startDate && endDate; + + if (!isStatusOnly && !isBothDatesSet) { + if ((startDate && !endDate) || (!startDate && endDate)) { + return; + } + return; + } + if (isBothDatesSet && new Date(startDate) > new Date(endDate)) { + alert('Start date cannot be later than end date.'); + return; + } + const payload = { + statusFilter: statusFilter && statusFilter !== 'All' ? statusFilter : null, + startDate: isBothDatesSet ? startDate : null, + endDate: isBothDatesSet ? endDate : null, + }; + const response = await getProjectGlobalDistribution(payload); + setChartData(response); + }; + + const CustomLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, percent }) => { + const RADIAN = Math.PI / 180; + const radius = innerRadius + (outerRadius - innerRadius) * 0.5; + const x = cx + radius * Math.cos(-midAngle * RADIAN); + const y = cy + radius * Math.sin(-midAngle * RADIAN); + + return ( + cx ? 'start' : 'end'} + dominantBaseline="central" + className="font-bold text-sm" + > + {`${(percent * 100).toFixed(1)}%`} + + ); + }; + + return ( +
    +
    + {/* Header */} +
    +

    + Global Distribution of Projects +

    +
    + + {/* Filters */} +
    +
    + {/* Status Filter */} +
    + + +
    + + {/* Start Date Filter */} +
    + + +
    + + {/* End Date Filter */} +
    + + +
    +
    +
    + + {/* Chart */} + {loading ? ( +
    +

    +
    + ) : ( +
    + {chartData.length > 0 ? ( + + + ({ ...d, percentage: parseFloat(d.percentage) }))} + cx="50%" + cy="50%" + labelLine={false} + label={CustomLabel} + outerRadius={180} + fill="#8884d8" + dataKey="percentage" + nameKey="region" + > + {chartData.map((entry, index) => ( + + ))} + + `${value.toFixed(1)}%`} + contentStyle={{ + backgroundColor: darkMode ? '#222e3c' : 'rgba(255, 255, 255, 0.95)', + border: '1px solid #e2e8f0', + borderRadius: '8px', + padding: '12px', + }} + /> + {value}} + /> + + + ) : ( +
    +

    + No projects match the selected filters +

    +
    + )} +
    + )} +
    +
    + ); +}; + +export default ProjectsGlobalDistribution; diff --git a/src/components/ProjectsGlobalDistribution/ProjectsGlobalDistribution.module.css b/src/components/ProjectsGlobalDistribution/ProjectsGlobalDistribution.module.css new file mode 100644 index 0000000000..da1772609e --- /dev/null +++ b/src/components/ProjectsGlobalDistribution/ProjectsGlobalDistribution.module.css @@ -0,0 +1,18 @@ +.filter{ + display: flex; + justify-content: space-around; + margin: 2em 0; +} + +.filterLabel{ + margin-right: 0.5em; +} + +.chartContainer { + display: flex; + justify-content: center; +} + +.darkmode, .filterDark { + background-color: #1b2a41 !important; +} \ No newline at end of file diff --git a/src/components/Reports/ProjectReport/WbsPiechart/WbsPieChart.jsx b/src/components/Reports/ProjectReport/WbsPiechart/WbsPieChart.jsx index be49452108..4ef36b0ff7 100644 --- a/src/components/Reports/ProjectReport/WbsPiechart/WbsPieChart.jsx +++ b/src/components/Reports/ProjectReport/WbsPiechart/WbsPieChart.jsx @@ -72,7 +72,7 @@ export function WbsPieChart({
    Owners, Managers and Admins in {projectName}
    - + )}
    - Reports Page +

    Reports Page

    { + const handleStatus = isActive => { return isActive ? 'Active' : 'Inactive'; }; // Helper function to format dates // eslint-disable-next-line no-unused-vars - const handleDate = (date) => { + const handleDate = date => { if (!date) return 'N/A'; return moment(date).format('MMM-DD-YY'); }; @@ -85,7 +85,7 @@ export function TeamReport({ match }) { // Helper function to get current team members // eslint-disable-next-line no-unused-vars - const getCurrentTeamMembers = (teamId) => { + const getCurrentTeamMembers = teamId => { // This function would fetch and display current team members // Implementation depends on your requirements // eslint-disable-next-line no-console @@ -99,33 +99,36 @@ export function TeamReport({ match }) { if (!allTeams || !Array.isArray(allTeams)) { return []; } - + return allTeams.filter(teamData => { // Filter by team name - if (searchParams.teamName && !teamData.teamName?.toLowerCase().includes(searchParams.teamName.toLowerCase())) { + if ( + searchParams.teamName && + !teamData.teamName?.toLowerCase().includes(searchParams.teamName.toLowerCase()) + ) { return false; } - + // Filter by creation date if (searchParams.createdAt && new Date(teamData.createdDatetime) < searchParams.createdAt) { return false; } - + // Filter by modification date if (searchParams.modifiedAt && new Date(teamData.modifiedDatetime) < searchParams.modifiedAt) { return false; } - + // Filter by active status if (searchParams.isActive && !teamData.isActive) { return false; } - + // Filter by inactive status if (searchParams.isInactive && teamData.isActive) { return false; } - + return true; }); }; @@ -193,7 +196,6 @@ export function TeamReport({ match }) { } }, [dispatch, allTeams]); - useEffect(() => { let isMounted = true; // flag to check component mount status const fetchTeamDetails = async teamId => { @@ -253,8 +255,7 @@ export function TeamReport({ match }) { if (timeEntry.isTangible) { hours.totalTangibleHrs += parseFloat(timeEntry.hours) + parseFloat(timeEntry.minutes) / 60; } else { - hours.totalIntangibleHrs += - parseFloat(timeEntry.hours) + parseFloat(timeEntry.minutes) / 60; + hours.totalIntangibleHrs += parseFloat(timeEntry.hours) + parseFloat(timeEntry.minutes) / 60; } } return hours; @@ -271,9 +272,7 @@ export function TeamReport({ match }) { .format('YYYY-MM-DD'); try { - const res = await axios.get( - ENDPOINTS.TIME_ENTRIES_PERIOD(member._id, startOfWeek, endOfWeek), - ); + const res = await axios.get(ENDPOINTS.TIME_ENTRIES_PERIOD(member._id, startOfWeek, endOfWeek)); const timeEntries = res.data; const output = calculateTotalHrsForPeriod(timeEntries); const totalTangibleHrs = output.totalTangibleHrs.toFixed(2); @@ -291,9 +290,7 @@ export function TeamReport({ match }) { useEffect(() => { const getTeamMembersWeeklyEffort = async () => { try { - const weeklyEfforts = await Promise.all( - teamMembers.map(member => getWeeklyTangibleHours(member)), - ); + const weeklyEfforts = await Promise.all(teamMembers.map(member => getWeeklyTangibleHours(member))); setTeamMembersWeeklyEffort(weeklyEfforts.filter(effort => !!effort)); } catch (err) { // eslint-disable-next-line no-console @@ -353,7 +350,7 @@ export function TeamReport({ match }) { return ( ( )} > - -
    + +
    - Team ID: {team._id} + Team ID: {team._id}
    -
    +
    Last updated: {moment(team.modifiedDatetime).format('MMM-DD-YY')}
    + -
    + +
    {/* Name Search */}
    -