diff --git a/.mise.toml b/.mise.toml
new file mode 100644
index 00000000000..94f3f791b7e
--- /dev/null
+++ b/.mise.toml
@@ -0,0 +1,2 @@
+[settings]
+python.github_attestations = false
diff --git a/.nvmrc b/.nvmrc
index 9de2256827a..2bd5a0a98a3 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-lts/iron
+22
diff --git a/mise.toml b/mise.toml
new file mode 100644
index 00000000000..76517acf39e
--- /dev/null
+++ b/mise.toml
@@ -0,0 +1,5 @@
+[settings]
+python.github_attestations = false
+
+[tools]
+node = "22"
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 4b99376ec2b..1f56dcda979 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -62,7 +62,7 @@
"date-fns-tz": "^2.0.1",
"dayjs": "^1.11.13",
"diff": "^5.2.2",
- "dompurify": "^3.4.12",
+ "dompurify": "^3.4.16",
"elliptic": "^6.6.1",
"font-awesome": "^4.7.0",
"fs-extra": "^11.3.0",
@@ -71,7 +71,7 @@
"html-to-pdfmake": "^2.0.6",
"html2canvas": "^1.4.1",
"jest": "^30.2.0",
- "joi": "^18.2.1",
+ "joi": "^18.2.5",
"joi-browser": "^13.4.0",
"jquery": "^3.7.1",
"jspdf": "^4.2.1",
@@ -84,7 +84,7 @@
"lodash": "^4.17.21",
"lucide-react": "^0.484.0",
"micromatch": "^4.0.8",
- "moment": "^2.30.1",
+ "moment": "^2.31.0",
"moment-timezone": "^0.5.33",
"object-hash": "^3.0.0",
"path-browserify": "^1.0.1",
@@ -145,7 +145,7 @@
"xlsx": "^0.18.5"
},
"devDependencies": {
- "@babel/core": "^7.28.0",
+ "@babel/core": "^7.29.6",
"@babel/eslint-parser": "^7.28.0",
"@babel/helper-validator-identifier": "^7.12.11",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
@@ -192,7 +192,7 @@
"stylelint-config-css-modules": "^4.5.1",
"stylelint-config-standard": "^39.0.1",
"typescript": "^4.8.4",
- "vite": "^6.3.5",
+ "vite": "^6.4.3",
"vitest": "^3.2.4"
},
"engines": {
@@ -210,6 +210,7 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
"license": "Apache-2.0",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -248,12 +249,12 @@
}
},
"node_modules/@ant-design/colors": {
- "version": "7.2.1",
- "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz",
- "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz",
+ "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==",
"license": "MIT",
"dependencies": {
- "@ant-design/fast-color": "^2.0.6"
+ "@ant-design/fast-color": "^3.0.0"
}
},
"node_modules/@ant-design/cssinjs": {
@@ -303,19 +304,16 @@
"license": "MIT"
},
"node_modules/@ant-design/cssinjs/node_modules/stylis": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
- "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz",
+ "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==",
"license": "MIT"
},
"node_modules/@ant-design/fast-color": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz",
- "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz",
+ "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==",
"license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.24.7"
- },
"engines": {
"node": ">=8.x"
}
@@ -405,24 +403,6 @@
"integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==",
"license": "MIT"
},
- "node_modules/@ant-design/icons/node_modules/@ant-design/colors": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz",
- "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==",
- "license": "MIT",
- "dependencies": {
- "@ant-design/fast-color": "^3.0.0"
- }
- },
- "node_modules/@ant-design/icons/node_modules/@ant-design/fast-color": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz",
- "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==",
- "license": "MIT",
- "engines": {
- "node": ">=8.x"
- }
- },
"node_modules/@ant-design/plots": {
"version": "2.6.8",
"resolved": "https://registry.npmjs.org/@ant-design/plots/-/plots-2.6.8.tgz",
@@ -569,12 +549,12 @@
"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.3.3",
+ "resolved": "https://registry.npmjs.org/@antv/g/-/g-6.3.3.tgz",
+ "integrity": "sha512-Fs07ACQ7LqIpGFhL1wrVhdNbpsezL+vZKzKnKQj3dFzmEHu3S0sSumJtx2iCjy5shQPDWFu8vqSRdYrZdTVlGg==",
"license": "MIT",
"dependencies": {
- "@antv/g-lite": "2.7.0",
+ "@antv/g-lite": "2.7.2",
"@antv/util": "^3.3.5",
"@babel/runtime": "^7.25.6",
"gl-matrix": "^3.4.3",
@@ -595,7 +575,7 @@
"tslib": "^2.5.3"
}
},
- "node_modules/@antv/g-lite": {
+ "node_modules/@antv/g-canvas/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==",
@@ -610,11 +590,20 @@
"tslib": "^2.5.3"
}
},
- "node_modules/@antv/g-lite/node_modules/eventemitter3": {
- "version": "5.0.4",
- "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
- "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
- "license": "MIT"
+ "node_modules/@antv/g-lite": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/@antv/g-lite/-/g-lite-2.7.2.tgz",
+ "integrity": "sha512-hno43swzUX3jBSi26O0l9YJxRVTiZ5HcNb3K6YKf8DRyT/hDo1zvnyD3vm/AmNa9va+3W76VznKgJThn4zRhYw==",
+ "license": "MIT",
+ "dependencies": {
+ "@antv/g-math": "3.1.0",
+ "@antv/util": "^3.3.5",
+ "@antv/vendor": "^1.0.3",
+ "@babel/runtime": "^7.25.6",
+ "eventemitter3": "^5.0.1",
+ "gl-matrix": "^3.4.3",
+ "tslib": "^2.5.3"
+ }
},
"node_modules/@antv/g-math": {
"version": "3.1.0",
@@ -640,6 +629,21 @@
"tslib": "^2.5.3"
}
},
+ "node_modules/@antv/g-plugin-dragndrop/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==",
+ "license": "MIT",
+ "dependencies": {
+ "@antv/g-math": "3.1.0",
+ "@antv/util": "^3.3.5",
+ "@antv/vendor": "^1.0.3",
+ "@babel/runtime": "^7.25.6",
+ "eventemitter3": "^5.0.1",
+ "gl-matrix": "^3.4.3",
+ "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",
@@ -653,6 +657,21 @@
"tslib": "^2.5.3"
}
},
+ "node_modules/@antv/g-svg/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==",
+ "license": "MIT",
+ "dependencies": {
+ "@antv/g-math": "3.1.0",
+ "@antv/util": "^3.3.5",
+ "@antv/vendor": "^1.0.3",
+ "@babel/runtime": "^7.25.6",
+ "eventemitter3": "^5.0.1",
+ "gl-matrix": "^3.4.3",
+ "tslib": "^2.5.3"
+ }
+ },
"node_modules/@antv/g2": {
"version": "5.4.8",
"resolved": "https://registry.npmjs.org/@antv/g2/-/g2-5.4.8.tgz",
@@ -858,12 +877,12 @@
"license": "ISC"
},
"node_modules/@babel/code-frame": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.29.7",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
@@ -872,30 +891,30 @@
}
},
"node_modules/@babel/compat-data": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
- "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
- "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
- "license": "MIT",
- "dependencies": {
- "@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.0",
- "@babel/helper-compilation-targets": "^7.27.2",
- "@babel/helper-module-transforms": "^7.27.3",
- "@babel/helpers": "^7.27.6",
- "@babel/parser": "^7.28.0",
- "@babel/template": "^7.27.2",
- "@babel/traverse": "^7.28.0",
- "@babel/types": "^7.28.0",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -949,13 +968,13 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
- "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
+ "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.28.3",
- "@babel/types": "^7.28.2",
+ "@babel/parser": "^7.29.8",
+ "@babel/types": "^7.29.8",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -978,13 +997,13 @@
}
},
"node_modules/@babel/helper-compilation-targets": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
- "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
"license": "MIT",
"dependencies": {
- "@babel/compat-data": "^7.27.2",
- "@babel/helper-validator-option": "^7.27.1",
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
@@ -1080,9 +1099,9 @@
}
},
"node_modules/@babel/helper-globals": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
- "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -1103,27 +1122,27 @@
}
},
"node_modules/@babel/helper-module-imports": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
- "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
"license": "MIT",
"dependencies": {
- "@babel/traverse": "^7.27.1",
- "@babel/types": "^7.27.1"
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
- "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
"license": "MIT",
"dependencies": {
- "@babel/helper-module-imports": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1",
- "@babel/traverse": "^7.28.3"
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1146,9 +1165,9 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
- "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -1205,27 +1224,27 @@
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
- "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
- "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -1247,13 +1266,13 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz",
- "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==",
+ "version": "7.29.10",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz",
+ "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==",
"license": "MIT",
"dependencies": {
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.28.2"
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8"
},
"engines": {
"node": ">=6.9.0"
@@ -1354,12 +1373,12 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
- "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
+ "version": "7.29.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz",
+ "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.28.2"
+ "@babel/types": "^7.29.8"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -1706,12 +1725,12 @@
}
},
"node_modules/@babel/plugin-syntax-typescript": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
- "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz",
+ "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==",
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -2159,16 +2178,16 @@
}
},
"node_modules/@babel/plugin-transform-modules-systemjs": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz",
- "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==",
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.8.tgz",
+ "integrity": "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-module-transforms": "^7.27.1",
- "@babel/helper-plugin-utils": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1",
- "@babel/traverse": "^7.27.1"
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helper-plugin-utils": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.8"
},
"engines": {
"node": ">=6.9.0"
@@ -2847,31 +2866,31 @@
}
},
"node_modules/@babel/template": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
- "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/parser": "^7.27.2",
- "@babel/types": "^7.27.1"
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz",
- "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
+ "version": "7.29.10",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz",
+ "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==",
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.3",
- "@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.28.3",
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.28.2",
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.8",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.9",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8",
"debug": "^4.3.1"
},
"engines": {
@@ -2879,13 +2898,13 @@
}
},
"node_modules/@babel/types": {
- "version": "7.28.2",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
- "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1"
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -3150,9 +3169,9 @@
}
},
"node_modules/@csstools/css-syntax-patches-for-csstree": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.8.tgz",
- "integrity": "sha512-CpMLjAvwQg3BL5S0IeqsZNMH7EQrEWi0kLKOC13ZBF0ZwERiLWlibNPJr8G1kdU3Ms/r2KiNrF81pUh2HwAHdg==",
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.9.tgz",
+ "integrity": "sha512-iGGw4OsAYsS6pD29MdJ2bX/nJx65a04ZZiw6x+VwWlP2DdXf6f++Zmuv/OzALpdyfVhjbduIIF2cXM7HWBIe9A==",
"dev": true,
"funding": [
{
@@ -4064,9 +4083,9 @@
}
},
"node_modules/@eslint/eslintrc/node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4273,6 +4292,7 @@
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.6.tgz",
"integrity": "sha512-mtBFIi1UsYQo7rYonYFkjgYKGoL8T+fEH6NGUpvuqtY3ytMsAoDaPo5rk25KuMtKDipY4bGYM/CkmCHA1N3FUg==",
+ "deprecated": "v0.2.x is no longer supported. Unless you are still using FontAwesome 5, please update to v3.1.1 or greater.",
"license": "MIT",
"dependencies": {
"prop-types": "^15.8.1"
@@ -4478,22 +4498,6 @@
}
}
},
- "node_modules/@inquirer/core/node_modules/ansi-escapes": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
- "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "type-fest": "^0.21.3"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/@inquirer/core/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -4526,19 +4530,6 @@
"node": ">=8"
}
},
- "node_modules/@inquirer/core/node_modules/type-fest": {
- "version": "0.21.3",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
- "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/@inquirer/core/node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
@@ -4611,35 +4602,6 @@
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
- "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
@@ -4655,23 +4617,6 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
- "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
"node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -4698,9 +4643,9 @@
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "version": "3.15.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz",
+ "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==",
"license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
@@ -4720,39 +4665,26 @@
}
},
"node_modules/@jest/console": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz",
- "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.5.2.tgz",
+ "integrity": "sha512-e8sQC4pCMHPBrPX7Hk8TJr+DM5dkUnImSTNag8SEWFAFAF6xIz4AJvCKapnrdHnZxep4bxw4PhG+ZbimYBMyKw==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
- "jest-message-util": "30.4.1",
- "jest-util": "30.4.1",
+ "jest-message-util": "30.5.1",
+ "jest-util": "30.5.1",
"slash": "^3.0.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/console/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/@jest/console/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -4762,13 +4694,13 @@
}
},
"node_modules/@jest/console/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -4780,9 +4712,9 @@
}
},
"node_modules/@jest/console/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
"node_modules/@jest/console/node_modules/ansi-styles": {
@@ -4797,35 +4729,20 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/@jest/console/node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/@jest/console/node_modules/jest-message-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz",
- "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz",
+ "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/stack-utils": "^2.0.3",
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
- "jest-util": "30.4.1",
+ "jest-util": "30.5.1",
"picomatch": "^4.0.3",
- "pretty-format": "30.4.1",
+ "pretty-format": "30.5.1",
"slash": "^3.0.0",
"stack-utils": "^2.0.6"
},
@@ -4833,22 +4750,13 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/console/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/@jest/console/node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
@@ -4860,9 +4768,9 @@
}
},
"node_modules/@jest/console/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -4872,32 +4780,32 @@
}
},
"node_modules/@jest/console/node_modules/pretty-format": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz",
- "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
"license": "MIT",
"dependencies": {
- "@jest/schemas": "30.4.1",
- "ansi-styles": "^5.2.0",
- "react-is-18": "npm:react-is@^18.3.1",
- "react-is-19": "npm:react-is@^19.2.5"
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/core": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz",
- "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.5.2.tgz",
+ "integrity": "sha512-/jqvFWoJF7LV1a6AUpYMbMm+2yIYsHJfR474H0SCzr9k8tnO/jl9wRg6zSG6lxgf6EozNlCG4amFzWsbZSvvZA==",
"license": "MIT",
"dependencies": {
- "@jest/console": "30.4.1",
- "@jest/pattern": "30.4.0",
- "@jest/reporters": "30.4.1",
- "@jest/test-result": "30.4.1",
- "@jest/transform": "30.4.1",
- "@jest/types": "30.4.1",
+ "@jest/console": "30.5.2",
+ "@jest/pattern": "30.5.0",
+ "@jest/reporters": "30.5.2",
+ "@jest/test-result": "30.5.2",
+ "@jest/transform": "30.5.2",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"ansi-escapes": "^4.3.2",
"chalk": "^4.1.2",
@@ -4905,20 +4813,20 @@
"exit-x": "^0.2.2",
"fast-json-stable-stringify": "^2.1.0",
"graceful-fs": "^4.2.11",
- "jest-changed-files": "30.4.1",
- "jest-config": "30.4.2",
- "jest-haste-map": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-regex-util": "30.4.0",
- "jest-resolve": "30.4.1",
- "jest-resolve-dependencies": "30.4.2",
- "jest-runner": "30.4.2",
- "jest-runtime": "30.4.2",
- "jest-snapshot": "30.4.1",
- "jest-util": "30.4.1",
- "jest-validate": "30.4.1",
- "jest-watcher": "30.4.1",
- "pretty-format": "30.4.1",
+ "jest-changed-files": "30.5.1",
+ "jest-config": "30.5.2",
+ "jest-haste-map": "30.5.1",
+ "jest-message-util": "30.5.1",
+ "jest-regex-util": "30.5.0",
+ "jest-resolve": "30.5.1",
+ "jest-resolve-dependencies": "30.5.2",
+ "jest-runner": "30.5.2",
+ "jest-runtime": "30.5.2",
+ "jest-snapshot": "30.5.2",
+ "jest-util": "30.5.1",
+ "jest-validate": "30.5.1",
+ "jest-watcher": "30.5.2",
+ "pretty-format": "30.5.1",
"slash": "^3.0.0"
},
"engines": {
@@ -4933,87 +4841,53 @@
}
}
},
- "node_modules/@jest/core/node_modules/@jest/diff-sequences": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz",
- "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==",
+ "node_modules/@jest/core/node_modules/@jest/schemas": {
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/core/node_modules/@jest/expect-utils": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz",
- "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==",
+ "node_modules/@jest/core/node_modules/@jest/transform": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.5.2.tgz",
+ "integrity": "sha512-LBGwmaE886C41jDZ65eqR1YbXM7642MK3ZhTowZd5+xDlrDLuf4ePuDdDmVSvcr8NTVJ3MqxLkMbUCzuigUaoQ==",
"license": "MIT",
"dependencies": {
- "@jest/get-type": "30.1.0"
+ "@babel/core": "^7.27.4",
+ "@jest/types": "30.5.1",
+ "@jridgewell/trace-mapping": "^0.3.31",
+ "babel-plugin-istanbul": "^8.0.0",
+ "chalk": "^4.1.2",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.5.1",
+ "jest-regex-util": "30.5.0",
+ "jest-util": "30.5.1",
+ "pirates": "^4.0.7",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^5.0.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/core/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
+ "node_modules/@jest/core/node_modules/@jest/types": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/core/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
- "license": "MIT",
- "dependencies": {
- "@sinclair/typebox": "^0.34.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/core/node_modules/@jest/transform": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz",
- "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.27.4",
- "@jest/types": "30.4.1",
- "@jridgewell/trace-mapping": "^0.3.25",
- "babel-plugin-istanbul": "^7.0.1",
- "chalk": "^4.1.2",
- "convert-source-map": "^2.0.0",
- "fast-json-stable-stringify": "^2.1.0",
- "graceful-fs": "^4.2.11",
- "jest-haste-map": "30.4.1",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
- "pirates": "^4.0.7",
- "slash": "^3.0.0",
- "write-file-atomic": "^5.0.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/core/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
- "license": "MIT",
- "dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
- "@types/istanbul-lib-coverage": "^2.0.6",
- "@types/istanbul-reports": "^3.0.4",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
"@types/yargs": "^17.0.33",
"chalk": "^4.1.2"
@@ -5023,26 +4897,11 @@
}
},
"node_modules/@jest/core/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
- "node_modules/@jest/core/node_modules/ansi-escapes": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
- "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
- "license": "MIT",
- "dependencies": {
- "type-fest": "^0.21.3"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/@jest/core/node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
@@ -5056,9 +4915,9 @@
}
},
"node_modules/@jest/core/node_modules/babel-plugin-istanbul": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz",
- "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-8.0.0.tgz",
+ "integrity": "sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg==",
"license": "BSD-3-Clause",
"workspaces": [
"test/babel-8"
@@ -5068,42 +4927,27 @@
"@istanbuljs/load-nyc-config": "^1.0.0",
"@istanbuljs/schema": "^0.1.3",
"istanbul-lib-instrument": "^6.0.2",
- "test-exclude": "^6.0.0"
+ "test-exclude": "^7.0.1"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
- "node_modules/@jest/core/node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
+ "node_modules/@jest/core/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"license": "MIT",
"engines": {
- "node": ">=8"
- }
- },
- "node_modules/@jest/core/node_modules/expect": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz",
- "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==",
- "license": "MIT",
- "dependencies": {
- "@jest/expect-utils": "30.4.1",
- "@jest/get-type": "30.1.0",
- "jest-matcher-utils": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-mock": "30.4.1",
- "jest-util": "30.4.1"
+ "node": ">=12.0.0"
},
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
}
},
"node_modules/@jest/core/node_modules/istanbul-lib-instrument": {
@@ -5122,74 +4966,42 @@
"node": ">=10"
}
},
- "node_modules/@jest/core/node_modules/jest-diff": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz",
- "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==",
- "license": "MIT",
- "dependencies": {
- "@jest/diff-sequences": "30.4.0",
- "@jest/get-type": "30.1.0",
- "chalk": "^4.1.2",
- "pretty-format": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/@jest/core/node_modules/jest-haste-map": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz",
- "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.5.1.tgz",
+ "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
+ "@parcel/watcher": "^2.6.0",
"@types/node": "*",
"anymatch": "^3.1.3",
"fb-watchman": "^2.0.2",
+ "fdir": "^6.5.0",
"graceful-fs": "^4.2.11",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
- "jest-worker": "30.4.1",
- "picomatch": "^4.0.3",
- "walker": "^1.0.8"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "optionalDependencies": {
- "fsevents": "^2.3.3"
- }
- },
- "node_modules/@jest/core/node_modules/jest-matcher-utils": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz",
- "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==",
- "license": "MIT",
- "dependencies": {
- "@jest/get-type": "30.1.0",
- "chalk": "^4.1.2",
- "jest-diff": "30.4.1",
- "pretty-format": "30.4.1"
+ "jest-regex-util": "30.5.0",
+ "jest-util": "30.5.1",
+ "jest-worker": "30.5.1",
+ "picomatch": "^4.0.3"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/core/node_modules/jest-message-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz",
- "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz",
+ "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/stack-utils": "^2.0.3",
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
- "jest-util": "30.4.1",
+ "jest-util": "30.5.1",
"picomatch": "^4.0.3",
- "pretty-format": "30.4.1",
+ "pretty-format": "30.5.1",
"slash": "^3.0.0",
"stack-utils": "^2.0.6"
},
@@ -5197,68 +5009,22 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/core/node_modules/jest-mock": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz",
- "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==",
- "license": "MIT",
- "dependencies": {
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "jest-util": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/@jest/core/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@jest/core/node_modules/jest-snapshot": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz",
- "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz",
+ "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==",
"license": "MIT",
- "dependencies": {
- "@babel/core": "^7.27.4",
- "@babel/generator": "^7.27.5",
- "@babel/plugin-syntax-jsx": "^7.27.1",
- "@babel/plugin-syntax-typescript": "^7.27.1",
- "@babel/types": "^7.27.3",
- "@jest/expect-utils": "30.4.1",
- "@jest/get-type": "30.1.0",
- "@jest/snapshot-utils": "30.4.1",
- "@jest/transform": "30.4.1",
- "@jest/types": "30.4.1",
- "babel-preset-current-node-syntax": "^1.2.0",
- "chalk": "^4.1.2",
- "expect": "30.4.1",
- "graceful-fs": "^4.2.11",
- "jest-diff": "30.4.1",
- "jest-matcher-utils": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-util": "30.4.1",
- "pretty-format": "30.4.1",
- "semver": "^7.7.2",
- "synckit": "^0.11.8"
- },
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/core/node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
@@ -5269,26 +5035,10 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/core/node_modules/jest-worker": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz",
- "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "@ungap/structured-clone": "^1.3.0",
- "jest-util": "30.4.1",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.1.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/@jest/core/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -5298,47 +5048,20 @@
}
},
"node_modules/@jest/core/node_modules/pretty-format": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz",
- "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
"license": "MIT",
"dependencies": {
- "@jest/schemas": "30.4.1",
- "ansi-styles": "^5.2.0",
- "react-is-18": "npm:react-is@^18.3.1",
- "react-is-19": "npm:react-is@^19.2.5"
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/core/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
- "node_modules/@jest/core/node_modules/type-fest": {
- "version": "0.21.3",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
- "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/@jest/core/node_modules/write-file-atomic": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
@@ -5353,10 +5076,9 @@
}
},
"node_modules/@jest/diff-sequences": {
- "version": "30.0.1",
- "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz",
- "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==",
- "dev": true,
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.5.0.tgz",
+ "integrity": "sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==",
"license": "MIT",
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
@@ -5379,30 +5101,34 @@
}
},
"node_modules/@jest/expect": {
+ "name": "@jest/fake-timers",
"version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz",
- "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
+ "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "expect": "^29.7.0",
- "jest-snapshot": "^29.7.0"
+ "@jest/types": "^29.6.3",
+ "@sinonjs/fake-timers": "^10.0.2",
+ "@types/node": "*",
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/expect-utils": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
- "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
- "dev": true,
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.5.2.tgz",
+ "integrity": "sha512-d/HOLSrJUlBIl6n3LzOa7cjW3xdePm5H5gQw2lixZ/0h157l9sw3mLblzFqnPcTbhDzhRQdBOX3A3KcG73nm5Q==",
"license": "MIT",
"dependencies": {
- "jest-get-type": "^29.6.3"
+ "@jest/get-type": "30.5.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/fake-timers": {
@@ -5424,9 +5150,9 @@
}
},
"node_modules/@jest/get-type": {
- "version": "30.1.0",
- "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz",
- "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.5.0.tgz",
+ "integrity": "sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q==",
"license": "MIT",
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
@@ -5449,55 +5175,67 @@
}
},
"node_modules/@jest/pattern": {
- "version": "30.0.1",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
- "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
- "dev": true,
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.5.0.tgz",
+ "integrity": "sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
- "jest-regex-util": "30.0.1"
+ "jest-regex-util": "30.5.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/pattern/node_modules/jest-regex-util": {
- "version": "30.0.1",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
- "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
- "dev": true,
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz",
+ "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==",
"license": "MIT",
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
+ "node_modules/@jest/react-is-18": {
+ "name": "react-is",
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
+ "node_modules/@jest/react-is-19": {
+ "name": "react-is",
+ "version": "19.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.3.1.tgz",
+ "integrity": "sha512-RRieF6IyxQBkcK6pP8xOHprLTPW6QjRkbGdImtqNme+/hYVwCZkLQLdlZGOhUqXwkS9IEynaGvPJtv6XB/cz0Q==",
+ "license": "MIT"
+ },
"node_modules/@jest/reporters": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz",
- "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.5.2.tgz",
+ "integrity": "sha512-ev6E9iG73twQcfAfoTnviuYkr3yOrYbzAtMFCimv2fxHXPIp9PBt2u1wNMt0aYWgl8FPwIcS8oqRJg6alnht4Q==",
"license": "MIT",
"dependencies": {
"@bcoe/v8-coverage": "^0.2.3",
- "@jest/console": "30.4.1",
- "@jest/test-result": "30.4.1",
- "@jest/transform": "30.4.1",
- "@jest/types": "30.4.1",
- "@jridgewell/trace-mapping": "^0.3.25",
+ "@jest/console": "30.5.2",
+ "@jest/test-result": "30.5.2",
+ "@jest/transform": "30.5.2",
+ "@jest/types": "30.5.1",
+ "@jridgewell/trace-mapping": "^0.3.31",
"@types/node": "*",
"chalk": "^4.1.2",
"collect-v8-coverage": "^1.0.2",
"exit-x": "^0.2.2",
- "glob": "^10.5.0",
+ "glob": "^13.0.6",
"graceful-fs": "^4.2.11",
"istanbul-lib-coverage": "^3.0.0",
"istanbul-lib-instrument": "^6.0.0",
"istanbul-lib-report": "^3.0.0",
"istanbul-lib-source-maps": "^5.0.0",
"istanbul-reports": "^3.1.3",
- "jest-message-util": "30.4.1",
- "jest-util": "30.4.1",
- "jest-worker": "30.4.1",
+ "jest-message-util": "30.5.1",
+ "jest-util": "30.5.1",
+ "jest-worker": "30.5.1",
"slash": "^3.0.0",
"string-length": "^4.0.2",
"v8-to-istanbul": "^9.0.1"
@@ -5520,23 +5258,10 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"license": "MIT"
},
- "node_modules/@jest/reporters/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/@jest/reporters/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -5546,22 +5271,22 @@
}
},
"node_modules/@jest/reporters/node_modules/@jest/transform": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz",
- "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.5.2.tgz",
+ "integrity": "sha512-LBGwmaE886C41jDZ65eqR1YbXM7642MK3ZhTowZd5+xDlrDLuf4ePuDdDmVSvcr8NTVJ3MqxLkMbUCzuigUaoQ==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.27.4",
- "@jest/types": "30.4.1",
- "@jridgewell/trace-mapping": "^0.3.25",
- "babel-plugin-istanbul": "^7.0.1",
+ "@jest/types": "30.5.1",
+ "@jridgewell/trace-mapping": "^0.3.31",
+ "babel-plugin-istanbul": "^8.0.0",
"chalk": "^4.1.2",
"convert-source-map": "^2.0.0",
"fast-json-stable-stringify": "^2.1.0",
"graceful-fs": "^4.2.11",
- "jest-haste-map": "30.4.1",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
+ "jest-haste-map": "30.5.1",
+ "jest-regex-util": "30.5.0",
+ "jest-util": "30.5.1",
"pirates": "^4.0.7",
"slash": "^3.0.0",
"write-file-atomic": "^5.0.1"
@@ -5571,13 +5296,13 @@
}
},
"node_modules/@jest/reporters/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -5589,9 +5314,9 @@
}
},
"node_modules/@jest/reporters/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
"node_modules/@jest/reporters/node_modules/ansi-styles": {
@@ -5607,9 +5332,9 @@
}
},
"node_modules/@jest/reporters/node_modules/babel-plugin-istanbul": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz",
- "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-8.0.0.tgz",
+ "integrity": "sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg==",
"license": "BSD-3-Clause",
"workspaces": [
"test/babel-8"
@@ -5619,52 +5344,62 @@
"@istanbuljs/load-nyc-config": "^1.0.0",
"@istanbuljs/schema": "^0.1.3",
"istanbul-lib-instrument": "^6.0.2",
- "test-exclude": "^6.0.0"
+ "test-exclude": "^7.0.1"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jest/reporters/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
}
},
"node_modules/@jest/reporters/node_modules/brace-expansion": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
- "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0"
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
}
},
- "node_modules/@jest/reporters/node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
+ "node_modules/@jest/reporters/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
}
},
"node_modules/@jest/reporters/node_modules/glob": {
- "version": "10.5.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
- "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
- "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
- "license": "ISC",
+ "version": "13.0.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
+ "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
+ "minimatch": "^10.2.2",
+ "minipass": "^7.1.3",
+ "path-scurry": "^2.0.2"
},
- "bin": {
- "glob": "dist/esm/bin.mjs"
+ "engines": {
+ "node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -5687,43 +5422,41 @@
}
},
"node_modules/@jest/reporters/node_modules/jest-haste-map": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz",
- "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.5.1.tgz",
+ "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
+ "@parcel/watcher": "^2.6.0",
"@types/node": "*",
"anymatch": "^3.1.3",
"fb-watchman": "^2.0.2",
+ "fdir": "^6.5.0",
"graceful-fs": "^4.2.11",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
- "jest-worker": "30.4.1",
- "picomatch": "^4.0.3",
- "walker": "^1.0.8"
+ "jest-regex-util": "30.5.0",
+ "jest-util": "30.5.1",
+ "jest-worker": "30.5.1",
+ "picomatch": "^4.0.3"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "optionalDependencies": {
- "fsevents": "^2.3.3"
}
},
"node_modules/@jest/reporters/node_modules/jest-message-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz",
- "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz",
+ "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/stack-utils": "^2.0.3",
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
- "jest-util": "30.4.1",
+ "jest-util": "30.5.1",
"picomatch": "^4.0.3",
- "pretty-format": "30.4.1",
+ "pretty-format": "30.5.1",
"slash": "^3.0.0",
"stack-utils": "^2.0.6"
},
@@ -5732,21 +5465,21 @@
}
},
"node_modules/@jest/reporters/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz",
+ "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==",
"license": "MIT",
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/reporters/node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
@@ -5757,41 +5490,50 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/reporters/node_modules/jest-worker": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz",
- "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==",
- "license": "MIT",
+ "node_modules/@jest/reporters/node_modules/lru-cache": {
+ "version": "11.5.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz",
+ "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@jest/reporters/node_modules/minimatch": {
+ "version": "10.2.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
+ "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "@types/node": "*",
- "@ungap/structured-clone": "^1.3.0",
- "jest-util": "30.4.1",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.1.1"
+ "brace-expansion": "^5.0.8"
},
"engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/@jest/reporters/node_modules/minimatch": {
- "version": "9.0.9",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
- "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
- "license": "ISC",
+ "node_modules/@jest/reporters/node_modules/path-scurry": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
+ "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "brace-expansion": "^2.0.2"
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
},
"engines": {
- "node": ">=16 || 14 >=14.17"
+ "node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@jest/reporters/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -5801,35 +5543,20 @@
}
},
"node_modules/@jest/reporters/node_modules/pretty-format": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz",
- "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
"license": "MIT",
"dependencies": {
- "@jest/schemas": "30.4.1",
- "ansi-styles": "^5.2.0",
- "react-is-18": "npm:react-is@^18.3.1",
- "react-is-19": "npm:react-is@^19.2.5"
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/reporters/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
"node_modules/@jest/reporters/node_modules/write-file-atomic": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
@@ -5857,12 +5584,12 @@
}
},
"node_modules/@jest/snapshot-utils": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz",
- "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.5.1.tgz",
+ "integrity": "sha512-V3wnxNtiVmw5PPVg433Cn3VdXnsOeu/ofLw3KC04Bn2y1wIlU5kozXQn48rhrgj/02LrCVtntTEF1yBha0XSUw==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
"natural-compare": "^1.4.0"
@@ -5871,23 +5598,10 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/snapshot-utils/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/@jest/snapshot-utils/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -5897,13 +5611,13 @@
}
},
"node_modules/@jest/snapshot-utils/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -5915,28 +5629,20 @@
}
},
"node_modules/@jest/snapshot-utils/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
- "node_modules/@jest/snapshot-utils/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/@jest/source-map": {
- "version": "30.0.1",
- "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz",
- "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.5.2.tgz",
+ "integrity": "sha512-c5CehvkbfHX6yyNg7bMn2ylqlp9AHXuZ/3hPH/Lh5bxD/vefd/lV9HBjMqXef9yQNqkdwyZvkLSVSwB38hy8Qw==",
"license": "MIT",
"dependencies": {
- "@jridgewell/trace-mapping": "^0.3.25",
+ "@jridgewell/trace-mapping": "^0.3.31",
"callsites": "^3.1.0",
+ "convert-source-map": "^2.0.0",
"graceful-fs": "^4.2.11"
},
"engines": {
@@ -5944,13 +5650,13 @@
}
},
"node_modules/@jest/test-result": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz",
- "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.5.2.tgz",
+ "integrity": "sha512-eO6YU5rsLfs60ne694e0IAmRgeBnoA8mu0nlxXDbl/1j5km0o2vO9/VbbUIKIH+WRKKEI+ELgwraRGn2Lhep8g==",
"license": "MIT",
"dependencies": {
- "@jest/console": "30.4.1",
- "@jest/types": "30.4.1",
+ "@jest/console": "30.5.2",
+ "@jest/types": "30.5.1",
"@types/istanbul-lib-coverage": "^2.0.6",
"collect-v8-coverage": "^1.0.2"
},
@@ -5958,23 +5664,10 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/test-result/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/@jest/test-result/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -5984,13 +5677,13 @@
}
},
"node_modules/@jest/test-result/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -6002,52 +5695,30 @@
}
},
"node_modules/@jest/test-result/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
- "node_modules/@jest/test-result/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/@jest/test-sequencer": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz",
- "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.5.2.tgz",
+ "integrity": "sha512-bmij8jZyYAC47ExT2GeP7PcrlwSNS+Bp3KeRuBH88gpcxqDv6fljN8PemS4J/lLZ79xb6mpaennQTTq0zUAVVg==",
"license": "MIT",
"dependencies": {
- "@jest/test-result": "30.4.1",
+ "@jest/test-result": "30.5.2",
"graceful-fs": "^4.2.11",
- "jest-haste-map": "30.4.1",
+ "jest-haste-map": "30.5.1",
"slash": "^3.0.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/test-sequencer/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/@jest/test-sequencer/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -6057,13 +5728,13 @@
}
},
"node_modules/@jest/test-sequencer/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -6075,66 +5746,66 @@
}
},
"node_modules/@jest/test-sequencer/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
- "node_modules/@jest/test-sequencer/node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
+ "node_modules/@jest/test-sequencer/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
}
},
"node_modules/@jest/test-sequencer/node_modules/jest-haste-map": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz",
- "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.5.1.tgz",
+ "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
+ "@parcel/watcher": "^2.6.0",
"@types/node": "*",
"anymatch": "^3.1.3",
"fb-watchman": "^2.0.2",
+ "fdir": "^6.5.0",
"graceful-fs": "^4.2.11",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
- "jest-worker": "30.4.1",
- "picomatch": "^4.0.3",
- "walker": "^1.0.8"
+ "jest-regex-util": "30.5.0",
+ "jest-util": "30.5.1",
+ "jest-worker": "30.5.1",
+ "picomatch": "^4.0.3"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "optionalDependencies": {
- "fsevents": "^2.3.3"
}
},
"node_modules/@jest/test-sequencer/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz",
+ "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==",
"license": "MIT",
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/test-sequencer/node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
@@ -6145,26 +5816,10 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/test-sequencer/node_modules/jest-worker": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz",
- "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "@ungap/structured-clone": "^1.3.0",
- "jest-util": "30.4.1",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.1.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/@jest/test-sequencer/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -6173,21 +5828,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/@jest/test-sequencer/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
"node_modules/@jest/transform": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
@@ -6243,6 +5883,16 @@
"@jridgewell/trace-mapping": "^0.3.24"
}
},
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -6517,13 +6167,13 @@
}
},
"node_modules/@mui/private-theming": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.1.tgz",
- "integrity": "sha512-WU3YLkKXii/x8ZEKnrLKsPwplCVE11yZxUvlaaZSIzCcI3x2OdFC8eMlNy74hVeUsYQvzzX1Es/k4ARPlFvpPQ==",
+ "version": "7.3.11",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.11.tgz",
+ "integrity": "sha512-9B+YKms0fRHbNrqp9tOT/DNbNnU5gyvJ1o3qAGXfq8GmZcbJnE3At9x07Zr/o0pkhzg4aDdwXVqe4+AcgtOCPA==",
"license": "MIT",
"dependencies": {
- "@babel/runtime": "^7.28.2",
- "@mui/utils": "^7.3.1",
+ "@babel/runtime": "^7.28.6",
+ "@mui/utils": "^7.3.11",
"prop-types": "^15.8.1"
},
"engines": {
@@ -6544,16 +6194,16 @@
}
},
"node_modules/@mui/styled-engine": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.1.tgz",
- "integrity": "sha512-Nqo6OHjvJpXJ1+9TekTE//+8RybgPQUKwns2Lh0sq+8rJOUSUKS3KALv4InSOdHhIM9Mdi8/L7LTF1/Ky6D6TQ==",
+ "version": "7.3.10",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.10.tgz",
+ "integrity": "sha512-WxE9SiF8xskAQqGjsp0poXCkCqsoXFEsSr0HBXfApmGHR+DBnXRp+z46Vsltg4gpPM4Z96DeAQRpeAOnhNg7Ng==",
"license": "MIT",
"dependencies": {
- "@babel/runtime": "^7.28.2",
+ "@babel/runtime": "^7.28.6",
"@emotion/cache": "^11.14.0",
"@emotion/serialize": "^1.3.3",
"@emotion/sheet": "^1.4.0",
- "csstype": "^3.1.3",
+ "csstype": "^3.2.3",
"prop-types": "^15.8.1"
},
"engines": {
@@ -6578,18 +6228,18 @@
}
},
"node_modules/@mui/system": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.1.tgz",
- "integrity": "sha512-mIidecvcNVpNJMdPDmCeoSL5zshKBbYPcphjuh6ZMjhybhqhZ4mX6k9zmIWh6XOXcqRQMg5KrcjnO0QstrNj3w==",
+ "version": "7.3.11",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.11.tgz",
+ "integrity": "sha512-7izwGWdNawAKpBKcRlx7f2gFnAAjmASBWvMcyX4YYEeLOFsbfGRbUYGInvnAcUeql3rPxI7F9Ft4oY2OLRz44g==",
"license": "MIT",
"dependencies": {
- "@babel/runtime": "^7.28.2",
- "@mui/private-theming": "^7.3.1",
- "@mui/styled-engine": "^7.3.1",
- "@mui/types": "^7.4.5",
- "@mui/utils": "^7.3.1",
+ "@babel/runtime": "^7.28.6",
+ "@mui/private-theming": "^7.3.11",
+ "@mui/styled-engine": "^7.3.10",
+ "@mui/types": "^7.4.12",
+ "@mui/utils": "^7.3.11",
"clsx": "^2.1.1",
- "csstype": "^3.1.3",
+ "csstype": "^3.2.3",
"prop-types": "^15.8.1"
},
"engines": {
@@ -6618,12 +6268,12 @@
}
},
"node_modules/@mui/types": {
- "version": "7.4.5",
- "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.5.tgz",
- "integrity": "sha512-ZPwlAOE3e8C0piCKbaabwrqZbW4QvWz0uapVPWya7fYj6PeDkl5sSJmomT7wjOcZGPB48G/a6Ubidqreptxz4g==",
+ "version": "7.4.12",
+ "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.12.tgz",
+ "integrity": "sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==",
"license": "MIT",
"dependencies": {
- "@babel/runtime": "^7.28.2"
+ "@babel/runtime": "^7.28.6"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -6635,17 +6285,17 @@
}
},
"node_modules/@mui/utils": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.1.tgz",
- "integrity": "sha512-/31y4wZqVWa0jzMnzo6JPjxwP6xXy4P3+iLbosFg/mJQowL1KIou0LC+lquWW60FKVbKz5ZUWBg2H3jausa0pw==",
+ "version": "7.3.11",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.11.tgz",
+ "integrity": "sha512-XTjGnifwteg71/ij+0e7Y7d+hwyntMYP5wPoA/g2drdGH+Flkvjwy0OfrVpKBbaOvofq4zU/LIyUZyKgmWu18g==",
"license": "MIT",
"dependencies": {
- "@babel/runtime": "^7.28.2",
- "@mui/types": "^7.4.5",
+ "@babel/runtime": "^7.28.6",
+ "@mui/types": "^7.4.12",
"@types/prop-types": "^15.7.15",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
- "react-is": "^19.1.1"
+ "react-is": "^19.2.3"
},
"engines": {
"node": ">=14.0.0"
@@ -6664,6 +6314,12 @@
}
}
},
+ "node_modules/@mui/utils/node_modules/react-is": {
+ "version": "19.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.3.1.tgz",
+ "integrity": "sha512-RRieF6IyxQBkcK6pP8xOHprLTPW6QjRkbGdImtqNme+/hYVwCZkLQLdlZGOhUqXwkS9IEynaGvPJtv6XB/cz0Q==",
+ "license": "MIT"
+ },
"node_modules/@mui/x-date-pickers": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-8.11.0.tgz",
@@ -6752,22 +6408,42 @@
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/@napi-rs/lzma-linux-x64-gnu": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
+ "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^22.20 || ^24.12 || >=25"
+ }
+ },
"node_modules/@napi-rs/wasm-runtime": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
- "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.6.tgz",
+ "integrity": "sha512-mRyDmEw9/fCDXCGfL1sbgIw/GUK0PVxRW4YwgPN61F3LQ+fSyoBFarJVzgGyhF6kRzEQZ0FejDqm4WLXSeqQmw==",
"license": "MIT",
"optional": true,
"dependencies": {
- "@tybys/wasm-util": "^0.10.1"
+ "@tybys/wasm-util": "^0.10.3"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=23.5.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"peerDependencies": {
- "@emnapi/core": "^1.7.1",
- "@emnapi/runtime": "^1.7.1"
+ "@emnapi/core": "^1.7.1 || 2.0.0-beta.2",
+ "@emnapi/runtime": "^1.7.1 || 2.0.0-beta.2"
}
},
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
@@ -6844,18 +6520,16 @@
"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,
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz",
+ "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==",
"hasInstallScript": true,
"license": "MIT",
- "optional": true,
"dependencies": {
- "detect-libc": "^1.0.3",
+ "detect-libc": "^2.0.3",
"is-glob": "^4.0.3",
- "micromatch": "^4.0.5",
- "node-addon-api": "^7.0.0"
+ "node-addon-api": "^7.0.0",
+ "picomatch": "^4.0.4"
},
"engines": {
"node": ">= 10.0.0"
@@ -6865,29 +6539,27 @@
"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"
+ "@parcel/watcher-android-arm64": "2.6.0",
+ "@parcel/watcher-darwin-arm64": "2.6.0",
+ "@parcel/watcher-darwin-x64": "2.6.0",
+ "@parcel/watcher-freebsd-x64": "2.6.0",
+ "@parcel/watcher-linux-arm-glibc": "2.6.0",
+ "@parcel/watcher-linux-arm-musl": "2.6.0",
+ "@parcel/watcher-linux-arm64-glibc": "2.6.0",
+ "@parcel/watcher-linux-arm64-musl": "2.6.0",
+ "@parcel/watcher-linux-x64-glibc": "2.6.0",
+ "@parcel/watcher-linux-x64-musl": "2.6.0",
+ "@parcel/watcher-win32-arm64": "2.6.0",
+ "@parcel/watcher-win32-x64": "2.6.0"
}
},
"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==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz",
+ "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==",
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -6902,13 +6574,12 @@
}
},
"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==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz",
+ "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==",
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -6923,13 +6594,12 @@
}
},
"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==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz",
+ "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==",
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -6944,13 +6614,12 @@
}
},
"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==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz",
+ "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==",
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -6965,13 +6634,12 @@
}
},
"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==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz",
+ "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==",
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -6986,13 +6654,12 @@
}
},
"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==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz",
+ "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==",
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -7007,13 +6674,12 @@
}
},
"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==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz",
+ "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==",
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -7028,13 +6694,12 @@
}
},
"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==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz",
+ "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==",
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -7049,13 +6714,12 @@
}
},
"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==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz",
+ "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==",
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -7070,13 +6734,12 @@
}
},
"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==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz",
+ "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==",
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -7091,13 +6754,12 @@
}
},
"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==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz",
+ "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==",
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -7111,14 +6773,13 @@
"url": "https://opencollective.com/parcel"
}
},
- "node_modules/@parcel/watcher-win32-ia32": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
- "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
+ "node_modules/@parcel/watcher-win32-x64": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz",
+ "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==",
"cpu": [
- "ia32"
+ "x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -7132,25 +6793,16 @@
"url": "https://opencollective.com/parcel"
}
},
- "node_modules/@parcel/watcher-win32-x64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
- "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
+ "node_modules/@parcel/watcher/node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
"engines": {
- "node": ">= 10.0.0"
+ "node": ">=12"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@pkgjs/parseargs": {
@@ -7250,6 +6902,7 @@
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/@plotly/mapbox-gl/-/mapbox-gl-1.13.4.tgz",
"integrity": "sha512-sR3/Pe5LqT/fhYgp4rT4aSFf1rTsxMbGiH6Hojc7PH36ny5Bn17iVFUjpzycafETURuFbLZUfjODO8LvSI+5zQ==",
+ "deprecated": "This package is deprecated as of August 2026. plotly.js v4 uses MapLibre for map traces — see https://github.com/maplibre/maplibre-gl-js.",
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@mapbox/geojson-rewind": "^0.5.2",
@@ -7681,6 +7334,18 @@
"react-dom": ">=16.9.0"
}
},
+ "node_modules/@rc-component/color-picker/node_modules/@ant-design/fast-color": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz",
+ "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=8.x"
+ }
+ },
"node_modules/@rc-component/context": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz",
@@ -7901,9 +7566,9 @@
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.66.0.tgz",
+ "integrity": "sha512-7y4XeoUHPecjfUDRmap8U0q6bjfLAPn7uOGT8BKyqy8uNDOjsGS8rlBhOx6sZLVwwjN6hapvGl2mW9/IS6UNww==",
"cpu": [
"arm"
],
@@ -7915,9 +7580,9 @@
]
},
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.66.0.tgz",
+ "integrity": "sha512-e8WugYZXpgqrlMXLCudYVRAxAcK7AIY36JxRDwFRD38L9q62G1VsEUW6ijXxzjxYjNB8i1zo3B/ycIQ/qjLC/Q==",
"cpu": [
"arm64"
],
@@ -7928,10 +7593,24 @@
"android"
]
},
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.66.0.tgz",
+ "integrity": "sha512-6vkDCfyDjNLokh+Rt1D2KtNgFFMEPdkWha/mbtP4cSjh9xiN8T/JBS/A7nDYKjmMAXHrvR9J3AzUk2LtFbIBLg==",
+ "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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.66.0.tgz",
+ "integrity": "sha512-/mzZAX0+Q6eXxvkR0HB0RZCCrsbmFD8DpsvCQoMYABDfq/DjqkbfytuJ7GvZ4AhR/Mgis2fXbZrGqfbcK2Z5fA==",
"cpu": [
"x64"
],
@@ -7943,9 +7622,9 @@
]
},
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.66.0.tgz",
+ "integrity": "sha512-JQJx9E8tUjGqed1RevDmoHaS3V30ODdV8uyvGxlwd4K5se6bkPrMeltSybqAtpJ+ltDRfo7FORQaJG2Hd5nGog==",
"cpu": [
"arm64"
],
@@ -7957,9 +7636,9 @@
]
},
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.66.0.tgz",
+ "integrity": "sha512-GEaUQQjMaG7X1304qMgqLjzCKv6CdDirzJSS+UqUs3xhJ9X2Kjzu0JDbLoUU42FWxwmZqz+SsgU1SlQv9GNRVA==",
"cpu": [
"x64"
],
@@ -7971,9 +7650,9 @@
]
},
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.66.0.tgz",
+ "integrity": "sha512-DgmAU9N0NhDQDzHgFUocDio7zRO8jR/nY/obDGvCR8zbJ6J77Fg6X49yPuExJUpYPbC82EOaRhG969pWzUSnHQ==",
"cpu": [
"arm"
],
@@ -7985,9 +7664,9 @@
]
},
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.66.0.tgz",
+ "integrity": "sha512-zG09YZ+sk/HoHq+P9gYB0EseQ3M20RbDuKGWey6fbiMDV1LxfXvhgSW9jDaCcvFPMgfLXpimzXY+UYvA8R2mLQ==",
"cpu": [
"arm"
],
@@ -7999,9 +7678,9 @@
]
},
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.66.0.tgz",
+ "integrity": "sha512-vlArhgyXbcVc1D4WZPoiYuqJIaMiSHBj6etRXua0KlfjlZeuiQyVAPYi0/N49O4mSp3u1iGUBLEjNa8ec81XZg==",
"cpu": [
"arm64"
],
@@ -8013,9 +7692,9 @@
]
},
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.66.0.tgz",
+ "integrity": "sha512-RxJZtk0G87YPotDaRDvRqStV8cbqLSC5TroHvQHHKsqBg0ok64RxKgtXeFsa7FBcy7mb4crBrG3xK8USWIqWNA==",
"cpu": [
"arm64"
],
@@ -8026,10 +7705,24 @@
"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==",
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.66.0.tgz",
+ "integrity": "sha512-qlGcw959PrLLkZhiBxF3OZ6SQvj+OKY85mCqGNgSRs4LZlDJ6ICqmz6UdxtINdhcXYwNIbZoapQVBAmZlD2hUQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.66.0.tgz",
+ "integrity": "sha512-TvTI/7Cy0WvRA1SXgwzu6bwXBnMpmeT/ln5q7WvSpqmXy1VbmEnY8HVIDQNBiaHJvoWqRr0M4FNo7qiqtyMaFw==",
"cpu": [
"loong64"
],
@@ -8041,9 +7734,23 @@
]
},
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.66.0.tgz",
+ "integrity": "sha512-UZEDkSoDRZ50NyLj70nO6mpIK3dvs2cPTTvQb1bA/PBDmr9fvn5KiESuGfh6srd7Zi6Lt9Sv2sVrFr1QoHLvPQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.66.0.tgz",
+ "integrity": "sha512-VRbxTvagLh3HRcysx1CNq+HHcvJbSebNI6fCbk7jwmmWHkXjrSIV3igXBpRHUTFdg0pagWGu50xoSZXnrZHjVQ==",
"cpu": [
"ppc64"
],
@@ -8055,9 +7762,9 @@
]
},
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.66.0.tgz",
+ "integrity": "sha512-uWdJpvYqh0LE/i+YBL3sMX0a2kUkICENjmrPmyBmMOFc3jMrs6MSQH/pMD+VIpGEoUcr3XkHGLLfymzIqPSRRg==",
"cpu": [
"riscv64"
],
@@ -8069,9 +7776,9 @@
]
},
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.66.0.tgz",
+ "integrity": "sha512-3FUd88pxgWAjK60OgCrika1AcNG+u8zaCdWjg4TlcZTDkt5TGyci8ZU64l3OJ9ax0MEjw3P80B/3PoSQDCLwJQ==",
"cpu": [
"riscv64"
],
@@ -8083,9 +7790,9 @@
]
},
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.66.0.tgz",
+ "integrity": "sha512-qT2rMmgyej62g5AdwktK+Bt2lBu497pzWZe12TTQ5JmZjOYPnEwcPEAOSLTBxBV+6gIES75rXm219v/QkT397A==",
"cpu": [
"s390x"
],
@@ -8097,9 +7804,9 @@
]
},
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.66.0.tgz",
+ "integrity": "sha512-E46rnZgp7J84Qwe8RMlF+rgimnDo/9fuiGbUArGB0ZkMWf97O7yeSCfKls5x/YJOF0RcuSe9RctuKe1hlxICiA==",
"cpu": [
"x64"
],
@@ -8111,9 +7818,9 @@
]
},
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.66.0.tgz",
+ "integrity": "sha512-Uz1HiZe+qBzqgdPsON+z0wRx3WdATuyzwvGT6cr2VZVrqqko0ZJVfkrQJtwDMuDcAfXYdo7XucaxJ7lTLjEcgg==",
"cpu": [
"x64"
],
@@ -8124,10 +7831,38 @@
"linux"
]
},
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.66.0.tgz",
+ "integrity": "sha512-Ph7TX3Ex5qdfcb7Tc6eEPugVkRfJ501eTovO4T5029YObfpZ7dzn3RiwpFvGp50nk2QUhvBMZ0PFGlEj6pOYTg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.66.0.tgz",
+ "integrity": "sha512-nZYPEya7ZkeNxUEiF7qmNZbyLTyRN3MK5cO22KqsaoneUzXJuzBIQRw+7BEQy9LGj2O5vrCE9V3EpK06FYo1tQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.66.0.tgz",
+ "integrity": "sha512-u7aTfS3R6C4hRsqXSzaO6qLZYLVuMkHMvcWlux4IM8IJif1VCl2cgPBSo5gSNoFoStQypQP+qwq8W1IyFBf1QQ==",
"cpu": [
"arm64"
],
@@ -8139,9 +7874,9 @@
]
},
"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==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.66.0.tgz",
+ "integrity": "sha512-e+i9ueHrH1Bafb0cf0woAIVJbYcoC8vRaD+vz+Xi8w6jz9cYJSV3vyKO6ELGSZ6g4W4MJOm2WPLN4/nQf77h7w==",
"cpu": [
"ia32"
],
@@ -8152,10 +7887,24 @@
"win32"
]
},
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.66.0.tgz",
+ "integrity": "sha512-KfTCbrtr2zXTqH2E1F+uYlFTrYibxHKqPAHobpd8Kfg49cEHmuHebOidCTDRgqG1suaPFX7oejFQeGE71zWKfA==",
+ "cpu": [
+ "x64"
+ ],
+ "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",
- "integrity": "sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.66.0.tgz",
+ "integrity": "sha512-xA+neBh1bqkqaS3b48T2UHZnDsTIpb9B0h+TdBzynjF/ymCpYndhfY10RqW0qSKB1HqqQA83mINVhAdwA+mETg==",
"cpu": [
"x64"
],
@@ -8443,9 +8192,9 @@
"license": "MIT"
},
"node_modules/@testing-library/react/node_modules/@types/yargs": {
- "version": "15.0.19",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz",
- "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==",
+ "version": "15.0.20",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.20.tgz",
+ "integrity": "sha512-KIkX+/GgfFitlASYCGoSF+T4XRXhOubJLhkLVtSfsRTe9jWMmuM2g28zQ41BtPTG7TRBb2xHW+LCNVE9QR/vsg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8610,9 +8359,9 @@
}
},
"node_modules/@tybys/wasm-util": {
- "version": "0.10.2",
- "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
- "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
+ "version": "0.10.4",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz",
+ "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -8822,9 +8571,9 @@
"license": "MIT"
},
"node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"license": "MIT"
},
"node_modules/@types/geojson": {
@@ -8923,23 +8672,10 @@
"pretty-format": "^30.0.0"
}
},
- "node_modules/@types/jest/node_modules/@jest/expect-utils": {
- "version": "30.1.1",
- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.1.1.tgz",
- "integrity": "sha512-5YUHr27fpJ64dnvtu+tt11ewATynrHkGYD+uSFgRr8V2eFJis/vEXgToyLwccIwqBihVfz9jwio+Zr1ab1Zihw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/get-type": "30.1.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/@types/jest/node_modules/@jest/schemas": {
- "version": "30.0.5",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
- "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8949,29 +8685,10 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@types/jest/node_modules/@jest/types": {
- "version": "30.0.5",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz",
- "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/pattern": "30.0.1",
- "@jest/schemas": "30.0.5",
- "@types/istanbul-lib-coverage": "^2.0.6",
- "@types/istanbul-reports": "^3.0.4",
- "@types/node": "*",
- "@types/yargs": "^17.0.33",
- "chalk": "^4.1.2"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/@types/jest/node_modules/@sinclair/typebox": {
- "version": "0.34.41",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz",
- "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==",
+ "version": "0.34.49",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
+ "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
"dev": true,
"license": "MIT"
},
@@ -8988,161 +8705,22 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "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": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@types/jest/node_modules/expect": {
- "version": "30.1.1",
- "resolved": "https://registry.npmjs.org/expect/-/expect-30.1.1.tgz",
- "integrity": "sha512-OKe7cdic4qbfWd/CcgwJvvCrNX2KWfuMZee9AfJHL1gTYmvqjBjZG1a2NwfhspBzxzlXwsN75WWpKTYfsJpBxg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/expect-utils": "30.1.1",
- "@jest/get-type": "30.1.0",
- "jest-matcher-utils": "30.1.1",
- "jest-message-util": "30.1.0",
- "jest-mock": "30.0.5",
- "jest-util": "30.0.5"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@types/jest/node_modules/jest-diff": {
- "version": "30.1.1",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.1.1.tgz",
- "integrity": "sha512-LUU2Gx8EhYxpdzTR6BmjL1ifgOAQJQELTHOiPv9KITaKjZvJ9Jmgigx01tuZ49id37LorpGc9dPBPlXTboXScw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/diff-sequences": "30.0.1",
- "@jest/get-type": "30.1.0",
- "chalk": "^4.1.2",
- "pretty-format": "30.0.5"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@types/jest/node_modules/jest-matcher-utils": {
- "version": "30.1.1",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.1.1.tgz",
- "integrity": "sha512-SuH2QVemK48BNTqReti6FtjsMPFsSOD/ZzRxU1TttR7RiRsRSe78d03bb4Cx6D4bQC/80Q8U4VnaaAH9FlbZ9w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/get-type": "30.1.0",
- "chalk": "^4.1.2",
- "jest-diff": "30.1.1",
- "pretty-format": "30.0.5"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@types/jest/node_modules/jest-message-util": {
- "version": "30.1.0",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.1.0.tgz",
- "integrity": "sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@jest/types": "30.0.5",
- "@types/stack-utils": "^2.0.3",
- "chalk": "^4.1.2",
- "graceful-fs": "^4.2.11",
- "micromatch": "^4.0.8",
- "pretty-format": "30.0.5",
- "slash": "^3.0.0",
- "stack-utils": "^2.0.6"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@types/jest/node_modules/jest-mock": {
- "version": "30.0.5",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz",
- "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/types": "30.0.5",
- "@types/node": "*",
- "jest-util": "30.0.5"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@types/jest/node_modules/jest-util": {
- "version": "30.0.5",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz",
- "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/types": "30.0.5",
- "@types/node": "*",
- "chalk": "^4.1.2",
- "ci-info": "^4.2.0",
- "graceful-fs": "^4.2.11",
- "picomatch": "^4.0.2"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/@types/jest/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
"node_modules/@types/jest/node_modules/pretty-format": {
- "version": "30.0.5",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz",
- "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/schemas": "30.0.5",
- "ansi-styles": "^5.2.0",
- "react-is": "^18.3.1"
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@types/jest/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/jsdom": {
"version": "21.1.7",
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz",
@@ -9247,12 +8825,12 @@
"optional": true
},
"node_modules/@types/react": {
- "version": "19.1.12",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
- "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz",
+ "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==",
"license": "MIT",
"dependencies": {
- "csstype": "^3.0.2"
+ "csstype": "^3.2.2"
}
},
"node_modules/@types/react-datepicker": {
@@ -9424,14 +9002,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.60.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz",
- "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==",
+ "version": "8.72.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.1.tgz",
+ "integrity": "sha512-lC/t6eMCZZQAAKZEYGL6RDVSuFehpdmMT6h+nJIjIa6oylN/PwDgDVtEIbB7O9xFfZYtZAYLoLlDNWKJhtF+uQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.60.0",
- "@typescript-eslint/visitor-keys": "8.60.0"
+ "@typescript-eslint/types": "8.72.1",
+ "@typescript-eslint/visitor-keys": "8.72.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -9459,9 +9037,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.60.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz",
- "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==",
+ "version": "8.72.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.1.tgz",
+ "integrity": "sha512-LmE5IhQet1BWOWEXqBrVtBPsMAOQ6Nzk35d+WLh3mk4EUF33sqlfxjio1MdYeSvKSG5skactKKuixBIKXhL4Rw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -9500,6 +9078,38 @@
"typescript": ">=4.8.4 <6.1.0"
}
},
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/types": {
+ "version": "8.60.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz",
+ "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.60.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz",
+ "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.60.0",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
"node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -9511,16 +9121,29 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "5.0.6",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
- "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
- "node": "18 || 20 || >=22"
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
@@ -9563,15 +9186,15 @@
"typescript": ">=4.8.4 <6.1.0"
}
},
- "node_modules/@typescript-eslint/visitor-keys": {
+ "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": {
"version": "8.60.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz",
- "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz",
+ "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.60.0",
- "eslint-visitor-keys": "^5.0.0"
+ "@typescript-eslint/visitor-keys": "8.60.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -9581,8 +9204,71 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
- "version": "5.0.1",
+ "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": {
+ "version": "8.60.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz",
+ "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.60.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz",
+ "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.60.0",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.72.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.1.tgz",
+ "integrity": "sha512-SB8xqipSzZHUePCCLNeYxqnxq83X61/qzNhjQGbTfjxGZIeOEP7tBgWYX05Tcd6CgE02WdTJnh5b8kMZJ/+bnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.72.1",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"dev": true,
@@ -9598,6 +9284,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "deprecated": "Potential CWE-502 - Update to 1.3.1 or higher",
"license": "ISC"
},
"node_modules/@unrs/resolver-binding-android-arm-eabi": {
@@ -9913,9 +9600,9 @@
}
},
"node_modules/@vitest/coverage-v8": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz",
- "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==",
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.7.tgz",
+ "integrity": "sha512-NEGWJS2XNu2PfRLQwOO3CTKj1tTETxNBdk454vDxVBhxJYhPaA/eS0nAI0c+1El1P7a60z8+i+ZrQoGESweGKg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9937,8 +9624,8 @@
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@vitest/browser": "3.2.4",
- "vitest": "3.2.4"
+ "@vitest/browser": "3.2.7",
+ "vitest": "3.2.7"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@@ -9946,125 +9633,16 @@
}
}
},
- "node_modules/@vitest/coverage-v8/node_modules/balanced-match": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
- "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "18 || 20 || >=22"
- }
- },
- "node_modules/@vitest/coverage-v8/node_modules/brace-expansion": {
- "version": "5.0.6",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
- "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^4.0.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- }
- },
- "node_modules/@vitest/coverage-v8/node_modules/glob": {
- "version": "10.5.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
- "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
- "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@vitest/coverage-v8/node_modules/glob/node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@vitest/coverage-v8/node_modules/glob/node_modules/brace-expansion": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
- "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/@vitest/coverage-v8/node_modules/glob/node_modules/minimatch": {
- "version": "9.0.9",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
- "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.2"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@vitest/coverage-v8/node_modules/minimatch": {
- "version": "10.2.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
- "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "brace-expansion": "^5.0.5"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@vitest/coverage-v8/node_modules/test-exclude": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz",
- "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@istanbuljs/schema": "^0.1.2",
- "glob": "^10.4.1",
- "minimatch": "^10.2.2"
- },
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/@vitest/expect": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
- "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz",
+ "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/chai": "^5.2.2",
- "@vitest/spy": "3.2.4",
- "@vitest/utils": "3.2.4",
+ "@vitest/spy": "3.2.7",
+ "@vitest/utils": "3.2.7",
"chai": "^5.2.0",
"tinyrainbow": "^2.0.0"
},
@@ -10073,13 +9651,13 @@
}
},
"node_modules/@vitest/mocker": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz",
- "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz",
+ "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "3.2.4",
+ "@vitest/spy": "3.2.7",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.17"
},
@@ -10100,9 +9678,9 @@
}
},
"node_modules/@vitest/pretty-format": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
- "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz",
+ "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10113,13 +9691,13 @@
}
},
"node_modules/@vitest/runner": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz",
- "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz",
+ "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "3.2.4",
+ "@vitest/utils": "3.2.7",
"pathe": "^2.0.3",
"strip-literal": "^3.0.0"
},
@@ -10128,13 +9706,13 @@
}
},
"node_modules/@vitest/snapshot": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz",
- "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz",
+ "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "3.2.4",
+ "@vitest/pretty-format": "3.2.7",
"magic-string": "^0.30.17",
"pathe": "^2.0.3"
},
@@ -10143,9 +9721,9 @@
}
},
"node_modules/@vitest/spy": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
- "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz",
+ "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10156,13 +9734,13 @@
}
},
"node_modules/@vitest/ui": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-3.2.4.tgz",
- "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==",
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-3.2.7.tgz",
+ "integrity": "sha512-eVtcpJXGhS0GjMuHROfbXLhlxooyUcuip8GNzzjDD5jzafZqzanJH4W3VGmUxHNx4fv6qQGUGJHRpGUdj+9D6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "3.2.4",
+ "@vitest/utils": "3.2.7",
"fflate": "^0.8.2",
"flatted": "^3.3.3",
"pathe": "^2.0.3",
@@ -10174,17 +9752,17 @@
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "vitest": "3.2.4"
+ "vitest": "3.2.7"
}
},
"node_modules/@vitest/utils": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
- "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz",
+ "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "3.2.4",
+ "@vitest/pretty-format": "3.2.7",
"loupe": "^3.1.4",
"tinyrainbow": "^2.0.0"
},
@@ -10348,9 +9926,9 @@
}
},
"node_modules/@xmldom/xmldom": {
- "version": "0.8.11",
- "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
- "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==",
+ "version": "0.8.15",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.15.tgz",
+ "integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@@ -10375,9 +9953,9 @@
"license": "MIT"
},
"node_modules/acorn": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
- "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "version": "8.19.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz",
+ "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -10418,13 +9996,15 @@
}
},
"node_modules/agent-base": {
- "version": "7.1.4",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
- "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
- "dev": true,
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
"engines": {
- "node": ">= 14"
+ "node": ">= 6.0.0"
}
},
"node_modules/ajv": {
@@ -10472,6 +10052,21 @@
"ajv": "^8.8.2"
}
},
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -10567,6 +10162,27 @@
"react-dom": ">=16.9.0"
}
},
+ "node_modules/antd/node_modules/@ant-design/colors": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz",
+ "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/fast-color": "^2.0.6"
+ }
+ },
+ "node_modules/antd/node_modules/@ant-design/fast-color": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz",
+ "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=8.x"
+ }
+ },
"node_modules/antd/node_modules/@ant-design/icons": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz",
@@ -10829,9 +10445,9 @@
}
},
"node_modules/asn1.js/node_modules/bn.js": {
- "version": "4.12.2",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
- "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
+ "version": "4.12.5",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz",
+ "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==",
"license": "MIT"
},
"node_modules/assert": {
@@ -10947,9 +10563,9 @@
}
},
"node_modules/axios": {
- "version": "1.20.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz",
- "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==",
+ "version": "1.21.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.21.0.tgz",
+ "integrity": "sha512-YCzXMB53Q9PZBsh7yN7wtapit4WepwcHS2fP8QhGsBxSz5YtgYobV7GGlWlAJi+RxfU77GQTQi2DSLUma42EYA==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.16.0",
@@ -10971,31 +10587,6 @@
"axios": ">= 0.17.0"
}
},
- "node_modules/axios/node_modules/agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
- "license": "MIT",
- "dependencies": {
- "debug": "4"
- },
- "engines": {
- "node": ">= 6.0.0"
- }
- },
- "node_modules/axios/node_modules/https-proxy-agent": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
- "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
- "license": "MIT",
- "dependencies": {
- "agent-base": "6",
- "debug": "4"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -11045,17 +10636,54 @@
"node": ">=8"
}
},
- "node_modules/babel-plugin-jest-hoist": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
- "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
+ "node_modules/babel-plugin-istanbul/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dev": true,
- "license": "MIT",
+ "license": "ISC",
"dependencies": {
- "@babel/template": "^7.3.3",
- "@babel/types": "^7.3.3",
- "@types/babel__core": "^7.1.14",
- "@types/babel__traverse": "^7.0.6"
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-jest-hoist": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
+ "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.3.3",
+ "@babel/types": "^7.3.3",
+ "@types/babel__core": "^7.1.14",
+ "@types/babel__traverse": "^7.0.6"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
@@ -11207,9 +10835,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.10.32",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz",
- "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==",
+ "version": "2.11.34",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.34.tgz",
+ "integrity": "sha512-Aqr8mA1kIvyJ3UY5YAY7ib8LSo1DmDMbMnVqGPtsKW1LxaFQFwn9Eb3yvq2fUKJsRvaXjnAV7R5osqETapzNiA==",
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.cjs"
@@ -11253,9 +10881,9 @@
"license": "MIT"
},
"node_modules/bn.js": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz",
- "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==",
+ "version": "5.2.5",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.5.tgz",
+ "integrity": "sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==",
"license": "MIT"
},
"node_modules/boolbase": {
@@ -11268,6 +10896,7 @@
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz",
"integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==",
+ "deprecated": "This version of Bootstrap is no longer supported. Please upgrade to the latest version.",
"funding": [
{
"type": "github",
@@ -11285,9 +10914,10 @@
}
},
"node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "version": "1.1.21",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz",
+ "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@@ -11373,30 +11003,29 @@
}
},
"node_modules/browserify-sign": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz",
- "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==",
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.6.tgz",
+ "integrity": "sha512-sd+Q65fjlWCYWtZKXiKfrUc8d+4jtp/8f0W2NkwzLtoW4bI6UDnWusLWIurHnmurW0XShIRxpwiOX4EoPtXUAg==",
"license": "ISC",
"dependencies": {
- "bn.js": "^5.2.1",
- "browserify-rsa": "^4.1.0",
+ "bn.js": "^5.2.3",
+ "browserify-rsa": "^4.1.1",
"create-hash": "^1.2.0",
"create-hmac": "^1.1.7",
- "elliptic": "^6.5.5",
- "hash-base": "~3.0",
+ "elliptic": "^6.6.1",
"inherits": "^2.0.4",
- "parse-asn1": "^5.1.7",
+ "parse-asn1": "^5.1.9",
"readable-stream": "^2.3.8",
"safe-buffer": "^5.2.1"
},
"engines": {
- "node": ">= 0.12"
+ "node": ">= 0.10"
}
},
"node_modules/browserslist": {
- "version": "4.28.2",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
- "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "version": "4.29.5",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.5.tgz",
+ "integrity": "sha512-khTmKu5NGYTotqcYo8Bew2k4RAE9PuXRSFRXG2lQBrCEQHyeQ/vX6gKMxfokwywBsLiXZMnHsmWMG1pnO8J7uQ==",
"funding": [
{
"type": "opencollective",
@@ -11413,11 +11042,11 @@
],
"license": "MIT",
"dependencies": {
- "baseline-browser-mapping": "^2.10.12",
- "caniuse-lite": "^1.0.30001782",
- "electron-to-chromium": "^1.5.328",
- "node-releases": "^2.0.36",
- "update-browserslist-db": "^1.2.3"
+ "baseline-browser-mapping": "^2.11.33",
+ "caniuse-lite": "^1.0.30001819",
+ "electron-to-chromium": "^1.5.464",
+ "node-releases": "^2.0.60",
+ "update-browserslist-db": "^1.3.4"
},
"bin": {
"browserslist": "cli.js"
@@ -11598,9 +11227,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001793",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
- "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==",
+ "version": "1.0.30001820",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001820.tgz",
+ "integrity": "sha512-gfWD6MxqVCqAOwmODfZf7DchWvlrA+SRdNX7ceLfg3xRqe5ssjjpF7EgslC/0tiLiNTajdaNN/q8c/5u/7Gz8w==",
"funding": [
{
"type": "opencollective",
@@ -11801,10 +11430,9 @@
}
},
"node_modules/ci-info": {
- "version": "3.9.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
- "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
- "dev": true,
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
+ "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
"funding": [
{
"type": "github",
@@ -11830,9 +11458,9 @@
}
},
"node_modules/cjs-module-lexer": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
- "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.3.0.tgz",
+ "integrity": "sha512-lmNyBzi6iYiqyrExTctfmjd8tInx+dHu9PBomTfH5RXS53c27JMFJgzNv5TyAnm3PgGf+MLy0+Ws41/HlVxdKQ==",
"license": "MIT"
},
"node_modules/clamp": {
@@ -11892,6 +11520,52 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/cli-truncate/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/cli-truncate/node_modules/string-width": {
+ "version": "8.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
+ "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.5.0",
+ "strip-ansi": "^7.1.2"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-truncate/node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
"node_modules/cli-width": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
@@ -12125,9 +11799,9 @@
}
},
"node_modules/colord": {
- "version": "2.9.3",
- "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
- "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/colord/-/colord-2.10.0.tgz",
+ "integrity": "sha512-AidJptpBJmjTclAp9BkLwJi0T93fo5epJnbaZslpg6QVzpHjAiveF55mE9AcUJiGMqRHgMDY8soMsQtuNYMHfw==",
"dev": true,
"license": "MIT"
},
@@ -12175,6 +11849,7 @@
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
"license": "MIT"
},
"node_modules/concat-stream": {
@@ -12277,9 +11952,9 @@
}
},
"node_modules/cosmiconfig/node_modules/yaml": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
- "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
+ "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
"license": "ISC",
"engines": {
"node": ">= 6"
@@ -12314,9 +11989,9 @@
}
},
"node_modules/create-ecdh/node_modules/bn.js": {
- "version": "4.12.2",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
- "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
+ "version": "4.12.5",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz",
+ "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==",
"license": "MIT"
},
"node_modules/create-hash": {
@@ -12480,6 +12155,7 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+ "deprecated": "Active development of CryptoJS has been discontinued. This library is no longer maintained.",
"license": "MIT"
},
"node_modules/css-box-model": {
@@ -12918,9 +12594,9 @@
}
},
"node_modules/d3-format": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
- "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
+ "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
"license": "ISC",
"engines": {
"node": ">=12"
@@ -13439,17 +13115,12 @@
"license": "MIT"
},
"node_modules/detect-libc": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
- "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
- "dev": true,
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
- "optional": true,
- "bin": {
- "detect-libc": "bin/detect-libc.js"
- },
"engines": {
- "node": ">=0.10"
+ "node": ">=8"
}
},
"node_modules/detect-newline": {
@@ -13476,16 +13147,6 @@
"node": ">=0.3.1"
}
},
- "node_modules/diff-sequences": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
- "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
"node_modules/diffie-hellman": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
@@ -13498,9 +13159,9 @@
}
},
"node_modules/diffie-hellman/node_modules/bn.js": {
- "version": "4.12.2",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
- "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
+ "version": "4.12.5",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz",
+ "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==",
"license": "MIT"
},
"node_modules/dir-glob": {
@@ -13588,9 +13249,9 @@
}
},
"node_modules/dompurify": {
- "version": "3.4.13",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz",
- "integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==",
+ "version": "3.4.17",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz",
+ "integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@@ -13683,9 +13344,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.364",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz",
- "integrity": "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==",
+ "version": "1.5.468",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.468.tgz",
+ "integrity": "sha512-8XdEp3sIqO+PzmVPxhzqHq/3+yCxp44m27cm2WY1sRn3ZhwKyKmqSi2hx6gEhmnOXmh8NUuYE00z6Onzn5m6DA==",
"license": "ISC"
},
"node_modules/elementary-circuits-directed-graph": {
@@ -13713,9 +13374,9 @@
}
},
"node_modules/elliptic/node_modules/bn.js": {
- "version": "4.12.2",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
- "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
+ "version": "4.12.5",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz",
+ "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==",
"license": "MIT"
},
"node_modules/emittery": {
@@ -14656,9 +14317,9 @@
}
},
"node_modules/eslint-plugin-vitest/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz",
+ "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -14679,13 +14340,13 @@
}
},
"node_modules/eslint-plugin-vitest/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
- "brace-expansion": "^2.0.1"
+ "brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
@@ -14764,9 +14425,9 @@
}
},
"node_modules/eslint/node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -14884,22 +14545,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/eslint/node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/eslint/node_modules/p-locate": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
@@ -15055,7 +14700,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
- "dev": true,
"license": "MIT"
},
"node_modules/events": {
@@ -15100,30 +14744,6 @@
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
- "node_modules/execa/node_modules/mimic-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/execa/node_modules/onetime": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
- "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
- "license": "MIT",
- "dependencies": {
- "mimic-fn": "^2.1.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/execa/node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -15140,20 +14760,20 @@
}
},
"node_modules/expect": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
- "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
- "dev": true,
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-30.5.2.tgz",
+ "integrity": "sha512-0LNuMvs8/5mRYhnCR4k+lGV7fqPMs6Bnksda4S3zsCUmdxBpAn4zU4NNzXdq1pMKe/H4W5t/zVIDSJ/H3e0vDA==",
"license": "MIT",
"dependencies": {
- "@jest/expect-utils": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "jest-matcher-utils": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0"
+ "@jest/expect-utils": "30.5.2",
+ "@jest/get-type": "30.5.0",
+ "jest-matcher-utils": "30.5.2",
+ "jest-message-util": "30.5.1",
+ "jest-mock": "30.5.2",
+ "jest-util": "30.5.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/expect-type": {
@@ -15166,6 +14786,134 @@
"node": ">=12.0.0"
}
},
+ "node_modules/expect/node_modules/@jest/schemas": {
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/expect/node_modules/@jest/types": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/istanbul-reports": "^3.0.4",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.33",
+ "chalk": "^4.1.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/expect/node_modules/@sinclair/typebox": {
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
+ "license": "MIT"
+ },
+ "node_modules/expect/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/expect/node_modules/jest-message-util": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz",
+ "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@jest/types": "30.5.1",
+ "@types/stack-utils": "^2.0.3",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "jest-util": "30.5.1",
+ "picomatch": "^4.0.3",
+ "pretty-format": "30.5.1",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/expect/node_modules/jest-mock": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.5.2.tgz",
+ "integrity": "sha512-KTHb37Fhj8xY8qPFvTFaFJHcdsumQp+ExsUlgoX232Agmc706gagBs53+CYvulV7MVQo9AgXOUxsqzzVBoJlrA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/expect-utils": "30.5.2",
+ "@jest/types": "30.5.1",
+ "@types/node": "*",
+ "jest-util": "30.5.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/expect/node_modules/jest-util": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.5.1",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "graceful-fs": "^4.2.11",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/expect/node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/expect/node_modules/pretty-format": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
"node_modules/ext": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
@@ -15308,9 +15056,9 @@
}
},
"node_modules/fast-uri": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
- "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "version": "3.1.9",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz",
+ "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==",
"funding": [
{
"type": "github",
@@ -15359,9 +15107,9 @@
"license": "MIT"
},
"node_modules/fflate": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
- "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
+ "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
"license": "MIT"
},
"node_modules/file-entry-cache": {
@@ -15449,9 +15197,9 @@
}
},
"node_modules/follow-redirects": {
- "version": "1.16.0",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
- "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.2.tgz",
+ "integrity": "sha512-/kVP2U8z596Ia0gwFrwyIeo74ta5aVmSeU1FfAUMAmcEP5EP7zydmag3SLiN64NaRKef3lxsq7i/g7ltJQDGow==",
"funding": [
{
"type": "individual",
@@ -15579,12 +15327,14 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
"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==",
+ "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -15828,21 +15578,21 @@
}
},
"node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "deprecated": "Glob versions prior to v9 are no longer supported",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
},
- "engines": {
- "node": "*"
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -15867,6 +15617,30 @@
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"license": "BSD-2-Clause"
},
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz",
+ "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/global-modules": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
@@ -15880,7 +15654,7 @@
"node": ">=6"
}
},
- "node_modules/global-prefix": {
+ "node_modules/global-modules/node_modules/global-prefix": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
"integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
@@ -15895,7 +15669,14 @@
"node": ">=6"
}
},
- "node_modules/global-prefix/node_modules/which": {
+ "node_modules/global-modules/node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/global-modules/node_modules/which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
@@ -15908,6 +15689,44 @@
"which": "bin/which"
}
},
+ "node_modules/global-prefix": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz",
+ "integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==",
+ "license": "MIT",
+ "dependencies": {
+ "ini": "^4.1.3",
+ "kind-of": "^6.0.3",
+ "which": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/global-prefix/node_modules/isexe": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz",
+ "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/global-prefix/node_modules/which": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
+ "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^3.1.1"
+ },
+ "bin": {
+ "node-which": "bin/which.js"
+ },
+ "engines": {
+ "node": "^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
@@ -16639,18 +16458,27 @@
"node": ">= 14"
}
},
- "node_modules/https-proxy-agent": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
- "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "node_modules/http-proxy-agent/node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"dev": true,
"license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
"dependencies": {
- "agent-base": "^7.1.2",
+ "agent-base": "6",
"debug": "4"
},
"engines": {
- "node": ">= 14"
+ "node": ">= 6"
}
},
"node_modules/human-signals": {
@@ -16731,9 +16559,9 @@
}
},
"node_modules/immutable": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz",
- "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz",
+ "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==",
"dev": true,
"license": "MIT"
},
@@ -16805,6 +16633,7 @@
"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.",
+ "dev": true,
"license": "ISC",
"dependencies": {
"once": "^1.3.0",
@@ -16818,11 +16647,13 @@
"license": "ISC"
},
"node_modules/ini": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
- "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
- "dev": true,
- "license": "ISC"
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz",
+ "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==",
+ "license": "ISC",
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
},
"node_modules/inline-style-parser": {
"version": "0.1.1",
@@ -17062,7 +16893,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -17105,6 +16935,22 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-fullwidth-code-point": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
+ "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-generator-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
@@ -17136,7 +16982,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@@ -17561,15 +17406,15 @@
}
},
"node_modules/jest": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz",
- "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-30.5.2.tgz",
+ "integrity": "sha512-3gnpdBIza8ijCl3iMV9ChE3hSt1+46865qqod863gQtJr1DixOkAU5DoGbi3u+XlRcw++BhZQMu/y4xBdHBvSQ==",
"license": "MIT",
"dependencies": {
- "@jest/core": "30.4.2",
- "@jest/types": "30.4.1",
+ "@jest/core": "30.5.2",
+ "@jest/types": "30.5.1",
"import-local": "^3.2.0",
- "jest-cli": "30.4.2"
+ "jest-cli": "30.5.2"
},
"bin": {
"jest": "bin/jest.js"
@@ -17587,36 +17432,23 @@
}
},
"node_modules/jest-changed-files": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz",
- "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.5.1.tgz",
+ "integrity": "sha512-0+bvMM/ENhDI29Z8q1r4HxiDIi4G5tnBmSw4esfPQoj9q8Nik7KIyuxHkTVnnniJQf05SxpGaKDQ+4h28ynGPg==",
"license": "MIT",
"dependencies": {
"execa": "^5.1.1",
- "jest-util": "30.4.1",
+ "jest-util": "30.5.1",
"p-limit": "^3.1.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-changed-files/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/jest-changed-files/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -17626,13 +17458,13 @@
}
},
"node_modules/jest-changed-files/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -17644,42 +17476,18 @@
}
},
"node_modules/jest-changed-files/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
- "node_modules/jest-changed-files/node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/jest-changed-files/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/jest-changed-files/node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
@@ -17690,25 +17498,10 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-changed-files/node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/jest-changed-files/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -17718,28 +17511,28 @@
}
},
"node_modules/jest-circus": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz",
- "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.5.2.tgz",
+ "integrity": "sha512-vffFgjs5JSJ9q4ds1vNW3klKYPJfYfIY61LT/zaZgvTeASJOWZUt8LBfHgIcV4rwxlPBLl/ePBGccHEclS4PlQ==",
"license": "MIT",
"dependencies": {
- "@jest/environment": "30.4.1",
- "@jest/expect": "30.4.1",
- "@jest/test-result": "30.4.1",
- "@jest/types": "30.4.1",
+ "@jest/environment": "30.5.2",
+ "@jest/expect": "30.5.2",
+ "@jest/test-result": "30.5.2",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
"co": "^4.6.0",
"dedent": "^1.6.0",
"is-generator-fn": "^2.1.0",
- "jest-each": "30.4.1",
- "jest-matcher-utils": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-runtime": "30.4.2",
- "jest-snapshot": "30.4.1",
- "jest-util": "30.4.1",
+ "jest-each": "30.5.2",
+ "jest-matcher-utils": "30.5.2",
+ "jest-message-util": "30.5.1",
+ "jest-runtime": "30.5.2",
+ "jest-snapshot": "30.5.2",
+ "jest-util": "30.5.1",
"p-limit": "^3.1.0",
- "pretty-format": "30.4.1",
+ "pretty-format": "30.5.1",
"pure-rand": "^7.0.0",
"slash": "^3.0.0",
"stack-utils": "^2.0.6"
@@ -17748,89 +17541,55 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-circus/node_modules/@jest/diff-sequences": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz",
- "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==",
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/jest-circus/node_modules/@jest/environment": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz",
- "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.5.2.tgz",
+ "integrity": "sha512-nwFPOUMbmsjNNRCpnJpB9HbSZu4C07wDDds++5j+j1MezzPLxMPDdbdUVjr2o4RULDOValnDd70taPFI1EtxAg==",
"license": "MIT",
"dependencies": {
- "@jest/fake-timers": "30.4.1",
- "@jest/types": "30.4.1",
+ "@jest/fake-timers": "30.5.2",
+ "@jest/types": "30.5.1",
"@types/node": "*",
- "jest-mock": "30.4.1"
+ "jest-mock": "30.5.2"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-circus/node_modules/@jest/expect": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz",
- "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==",
- "license": "MIT",
- "dependencies": {
- "expect": "30.4.1",
- "jest-snapshot": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-circus/node_modules/@jest/expect-utils": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz",
- "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.5.2.tgz",
+ "integrity": "sha512-2E+1Pg6jJxbRjLNLpciC71iri/3qsKJL222/aXJEFwC0VdUSvfS+JZQQLEVjUYYc8t8pQu2MGE3cF2+bNTYRsg==",
"license": "MIT",
"dependencies": {
- "@jest/get-type": "30.1.0"
+ "expect": "30.5.2",
+ "jest-snapshot": "30.5.2"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-circus/node_modules/@jest/fake-timers": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz",
- "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.5.2.tgz",
+ "integrity": "sha512-GKk0h0tKT5SpDPxDhPg3r9mm5s8/YCBzVNMOGdRiQXwUiMsjU10mVPlMaQstH6/cu50vOb8N9oQUMETHuwwxKw==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@sinonjs/fake-timers": "^15.4.0",
"@types/node": "*",
- "jest-message-util": "30.4.1",
- "jest-mock": "30.4.1",
- "jest-util": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-circus/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
+ "jest-message-util": "30.5.1",
+ "jest-mock": "30.5.2",
+ "jest-util": "30.5.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-circus/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -17839,39 +17598,14 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-circus/node_modules/@jest/transform": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz",
- "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.27.4",
- "@jest/types": "30.4.1",
- "@jridgewell/trace-mapping": "^0.3.25",
- "babel-plugin-istanbul": "^7.0.1",
- "chalk": "^4.1.2",
- "convert-source-map": "^2.0.0",
- "fast-json-stable-stringify": "^2.1.0",
- "graceful-fs": "^4.2.11",
- "jest-haste-map": "30.4.1",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
- "pirates": "^4.0.7",
- "slash": "^3.0.0",
- "write-file-atomic": "^5.0.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/jest-circus/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -17883,9 +17617,9 @@
}
},
"node_modules/jest-circus/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
"node_modules/jest-circus/node_modules/@sinonjs/fake-timers": {
@@ -17909,210 +17643,49 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/jest-circus/node_modules/babel-plugin-istanbul": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz",
- "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==",
- "license": "BSD-3-Clause",
- "workspaces": [
- "test/babel-8"
- ],
+ "node_modules/jest-circus/node_modules/jest-message-util": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz",
+ "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==",
+ "license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@istanbuljs/load-nyc-config": "^1.0.0",
- "@istanbuljs/schema": "^0.1.3",
- "istanbul-lib-instrument": "^6.0.2",
- "test-exclude": "^6.0.0"
+ "@babel/code-frame": "^7.27.1",
+ "@jest/types": "30.5.1",
+ "@types/stack-utils": "^2.0.3",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "jest-util": "30.5.1",
+ "picomatch": "^4.0.3",
+ "pretty-format": "30.5.1",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
},
"engines": {
- "node": ">=12"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-circus/node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/jest-circus/node_modules/expect": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz",
- "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==",
- "license": "MIT",
- "dependencies": {
- "@jest/expect-utils": "30.4.1",
- "@jest/get-type": "30.1.0",
- "jest-matcher-utils": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-mock": "30.4.1",
- "jest-util": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-circus/node_modules/istanbul-lib-instrument": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
- "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@babel/core": "^7.23.9",
- "@babel/parser": "^7.23.9",
- "@istanbuljs/schema": "^0.1.3",
- "istanbul-lib-coverage": "^3.2.0",
- "semver": "^7.5.4"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/jest-circus/node_modules/jest-diff": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz",
- "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==",
- "license": "MIT",
- "dependencies": {
- "@jest/diff-sequences": "30.4.0",
- "@jest/get-type": "30.1.0",
- "chalk": "^4.1.2",
- "pretty-format": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-circus/node_modules/jest-haste-map": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz",
- "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==",
- "license": "MIT",
- "dependencies": {
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "anymatch": "^3.1.3",
- "fb-watchman": "^2.0.2",
- "graceful-fs": "^4.2.11",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
- "jest-worker": "30.4.1",
- "picomatch": "^4.0.3",
- "walker": "^1.0.8"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "optionalDependencies": {
- "fsevents": "^2.3.3"
- }
- },
- "node_modules/jest-circus/node_modules/jest-matcher-utils": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz",
- "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==",
- "license": "MIT",
- "dependencies": {
- "@jest/get-type": "30.1.0",
- "chalk": "^4.1.2",
- "jest-diff": "30.4.1",
- "pretty-format": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-circus/node_modules/jest-message-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz",
- "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@jest/types": "30.4.1",
- "@types/stack-utils": "^2.0.3",
- "chalk": "^4.1.2",
- "graceful-fs": "^4.2.11",
- "jest-util": "30.4.1",
- "picomatch": "^4.0.3",
- "pretty-format": "30.4.1",
- "slash": "^3.0.0",
- "stack-utils": "^2.0.6"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-circus/node_modules/jest-mock": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz",
- "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==",
+ "node_modules/jest-circus/node_modules/jest-mock": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.5.2.tgz",
+ "integrity": "sha512-KTHb37Fhj8xY8qPFvTFaFJHcdsumQp+ExsUlgoX232Agmc706gagBs53+CYvulV7MVQo9AgXOUxsqzzVBoJlrA==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/expect-utils": "30.5.2",
+ "@jest/types": "30.5.1",
"@types/node": "*",
- "jest-util": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-circus/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-circus/node_modules/jest-snapshot": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz",
- "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==",
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.27.4",
- "@babel/generator": "^7.27.5",
- "@babel/plugin-syntax-jsx": "^7.27.1",
- "@babel/plugin-syntax-typescript": "^7.27.1",
- "@babel/types": "^7.27.3",
- "@jest/expect-utils": "30.4.1",
- "@jest/get-type": "30.1.0",
- "@jest/snapshot-utils": "30.4.1",
- "@jest/transform": "30.4.1",
- "@jest/types": "30.4.1",
- "babel-preset-current-node-syntax": "^1.2.0",
- "chalk": "^4.1.2",
- "expect": "30.4.1",
- "graceful-fs": "^4.2.11",
- "jest-diff": "30.4.1",
- "jest-matcher-utils": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-util": "30.4.1",
- "pretty-format": "30.4.1",
- "semver": "^7.7.2",
- "synckit": "^0.11.8"
+ "jest-util": "30.5.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-circus/node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
@@ -18123,41 +17696,10 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-circus/node_modules/jest-worker": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz",
- "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "@ungap/structured-clone": "^1.3.0",
- "jest-util": "30.4.1",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.1.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-circus/node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/jest-circus/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -18167,15 +17709,15 @@
}
},
"node_modules/jest-circus/node_modules/pretty-format": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz",
- "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
"license": "MIT",
"dependencies": {
- "@jest/schemas": "30.4.1",
- "ansi-styles": "^5.2.0",
- "react-is-18": "npm:react-is@^18.3.1",
- "react-is-19": "npm:react-is@^19.2.5"
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
@@ -18197,49 +17739,21 @@
],
"license": "MIT"
},
- "node_modules/jest-circus/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
- "node_modules/jest-circus/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==",
- "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/jest-cli": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz",
- "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.5.2.tgz",
+ "integrity": "sha512-YYYxUUVjFgPvgEleKNKMaYGIFgR3l3832Cl0kRL/oCiDZ03v7wImUtquHl2OOBCCNgprjZgBN5bczS/JklPBDg==",
"license": "MIT",
"dependencies": {
- "@jest/core": "30.4.2",
- "@jest/test-result": "30.4.1",
- "@jest/types": "30.4.1",
+ "@jest/core": "30.5.2",
+ "@jest/test-result": "30.5.2",
+ "@jest/types": "30.5.1",
"chalk": "^4.1.2",
"exit-x": "^0.2.2",
"import-local": "^3.2.0",
- "jest-config": "30.4.2",
- "jest-util": "30.4.1",
- "jest-validate": "30.4.1",
+ "jest-config": "30.5.2",
+ "jest-util": "30.5.1",
+ "jest-validate": "30.5.1",
"yargs": "^17.7.2"
},
"bin": {
@@ -18257,23 +17771,10 @@
}
}
},
- "node_modules/jest-cli/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/jest-cli/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -18283,13 +17784,13 @@
}
},
"node_modules/jest-cli/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -18301,42 +17802,18 @@
}
},
"node_modules/jest-cli/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
- "node_modules/jest-cli/node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/jest-cli/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/jest-cli/node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
@@ -18348,9 +17825,9 @@
}
},
"node_modules/jest-cli/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -18360,32 +17837,32 @@
}
},
"node_modules/jest-config": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz",
- "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.5.2.tgz",
+ "integrity": "sha512-U6221OZekabePvGcGPf4raIBKMvYQWFHvhobDJRojOYaFMj2HaGytAdknjDhYc6JTz2fdHW9+6k0wWUDaNeOFQ==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.27.4",
- "@jest/get-type": "30.1.0",
- "@jest/pattern": "30.4.0",
- "@jest/test-sequencer": "30.4.1",
- "@jest/types": "30.4.1",
- "babel-jest": "30.4.1",
+ "@jest/get-type": "30.5.0",
+ "@jest/pattern": "30.5.0",
+ "@jest/test-sequencer": "30.5.2",
+ "@jest/types": "30.5.1",
+ "babel-jest": "30.5.2",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
"deepmerge": "^4.3.1",
- "glob": "^10.5.0",
+ "glob": "^13.0.6",
"graceful-fs": "^4.2.11",
- "jest-circus": "30.4.2",
- "jest-docblock": "30.4.0",
- "jest-environment-node": "30.4.1",
- "jest-regex-util": "30.4.0",
- "jest-resolve": "30.4.1",
- "jest-runner": "30.4.2",
- "jest-util": "30.4.1",
- "jest-validate": "30.4.1",
+ "jest-circus": "30.5.2",
+ "jest-docblock": "30.5.0",
+ "jest-environment-node": "30.5.2",
+ "jest-regex-util": "30.5.0",
+ "jest-resolve": "30.5.1",
+ "jest-runner": "30.5.2",
+ "jest-util": "30.5.1",
+ "jest-validate": "30.5.1",
"parse-json": "^5.2.0",
- "pretty-format": "30.4.1",
+ "pretty-format": "30.5.1",
"slash": "^3.0.0",
"strip-json-comments": "^3.1.1"
},
@@ -18409,23 +17886,10 @@
}
}
},
- "node_modules/jest-config/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/jest-config/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -18435,22 +17899,22 @@
}
},
"node_modules/jest-config/node_modules/@jest/transform": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz",
- "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.5.2.tgz",
+ "integrity": "sha512-LBGwmaE886C41jDZ65eqR1YbXM7642MK3ZhTowZd5+xDlrDLuf4ePuDdDmVSvcr8NTVJ3MqxLkMbUCzuigUaoQ==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.27.4",
- "@jest/types": "30.4.1",
- "@jridgewell/trace-mapping": "^0.3.25",
- "babel-plugin-istanbul": "^7.0.1",
+ "@jest/types": "30.5.1",
+ "@jridgewell/trace-mapping": "^0.3.31",
+ "babel-plugin-istanbul": "^8.0.0",
"chalk": "^4.1.2",
"convert-source-map": "^2.0.0",
"fast-json-stable-stringify": "^2.1.0",
"graceful-fs": "^4.2.11",
- "jest-haste-map": "30.4.1",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
+ "jest-haste-map": "30.5.1",
+ "jest-regex-util": "30.5.0",
+ "jest-util": "30.5.1",
"pirates": "^4.0.7",
"slash": "^3.0.0",
"write-file-atomic": "^5.0.1"
@@ -18460,13 +17924,13 @@
}
},
"node_modules/jest-config/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -18478,9 +17942,9 @@
}
},
"node_modules/jest-config/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
"node_modules/jest-config/node_modules/ansi-styles": {
@@ -18496,15 +17960,15 @@
}
},
"node_modules/jest-config/node_modules/babel-jest": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz",
- "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.5.2.tgz",
+ "integrity": "sha512-ES8WeJ2fNWPEDunyAtUfJ12nHyaWUloCHdkfK2oW5uSoQSUmjNKdWiGaXJITJwvFEWoD/evHEg/QoCXAvLaQqQ==",
"license": "MIT",
"dependencies": {
- "@jest/transform": "30.4.1",
+ "@jest/transform": "30.5.2",
"@types/babel__core": "^7.20.5",
- "babel-plugin-istanbul": "^7.0.1",
- "babel-preset-jest": "30.4.0",
+ "babel-plugin-istanbul": "^8.0.0",
+ "babel-preset-jest": "30.5.0",
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
"slash": "^3.0.0"
@@ -18516,10 +17980,26 @@
"@babel/core": "^7.11.0 || ^8.0.0-0"
}
},
+ "node_modules/jest-config/node_modules/babel-jest/node_modules/babel-preset-jest": {
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.5.0.tgz",
+ "integrity": "sha512-ZGPn5ClP4lBDpuOK8W1yQIOy359HmbnZv3suucAlIe+SEE9yDsxV4S2PjSbH2Vc97U+WmzYD7vw3kr8NsQ/i6w==",
+ "license": "MIT",
+ "dependencies": {
+ "babel-plugin-jest-hoist": "30.5.0",
+ "babel-preset-current-node-syntax": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0 || ^8.0.0-beta.1 || ^8.0.0"
+ }
+ },
"node_modules/jest-config/node_modules/babel-plugin-istanbul": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz",
- "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-8.0.0.tgz",
+ "integrity": "sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg==",
"license": "BSD-3-Clause",
"workspaces": [
"test/babel-8"
@@ -18529,16 +18009,16 @@
"@istanbuljs/load-nyc-config": "^1.0.0",
"@istanbuljs/schema": "^0.1.3",
"istanbul-lib-instrument": "^6.0.2",
- "test-exclude": "^6.0.0"
+ "test-exclude": "^7.0.1"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/jest-config/node_modules/babel-plugin-jest-hoist": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz",
- "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.5.0.tgz",
+ "integrity": "sha512-gtGo1B+u14jrZQv6TdSWIWkTqclboo7Qn+dFAGUOIuXLFuJKAdg3U5MIWzZnW4vfUb4dX9skmAMiby86e/SF4A==",
"license": "MIT",
"dependencies": {
"@types/babel__core": "^7.20.5"
@@ -18547,62 +18027,56 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-config/node_modules/babel-preset-jest": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz",
- "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==",
+ "node_modules/jest-config/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"license": "MIT",
- "dependencies": {
- "babel-plugin-jest-hoist": "30.4.0",
- "babel-preset-current-node-syntax": "^1.2.0"
- },
"engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.11.0 || ^8.0.0-beta.1"
+ "node": "18 || 20 || >=22"
}
},
"node_modules/jest-config/node_modules/brace-expansion": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
- "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0"
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
}
},
- "node_modules/jest-config/node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
+ "node_modules/jest-config/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
}
},
"node_modules/jest-config/node_modules/glob": {
- "version": "10.5.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
- "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
- "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
- "license": "ISC",
+ "version": "13.0.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
+ "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
+ "minimatch": "^10.2.2",
+ "minipass": "^7.1.3",
+ "path-scurry": "^2.0.2"
},
- "bin": {
- "glob": "dist/esm/bin.mjs"
+ "engines": {
+ "node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -18625,45 +18099,43 @@
}
},
"node_modules/jest-config/node_modules/jest-haste-map": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz",
- "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.5.1.tgz",
+ "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
+ "@parcel/watcher": "^2.6.0",
"@types/node": "*",
"anymatch": "^3.1.3",
"fb-watchman": "^2.0.2",
+ "fdir": "^6.5.0",
"graceful-fs": "^4.2.11",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
- "jest-worker": "30.4.1",
- "picomatch": "^4.0.3",
- "walker": "^1.0.8"
+ "jest-regex-util": "30.5.0",
+ "jest-util": "30.5.1",
+ "jest-worker": "30.5.1",
+ "picomatch": "^4.0.3"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "optionalDependencies": {
- "fsevents": "^2.3.3"
}
},
"node_modules/jest-config/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz",
+ "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==",
"license": "MIT",
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-config/node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
@@ -18674,41 +18146,50 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-config/node_modules/jest-worker": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz",
- "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==",
- "license": "MIT",
+ "node_modules/jest-config/node_modules/lru-cache": {
+ "version": "11.5.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz",
+ "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/jest-config/node_modules/minimatch": {
+ "version": "10.2.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
+ "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "@types/node": "*",
- "@ungap/structured-clone": "^1.3.0",
- "jest-util": "30.4.1",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.1.1"
+ "brace-expansion": "^5.0.8"
},
"engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/jest-config/node_modules/minimatch": {
- "version": "9.0.9",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
- "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
- "license": "ISC",
+ "node_modules/jest-config/node_modules/path-scurry": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
+ "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "brace-expansion": "^2.0.2"
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
},
"engines": {
- "node": ">=16 || 14 >=14.17"
+ "node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/jest-config/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -18718,35 +18199,20 @@
}
},
"node_modules/jest-config/node_modules/pretty-format": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz",
- "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
"license": "MIT",
"dependencies": {
- "@jest/schemas": "30.4.1",
- "ansi-styles": "^5.2.0",
- "react-is-18": "npm:react-is@^18.3.1",
- "react-is-19": "npm:react-is@^19.2.5"
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-config/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
"node_modules/jest-config/node_modules/write-file-atomic": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
@@ -18761,26 +18227,42 @@
}
},
"node_modules/jest-diff": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
- "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
- "dev": true,
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.5.2.tgz",
+ "integrity": "sha512-rBtHnI6BWTiWj3lM7fOLVfChx7R9B0u9VV3PRHawKs79x6ZjW1QoSrKenaigNGWlvhtoi2BqvDbABDie+Os3xQ==",
"license": "MIT",
"dependencies": {
- "chalk": "^4.0.0",
- "diff-sequences": "^29.6.3",
- "jest-get-type": "^29.6.3",
- "pretty-format": "^29.7.0"
+ "@jest/diff-sequences": "30.5.0",
+ "@jest/get-type": "30.5.0",
+ "chalk": "^4.1.2",
+ "pretty-format": "30.5.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-diff/node_modules/@jest/schemas": {
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
+ "node_modules/jest-diff/node_modules/@sinclair/typebox": {
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
+ "license": "MIT"
+ },
"node_modules/jest-diff/node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@@ -18790,31 +18272,24 @@
}
},
"node_modules/jest-diff/node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
- "dev": true,
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
"license": "MIT",
"dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-diff/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/jest-docblock": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz",
- "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.5.0.tgz",
+ "integrity": "sha512-NwDqcxtoZi33RhuW+zJS/RVA3rmheQ8BnwpYZuc/Eruaz6seQb7+aoCeDZu/3X7W2XmD8DbSo9Pn72DbKsBFYw==",
"license": "MIT",
"dependencies": {
"detect-newline": "^3.1.0"
@@ -18824,38 +18299,26 @@
}
},
"node_modules/jest-each": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz",
- "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.5.2.tgz",
+ "integrity": "sha512-GRrW+7fmmgmkvyfEBqc1QFdWnF1Ex5yG6Y6AWL4TTxnkWlKIT98dsKh3P3UuWm6v4XsQwVE6DEzXIPMzWJ3ZbA==",
"license": "MIT",
"dependencies": {
- "@jest/get-type": "30.1.0",
- "@jest/types": "30.4.1",
+ "@jest/get-type": "30.5.0",
+ "@jest/types": "30.5.1",
"chalk": "^4.1.2",
- "jest-util": "30.4.1",
- "pretty-format": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-each/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
+ "jest-regex-util": "30.5.0",
+ "jest-util": "30.5.1",
+ "pretty-format": "30.5.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-each/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -18865,13 +18328,13 @@
}
},
"node_modules/jest-each/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -18883,9 +18346,9 @@
}
},
"node_modules/jest-each/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
"node_modules/jest-each/node_modules/ansi-styles": {
@@ -18900,37 +18363,22 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/jest-each/node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/jest-each/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz",
+ "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==",
"license": "MIT",
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-each/node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
@@ -18942,9 +18390,9 @@
}
},
"node_modules/jest-each/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -18954,87 +18402,74 @@
}
},
"node_modules/jest-each/node_modules/pretty-format": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz",
- "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
"license": "MIT",
"dependencies": {
- "@jest/schemas": "30.4.1",
- "ansi-styles": "^5.2.0",
- "react-is-18": "npm:react-is@^18.3.1",
- "react-is-19": "npm:react-is@^19.2.5"
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-environment-node": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz",
- "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.5.2.tgz",
+ "integrity": "sha512-Ciz4KgTGIbojxSKpuFImgWJj5EyqfE8xMq9NxWmOXnQywHHS03H9Mv+vaCB29J5pr3JBrY27WbRV18rBdjh8Dg==",
"license": "MIT",
"dependencies": {
- "@jest/environment": "30.4.1",
- "@jest/fake-timers": "30.4.1",
- "@jest/types": "30.4.1",
+ "@jest/environment": "30.5.2",
+ "@jest/fake-timers": "30.5.2",
+ "@jest/types": "30.5.1",
"@types/node": "*",
- "jest-mock": "30.4.1",
- "jest-util": "30.4.1",
- "jest-validate": "30.4.1"
+ "jest-mock": "30.5.2",
+ "jest-util": "30.5.1",
+ "jest-validate": "30.5.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-environment-node/node_modules/@jest/environment": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz",
- "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.5.2.tgz",
+ "integrity": "sha512-nwFPOUMbmsjNNRCpnJpB9HbSZu4C07wDDds++5j+j1MezzPLxMPDdbdUVjr2o4RULDOValnDd70taPFI1EtxAg==",
"license": "MIT",
"dependencies": {
- "@jest/fake-timers": "30.4.1",
- "@jest/types": "30.4.1",
+ "@jest/fake-timers": "30.5.2",
+ "@jest/types": "30.5.1",
"@types/node": "*",
- "jest-mock": "30.4.1"
+ "jest-mock": "30.5.2"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-environment-node/node_modules/@jest/fake-timers": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz",
- "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.5.2.tgz",
+ "integrity": "sha512-GKk0h0tKT5SpDPxDhPg3r9mm5s8/YCBzVNMOGdRiQXwUiMsjU10mVPlMaQstH6/cu50vOb8N9oQUMETHuwwxKw==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@sinonjs/fake-timers": "^15.4.0",
"@types/node": "*",
- "jest-message-util": "30.4.1",
- "jest-mock": "30.4.1",
- "jest-util": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-environment-node/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
+ "jest-message-util": "30.5.1",
+ "jest-mock": "30.5.2",
+ "jest-util": "30.5.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-environment-node/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -19044,13 +18479,13 @@
}
},
"node_modules/jest-environment-node/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -19062,9 +18497,9 @@
}
},
"node_modules/jest-environment-node/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
"node_modules/jest-environment-node/node_modules/@sinonjs/fake-timers": {
@@ -19088,35 +18523,20 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/jest-environment-node/node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/jest-environment-node/node_modules/jest-message-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz",
- "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz",
+ "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/stack-utils": "^2.0.3",
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
- "jest-util": "30.4.1",
+ "jest-util": "30.5.1",
"picomatch": "^4.0.3",
- "pretty-format": "30.4.1",
+ "pretty-format": "30.5.1",
"slash": "^3.0.0",
"stack-utils": "^2.0.6"
},
@@ -19125,35 +18545,27 @@
}
},
"node_modules/jest-environment-node/node_modules/jest-mock": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz",
- "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.5.2.tgz",
+ "integrity": "sha512-KTHb37Fhj8xY8qPFvTFaFJHcdsumQp+ExsUlgoX232Agmc706gagBs53+CYvulV7MVQo9AgXOUxsqzzVBoJlrA==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/expect-utils": "30.5.2",
+ "@jest/types": "30.5.1",
"@types/node": "*",
- "jest-util": "30.4.1"
+ "jest-util": "30.5.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-environment-node/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/jest-environment-node/node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
@@ -19165,9 +18577,9 @@
}
},
"node_modules/jest-environment-node/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -19177,30 +18589,20 @@
}
},
"node_modules/jest-environment-node/node_modules/pretty-format": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz",
- "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
"license": "MIT",
"dependencies": {
- "@jest/schemas": "30.4.1",
- "ansi-styles": "^5.2.0",
- "react-is-18": "npm:react-is@^18.3.1",
- "react-is-19": "npm:react-is@^19.2.5"
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-get-type": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
- "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
"node_modules/jest-haste-map": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz",
@@ -19227,23 +18629,55 @@
"fsevents": "^2.3.2"
}
},
+ "node_modules/jest-haste-map/node_modules/jest-worker": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
+ "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "jest-util": "^29.7.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-haste-map/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
"node_modules/jest-leak-detector": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz",
- "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.5.1.tgz",
+ "integrity": "sha512-gt4GT2aWgEoCNTcBe4rqS74xTIUJxi+UD9SNSu9aOk5LmTEd6fS5KSTmAKAfitCCktQHNN+upUuL6EkBfFbMDQ==",
"license": "MIT",
"dependencies": {
- "@jest/get-type": "30.1.0",
- "pretty-format": "30.4.1"
+ "@jest/get-type": "30.5.0",
+ "pretty-format": "30.5.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-leak-detector/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -19253,9 +18687,9 @@
}
},
"node_modules/jest-leak-detector/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
"node_modules/jest-leak-detector/node_modules/ansi-styles": {
@@ -19271,41 +18705,57 @@
}
},
"node_modules/jest-leak-detector/node_modules/pretty-format": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz",
- "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
"license": "MIT",
"dependencies": {
- "@jest/schemas": "30.4.1",
- "ansi-styles": "^5.2.0",
- "react-is-18": "npm:react-is@^18.3.1",
- "react-is-19": "npm:react-is@^19.2.5"
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-matcher-utils": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
- "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
- "dev": true,
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.5.2.tgz",
+ "integrity": "sha512-lFkB365PTIHOhPzwrb9T6gvlDnPpOZ7/c3ewOKKB7bzztqjVrtlyRL2MkyfJXpyY2u2SKw001JKrTx14fBUFcQ==",
"license": "MIT",
"dependencies": {
- "chalk": "^4.0.0",
- "jest-diff": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "pretty-format": "^29.7.0"
+ "@jest/get-type": "30.5.0",
+ "chalk": "^4.1.2",
+ "jest-diff": "30.5.2",
+ "pretty-format": "30.5.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils/node_modules/@jest/schemas": {
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
+ "node_modules/jest-matcher-utils/node_modules/@sinclair/typebox": {
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
+ "license": "MIT"
+ },
"node_modules/jest-matcher-utils/node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@@ -19315,27 +18765,20 @@
}
},
"node_modules/jest-matcher-utils/node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
- "dev": true,
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
"license": "MIT",
"dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-matcher-utils/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/jest-message-util": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
@@ -19407,23 +18850,6 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
- "node_modules/jest-pnp-resolver": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
- "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- },
- "peerDependencies": {
- "jest-resolve": "*"
- },
- "peerDependenciesMeta": {
- "jest-resolve": {
- "optional": true
- }
- }
- },
"node_modules/jest-regex-util": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
@@ -19435,100 +18861,252 @@
}
},
"node_modules/jest-resolve": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz",
- "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.5.1.tgz",
+ "integrity": "sha512-wprhLejRtwN6h8ZgaqC0eYjGJ1uMdGPT/+b3eFncbG4NWuuP9QL+vWwRinu9waw7hkOLcpMJGVJAMgBwsPssMQ==",
"license": "MIT",
"dependencies": {
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
- "jest-haste-map": "30.4.1",
- "jest-pnp-resolver": "^1.2.3",
- "jest-util": "30.4.1",
- "jest-validate": "30.4.1",
+ "jest-haste-map": "30.5.1",
+ "jest-util": "30.5.1",
+ "jest-validate": "30.5.1",
"slash": "^3.0.0",
- "unrs-resolver": "^1.7.11"
+ "unrs-resolver": "^1.12.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-resolve-dependencies": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz",
- "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.5.2.tgz",
+ "integrity": "sha512-GMI0DP5enX9Z2qW2zFy1bOkrCfWAOPRJQ7qHt0pdfrxBPsgWxglMPrRSK2TX/wRWYxcPRSLFg3Z88qPOzHx7FQ==",
"license": "MIT",
"dependencies": {
- "jest-regex-util": "30.4.0",
- "jest-snapshot": "30.4.1"
+ "jest-regex-util": "30.5.0",
+ "jest-snapshot": "30.5.2"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/@jest/diff-sequences": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz",
- "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==",
+ "node_modules/jest-resolve-dependencies/node_modules/jest-regex-util": {
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz",
+ "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==",
"license": "MIT",
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/@jest/expect-utils": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz",
- "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==",
+ "node_modules/jest-resolve/node_modules/@jest/schemas": {
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
- "@jest/get-type": "30.1.0"
+ "@sinclair/typebox": "^0.34.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
+ "node_modules/jest-resolve/node_modules/@jest/types": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
- "jest-regex-util": "30.4.0"
+ "@types/yargs": "^17.0.33",
+ "chalk": "^4.1.2"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "node_modules/jest-resolve/node_modules/@sinclair/typebox": {
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
+ "license": "MIT"
+ },
+ "node_modules/jest-resolve/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"license": "MIT",
- "dependencies": {
- "@sinclair/typebox": "^0.34.0"
- },
"engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
}
},
- "node_modules/jest-resolve-dependencies/node_modules/@jest/transform": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz",
- "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==",
+ "node_modules/jest-resolve/node_modules/jest-haste-map": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.5.1.tgz",
+ "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.5.1",
+ "@parcel/watcher": "^2.6.0",
+ "@types/node": "*",
+ "anymatch": "^3.1.3",
+ "fb-watchman": "^2.0.2",
+ "fdir": "^6.5.0",
+ "graceful-fs": "^4.2.11",
+ "jest-regex-util": "30.5.0",
+ "jest-util": "30.5.1",
+ "jest-worker": "30.5.1",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-resolve/node_modules/jest-regex-util": {
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz",
+ "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==",
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-resolve/node_modules/jest-util": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.5.1",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "graceful-fs": "^4.2.11",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-resolve/node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/jest-runner": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.5.2.tgz",
+ "integrity": "sha512-/B6px7hiiNhVSRwuv9AGn0nawYvwsHeQZ3ItTd5Gp2diFd0EOKaWVeWqcbw3L88vx1GfpCKjQkxWItZCTl11Rw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.5.2",
+ "@jest/environment": "30.5.2",
+ "@jest/source-map": "30.5.2",
+ "@jest/test-result": "30.5.2",
+ "@jest/transform": "30.5.2",
+ "@jest/types": "30.5.1",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "emittery": "^0.13.1",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-docblock": "30.5.0",
+ "jest-environment-node": "30.5.2",
+ "jest-haste-map": "30.5.1",
+ "jest-leak-detector": "30.5.1",
+ "jest-message-util": "30.5.1",
+ "jest-resolve": "30.5.1",
+ "jest-runtime": "30.5.2",
+ "jest-util": "30.5.1",
+ "jest-watcher": "30.5.2",
+ "jest-worker": "30.5.1",
+ "p-limit": "^3.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runner/node_modules/@jest/environment": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.5.2.tgz",
+ "integrity": "sha512-nwFPOUMbmsjNNRCpnJpB9HbSZu4C07wDDds++5j+j1MezzPLxMPDdbdUVjr2o4RULDOValnDd70taPFI1EtxAg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/fake-timers": "30.5.2",
+ "@jest/types": "30.5.1",
+ "@types/node": "*",
+ "jest-mock": "30.5.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runner/node_modules/@jest/fake-timers": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.5.2.tgz",
+ "integrity": "sha512-GKk0h0tKT5SpDPxDhPg3r9mm5s8/YCBzVNMOGdRiQXwUiMsjU10mVPlMaQstH6/cu50vOb8N9oQUMETHuwwxKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.5.1",
+ "@sinonjs/fake-timers": "^15.4.0",
+ "@types/node": "*",
+ "jest-message-util": "30.5.1",
+ "jest-mock": "30.5.2",
+ "jest-util": "30.5.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runner/node_modules/@jest/schemas": {
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runner/node_modules/@jest/transform": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.5.2.tgz",
+ "integrity": "sha512-LBGwmaE886C41jDZ65eqR1YbXM7642MK3ZhTowZd5+xDlrDLuf4ePuDdDmVSvcr8NTVJ3MqxLkMbUCzuigUaoQ==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.27.4",
- "@jest/types": "30.4.1",
- "@jridgewell/trace-mapping": "^0.3.25",
- "babel-plugin-istanbul": "^7.0.1",
+ "@jest/types": "30.5.1",
+ "@jridgewell/trace-mapping": "^0.3.31",
+ "babel-plugin-istanbul": "^8.0.0",
"chalk": "^4.1.2",
"convert-source-map": "^2.0.0",
"fast-json-stable-stringify": "^2.1.0",
"graceful-fs": "^4.2.11",
- "jest-haste-map": "30.4.1",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
+ "jest-haste-map": "30.5.1",
+ "jest-regex-util": "30.5.0",
+ "jest-util": "30.5.1",
"pirates": "^4.0.7",
"slash": "^3.0.0",
"write-file-atomic": "^5.0.1"
@@ -19537,14 +19115,14 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "node_modules/jest-runner/node_modules/@jest/types": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -19555,13 +19133,22 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "node_modules/jest-runner/node_modules/@sinclair/typebox": {
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
- "node_modules/jest-resolve-dependencies/node_modules/ansi-styles": {
+ "node_modules/jest-runner/node_modules/@sinonjs/fake-timers": {
+ "version": "15.4.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz",
+ "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.1"
+ }
+ },
+ "node_modules/jest-runner/node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
@@ -19573,10 +19160,10 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/babel-plugin-istanbul": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz",
- "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==",
+ "node_modules/jest-runner/node_modules/babel-plugin-istanbul": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-8.0.0.tgz",
+ "integrity": "sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg==",
"license": "BSD-3-Clause",
"workspaces": [
"test/babel-8"
@@ -19586,45 +19173,30 @@
"@istanbuljs/load-nyc-config": "^1.0.0",
"@istanbuljs/schema": "^0.1.3",
"istanbul-lib-instrument": "^6.0.2",
- "test-exclude": "^6.0.0"
+ "test-exclude": "^7.0.1"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
+ "node_modules/jest-runner/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"license": "MIT",
"engines": {
- "node": ">=8"
- }
- },
- "node_modules/jest-resolve-dependencies/node_modules/expect": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz",
- "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==",
- "license": "MIT",
- "dependencies": {
- "@jest/expect-utils": "30.4.1",
- "@jest/get-type": "30.1.0",
- "jest-matcher-utils": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-mock": "30.4.1",
- "jest-util": "30.4.1"
+ "node": ">=12.0.0"
},
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
}
},
- "node_modules/jest-resolve-dependencies/node_modules/istanbul-lib-instrument": {
+ "node_modules/jest-runner/node_modules/istanbul-lib-instrument": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
"integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
@@ -19640,74 +19212,42 @@
"node": ">=10"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/jest-diff": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz",
- "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==",
- "license": "MIT",
- "dependencies": {
- "@jest/diff-sequences": "30.4.0",
- "@jest/get-type": "30.1.0",
- "chalk": "^4.1.2",
- "pretty-format": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-resolve-dependencies/node_modules/jest-haste-map": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz",
- "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==",
+ "node_modules/jest-runner/node_modules/jest-haste-map": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.5.1.tgz",
+ "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
+ "@parcel/watcher": "^2.6.0",
"@types/node": "*",
"anymatch": "^3.1.3",
"fb-watchman": "^2.0.2",
+ "fdir": "^6.5.0",
"graceful-fs": "^4.2.11",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
- "jest-worker": "30.4.1",
- "picomatch": "^4.0.3",
- "walker": "^1.0.8"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "optionalDependencies": {
- "fsevents": "^2.3.3"
- }
- },
- "node_modules/jest-resolve-dependencies/node_modules/jest-matcher-utils": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz",
- "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==",
- "license": "MIT",
- "dependencies": {
- "@jest/get-type": "30.1.0",
- "chalk": "^4.1.2",
- "jest-diff": "30.4.1",
- "pretty-format": "30.4.1"
+ "jest-regex-util": "30.5.0",
+ "jest-util": "30.5.1",
+ "jest-worker": "30.5.1",
+ "picomatch": "^4.0.3"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/jest-message-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz",
- "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==",
+ "node_modules/jest-runner/node_modules/jest-message-util": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz",
+ "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/stack-utils": "^2.0.3",
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
- "jest-util": "30.4.1",
+ "jest-util": "30.5.1",
"picomatch": "^4.0.3",
- "pretty-format": "30.4.1",
+ "pretty-format": "30.5.1",
"slash": "^3.0.0",
"stack-utils": "^2.0.6"
},
@@ -19715,68 +19255,37 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/jest-mock": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz",
- "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==",
+ "node_modules/jest-runner/node_modules/jest-mock": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.5.2.tgz",
+ "integrity": "sha512-KTHb37Fhj8xY8qPFvTFaFJHcdsumQp+ExsUlgoX232Agmc706gagBs53+CYvulV7MVQo9AgXOUxsqzzVBoJlrA==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/expect-utils": "30.5.2",
+ "@jest/types": "30.5.1",
"@types/node": "*",
- "jest-util": "30.4.1"
+ "jest-util": "30.5.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-resolve-dependencies/node_modules/jest-snapshot": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz",
- "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==",
+ "node_modules/jest-runner/node_modules/jest-regex-util": {
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz",
+ "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==",
"license": "MIT",
- "dependencies": {
- "@babel/core": "^7.27.4",
- "@babel/generator": "^7.27.5",
- "@babel/plugin-syntax-jsx": "^7.27.1",
- "@babel/plugin-syntax-typescript": "^7.27.1",
- "@babel/types": "^7.27.3",
- "@jest/expect-utils": "30.4.1",
- "@jest/get-type": "30.1.0",
- "@jest/snapshot-utils": "30.4.1",
- "@jest/transform": "30.4.1",
- "@jest/types": "30.4.1",
- "babel-preset-current-node-syntax": "^1.2.0",
- "chalk": "^4.1.2",
- "expect": "30.4.1",
- "graceful-fs": "^4.2.11",
- "jest-diff": "30.4.1",
- "jest-matcher-utils": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-util": "30.4.1",
- "pretty-format": "30.4.1",
- "semver": "^7.7.2",
- "synckit": "^0.11.8"
- },
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
+ "node_modules/jest-runner/node_modules/jest-util": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
@@ -19787,26 +19296,10 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/jest-worker": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz",
- "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "@ungap/structured-clone": "^1.3.0",
- "jest-util": "30.4.1",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.1.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-resolve-dependencies/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "node_modules/jest-runner/node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -19815,37 +19308,22 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/pretty-format": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz",
- "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==",
+ "node_modules/jest-runner/node_modules/pretty-format": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
"license": "MIT",
"dependencies": {
- "@jest/schemas": "30.4.1",
- "ansi-styles": "^5.2.0",
- "react-is-18": "npm:react-is@^18.3.1",
- "react-is-19": "npm:react-is@^19.2.5"
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve-dependencies/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
- "node_modules/jest-resolve-dependencies/node_modules/write-file-atomic": {
+ "node_modules/jest-runner/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==",
@@ -19858,245 +19336,104 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
- "node_modules/jest-resolve/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
+ "node_modules/jest-runtime": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.5.2.tgz",
+ "integrity": "sha512-0paUAHBlheai10A3XSy9Xh+YBG0IROy+/qzcDPzQ/nbbXrznQzeWy5qniICWK6d3W3scaRQCVAbXdcK0ibBq4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.5.2",
+ "@jest/fake-timers": "30.5.2",
+ "@jest/globals": "30.5.2",
+ "@jest/source-map": "30.5.2",
+ "@jest/test-result": "30.5.2",
+ "@jest/transform": "30.5.2",
+ "@jest/types": "30.5.1",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "cjs-module-lexer": "^2.2.0",
+ "collect-v8-coverage": "^1.0.2",
+ "es-module-lexer": "^2.1.0",
+ "glob": "^13.0.6",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.5.1",
+ "jest-message-util": "30.5.1",
+ "jest-mock": "30.5.2",
+ "jest-regex-util": "30.5.0",
+ "jest-resolve": "30.5.1",
+ "jest-snapshot": "30.5.2",
+ "jest-util": "30.5.1",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runtime/node_modules/@jest/environment": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.5.2.tgz",
+ "integrity": "sha512-nwFPOUMbmsjNNRCpnJpB9HbSZu4C07wDDds++5j+j1MezzPLxMPDdbdUVjr2o4RULDOValnDd70taPFI1EtxAg==",
"license": "MIT",
"dependencies": {
+ "@jest/fake-timers": "30.5.2",
+ "@jest/types": "30.5.1",
"@types/node": "*",
- "jest-regex-util": "30.4.0"
+ "jest-mock": "30.5.2"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "node_modules/jest-runtime/node_modules/@jest/expect": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.5.2.tgz",
+ "integrity": "sha512-2E+1Pg6jJxbRjLNLpciC71iri/3qsKJL222/aXJEFwC0VdUSvfS+JZQQLEVjUYYc8t8pQu2MGE3cF2+bNTYRsg==",
"license": "MIT",
"dependencies": {
- "@sinclair/typebox": "^0.34.0"
+ "expect": "30.5.2",
+ "jest-snapshot": "30.5.2"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "node_modules/jest-runtime/node_modules/@jest/fake-timers": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.5.2.tgz",
+ "integrity": "sha512-GKk0h0tKT5SpDPxDhPg3r9mm5s8/YCBzVNMOGdRiQXwUiMsjU10mVPlMaQstH6/cu50vOb8N9oQUMETHuwwxKw==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
- "@types/istanbul-lib-coverage": "^2.0.6",
- "@types/istanbul-reports": "^3.0.4",
+ "@jest/types": "30.5.1",
+ "@sinonjs/fake-timers": "^15.4.0",
"@types/node": "*",
- "@types/yargs": "^17.0.33",
- "chalk": "^4.1.2"
+ "jest-message-util": "30.5.1",
+ "jest-mock": "30.5.2",
+ "jest-util": "30.5.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
- "license": "MIT"
- },
- "node_modules/jest-resolve/node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
+ "node_modules/jest-runtime/node_modules/@jest/globals": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.5.2.tgz",
+ "integrity": "sha512-62zfS+dL+M5aTNHXCNLyqD+j4oqxTIzDYrZbpqMP8Yn+gvlAGyzC6BSPNb1ZmFOakV1RtFYX/O3FCQXSWmQdgg==",
"license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.5.2",
+ "@jest/expect": "30.5.2",
+ "@jest/types": "30.5.1",
+ "jest-mock": "30.5.2"
+ },
"engines": {
- "node": ">=8"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-resolve/node_modules/jest-haste-map": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz",
- "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==",
- "license": "MIT",
- "dependencies": {
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "anymatch": "^3.1.3",
- "fb-watchman": "^2.0.2",
- "graceful-fs": "^4.2.11",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
- "jest-worker": "30.4.1",
- "picomatch": "^4.0.3",
- "walker": "^1.0.8"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "optionalDependencies": {
- "fsevents": "^2.3.3"
- }
- },
- "node_modules/jest-resolve/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-resolve/node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
- "license": "MIT",
- "dependencies": {
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "chalk": "^4.1.2",
- "ci-info": "^4.2.0",
- "graceful-fs": "^4.2.11",
- "picomatch": "^4.0.3"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-resolve/node_modules/jest-worker": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz",
- "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "@ungap/structured-clone": "^1.3.0",
- "jest-util": "30.4.1",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.1.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-resolve/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/jest-resolve/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
- "node_modules/jest-runner": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz",
- "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==",
- "license": "MIT",
- "dependencies": {
- "@jest/console": "30.4.1",
- "@jest/environment": "30.4.1",
- "@jest/test-result": "30.4.1",
- "@jest/transform": "30.4.1",
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "chalk": "^4.1.2",
- "emittery": "^0.13.1",
- "exit-x": "^0.2.2",
- "graceful-fs": "^4.2.11",
- "jest-docblock": "30.4.0",
- "jest-environment-node": "30.4.1",
- "jest-haste-map": "30.4.1",
- "jest-leak-detector": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-resolve": "30.4.1",
- "jest-runtime": "30.4.2",
- "jest-util": "30.4.1",
- "jest-watcher": "30.4.1",
- "jest-worker": "30.4.1",
- "p-limit": "^3.1.0",
- "source-map-support": "0.5.13"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runner/node_modules/@jest/environment": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz",
- "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==",
- "license": "MIT",
- "dependencies": {
- "@jest/fake-timers": "30.4.1",
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "jest-mock": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runner/node_modules/@jest/fake-timers": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz",
- "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==",
- "license": "MIT",
- "dependencies": {
- "@jest/types": "30.4.1",
- "@sinonjs/fake-timers": "^15.4.0",
- "@types/node": "*",
- "jest-message-util": "30.4.1",
- "jest-mock": "30.4.1",
- "jest-util": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runner/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runner/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "node_modules/jest-runtime/node_modules/@jest/schemas": {
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -20105,23 +19442,23 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runner/node_modules/@jest/transform": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz",
- "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==",
+ "node_modules/jest-runtime/node_modules/@jest/transform": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.5.2.tgz",
+ "integrity": "sha512-LBGwmaE886C41jDZ65eqR1YbXM7642MK3ZhTowZd5+xDlrDLuf4ePuDdDmVSvcr8NTVJ3MqxLkMbUCzuigUaoQ==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.27.4",
- "@jest/types": "30.4.1",
- "@jridgewell/trace-mapping": "^0.3.25",
- "babel-plugin-istanbul": "^7.0.1",
+ "@jest/types": "30.5.1",
+ "@jridgewell/trace-mapping": "^0.3.31",
+ "babel-plugin-istanbul": "^8.0.0",
"chalk": "^4.1.2",
"convert-source-map": "^2.0.0",
"fast-json-stable-stringify": "^2.1.0",
"graceful-fs": "^4.2.11",
- "jest-haste-map": "30.4.1",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
+ "jest-haste-map": "30.5.1",
+ "jest-regex-util": "30.5.0",
+ "jest-util": "30.5.1",
"pirates": "^4.0.7",
"slash": "^3.0.0",
"write-file-atomic": "^5.0.1"
@@ -20130,14 +19467,14 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runner/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "node_modules/jest-runtime/node_modules/@jest/types": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -20148,13 +19485,13 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runner/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "node_modules/jest-runtime/node_modules/@sinclair/typebox": {
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
- "node_modules/jest-runner/node_modules/@sinonjs/fake-timers": {
+ "node_modules/jest-runtime/node_modules/@sinonjs/fake-timers": {
"version": "15.4.0",
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz",
"integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==",
@@ -20163,7 +19500,7 @@
"@sinonjs/commons": "^3.0.1"
}
},
- "node_modules/jest-runner/node_modules/ansi-styles": {
+ "node_modules/jest-runtime/node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
@@ -20175,10 +19512,10 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/jest-runner/node_modules/babel-plugin-istanbul": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz",
- "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==",
+ "node_modules/jest-runtime/node_modules/babel-plugin-istanbul": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-8.0.0.tgz",
+ "integrity": "sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg==",
"license": "BSD-3-Clause",
"workspaces": [
"test/babel-8"
@@ -20188,28 +19525,74 @@
"@istanbuljs/load-nyc-config": "^1.0.0",
"@istanbuljs/schema": "^0.1.3",
"istanbul-lib-instrument": "^6.0.2",
- "test-exclude": "^6.0.0"
+ "test-exclude": "^7.0.1"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
- "node_modules/jest-runner/node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
+ "node_modules/jest-runtime/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": "18 || 20 || >=22"
}
},
- "node_modules/jest-runner/node_modules/istanbul-lib-instrument": {
+ "node_modules/jest-runtime/node_modules/brace-expansion": {
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/jest-runtime/node_modules/es-module-lexer": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz",
+ "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==",
+ "license": "MIT"
+ },
+ "node_modules/jest-runtime/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-runtime/node_modules/glob": {
+ "version": "13.0.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
+ "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "minimatch": "^10.2.2",
+ "minipass": "^7.1.3",
+ "path-scurry": "^2.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/jest-runtime/node_modules/istanbul-lib-instrument": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
"integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
@@ -20225,44 +19608,42 @@
"node": ">=10"
}
},
- "node_modules/jest-runner/node_modules/jest-haste-map": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz",
- "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==",
+ "node_modules/jest-runtime/node_modules/jest-haste-map": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.5.1.tgz",
+ "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
+ "@parcel/watcher": "^2.6.0",
"@types/node": "*",
"anymatch": "^3.1.3",
"fb-watchman": "^2.0.2",
+ "fdir": "^6.5.0",
"graceful-fs": "^4.2.11",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
- "jest-worker": "30.4.1",
- "picomatch": "^4.0.3",
- "walker": "^1.0.8"
+ "jest-regex-util": "30.5.0",
+ "jest-util": "30.5.1",
+ "jest-worker": "30.5.1",
+ "picomatch": "^4.0.3"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "optionalDependencies": {
- "fsevents": "^2.3.3"
}
},
- "node_modules/jest-runner/node_modules/jest-message-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz",
- "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==",
+ "node_modules/jest-runtime/node_modules/jest-message-util": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz",
+ "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/stack-utils": "^2.0.3",
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
- "jest-util": "30.4.1",
+ "jest-util": "30.5.1",
"picomatch": "^4.0.3",
- "pretty-format": "30.4.1",
+ "pretty-format": "30.5.1",
"slash": "^3.0.0",
"stack-utils": "^2.0.6"
},
@@ -20270,36 +19651,37 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runner/node_modules/jest-mock": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz",
- "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==",
+ "node_modules/jest-runtime/node_modules/jest-mock": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.5.2.tgz",
+ "integrity": "sha512-KTHb37Fhj8xY8qPFvTFaFJHcdsumQp+ExsUlgoX232Agmc706gagBs53+CYvulV7MVQo9AgXOUxsqzzVBoJlrA==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/expect-utils": "30.5.2",
+ "@jest/types": "30.5.1",
"@types/node": "*",
- "jest-util": "30.4.1"
+ "jest-util": "30.5.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runner/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
+ "node_modules/jest-runtime/node_modules/jest-regex-util": {
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz",
+ "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==",
"license": "MIT",
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runner/node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
+ "node_modules/jest-runtime/node_modules/jest-util": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
@@ -20310,41 +19692,50 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runner/node_modules/jest-worker": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz",
- "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==",
- "license": "MIT",
+ "node_modules/jest-runtime/node_modules/lru-cache": {
+ "version": "11.5.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz",
+ "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/jest-runtime/node_modules/minimatch": {
+ "version": "10.2.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
+ "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "@types/node": "*",
- "@ungap/structured-clone": "^1.3.0",
- "jest-util": "30.4.1",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.1.1"
+ "brace-expansion": "^5.0.8"
},
"engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/jest-runner/node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "license": "MIT",
+ "node_modules/jest-runtime/node_modules/path-scurry": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
+ "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "yocto-queue": "^0.1.0"
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
},
"engines": {
- "node": ">=10"
+ "node": "18 || 20 || >=22"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/jest-runner/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "node_modules/jest-runtime/node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -20353,199 +19744,70 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/jest-runner/node_modules/pretty-format": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz",
- "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==",
+ "node_modules/jest-runtime/node_modules/pretty-format": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
"license": "MIT",
"dependencies": {
- "@jest/schemas": "30.4.1",
- "ansi-styles": "^5.2.0",
- "react-is-18": "npm:react-is@^18.3.1",
- "react-is-19": "npm:react-is@^19.2.5"
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runner/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "license": "BSD-3-Clause",
+ "node_modules/jest-runtime/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==",
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
+ },
"engines": {
- "node": ">=0.10.0"
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
- "node_modules/jest-runner/node_modules/source-map-support": {
- "version": "0.5.13",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
- "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "node_modules/jest-snapshot": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.5.2.tgz",
+ "integrity": "sha512-jKIdes25AnAH73dOCi2qE6J6vHJ6L2vyBS11M89kT50DVJcLTf2AOjvcc+2vVaN8lUbY5v2R83aqXo3Ro9fqYA==",
"license": "MIT",
"dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- }
- },
- "node_modules/jest-runner/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
- "node_modules/jest-runner/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==",
- "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/jest-runtime": {
- "version": "30.4.2",
- "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz",
- "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==",
- "license": "MIT",
- "dependencies": {
- "@jest/environment": "30.4.1",
- "@jest/fake-timers": "30.4.1",
- "@jest/globals": "30.4.1",
- "@jest/source-map": "30.0.1",
- "@jest/test-result": "30.4.1",
- "@jest/transform": "30.4.1",
- "@jest/types": "30.4.1",
- "@types/node": "*",
+ "@babel/core": "^7.27.4",
+ "@babel/generator": "^7.27.5",
+ "@babel/plugin-syntax-jsx": "^7.27.1",
+ "@babel/plugin-syntax-typescript": "^7.27.1",
+ "@babel/types": "^7.27.3",
+ "@jest/expect-utils": "30.5.2",
+ "@jest/get-type": "30.5.0",
+ "@jest/snapshot-utils": "30.5.1",
+ "@jest/transform": "30.5.2",
+ "@jest/types": "30.5.1",
+ "babel-preset-current-node-syntax": "^1.2.0",
"chalk": "^4.1.2",
- "cjs-module-lexer": "^2.1.0",
- "collect-v8-coverage": "^1.0.2",
- "glob": "^10.5.0",
+ "expect": "30.5.2",
"graceful-fs": "^4.2.11",
- "jest-haste-map": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-mock": "30.4.1",
- "jest-regex-util": "30.4.0",
- "jest-resolve": "30.4.1",
- "jest-snapshot": "30.4.1",
- "jest-util": "30.4.1",
- "slash": "^3.0.0",
- "strip-bom": "^4.0.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runtime/node_modules/@jest/diff-sequences": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz",
- "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==",
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runtime/node_modules/@jest/environment": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz",
- "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==",
- "license": "MIT",
- "dependencies": {
- "@jest/fake-timers": "30.4.1",
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "jest-mock": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runtime/node_modules/@jest/expect": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz",
- "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==",
- "license": "MIT",
- "dependencies": {
- "expect": "30.4.1",
- "jest-snapshot": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runtime/node_modules/@jest/expect-utils": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz",
- "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==",
- "license": "MIT",
- "dependencies": {
- "@jest/get-type": "30.1.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runtime/node_modules/@jest/fake-timers": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz",
- "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==",
- "license": "MIT",
- "dependencies": {
- "@jest/types": "30.4.1",
- "@sinonjs/fake-timers": "^15.4.0",
- "@types/node": "*",
- "jest-message-util": "30.4.1",
- "jest-mock": "30.4.1",
- "jest-util": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runtime/node_modules/@jest/globals": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz",
- "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==",
- "license": "MIT",
- "dependencies": {
- "@jest/environment": "30.4.1",
- "@jest/expect": "30.4.1",
- "@jest/types": "30.4.1",
- "jest-mock": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runtime/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
+ "jest-diff": "30.5.2",
+ "jest-matcher-utils": "30.5.2",
+ "jest-message-util": "30.5.1",
+ "jest-util": "30.5.1",
+ "pretty-format": "30.5.1",
+ "semver": "^7.7.2",
+ "synckit": "^0.11.8"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runtime/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "node_modules/jest-snapshot/node_modules/@jest/schemas": {
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -20554,23 +19816,23 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runtime/node_modules/@jest/transform": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz",
- "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==",
+ "node_modules/jest-snapshot/node_modules/@jest/transform": {
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.5.2.tgz",
+ "integrity": "sha512-LBGwmaE886C41jDZ65eqR1YbXM7642MK3ZhTowZd5+xDlrDLuf4ePuDdDmVSvcr8NTVJ3MqxLkMbUCzuigUaoQ==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.27.4",
- "@jest/types": "30.4.1",
- "@jridgewell/trace-mapping": "^0.3.25",
- "babel-plugin-istanbul": "^7.0.1",
+ "@jest/types": "30.5.1",
+ "@jridgewell/trace-mapping": "^0.3.31",
+ "babel-plugin-istanbul": "^8.0.0",
"chalk": "^4.1.2",
"convert-source-map": "^2.0.0",
"fast-json-stable-stringify": "^2.1.0",
"graceful-fs": "^4.2.11",
- "jest-haste-map": "30.4.1",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
+ "jest-haste-map": "30.5.1",
+ "jest-regex-util": "30.5.0",
+ "jest-util": "30.5.1",
"pirates": "^4.0.7",
"slash": "^3.0.0",
"write-file-atomic": "^5.0.1"
@@ -20579,14 +19841,14 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runtime/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "node_modules/jest-snapshot/node_modules/@jest/types": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -20597,22 +19859,13 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runtime/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "node_modules/jest-snapshot/node_modules/@sinclair/typebox": {
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
- "node_modules/jest-runtime/node_modules/@sinonjs/fake-timers": {
- "version": "15.4.0",
- "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz",
- "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sinonjs/commons": "^3.0.1"
- }
- },
- "node_modules/jest-runtime/node_modules/ansi-styles": {
+ "node_modules/jest-snapshot/node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
@@ -20624,10 +19877,10 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/jest-runtime/node_modules/babel-plugin-istanbul": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz",
- "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==",
+ "node_modules/jest-snapshot/node_modules/babel-plugin-istanbul": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-8.0.0.tgz",
+ "integrity": "sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg==",
"license": "BSD-3-Clause",
"workspaces": [
"test/babel-8"
@@ -20637,75 +19890,30 @@
"@istanbuljs/load-nyc-config": "^1.0.0",
"@istanbuljs/schema": "^0.1.3",
"istanbul-lib-instrument": "^6.0.2",
- "test-exclude": "^6.0.0"
+ "test-exclude": "^7.0.1"
},
"engines": {
- "node": ">=12"
- }
- },
- "node_modules/jest-runtime/node_modules/brace-expansion": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
- "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/jest-runtime/node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/jest-runtime/node_modules/expect": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz",
- "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==",
+ "node_modules/jest-snapshot/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"license": "MIT",
- "dependencies": {
- "@jest/expect-utils": "30.4.1",
- "@jest/get-type": "30.1.0",
- "jest-matcher-utils": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-mock": "30.4.1",
- "jest-util": "30.4.1"
- },
"engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runtime/node_modules/glob": {
- "version": "10.5.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
- "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
- "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
+ "node": ">=12.0.0"
},
- "bin": {
- "glob": "dist/esm/bin.mjs"
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
},
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
}
},
- "node_modules/jest-runtime/node_modules/istanbul-lib-instrument": {
+ "node_modules/jest-snapshot/node_modules/istanbul-lib-instrument": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
"integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
@@ -20721,74 +19929,42 @@
"node": ">=10"
}
},
- "node_modules/jest-runtime/node_modules/jest-diff": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz",
- "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==",
- "license": "MIT",
- "dependencies": {
- "@jest/diff-sequences": "30.4.0",
- "@jest/get-type": "30.1.0",
- "chalk": "^4.1.2",
- "pretty-format": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runtime/node_modules/jest-haste-map": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz",
- "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==",
+ "node_modules/jest-snapshot/node_modules/jest-haste-map": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.5.1.tgz",
+ "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
+ "@parcel/watcher": "^2.6.0",
"@types/node": "*",
"anymatch": "^3.1.3",
"fb-watchman": "^2.0.2",
+ "fdir": "^6.5.0",
"graceful-fs": "^4.2.11",
- "jest-regex-util": "30.4.0",
- "jest-util": "30.4.1",
- "jest-worker": "30.4.1",
- "picomatch": "^4.0.3",
- "walker": "^1.0.8"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "optionalDependencies": {
- "fsevents": "^2.3.3"
- }
- },
- "node_modules/jest-runtime/node_modules/jest-matcher-utils": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz",
- "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==",
- "license": "MIT",
- "dependencies": {
- "@jest/get-type": "30.1.0",
- "chalk": "^4.1.2",
- "jest-diff": "30.4.1",
- "pretty-format": "30.4.1"
+ "jest-regex-util": "30.5.0",
+ "jest-util": "30.5.1",
+ "jest-worker": "30.5.1",
+ "picomatch": "^4.0.3"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runtime/node_modules/jest-message-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz",
- "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==",
+ "node_modules/jest-snapshot/node_modules/jest-message-util": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz",
+ "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/stack-utils": "^2.0.3",
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
- "jest-util": "30.4.1",
+ "jest-util": "30.5.1",
"picomatch": "^4.0.3",
- "pretty-format": "30.4.1",
+ "pretty-format": "30.5.1",
"slash": "^3.0.0",
"stack-utils": "^2.0.6"
},
@@ -20796,68 +19972,22 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runtime/node_modules/jest-mock": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz",
- "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==",
- "license": "MIT",
- "dependencies": {
- "@jest/types": "30.4.1",
- "@types/node": "*",
- "jest-util": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runtime/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runtime/node_modules/jest-snapshot": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz",
- "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==",
+ "node_modules/jest-snapshot/node_modules/jest-regex-util": {
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz",
+ "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==",
"license": "MIT",
- "dependencies": {
- "@babel/core": "^7.27.4",
- "@babel/generator": "^7.27.5",
- "@babel/plugin-syntax-jsx": "^7.27.1",
- "@babel/plugin-syntax-typescript": "^7.27.1",
- "@babel/types": "^7.27.3",
- "@jest/expect-utils": "30.4.1",
- "@jest/get-type": "30.1.0",
- "@jest/snapshot-utils": "30.4.1",
- "@jest/transform": "30.4.1",
- "@jest/types": "30.4.1",
- "babel-preset-current-node-syntax": "^1.2.0",
- "chalk": "^4.1.2",
- "expect": "30.4.1",
- "graceful-fs": "^4.2.11",
- "jest-diff": "30.4.1",
- "jest-matcher-utils": "30.4.1",
- "jest-message-util": "30.4.1",
- "jest-util": "30.4.1",
- "pretty-format": "30.4.1",
- "semver": "^7.7.2",
- "synckit": "^0.11.8"
- },
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runtime/node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
+ "node_modules/jest-snapshot/node_modules/jest-util": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
@@ -20868,41 +19998,10 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runtime/node_modules/jest-worker": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz",
- "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "@ungap/structured-clone": "^1.3.0",
- "jest-util": "30.4.1",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.1.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-runtime/node_modules/minimatch": {
- "version": "9.0.9",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
- "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.2"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/jest-runtime/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "node_modules/jest-snapshot/node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -20911,46 +20010,22 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/jest-runtime/node_modules/pretty-format": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz",
- "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==",
+ "node_modules/jest-snapshot/node_modules/pretty-format": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
"license": "MIT",
"dependencies": {
- "@jest/schemas": "30.4.1",
- "ansi-styles": "^5.2.0",
- "react-is-18": "npm:react-is@^18.3.1",
- "react-is-19": "npm:react-is@^19.2.5"
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-runtime/node_modules/strip-bom": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
- "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/jest-runtime/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
- "node_modules/jest-runtime/node_modules/write-file-atomic": {
+ "node_modules/jest-snapshot/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==",
@@ -20963,125 +20038,61 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
- "node_modules/jest-snapshot": {
+ "node_modules/jest-util": {
"version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz",
- "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
+ "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/core": "^7.11.6",
- "@babel/generator": "^7.7.2",
- "@babel/plugin-syntax-jsx": "^7.7.2",
- "@babel/plugin-syntax-typescript": "^7.7.2",
- "@babel/types": "^7.3.3",
- "@jest/expect-utils": "^29.7.0",
- "@jest/transform": "^29.7.0",
"@jest/types": "^29.6.3",
- "babel-preset-current-node-syntax": "^1.0.0",
+ "@types/node": "*",
"chalk": "^4.0.0",
- "expect": "^29.7.0",
+ "ci-info": "^3.2.0",
"graceful-fs": "^4.2.9",
- "jest-diff": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "jest-matcher-utils": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0",
- "natural-compare": "^1.4.0",
- "pretty-format": "^29.7.0",
- "semver": "^7.5.3"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/jest-snapshot/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/jest-snapshot/node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
+ "picomatch": "^2.2.3"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
- "node_modules/jest-snapshot/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/jest-util": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
- "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+ "node_modules/jest-util/node_modules/ci-info": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+ "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
"dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
"license": "MIT",
- "dependencies": {
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "ci-info": "^3.2.0",
- "graceful-fs": "^4.2.9",
- "picomatch": "^2.2.3"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=8"
}
},
"node_modules/jest-validate": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz",
- "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.5.1.tgz",
+ "integrity": "sha512-i/buJ56wTpxihE93hQYNMfdOThS87+HGvLZzZJmU4xggPcdTYQq051iwALLCHp3q+SkCGH+EFejQZz5EbBSkkg==",
"license": "MIT",
"dependencies": {
- "@jest/get-type": "30.1.0",
- "@jest/types": "30.4.1",
+ "@jest/get-type": "30.5.0",
+ "@jest/types": "30.5.1",
"camelcase": "^6.3.0",
"chalk": "^4.1.2",
"leven": "^3.1.0",
- "pretty-format": "30.4.1"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
- "node_modules/jest-validate/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
+ "pretty-format": "30.5.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-validate/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -21091,13 +20102,13 @@
}
},
"node_modules/jest-validate/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -21109,9 +20120,9 @@
}
},
"node_modules/jest-validate/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
"node_modules/jest-validate/node_modules/ansi-styles": {
@@ -21138,66 +20149,44 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-validate/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/jest-validate/node_modules/pretty-format": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz",
- "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz",
+ "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==",
"license": "MIT",
"dependencies": {
- "@jest/schemas": "30.4.1",
- "ansi-styles": "^5.2.0",
- "react-is-18": "npm:react-is@^18.3.1",
- "react-is-19": "npm:react-is@^19.2.5"
+ "@jest/react-is-18": "npm:react-is@^18.3.1",
+ "@jest/react-is-19": "npm:react-is@^19.2.5",
+ "@jest/schemas": "30.5.0",
+ "ansi-styles": "^5.2.0"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-watcher": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz",
- "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==",
+ "version": "30.5.2",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.5.2.tgz",
+ "integrity": "sha512-rqRgg4R11GA9m1UsEZwQeixHsgZReCn6TOHAHtIH33yXrIvoX0OGPA20Yzzjkz7X40fZvcRSxEpQppYPcWzmcQ==",
"license": "MIT",
"dependencies": {
- "@jest/test-result": "30.4.1",
- "@jest/types": "30.4.1",
+ "@jest/test-result": "30.5.2",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"ansi-escapes": "^4.3.2",
"chalk": "^4.1.2",
"emittery": "^0.13.1",
- "jest-util": "30.4.1",
+ "jest-util": "30.5.1",
"string-length": "^4.0.2"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-watcher/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/jest-watcher/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -21207,13 +20196,13 @@
}
},
"node_modules/jest-watcher/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -21225,57 +20214,99 @@
}
},
"node_modules/jest-watcher/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
- "node_modules/jest-watcher/node_modules/ansi-escapes": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
- "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "node_modules/jest-watcher/node_modules/jest-util": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
"license": "MIT",
"dependencies": {
- "type-fest": "^0.21.3"
+ "@jest/types": "30.5.1",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "graceful-fs": "^4.2.11",
+ "picomatch": "^4.0.3"
},
"engines": {
- "node": ">=8"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-watcher/node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/jest-watcher/node_modules/ci-info": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
- "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
+ "node_modules/jest-worker": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.5.1.tgz",
+ "integrity": "sha512-Cbxh5v7AoLuFRmFJSM4/aHdQ68rjXvUWr716EE0Dh3I7T+T/3FgFKhOERGXHcU2Meftq9+9zxPM3TSNyI9D+HA==",
"license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@ungap/structured-clone": "^1.3.0",
+ "jest-util": "30.5.1",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.1.1"
+ },
"engines": {
- "node": ">=8"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/@jest/schemas": {
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-watcher/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
+ "node_modules/jest-worker/node_modules/@jest/types": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
+ "dependencies": {
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/istanbul-reports": "^3.0.4",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.33",
+ "chalk": "^4.1.2"
+ },
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-watcher/node_modules/jest-util": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz",
- "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==",
+ "node_modules/jest-worker/node_modules/@sinclair/typebox": {
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
+ "license": "MIT"
+ },
+ "node_modules/jest-worker/node_modules/jest-util": {
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz",
+ "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==",
"license": "MIT",
"dependencies": {
- "@jest/types": "30.4.1",
+ "@jest/types": "30.5.1",
"@types/node": "*",
"chalk": "^4.1.2",
"ci-info": "^4.2.0",
@@ -21286,10 +20317,10 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-watcher/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "node_modules/jest-worker/node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -21298,39 +20329,10 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/jest-watcher/node_modules/type-fest": {
- "version": "0.21.3",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
- "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/jest-worker": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
- "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-util": "^29.7.0",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
"node_modules/jest-worker/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
@@ -21342,23 +20344,10 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
- "node_modules/jest/node_modules/@jest/pattern": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz",
- "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-regex-util": "30.4.0"
- },
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/jest/node_modules/@jest/schemas": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz",
- "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==",
+ "version": "30.5.0",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz",
+ "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
@@ -21368,13 +20357,13 @@
}
},
"node_modules/jest/node_modules/@jest/types": {
- "version": "30.4.1",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz",
- "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==",
+ "version": "30.5.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz",
+ "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==",
"license": "MIT",
"dependencies": {
- "@jest/pattern": "30.4.0",
- "@jest/schemas": "30.4.1",
+ "@jest/pattern": "30.5.0",
+ "@jest/schemas": "30.5.0",
"@types/istanbul-lib-coverage": "^2.0.6",
"@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
@@ -21386,24 +20375,15 @@
}
},
"node_modules/jest/node_modules/@sinclair/typebox": {
- "version": "0.34.49",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
- "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==",
+ "version": "0.34.52",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz",
+ "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==",
"license": "MIT"
},
- "node_modules/jest/node_modules/jest-regex-util": {
- "version": "30.4.0",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
- "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==",
- "license": "MIT",
- "engines": {
- "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
- }
- },
"node_modules/joi": {
- "version": "18.2.1",
- "resolved": "https://registry.npmjs.org/joi/-/joi-18.2.1.tgz",
- "integrity": "sha512-2/OKlogiESf2Nh3TFCrRjrr9z1DRHeW0I+KReF67+4J0Ns+8hBtHRmoWAZ2OFU6I5+TWLEe6sVlSdXPjHm5UbQ==",
+ "version": "18.3.0",
+ "resolved": "https://registry.npmjs.org/joi/-/joi-18.3.0.tgz",
+ "integrity": "sha512-/RwAK4BAfzcdbdFg073tzq4/xhY6Q7lMd5AmjqTL6pu0QFJdEmtswgm8BvgwXvOKuPNYQ8XrdQrrW/gF65LiNw==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/address": "^5.1.1",
@@ -21429,6 +20409,7 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/jpeg-exif/-/jpeg-exif-1.1.4.tgz",
"integrity": "sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==",
+ "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"license": "MIT"
},
"node_modules/jquery": {
@@ -21444,10 +20425,20 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
+ "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
"dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -21496,6 +20487,30 @@
}
}
},
+ "node_modules/jsdom/node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/jsdom/node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -21783,8 +20798,93 @@
"rfdc": "^1.4.1",
"wrap-ansi": "^9.0.0"
},
- "engines": {
- "node": ">=20.0.0"
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/listr2/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/listr2/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/listr2/node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/listr2/node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/listr2/node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/listr2/node_modules/wrap-ansi": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/loader-runner": {
@@ -21813,9 +20913,9 @@
}
},
"node_modules/lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT"
},
"node_modules/lodash.debounce": {
@@ -21883,7 +20983,9 @@
}
},
"node_modules/log-update/node_modules/ansi-escapes": {
- "version": "7.2.0",
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz",
+ "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -21909,6 +21011,44 @@
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
+ "node_modules/log-update/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/log-update/node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/log-update/node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/log-update/node_modules/strip-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
@@ -21925,6 +21065,24 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
+ "node_modules/log-update/node_modules/wrap-ansi": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -22013,6 +21171,7 @@
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
"integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"tmpl": "1.0.5"
@@ -22147,38 +21306,6 @@
"integrity": "sha512-jR1MwkLaZGa8Zftct9ZFruyWFrdl9ZyD2OliXNy9Qq5bBPeg5wHVpBQF9p5GjnicSDQqvBVpysxTPKmWdsfWMA==",
"license": "ISC"
},
- "node_modules/maplibre-gl/node_modules/global-prefix": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz",
- "integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==",
- "license": "MIT",
- "dependencies": {
- "ini": "^4.1.3",
- "kind-of": "^6.0.3",
- "which": "^4.0.0"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/maplibre-gl/node_modules/ini": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz",
- "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==",
- "license": "ISC",
- "engines": {
- "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
- }
- },
- "node_modules/maplibre-gl/node_modules/isexe": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz",
- "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/maplibre-gl/node_modules/potpack": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/potpack/-/potpack-2.1.0.tgz",
@@ -22206,21 +21333,6 @@
"integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==",
"license": "ISC"
},
- "node_modules/maplibre-gl/node_modules/which": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
- "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
- "license": "ISC",
- "dependencies": {
- "isexe": "^3.1.1"
- },
- "bin": {
- "node-which": "bin/which.js"
- },
- "engines": {
- "node": "^16.13.0 || >=18.0.0"
- }
- },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -22284,6 +21396,15 @@
"url": "https://github.com/sindresorhus/mem?sponsor=1"
}
},
+ "node_modules/mem/node_modules/mimic-fn": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
+ "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
@@ -22346,9 +21467,9 @@
}
},
"node_modules/miller-rabin/node_modules/bn.js": {
- "version": "4.12.2",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
- "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
+ "version": "4.12.5",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz",
+ "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==",
"license": "MIT"
},
"node_modules/mime-db": {
@@ -22373,12 +21494,12 @@
}
},
"node_modules/mimic-fn": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
- "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=6"
}
},
"node_modules/mimic-function": {
@@ -22417,9 +21538,10 @@
"license": "MIT"
},
"node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
@@ -22438,10 +21560,10 @@
}
},
"node_modules/minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "license": "ISC",
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
@@ -22486,9 +21608,9 @@
}
},
"node_modules/moment": {
- "version": "2.30.1",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
- "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
+ "version": "2.31.0",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.31.0.tgz",
+ "integrity": "sha512-0acOTfMiWOheYS4eoWb80yYMb/JLvVv9SHbs2PehaDzfUG0Bw855SKyk0IKTnPGa5+U2bmi3W68l1+sGLX/pvw==",
"license": "MIT",
"engines": {
"node": "*"
@@ -22573,6 +21695,19 @@
}
}
},
+ "node_modules/msw/node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/murmurhash-js": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz",
@@ -22603,9 +21738,9 @@
}
},
"node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "version": "3.3.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
+ "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
"dev": true,
"funding": [
{
@@ -22622,11 +21757,12 @@
}
},
"node_modules/napi-postinstall": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
- "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==",
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.7.tgz",
+ "integrity": "sha512-jyU4cU4kVAmy/3qjP+DcoQTNXfINHc+dFw54gGqWC8nu0mpV0kOnk7kff+J+Q18SbX0k3Np8Rm7yFjd3phatNQ==",
"license": "MIT",
"bin": {
+ "napi-patch": "lib/patch.js",
"napi-postinstall": "lib/cli.js"
},
"engines": {
@@ -22709,9 +21845,7 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
- "dev": true,
- "license": "MIT",
- "optional": true
+ "license": "MIT"
},
"node_modules/node-int64": {
"version": "0.4.0",
@@ -22720,9 +21854,9 @@
"license": "MIT"
},
"node_modules/node-releases": {
- "version": "2.0.46",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz",
- "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==",
+ "version": "2.0.61",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.61.tgz",
+ "integrity": "sha512-5NUN6QCxNFrS9oEA9RPS+d0AP+ooGBzzovY2JhIpDRHacPvOhHjXjUsUMX9qp4nLHZ+5jVgXB2jj/P4arGOE/g==",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -22941,16 +22075,15 @@
}
},
"node_modules/onetime": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
- "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
- "dev": true,
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"license": "MIT",
"dependencies": {
- "mimic-function": "^5.0.0"
+ "mimic-fn": "^2.1.0"
},
"engines": {
- "node": ">=18"
+ "node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -23009,15 +22142,15 @@
}
},
"node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"license": "MIT",
"dependencies": {
- "p-try": "^2.0.0"
+ "yocto-queue": "^0.1.0"
},
"engines": {
- "node": ">=6"
+ "node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -23035,6 +22168,21 @@
"node": ">=8"
}
},
+ "node_modules/p-locate/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
@@ -23075,16 +22223,15 @@
"license": "MIT"
},
"node_modules/parse-asn1": {
- "version": "5.1.7",
- "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz",
- "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==",
+ "version": "5.1.10",
+ "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.10.tgz",
+ "integrity": "sha512-gBlTKLbYnAJN9WjmawVOMfaGvVoy4lX0LH18t7ccwtEG9RgAa4ETf2eu3d2J4e/5SoCjCms0A0UF4tTw/Eyn6Q==",
"license": "ISC",
"dependencies": {
"asn1.js": "^4.10.1",
"browserify-aes": "^1.2.0",
"evp_bytestokey": "^1.0.3",
- "hash-base": "~3.0",
- "pbkdf2": "^3.1.2",
+ "pbkdf2": "^3.1.7",
"safe-buffer": "^5.2.1"
},
"engines": {
@@ -23198,6 +22345,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -23287,51 +22435,20 @@
}
},
"node_modules/pbkdf2": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.3.tgz",
- "integrity": "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==",
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.7.tgz",
+ "integrity": "sha512-nS4mvFgVwUrecPTRdpdseM2fwpdfnX0/HibA8DgBRQtihUAzHQYJFU2l+OlEtiXtpqkrMvCPDwyXMUnASDt6Qw==",
"license": "MIT",
"dependencies": {
- "create-hash": "~1.1.3",
+ "create-hash": "^1.2.0",
"create-hmac": "^1.1.7",
- "ripemd160": "=2.0.1",
+ "ripemd160": "^2.0.3",
"safe-buffer": "^5.2.1",
- "sha.js": "^2.4.11",
- "to-buffer": "^1.2.0"
+ "sha.js": "^2.4.12",
+ "to-buffer": "^1.2.2"
},
"engines": {
- "node": ">=0.12"
- }
- },
- "node_modules/pbkdf2/node_modules/create-hash": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz",
- "integrity": "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==",
- "license": "MIT",
- "dependencies": {
- "cipher-base": "^1.0.1",
- "inherits": "^2.0.1",
- "ripemd160": "^2.0.0",
- "sha.js": "^2.4.0"
- }
- },
- "node_modules/pbkdf2/node_modules/hash-base": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz",
- "integrity": "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==",
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.1"
- }
- },
- "node_modules/pbkdf2/node_modules/ripemd160": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz",
- "integrity": "sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==",
- "license": "MIT",
- "dependencies": {
- "hash-base": "^2.0.0",
- "inherits": "^2.0.1"
+ "node": ">= 0.10"
}
},
"node_modules/pdfast": {
@@ -23374,9 +22491,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"license": "MIT",
"engines": {
"node": ">=8.6"
@@ -23420,9 +22537,9 @@
}
},
"node_modules/plotly.js": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-3.5.1.tgz",
- "integrity": "sha512-rUzQ7Q46whi1aWT2JlvKE2dnryNORPrxyy66OGSMXgejK3XgD4ysD9SapMOI1RzzUJyX0KbfVDFcB9/DqOyH9Q==",
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-3.7.0.tgz",
+ "integrity": "sha512-r17/w3Ob/E+A1vqN382PV/6Dz+IAxAsxNCpWcnGfGFLqE0M6vPUa+ve7HPhivrl9LMicZg2Fy0U5GbpauHo32A==",
"license": "MIT",
"dependencies": {
"@plotly/d3": "3.8.2",
@@ -23614,9 +22731,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.6",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
- "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "version": "8.5.32",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz",
+ "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==",
"dev": true,
"funding": [
{
@@ -23634,9 +22751,9 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.11",
+ "nanoid": "^3.3.20",
"picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
+ "source-map-js": "^1.2.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -23685,9 +22802,9 @@
}
},
"node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.7",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.7.tgz",
+ "integrity": "sha512-wWBro/7hJ40ye6TPiXkUQoXKCw23xSsjriUvqd7Uls9Sso3oLiON1flc18cCdnjgCRXw4fBVqUJJeOdDTh3P3g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -23782,9 +22899,9 @@
"license": "MIT"
},
"node_modules/probe-image-size": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.3.0.tgz",
- "integrity": "sha512-7CaDeBwiAbh6ohXsvLbAZhO7wzsZAmaevfxe39qvCwRh8LyaZfDlBGGLU1CCTgrTLtCOdwBBhjOrIHaIIimHfQ==",
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.5.0.tgz",
+ "integrity": "sha512-ietdx4XPgmnzHRZOq1/KtcnsBkL4bRsBe+Szj5Tuq1/KPqDG42OspG1IhJb7vz6Z9pSzhp0J6UPnw6hujJTWoQ==",
"funding": [
{
"type": "github",
@@ -23798,8 +22915,7 @@
"license": "MIT",
"dependencies": {
"lodash.merge": "^4.6.2",
- "needle": "^2.5.2",
- "stream-parser": "~0.3.1"
+ "needle": "^2.5.2"
}
},
"node_modules/process-nextick-args": {
@@ -23887,9 +23003,9 @@
}
},
"node_modules/public-encrypt/node_modules/bn.js": {
- "version": "4.12.2",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
- "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
+ "version": "4.12.5",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz",
+ "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==",
"license": "MIT"
},
"node_modules/punycode": {
@@ -24801,20 +23917,6 @@
"integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==",
"license": "MIT"
},
- "node_modules/react-is-18": {
- "name": "react-is",
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "license": "MIT"
- },
- "node_modules/react-is-19": {
- "name": "react-is",
- "version": "19.2.6",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz",
- "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==",
- "license": "MIT"
- },
"node_modules/react-leaflet": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz",
@@ -25299,9 +24401,9 @@
}
},
"node_modules/react-to-pdf/node_modules/dompurify": {
- "version": "2.5.8",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz",
- "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==",
+ "version": "2.5.9",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.9.tgz",
+ "integrity": "sha512-i6mvVmWN4xo9LrhCOZrDgSs9noW6nOahbrmzjRbPF36YPyj5Ue5lgok0MHDWkG7xzpWFO2OYttXdzM7rJxHvNA==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optional": true
},
@@ -25369,6 +24471,7 @@
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
"integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==",
+ "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).",
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
@@ -25508,9 +24611,9 @@
}
},
"node_modules/read-excel-file/node_modules/fflate": {
- "version": "0.7.4",
- "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
- "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==",
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.5.tgz",
+ "integrity": "sha512-QieYf//cis6ywHNi5qW1+PXPQ4bC+XVJAtS4AXIML8P76GroEiOxm/oQtn1f02UkJY1+KsXMJcC+R2v/Eg4G3g==",
"license": "MIT"
},
"node_modules/readable-stream": {
@@ -25552,6 +24655,7 @@
"version": "2.15.4",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz",
"integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==",
+ "deprecated": "1.x and 2.x branches are no longer active. Bump to Recharts v3 to receive latest features and bugfixes. See https://github.com/recharts/recharts/wiki/3.0-migration-guide",
"license": "MIT",
"dependencies": {
"clsx": "^2.0.0",
@@ -25979,6 +25083,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/restore-cursor/node_modules/onetime": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
+ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-function": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/reusify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
@@ -26017,21 +25137,61 @@
"dependencies": {
"glob": "^7.1.3"
},
- "bin": {
- "rimraf": "bin.js"
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rimraf/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ripemd160": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
- "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz",
+ "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==",
"license": "MIT",
"dependencies": {
- "hash-base": "^3.0.0",
- "inherits": "^2.0.1"
+ "hash-base": "^3.1.2",
+ "inherits": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/ripemd160/node_modules/hash-base": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz",
+ "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.4",
+ "readable-stream": "^2.3.8",
+ "safe-buffer": "^5.2.1",
+ "to-buffer": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
}
},
"node_modules/robust-predicates": {
@@ -26041,13 +25201,13 @@
"license": "Unlicense"
},
"node_modules/rollup": {
- "version": "4.49.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.49.0.tgz",
- "integrity": "sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==",
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.66.0.tgz",
+ "integrity": "sha512-Onq6/qO4ARhxIs+jYiHE0pYehadLyMECAiGEqQ+0/aWND3+2PmifJIIqBLYtuBODqHTlOUEQFtIVPyCy0Lck7A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/estree": "1.0.8"
+ "@types/estree": "1.0.9"
},
"bin": {
"rollup": "dist/bin/rollup"
@@ -26057,43 +25217,35 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.49.0",
- "@rollup/rollup-android-arm64": "4.49.0",
- "@rollup/rollup-darwin-arm64": "4.49.0",
- "@rollup/rollup-darwin-x64": "4.49.0",
- "@rollup/rollup-freebsd-arm64": "4.49.0",
- "@rollup/rollup-freebsd-x64": "4.49.0",
- "@rollup/rollup-linux-arm-gnueabihf": "4.49.0",
- "@rollup/rollup-linux-arm-musleabihf": "4.49.0",
- "@rollup/rollup-linux-arm64-gnu": "4.49.0",
- "@rollup/rollup-linux-arm64-musl": "4.49.0",
- "@rollup/rollup-linux-loongarch64-gnu": "4.49.0",
- "@rollup/rollup-linux-ppc64-gnu": "4.49.0",
- "@rollup/rollup-linux-riscv64-gnu": "4.49.0",
- "@rollup/rollup-linux-riscv64-musl": "4.49.0",
- "@rollup/rollup-linux-s390x-gnu": "4.49.0",
- "@rollup/rollup-linux-x64-gnu": "4.49.0",
- "@rollup/rollup-linux-x64-musl": "4.49.0",
- "@rollup/rollup-win32-arm64-msvc": "4.49.0",
- "@rollup/rollup-win32-ia32-msvc": "4.49.0",
- "@rollup/rollup-win32-x64-msvc": "4.49.0",
+ "@napi-rs/lzma-linux-x64-gnu": "1.5.1",
+ "@rollup/rollup-android-arm-eabi": "4.66.0",
+ "@rollup/rollup-android-arm64": "4.66.0",
+ "@rollup/rollup-darwin-arm64": "4.66.0",
+ "@rollup/rollup-darwin-x64": "4.66.0",
+ "@rollup/rollup-freebsd-arm64": "4.66.0",
+ "@rollup/rollup-freebsd-x64": "4.66.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.66.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.66.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.66.0",
+ "@rollup/rollup-linux-arm64-musl": "4.66.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.66.0",
+ "@rollup/rollup-linux-loong64-musl": "4.66.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.66.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.66.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.66.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.66.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.66.0",
+ "@rollup/rollup-linux-x64-gnu": "4.66.0",
+ "@rollup/rollup-linux-x64-musl": "4.66.0",
+ "@rollup/rollup-openbsd-x64": "4.66.0",
+ "@rollup/rollup-openharmony-arm64": "4.66.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.66.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.66.0",
+ "@rollup/rollup-win32-x64-gnu": "4.66.0",
+ "@rollup/rollup-win32-x64-msvc": "4.66.0",
"fsevents": "~2.3.2"
}
},
- "node_modules/rollup/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/rrweb-cssom": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
@@ -26643,22 +25795,6 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
- "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "get-east-asian-width": "^1.3.1"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -26669,34 +25805,15 @@
}
},
"node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
+ "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
- "node_modules/source-map-support": {
- "version": "0.5.21",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
- "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
- "license": "MIT",
- "dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- }
- },
- "node_modules/source-map-support/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -26825,30 +25942,6 @@
"node": ">= 6"
}
},
- "node_modules/stream-parser": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz",
- "integrity": "sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==",
- "license": "MIT",
- "dependencies": {
- "debug": "2"
- }
- },
- "node_modules/stream-parser/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/stream-parser/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "license": "MIT"
- },
"node_modules/stream-shift": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
@@ -26916,17 +26009,17 @@
}
},
"node_modules/string-width": {
- "version": "8.2.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
- "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
- "dev": true,
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"license": "MIT",
"dependencies": {
- "get-east-asian-width": "^1.5.0",
- "strip-ansi": "^7.1.2"
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
},
"engines": {
- "node": ">=20"
+ "node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -26966,7 +26059,6 @@
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -26976,13 +26068,12 @@
}
},
"node_modules/string-width/node_modules/strip-ansi": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
- "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
- "dev": true,
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"license": "MIT",
"dependencies": {
- "ansi-regex": "^6.0.1"
+ "ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
@@ -27130,13 +26221,12 @@
}
},
"node_modules/strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
- "dev": true,
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
"license": "MIT",
"engines": {
- "node": ">=4"
+ "node": ">=8"
}
},
"node_modules/strip-final-newline": {
@@ -27908,18 +26998,73 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT"
},
+ "node_modules/terser/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/terser/node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
"node_modules/test-exclude": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
- "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz",
+ "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==",
"license": "ISC",
"dependencies": {
"@istanbuljs/schema": "^0.1.2",
- "glob": "^7.1.4",
- "minimatch": "^3.0.4"
+ "glob": "^10.4.1",
+ "minimatch": "^10.2.2"
},
"engines": {
- "node": ">=8"
+ "node": ">=18"
+ }
+ },
+ "node_modules/test-exclude/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/test-exclude/node_modules/brace-expansion": {
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/test-exclude/node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/text-segmentation": {
@@ -28031,9 +27176,9 @@
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -28076,9 +27221,9 @@
}
},
"node_modules/tinyspy": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz",
- "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==",
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.6.tgz",
+ "integrity": "sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -28109,12 +27254,13 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true,
"license": "BSD-3-Clause"
},
"node_modules/to-buffer": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz",
- "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz",
+ "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==",
"license": "MIT",
"dependencies": {
"isarray": "^2.0.5",
@@ -28283,6 +27429,16 @@
"json5": "lib/cli.js"
}
},
+ "node_modules/tsconfig-paths/node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -28318,13 +27474,12 @@
}
},
"node_modules/type-fest": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
- "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
- "dev": true,
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
"license": "(MIT OR CC0-1.0)",
"engines": {
- "node": ">=16"
+ "node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -28478,9 +27633,9 @@
}
},
"node_modules/undici": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz",
- "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==",
+ "version": "7.30.1",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.1.tgz",
+ "integrity": "sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
@@ -28628,9 +27783,9 @@
}
},
"node_modules/update-browserslist-db": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
- "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz",
+ "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==",
"funding": [
{
"type": "opencollective",
@@ -28739,6 +27894,7 @@
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
@@ -28800,9 +27956,9 @@
}
},
"node_modules/vite": {
- "version": "6.3.5",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
- "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
+ "version": "6.4.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.5.tgz",
+ "integrity": "sha512-4enwjFfi4IwtEh0w+jPzpLVOFQiPHmmzPxcHXQOyEur47o6u0jE573DmGpjA+7irXoMLYRZD5ZaFUEneS3abPg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -28916,9 +28072,9 @@
}
},
"node_modules/vite/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -28929,20 +28085,20 @@
}
},
"node_modules/vitest": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
- "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz",
+ "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/chai": "^5.2.2",
- "@vitest/expect": "3.2.4",
- "@vitest/mocker": "3.2.4",
- "@vitest/pretty-format": "^3.2.4",
- "@vitest/runner": "3.2.4",
- "@vitest/snapshot": "3.2.4",
- "@vitest/spy": "3.2.4",
- "@vitest/utils": "3.2.4",
+ "@vitest/expect": "3.2.7",
+ "@vitest/mocker": "3.2.7",
+ "@vitest/pretty-format": "^3.2.7",
+ "@vitest/runner": "3.2.7",
+ "@vitest/snapshot": "3.2.7",
+ "@vitest/spy": "3.2.7",
+ "@vitest/utils": "3.2.7",
"chai": "^5.2.0",
"debug": "^4.4.1",
"expect-type": "^1.2.1",
@@ -28972,8 +28128,8 @@
"@edge-runtime/vm": "*",
"@types/debug": "^4.1.12",
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
- "@vitest/browser": "3.2.4",
- "@vitest/ui": "3.2.4",
+ "@vitest/browser": "3.2.7",
+ "@vitest/ui": "3.2.7",
"happy-dom": "*",
"jsdom": "*"
},
@@ -29002,9 +28158,9 @@
}
},
"node_modules/vitest/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -29042,6 +28198,7 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
"integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
"license": "Apache-2.0",
"dependencies": {
"makeerror": "1.0.12"
@@ -29168,6 +28325,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
"license": "MIT",
"dependencies": {
"iconv-lite": "0.6.3"
@@ -29364,18 +28522,17 @@
}
},
"node_modules/wrap-ansi": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
- "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
- "dev": true,
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"license": "MIT",
"dependencies": {
- "ansi-styles": "^6.2.1",
- "string-width": "^7.0.0",
- "strip-ansi": "^7.1.0"
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
},
"engines": {
- "node": ">=18"
+ "node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
@@ -29432,7 +28589,6 @@
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -29445,7 +28601,6 @@
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -29454,39 +28609,13 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/wrap-ansi/node_modules/emoji-regex": {
- "version": "10.6.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
- "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/wrap-ansi/node_modules/string-width": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
- "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^10.3.0",
- "get-east-asian-width": "^1.0.0",
- "strip-ansi": "^7.1.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/wrap-ansi/node_modules/strip-ansi": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
- "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
- "dev": true,
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"license": "MIT",
"dependencies": {
- "ansi-regex": "^6.0.1"
+ "ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
@@ -29523,9 +28652,9 @@
"license": "ISC"
},
"node_modules/ws": {
- "version": "8.18.3",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
- "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "version": "8.22.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz",
+ "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -29619,9 +28748,9 @@
"license": "ISC"
},
"node_modules/yaml": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
- "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz",
+ "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==",
"dev": true,
"license": "ISC",
"bin": {
diff --git a/src/actions/__tests__/userManagement.test.js b/src/actions/__tests__/userManagement.test.js
index 6dbd17a20bc..99c9ba71493 100644
--- a/src/actions/__tests__/userManagement.test.js
+++ b/src/actions/__tests__/userManagement.test.js
@@ -87,21 +87,50 @@ describe('User Management Actions', () => {
]);
});
- it('allows an empty reason and removes a previously saved reason', async () => {
- const mockUser = { _id: '123', notRehireableReason: 'Previous reason' };
- axios.patch.mockResolvedValueOnce({ data: {} });
+ it('stores the audit history returned by the backend', async () => {
+ const mockUser = { _id: '123' };
+ const history = [
+ {
+ action: 'submitted',
+ reason: 'Missed commitments',
+ changedAt: '2026-01-02T03:04:00.000Z',
+ changedBy: { username: 'admin@example.org', firstName: 'Alex', lastName: 'Admin' },
+ },
+ ];
+ axios.patch.mockResolvedValueOnce({
+ data: {
+ notRehireableReason: 'Missed commitments',
+ notRehireableReasonHistory: history,
+ },
+ });
- await store.dispatch(actions.updateRehireableStatus(mockUser, false, ''));
+ await store.dispatch(actions.updateRehireableStatus(mockUser, false, 'Missed commitments'));
- expect(axios.patch).toHaveBeenCalledWith(ENDPOINTS.UPDATE_REHIREABLE_STATUS(mockUser._id), {
- isRehireable: false,
- notRehireableReason: '',
- });
expect(store.getActions()).toEqual([
- { type: 'USER_PROFILE_UPDATE', user: { _id: '123', isRehireable: false } },
+ {
+ type: 'USER_PROFILE_UPDATE',
+ user: {
+ _id: '123',
+ isRehireable: false,
+ notRehireableReason: 'Missed commitments',
+ notRehireableReasonHistory: history,
+ },
+ },
]);
});
+ it('does not send an empty reason when changing to not rehireable', async () => {
+ const mockUser = { _id: '123', notRehireableReason: 'Previous reason' };
+ axios.patch.mockResolvedValueOnce({ data: {} });
+
+ await expect(
+ store.dispatch(actions.updateRehireableStatus(mockUser, false, ' ')),
+ ).rejects.toThrow('A reason is required');
+
+ expect(axios.patch).not.toHaveBeenCalled();
+ expect(store.getActions()).toEqual([]);
+ });
+
it('clears a saved reason when changing back to rehireable', async () => {
const mockUser = { _id: '123', notRehireableReason: 'Previous reason' };
axios.patch.mockResolvedValueOnce({ data: {} });
@@ -250,6 +279,37 @@ describe('User Management Actions', () => {
await store.dispatch(actions.getUserProfileBasicInfo({ source: mockSource }));
expect(store.getActions()).toEqual(expectedActions);
});
+
+ it('fetches a single user basic info by userId using the query-param contract', async () => {
+ const mockUserId = 'user-123';
+ const mockBasicInfo = { _id: mockUserId, firstName: 'John', lastName: 'Doe' };
+
+ axios.get.mockResolvedValueOnce({ data: mockBasicInfo });
+
+ const expectedActions = [
+ { type: 'FETCH_USER_PROFILE_BASIC_INFO' },
+ { type: 'RECEIVE_USER_PROFILE_BASIC_INFO', payload: mockBasicInfo }
+ ];
+
+ await store.dispatch(actions.getUserProfileBasicInfo({ userId: mockUserId }));
+ expect(store.getActions()).toEqual(expectedActions);
+ expect(axios.get).toHaveBeenCalledWith(ENDPOINTS.USER_PROFILE_BASIC_INFO_BY_ID(mockUserId));
+ });
+
+ it('does not leave the fetch pending when the userId lookup is rejected (400)', async () => {
+ const mockUserId = 'user-123';
+ const badRequestError = { response: { status: 400, data: { error: 'Source parameter is required' } } };
+
+ axios.get.mockRejectedValueOnce(badRequestError);
+
+ const expectedActions = [
+ { type: 'FETCH_USER_PROFILE_BASIC_INFO' },
+ { type: 'FETCH_USER_PROFILE_BASIC_INFO_ERROR' }
+ ];
+
+ await store.dispatch(actions.getUserProfileBasicInfo({ userId: mockUserId }));
+ expect(store.getActions()).toEqual(expectedActions);
+ });
});
describe('UI State Actions', () => {
diff --git a/src/actions/__tests__/weeklyProjectSummaryActions.test.js b/src/actions/__tests__/weeklyProjectSummaryActions.test.js
new file mode 100644
index 00000000000..7d5f47395af
--- /dev/null
+++ b/src/actions/__tests__/weeklyProjectSummaryActions.test.js
@@ -0,0 +1,96 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import configureMockStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+import axios from 'axios';
+import {
+ fetchWeeklyProjectSummaryProjectStatus,
+} from '../bmdashboard/weeklyProjectSummaryActions';
+import {
+ FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_BEGIN,
+ FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_ERROR,
+ FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_SUCCESS,
+} from '../../constants/bmdashboard/weeklyProjectSummaryConstants';
+
+vi.mock('axios');
+
+const mockStore = configureMockStore([thunk]);
+
+const createDeferred = () => {
+ let resolve;
+ let reject;
+ const promise = new Promise((promiseResolve, promiseReject) => {
+ resolve = promiseResolve;
+ reject = promiseReject;
+ });
+
+ return { promise, resolve, reject };
+};
+
+describe('weeklyProjectSummaryActions Project Status requests', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('dispatches unique request metadata for rapid Project Status requests', async () => {
+ const firstRequest = createDeferred();
+ const secondRequest = createDeferred();
+ axios.get.mockReturnValueOnce(firstRequest.promise).mockReturnValueOnce(secondRequest.promise);
+ const store = mockStore({});
+
+ const firstDispatch = store.dispatch(
+ fetchWeeklyProjectSummaryProjectStatus({
+ startDate: '2026-09-13',
+ endDate: '2026-09-19',
+ projectId: '',
+ comparisonStartDate: null,
+ comparisonEndDate: undefined,
+ }),
+ );
+ const secondDispatch = store.dispatch(
+ fetchWeeklyProjectSummaryProjectStatus({
+ startDate: '2026-09-20',
+ endDate: '2026-09-26',
+ }),
+ );
+
+ const [firstBegin, secondBegin] = store.getActions();
+ expect(firstBegin.type).toBe(FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_BEGIN);
+ expect(secondBegin.type).toBe(FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_BEGIN);
+ expect(firstBegin.meta.requestId).not.toBe(secondBegin.meta.requestId);
+ expect(firstBegin.meta.requestKey).toBe('endDate=2026-09-19&startDate=2026-09-13');
+ expect(secondBegin.meta.requestKey).toBe('endDate=2026-09-26&startDate=2026-09-20');
+
+ secondRequest.resolve({ data: { current: { metrics: { totalProjects: { value: 2 } } } } });
+ await secondDispatch;
+ firstRequest.resolve({ data: { current: { metrics: { totalProjects: { value: 1 } } } } });
+ await firstDispatch;
+
+ const successActions = store
+ .getActions()
+ .filter(action => action.type === FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_SUCCESS);
+ expect(successActions).toHaveLength(2);
+ expect(successActions[0].meta.requestId).toBe(secondBegin.meta.requestId);
+ expect(successActions[1].meta.requestId).toBe(firstBegin.meta.requestId);
+ });
+
+ it('dispatches the active request metadata on Project Status failure', async () => {
+ const error = new Error('network failure');
+ axios.get.mockRejectedValue(error);
+ const store = mockStore({});
+
+ const result = await store.dispatch(
+ fetchWeeklyProjectSummaryProjectStatus({
+ startDate: '2026-09-13',
+ endDate: '2026-09-19',
+ }),
+ );
+
+ const [beginAction, errorAction] = store.getActions();
+ expect(result).toBeNull();
+ expect(errorAction).toMatchObject({
+ type: FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_ERROR,
+ payload: error,
+ meta: beginAction.meta,
+ });
+ });
+});
diff --git a/src/actions/actionTypes.js b/src/actions/actionTypes.js
new file mode 100644
index 00000000000..943ef0d67e2
--- /dev/null
+++ b/src/actions/actionTypes.js
@@ -0,0 +1,2 @@
+export const FETCH_ALL_BADGES = 'FETCH_ALL_BADGES';
+export const FETCH_BADGES_FAILURE = 'FETCH_BADGES_FAILURE';
\ No newline at end of file
diff --git a/src/actions/bmdashboard/weeklyProjectSummaryActions.js b/src/actions/bmdashboard/weeklyProjectSummaryActions.js
index 74836a9c8dd..07e9dfc2f65 100644
--- a/src/actions/bmdashboard/weeklyProjectSummaryActions.js
+++ b/src/actions/bmdashboard/weeklyProjectSummaryActions.js
@@ -1,7 +1,12 @@
+import axios from 'axios';
+import { ENDPOINTS } from '~/utils/URL';
import {
SET_PROJECT_FILTER,
SET_DATE_RANGE_FILTER,
SET_COMPARISON_PERIOD_FILTER,
+ FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_BEGIN,
+ FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_SUCCESS,
+ FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_ERROR,
} from '../../constants/bmdashboard/weeklyProjectSummaryConstants';
export const setProjectFilter = project => ({
@@ -18,3 +23,64 @@ export const setComparisonPeriodFilter = comparisonPeriod => ({
type: SET_COMPARISON_PERIOD_FILTER,
payload: comparisonPeriod,
});
+
+let weeklyProjectSummaryProjectStatusRequestCounter = 0;
+
+const createWeeklyProjectSummaryProjectStatusRequestId = () => {
+ weeklyProjectSummaryProjectStatusRequestCounter += 1;
+ return `weekly-project-summary-project-status-${Date.now()}-${weeklyProjectSummaryProjectStatusRequestCounter}`;
+};
+
+export const getWeeklyProjectSummaryProjectStatusRequestKey = params => {
+ const queryParams = new URLSearchParams();
+ Object.entries(params || {})
+ .filter(([, value]) => value !== undefined && value !== null && value !== '')
+ .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
+ .forEach(([key, value]) => {
+ queryParams.append(key, value);
+ });
+
+ return queryParams.toString();
+};
+
+export const fetchWeeklyProjectSummaryProjectStatusBegin = meta => ({
+ type: FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_BEGIN,
+ meta,
+});
+
+export const fetchWeeklyProjectSummaryProjectStatusSuccess = (payload, meta) => ({
+ type: FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_SUCCESS,
+ payload,
+ meta,
+});
+
+export const fetchWeeklyProjectSummaryProjectStatusError = (error, meta) => ({
+ type: FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_ERROR,
+ payload: error,
+ meta,
+});
+
+export const fetchWeeklyProjectSummaryProjectStatus = params => async dispatch => {
+ const requestId = createWeeklyProjectSummaryProjectStatusRequestId();
+ const requestKey = getWeeklyProjectSummaryProjectStatusRequestKey(params);
+ const requestMeta = { requestId, requestKey };
+
+ dispatch(fetchWeeklyProjectSummaryProjectStatusBegin(requestMeta));
+
+ try {
+ const queryParams = new URLSearchParams();
+ Object.entries(params || {}).forEach(([key, value]) => {
+ if (value !== undefined && value !== null && value !== '') {
+ queryParams.append(key, value);
+ }
+ });
+
+ const url = `${ENDPOINTS.BM_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS}?${queryParams.toString()}`;
+ const response = await axios.get(url);
+ dispatch(fetchWeeklyProjectSummaryProjectStatusSuccess(response.data, requestMeta));
+ return response.data;
+ } catch (error) {
+ dispatch(fetchWeeklyProjectSummaryProjectStatusError(error, requestMeta));
+ return null;
+ }
+};
diff --git a/src/actions/communityPortal/eventActions.js b/src/actions/communityPortal/eventActions.js
deleted file mode 100644
index 4ebd88af93a..00000000000
--- a/src/actions/communityPortal/eventActions.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import axios from 'axios';
-import { ENDPOINTS } from '../../utils/URL';
-import { toast } from 'react-toastify';
-
-export const createEvent = eventData => {
- return async dispatch => {
- try {
- const res = await axios.post(ENDPOINTS.EVENTS, eventData);
- if (res.status === 201) {
- toast.success('Event created successfully!');
- return { success: true, event: res.data };
- }
- return { success: false, error: 'Unexpected response status' };
- } catch (error) {
- if (error.response?.status === 500) {
- toast.error('Error creating event. Please try again.');
- } else if (error.response?.status === 404 || error.response?.status === 403 || error.response?.status === 400) {
- toast.error('Permission or Validation Error. Please check your input or access rights');
- } else {
- toast.error('Error creating event. Please try again.');
- }
- return { success: false, error: error.response?.data || error.message };
- }
- };
-};
-
diff --git a/src/actions/promotionActions.js b/src/actions/promotionActions.js
index f66a05d6124..06f8571e83c 100644
--- a/src/actions/promotionActions.js
+++ b/src/actions/promotionActions.js
@@ -6,6 +6,11 @@ export const getPromotionEligibility = async requestor => {
return res.data;
};
+export const getPromotionPending = async requestor => {
+ const res = await axios.post(ENDPOINTS.PROMOTION_PENDING, { requestor });
+ return res.data;
+};
+
export const postPromotionEligibility = async (memberIds, requestor) => {
const res = await axios.post(ENDPOINTS.PROMOTE_MEMBERS, {
memberIds,
diff --git a/src/actions/userManagement.js b/src/actions/userManagement.js
index a2e0c9b7b61..f554fc18b33 100644
--- a/src/actions/userManagement.js
+++ b/src/actions/userManagement.js
@@ -305,16 +305,31 @@ export const updateRehireableStatus = (user, isRehireable, notRehireableReason =
const reason = typeof notRehireableReason === 'string' ? notRehireableReason.trim() : '';
const requestData = { isRehireable };
- if (!isRehireable && reason) {
+ if (!isRehireable && !reason) {
+ throw new Error('A reason is required when a user is not rehireable.');
+ }
+
+ if (!isRehireable) {
userProfile.notRehireableReason = reason;
requestData.notRehireableReason = reason;
} else {
delete userProfile.notRehireableReason;
- if (!isRehireable) requestData.notRehireableReason = '';
}
- await axios.patch(ENDPOINTS.UPDATE_REHIREABLE_STATUS(user._id), requestData);
+ const response = await axios.patch(ENDPOINTS.UPDATE_REHIREABLE_STATUS(user._id), requestData);
+ const responseData = response?.data || {};
+ if (Array.isArray(responseData.notRehireableReasonHistory)) {
+ userProfile.notRehireableReasonHistory = responseData.notRehireableReasonHistory;
+ }
+ if (typeof responseData.notRehireableReason === 'string') {
+ if (responseData.notRehireableReason) {
+ userProfile.notRehireableReason = responseData.notRehireableReason;
+ } else {
+ delete userProfile.notRehireableReason;
+ }
+ }
dispatch(userProfileUpdateAction(userProfile));
+ return userProfile;
};
};
@@ -423,7 +438,7 @@ export const getUserProfileBasicInfo = ({ userId, source } = {}) => {
// API request to fetch basic user profile information
let userProfileBasicInfoPromise;
if (userId)
- userProfileBasicInfoPromise = axios.get(ENDPOINTS.USER_PROFILE(userId));
+ userProfileBasicInfoPromise = axios.get(ENDPOINTS.USER_PROFILE_BASIC_INFO_BY_ID(userId));
else if (source)
userProfileBasicInfoPromise = axios.get(ENDPOINTS.USER_PROFILE_BASIC_INFO(source));
else
@@ -471,4 +486,4 @@ export const updateUserInfomation = value => dispatch => {
*/
export const finishUserInfoUpdate = () => dispatch => {
dispatch({ type: FINISH_USER_INFO_UPDATE });
-};
\ No newline at end of file
+};
diff --git a/src/components/BMDashboard/InteractiveMap/BaseInteractiveMap.jsx b/src/components/BMDashboard/InteractiveMap/BaseInteractiveMap.jsx
index 4e15625aa15..6d7af7f0f56 100644
--- a/src/components/BMDashboard/InteractiveMap/BaseInteractiveMap.jsx
+++ b/src/components/BMDashboard/InteractiveMap/BaseInteractiveMap.jsx
@@ -1,6 +1,13 @@
import { MapContainer, TileLayer } from 'react-leaflet';
import MarkerClusterGroup from '@changey/react-leaflet-markercluster';
-import { MapThemeUpdater, ProjectMarkers, MapLegend, ProjectCounter } from './MapSharedComponents';
+import {
+ MapThemeUpdater,
+ ProjectMarkers,
+ MapLegend,
+ ProjectCounter,
+ MapConfig,
+} from './MapSharedComponents';
+import styles from './InteractiveMap.module.css';
function BaseInteractiveMap({
orgs = [],
@@ -38,6 +45,7 @@ function BaseInteractiveMap({
return (
diff --git a/src/components/BMDashboard/InteractiveMap/InteractiveMap.jsx b/src/components/BMDashboard/InteractiveMap/InteractiveMap.jsx
index 6932911f619..6163c0f3d4e 100644
--- a/src/components/BMDashboard/InteractiveMap/InteractiveMap.jsx
+++ b/src/components/BMDashboard/InteractiveMap/InteractiveMap.jsx
@@ -14,6 +14,7 @@ import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import styles from './InteractiveMap.module.css';
+import { MapConfig } from './MapSharedComponents';
const MapDateInput = forwardRef(({ darkMode, className, ...props }, ref) => (
@@ -511,11 +505,8 @@ export default function InteractiveMap() {
[-85, -180],
[85, 180],
]}
- url={
- darkMode
- ? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
- : 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
- }
+ url={MapConfig.tileUrl}
+ attribution='© OpenStreetMap contributors'
minZoom={2}
maxZoom={15}
/>
diff --git a/src/components/BMDashboard/InteractiveMap/InteractiveMap.module.css b/src/components/BMDashboard/InteractiveMap/InteractiveMap.module.css
index 92545378130..af3cb9d025b 100644
--- a/src/components/BMDashboard/InteractiveMap/InteractiveMap.module.css
+++ b/src/components/BMDashboard/InteractiveMap/InteractiveMap.module.css
@@ -343,7 +343,9 @@
.mapArea {
position: relative;
width: 100%;
- height: 90vh;
+
+ /* Fixed, responsive height so the whole map fits inside its dashboard card */
+ height: clamp(320px, 55vh, 520px);
border-radius: 12px;
overflow: hidden;
margin-top: 10px;
@@ -368,9 +370,10 @@
outline: 1px solid transparent;
}
-/* Dark tiles already provide the dark-mode map look; avoid extra dimming. */
-.darkMap {
- filter: none;
+/* Dark mode: darken the standard OSM tiles (keyless). Only the tile layer is filtered,
+ so markers, clusters, legend and popups keep their real colors. */
+.darkMap :global(.leaflet-tile-pane) {
+ filter: invert(1) hue-rotate(180deg) brightness(0.95) contrast(0.9) saturate(0.6);
}
/* ----------------------------------
@@ -435,11 +438,6 @@
color: #6acfde !important;
}
-/* Hide zoom buttons */
-:global(.leaflet-control-zoom) {
- display: none !important;
-}
-
:global(.leaflet-control) {
z-index: 2000 !important;
}
@@ -511,8 +509,8 @@
.legendBox {
position: absolute; /* absolute inside the map container */
- width: 160px; /* fixed width */
- height: 180px; /* fixed height */
+ width: max-content; /* size to content so it covers as little of the map as possible */
+ height: max-content;
cursor: move;
user-select: none;
padding: 10px 12px;
@@ -531,6 +529,27 @@
justify-content: flex-start;
}
+.legendBox h4 {
+ margin: 0 0 6px;
+ font-size: 14px;
+ font-weight: 700;
+}
+
+/* Zoom buttons: keep them readable in dark mode */
+.darkPage :global(.leaflet-bar a) {
+ background-color: #1c2541;
+ color: #f8fafc;
+ border-bottom-color: #465a74;
+}
+
+.darkPage :global(.leaflet-bar a:hover) {
+ background-color: #2d4059;
+}
+
+.darkPage :global(.leaflet-bar) {
+ border-color: #465a74;
+}
+
.darkPage .legendBox {
border: 1px solid rgb(255 255 255 / 15%);
color: #fff;
diff --git a/src/components/BMDashboard/InteractiveMap/MapSharedComponents.jsx b/src/components/BMDashboard/InteractiveMap/MapSharedComponents.jsx
index d1a944d74ff..81cf799c27c 100644
--- a/src/components/BMDashboard/InteractiveMap/MapSharedComponents.jsx
+++ b/src/components/BMDashboard/InteractiveMap/MapSharedComponents.jsx
@@ -504,10 +504,9 @@ export const MapUtils = {
MAP CONFIGURATION CONSTANTS
----------------------------------------------------- */
export const MapConfig = {
- tileUrls: {
- dark: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
- light: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
- },
+ // One tile source for both themes. CARTO's dark_all tiles now require an API key
+ // (they render "API KEY REQUIRED"), so dark mode darkens these tiles with CSS instead.
+ tileUrl: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
maxZoom: 15,
minZoom: 1,
clusterSettings: {
diff --git a/src/components/BMDashboard/Issues/IssueCharts.module.css b/src/components/BMDashboard/Issues/IssueCharts.module.css
index 465defd6fbf..c4188a76009 100644
--- a/src/components/BMDashboard/Issues/IssueCharts.module.css
+++ b/src/components/BMDashboard/Issues/IssueCharts.module.css
@@ -549,3 +549,93 @@ Issue Charts Module CSS
:global(.darkCalendar) :global(.react-datepicker__triangle) {
display: none !important;
}
+
+/* ----- Issue Tracking card (Weekly Project Summary) ----- */
+.filtersRow {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 1rem 1rem 0.5rem;
+}
+
+.dateRangeGroup {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ flex: 0 1 auto;
+ min-width: 0;
+}
+
+.dateRangeGroup :global(.react-datepicker-wrapper) {
+ width: 9.5rem;
+}
+
+.dateRangeGroup :global(.react-datepicker-wrapper) input {
+ width: 100%;
+}
+
+.dateSeparator {
+ white-space: nowrap;
+}
+
+.projectsFilter {
+ flex: 1 1 220px;
+ min-width: 0;
+}
+
+.typeFilter {
+ flex: 0 0 auto;
+}
+
+.filterControl {
+ height: 38px;
+ font-size: 0.875rem;
+ box-sizing: border-box;
+}
+
+.issuesChartArea {
+ position: relative;
+ width: 100%;
+ max-width: 100%;
+ min-width: 0;
+ box-sizing: border-box;
+ padding: 0.5rem 1rem 1rem;
+}
+
+.dark .noData {
+ color: #cfd7e3;
+}
+
+@media (width <= 576px) {
+ .dateRangeGroup {
+ flex: 1 1 100%;
+ }
+
+ .dateRangeGroup :global(.react-datepicker-wrapper) {
+ flex: 1 1 0;
+ width: auto;
+ min-width: 0;
+ }
+
+ .projectsFilter,
+ .typeFilter {
+ flex: 1 1 100%;
+ }
+
+ .typeFilter select {
+ width: 100%;
+ }
+}
+
+/* Dark mode: readable date placeholders + match the type select to the other controls */
+.dark .dateDark::placeholder {
+ color: #cfd7e3 !important;
+ opacity: 1;
+}
+
+.dark .selectDark {
+ background-color: #2b3e59 !important;
+ border-color: #4a5a72 !important;
+ color: #fff !important;
+}
diff --git a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx
index 9447cf91851..9507e4b8ce1 100644
--- a/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx
+++ b/src/components/BMDashboard/Issues/LongestOpenIssuesChart.jsx
@@ -56,6 +56,9 @@ function IssuesCharts({ bmProjects = [] }) {
borderColor:
graphType === 'Longest Open' ? 'rgba(54, 162, 235, 1)' : 'rgba(255, 99, 132, 1)',
borderWidth: 1,
+ barPercentage: 0.6,
+ categoryPercentage: 0.8,
+ maxBarThickness: 32,
},
],
};
@@ -64,16 +67,32 @@ function IssuesCharts({ bmProjects = [] }) {
const vals = chartData.map(d => (graphType === 'Longest Open' ? d.daysOpen : d.totalCost));
const dataMax = vals.length > 0 ? Math.max(...vals) : 600;
const xMax = Math.ceil((dataMax + 50) / 250) * 250;
+ const gridColor = darkMode ? 'rgba(255, 255, 255, 0.12)' : 'rgba(0, 0, 0, 0.08)';
+ const axisBorderColor = darkMode ? 'rgba(255, 255, 255, 0.35)' : 'rgba(0, 0, 0, 0.25)';
return {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
+ resizeDelay: 100,
layout: {
- padding: { right: 80, left: 10 },
+ padding: ({ chart }) => ({ right: chart.width < 420 ? 56 : 80, left: 4 }),
},
plugins: {
legend: { display: false },
+ tooltip: {
+ backgroundColor: darkMode ? '#1e2d42' : '#ffffff',
+ titleColor: darkMode ? '#ffffff' : '#111111',
+ bodyColor: darkMode ? '#e0e6f0' : '#333333',
+ borderColor: darkMode ? '#4a5a72' : '#ced4da',
+ borderWidth: 1,
+ callbacks: {
+ title: items => {
+ const issue = chartData[items[0]?.dataIndex];
+ return issue ? issue.title || String(issue.issueId) : '';
+ },
+ },
+ },
datalabels: {
anchor: 'end',
align: 'right',
@@ -102,10 +121,20 @@ function IssuesCharts({ bmProjects = [] }) {
color: darkMode ? '#fff' : '#000',
},
ticks: { stepSize: 250, color: darkMode ? '#ccc' : '#333' },
+ grid: { color: gridColor },
+ border: { color: axisBorderColor },
},
y: {
afterFit: scale => {
- scale.width = 200;
+ scale.width = Math.round(Math.min(200, Math.max(90, scale.chart.width * 0.32)));
+ },
+ // Shorten y-axis labels to fit the label column (full title is in the tooltip).
+ afterTickToLabelConversion: scale => {
+ const maxChars = Math.max(10, Math.floor((scale.chart.width * 0.32) / 7.5));
+ scale.ticks.forEach(tick => {
+ const label = String(tick.label ?? '');
+ tick.label = label.length > maxChars ? `${label.slice(0, maxChars - 1)}…` : label;
+ });
},
title: {
display: true,
@@ -118,6 +147,8 @@ function IssuesCharts({ bmProjects = [] }) {
maxRotation: 0,
autoSkip: false,
},
+ grid: { color: gridColor },
+ border: { color: axisBorderColor },
},
},
elements: {
@@ -129,73 +160,77 @@ function IssuesCharts({ bmProjects = [] }) {
const projectOptions = bmProjects.map(p => ({ value: p._id, label: p.name }));
const selectedProjectOptions = projectOptions.filter(opt => selectedProjects.includes(opt.value));
- const darkSelectStyles = darkMode
- ? {
- control: base => ({
- ...base,
- background: '#2b3e59',
- borderColor: '#4a5a72',
- color: '#fff',
- }),
- menu: base => ({ ...base, background: '#2b3e59' }),
- option: (base, { isFocused }) => ({
- ...base,
- background: isFocused ? '#4a5a72' : '#2b3e59',
- color: '#fff',
- }),
- multiValue: base => ({ ...base, background: '#4a5a72' }),
- multiValueLabel: base => ({ ...base, color: '#fff' }),
- singleValue: base => ({ ...base, color: '#fff' }),
- input: base => ({ ...base, color: '#fff' }),
- placeholder: base => ({ ...base, color: '#aaa' }),
- }
- : {};
+ const selectStyles = {
+ control: base => ({
+ ...base,
+ minHeight: 38,
+ ...(darkMode && { background: '#2b3e59', borderColor: '#4a5a72', color: '#fff' }),
+ }),
+ menu: base => ({ ...base, zIndex: 5, ...(darkMode && { background: '#2b3e59' }) }),
+ option: (base, { isFocused }) =>
+ darkMode ? { ...base, background: isFocused ? '#4a5a72' : '#2b3e59', color: '#fff' } : base,
+ multiValue: base => (darkMode ? { ...base, background: '#4a5a72' } : base),
+ multiValueLabel: base => (darkMode ? { ...base, color: '#fff' } : base),
+ singleValue: base => (darkMode ? { ...base, color: '#fff' } : base),
+ input: base => (darkMode ? { ...base, color: '#fff' } : base),
+ placeholder: base => ({ ...base, color: darkMode ? '#cfd7e3' : '#6c757d' }),
+ };
+
+ // Height grows with the number of bars; width always follows the card.
+ const chartHeight = Math.max(240, chartData.length * 56 + 110);
return (
-
-
+
+
setDateRange(prev => ({ ...prev, start: value }))}
placeholderText="Start date"
+ ariaLabelledBy="issues-start-date"
calendarClassName={darkMode ? styles.darkCalendar : styles.lightCalendar}
- className={darkMode ? styles.dateDark : styles.dateInput}
+ className={`${darkMode ? styles.dateDark : styles.dateInput} ${styles.filterControl}`}
isClearable
/>
- to
+ to
setDateRange(prev => ({ ...prev, end: value }))}
placeholderText="End date"
+ ariaLabelledBy="issues-end-date"
calendarClassName={darkMode ? styles.darkCalendar : styles.lightCalendar}
- className={darkMode ? styles.dateDark : styles.dateInput}
+ className={`${darkMode ? styles.dateDark : styles.dateInput} ${styles.filterControl}`}
isClearable
/>
-
+
setSelectedProjects(selected.map(s => s.value))}
placeholder="All Projects"
- styles={darkSelectStyles}
+ aria-label="Filter by project"
+ styles={selectStyles}
/>
-
+
setGraphType(e.target.value)}
- className={darkMode ? styles.selectDark : styles.select}
+ className={`${darkMode ? styles.selectDark : styles.select} ${styles.filterControl}`}
>
Longest Open
Most Expensive
-
+
0 ? chartHeight : 'auto' }}
+ >
{chartData.length > 0 ? (
YEAR_COLORS[idx % YEAR_COLORS.length];
+
const stripNumericSuffix = value => {
const str = String(value);
let end = str.length;
@@ -103,7 +117,36 @@ const issueChartCardTickRotationPlugin = {
},
};
-const generateColor = idx => `hsl(${(idx * 60) % 360}, 70%, 50%)`;
+// Draws the issue count above each bar (no extra dependency needed)
+const barValueLabelsPlugin = {
+ id: 'issueChartBarValueLabels',
+ afterDatasetsDraw(chart, _args, pluginOptions = {}) {
+ if (!pluginOptions.enabled) return;
+
+ const { ctx } = chart;
+ ctx.save();
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'bottom';
+ ctx.fillStyle = pluginOptions.color || '#232323';
+
+ chart.data.datasets.forEach((dataset, datasetIndex) => {
+ if (!chart.isDatasetVisible(datasetIndex)) return;
+ const meta = chart.getDatasetMeta(datasetIndex);
+
+ meta.data.forEach((bar, index) => {
+ const value = Number(dataset.data?.[index]) || 0;
+ if (value <= 0) return;
+
+ // Scale text to the bar width so labels stay readable without overlapping neighbours
+ const fontSize = Math.max(8, Math.min(12, Math.floor((bar.width || 10) * 0.9)));
+ ctx.font = `600 ${fontSize}px sans-serif`;
+ ctx.fillText(String(value), bar.x, bar.y - 2);
+ });
+ });
+
+ ctx.restore();
+ },
+};
const handleLegendClick = (e, legendItem, legend) => {
const index = legendItem.datasetIndex;
@@ -147,11 +190,15 @@ function IssueChart({ variant = 'standalone', showTitle = true }) {
const { loading, issues, error } = useSelector(state => state.bmissuechart);
const [filters, setFilters] = useState({ issueTypes: [], years: [] });
+ // 'multi' compares several years side by side; 'single' focuses on one year
+ const [viewMode, setViewMode] = useState('multi');
+ const [singleYear, setSingleYear] = useState(null);
const [isMobile, setIsMobile] = useState(
typeof window !== 'undefined' ? window.innerWidth <= 640 : false,
);
const isCardVariant = variant === 'card';
+ const isSingleYearView = viewMode === 'single';
useEffect(() => {
dispatch(fetchIssues());
@@ -229,11 +276,14 @@ function IssueChart({ variant = 'standalone', showTitle = true }) {
const flatIssueTypeOptions = flattenOptions(issueTypes);
const uniqueYears = years.map(y => y.value);
+ const latestYear = uniqueYears.at(-1) ?? null;
+ // Single-year view defaults to the most recent year until the user picks one
+ const selectedSingleYear = singleYear ?? latestYear;
const yearColorMap = useMemo(
() =>
uniqueYears.reduce((acc, year, idx) => {
- acc[year] = generateColor(idx);
+ acc[year] = getYearColor(idx);
return acc;
}, {}),
[uniqueYears],
@@ -266,6 +316,7 @@ function IssueChart({ variant = 'standalone', showTitle = true }) {
issueTypes: [],
years: [],
});
+ setSingleYear(null);
};
const chartData = useMemo(() => {
@@ -307,7 +358,10 @@ function IssueChart({ variant = 'standalone', showTitle = true }) {
return aLower.localeCompare(bLower, undefined, { numeric: true });
});
- const filteredYears = filters.years.length ? filters.years : uniqueYears;
+ let filteredYears = filters.years.length ? filters.years : uniqueYears;
+ if (isSingleYearView) {
+ filteredYears = selectedSingleYear == null ? [] : [selectedSingleYear];
+ }
const labels = selectedBases;
const datasets = filteredYears.map(year => ({
@@ -324,7 +378,7 @@ function IssueChart({ variant = 'standalone', showTitle = true }) {
}));
return { labels, datasets };
- }, [issues, filters, uniqueYears, yearColorMap, darkMode]);
+ }, [issues, filters, uniqueYears, yearColorMap, darkMode, isSingleYearView, selectedSingleYear]);
const chartAnalysis = useMemo(() => {
const labels = chartData?.labels ?? [];
@@ -401,6 +455,8 @@ function IssueChart({ variant = 'standalone', showTitle = true }) {
maintainAspectRatio: false,
layout: {
padding: {
+ // Room above the tallest bar for its value label
+ top: 16,
bottom: isCardVariant ? 8 : 56,
},
},
@@ -436,6 +492,10 @@ function IssueChart({ variant = 'standalone', showTitle = true }) {
datalabels: {
display: false,
},
+ issueChartBarValueLabels: {
+ enabled: true,
+ color: darkMode ? '#f3f4f6' : '#1f2937',
+ },
issueChartCardTickRotation: {
enabled: isCardVariant,
isMobile,
@@ -499,7 +559,7 @@ function IssueChart({ variant = 'standalone', showTitle = true }) {
);
const chartPlugins = useMemo(
- () => [xAxisBackgroundPlugin(darkMode), issueChartCardTickRotationPlugin],
+ () => [xAxisBackgroundPlugin(darkMode), issueChartCardTickRotationPlugin, barValueLabelsPlugin],
[darkMode],
);
@@ -651,12 +711,29 @@ function IssueChart({ variant = 'standalone', showTitle = true }) {
const activeFilterSummary = useMemo(() => {
const issueTypeCount = filters.issueTypes.length || getValidIssueTypes(issues).length;
+
+ if (isSingleYearView) {
+ return `${issueTypeCount} Issue Types | ${selectedSingleYear ?? 'No year'}`;
+ }
+
const yearList = filters.years.length ? filters.years : uniqueYears;
const range =
yearList.length > 0 ? `${Math.min(...yearList)}–${Math.max(...yearList)}` : 'No years';
return `${issueTypeCount} Issue Types | ${range}`;
- }, [filters.issueTypes, filters.years, issues, uniqueYears]);
+ }, [
+ filters.issueTypes,
+ filters.years,
+ issues,
+ uniqueYears,
+ isSingleYearView,
+ selectedSingleYear,
+ ]);
+
+ const viewToggleButtonClass = isActive =>
+ `${styles.issueChartViewToggleButton} ${
+ isActive ? styles.issueChartViewToggleButtonActive : ''
+ }`;
const chartContent = (
)}
+ {/* Switch between comparing several years and focusing on one year */}
+
+ setViewMode('multi')}
+ >
+ Compare Years
+
+ setViewMode('single')}
+ >
+ Single Year
+
+
+
Year:
- handleFilterChange(selected, 'years')}
- value={years.filter(option => filters.years.includes(option.value))}
- styles={selectStyles}
- aria-label="Filter issues by year"
- placeholder="Select years"
- components={{ MenuList: FilterMenuList }}
- filterField="years"
- />
+ {isSingleYearView ? (
+ setSingleYear(selected?.value ?? null)}
+ value={years.find(option => option.value === selectedSingleYear) || null}
+ styles={selectStyles}
+ aria-label="Select a year"
+ placeholder="Select a year"
+ />
+ ) : (
+ handleFilterChange(selected, 'years')}
+ value={years.filter(option => filters.years.includes(option.value))}
+ styles={selectStyles}
+ aria-label="Filter issues by year"
+ placeholder="Select years"
+ components={{ MenuList: FilterMenuList }}
+ filterField="years"
+ />
+ )}
diff --git a/src/components/BMDashboard/LessonsLearnt/LessonsLearntChart.jsx b/src/components/BMDashboard/LessonsLearnt/LessonsLearntChart.jsx
index 32d59a652dc..e96b9484009 100644
--- a/src/components/BMDashboard/LessonsLearnt/LessonsLearntChart.jsx
+++ b/src/components/BMDashboard/LessonsLearnt/LessonsLearntChart.jsx
@@ -6,6 +6,7 @@ import axios from 'axios';
import Select from 'react-select';
import DatePicker from 'react-datepicker';
import PropTypes from 'prop-types';
+import ChartDataLabels from 'chartjs-plugin-datalabels';
import { ENDPOINTS } from '../../../utils/URL';
import styles from './LessonsLearntChart.module.css';
@@ -111,6 +112,16 @@ const useLessonsData = (selectedProjects, startDate, endDate) => {
const BAR_COLOR_LIGHT = '#10b981';
const BAR_COLOR_DARK = '#34d399';
+// Dark text stays readable on both green bar shades
+const BAR_LABEL_COLOR = '#063a2a';
+const MAX_TICK_LABEL = 14;
+
+const changeColor = (change, darkMode) => {
+ const value = Number.parseFloat(String(change || '0').replace('%', ''));
+ if (value > 0) return darkMode ? '#7ee08a' : '#2e7d32';
+ if (value < 0) return darkMode ? '#ff8a80' : '#c62828';
+ return darkMode ? '#94a3b8' : '#6b7280';
+};
function LessonsLearntChart({ darkMode: propDarkMode }) {
const reduxDarkMode = useSelector(state => state.theme.darkMode);
@@ -172,8 +183,29 @@ function LessonsLearntChart({ darkMode: propDarkMode }) {
return {
responsive: true,
maintainAspectRatio: false,
+ layout: { padding: { left: 8, right: 8 } },
plugins: {
title: { display: false },
+ // Count inside each bar; month-over-month change just above it, aligned to its bar
+ datalabels: {
+ labels: {
+ count: {
+ anchor: 'center',
+ align: 'center',
+ color: BAR_LABEL_COLOR,
+ font: { weight: 'bold', size: 12 },
+ display: ctx => ctx.dataset.data[ctx.dataIndex] > 0,
+ },
+ change: {
+ anchor: 'end',
+ align: 'end',
+ offset: 2,
+ font: { weight: 'bold', size: 11 },
+ formatter: (_value, ctx) => lessonsData[ctx.dataIndex]?.changePercentage || '0%',
+ color: ctx => changeColor(lessonsData[ctx.dataIndex]?.changePercentage, darkMode),
+ },
+ },
+ },
tooltip: {
callbacks: {
afterLabel: context => {
@@ -189,11 +221,22 @@ function LessonsLearntChart({ darkMode: propDarkMode }) {
scales: {
y: {
beginAtZero: true,
+ grace: '12%', // room above the tallest bar for its change label
ticks: { stepSize: 1, color: tickColor },
grid: { color: gridColor },
},
x: {
- ticks: { color: tickColor },
+ ticks: {
+ color: tickColor,
+ autoSkip: false,
+ maxRotation: 45,
+ // Shorten long project names (tooltip shows the full name). On a category
+ // axis `value` is the bar index, so the name comes from the data.
+ callback: value => {
+ const name = lessonsData[value]?.projectName || 'Unknown';
+ return name.length > MAX_TICK_LABEL ? `${name.slice(0, MAX_TICK_LABEL - 1)}…` : name;
+ },
+ },
grid: { color: gridColor },
},
},
@@ -317,20 +360,7 @@ function LessonsLearntChart({ darkMode: propDarkMode }) {
{!isLoading && !error && Array.isArray(lessonsData) && lessonsData.length > 0 && (
<>
-
-
-
- {lessonsData.map((d, idx) => (
-
- {d?.changePercentage || '0%'}
-
- ))}
+
>
)}
diff --git a/src/components/BMDashboard/LessonsLearnt/LessonsLearntChart.module.css b/src/components/BMDashboard/LessonsLearnt/LessonsLearntChart.module.css
index d77ec32cb59..8dca9ea7914 100644
--- a/src/components/BMDashboard/LessonsLearnt/LessonsLearntChart.module.css
+++ b/src/components/BMDashboard/LessonsLearnt/LessonsLearntChart.module.css
@@ -32,8 +32,9 @@
}
.filterRow {
- display: flex;
- flex-wrap: wrap;
+ /* Project, From and To on one even row */
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.75rem;
margin-bottom: 1rem;
align-items: flex-end;
@@ -43,8 +44,17 @@
.filter {
display: flex;
flex-direction: column;
- flex: 1 1 140px;
- min-width: 120px;
+ min-width: 0;
+}
+
+.filter :global(.react-datepicker-wrapper) {
+ width: 100%;
+}
+
+.filter :global(.react-datepicker__input-container input) {
+ width: 100%;
+ box-sizing: border-box;
+ min-height: 38px;
}
.filterLabel {
@@ -192,28 +202,6 @@
padding: 2rem 0;
}
-.percentageLabels {
- position: absolute;
- top: -1.25rem;
- width: 100%;
- display: flex;
- justify-content: space-around;
- pointer-events: none;
-}
-
-.percentageLabel {
- position: absolute;
- transform: translateX(-50%);
- font-size: 0.75rem;
- font-weight: 600;
- color: var(--accent-color, #10b981);
- transition: color 0.3s ease;
-}
-
-.darkMode .percentageLabel {
- color: #34d399;
-}
-
/* Responsive */
@media (width <= 768px) {
.chartContainer {
@@ -224,9 +212,8 @@
gap: 0.5rem;
}
- .filter {
- min-width: 100px;
- flex: 1 1 100%;
+ .filterRow {
+ grid-template-columns: 1fr;
}
.barContainer {
diff --git a/src/components/BMDashboard/Projects/ProjectDetails/ProjectDetails.jsx b/src/components/BMDashboard/Projects/ProjectDetails/ProjectDetails.jsx
index 4b1f866b477..ca706c27fb2 100644
--- a/src/components/BMDashboard/Projects/ProjectDetails/ProjectDetails.jsx
+++ b/src/components/BMDashboard/Projects/ProjectDetails/ProjectDetails.jsx
@@ -115,7 +115,7 @@ function ProjectDetails() {
{/* TEAM */}
{/* --------------------------- */}
-
+
{/* TOOLS + MATERIALS ROW */}
diff --git a/src/components/BMDashboard/Projects/ProjectDetails/__tests__/ProjectDetails.test.jsx b/src/components/BMDashboard/Projects/ProjectDetails/__tests__/ProjectDetails.test.jsx
new file mode 100644
index 00000000000..ac761b29daa
--- /dev/null
+++ b/src/components/BMDashboard/Projects/ProjectDetails/__tests__/ProjectDetails.test.jsx
@@ -0,0 +1,55 @@
+import { render, screen } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import createMockStore from 'redux-mock-store';
+import { MemoryRouter, Route } from 'react-router-dom';
+import ProjectDetails from '../ProjectDetails';
+
+vi.mock('../../../../../actions/bmdashboard/projectActions', () => ({
+ fetchBMProjects: vi.fn(() => ({ type: 'FETCH_BM_PROJECTS' })),
+}));
+
+vi.mock('../LogBar', () => ({
+ LoggingButtons: () => null,
+ AddItemButtons: () => null,
+ TeamButtons: ({ projectId }) =>
Log Issue ,
+}));
+
+vi.mock('../RentedTools/RentedToolsDisplay', () => ({
+ default: () => null,
+}));
+
+vi.mock('../Materials/MaterialsDisplay', () => ({
+ default: () => null,
+}));
+
+vi.mock('../ProjectLog', () => ({
+ default: () => null,
+}));
+
+const mockStore = createMockStore([]);
+
+describe('ProjectDetails', () => {
+ it('passes the selected project ID to the Log Issue button', () => {
+ const projectId = '6823e200a3475f85a80d5d9c';
+
+ const store = mockStore({
+ theme: { darkMode: false },
+ bmProjects: [{ _id: projectId, name: 'Test Project' }],
+ });
+
+ render(
+
+
+
+
+
+
+ ,
+ );
+
+ expect(screen.getByRole('link', { name: 'Log Issue' })).toHaveAttribute(
+ 'href',
+ `/bmdashboard/issues/add/${projectId}`,
+ );
+ });
+});
diff --git a/src/components/BMDashboard/RentalChart/RentalChart.jsx b/src/components/BMDashboard/RentalChart/RentalChart.jsx
index c78ec3302ae..83eeefe5a87 100644
--- a/src/components/BMDashboard/RentalChart/RentalChart.jsx
+++ b/src/components/BMDashboard/RentalChart/RentalChart.jsx
@@ -4,7 +4,9 @@ import axios from 'axios';
import { ENDPOINTS } from '~/utils/URL';
import { Line } from 'react-chartjs-2';
import DatePicker from 'react-datepicker';
+import ChartDataLabels from 'chartjs-plugin-datalabels';
import styles from './RentalChart.module.css';
+import datePickerStyles from './RentalDatePicker.module.css';
import { toast } from 'react-toastify';
import {
Chart as ChartJS,
@@ -19,7 +21,6 @@ import {
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);
-// these colors can be randomly generated once more projects are shared here. Colors generated from ChatGPT
const PROJECT_COLORS = [
{ borderColor: 'rgb(53, 162, 235)', backgroundColor: 'rgba(53, 162, 235, 0.5)' },
{ borderColor: 'rgb(255, 99, 132)', backgroundColor: 'rgba(255, 99, 132, 0.5)' },
@@ -44,7 +45,17 @@ const MONTHS = [
'December',
];
-/* ---------- helper functions to keep processChartData simple ---------- */
+const CHART_COLORS = {
+ darkBg: 'transparent',
+ lightBg: '#ffffff',
+ darkText: '#e0e0e0',
+ lightText: '#333333',
+};
+
+const FILTER_ALL = 'All';
+const MIN_LABELED_CHART_WIDTH = 600;
+// Below this width the chart switches to smaller fonts and shorter axis titles
+const COMPACT_CHART_WIDTH = 576;
const filterRentalData = (data, selectedProject, selectedTool, dateRange) =>
data.filter(item => {
@@ -79,11 +90,19 @@ const buildMonthsRange = dateRange => {
return { labels, monthsInRange, totalMonths };
};
-const aggregateDataByGroup = (filteredData, monthsInRange, totalMonths, groupBy, chartType) => {
+// One decimal max, e.g. 16.342 -> "16.3%", 20 -> "20%"
+const formatPercent = value => `${Number(Number(value).toFixed(1))}%`;
+
+const getProjectName = (projMap, projectId) =>
+ projMap instanceof Map && projMap.has(projectId)
+ ? projMap.get(projectId)
+ : `Project ${projectId.substring(0, 8)}...`;
+
+const aggregateDataByProject = (filteredData, monthsInRange, totalMonths, chartType, projMap) => {
const groupMap = new Map();
for (const item of filteredData) {
- const groupKey = groupBy === 'project' ? item.projectId : item.toolName;
+ const groupKey = item.projectId;
const date = new Date(item.date);
const year = date.getFullYear();
const month = date.getMonth();
@@ -101,6 +120,7 @@ const aggregateDataByGroup = (filteredData, monthsInRange, totalMonths, groupBy,
if (!groupMap.has(groupKey)) {
groupMap.set(groupKey, {
key: groupKey,
+ name: getProjectName(projMap, groupKey),
dataPoints: new Array(totalMonths).fill(undefined),
monthsWithData: new Set(),
});
@@ -117,13 +137,12 @@ const aggregateDataByGroup = (filteredData, monthsInRange, totalMonths, groupBy,
return groupMap;
};
-const buildDatasetsFromGroupMap = (groupMap, groupBy) =>
+const buildDatasetsFromGroupMap = groupMap =>
Array.from(groupMap.values()).map((group, index) => {
const colorIndex = index % PROJECT_COLORS.length;
- const label = groupBy === 'project' ? `Project ${group.key.substring(0, 8)}...` : group.key;
return {
- label,
+ label: group.name,
data: group.dataPoints,
borderColor: PROJECT_COLORS[colorIndex].borderColor,
backgroundColor: PROJECT_COLORS[colorIndex].backgroundColor,
@@ -136,62 +155,149 @@ const buildDatasetsFromGroupMap = (groupMap, groupBy) =>
};
});
-/* ---------------------------------------------------------------------- */
+// Label above the point when it's the month's highest visible value, below otherwise,
+// so labels for nearby points split apart instead of covering each other
+const isHighestInMonth = ({ chart, dataIndex, datasetIndex, dataset }) => {
+ const value = dataset.data[dataIndex];
+ return !chart.data.datasets.some(
+ (other, i) => i !== datasetIndex && chart.isDatasetVisible(i) && other.data[dataIndex] > value,
+ );
+};
+
+const getDatalabelFormatter = (value, chartType) => {
+ if (value == null || Number.isNaN(value)) return '';
+ if (chartType === 'percentage') {
+ return formatPercent(value);
+ }
+ return `$${value.toFixed(2)}`;
+};
+
+const getYAxisTitle = (chartType, isCompact) => {
+ if (chartType === 'percentage') {
+ return isCompact ? '% of Materials Cost' : 'Percentage of Total Materials Cost (%)';
+ }
+ return isCompact ? 'Rental Cost ($)' : 'Total Rental Cost ($)';
+};
+
+const formatDate = date => `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`;
+
+const buildChartTitle = (selectedProject, selectedTool, dateRange, availableProjects) => {
+ const toolPart = selectedTool === FILTER_ALL ? '' : ` for ${selectedTool}`;
+ const projectPart =
+ selectedProject === FILTER_ALL
+ ? ' by Project'
+ : ` in ${getProjectName(availableProjects, selectedProject)}`;
+
+ return `Rental Costs${toolPart}${projectPart} (${formatDate(dateRange.startDate)} - ${formatDate(
+ dateRange.endDate,
+ )})`;
+};
export default function RentalChart() {
const chartRef = useRef(null);
- const [chartData, setChartData] = useState({
- labels: [],
- datasets: [],
- });
+ const chartWrapperRef = useRef(null);
+ const [chartWidth, setChartWidth] = useState(0);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [chartType, setChartType] = useState('cost');
- const [selectedProject, setSelectedProject] = useState('All');
- const [selectedTool, setSelectedTool] = useState('All');
- const [groupBy] = useState('project');
+ const [selectedProject, setSelectedProject] = useState(FILTER_ALL);
+ const [selectedTool, setSelectedTool] = useState(FILTER_ALL);
const darkMode = useSelector(state => state.theme.darkMode);
+ const bmProjects = useSelector(state => state.bmProjects);
const [dateRange, setDateRange] = useState({
startDate: new Date(2024, 0, 1),
endDate: new Date(2024, 11, 31),
});
- const [availableProjects, setAvailableProjects] = useState([]);
- const [availableTools, setAvailableTools] = useState([]);
+ const [fallbackProjectNames, setFallbackProjectNames] = useState(new Map());
const [rawData, setRawData] = useState([]);
- const processChartData = data => {
- const filteredData = filterRentalData(data, selectedProject, selectedTool, dateRange);
+ // Only list projects that have rental data; prefer names from the BM projects list
+ const availableProjects = useMemo(() => {
+ const bmProjectNames = new Map(
+ (Array.isArray(bmProjects) ? bmProjects : []).map(p => [p._id, p.name]),
+ );
+ const projectMap = new Map();
+ rawData.forEach(item => {
+ if (item.projectId && !projectMap.has(item.projectId)) {
+ projectMap.set(
+ item.projectId,
+ bmProjectNames.get(item.projectId) ||
+ fallbackProjectNames.get(item.projectId) ||
+ getProjectName(null, item.projectId),
+ );
+ }
+ });
+ return projectMap;
+ }, [rawData, bmProjects, fallbackProjectNames]);
+
+ // Tools are picked per project, so the list only has the selected project's tools
+ const availableTools = useMemo(() => {
+ if (selectedProject === FILTER_ALL) return [];
+ const projectData = rawData.filter(item => item.projectId === selectedProject);
+ return [...new Set(projectData.map(item => item.toolName))].sort((a, b) =>
+ a.localeCompare(b, undefined, { sensitivity: 'base' }),
+ );
+ }, [rawData, selectedProject]);
+
+ useEffect(() => {
+ if (selectedTool !== FILTER_ALL && !availableTools.includes(selectedTool)) {
+ setSelectedTool(FILTER_ALL);
+ }
+ }, [availableTools, selectedTool]);
+
+ const chartData = useMemo(() => {
+ const filteredData = filterRentalData(rawData, selectedProject, selectedTool, dateRange);
const { labels, monthsInRange, totalMonths } = buildMonthsRange(dateRange);
- const groupMap = aggregateDataByGroup(
+ const groupMap = aggregateDataByProject(
filteredData,
monthsInRange,
totalMonths,
- groupBy,
chartType,
+ availableProjects,
);
- const datasets = buildDatasetsFromGroupMap(groupMap, groupBy);
-
- setChartData({ labels, datasets });
- };
+ return { labels, datasets: buildDatasetsFromGroupMap(groupMap) };
+ }, [rawData, selectedProject, selectedTool, dateRange, chartType, availableProjects]);
useEffect(() => {
const fetchRentalData = async () => {
try {
setLoading(true);
- const response = await axios.get(ENDPOINTS.BM_RENTAL_CHART);
- if (response.data.success) {
- const { data } = response.data;
- setRawData(data);
-
- const projectIds = [...new Set(data.map(item => item.projectId))];
- setAvailableProjects(projectIds);
+ const headers = { Authorization: localStorage.getItem('token') };
+ const [rentalResponse, projectsResponse] = await Promise.allSettled([
+ axios.get(ENDPOINTS.BM_RENTAL_CHART),
+ axios.get(ENDPOINTS.BM_TOOLS_RETURNED_LATE_PROJECTS, { headers }),
+ ]);
+
+ if (rentalResponse.status === 'fulfilled' && rentalResponse.value.data.success) {
+ const { data } = rentalResponse.value.data;
+
+ // Fallback names for projects missing from the BM projects list
+ const projectMap = new Map();
+ if (projectsResponse.status === 'fulfilled') {
+ let pData = projectsResponse.value.data;
+ if (pData && !Array.isArray(pData)) pData = pData.data || pData.results || [];
+
+ if (Array.isArray(pData)) {
+ pData.forEach(p => {
+ const pId = p.projectId || p._id;
+ const pName = p.projectName || p.name;
+ if (pId && pName) {
+ projectMap.set(pId, pName);
+ }
+ });
+ }
+ }
- const toolNames = [...new Set(data.map(item => item.toolName))];
- setAvailableTools(toolNames);
+ data.forEach(item => {
+ if (item.projectId && item.projectName && !projectMap.has(item.projectId)) {
+ projectMap.set(item.projectId, item.projectName);
+ }
+ });
- processChartData(data);
+ setFallbackProjectNames(projectMap);
+ setRawData(data);
} else {
setError('Failed to fetch data');
}
@@ -205,42 +311,23 @@ export default function RentalChart() {
}
};
- fetchRentalData();
- // eslint-disable-next-line react-hooks/exhaustive-deps
+ // Errors are handled inside fetchRentalData
+ fetchRentalData().catch(() => {});
}, []);
- function generateChartTitle() {
- let title = 'Rental Costs';
-
- if (groupBy === 'project') {
- title += ' by Project';
- if (selectedProject !== 'All') {
- title = `Rental Costs for Project ${selectedProject.substring(0, 8)}...`;
- }
- } else {
- title += ' by Tool Type';
- if (selectedTool !== 'All') {
- title = `Rental Costs for ${selectedTool}`;
- }
- }
-
- const formatDate = date => `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`;
-
- title += ` (${formatDate(dateRange.startDate)} - ${formatDate(dateRange.endDate)})`;
-
- return title;
- }
-
+ // Track the chart area's width so fonts and labels can shrink on small screens
useEffect(() => {
- if (rawData.length > 0) {
- processChartData(rawData);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [chartType, selectedProject, selectedTool, dateRange, groupBy, rawData]);
+ const wrapper = chartWrapperRef.current;
+ if (!wrapper || typeof ResizeObserver === 'undefined') return undefined;
+ const observer = new ResizeObserver(([entry]) => setChartWidth(entry.contentRect.width));
+ observer.observe(wrapper);
+ return () => observer.disconnect();
+ }, []);
+
+ const isCompact = chartWidth > 0 && chartWidth < COMPACT_CHART_WIDTH;
const options = useMemo(() => {
const textColor = darkMode ? '#ffffff' : '#000000';
- const bgColor = darkMode ? '#1b2a41' : '#ffffff';
const tooltipBorder = darkMode ? '#ffffff' : '#000000';
const tooltipBg = darkMode ? '#343a40' : '#f8f9fa';
const titleColor = darkMode ? '#ffffff' : '#000000';
@@ -250,24 +337,37 @@ export default function RentalChart() {
return {
responsive: true,
maintainAspectRatio: false,
- backgroundColor: bgColor,
+ backgroundColor: darkMode ? CHART_COLORS.darkBg : CHART_COLORS.lightBg,
+ // Hovering anywhere in a month lists every project, so hidden labels stay readable
+ interaction: { mode: 'index', intersect: false },
plugins: {
legend: {
position: 'top',
- labels: { color: textColor, font: { size: 16 } },
+ labels: {
+ color: textColor,
+ font: { size: isCompact ? 12 : 16 },
+ boxWidth: isCompact ? 24 : 40,
+ },
},
title: {
display: true,
- text: generateChartTitle(),
- font: { size: 25 },
- color: titleColor,
+ text: buildChartTitle(selectedProject, selectedTool, dateRange, availableProjects),
+ font: {
+ size: isCompact ? 12 : 14,
+ },
+ color: darkMode ? CHART_COLORS.darkText : CHART_COLORS.lightText,
+ padding: {
+ bottom: isCompact ? 10 : 20,
+ },
},
tooltip: {
+ filter: item => Number.isFinite(item.parsed.y),
callbacks: {
label({ dataset, parsed }) {
let label = dataset.label ? `${dataset.label}: ` : '';
if (parsed.y !== null) {
- label += chartType === 'percentage' ? `${parsed.y}%` : `$${parsed.y.toFixed(2)}`;
+ label +=
+ chartType === 'percentage' ? formatPercent(parsed.y) : `$${parsed.y.toFixed(2)}`;
}
return label;
},
@@ -277,69 +377,67 @@ export default function RentalChart() {
bodyColor: textColor,
borderColor: tooltipBorder,
borderWidth: 2,
- titleFont: { size: 18 },
- bodyFont: { size: 16 },
+ titleFont: { size: isCompact ? 14 : 18 },
+ bodyFont: { size: isCompact ? 12 : 16 },
},
datalabels: {
color: darkMode ? '#e0e0e0' : '#333333',
- // move each dataset to a slightly different side of the point
- anchor: ctx => {
- // horizontal side of the point
- if (ctx.datasetIndex === 0) return 'end'; // right
- if (ctx.datasetIndex === 1) return 'start'; // left
- return 'center'; // 3rd stays centered if any
- },
-
- align: ctx => {
- // vertical side of the anchor
- if (ctx.datasetIndex === 0) return 'bottom'; // slightly below
- if (ctx.datasetIndex === 1) return 'top'; // slightly above
- return 'top';
- },
- offset: 8, // distance in px away from the point (same for all is fine)
+ backgroundColor: darkMode ? 'rgba(27, 42, 65, 0.85)' : 'rgba(255, 255, 255, 0.85)',
+ borderRadius: 4,
+ padding: { top: 2, bottom: 2, left: 4, right: 4 },
+ anchor: ctx => (isHighestInMonth(ctx) ? 'end' : 'start'),
+ align: ctx => (isHighestInMonth(ctx) ? 'top' : 'bottom'),
+ offset: 4,
+ // Too narrow for per-point labels (the tooltip still shows values); otherwise hide
+ // only labels that would collide, and keep the rest inside the plot
+ display: ctx => (ctx.chart.width < MIN_LABELED_CHART_WIDTH ? false : 'auto'),
+ clamp: true,
font: {
size: 12,
},
- formatter: value => {
- if (value == null || Number.isNaN(value)) return '';
-
- // percentage mode → whole-number percent
- if (chartType === 'percentage') {
- return `${value.toFixed(0)}%`; // e.g., 348%
- }
-
- // cost mode → dollars (you can use 0 or 2 decimals as you like)
- return `$${value.toFixed(2)}`; // e.g., $175.00
- },
+ formatter: value => getDatalabelFormatter(value, chartType),
},
},
scales: {
x: {
- title: { display: true, text: 'Month/Year', color: textColor, font: { size: 18 } },
- ticks: { color: textColor },
+ // Pad both ends so edge points and their labels clear the y-axis ticks
+ offset: true,
+ title: {
+ display: true,
+ text: 'Month/Year',
+ color: textColor,
+ font: { size: isCompact ? 13 : 18 },
+ },
+ ticks: {
+ color: textColor,
+ font: { size: isCompact ? 10 : 12 },
+ maxRotation: isCompact ? 60 : 50,
+ autoSkip: true,
+ autoSkipPadding: isCompact ? 4 : 10,
+ },
grid: { color: gridXColor },
},
y: {
beginAtZero: true,
+ // Headroom above the highest point so its label doesn't hit the legend
+ grace: '10%',
title: {
display: true,
- text:
- chartType === 'percentage'
- ? 'Percentage of Total Materials Cost (%)'
- : 'Total Rental Cost ($)',
+ text: getYAxisTitle(chartType, isCompact),
color: textColor,
- font: { size: 18 },
+ font: { size: isCompact ? 13 : 18 },
},
ticks: {
- callback: value => (chartType === 'percentage' ? `${value}%` : `$${value}`),
+ callback: value => (chartType === 'percentage' ? formatPercent(value) : `$${value}`),
color: textColor,
+ font: { size: isCompact ? 10 : 12 },
},
grid: { color: gridYColor },
},
},
};
- }, [darkMode, chartType, dateRange, selectedProject, selectedTool]);
+ }, [darkMode, chartType, dateRange, selectedProject, selectedTool, availableProjects, isCompact]);
const handleTypeChange = e => {
setChartType(e.target.value);
@@ -347,6 +445,7 @@ export default function RentalChart() {
const handleProjectChange = e => {
setSelectedProject(e.target.value);
+ setSelectedTool(FILTER_ALL);
};
const handleToolChange = e => {
@@ -377,105 +476,151 @@ export default function RentalChart() {
const renderChartContent = () => {
if (loading) {
- return
Loading Chart Data....
;
+ return (
+
+ Loading Chart Data....
+
+ );
}
if (error) {
- return
{error}
;
+ return (
+
{error}
+ );
}
if (chartData.datasets.length === 0) {
- return
No data available for the selected filters
;
+ return (
+
+ No data available for the selected filters
+
+ );
}
- return
;
+ return
;
};
return (
-
-
-
Rental Cost Over Time
-
-
-
- Display:
-
- Total Rental Cost
- % of Materials Cost
-
-
-
-
- Project:
-
- All Projects
- {availableProjects.map(projectId => (
-
- Project {projectId.substring(0, 8)}...
-
- ))}
-
-
-
-
- Tool:
-
- All Tools
- {availableTools.map(tool => (
-
- {tool}
-
- ))}
-
-
+
+
Rental Cost Over Time
+
+
+
+
+
+ Display:{' '}
+
+
+ Total Rental Cost
+ % of Materials Cost
+
-
-
- From:
-
-
-
-
- To:
-
-
+
+
+ Project:{' '}
+
+
+ All Projects
+ {Array.from(availableProjects.entries()).map(([id, name]) => (
+
+ {name}
+
+ ))}
+
+
+
+
+
+ Tool:{' '}
+
+
+ All Tools
+ {availableTools.map(tool => (
+
+ {tool}
+
+ ))}
+
-
{renderChartContent()}
+
+
+
+ From:{' '}
+
+
+
+
+
+
+ To:{' '}
+
+
+
+
+
+
+
+ {renderChartContent()}
);
diff --git a/src/components/BMDashboard/RentalChart/RentalChart.module.css b/src/components/BMDashboard/RentalChart/RentalChart.module.css
index 6dc8c03ccb8..8cc3d64a82d 100644
--- a/src/components/BMDashboard/RentalChart/RentalChart.module.css
+++ b/src/components/BMDashboard/RentalChart/RentalChart.module.css
@@ -3,18 +3,41 @@
/* ======================
Light Mode Base
====================== */
-.rentalContainer {
+.rental-container {
position: relative;
width: 100%;
margin: 0 auto;
padding: 20px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ background-color: #fff;
+ border-radius: 8px;
+ min-height: 15vh;
}
-/* General text */
-.textLight {
+.rental-container.dark-mode {
+ background-color: #1b2a41 !important;
color: #e0e0e0;
}
+.text-light {
+ color: #e0e0e0;
+}
+
+/* Same title style as the Percent of Tools Returned Late card */
+.rental-container h1 {
+ margin-bottom: 20px;
+ text-align: left;
+ color: #333;
+ font-size: 24px;
+ font-weight: bold;
+}
+
+.rental-container h1.text-light {
+ color: #fff;
+}
+
/* Chart wrapper (applies in dark + light mode) */
.chartWrapper {
height: 600px;
@@ -27,8 +50,10 @@
box-shadow: 0 2px 10px rgb(0 0 0 / 5%);
}
-/* Chart controls + filters */
-.chartControls {
+/* =========================================
+ Filter Controls
+ ========================================= */
+.chart-controls {
display: flex;
flex-direction: column;
gap: 15px;
@@ -36,30 +61,38 @@
width: 100%;
}
-.filterRow {
+.chart-filters {
display: flex;
flex-wrap: wrap;
- gap: 20px;
- margin-top: 40px;
- align-items: flex-start;
- justify-content: flex-end;
+ justify-content: center;
+ align-items: center;
+ gap: 12px 24px;
+ margin: 15px 0 20px;
}
-.topFilters {
- width: 100%;
- margin-bottom: 10px;
+/* Let both filter rows flow into one centered row that wraps on small screens */
+.filter-row {
+ display: contents;
}
-.dateFilters {
- width: 100%;
+/* Label sits inline, to the left of its control */
+.filter-group {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 8px;
}
-.filterGroup {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- gap: 6px;
- min-width: 150px;
+.filter-group label {
+ margin: 0;
+ white-space: nowrap;
+}
+
+/* Datepicker's inline wrapper adds a line-height gap under the input; its input is
+ forced to width: 100% by a dashboard-wide rule, so size the wrapper instead */
+.filter-group :global(.react-datepicker-wrapper) {
+ display: block;
+ width: 130px;
}
.filterGroup label {
@@ -69,33 +102,74 @@
color: inherit;
}
-.filterText {
- margin-right: 0;
+.filter-text,
+.date-label {
+ margin-right: 8px;
white-space: nowrap;
}
-/* Date picker input */
-.datePicker {
+/* =========================================
+ Inputs & Dropdowns
+ ========================================= */
+
+/* 38px matches the dashboard-wide datepicker input height */
+.rental-chart-select,
+.date-picker {
+ box-sizing: border-box;
+ height: 38px;
padding: 6px 10px;
border: 1px solid #ccc;
- border-radius: 6px; /* rounded */
+ border-radius: 4px;
font-size: 14px;
+}
+
+.rental-chart-select {
+ min-width: 150px;
+}
+
+.date-picker {
width: 120px;
}
-select,
-.rentalChartSelect {
+.dark-select {
+ background-color: #1b2a41 !important;
+ color: #e0e0e0;
+ border: 1px solid #555 !important;
+}
+
+/* Tool filter stays disabled until a project is picked */
+.rental-chart-select:disabled {
+ opacity: 0.55;
+ cursor: not-allowed;
+}
+
+.date-picker.dark-date-picker {
+ background-color: #1b2a41 !important;
+ color: #e0e0e0 !important;
+ border: 1px solid #555 !important;
+}
+
+/* =========================================
+ Chart Area & Loading States
+ ========================================= */
+
+/* Flat, like the Returned Late chart: the dashboard card already frames it */
+.chart-wrapper {
+ height: 500px;
+ width: 100%;
+ margin: 0 auto;
+ box-sizing: border-box;
background-color: #fff;
- color: #333;
- border: 1px solid #ccc;
- border-radius: 6px;
- cursor: pointer;
+}
+
+.dark-chart {
+ background-color: #1b2a41;
}
/* States */
.loading,
.error,
-.noData {
+.no-data {
display: flex;
justify-content: center;
align-items: center;
@@ -108,128 +182,9 @@ select,
color: #d32f2f;
}
-/* React DatePicker overrides (light mode base) */
-.reactDatepicker {
- background-color: #fff;
- color: #333;
- border: 1px solid #ccc;
- border-radius: 6px;
-}
-
-.reactDatepicker__header {
- background-color: #f5f5f5;
- border-bottom: 1px solid #ccc;
- border-radius: 6px 6px 0 0;
-}
-
-.reactDatepicker__current-month,
-.reactDatepicker__day-name,
-.reactDatepicker__day,
-.reactDatepicker__month-text,
-.reactDatepicker__year-text {
- color: #333;
-}
-
-.reactDatepicker__day:hover,
-.reactDatepicker__month-text:hover,
-.reactDatepicker__year-text:hover {
- background-color: #eee;
-}
-
-.reactDatepicker__day--selected,
-.reactDatepicker__day--in-selecting-range,
-.reactDatepicker__day--in-range {
- background-color: #0078d7;
- color: white;
- border-radius: 50%;
-}
-
-.reactDatepickerPopper {
+:global(.react-datepicker-popper) {
z-index: 999 !important;
max-width: 100vw;
- border-radius: 6px;
-}
-
-/* ======================
- Dark Mode Overrides
- ====================== */
-.darkMode {
- background-color: #1b2a41;
- color: #e0e0e0;
-}
-
-.darkMode .rentalContainer {
- background-color: #1b2a41;
- color: #e0e0e0;
-}
-
-.darkMode select,
-.darkMode .rentalChartSelect {
- background-color: #333;
- color: #e0e0e0;
- border: 1px solid #555;
-}
-
-/* DatePicker input & popper */
-.darkMode .DatePickerInput,
-.darkMode .DatePicker,
-.darkMode .DatePickerPopper,
-.darkMode .DatePickerCalendar {
- background-color: #333;
- color: #e0e0e0;
- border: 1px solid #555;
- border-radius: 6px;
- z-index: 9999;
-}
-
-/* Calendar header */
-.darkMode .DatePickerCalendar .react-datepicker__header {
- background-color: #333;
- border-bottom: 1px solid #555;
- color: #e0e0e0;
- border-radius: 6px 6px 0 0;
-}
-
-/* Days, months, years */
-.darkMode .DatePickerCalendar .react-datepicker__day,
-.darkMode .DatePickerCalendar .react-datepicker__day-name,
-.darkMode .DatePickerCalendar .react-datepicker__month-text,
-.darkMode .DatePickerCalendar .react-datepicker__year-text {
- color: #e0e0e0;
- background-color: #333;
- border-radius: 4px;
-}
-
-/* Dark mode DatePicker root */
-.darkMode .react-datepicker {
- background-color: #000 !important; /* match your select */
- color: #e0e0e0 !important;
- border: 1px solid #444 !important;
- border-radius: 6px;
-}
-
-/* Header inside */
-.darkMode .react-datepicker__header {
- background-color: #111 !important;
- border-bottom: 1px solid #444 !important;
- color: #e0e0e0 !important;
-}
-
-/* Hover effect */
-.darkMode .DatePickerCalendar .react-datepicker__day:hover,
-.darkMode .DatePickerCalendar .react-datepicker__month-text:hover,
-.darkMode .DatePickerCalendar .react-datepicker__year-text:hover {
- background-color: #444;
- color: #fff;
-}
-
-/* Selected and in-range */
-.darkMode .DatePickerCalendar .react-datepicker__day--selected,
-.darkMode .DatePickerCalendar .react-datepicker__day--in-range,
-.darkMode .DatePickerCalendar .react-datepicker__day--in-selecting-range {
- background-color: #555;
- color: #fff;
- border-radius: 50%;
}
/* Chart wrapper */
@@ -238,3 +193,63 @@ select,
border-radius: 8px;
box-shadow: 0 2px 10px rgb(255 255 255 / 5%);
}
+
+/* =========================================
+ Small Screens
+ ========================================= */
+
+/* Sized by the card's own width, not the viewport: the dashboard can make the card
+ narrow even on a wide screen */
+.rental-container {
+ container-type: inline-size;
+}
+
+@container (width <= 640px) {
+ .rental-container h1 {
+ margin-bottom: 12px;
+ font-size: 20px;
+ }
+
+ /* Stack filters in a two-column grid so labels and controls line up */
+ .chart-filters {
+ display: grid;
+ grid-template-columns: max-content minmax(0, 1fr);
+ gap: 10px 12px;
+ width: 100%;
+ max-width: 420px;
+ margin: 10px auto 16px;
+ }
+
+ .filter-group {
+ display: contents;
+ }
+
+ .filter-group label {
+ text-align: left;
+ }
+
+ .rental-chart-select,
+ .date-picker {
+ width: 100%;
+ min-width: 0;
+ }
+
+ .filter-group :global(.react-datepicker-wrapper) {
+ width: 100%;
+ }
+
+ .filter-text,
+ .date-label {
+ margin-right: 0;
+ }
+
+ .chart-wrapper {
+ height: 380px;
+ }
+}
+
+@media (width <= 576px) {
+ .rental-container {
+ padding: 12px;
+ }
+}
diff --git a/src/components/BMDashboard/RentalChart/RentalDatePicker.module.css b/src/components/BMDashboard/RentalChart/RentalDatePicker.module.css
new file mode 100644
index 00000000000..fa9961c2620
--- /dev/null
+++ b/src/components/BMDashboard/RentalChart/RentalDatePicker.module.css
@@ -0,0 +1,158 @@
+/* Shared calendar popup theme for the Rental Tracking charts.
+ Applied through react-datepicker's calendarClassName so it only affects these pickers.
+ Colors follow the HGN design system (N = neutral, P = primary, H = highlight). */
+
+.calendar {
+ --cal-bg: #fff;
+ --cal-border: #e5e5e6; /* N40 */
+ --cal-text: #1c313b; /* P500 */
+ --cal-muted: #63676c; /* N500 */
+ --cal-outside: #b6b8ba; /* N70 */
+ --cal-hover-bg: #efeff0; /* N30 */
+ --cal-selected-bg: #595d62; /* N600 */
+ --cal-selected-text: #fff;
+ --cal-range-bg: #e8f4f9; /* H50 */
+ --cal-range-text: #1c313b;
+ --cal-arrow: #2e5061; /* P300 */
+ --cal-select-bg: #fff;
+
+ font-family: inherit;
+ background-color: var(--cal-bg);
+ color: var(--cal-text);
+ border: 1px solid var(--cal-border);
+ border-radius: 8px;
+ box-shadow: 0 6px 20px rgb(0 0 0 / 12%);
+ padding: 4px;
+}
+
+.calendar.dark {
+ --cal-bg: #1b2a41;
+ --cal-border: #3f444a; /* N800 */
+ --cal-text: #e0e0e0;
+ --cal-muted: #b6b8ba; /* N70 */
+ --cal-outside: #707377; /* N400 */
+ --cal-hover-bg: #2c3e50;
+ --cal-selected-bg: #3ea0cb; /* H200 */
+ --cal-selected-text: #fff;
+ --cal-range-bg: #2e5061; /* P300 */
+ --cal-range-text: #fff;
+ --cal-arrow: #e0e0e0;
+ --cal-select-bg: #2c3e50;
+
+ box-shadow: 0 6px 20px rgb(0 0 0 / 45%);
+}
+
+.calendar :global(.react-datepicker__triangle) {
+ display: none;
+}
+
+.calendar :global(.react-datepicker__header) {
+ background-color: var(--cal-bg);
+ border-bottom: none;
+ padding-top: 10px;
+}
+
+.calendar :global(.react-datepicker__current-month) {
+ color: var(--cal-text);
+ font-size: 15px;
+ font-weight: 700;
+ margin-bottom: 6px;
+}
+
+.calendar :global(.react-datepicker__day-name) {
+ color: var(--cal-muted);
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+}
+
+.calendar :global(.react-datepicker__navigation) {
+ top: 10px;
+}
+
+.calendar :global(.react-datepicker__navigation-icon::before) {
+ border-color: var(--cal-arrow);
+ border-width: 2px 2px 0 0;
+}
+
+.calendar :global(.react-datepicker__navigation:hover .react-datepicker__navigation-icon::before) {
+ border-color: var(--cal-text);
+}
+
+.calendar :global(.react-datepicker__month-select),
+.calendar :global(.react-datepicker__year-select) {
+ background-color: var(--cal-select-bg);
+ color: var(--cal-text);
+ border: 1px solid var(--cal-border);
+ border-radius: 4px;
+ padding: 2px 4px;
+ font-size: 13px;
+}
+
+.calendar :global(.react-datepicker__month) {
+ margin: 6px;
+}
+
+/* Every day: round, readable text. Three classes deep to beat the library's range rules. */
+.calendar :global(.react-datepicker__month .react-datepicker__day) {
+ color: var(--cal-text);
+ border-radius: 50%;
+ background-color: transparent;
+ font-weight: 400;
+}
+
+.calendar :global(.react-datepicker__month .react-datepicker__day--outside-month) {
+ color: var(--cal-outside);
+}
+
+.calendar :global(.react-datepicker__month .react-datepicker__day--in-range),
+.calendar :global(.react-datepicker__month .react-datepicker__day--in-selecting-range) {
+ background-color: var(--cal-range-bg);
+ color: var(--cal-range-text);
+}
+
+.calendar :global(.react-datepicker__month .react-datepicker__day:hover),
+.calendar :global(.react-datepicker__month .react-datepicker__day--keyboard-selected) {
+ background-color: var(--cal-hover-bg);
+ color: var(--cal-text);
+}
+
+.calendar :global(.react-datepicker__month .react-datepicker__day--selected),
+.calendar :global(.react-datepicker__month .react-datepicker__day--range-start),
+.calendar :global(.react-datepicker__month .react-datepicker__day--range-end),
+.calendar :global(.react-datepicker__month .react-datepicker__day--selected:hover) {
+ background-color: var(--cal-selected-bg);
+ color: var(--cal-selected-text);
+ font-weight: 600;
+}
+
+.calendar :global(.react-datepicker__month .react-datepicker__day--today) {
+ font-weight: 700;
+}
+
+.calendar :global(.react-datepicker__month .react-datepicker__day--disabled) {
+ color: var(--cal-outside);
+ background-color: transparent;
+ cursor: default;
+ opacity: 0.6;
+}
+
+/* Other dashboard cards ship `.darkMode .react-datepicker__tab-loop ...` rules with !important
+ that reach every calendar on the page; restate the values those rules override. */
+.calendar.dark :global(.react-datepicker__header) {
+ background-color: var(--cal-bg) !important;
+ border-bottom-color: transparent !important;
+}
+
+.calendar.dark :global(.react-datepicker__header .react-datepicker__day-name) {
+ color: var(--cal-muted) !important;
+}
+
+.calendar.dark :global(.react-datepicker__month .react-datepicker__day--outside-month) {
+ color: var(--cal-outside) !important;
+}
+
+:global(.react-datepicker-popper) :global(.react-datepicker).calendar.dark {
+ background-color: var(--cal-bg) !important;
+ border-color: var(--cal-border) !important;
+}
diff --git a/src/components/BMDashboard/RentalChart/ReturnedLateChart.jsx b/src/components/BMDashboard/RentalChart/ReturnedLateChart.jsx
index cfbced79ae5..2f42546ee67 100644
--- a/src/components/BMDashboard/RentalChart/ReturnedLateChart.jsx
+++ b/src/components/BMDashboard/RentalChart/ReturnedLateChart.jsx
@@ -6,6 +6,8 @@ import DatePicker from 'react-datepicker';
import { MultiSelect } from 'react-multi-select-component';
import 'react-datepicker/dist/react-datepicker.css';
import styles from './ReturnedLateChart.module.css';
+import datePickerStyles from './RentalDatePicker.module.css';
+import { Select } from 'antd';
import {
Chart as ChartJS,
CategoryScale,
@@ -66,12 +68,17 @@ function getRequestErrorMessage(error, fallbackMessage) {
);
}
+// Below this width the chart switches to smaller fonts and shorter axis titles
+const COMPACT_CHART_WIDTH = 576;
+
export default function ReturnedLateChart() {
const chartRef = useRef(null);
+ const chartContainerRef = useRef(null);
+ const [chartWidth, setChartWidth] = useState(0);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [availableProjects, setAvailableProjects] = useState([]);
- const [availableTools, setAvailableTools] = useState([]);
+ const [allToolsData, setAllToolsData] = useState([]);
const [selectedProject, setSelectedProject] = useState('All');
const [selectedTools, setSelectedTools] = useState([]);
const [dateRange, setDateRange] = useState({
@@ -87,6 +94,17 @@ export default function ReturnedLateChart() {
const darkMode = useSelector(state => state.theme.darkMode);
const [sortOption, setSortOption] = useState('DESC');
const isMultiProjectView = selectedProject === 'All';
+ // Tools are picked per project, so the list only has the selected project's tools
+ const availableTools = useMemo(() => {
+ if (isMultiProjectView) return [];
+ const tools = allToolsData
+ .filter(item => item.projectId === selectedProject)
+ .map(item => item.toolName)
+ .filter(Boolean);
+ return [...new Set(tools)]
+ .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
+ .map(tool => ({ label: tool, value: tool }));
+ }, [allToolsData, isMultiProjectView, selectedProject]);
const visibleDatasets = useMemo(
() =>
chartData.datasets.map(dataset => ({
@@ -154,8 +172,7 @@ export default function ReturnedLateChart() {
if (toolsResult.status === 'fulfilled') {
const data = getArrayPayload(toolsResult.value.data);
- const tools = Array.from(new Set(data.map(d => d.toolName))).filter(Boolean);
- setAvailableTools(tools.map(t => ({ label: t, value: t })));
+ setAllToolsData(data);
didLoadAnyData = didLoadAnyData || data.length > 0;
}
@@ -178,7 +195,8 @@ export default function ReturnedLateChart() {
}
};
- fetchInitial();
+ // Errors are handled inside fetchInitial
+ fetchInitial().catch(() => {});
}, []);
const buildUrl = () => {
@@ -300,7 +318,8 @@ export default function ReturnedLateChart() {
setLoading(false);
}
};
- fetchData();
+ // Errors are handled inside fetchData
+ fetchData().catch(() => {});
}, [availableProjects, darkMode, selectedProject, dateRange, selectedTools, sortOption]);
useEffect(() => {
@@ -333,9 +352,23 @@ export default function ReturnedLateChart() {
[chartData.datasets, chartData.labels, rawToolsData],
);
+ // Track the chart area's width so fonts and labels can shrink on small screens
+ useEffect(() => {
+ const container = chartContainerRef.current;
+ if (!container || typeof ResizeObserver === 'undefined') return undefined;
+ const observer = new ResizeObserver(([entry]) => setChartWidth(entry.contentRect.width));
+ observer.observe(container);
+ return () => observer.disconnect();
+ }, []);
+
+ const isCompact = chartWidth > 0 && chartWidth < COMPACT_CHART_WIDTH;
+
const options = useMemo(() => {
const textColor = darkMode ? '#fff' : '#333';
- const datalabelCOlor = darkMode ? '#fff' : '#111';
+ const datalabelColor = darkMode ? '#fff' : '#111';
+ // Same grid/axis line colors as the Rental Cost chart; Chart.js' default is invisible on dark
+ const gridColor = darkMode ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)';
+ const axisBorderColor = darkMode ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.25)';
return {
responsive: true,
maintainAspectRatio: false,
@@ -356,8 +389,11 @@ export default function ReturnedLateChart() {
align: 'top',
offset: 4,
formatter: value => `${Number(value).toFixed(0)}%`,
- color: datalabelCOlor,
- font: { weight: 'bold' },
+ // Skip empty bars so "0%" labels don't crowd the axis
+ display: ctx => ctx.dataset.data[ctx.dataIndex] > 0,
+ clamp: true,
+ color: datalabelColor,
+ font: { weight: 'bold', size: isCompact ? 10 : 12 },
},
tooltip: {
callbacks: {
@@ -367,7 +403,7 @@ export default function ReturnedLateChart() {
label(context) {
const v = context.parsed.y;
const label = context.dataset.label;
- return `${label}: ${v}%`;
+ return `${label}: ${Number(Number(v).toFixed(1))}%`;
},
afterLabel(context) {
const toolDetail = rawToolsData.find(
@@ -388,30 +424,39 @@ export default function ReturnedLateChart() {
title: {
display: true,
text: 'Tool Name',
- font: { size: 16, weight: 'bold' },
+ font: { size: isCompact ? 12 : 16, weight: 'bold' },
color: textColor,
},
ticks: {
color: textColor,
+ font: { size: isCompact ? 10 : 12 },
+ maxRotation: isCompact ? 60 : 50,
+ autoSkip: true,
},
+ grid: { color: gridColor },
+ border: { color: axisBorderColor },
},
y: {
beginAtZero: true,
title: {
display: true,
- text: 'Percent of tools returned late',
- font: { size: 16, weight: 'bold' },
+ text: isCompact ? '% returned late' : 'Percent of tools returned late',
+ font: { size: isCompact ? 12 : 16, weight: 'bold' },
color: textColor,
},
ticks: {
color: textColor,
+ font: { size: isCompact ? 10 : 12 },
callback: v => `${v}%`,
},
- max: maxChartValue > 0 ? maxChartValue * 1.15 : 100,
+ grid: { color: gridColor },
+ border: { color: axisBorderColor },
+ // Round the top up to the next 10% (leaving room for labels) so the last tick is clean
+ max: maxChartValue > 0 ? Math.min(100, Math.ceil((maxChartValue * 1.15) / 10) * 10) : 100,
},
},
};
- }, [darkMode, handleBarClick, maxChartValue, rawToolsData]);
+ }, [darkMode, handleBarClick, maxChartValue, rawToolsData, isCompact]);
const toggleProjectVisibility = projectId => {
if (!projectId || !isMultiProjectView) return;
@@ -423,7 +468,6 @@ export default function ReturnedLateChart() {
const multiProjectLegendVisible = isMultiProjectView && legendItems.length > 1;
- const handleProjectChange = e => setSelectedProject(e.target.value);
const handleStartDateChange = date =>
setDateRange(prev => ({ startDate: date, endDate: prev.endDate < date ? date : prev.endDate }));
const handleEndDateChange = date =>
@@ -431,7 +475,13 @@ export default function ReturnedLateChart() {
startDate: prev.startDate > date ? date : prev.startDate,
endDate: date,
}));
- const isOxfordBlue = darkMode ? 'bg-oxford-blue' : '';
+
+ const isOxfordBlue = darkMode ? styles['bg-oxford-blue'] : '';
+
+ const getLegendItemClass = hidden => {
+ if (hidden) return styles['returned-late-legend-item-hidden'];
+ return darkMode ? 'dark-mode-legend' : '';
+ };
return (
@@ -451,9 +501,9 @@ export default function ReturnedLateChart() {
toggleProjectVisibility(item.projectId)}
aria-pressed={!item.hidden}
title={`${item.hidden ? 'Show' : 'Hide'} ${item.label}`}
@@ -475,83 +525,106 @@ export default function ReturnedLateChart() {
-
+ {/* FIX: Added htmlFor to pacify the linter! */}
+
Project:
- {
+ setSelectedProject(value);
+ setSelectedTools([]);
+ }}
className={`${styles['returned-late-project-select']} ${
darkMode ? styles['background-dark'] : ''
}`}
+ popupClassName={darkMode ? styles['dark-dropdown-menu'] : ''}
>
- All Projects
+ All Projects
{availableProjects.map(p => (
-
+
{p.projectName}
-
+
))}
-
+
+ {/* Added the darkMode text-white logic to the label! */}
Tools:
-
+ {/* MultiSelect has no title prop, so the wrapper shows the disabled hint */}
+
+
+
+ {/* FIX: Added htmlFor to pacify the linter! */}
Sort By:
-
- setSortOption(e.target.value)}
+ onChange={value => setSortOption(value)}
className={styles['returned-late-project-select']}
+ popupClassName={darkMode ? styles['dark-dropdown-menu'] : ''}
>
- Highest % Late
- Lowest % Late
- Alphabetical (A–Z)
-
+ Highest % Late
+ Lowest % Late
+ Alphabetical (A–Z)
+
-
+
From:
+
-
+
To:
-
+
{loading && (
s, so the app-wide label margin/bold leaked in as gaps between rows */
+.returned-late-tools-select :global(.select-item) {
+ margin: 0;
+ padding: 8px 10px;
+ font-weight: 400;
+}
+
+.returned-late-tools-select :global(.select-item input) {
+ accent-color: #3ea0cb;
+}
+
+.returned-late-tools-select :global(.search input) {
+ color: inherit;
+}
+
+.returned-late-tools-select :global(.dropdown-content) {
+ z-index: 9999;
}
.returned-late-chart-container {
height: 520px;
position: relative;
+ z-index: 0;
}
.returned-late-loading {
@@ -139,13 +239,18 @@
color: #666;
}
+.bg-oxford-blue .returned-late-loading {
+ color: #ffffff;
+}
+
.returned-late-error {
- color: #d32f2f;
- padding: 16px;
- background-color: #ffebee;
- border: 1px solid #ffcdd2;
- border-radius: 4px;
+ color: #ffffff !important;
+ background-color: #b3261e;
+ padding: 14px 16px;
+ border-radius: 6px;
margin: 20px 0;
+ font-weight: 500;
+ text-align: center;
}
.returned-late-no-data {
@@ -158,193 +263,139 @@
font-style: italic;
}
-.background-dark {
- background-color: #2a3f5f;
- border: 1px solid #555;
+.bg-oxford-blue .returned-late-no-data {
+ color: #ffffff;
}
-.background-red {
- background-color: #ff4d4f;
-}
-
-:global(.dark.rmsc ) {
- --rmsc-border: none;
- --rmsc-bg: none;
- border:transparent;
+.bg-oxford-blue .returned-late-date-picker {
+ background-color: #1b2a41 !important;
+ color: #e0e0e0 !important;
+ border: 1px solid #555 !important;
}
-:global(.dark.rmsc .dropdown-heading) {
- background-color: #2a3f5f;
- color: #fff;
- border: 1px solid #555;
- border-radius: 4px;
- overflow: hidden;
+.bg-oxford-blue .returned-late-project-select {
+ background-color: #1b2a41 !important;
+ border: none !important;
}
-:global(.dark.rmsc .select-item:hover) {
- background-color: #253246;
+.bg-oxford-blue {
+ background-color: #1b2a41 !important;
+ color: #ffffff;
}
-:global(.dark.rmsc .select-item.selected) {
- background-color: #253246;
+.bg-oxford-blue .returned-late-chart-container {
+ background-color: #1b2a41 !important;
}
-:global(.rmsc.dark .options) {
- background-color: #2a3f5f;
- color: #fff;
-}
+/* ==========================================================================
+ Ant Design Dropdown Menu Overrides (Dark Mode)
+ ========================================================================== */
-/* Search box */
-:global(.rmsc.dark .search) {
- background-color: #2a3f5f;
- color: #fff;
-}
+.bg-oxford-blue :global(.ant-select:not(.ant-select-disabled) .ant-select-selector) {
+ background-color: #1b2a41 !important;
+ color: #e0e0e0 !important;
+ border: 1px solid #555555 !important;
-:global(.rmsc.dark .search input:focus) {
- background-color: #223653;
}
-
-.returned-late-detail-backdrop {
- position: fixed;
- inset: 0;
- background: rgb(0 0 0 / 25%);
- z-index: 2147483646;
+.bg-oxford-blue :global(.ant-select-selection-item) {
+ color: #e0e0e0 !important;
}
-.returned-late-detail-panel {
- position: fixed;
- top: 0;
- right: 0;
- width: 360px;
- height: 100vh;
- padding: 20px;
- z-index: 2147483647;
- box-shadow: -4px 0 10px rgb(0 0 0 / 40%);
- background: #fff;
- color: #111;
-}
-
-.dark-panel {
- background: #0b2545;
- color: #fff;
-}
-
-.returned-late-detail-close {
- background: none;
- border: none;
- font-size: 20px;
- float: right;
- cursor: pointer;
- color: inherit;
-}
-
-:global(html.dark-mode) .returned-late-chart h1,
-:global(body.dark-mode) .returned-late-chart h1 {
- color: #fff;
+.bg-oxford-blue :global(.ant-select-arrow) {
+ color: #e0e0e0 !important;
}
-:global(html.dark-mode) .returned-late-legend-hint,
-:global(body.dark-mode) .returned-late-legend-hint {
- color: #cbd5e1;
+.dark-dropdown-menu {
+ background-color: #1b2a41 !important;
}
-:global(html.dark-mode) .returned-late-legend-item,
-:global(body.dark-mode) .returned-late-legend-item {
- background: #0f172a;
- color: #f8fafc;
- border-color: #475569;
+.dark-dropdown-menu :global(.ant-select-item) {
+ color: #e0e0e0 !important;
}
-:global(html.dark-mode) .returned-late-legend-item:hover,
-:global(body.dark-mode) .returned-late-legend-item:hover {
- border-color: #94a3b8;
- background: #172033;
-}
-:global(html.dark-mode) .returned-late-legend-item-hidden,
-:global(body.dark-mode) .returned-late-legend-item-hidden {
- background: #172033;
- color: #cbd5e1;
+.dark-dropdown-menu :global(.ant-select-item-option-active):not(:global(.ant-select-item-option-disabled)) {
+ background-color: #2c3e50 !important;
}
-:global(html.dark-mode) .returned-late-filter-label,
-:global(body.dark-mode) .returned-late-filter-label {
- color: #fff;
-}
-:global(html.dark-mode) .returned-late-project-select,
-:global(body.dark-mode) .returned-late-project-select {
- background-color: #1e293b;
- color: #fff;
- border: 1px solid #475569;
+.dark-dropdown-menu :global(.ant-select-item-option-selected):not(:global(.ant-select-item-option-disabled)) {
+ background-color: #374151 !important;
+ color: #ffffff !important;
+ font-weight: 600;
}
-:global(html.dark-mode) .returned-late-project-select option,
-:global(body.dark-mode) .returned-late-project-select option {
- background-color: #1e293b;
- color: #fff;
-}
-:global(html.dark-mode) .returned-late-date-picker,
-:global(body.dark-mode) .returned-late-date-picker {
- background-color: #1e293b;
- color: #fff;
- border: 1px solid #475569;
+.dark-dropdown-menu :global(.ant-select-empty) {
+ color: #374151 !important;
}
-:global(html.dark-mode) .returned-late-tools-select :global(.dropdown-container),
-:global(body.dark-mode) .returned-late-tools-select :global(.dropdown-container) {
- background-color: #1e293b;
- color: #fff;
- border: 1px solid #475569;
+.bg-oxford-blue .returned-late-legend-item {
+ background-color: #1b2a41 !important;
+ border-color: #555555 !important;
+ color: #ffffff !important;
}
-:global(html.dark-mode) .returned-late-tools-select :global(.dropdown-heading),
-:global(body.dark-mode) .returned-late-tools-select :global(.dropdown-heading) {
- color: #fff;
+.bg-oxford-blue .returned-late-legend-item-hidden {
+ opacity: 0.45 !important;
+ background-color: #152234 !important;
+ border-color: #334155 !important;
}
-:global(html.dark-mode) .returned-late-tools-select :global(.select-panel),
-:global(body.dark-mode) .returned-late-tools-select :global(.select-panel) {
- background-color: #1e293b;
- color: #fff;
- border: 1px solid #475569;
-}
+/* ==========================================================================
+ Small Screens
+ ========================================================================== */
+@container (width <= 640px) {
+ .returned-late-chart h1 {
+ margin-bottom: 12px;
+ font-size: 20px;
+ }
-:global(html.dark-mode) .returned-late-tools-select :global(.select-panel) :global(input),
-:global(body.dark-mode) .returned-late-tools-select :global(.select-panel) :global(input) {
- background-color: #0f172a;
- color: #fff;
- border: 1px solid #475569;
-}
+ .returned-late-legend-block {
+ align-items: flex-start;
+ }
-:global(html.dark-mode) .returned-late-tools-select :global(.select-item),
-:global(body.dark-mode) .returned-late-tools-select :global(.select-item) {
- color: #fff;
-}
+ .returned-late-legend {
+ justify-content: flex-start;
+ }
-:global(html.dark-mode) .returned-late-tools-select :global(.select-item:hover),
-:global(body.dark-mode) .returned-late-tools-select :global(.select-item:hover) {
- background-color: #243b64;
-}
+ /* One filter per row, labels in a fixed column so the controls line up */
+ .returned-late-filters {
+ flex-direction: column;
+ align-items: stretch;
+ gap: 10px;
+ width: 100%;
+ max-width: 420px;
+ margin: 10px auto 16px;
+ }
-@media screen and (width <= 768px) {
- .returned-late-chart h1 {
- margin-bottom: 8px;
+ .returned-late-filter-group {
+ display: grid;
+ grid-template-columns: 72px minmax(0, 1fr);
+ gap: 12px;
}
- .returned-late-legend-hint {
- margin-top: 0;
+ .returned-late-filter-label {
+ text-align: left;
}
- .returned-late-legend-block {
- align-items: stretch;
+ .returned-late-filter-group > div,
+ .returned-late-project-select,
+ .returned-late-chart .returned-late-tools-select,
+ .returned-late-filter-group :global(.react-datepicker-wrapper) {
width: 100%;
+ min-width: 0;
}
- .returned-late-legend {
- justify-content: flex-start;
+ .returned-late-chart-container {
+ height: 380px;
+ }
+}
+
+@media (width <= 576px) {
+ .returned-late-chart {
+ padding: 12px;
}
}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/CardSelector.jsx b/src/components/BMDashboard/WeeklyProjectSummary/CardSelector.jsx
new file mode 100644
index 00000000000..5207e535d7e
--- /dev/null
+++ b/src/components/BMDashboard/WeeklyProjectSummary/CardSelector.jsx
@@ -0,0 +1,194 @@
+import { useEffect, useRef, useState } from 'react';
+import PropTypes from 'prop-types';
+import styles from './CardSelector.module.css';
+
+export const CARD_LIMIT_OPTIONS = ['4', '6', '8', 'all'];
+
+// Preset card groups. Keys that are not on the page are ignored.
+export const CARD_PRESETS = [
+ {
+ id: 'finance',
+ label: 'Finance Focus',
+ keys: ['Financials', 'Financials Tracking', 'Loss Tracking', 'Material Consumption'],
+ },
+ {
+ id: 'schedule',
+ label: 'Schedule Focus',
+ keys: [
+ 'Project Status',
+ 'Risk profile for projects',
+ 'Issue Tracking',
+ 'Issues Breakdown',
+ 'Labor and Time Tracking',
+ ],
+ },
+ {
+ id: 'operations',
+ label: 'Operations Focus',
+ keys: [
+ 'Tools and Equipment Tracking',
+ 'Rental Tracking',
+ 'Material Consumption',
+ 'Injury Severity by Projects',
+ 'Lessons Learned',
+ 'Global Distribution and Project Status Overview',
+ ],
+ },
+ { id: 'all', label: 'All Cards', keys: null },
+];
+
+function CardSelector({ cards, visibleKeys, cardLimit, shownCount, onChange, darkMode }) {
+ const [open, setOpen] = useState(false);
+ const wrapperRef = useRef(null);
+
+ useEffect(() => {
+ if (open === false) return undefined;
+ const handleClickOutside = event => {
+ if (wrapperRef.current && wrapperRef.current.contains(event.target) === false) {
+ setOpen(false);
+ }
+ };
+ const handleEscape = event => {
+ if (event.key === 'Escape') setOpen(false);
+ };
+ document.addEventListener('mousedown', handleClickOutside);
+ document.addEventListener('keydown', handleEscape);
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ document.removeEventListener('keydown', handleEscape);
+ };
+ }, [open]);
+
+ const allKeys = cards.map(card => card.key);
+
+ const toggleKey = key => {
+ const next = new Set(visibleKeys);
+ if (next.has(key)) {
+ next.delete(key);
+ } else {
+ next.add(key);
+ }
+ onChange(
+ allKeys.filter(k => next.has(k)),
+ cardLimit,
+ );
+ };
+
+ const applyPreset = preset => {
+ const keys = preset.keys ? allKeys.filter(k => preset.keys.includes(k)) : allKeys;
+ onChange(keys, 'all');
+ };
+
+ return (
+
+
setOpen(prev => prev === false)}
+ aria-expanded={open}
+ aria-haspopup="dialog"
+ >
+ ⚙ Customize Cards ({shownCount}/{cards.length})
+
+
+ {open && (
+
+
+
Presets
+
+ {CARD_PRESETS.map(preset => (
+ applyPreset(preset)}
+ >
+ {preset.label}
+
+ ))}
+
+
+
+
+
+ Show
+
+ onChange(visibleKeys, e.target.value)}
+ >
+ {CARD_LIMIT_OPTIONS.map(option => (
+
+ {option === 'all' ? 'All' : option}
+
+ ))}
+
+
+
+
+
+
Cards
+
+ onChange(allKeys, cardLimit)}
+ >
+ Select all
+
+ onChange([], cardLimit)}
+ >
+ Clear
+
+
+
+
+
+
+ )}
+
+ );
+}
+
+CardSelector.propTypes = {
+ cards: PropTypes.arrayOf(
+ PropTypes.shape({ key: PropTypes.string.isRequired, title: PropTypes.string.isRequired }),
+ ).isRequired,
+ visibleKeys: PropTypes.arrayOf(PropTypes.string).isRequired,
+ cardLimit: PropTypes.oneOf(CARD_LIMIT_OPTIONS).isRequired,
+ shownCount: PropTypes.number.isRequired,
+ onChange: PropTypes.func.isRequired,
+ darkMode: PropTypes.bool,
+};
+
+CardSelector.defaultProps = {
+ darkMode: false,
+};
+
+export default CardSelector;
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/CardSelector.module.css b/src/components/BMDashboard/WeeklyProjectSummary/CardSelector.module.css
new file mode 100644
index 00000000000..8412efafb6f
--- /dev/null
+++ b/src/components/BMDashboard/WeeklyProjectSummary/CardSelector.module.css
@@ -0,0 +1,148 @@
+.cardSelector {
+ position: relative;
+ display: inline-block;
+}
+
+.toggleButton {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 12px;
+ border: 1px solid #ced4da;
+ border-radius: 6px;
+ background: #fff;
+ color: #212529;
+ font-size: 0.875rem;
+ cursor: pointer;
+}
+
+.toggleButton:hover {
+ background: #f1f3f5;
+}
+
+.toggleButtonDark {
+ background: #2b3e59;
+ border-color: #4a5a72;
+ color: #fff;
+}
+
+.toggleButtonDark:hover {
+ background: #36506f;
+}
+
+.panel {
+ position: absolute;
+ top: calc(100% + 6px);
+ right: 0;
+ z-index: 20;
+ width: min(340px, 90vw);
+ max-height: 70vh;
+ overflow-y: auto;
+ padding: 12px;
+ border: 1px solid #dee2e6;
+ border-radius: 8px;
+ background: #fff;
+ color: #212529;
+ box-shadow: 0 6px 20px rgb(0 0 0 / 15%);
+ box-sizing: border-box;
+}
+
+.panelDark {
+ background: #1e2d42;
+ border-color: #4a5a72;
+ color: #e0e6f0;
+}
+
+.group {
+ margin-bottom: 12px;
+}
+
+.group:last-child {
+ margin-bottom: 0;
+}
+
+.groupLabel {
+ display: block;
+ margin-bottom: 6px;
+ font-size: 0.75rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.03em;
+ opacity: 0.8;
+}
+
+.presetRow {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+}
+
+.presetButton {
+ padding: 4px 10px;
+ border: 1px solid currentcolor;
+ border-radius: 999px;
+ background: transparent;
+ color: inherit;
+ font-size: 0.8rem;
+ cursor: pointer;
+}
+
+.presetButton:hover {
+ opacity: 0.8;
+}
+
+.limitSelect {
+ padding: 4px 8px;
+ border: 1px solid #ced4da;
+ border-radius: 4px;
+ background: #fff;
+ color: #212529;
+ font-size: 0.875rem;
+}
+
+.panelDark .limitSelect {
+ background: #2b3e59;
+ border-color: #4a5a72;
+ color: #fff;
+}
+
+.listHeader {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 6px;
+}
+
+.listHeader .groupLabel {
+ margin-bottom: 0;
+}
+
+.linkButton {
+ margin-left: 8px;
+ padding: 0;
+ border: 0;
+ background: none;
+ color: #0d6efd;
+ font-size: 0.8rem;
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+.panelDark .linkButton {
+ color: #8ab4ff;
+}
+
+.cardList {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.cardOption {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 4px 0;
+ font-size: 0.875rem;
+ cursor: pointer;
+}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/CostPredictionChart.jsx b/src/components/BMDashboard/WeeklyProjectSummary/CostPredictionChart.jsx
index 27dad4dd9ac..4141289cca1 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/CostPredictionChart.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/CostPredictionChart.jsx
@@ -56,7 +56,55 @@ const PROJECTS = [
];
const DEFAULT_WINDOW_MONTHS = 6;
-const TODAY_STR = new Date().toISOString().slice(0, 10);
+// Local calendar date as YYYY-MM-DD. toISOString() returns the UTC date, which is
+// already "tomorrow" in the evening for timezones behind UTC (e.g. 6 p.m. Pacific),
+// so it must not be used for the date-input limit or clamping.
+export function getLocalDateString(date = new Date()) {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+ return `${year}-${month}-${day}`;
+}
+
+const MONTH_NAMES = [
+ 'january',
+ 'february',
+ 'march',
+ 'april',
+ 'may',
+ 'june',
+ 'july',
+ 'august',
+ 'september',
+ 'october',
+ 'november',
+ 'december',
+];
+
+// Parses a "Month YYYY" label (e.g. "March 2026") into a local Date set to
+// the 1st of that month. Avoids relying on the native Date constructor's
+// implementation-defined parsing of non-ISO strings, which behaves
+// inconsistently across browsers/engines.
+export function parseMonthLabel(label) {
+ const parts = String(label || '')
+ .trim()
+ .split(/\s+/);
+ if (parts.length !== 2) return new Date(NaN);
+ const monthIndex = MONTH_NAMES.indexOf(parts[0].toLowerCase());
+ const year = Number(parts[1]);
+ if (monthIndex === -1 || Number.isNaN(year)) return new Date(NaN);
+ return new Date(year, monthIndex, 1);
+}
+
+// Parses a "YYYY-MM-DD" date-input value into a local Date, avoiding the
+// UTC-midnight interpretation the Date constructor applies to bare ISO
+// date strings (which can shift the date by a day in non-UTC timezones).
+export function parseIsoDateOnly(value) {
+ if (!value) return null;
+ const [y, m, d] = value.split('-').map(Number);
+ if (!y || !m || !d) return null;
+ return new Date(y, m - 1, d);
+}
const SAMPLE_DATA_BASE = {
'building-1': { plannedStart: 400, plannedGrowth: 5.6, actualStart: 380, actualGrowth: 5.4 },
@@ -285,6 +333,8 @@ function CostPredictionChart({ projectId }) {
// filter state
const [selectedProjectId, setSelectedProjectId] = useState('');
const [dateRange, setDateRange] = useState({ start: '', end: '' }); // 'YYYY-MM-DD'
+ // Computed each render so it stays correct if the page is left open past midnight.
+ const todayStr = getLocalDateString();
const legendItems = [
{ label: 'Planned Cost', color: SERIES_COLORS.planned, type: 'circle' },
@@ -337,17 +387,17 @@ function CostPredictionChart({ projectId }) {
return chartData.slice(-DEFAULT_WINDOW_MONTHS);
}
return chartData.filter(d => {
- const pointDate = new Date(d.month);
+ const pointDate = parseMonthLabel(d.month);
if (dateRange.start) {
- const s = new Date(dateRange.start);
- const startMonth = new Date(s.getFullYear(), s.getMonth(), 1);
- if (pointDate < startMonth) return false;
+ const s = parseIsoDateOnly(dateRange.start);
+ const startMonth = s && new Date(s.getFullYear(), s.getMonth(), 1);
+ if (startMonth && pointDate < startMonth) return false;
}
if (dateRange.end) {
- const e = new Date(dateRange.end);
- const endMonth = new Date(e.getFullYear(), e.getMonth(), 1);
- if (pointDate > endMonth) return false;
+ const e = parseIsoDateOnly(dateRange.end);
+ const endMonth = e && new Date(e.getFullYear(), e.getMonth(), 1);
+ if (endMonth && pointDate > endMonth) return false;
}
return true;
});
@@ -450,7 +500,7 @@ function CostPredictionChart({ projectId }) {
style={filterStyles.control}
value={dateRange.start}
min="2011-01-01"
- max={dateRange.end || TODAY_STR}
+ max={dateRange.end || todayStr}
onChange={e => setDateRange(prev => ({ ...prev, start: e.target.value }))}
/>
@@ -468,8 +518,11 @@ function CostPredictionChart({ projectId }) {
style={filterStyles.control}
value={dateRange.end}
min={dateRange.start || undefined}
- max={TODAY_STR}
- onChange={e => setDateRange(prev => ({ ...prev, end: e.target.value }))}
+ max={todayStr}
+ onChange={e => {
+ const clamped = e.target.value > todayStr ? todayStr : e.target.value;
+ setDateRange(prev => ({ ...prev, end: clamped }));
+ }}
/>
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/DistributionLaborHours/DistributionLaborHours.jsx b/src/components/BMDashboard/WeeklyProjectSummary/DistributionLaborHours/DistributionLaborHours.jsx
index 15929b03294..a5fddea041a 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/DistributionLaborHours/DistributionLaborHours.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/DistributionLaborHours/DistributionLaborHours.jsx
@@ -1,22 +1,23 @@
-import React, { useState, useEffect } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
import Select from 'react-select';
import { useSelector } from 'react-redux';
import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer } from 'recharts';
import styles from './DistributionLaborHours.module.css';
-const COLORS = ['#2a647c', '#2e8ea3', '#ffab91', '#ffccbb', '#bbbbbb', '#f9f3e3'];
+const COLORS = ['#4f8fa8', '#3fa4b8', '#ffab91', '#ffccbb', '#c7c7c7', '#f9f3e3'];
const CustomTooltip = ({ active, payload, total, darkMode }) => {
if (active && payload && payload.length) {
const { name, value } = payload[0];
- const percent = ((value / total) * 100).toFixed(1);
+ const percent = total > 0 ? ((value / total) * 100).toFixed(1) : '0.0';
+
return (
{name}
@@ -25,6 +26,7 @@ const CustomTooltip = ({ active, payload, total, darkMode }) => {
);
}
+
return null;
};
@@ -33,37 +35,135 @@ export default function DistributionLaborHours() {
const [originalData, setOriginalData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
- const [dateRange, setDateRange] = useState({ from: '', to: '' });
+
+ const [dateRange, setDateRange] = useState({
+ from: '',
+ to: '',
+ });
+
const [projectFilter, setProjectFilter] = useState('');
const [memberFilter, setMemberFilter] = useState('');
+ const [filtersApplied, setFiltersApplied] = useState(false);
useEffect(() => {
const fetchData = async () => {
const mockData = [
- { name: 'Stud Wall Construction', value: 25.9 },
- { name: 'Foundation Concreting', value: 18.5 },
- { name: 'Task A', value: 22.2 },
- { name: 'Task B', value: 18.5 },
- { name: 'Task C', value: 14.8 },
- { name: 'Electrical', value: 12 },
- { name: 'Plumbing', value: 8 },
- { name: 'Welding', value: 6 },
+ {
+ name: 'Stud Wall Construction',
+ value: 25.9,
+ project: 'Project A',
+ member: 'Member 1',
+ date: '2026-08-05',
+ },
+ {
+ name: 'Foundation Concreting',
+ value: 18.5,
+ project: 'Project A',
+ member: 'Member 2',
+ date: '2026-08-09',
+ },
+ {
+ name: 'Task A',
+ value: 22.2,
+ project: 'Project B',
+ member: 'Member 1',
+ date: '2026-08-12',
+ },
+ {
+ name: 'Task B',
+ value: 18.5,
+ project: 'Project B',
+ member: 'Member 2',
+ date: '2026-08-16',
+ },
+ {
+ name: 'Task C',
+ value: 14.8,
+ project: 'Project A',
+ member: 'Member 1',
+ date: '2026-08-20',
+ },
+ {
+ name: 'Electrical',
+ value: 12,
+ project: 'Project A',
+ member: 'Member 2',
+ date: '2026-08-22',
+ },
+ {
+ name: 'Plumbing',
+ value: 8,
+ project: 'Project B',
+ member: 'Member 1',
+ date: '2026-08-24',
+ },
+ {
+ name: 'Welding',
+ value: 6,
+ project: 'Project B',
+ member: 'Member 2',
+ date: '2026-08-27',
+ },
];
+
setOriginalData(mockData);
};
+
fetchData();
}, []);
- useEffect(() => {
- const top5 = originalData.slice(0, 5);
- const othersTotal = originalData.slice(5).reduce((sum, item) => sum + item.value, 0);
+ const formatChartData = data => {
+ const sortedData = [...data].sort((a, b) => b.value - a.value);
+
+ const top5 = sortedData.slice(0, 5).map(({ name, value }) => ({
+ name,
+ value,
+ }));
+
+ const othersTotal = sortedData.slice(5).reduce((sum, item) => sum + item.value, 0);
+
if (othersTotal > 0) {
- top5.push({ name: 'Others', value: othersTotal });
+ top5.push({
+ name: 'Others',
+ value: Number(othersTotal.toFixed(1)),
+ });
}
- setFilteredData(top5);
- }, [originalData, dateRange, projectFilter, memberFilter]);
- const totalHours = filteredData.reduce((sum, item) => sum + item.value, 0);
+ return top5;
+ };
+
+ useEffect(() => {
+ if (originalData.length > 0) {
+ setFilteredData(formatChartData(originalData));
+ }
+ }, [originalData]);
+
+ const handleSubmit = () => {
+ let result = [...originalData];
+
+ if (dateRange.from) {
+ result = result.filter(item => item.date >= dateRange.from);
+ }
+
+ if (dateRange.to) {
+ result = result.filter(item => item.date <= dateRange.to);
+ }
+
+ if (projectFilter) {
+ result = result.filter(item => item.project === projectFilter);
+ }
+
+ if (memberFilter) {
+ result = result.filter(item => item.member === memberFilter);
+ }
+
+ setFilteredData(formatChartData(result));
+ setFiltersApplied(true);
+ };
+
+ const totalHours = useMemo(() => filteredData.reduce((sum, item) => sum + item.value, 0), [
+ filteredData,
+ ]);
const projectOptions = [
{ value: '', label: 'ALL' },
@@ -77,104 +177,191 @@ export default function DistributionLaborHours() {
{ value: 'Member 2', label: 'Member 2' },
];
+ const selectedProject =
+ projectOptions.find(option => option.value === projectFilter) || projectOptions[0];
+
+ const selectedMember =
+ memberOptions.find(option => option.value === memberFilter) || memberOptions[0];
+
+ const menuPortalTarget = typeof document !== 'undefined' ? document.body : null;
+
+ const selectStyles = {
+ menuPortal: base => ({
+ ...base,
+ zIndex: 9999,
+ }),
+ menu: base => ({
+ ...base,
+ zIndex: 9999,
+ }),
+ };
+
return (
-
Distribution of Labor Hours
-
- {/* Filters */}
-
-
- From:
- setDateRange({ ...dateRange, from: e.target.value })}
- />
-
-
- To:
- setDateRange({ ...dateRange, to: e.target.value })}
- />
-
-
- Project:
- opt.value === projectFilter)}
- onChange={opt => setProjectFilter(opt.value)}
- className="react-select-container"
- classNamePrefix="react-select"
- />
-
-
- Member:
- opt.value === memberFilter)}
- onChange={opt => setMemberFilter(opt.value)}
- className="react-select-container"
- classNamePrefix="react-select"
- />
-
-
-
-
- Submit
-
-
-
+
+
Distribution of Labor Hours
+
+
+
+
+ From
+ {
+ const newFrom = e.target.value;
+
+ setDateRange(previous => ({
+ from: newFrom,
+ to: previous.to && newFrom && newFrom > previous.to ? '' : previous.to,
+ }));
+
+ setFiltersApplied(false);
+ }}
+ />
+
+
+
+
To
+
{
+ const newTo = e.target.value;
+
+ setDateRange(previous => ({
+ from: previous.from && newTo && newTo < previous.from ? '' : previous.from,
+ to: newTo,
+ }));
- {/* Chart + Legend */}
-
-
- {filteredData.map((entry, index) => (
-
-
-
- {entry.name}: {entry.value} hrs
-
- ))}
+
+
+ Project
+ {
+ setProjectFilter(option?.value || '');
+ setFiltersApplied(false);
+ }}
+ className={styles.selectContainer}
+ classNamePrefix="labor-hours-select"
+ menuPortalTarget={menuPortalTarget}
+ menuPosition="fixed"
+ styles={selectStyles}
+ isSearchable={false}
+ />
+
+
+
+ Member
+ {
+ setMemberFilter(option?.value || '');
+ setFiltersApplied(false);
+ }}
+ className={styles.selectContainer}
+ classNamePrefix="labor-hours-select"
+ menuPortalTarget={menuPortalTarget}
+ menuPosition="fixed"
+ styles={selectStyles}
+ isSearchable={false}
+ />
+
+
+
+
+
+ Submit
+
+
+ {filtersApplied && Filters applied }
+
-
-
-
- (
-
- {`${((value / totalHours) * 100).toFixed(1)}%`}
-
- )}
- >
+
+ {filteredData.length > 0 ? (
+
+
{filteredData.map((entry, index) => (
-
|
+
+
+
+
+ {entry.name}: {entry.value} hrs
+
+
))}
-
-
} />
-
-
+
+
+
+
+
+ (
+
+ {totalHours > 0 ? `${((value / totalHours) * 100).toFixed(1)}%` : '0.0%'}
+
+ )}
+ >
+ {filteredData.map((entry, index) => (
+ |
+ ))}
+
+
+ } />
+
+
+
+
+ ) : (
+
No labor-hour data found for the selected filters.
+ )}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/DistributionLaborHours/DistributionLaborHours.module.css b/src/components/BMDashboard/WeeklyProjectSummary/DistributionLaborHours/DistributionLaborHours.module.css
index e59d56e8a95..f793516ffd3 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/DistributionLaborHours/DistributionLaborHours.module.css
+++ b/src/components/BMDashboard/WeeklyProjectSummary/DistributionLaborHours/DistributionLaborHours.module.css
@@ -1,23 +1,55 @@
-/* stylelint-disable */
+/* stylelint-disable */
+
.container {
- border-radius: 8px;
- padding: 12px;
- box-shadow: 0 1px 3px rgb(0 0 0 / 15%);
width: 100%;
height: 100%;
+ min-height: 650px;
+ box-sizing: border-box;
+ container-type: inline-size;
+
display: flex;
flex-direction: column;
+ align-items: stretch;
+ justify-content: flex-start;
+
+ border-radius: 8px;
+ padding: 20px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 15%);
+
transition: background-color 0.3s ease, color 0.3s ease;
}
+/*
+ * Keep the title, filters, Submit button, and chart
+ * grouped together with consistent vertical spacing.
+ * The content starts near the top of the card.
+ */
+.content {
+ width: 100%;
+ max-width: 100%;
+ min-width: 0;
+
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+/* =========================
+ TITLE
+ ========================= */
+
.title {
+ width: 100%;
+
+ margin: 0 0 20px;
+
+ color: #111827;
+
font-size: large;
font-weight: bold;
- margin-bottom: 18px;
- color: #111827;
+ text-align: center;
}
-/* Keep title blue in dark mode */
:global(.dark) .title,
:global(.dark-mode) .title,
:global(.bm-dashboard-dark) .title,
@@ -25,234 +57,491 @@
color: #2563eb !important;
}
-.filters {
+/* =========================
+ FILTERS
+ ========================= */
+
+.filtersSection {
+ width: 100%;
+
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.filterRow {
+ width: 100%;
+
display: flex;
flex-wrap: wrap;
+
justify-content: center;
- gap: 12px;
- margin-bottom: 16px;
+ align-items: flex-end;
+
+ column-gap: 18px;
+ row-gap: 12px;
}
-.filters label {
+.filterGroup {
+ min-width: 115px;
+
display: flex;
flex-direction: column;
+
+ gap: 6px;
+}
+
+.filterGroup label {
+ margin: 0;
+
font-size: 0.9rem;
+ font-weight: 600;
+ text-align: center;
}
-/* Dark mode: make filter labels readable */
-:global(.dark) .filters label,
-:global(.dark-mode) .filters label,
-:global([data-theme='dark']) .filters label {
+:global(.dark) .filterGroup label,
+:global(.dark-mode) .filterGroup label,
+:global(.bm-dashboard-dark) .filterGroup label,
+:global([data-theme='dark']) .filterGroup label {
color: #fff;
}
-/* Scope to date inputs only — react-select renders its own
inside
- .filters, and this box styling must not apply to it. */
-.filters input[type='date'] {
- height: auto !important;
- border-radius: 6px;
+/* =========================
+ DATE INPUTS
+ ========================= */
+
+.filterGroup input[type='date'] {
+ min-height: 38px;
+
box-sizing: border-box;
+
+ padding: 7px 9px;
+
+ border: 1px solid #d1d5db;
+ border-radius: 6px;
+
font-size: 0.95rem;
line-height: 1.2;
}
-/* Give the dropdowns enough width and keep option text on one line */
-:global(.react-select-container) {
- min-width: 100px;
+:global(.dark) .filterGroup input[type='date'],
+:global(.dark-mode) .filterGroup input[type='date'],
+:global(.bm-dashboard-dark) .filterGroup input[type='date'],
+:global([data-theme='dark']) .filterGroup input[type='date'] {
+ background-color: #1f2937 !important;
+ color: #f9fafb !important;
+ border-color: #374151 !important;
+
+ color-scheme: dark;
+}
+
+/* =========================
+ REACT SELECT
+ ========================= */
+
+.selectContainer {
+ min-width: 115px;
+}
+
+:global(.labor-hours-select__control) {
+ min-height: 38px !important;
+ cursor: pointer !important;
}
-:global(.react-select__option),
-:global(.react-select__single-value) {
+:global(.labor-hours-select__option),
+:global(.labor-hours-select__single-value) {
white-space: nowrap;
font-weight: normal !important;
}
-:global(.dark) :global(.react-select__control),
-:global(.dark-mode) :global(.react-select__control),
-:global([data-theme='dark']) :global(.react-select__control) {
+:global(.labor-hours-select__menu),
+:global(.labor-hours-select__menu-portal) {
+ z-index: 9999 !important;
+}
+
+:global(.labor-hours-select__indicator-separator) {
+ background-color: #ccc !important;
+}
+
+:global(.labor-hours-select__dropdown-indicator) {
+ color: #ccc !important;
+}
+
+/* =========================
+ SELECT DARK MODE
+ ========================= */
+
+:global(.dark) :global(.labor-hours-select__control),
+:global(.dark-mode) :global(.labor-hours-select__control),
+:global(.bm-dashboard-dark) :global(.labor-hours-select__control),
+:global([data-theme='dark']) :global(.labor-hours-select__control) {
background-color: #1f2937 !important;
border-color: #374151 !important;
}
-:global(.dark) :global(.react-select__single-value),
-:global(.dark-mode) :global(.react-select__single-value),
-:global([data-theme='dark']) :global(.react-select__single-value),
-:global(.dark) :global(.react-select__input),
-:global(.dark-mode) :global(.react-select__input),
-:global([data-theme='dark']) :global(.react-select__input) {
+:global(.dark) :global(.labor-hours-select__single-value),
+:global(.dark-mode) :global(.labor-hours-select__single-value),
+:global(.bm-dashboard-dark) :global(.labor-hours-select__single-value),
+:global([data-theme='dark']) :global(.labor-hours-select__single-value) {
color: #f9fafb !important;
}
-:global(.dark) :global(.react-select__placeholder),
-:global(.dark-mode) :global(.react-select__placeholder),
-:global([data-theme='dark']) :global(.react-select__placeholder) {
- color: #9ca3af !important;
+:global(.dark) :global(.labor-hours-select__input),
+:global(.dark-mode) :global(.labor-hours-select__input),
+:global(.bm-dashboard-dark) :global(.labor-hours-select__input),
+:global([data-theme='dark']) :global(.labor-hours-select__input) {
+ color: #f9fafb !important;
}
-/* Dim the vertical separator between the value and the dropdown arrow */
-:global(.react-select__indicator-separator) {
- background-color: #ccc !important;
+:global(.dark) :global(.labor-hours-select__placeholder),
+:global(.dark-mode) :global(.labor-hours-select__placeholder),
+:global(.bm-dashboard-dark) :global(.labor-hours-select__placeholder),
+:global([data-theme='dark']) :global(.labor-hours-select__placeholder) {
+ color: #9ca3af !important;
}
-/* Dark mode: use the panel color for the separator */
-:global(.dark) :global(.react-select__indicator-separator),
-:global(.dark-mode) :global(.react-select__indicator-separator),
-:global([data-theme='dark']) :global(.react-select__indicator-separator) {
- background-color: #2d4059 !important;
+:global(.dark) :global(.labor-hours-select__indicator-separator),
+:global(.dark-mode) :global(.labor-hours-select__indicator-separator),
+:global(.bm-dashboard-dark) :global(.labor-hours-select__indicator-separator),
+:global([data-theme='dark']) :global(.labor-hours-select__indicator-separator) {
+ background-color: #4b5563 !important;
}
-/* Make the dropdown arrow a light gray (not bright white) in both modes */
-:global(.react-select__dropdown-indicator) {
- color: #ccc !important;
-}
-
-:global(.dark) :global(.react-select__menu),
-:global(.dark-mode) :global(.react-select__menu),
-:global([data-theme='dark']) :global(.react-select__menu) {
+:global(.dark) :global(.labor-hours-select__menu),
+:global(.dark-mode) :global(.labor-hours-select__menu),
+:global(.bm-dashboard-dark) :global(.labor-hours-select__menu),
+:global([data-theme='dark']) :global(.labor-hours-select__menu) {
background-color: #1f2937 !important;
border: 1px solid #374151 !important;
- z-index: 10;
}
-:global(.dark) :global(.react-select__option),
-:global(.dark-mode) :global(.react-select__option),
-:global([data-theme='dark']) :global(.react-select__option) {
+:global(.dark) :global(.labor-hours-select__option),
+:global(.dark-mode) :global(.labor-hours-select__option),
+:global(.bm-dashboard-dark) :global(.labor-hours-select__option),
+:global([data-theme='dark']) :global(.labor-hours-select__option) {
background-color: #1f2937 !important;
color: #f9fafb !important;
+ cursor: pointer;
}
-:global(.dark) :global(.react-select__option--is-focused),
-:global(.dark-mode) :global(.react-select__option--is-focused),
-:global([data-theme='dark']) :global(.react-select__option--is-focused) {
+:global(.dark) :global(.labor-hours-select__option--is-focused),
+:global(.dark-mode) :global(.labor-hours-select__option--is-focused),
+:global(.bm-dashboard-dark) :global(.labor-hours-select__option--is-focused),
+:global([data-theme='dark']) :global(.labor-hours-select__option--is-focused) {
background-color: #374151 !important;
- color: #ffffff !important;
+ color: #fff !important;
}
-/* Dark mode: style date inputs only (not react-select's internal input) */
-:global(.dark) .filters input[type='date'],
-:global(.dark-mode) .filters input[type='date'],
-:global([data-theme='dark']) .filters input[type='date'] {
- background-color: #1f2937 !important;
- color: #f9fafb !important;
- border: 1px solid #374151 !important;
+
+:global(.dark) :global(.labor-hours-select__option--is-selected),
+:global(.dark-mode) :global(.labor-hours-select__option--is-selected),
+:global(.bm-dashboard-dark) :global(.labor-hours-select__option--is-selected),
+:global([data-theme='dark']) :global(.labor-hours-select__option--is-selected) {
+ background-color: #2563eb !important;
+ color: #fff !important;
}
-:global(.dark) .filters input[type='date'],
-:global(.dark-mode) .filters input[type='date'],
-:global([data-theme='dark']) .filters input[type='date'] {
- color-scheme: dark;
+/* =========================
+ SUBMIT
+ ========================= */
+
+.submitSection {
+ position: relative;
+
+ width: 100%;
+ height: 42px;
+
+ margin-top: 12px;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.button {
+ min-height: 38px;
+
+ padding: 8px 18px;
+
+ border: none;
+ border-radius: 6px;
+
+ background-color: #2563eb;
+ color: #fff;
+
+ cursor: pointer;
+
+ transition: background-color 0.3s ease;
+}
+
+.button:hover {
+ background-color: #1d4ed8;
+}
+
+.button:focus-visible {
+ outline: 2px solid #93c5fd;
+ outline-offset: 2px;
+}
+
+:global(.dark) .button,
+:global(.dark-mode) .button,
+:global(.bm-dashboard-dark) .button,
+:global([data-theme='dark']) .button {
+ background-color: #2563eb;
+ color: #fff;
}
-.chartWrapper {
+.appliedMessage {
+ position: absolute;
+
+ left: calc(50% + 58px);
+ top: 50%;
+
+ transform: translateY(-50%);
+
+ white-space: nowrap;
+
+ color: #166534;
+
+ font-size: 0.8rem;
+ font-weight: 500;
+}
+
+:global(.dark) .appliedMessage,
+:global(.dark-mode) .appliedMessage,
+:global(.bm-dashboard-dark) .appliedMessage,
+:global([data-theme='dark']) .appliedMessage {
+ color: #86efac;
+}
+
+/* =========================
+ VISUALIZATION
+ ========================= */
+
+/*
+ * Important:
+ * No flex-grow.
+ * No min-height.
+ * No huge padding.
+ *
+ * The chart simply follows the filters with a controlled gap.
+ */
+.visualizationSection {
+ width: 100%;
+ min-width: 0;
+ flex: 0 0 auto;
+
display: flex;
- justify-content: space-between;
align-items: center;
- flex-wrap: wrap;
- gap: 16px;
+ justify-content: center;
+
+ margin-top: 28px;
+ padding: 0;
+}
+
+.visualizationContent {
+ width: 100%;
+ max-width: 700px;
+ min-width: 0;
+
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(0, 340px);
+
+ justify-content: center;
+ align-items: center;
+ column-gap: 24px;
}
+/* =========================
+ LEGEND
+ ========================= */
+
.legend {
+ width: 100%;
+ min-width: 0;
+
display: flex;
flex-direction: column;
- min-width: 150px;
+ justify-content: center;
}
.legendItem {
display: flex;
align-items: center;
- margin-bottom: 8px;
+
+ margin-bottom: 9px;
+
font-size: 0.85rem;
}
+.legendItem:last-child {
+ margin-bottom: 0;
+}
+
+.legendText {
+ min-width: 0;
+ overflow-wrap: anywhere;
+ line-height: 1.3;
+}
+
.colorBox {
width: 12px;
height: 12px;
+
margin-right: 8px;
+
border-radius: 2px;
+
+ flex-shrink: 0;
+}
+
+/* =========================
+ PIE
+ ========================= */
+
+.pieChartContainer {
+ width: 100%;
+ max-width: 340px;
+ height: 300px;
+ min-width: 0;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
+/* =========================
+ TOOLTIP
+ ========================= */
+
.tooltip {
- background-color: #f3f4f6;
- color: #111827; /* darker percentage text in light mode */
- padding: 8px;
+ padding: 8px 10px;
+
border-radius: 6px;
+
+ background-color: #f3f4f6;
+ color: #111827;
+
+ box-shadow: 0 2px 6px rgb(0 0 0 / 15%);
+}
+
+.tooltip p {
+ margin: 2px 0;
}
-/* Dark mode: ensure tooltip text is white on hover */
:global(.dark) .tooltip,
:global(.dark-mode) .tooltip,
+:global(.bm-dashboard-dark) .tooltip,
:global([data-theme='dark']) .tooltip {
color: #fff;
}
-:global(.dark) .tooltip:hover,
-:global(.dark-mode) .tooltip:hover,
-:global([data-theme='dark']) .tooltip:hover {
- color: #fff;
-}
-
-/* Dark mode: force ALL tooltip text to white */
:global(.dark) .tooltip *,
:global(.dark-mode) .tooltip *,
+:global(.bm-dashboard-dark) .tooltip *,
:global([data-theme='dark']) .tooltip * {
color: #fff !important;
}
-.buttonContainer {
- width: 100%;
+/* =========================
+ NO DATA
+ ========================= */
+
+.noData {
+ width: 100%;
+ min-height: 250px;
+
display: flex;
+ align-items: center;
justify-content: center;
- margin-top: 5px;
-}
-/* Submit button – light & dark mode safe */
-.button {
- background-color: #2563eb; /* blue */
- color: #fff; /* white text */
- border: none;
- border-radius: 6px;
- padding: 8px 16px;
- cursor: pointer;
- transition: background-color 0.3s ease;
+ color: #4b5563;
+
+ text-align: center;
+ font-size: 0.95rem;
}
-/* Hover */
-.button:hover {
- background-color: #1d4ed8;
+:global(.dark) .noData,
+:global(.dark-mode) .noData,
+:global(.bm-dashboard-dark) .noData,
+:global([data-theme='dark']) .noData {
+ color: #d1d5db;
}
-/* Dark mode – keep same button color (no brightness jump) */
-:global(.dark) .button,
-:global(.dark-mode) .button,
-:global([data-theme='dark']) .button {
- background-color: #2563eb;
- color: #fff;
+@container (max-width: 620px) {
+ .visualizationContent {
+ grid-template-columns: minmax(0, 1fr);
+ justify-items: center;
+ row-gap: 16px;
+ }
+
+ .legend {
+ width: 100%;
+ max-width: 320px;
+ }
+
+ .pieChartContainer {
+ width: 100%;
+ max-width: 300px;
+ height: 290px;
+ }
+
+ .visualizationSection {
+ margin-top: 24px;
+ }
}
+/* =========================
+ TABLET
+ ========================= */
-.pieChartContainer {
- display: flex;
- justify-content: center;
- align-items: center;
- width: 100%;
+@media (width <= 900px) {
+ .container {
+ height: auto;
+ min-height: auto;
+ padding: 24px 18px;
+ }
+
+ .content {
+ max-width: none;
+ }
}
+/* =========================
+ MOBILE
+ ========================= */
+
@media (width <= 768px) {
- .chartWrapper {
+ .container {
+ padding: 20px 14px;
+ }
+
+ .filterRow {
flex-direction: column;
align-items: center;
+
+ gap: 10px;
}
- .legend {
- margin-bottom: 16px;
- align-items: center;
+ .filterGroup {
+ width: 100%;
+ max-width: 280px;
+ }
+
+ .selectContainer {
+ width: 100%;
}
- .filters {
+ .submitSection {
+ height: auto;
+
+ margin-top: 12px;
+
flex-direction: column;
- align-items: center;
- gap: 10px;
+
+ gap: 6px;
}
- .filters label {
- width: 100%;
+ .appliedMessage {
+ position: static;
+ transform: none;
}
-}
\ No newline at end of file
+}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/Financials/LossTrackingLineCharts/LossTrackingLineChart.jsx b/src/components/BMDashboard/WeeklyProjectSummary/Financials/LossTrackingLineCharts/LossTrackingLineChart.jsx
index 1ff6ef8a97c..0e038b0f58f 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/Financials/LossTrackingLineCharts/LossTrackingLineChart.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/Financials/LossTrackingLineCharts/LossTrackingLineChart.jsx
@@ -1,4 +1,3 @@
-// LossTrackingLineChart.jsx
import { useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import {
@@ -24,6 +23,32 @@ const colors = {
'2024-Glass': '#ff8c00',
};
+// Custom tooltip: line colors shown as swatches, text in the theme's text color so every
+// row is readable in light and dark mode; rows sorted from highest to lowest loss.
+function LossTooltip({ active, payload, label }) {
+ if (!active || !payload?.length) return null;
+ const rows = payload
+ .filter(item => Number.isFinite(item.value))
+ .sort((a, b) => b.value - a.value);
+
+ return (
+
+
{label}
+ {rows.map(item => (
+
+
+ {item.name}
+ {item.value}%
+
+ ))}
+
+ );
+}
+
const rawData = [
{
year: 2022,
@@ -140,16 +165,13 @@ export default function LossTrackingLineChart() {
const darkMode = useSelector(state => state.theme.darkMode);
const hostRef = useRef(null);
- // --- Promote parent wrapper (no parent code changes needed) ---
useEffect(() => {
const host = hostRef.current;
if (!host) return;
- // Find the wrapper the page uses around our component
const wrapper = host.closest('.weekly-project-summary-card.financial-big');
if (!wrapper) return;
- // Save previous inline styles to restore on unmount
const prev = {
gridColumn: wrapper.style.gridColumn,
flex: wrapper.style.flex,
@@ -158,7 +180,6 @@ export default function LossTrackingLineChart() {
minWidth: wrapper.style.minWidth,
};
- // Make the wrapper span the full grid row & fill flex rows
wrapper.style.gridColumn = '1 / -1';
wrapper.style.flex = '1 1 100%';
wrapper.style.width = '100%';
@@ -192,26 +213,63 @@ export default function LossTrackingLineChart() {
);
const filteredLines = useMemo(() => {
+ const filterStart = startDate ? startDate.substring(0, 7) : '';
+ const filterEnd = endDate ? endDate.substring(0, 7) : '';
+
return rawData.filter(line => {
- const yearMatch = year === 'All' || String(line.year) === year;
const materialMatch = material === 'All' || line.material === material;
+ let yearMatch = year === 'All' || String(line.year) === year;
+
+ // Filter lines dynamically if they sit entirely outside the selected calendar period
+ if (filterStart && `${line.year}-12` < filterStart) yearMatch = false; // Checks through end of year
+ if (filterEnd && `${line.year}-01` > filterEnd) yearMatch = false; // Checks from start of year
+
return yearMatch && materialMatch;
});
- }, [material, year]);
+ }, [material, year, startDate, endDate]);
+
+ // Enforces valid boundary limitations on HTML pickers dynamically
+ const dateBounds = useMemo(() => {
+ if (year === 'All') return { min: '2022-01-01', max: '2024-12-31' };
+ return { min: `${year}-01-01`, max: `${year}-12-31` };
+ }, [year]);
+
+ // Clears date pickers gracefully if they don't belong to the newly active year selection
+ const handleYearChange = newYear => {
+ setYear(newYear);
+ if (newYear !== 'All') {
+ if (startDate && !startDate.startsWith(newYear)) setStartDate('');
+ if (endDate && !endDate.startsWith(newYear)) setEndDate('');
+ }
+ };
+ // Groups and explicitly sorts keys chronologically to handle continuous year-over-year transitions smoothly
const chartData = useMemo(() => {
const merged = {};
+ const filterStart = startDate ? startDate.substring(0, 7) : '';
+ const filterEnd = endDate ? endDate.substring(0, 7) : '';
+
filteredLines.forEach(line => {
line.data.forEach(({ date, month, value }) => {
- const withinRange = (!startDate || date >= startDate) && (!endDate || date <= endDate);
+ const withinRange =
+ (!filterStart || date >= filterStart) && (!filterEnd || date <= filterEnd);
+
if (withinRange) {
- if (!merged[month]) merged[month] = { month };
- merged[month][`${line.year}-${line.material}`] = value;
+ if (!merged[date]) {
+ merged[date] = {
+ date,
+ displayLabel: year === 'All' ? `${month} ${String(line.year).substring(2)}` : month,
+ };
+ }
+ merged[date][`${line.year}-${line.material}`] = value;
}
});
});
- return Object.values(merged);
- }, [filteredLines, startDate, endDate]);
+
+ return Object.keys(merged)
+ .sort((a, b) => a.localeCompare(b))
+ .map(key => merged[key]);
+ }, [filteredLines, startDate, endDate, year]);
const isDefaultFilters =
material === DEFAULTS.material &&
@@ -255,10 +313,9 @@ export default function LossTrackingLineChart() {
))}
-
Year
- setYear(e.target.value)}>
+ handleYearChange(e.target.value)}>
{years.map(y => (
{y}
@@ -266,45 +323,35 @@ export default function LossTrackingLineChart() {
))}
-
Start Date
-
End Date
-
Reset Filters
-
{!isDateRangeValid && (
Start date must be before or equal to end date.
@@ -317,54 +364,53 @@ export default function LossTrackingLineChart() {
No data available for the selected filters.
) : (
<>
-
-
-
-
-
-
- {filteredLines.map(line => (
-
+
+
+
+
- ))}
-
-
+
+ }
+ cursor={{ stroke: gridColor, strokeWidth: 2 }}
+ />
+ {filteredLines.map(line => (
+
+ ))}
+
+
+
{legendItems.map(item => (
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/Financials/LossTrackingLineCharts/LossTrackingLineChart.module.css b/src/components/BMDashboard/WeeklyProjectSummary/Financials/LossTrackingLineCharts/LossTrackingLineChart.module.css
index f66d850d0a6..2a24026938e 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/Financials/LossTrackingLineCharts/LossTrackingLineChart.module.css
+++ b/src/components/BMDashboard/WeeklyProjectSummary/Financials/LossTrackingLineCharts/LossTrackingLineChart.module.css
@@ -1,4 +1,4 @@
-/* ---------- Theme tokens ---------- */
+/* stylelint-disable */
:root {
--bg-color: #fff;
--text-color: #1f1f1f;
@@ -20,16 +20,16 @@
--radius-sm: 8px;
--space: 1rem;
--shadow: 0 2px 10px rgb(0 0 0 / 8%);
+ --error-color: #dc3545;
}
-/* Optional manual dark mode (toggle this class on our container) */
.darkMode {
--bg-color: #0f1522;
--text-color: #f1f3f7;
--muted-text: #c9ced8;
- --border-color: #2c3a52;
- --tooltip-bg: #1a2336;
- --surface: #182235;
+ --border-color: #555;
+ --tooltip-bg: #1b2a41;
+ --surface: #1b2a41;
--surface-muted: #111a2b;
--accent-color: #7aa2ff;
--accent-hover: #9bb9ff;
@@ -40,9 +40,9 @@
--reset-btn-bg: #ff6b5e;
--reset-btn-hover: #ff4a3a;
--reset-btn-text: #0f1522;
+ --error-color: #ff6b6b;
}
-/* ---------- Host & card container ---------- */
.host {
width: 100%;
max-width: 100%;
@@ -74,14 +74,11 @@
letter-spacing: 0.1px;
}
-/* ---------- Filters (grid: one lane when space allows) ---------- */
.lossTrackingChartFilters {
display: grid;
-
- /* Each item gets at least 220px; if space allows, they all sit on one row */
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 0.85rem var(--space);
- align-items: end; /* align baselines for inputs and button */
+ align-items: end;
background-color: var(--surface-muted);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
@@ -95,12 +92,11 @@
font-size: 0.95rem;
color: var(--text-color);
gap: 0.35rem;
- min-width: 0; /* allow shrinking to column width */
+ min-width: 0;
}
-/* Inputs */
.lossTrackingChartFilters select,
-.lossTrackingChartFilters input[type="month"] {
+.lossTrackingChartFilters [type="date"] {
padding: 0.55rem 0.7rem;
border: 1px solid var(--border-color);
border-radius: 8px;
@@ -122,16 +118,21 @@
padding-right: 2.45rem !important;
}
-.lossTrackingChartFilters input[type="month"] {
+.lossTrackingChartFilters [type="date"] {
color-scheme: light;
}
-.darkMode .lossTrackingChartFilters input[type="month"] {
+.darkMode .lossTrackingChartFilters [type="date"] {
color-scheme: dark;
}
-.lossTrackingChartFilters input[type="month"]::-webkit-calendar-picker-indicator {
+.lossTrackingChartFilters input[type="date"]::-webkit-calendar-picker-indicator {
opacity: 0;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
cursor: pointer;
}
@@ -156,13 +157,12 @@
}
.lossTrackingChartFilters select:focus-visible,
-.lossTrackingChartFilters input[type="month"]:focus-visible {
+.lossTrackingChartFilters [type="date"]:focus-visible {
outline: none;
border-color: var(--ring);
box-shadow: 0 0 0 2px var(--ring), 0 0 0 4px var(--ring-offset);
}
-/* Reset button — behaves like any grid item so it aligns in the same lane */
.resetBtn {
padding: 0.6rem 1rem;
font-size: 0.95rem;
@@ -176,13 +176,16 @@
letter-spacing: 0.2px;
transition: background-color 0.15s ease, transform 0.02s ease, box-shadow 0.15s ease, color 0.15s ease;
box-shadow: 0 1px 0 rgb(0 0 0 / 4%), 0 2px 8px rgb(0 0 0 / 8%);
+ place-self: end start;
+}
- /* IMPORTANT: no margin-left/auto and no order hacks */
- place-self: end start; /* align start like labels */ /* baseline alignment with inputs */
+.resetBtn:hover {
+ background-color: var(--reset-btn-hover);
}
-.resetBtn:hover { background-color: var(--reset-btn-hover); }
-.resetBtn:active { transform: translateY(1px); }
+.resetBtn:active {
+ transform: translateY(1px);
+}
.resetBtn:focus-visible {
outline: none;
@@ -195,81 +198,54 @@
box-shadow: none;
}
-/* Date range validation error — spans full grid width */
-.dateRangeError {
+div.lossTrackingChartContainer span.dateRangeError {
grid-column: 1 / -1;
- color: #e53935;
- font-size: 0.82rem;
- font-weight: 500;
+ color: var(--error-color) !important;
+ font-size: 0.85rem;
+ font-weight: 600;
padding: 0.15rem 0.1rem;
+ display: block;
+ margin-top: 10px;
}
-/* Optional: on very narrow screens, make the button full width to match stacked inputs */
-@media (width <= 768px) {
- .resetBtn { justify-self: stretch; }
-
- .customLegend {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
-}
-
-/* ---------- Chart area (responsive height) ---------- */
.chartWrapper {
+ display: flex;
+ flex-direction: column;
width: 100%;
- /* Responsive, predictable height for ResponsiveContainer (height="100%") */
+ /* Height of the plot area only; the wrapper grows to fit the legend below it */
--chart-height: clamp(320px, 42vh, 680px);
- height: var(--chart-height);
background-color: var(--surface-muted);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
padding: 1rem;
- min-width: 0; /* let SVG measure correctly in flex/grid */
-}
-
-@media screen and (width >= 1440px) {
- .chartWrapper { --chart-height: clamp(340px, 40vh, 720px); }
-}
-
-/* Recharts tweaks */
-.recharts-cartesian-grid line { stroke: var(--grid-color); }
-
-.customLegend {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 0.55rem 1.1rem;
- margin-top: 1rem;
- color: var(--text-color);
- font-size: 0.95rem;
- align-items: start;
+ min-width: 0;
}
-.legendItem {
- display: inline-flex;
- align-items: center;
- gap: 0.45rem;
+/* Fixed-height box for ResponsiveContainer (height="100%") */
+.chartArea {
+ height: var(--chart-height);
min-width: 0;
- justify-self: start;
+ width: 100%;
}
-.legendSwatch {
- width: 10px;
- height: 10px;
- border-radius: 999px;
- flex: 0 0 auto;
+@media (width >=1440px) {
+ .chartWrapper {
+ --chart-height: clamp(340px, 40vh, 720px);
+ }
}
-.legendItem > span:last-child {
- white-space: nowrap;
+.recharts-cartesian-grid line {
+ stroke: var(--grid-color);
}
-/* ---------- No data state ---------- */
.noDataMessage {
display: flex;
justify-content: center;
align-items: center;
- height: 100%;
+ height: var(--chart-height);
+ box-sizing: border-box;
font-size: 1rem;
font-weight: 600;
color: var(--muted-text);
@@ -281,30 +257,99 @@
transition: background-color 0.25s ease, color 0.25s ease, border-color 0.25s ease;
}
-/* --- Ultra-narrow phones: <= 400px --- */
-@media (width <= 400px) {
+.customLegend {
+ display: flex;
+ flex-wrap: wrap !important;
+ justify-content: center;
+ align-items: center;
+ gap: 15px;
+ margin-top: 20px;
+ width: 100%;
+}
+
+.legendItem {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ white-space: nowrap;
+ font-size: 14px;
+}
+
+.legendSwatch {
+ width: 10px;
+ height: 10px;
+ border-radius: 999px;
+ flex: 0 0 auto;
+}
+
+@media (width <=768px) {
+ .resetBtn {
+ justify-self: stretch;
+ }
+}
+
+@media (width <=400px) {
.lossTrackingChartFilters {
- grid-template-columns: 1fr; /* force single column */
- gap: 0.6rem; /* slightly tighter spacing */
- padding: 0.75rem; /* reduce inner padding */
+ grid-template-columns: 1fr;
+ gap: 0.6rem;
+ padding: 0.75rem;
}
.lossTrackingChartFilters label {
- min-width: 0; /* allow shrink */
+ min-width: 0;
}
.lossTrackingChartFilters select,
- .lossTrackingChartFilters input[type="month"],
+ .lossTrackingChartFilters [type="date"],
.resetBtn {
- width: 100%; /* full width controls */
- box-sizing: border-box; /* respect padding/border */
+ width: 100%;
+ box-sizing: border-box;
}
+}
- .resetBtn {
- justify-self: stretch; /* no overflow due to intrinsic width */
- }
+/* ---------- Tooltip ---------- */
+.tooltip {
+ min-width: 190px;
+ padding: 0.6rem 0.75rem;
+ background-color: var(--tooltip-bg);
+ color: var(--text-color);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-sm);
+ box-shadow: 0 6px 18px rgb(0 0 0 / 18%);
+ font-size: 0.85rem;
+ line-height: 1.2;
+}
- .customLegend {
- grid-template-columns: 1fr;
- }
+.tooltipTitle {
+ margin-bottom: 0.45rem;
+ padding-bottom: 0.35rem;
+ border-bottom: 1px solid var(--border-color);
+ font-weight: 700;
+}
+
+.tooltipRow {
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.15rem 0;
+}
+
+.tooltipSwatch {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+
+ /* Outline keeps pale colors (e.g. yellow) visible on light backgrounds */
+ box-shadow: 0 0 0 1px rgb(0 0 0 / 15%);
+}
+
+.tooltipLabel {
+ white-space: nowrap;
+}
+
+.tooltipValue {
+ font-weight: 700;
+ font-variant-numeric: tabular-nums;
+ text-align: right;
}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/GroupedBarGraphInjurySeverity/InjuryCategoryBarChart.jsx b/src/components/BMDashboard/WeeklyProjectSummary/GroupedBarGraphInjurySeverity/InjuryCategoryBarChart.jsx
index a18f90ce622..7e5dd5db482 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/GroupedBarGraphInjurySeverity/InjuryCategoryBarChart.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/GroupedBarGraphInjurySeverity/InjuryCategoryBarChart.jsx
@@ -162,13 +162,19 @@ function InjuryCategoryBarChart() {
}, [data]);
const allSeriesProjectIds = useMemo(() => {
const set = new Set(projects.map(p => String(p._id)));
+ // Include projects that have bars but are missing from the project list, so the legend matches the chart
+ for (const pid of seriesProjectIds) set.add(pid);
return Array.from(set);
- }, [projects]);
+ }, [projects, seriesProjectIds]);
const colorProjectIds = useMemo(() => {
const source = unfilteredProjects.length ? unfilteredProjects : projects;
const set = new Set(source.map(project => String(project._id)));
+ // Every project shown (current list or bars) needs a palette color; otherwise it renders black,
+ // which is invisible on the dark-mode chart background
+ for (const p of projects) set.add(String(p._id));
+ for (const pid of seriesProjectIds) set.add(pid);
return Array.from(set);
- }, [unfilteredProjects, projects]);
+ }, [unfilteredProjects, projects, seriesProjectIds]);
const showLabels = seriesProjectIds.length <= 4;
@@ -215,44 +221,70 @@ function InjuryCategoryBarChart() {
endDate,
]);
- 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)',
+ // Theme-aware react-select styles for both modes. Styling lives here instead of global
+ // CSS overrides so it cannot leak into other dropdowns in the app.
+ const selectStyles = useMemo(() => {
+ const textColor = darkMode ? '#ffffff' : '#111827';
+ const mutedColor = darkMode ? '#cbd5e1' : '#6b7280';
+ const surfaceColor = darkMode ? '#2b3e59' : '#ffffff';
+ const borderColor = darkMode ? '#6b7280' : '#d1d5db';
+
+ return {
+ control: (base, state) => ({
+ ...base,
+ backgroundColor: surfaceColor,
+ borderColor: state.isFocused ? '#3b82f6' : borderColor,
+ boxShadow: state.isFocused ? '0 0 0 1px #3b82f6' : 'none',
+ '&:hover': { borderColor: '#3b82f6' },
+ }),
+ placeholder: base => ({ ...base, color: mutedColor }),
+ // Typed search text stays readable in dark mode (was black on dark before)
+ input: base => ({ ...base, color: textColor }),
+ singleValue: base => ({ ...base, color: textColor }),
+ multiValue: base => ({ ...base, backgroundColor: darkMode ? '#375071' : '#e5e7eb' }),
+ multiValueLabel: base => ({ ...base, color: textColor }),
+ multiValueRemove: base => ({
+ ...base,
+ color: textColor,
+ ':hover': { backgroundColor: '#0d55b3', color: '#ffffff' },
+ }),
+ indicatorSeparator: base => ({ ...base, backgroundColor: borderColor }),
+ dropdownIndicator: base => ({ ...base, color: mutedColor, ':hover': { color: textColor } }),
+ clearIndicator: base => ({ ...base, color: mutedColor, ':hover': { color: textColor } }),
+ menu: base => ({ ...base, backgroundColor: surfaceColor, zIndex: 5 }),
+ option: (base, state) => {
+ let backgroundColor = surfaceColor;
+ if (state.isFocused) backgroundColor = darkMode ? '#334155' : '#e2e8f0';
+ if (state.isSelected) backgroundColor = darkMode ? '#1d4ed8' : '#dbeafe';
+ return {
+ ...base,
+ backgroundColor,
+ color: textColor,
+ ':active': { backgroundColor: darkMode ? '#0d55b3' : '#bfdbfe' },
+ };
},
- }),
- singleValue: base => ({
- ...base,
- color: 'white',
- }),
- };
- // : {};
+ noOptionsMessage: base => ({ ...base, color: mutedColor }),
+ };
+ }, [darkMode]);
- return (
-
-
-
Injury Severity by Category of Worker Injured
+ const containerClassName = `${styles.injuryChartContainer} ${
+ darkMode ? styles.injuryChartDark : ''
+ }`;
+ // Applied to the calendar popup itself so dark styles don't depend on global classes
+ const calendarClassName = darkMode ? styles.injuryDarkCalendar : undefined;
-
-
-
+ return (
+
+
+
Injury Severity by Category of Worker Injured
+
+
+
+
Projects
-
-
+
+
Severities
-
-
+
+
Injury types
-
-
+
+ {/* Unique id: "start-date" is also used by Most Frequent Keywords on the same page */}
+
Start date
-
- {loading && Loading…
}
- {!loading && error && Error: {String(error)}
}
+ {loading && Loading…
}
+ {!loading && error && Error: {String(error)}
}
{!loading && !error && chartData.length > 0 && (
@@ -429,7 +482,8 @@ function InjuryCategoryBarChart() {
dataKey={pid}
position="top"
formatter={v => (v > 0 ? v : '')}
- // fill={darkMode ? '#fff' : '#000'}
+ // Readable value labels in both themes (were dim gray in dark mode)
+ fill={darkMode ? '#f3f4f6' : '#374151'}
/>
)}
@@ -440,7 +494,7 @@ function InjuryCategoryBarChart() {
)}
{!loading && !error && chartData.length === 0 && (
-
No data for selected filters.
+
No data for selected filters.
)}
);
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/GroupedBarGraphInjurySeverity/InjuryCategoryBarChart.module.css b/src/components/BMDashboard/WeeklyProjectSummary/GroupedBarGraphInjurySeverity/InjuryCategoryBarChart.module.css
index 825c2a494cb..8abc0012f03 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/GroupedBarGraphInjurySeverity/InjuryCategoryBarChart.module.css
+++ b/src/components/BMDashboard/WeeklyProjectSummary/GroupedBarGraphInjurySeverity/InjuryCategoryBarChart.module.css
@@ -1,17 +1,8 @@
-:global(.react-datepicker__navigation .react-datepicker__navigation-icon) {
- width: 9px !important;
-}
-
-:global(.react-datepicker__navigation .react-datepicker__navigation-icon::before) {
- border-color: #555 !important;
- border-width: 2px 2px 0 0 !important;
- width: 9px !important;
- height: 9px !important;
-}
+/* Injury Severity by Category of Worker Injured.
+ All classes are module-scoped so nothing here can leak into other components. */
-
-/* ===== LAYOUT FIX ===== */
-:global(.injury-chart-container) {
+/* ===== Card layout ===== */
+.injuryChartContainer {
width: 100%;
height: 100%;
min-height: 0;
@@ -24,400 +15,189 @@
box-sizing: border-box;
}
-:global(.darkMode.injury-chart-container) {
+.injuryChartDark {
border-color: #4a5a77;
}
-:global(.injury-chart-header) {
+.injuryChartHeader {
width: 100%;
}
-:global(.injury-chart-title) {
+.injuryChartTitle {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 0.75rem;
color: #333;
+ text-align: center;
}
-:global(.darkMode .injury-chart-title) {
+.injuryChartDark .injuryChartTitle {
color: #fff;
}
-:global(.injury-chart-filters) {
- display: flex;
- flex-wrap: wrap;
- gap: 1rem;
+/* ===== Filters: even 2-column grid: Projects | Severities, Injury types (full row), Start | End ===== */
+.injuryChartFilters {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 0.75rem 1rem;
width: 100%;
- align-items: flex-start;
+ align-items: end;
}
-:global(.filter.injury-filter) {
+.injuryFilter {
display: flex;
flex-direction: column;
- min-width: 160px;
- flex: 1 1 160px;
- max-width: 220px;
-}
-
-.chartArea {
- /* Fills the card space left after filters while keeping Recharts responsive. */
- flex: 1 1 auto;
- width: 100%;
- min-height: clamp(560px, 62vh, 760px);
+ gap: 0.375rem;
+ min-width: 0;
}
-
-
-/* stylelint-disable */
-/* ---------- FINAL FIX: SELECTED VALUE BACKGROUND IN DARK MODE ---------- */
-:global(.darkMode .injurySelect__value-container) {
- background-color: #2b3e59 !important;
+.injuryFilter:nth-child(3) {
+ grid-column: 1 / -1;
}
-:global(.darkMode .injurySelect__value-container--has-value) {
- background-color: #2b3e59 !important;
-}
-
-:global(.darkMode .injurySelect__single-value) {
- color: #fff !important;
- opacity: 1 !important;
-}
-
-/* FORCE FILTER LABEL VISIBILITY IN DARK MODE (PROJECTS, SEVERITIES, START/END DATE) */
-:global(.darkMode .filter label) {
- color: #fff !important;
+.injuryFilterLabel {
+ margin: 0;
+ font-weight: 600;
+ text-align: center;
+ color: #333;
}
-/* Explicit fix for Start Date / End Date labels rendered by DatePicker */
-:global(.darkMode label[for="start-date"]),
-:global(.darkMode label[for="end-date"]) {
- color: #fff !important;
+.injuryChartDark .injuryFilterLabel {
+ color: #f3f4f6;
}
-:global(.darkMode .react-datepicker__input-container input) {
- background-color: #2b3e59 !important;
- border: 1px solid grey;
- border-radius: 2px;
- color: #fff !important;
+/* ===== Date inputs: full column width, same height as the dropdowns ===== */
+.injuryDateWrapper {
+ width: 100%;
}
-/* ================= LABEL VISIBILITY FIX ================= */
-:global(.darkMode .injury-filter label) {
- color: #f3f4f6; /* greyish white, NOT pure white */
- margin-top: 4px;
+.injuryDateWrapper :global(.react-datepicker__input-container) {
display: block;
-}
-
-/* ================= DATE INPUT DARK MODE FIX ================= */
-
-/* :global(.darkMode .injury-date-input) {
- background-color: #2b3e59 !important;
- color: #f3f4f6 !important;
-}
-
-:global(.darkMode .injury-date-input::placeholder) {
- color: #d1d5db !important;
-}
-
-:global(.darkMode .react-datepicker__input-container input) {
- background-color: #2b3e59 !important;
- color: #f3f4f6 !important;
-} */
-
-/* ---- HOTFIX: react-select classNamePrefix is `injury-select` (with a hyphen), so we must target those global classes ---- */
-
-:global(.darkMode .injury-select__control) {
- background-color: #2b3e59 !important;
- border-color: #6b7280 !important;
-}
-
-:global(.darkMode .injury-select__control *),
-:global(.darkMode .injury-select__single-value),
-:global(.darkMode .injury-select__input-container),
-:global(.darkMode .injury-select__input),
-:global(.darkMode .injury-select__multi-value__label),
-:global(.darkMode .injury-select__placeholder) {
- color: #fff !important;
- opacity: 1 !important;
-}
-
-/* Ensure selected value is clearly visible in the control */
-:global(.darkMode .injury-select__value-container),
-:global(.darkMode .injury-select__value-container--has-value) {
- color: #fff !important;
-}
-
-/* Multi-select chip background + text */
-:global(.darkMode .injury-select__multi-value) {
- background-color: rgb(255 255 255 / 12%) !important;
-}
-
-:global(.darkMode .injury-select__multi-value__remove) {
- color: #fff !important;
-}
-
-/* Menu options */
-:global(.darkMode .injury-select__menu),
-:global(.darkMode .injury-select__menu-list) {
- background-color: #2b3e59 !important;
-}
-
-:global(.darkMode .injury-select__option) {
- background-color: #2b3e59 !important;
- color: #f3f4f6 !important;
-}
-
-:global(.darkMode .injury-select__option--is-focused) {
- background-color: #334155 !important;
- color: #fff !important;
-}
-
-:global(.darkMode .injury-select__option--is-selected) {
- background-color: #1d4ed8 !important;
- color: #fff !important;
-}
-
-
-/* DatePicker input: keep same dark background + white text */
-
-/* Selected value text → BLACK in dark mode */
-:global(.darkMode .injury-select__single-value),
-:global(.darkMode .injury-select__multi-value__label) {
- color: #000 !important;
- opacity: 1 !important;
-}
-
-/* Ensure filter blocks always stack label + input vertically */
-:global(.filter) {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
-}
-
-.injury-chart-label {
- color: #444;
- font-size: 0.9rem;
- margin-bottom: 0.25rem;
- font-weight: 500;
-}
-/* Consistent spacing between label and input */
-:global(.filter label) {
- margin-bottom: 6px;
-}
-
-/* Ensure date inputs behave same as dropdowns */
-:global(.filter .react-datepicker-wrapper) {
width: 100%;
}
-:global(.filter .react-datepicker__input-container input) {
- width: 180px;
- max-width: 180px;
- cursor: pointer;
-}
-
-:global(.filter .injurySelect__control) {
+.injuryDateInput {
width: 100%;
+ min-height: 38px;
+ box-sizing: border-box;
+ padding: 0.375rem 0.75rem;
+ border: 1px solid #d1d5db;
+ border-radius: 4px;
+ background-color: #fff;
+ color: #111827;
+ cursor: pointer;
}
-
-
-/* Ensure input text while typing is also black */
-:global(.darkMode .injurySelect__input-container),
-:global(.darkMode .injurySelect__input) {
- color: #000 !important;
-}
-
-.darkMode .injury-chart-title {
- color: #ffffff;
-}
-
-.darkMode .injury-chart-label {
- color: #e0e0e0 !important;
-}
-
-.darkMode .injury-chart-filters {
- background-color: #142237;
- color: black;
- border-radius: 8px;
- padding: 1rem;
-}
-
-/* React-Select dark mode styles */
-.darkMode :global(.injury-select__control) {
- background-color: #2b3e59 !important;
- border-color: #555 !important;
- color: #ffffff !important;
-}
-
-.darkMode :global(.injury-select__control:hover) {
- border-color: #777 !important;
-}
-
-.darkMode :global(.injury-select__control--is-focused) {
- border-color: #0d55b3 !important;
- box-shadow: 0 0 0 1px #0d55b3 !important;
-}
-
-.darkMode :global(.injury-select__single-value),
-.darkMode :global(.injury-select__multi-value__label),
-.darkMode :global(.injury-select__input-container) {
- color: #ffffff !important;
-}
-
-.darkMode :global(.injury-select__input-container input) {
- color: #ffffff !important;
-}
-
-.darkMode :global(.injury-select__placeholder) {
- color: #a0a0a0 !important;
-}
-
-.darkMode :global(.injury-select__menu) {
- background-color: #2b3e59 !important;
- border-color: #555 !important;
-}
-
-.darkMode :global(.injury-select__option) {
- background-color: #2b3e59 !important;
- color: #ffffff !important;
-}
-
-.darkMode :global(.injury-select__option:hover),
-.darkMode :global(.injury-select__option--is-focused) {
- background-color: #0d55b3 !important;
- color: #ffffff !important;
-}
-
-.darkMode :global(.injury-select__option--is-selected) {
- background-color: #375071 !important;
- color: #ffffff !important;
-}
-
-.darkMode :global(.injury-select__multi-value) {
- background-color: #375071 !important;
-}
-
-.darkMode :global(.injury-select__multi-value__remove) {
- color: #ffffff !important;
-}
-
-.darkMode :global(.injury-select__multi-value__remove:hover) {
- background-color: #0d55b3 !important;
- color: #ffffff !important;
-}
-
-.darkMode :global(.injury-select__indicator-separator) {
- background-color: #555 !important;
+.injuryDateInput::placeholder {
+ color: #6b7280;
}
-.darkMode :global(.injury-select__dropdown-indicator),
-.darkMode :global(.injury-select__clear-indicator) {
- color: #ffffff !important;
+.injuryDateInput:hover,
+.injuryDateInput:focus {
+ border-color: #3b82f6;
}
-.darkMode :global(.injury-select__dropdown-indicator:hover),
-.darkMode :global(.injury-select__clear-indicator:hover) {
- color: #0d55b3 !important;
+.injuryDateInput:focus {
+ outline: none;
+ box-shadow: 0 0 0 1px #3b82f6;
}
-/* React-DatePicker dark mode styles */
-.darkMode :global(.react-datepicker__input-container input) {
+/* !important: a global input style elsewhere in the app forces a white background */
+.injuryChartDark .injuryDateInput {
background-color: #2b3e59 !important;
- color: #ffffff !important;
- border: 1px solid #555 !important;
- padding: 0.5rem !important;
- border-radius: 4px !important;
+ border-color: #6b7280 !important;
+ color: #fff !important;
}
-.darkMode :global(.react-datepicker__input-container input::placeholder) {
- color: #a0a0a0 !important;
+.injuryChartDark .injuryDateInput::placeholder {
+ color: #cbd5e1;
}
-.darkMode :global(.react-datepicker__input-container input:focus) {
- border-color: #0d55b3 !important;
- outline: none !important;
- box-shadow: 0 0 0 1px #0d55b3 !important;
+/* ===== Dark calendar popup (class is set on the calendar via calendarClassName) ===== */
+.injuryDarkCalendar {
+ background-color: #3a506b;
+ border-color: #555;
+ color: #fff;
}
-:global(.darkMode .react-datepicker) {
- background-color: #3A506B !important;
- border-color: #555 !important;
- color: #ffffff !important;
+.injuryDarkCalendar :global(.react-datepicker__header) {
+ background-color: #1c2541;
+ border-bottom-color: #555;
}
-:global(.darkMode .react-datepicker__header) {
- background-color: #1C2541 !important;
- border-bottom-color: #555 !important;
+.injuryDarkCalendar :global(.react-datepicker__current-month),
+.injuryDarkCalendar :global(.react-datepicker__day-name),
+.injuryDarkCalendar :global(.react-datepicker__day) {
+ color: #fff;
}
-:global(.darkMode .react-datepicker__current-month),
-:global(.darkMode .react-datepicker__day-name),
-:global(.darkMode .react-datepicker__day),
-:global(.darkMode .react-datepicker__time-name) {
- color: #ffffff !important;
+.injuryDarkCalendar :global(.react-datepicker__day:hover),
+.injuryDarkCalendar :global(.react-datepicker__day--keyboard-selected),
+.injuryDarkCalendar :global(.react-datepicker__day--selected),
+.injuryDarkCalendar :global(.react-datepicker__day--in-range),
+.injuryDarkCalendar :global(.react-datepicker__day--in-selecting-range) {
+ background-color: #0d55b3;
+ color: #fff;
}
-:global(.darkMode .react-datepicker__day:hover),
-:global(.darkMode .react-datepicker__day--keyboard-selected) {
- background-color: #0d55b3 !important;
- color: #ffffff !important;
+.injuryDarkCalendar :global(.react-datepicker__day--outside-month) {
+ color: #cbd5e1;
}
-:global(.darkMode .react-datepicker__day--selected),
-:global(.darkMode .react-datepicker__day--in-selecting-range),
-:global(.darkMode .react-datepicker__day--in-range) {
- background-color: #0d55b3 !important;
- color: #ffffff !important;
+.injuryDarkCalendar :global(.react-datepicker__day--disabled),
+.injuryDarkCalendar :global(.react-datepicker__day--disabled:hover) {
+ background-color: transparent;
+ color: #64748b;
}
-.darkMode :global(.react-datepicker__navigation-icon::before) {
- border-color: #fff !important;
+/* Month arrows: light enough to see on the dark header */
+.injuryDarkCalendar :global(.react-datepicker__navigation-icon::before) {
+ border-color: #e5e7eb;
}
-/* Chart elements dark mode */
-.darkMode :global(.recharts-cartesian-axis-tick-value) {
- fill: #ffffff !important;
+.injuryDarkCalendar
+ :global(.react-datepicker__navigation:hover .react-datepicker__navigation-icon::before) {
+ border-color: #fff;
}
-.darkMode :global(.recharts-legend-item-text) {
- color: #ffffff !important;
+/* ===== Chart area ===== */
+.chartArea {
+ /* Fills the card space left after filters while keeping Recharts responsive. */
+ flex: 1 1 auto;
+ width: 100%;
+ min-height: clamp(560px, 62vh, 760px);
}
-/* Loading and error text in dark mode */
-.darkMode :global(p) {
- color: #ffffff !important;
+/* ===== Loading / error / empty states ===== */
+.injuryStatusText {
+ color: #374151;
+ text-align: center;
}
-.darkMode .error {
- color: #ff6b6b !important;
+.injuryChartDark .injuryStatusText {
+ color: #f3f4f6;
}
-.darkMode .empty {
- color: #a0a0a0 !important;
-}
-/* Recharts default tick text selector (X axis) */
-:global(.darkMode .recharts-xAxis .recharts-cartesian-axis-tick-value) {
- fill: #fff !important;
-}
-:global(.darkMode .injurySelect__multi-value) {
- background-color: #375071 !important;
+.injuryErrorText {
+ color: #dc2626;
+ text-align: center;
}
-:global(.darkMode .injurySelect__multi-value__label) {
- color: #ffffff !important;
+.injuryChartDark .injuryErrorText {
+ color: #ff6b6b;
}
-:global(.darkMode .injurySelect__multi-value__remove) {
- color: #ffffff !important;
- background-color: transparent !important;
+.injuryEmptyText {
+ color: #6b7280;
+ text-align: center;
+ padding: 1rem 0;
}
-:global(.darkMode .injurySelect__multi-value__remove:hover) {
- background-color: #0d55b3 !important;
- color: #ffffff !important;
+.injuryChartDark .injuryEmptyText {
+ color: #cbd5e1;
}
+/* ===== Tooltip rows keep their project color ===== */
.tooltipSeriesRow {
color: var(--tooltip-series-color) !important;
margin: 8px 0 0;
@@ -427,10 +207,17 @@
color: inherit !important;
}
-:global(.darkMode) .tooltipSeriesRow {
+/* Higher specificity so a global dark-mode text color can't turn the rows white */
+.injuryChartDark .tooltipSeriesRow {
color: var(--tooltip-series-color) !important;
}
-:global(.darkMode) .tooltipSeriesRow * {
+.injuryChartDark .tooltipSeriesRow * {
color: inherit !important;
}
+
+@media (width <= 480px) {
+ .injuryChartFilters {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/MaterialConsumption/MaterialConsumption.jsx b/src/components/BMDashboard/WeeklyProjectSummary/MaterialConsumption/MaterialConsumption.jsx
index 2673265be02..a3c4a49a279 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/MaterialConsumption/MaterialConsumption.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/MaterialConsumption/MaterialConsumption.jsx
@@ -24,13 +24,24 @@ function useMaterialConsumptionData(quantityOfMaterialsUsedData) {
}, [materials, quantityOfMaterialsUsedData, shouldUseStoreMaterials]);
}
-function renderMaterialCard(cardKey, quantityOfMaterialsUsedData) {
+function renderMaterialCard(
+ cardKey,
+ quantityOfMaterialsUsedData,
+ comparisonMode,
+ currentDateRange,
+ comparisonDateRange,
+) {
if (cardKey === 'quantity') {
return (
-
+
);
}
@@ -55,7 +66,12 @@ function renderMaterialCard(cardKey, quantityOfMaterialsUsedData) {
);
}
-export function MaterialConsumptionCards({ quantityOfMaterialsUsedData }) {
+export function MaterialConsumptionCards({
+ quantityOfMaterialsUsedData,
+ comparisonMode = 'No Comparison',
+ currentDateRange = {},
+ comparisonDateRange = {},
+}) {
const materialData = useMaterialConsumptionData(quantityOfMaterialsUsedData);
const darkMode = useSelector(state => state.theme.darkMode);
// Keep this order aligned with the PR requirement for the Material Consumption section and route.
@@ -75,7 +91,13 @@ export function MaterialConsumptionCards({ quantityOfMaterialsUsedData }) {
darkMode ? materialStyles.materialConsumptionCardDark : ''
}`}
>
- {renderMaterialCard(cardKey, materialData)}
+ {renderMaterialCard(
+ cardKey,
+ materialData,
+ comparisonMode,
+ currentDateRange,
+ comparisonDateRange,
+ )}
))}
>
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/MostFrequentKeywords/MostFrequentKeywords.jsx b/src/components/BMDashboard/WeeklyProjectSummary/MostFrequentKeywords/MostFrequentKeywords.jsx
index 2b74303f6d5..d2706cc5879 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/MostFrequentKeywords/MostFrequentKeywords.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/MostFrequentKeywords/MostFrequentKeywords.jsx
@@ -8,8 +8,6 @@ import styles from './MostFrequentKeywords.module.css';
import Select, { components as selectComponents } from 'react-select';
import PropTypes from 'prop-types';
-const calculateDistance = (x1, y1, x2, y2) => Math.hypot(x1 - x2, y1 - y2);
-
function deduplicateByTag(sorted, maxItems) {
const latestItems = [];
const usedTags = new Set();
@@ -44,6 +42,20 @@ function checkDateInRange(itemDate, startDate, endDate) {
return true;
}
+// Split a label into at most two balanced lines (multi-word labels only).
+function wrapLabel(text) {
+ const words = text.trim().split(/\s+/);
+ if (words.length === 1) return [text];
+ let best = null;
+ for (let i = 1; i < words.length; i++) {
+ const a = words.slice(0, i).join(' ');
+ const b = words.slice(i).join(' ');
+ const longest = Math.max(a.length, b.length);
+ if (!best || longest < best.longest) best = { longest, lines: [a, b] };
+ }
+ return best.lines;
+}
+
const formatCalendarMonth = date =>
date.toLocaleString('en-US', {
month: 'long',
@@ -193,6 +205,133 @@ function buildPalette(darkMode) {
};
}
+/* ------------------------------------------------------------------ */
+/* Bubble layout helpers (kept outside the component to keep */
+/* cognitive complexity of getPositions low). */
+/* ------------------------------------------------------------------ */
+
+const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
+
+const ORBIT_FACTORS = { 'extra-small': 0.22, tablet: 0.28, large: 0.3 };
+const ORBIT_MIN_GAPS = { 'extra-small': 12, small: 18, tablet: 25 };
+const COLLISION_GAPS = { 'extra-small': 10, small: 15, tablet: 18 };
+
+function computeBounds(sizes, width, height, maxBubbleRadius) {
+ return {
+ width,
+ height,
+ padding: sizes.padding,
+ horizontalPadding: sizes.padding + maxBubbleRadius + 8,
+ topPadding: sizes.padding + maxBubbleRadius * 0.75 + 12,
+ bottomPadding:
+ sizes.padding + maxBubbleRadius * 1.15 + sizes.countFontSize + sizes.centerSize * 0.5 + 28,
+ };
+}
+
+function computeOrbitRadius(sizes, bounds, centerY, maxBubbleRadius) {
+ const { width, height, horizontalPadding, topPadding, bottomPadding } = bounds;
+ const availableOrbitX = Math.max(0, width / 2 - horizontalPadding);
+ const availableOrbitY = Math.max(
+ 0,
+ Math.min(centerY - topPadding, height - bottomPadding - centerY),
+ );
+
+ const orbitFactor = ORBIT_FACTORS[sizes.deviceType] ?? 0.26;
+ const minGap = ORBIT_MIN_GAPS[sizes.deviceType] ?? 30;
+
+ const idealRadius = Math.min(
+ Math.min(width, height) * orbitFactor,
+ availableOrbitX,
+ availableOrbitY,
+ );
+ if (idealRadius <= 0) return 0;
+
+ const minRequiredRadius = sizes.centerSize + maxBubbleRadius + minGap;
+ const desired = Math.max(idealRadius, minRequiredRadius);
+ return Math.max(Math.min(desired, availableOrbitX, availableOrbitY), 0);
+}
+
+function clampToBounds(pos, bounds) {
+ pos.x = clamp(pos.x, bounds.padding + pos.r + 5, bounds.width - bounds.padding - pos.r - 5);
+ pos.y = clamp(pos.y, bounds.topPadding + 5, bounds.height - bounds.bottomPadding - 5);
+}
+
+function placeOnOrbit({ index, total, radius, r, center, centerSize, isMobile, bounds }) {
+ const angle = (index * 2 * Math.PI) / total - Math.PI / 2; // start from top
+ let x = center.x + radius * Math.cos(angle);
+ let y = center.y + radius * Math.sin(angle);
+
+ const distFromCenter = Math.hypot(x - center.x, y - center.y);
+ const minCenterDist = centerSize + r + (isMobile ? 15 : 20);
+ if (distFromCenter > 0 && distFromCenter < minCenterDist) {
+ const scale = minCenterDist / distFromCenter;
+ x = center.x + (x - center.x) * scale;
+ y = center.y + (y - center.y) * scale;
+ }
+
+ const pos = { x, y, r };
+ clampToBounds(pos, bounds);
+ return pos;
+}
+
+// Push two overlapping bubbles apart. Returns true if they collided.
+function separatePair(a, b, gap, bounds) {
+ const dx = b.x - a.x;
+ const dy = b.y - a.y;
+ const dist = Math.hypot(dx, dy);
+ const minDist = a.r + b.r + gap;
+ if (dist >= minDist) return false;
+
+ const angle = Math.atan2(dy, dx);
+ const push = (minDist - dist) / 2 + 2;
+ a.x -= Math.cos(angle) * push;
+ a.y -= Math.sin(angle) * push;
+ b.x += Math.cos(angle) * push;
+ b.y += Math.sin(angle) * push;
+ clampToBounds(a, bounds);
+ clampToBounds(b, bounds);
+ return true;
+}
+
+function separateAllPairs(positions, gap, bounds) {
+ let collided = false;
+ for (let i = 0; i < positions.length; i++) {
+ for (let j = i + 1; j < positions.length; j++) {
+ if (separatePair(positions[i], positions[j], gap, bounds)) collided = true;
+ }
+ }
+ return collided;
+}
+
+// Keep every bubble (ellipse: rx = r, ry = 0.6r) clear of the center circle.
+function pushClearOfCenter(positions, center, centerSize) {
+ let moved = false;
+ positions.forEach(p => {
+ const dx = p.x - center.x;
+ const dy = p.y - center.y;
+ const phi = Math.atan2(dy, dx);
+ const rx = p.r;
+ const ry = p.r * 0.6;
+ const extent = (rx * ry) / Math.hypot(ry * Math.cos(phi), rx * Math.sin(phi));
+ const minD = centerSize + extent + 10;
+ if (Math.hypot(dx, dy) < minD) {
+ moved = true;
+ p.x = center.x + Math.cos(phi) * minD;
+ p.y = center.y + Math.sin(phi) * minD;
+ }
+ });
+ return moved;
+}
+
+function resolveCollisions(positions, { sizes, bounds, center, maxIterations = 30 }) {
+ const gap = COLLISION_GAPS[sizes.deviceType] ?? 20;
+ for (let i = 0; i < maxIterations; i++) {
+ const pairsMoved = separateAllPairs(positions, gap, bounds);
+ const centerMoved = pushClearOfCenter(positions, center, sizes.centerSize);
+ if (!pairsMoved && !centerMoved) break;
+ }
+}
+
function applyDarkCalendarStyles() {
const poppers = Array.from(document.querySelectorAll('.react-datepicker-popper'));
const activePopper = poppers.find(popper => popper.offsetParent !== null) || poppers.at(-1);
@@ -288,31 +427,6 @@ const TEST_DATASETS = {
},
};
-function generateProjectSpecificData(projectName) {
- if (projectName.toLowerCase().includes('duplicable city center')) {
- return [
- { tag: 'Modular Design', count: 85, date: '2025-03-15' },
- { tag: 'Prefabrication', count: 78, date: '2025-04-22' },
- { tag: 'Replicable Units', count: 72, date: '2025-05-10' },
- { tag: 'Standard Parts', count: 64, date: '2025-06-18' },
- { tag: 'Urban Planning', count: 81, date: '2025-08-30' },
- { tag: 'Smart City Tech', count: 69, date: '2025-10-05' },
- { tag: 'Energy Efficiency', count: 76, date: '2026-01-19' },
- { tag: 'Mixed Use', count: 68, date: '2026-05-08' },
- ];
- }
- return [
- { tag: 'Site Planning', count: 72, date: '2024-03-15' },
- { tag: 'Foundation', count: 65, date: '2024-06-22' },
- { tag: 'Framing', count: 58, date: '2024-09-10' },
- { tag: 'Electrical', count: 62, date: '2025-01-18' },
- { tag: 'Plumbing', count: 54, date: '2025-04-25' },
- { tag: 'HVAC', count: 67, date: '2025-07-30' },
- { tag: 'Finishing', count: 59, date: '2025-11-14' },
- { tag: 'Landscaping', count: 51, date: '2026-02-05' },
- ];
-}
-
function MFKDatePickers({
darkMode,
isMobile,
@@ -419,7 +533,7 @@ function MFKChartArea({ isLoading, error, tags, selectedOption, svgRef, containe
{selectedOption ? 'No data' : 'Select source'}
)}
{!isLoading && !error && tags.length > 0 && (
-
+
)}
);
@@ -444,51 +558,6 @@ const DropdownIndicator = props => (
);
-// Pick the most recent unique-tag items, capped at maxItems.
-function getLatestData(data, isMobile) {
- if (!data || data.length === 0) return [];
-
- const sorted = [...data].sort((a, b) => new Date(b.date) - new Date(a.date));
- const maxItems = isMobile ? 6 : 8;
-
- if (sorted.length < maxItems) return sorted;
-
- const latestItems = [];
- const usedTags = new Set();
-
- for (const item of sorted) {
- if (!usedTags.has(item.tag)) {
- latestItems.push(item);
- usedTags.add(item.tag);
- if (latestItems.length >= maxItems) break;
- }
- }
-
- return latestItems;
-}
-
-// Items without a real date are always included; otherwise check the bounds.
-function isWithinDateRange(item, startDate, endDate) {
- if (!item.date) return true;
-
- const itemDate = new Date(item.date);
- itemDate.setHours(0, 0, 0, 0);
-
- if (startDate) {
- const start = new Date(startDate);
- start.setHours(0, 0, 0, 0);
- if (itemDate < start) return false;
- }
-
- if (endDate) {
- const end = new Date(endDate);
- end.setHours(23, 59, 59, 999);
- if (itemDate > end) return false;
- }
-
- return true;
-}
-
function MostFrequentKeywords({ darkMode: propDarkMode } = {}) {
const svgRef = useRef();
const containerRef = useRef();
@@ -527,7 +596,7 @@ function MostFrequentKeywords({ darkMode: propDarkMode } = {}) {
}
};
- const fetchProjectData = async (projectId, projectName) => {
+ const fetchProjectData = async projectId => {
try {
setIsLoading(true);
setError('');
@@ -595,43 +664,84 @@ function MostFrequentKeywords({ darkMode: propDarkMode } = {}) {
return () => window.removeEventListener('resize', checkMobile);
}, []);
- // Responsive sizing - optimized for perfect proportions
+ // Responsive sizing - optimized for all devices
const getResponsiveSizes = useCallback(() => {
const width = dimensions.width;
const height = dimensions.height;
const smallestDim = Math.min(width, height);
- if (isMobile) {
+ // Extra small devices (< 360px)
+ if (width < 360) {
+ return {
+ centerSize: Math.min(28, smallestDim * 0.12),
+ minBubbleSize: 22,
+ maxBubbleSize: 36,
+ maxFontSize: 9,
+ countFontSize: 7,
+ centerFontSize: 9,
+ padding: 6,
+ radiusFactor: 0.24,
+ isMobile: true,
+ deviceType: 'extra-small',
+ };
+ }
+
+ // Small devices (360px - 480px)
+ if (width <= 480) {
return {
centerSize: Math.min(35, smallestDim * 0.14),
minBubbleSize: 28,
maxBubbleSize: 42,
- maxFontSize: 11,
- countFontSize: 9,
+ maxFontSize: 10,
+ countFontSize: 8,
+ centerFontSize: 10,
padding: 8,
radiusFactor: 0.24,
isMobile: true,
+ deviceType: 'small',
+ };
+ }
+
+ // Tablet (481px - 768px)
+ if (width <= 768) {
+ return {
+ centerSize: Math.min(42, smallestDim * 0.12),
+ minBubbleSize: 32,
+ maxBubbleSize: 50,
+ maxFontSize: 12,
+ countFontSize: 10,
+ centerFontSize: 12,
+ padding: 12,
+ radiusFactor: 0.22,
+ isMobile: false,
+ deviceType: 'tablet',
};
}
+
+ // Large devices (> 768px)
return {
centerSize: Math.min(50, smallestDim * 0.1),
minBubbleSize: 35,
maxBubbleSize: 58,
maxFontSize: 14,
countFontSize: 11,
+ centerFontSize: 13,
padding: 15,
radiusFactor: 0.22,
isMobile: false,
+ deviceType: 'large',
};
}, [dimensions, isMobile]);
+ // Top keywords by count (ties broken by most recent), one entry per tag.
const getLatestData = useCallback(
data => {
if (!data || data.length === 0) return [];
- const sorted = [...data].sort((a, b) => new Date(b.date) - new Date(a.date));
const maxItems = isMobile ? 6 : 8;
- if (sorted.length >= maxItems) return deduplicateByTag(sorted, maxItems);
- return sorted;
+ const sorted = [...data].sort(
+ (a, b) => b.count - a.count || new Date(b.date) - new Date(a.date),
+ );
+ return deduplicateByTag(sorted, maxItems); // keeps the highest-count entry per tag
},
[isMobile],
);
@@ -641,7 +751,7 @@ function MostFrequentKeywords({ darkMode: propDarkMode } = {}) {
if (!tagsToFilter || tagsToFilter.length === 0) return [];
if (!startDate && !endDate) {
- return getLatestData(tagsToFilter, isMobile);
+ return getLatestData(tagsToFilter);
}
const filtered = tagsToFilter.filter(item => {
@@ -658,7 +768,7 @@ function MostFrequentKeywords({ darkMode: propDarkMode } = {}) {
return result;
},
- [startDate, endDate, isMobile],
+ [startDate, endDate, isMobile, getLatestData],
);
useEffect(() => {
@@ -690,7 +800,7 @@ function MostFrequentKeywords({ darkMode: propDarkMode } = {}) {
fetchProjects();
}, []);
- // SIMPLE: Bubble size based ONLY on frequency, not text length
+ // Base bubble size based ONLY on frequency, not text length
const getBubbleSize = useCallback(
(count, allCounts) => {
const sizes = getResponsiveSizes();
@@ -707,86 +817,79 @@ function MostFrequentKeywords({ darkMode: propDarkMode } = {}) {
[getResponsiveSizes],
);
- // SIMPLE: Text display - just truncate if too long
- const getDisplayText = useCallback((tag, maxLength) => {
- if (tag.length <= maxLength) return tag;
- return `${tag.substring(0, maxLength - 2)}…`;
- }, []);
+ // Decide lines, font size and bubble radius so the full label always fits.
+ const getLabelLayout = useCallback(
+ (tag, baseR) => {
+ const sizes = getResponsiveSizes();
+ const cw = isMobile ? 0.55 : 0.6; // avg char width / font size
+ const lines = wrapLabel(tag);
+ const longest = Math.max(...lines.map(l => l.length));
+ const countFs = sizes.countFontSize;
+
+ let fontSize = Math.max(8, Math.min(sizes.maxFontSize, 11));
+ const neededR = fs =>
+ Math.max(
+ (longest * fs * cw) / 1.7, // width: text must fit inside ~1.7r
+ ((lines.length * fs * 1.2 + countFs * 1.3) / 2 + 3) / 0.6, // height: ry = 0.6r
+ );
+
+ let r = Math.max(baseR, neededR(fontSize));
+
+ // Safety cap so one long word can't produce a giant bubble: shrink font instead.
+ const rMax = sizes.maxBubbleSize * 1.35;
+ if (r > rMax) {
+ r = rMax;
+ const byWidth = (r * 1.7) / (longest * cw);
+ const byHeight = ((0.6 * r - 3) * 2 - countFs * 1.3) / (lines.length * 1.2);
+ fontSize = Math.max(7, Math.min(fontSize, byWidth, byHeight));
+ }
+ return { lines, fontSize, r };
+ },
+ [getResponsiveSizes, isMobile],
+ );
- // Simple, reliable position calculation
+ // Position calculation with collision avoidance (logic lives in the helpers above)
const getPositions = useCallback(
(tags, width, height, centerX, centerY) => {
if (!tags.length) return [];
const sizes = getResponsiveSizes();
const counts = tags.map(t => t.count);
- const centerSize = sizes.centerSize;
-
- // Calculate bubble radii
- const radii = tags.map((_, i) => getBubbleSize(counts[i], counts));
- const maxBubbleRadius = Math.max(...radii, sizes.minBubbleSize);
- const horizontalPadding = sizes.padding + maxBubbleRadius + 8;
- const topPadding = sizes.padding + maxBubbleRadius * 0.75 + 12;
- const bottomPadding =
- sizes.padding + maxBubbleRadius * 1.15 + sizes.countFontSize + centerSize * 0.5 + 28;
-
- // Keep the orbit inside the visible area with room for labels and the center text.
- const availableOrbitX = Math.max(0, width / 2 - horizontalPadding);
- const availableOrbitY = Math.max(
- 0,
- Math.min(centerY - topPadding, height - bottomPadding - centerY),
- );
- const idealRadius = Math.min(
- Math.min(width, height) * (isMobile ? 0.2 : 0.17),
- availableOrbitX,
- availableOrbitY,
- );
- const minRequiredRadius = centerSize + maxBubbleRadius + (isMobile ? 6 : 10);
- const radius =
- idealRadius > 0
- ? Math.max(
- Math.min(Math.max(idealRadius, minRequiredRadius), availableOrbitX, availableOrbitY),
- 0,
- )
- : 0;
-
- const positions = [];
-
- for (let i = 0; i < tags.length; i++) {
- const angle = (i * 2 * Math.PI) / tags.length - Math.PI / 2; // Start from top
- const r = radii[i];
-
- let x = centerX + radius * Math.cos(angle);
- let y = centerY + radius * Math.sin(angle);
-
- // Ensure minimum distance from center
- const distFromCenter = calculateDistance(x, y, centerX, centerY);
- const minCenterDist = centerSize + r + (isMobile ? 8 : 12);
-
- if (distFromCenter < minCenterDist && distFromCenter > 0) {
- const scale = minCenterDist / distFromCenter;
- x = centerX + (x - centerX) * scale;
- y = centerY + (y - centerY) * scale;
- }
-
- // Keep within bounds
- x = Math.max(sizes.padding + r, Math.min(width - sizes.padding - r, x));
- y = Math.max(topPadding, Math.min(height - bottomPadding, y));
-
- positions.push({
- x,
- y,
- angle: Math.atan2(y - centerY, x - centerX),
- r,
- tag: tags[i].tag,
- count: tags[i].count,
- fullTag: tags[i].tag,
- });
- }
+ const layouts = tags.map((t, i) => getLabelLayout(t.tag, getBubbleSize(counts[i], counts)));
+ const maxBubbleRadius = Math.max(...layouts.map(l => l.r), sizes.minBubbleSize);
+
+ const center = { x: centerX, y: centerY };
+ const bounds = computeBounds(sizes, width, height, maxBubbleRadius);
+ const radius = computeOrbitRadius(sizes, bounds, centerY, maxBubbleRadius);
+
+ const positions = tags.map((t, i) => ({
+ ...placeOnOrbit({
+ index: i,
+ total: tags.length,
+ radius,
+ r: layouts[i].r,
+ center,
+ centerSize: sizes.centerSize,
+ isMobile,
+ bounds,
+ }),
+ tag: t.tag,
+ fullTag: t.tag,
+ count: t.count,
+ lines: layouts[i].lines,
+ fontSize: layouts[i].fontSize,
+ }));
+
+ resolveCollisions(positions, { sizes, bounds, center });
+
+ // Pushing moves bubbles, so recompute angles for the connector lines.
+ positions.forEach(p => {
+ p.angle = Math.atan2(p.y - centerY, p.x - centerX);
+ });
return positions;
},
- [getBubbleSize, getResponsiveSizes, isMobile],
+ [getBubbleSize, getResponsiveSizes, getLabelLayout, isMobile],
);
const getNodeColor = index => {
@@ -912,40 +1015,39 @@ function MostFrequentKeywords({ darkMode: propDarkMode } = {}) {
.style('pointer-events', 'none');
};
- // Function to create text elements
- const createTextElements = (svg, x, y, tag, count, r, sizes, colors) => {
+ // Function to create text elements: full label wrapped to at most two lines, never truncated
+ const createTextElements = (svg, pos, sizes, colors) => {
+ const { x, y, lines, fontSize, count } = pos;
const textGroup = svg
.append('g')
.attr('transform', `translate(${x}, ${y})`)
.style('pointer-events', 'none');
- const tagFontSize = sizes.isMobile
- ? Math.min(sizes.maxFontSize, Math.max(9, r * 0.22))
- : Math.min(sizes.maxFontSize, Math.max(10, r * 0.22));
-
- const countFontSize = sizes.countFontSize;
+ const countFs = sizes.countFontSize;
+ const lh = fontSize * 1.2;
+ const total = lines.length * lh + countFs * 1.3;
+ const top = -total / 2;
- // Tag text - positioned in upper half of bubble
- const maxTagLength = Math.floor(r / (sizes.isMobile ? 4 : 3.8));
- const displayTag = getDisplayText(tag, maxTagLength);
-
- textGroup
- .append('text')
- .attr('x', 0)
- .attr('y', -tagFontSize * 0.3)
- .attr('text-anchor', 'middle')
- .attr('font-size', tagFontSize)
- .attr('font-weight', '600')
- .attr('fill', colors.text)
- .text(displayTag);
+ lines.forEach((line, i) => {
+ textGroup
+ .append('text')
+ .attr('x', 0)
+ .attr('y', top + lh * (i + 0.5))
+ .attr('text-anchor', 'middle')
+ .attr('dominant-baseline', 'middle')
+ .attr('font-size', fontSize)
+ .attr('font-weight', '600')
+ .attr('fill', colors.text)
+ .text(line);
+ });
- // Count - positioned clearly at bottom of bubble
textGroup
.append('text')
.attr('x', 0)
- .attr('y', r * 0.4)
+ .attr('y', top + lines.length * lh + countFs * 0.65)
.attr('text-anchor', 'middle')
- .attr('font-size', countFontSize)
+ .attr('dominant-baseline', 'middle')
+ .attr('font-size', countFs)
.attr('font-weight', '500')
.attr('fill', colors.text)
.style('opacity', 0.9)
@@ -983,7 +1085,7 @@ function MostFrequentKeywords({ darkMode: propDarkMode } = {}) {
// Function to render a single bubble
const renderSingleBubble = (svg, pos, i, sizes) => {
- const { x, y, r, tag, count, fullTag } = pos;
+ const { x, y, r, count, fullTag } = pos;
const colors = getNodeColor(i);
const nodeGroup = svg
@@ -999,7 +1101,7 @@ function MostFrequentKeywords({ darkMode: propDarkMode } = {}) {
createVisibleBubble(nodeGroup, colors, r);
// Create text elements
- createTextElements(svg, x, y, tag, count, r, sizes, colors);
+ createTextElements(svg, pos, sizes, colors);
};
// Function to render all bubbles
@@ -1076,7 +1178,10 @@ function MostFrequentKeywords({ darkMode: propDarkMode } = {}) {
// Function to draw center circle
const drawCenterCircle = (svg, centerX, centerY, sizes) => {
- const centerGroup = svg.append('g').attr('transform', `translate(${centerX}, ${centerY})`);
+ const centerGroup = svg
+ .append('g')
+ .attr('transform', `translate(${centerX}, ${centerY})`)
+ .style('pointer-events', 'none');
centerGroup
.append('circle')
@@ -1091,27 +1196,19 @@ function MostFrequentKeywords({ darkMode: propDarkMode } = {}) {
: 'drop-shadow(0 2px 3px rgba(0,0,0,0.1))',
);
- const centerFontSize = sizes.isMobile ? 11 : 13;
+ // Size "Top Words" (9 chars) from the circle so it never touches the edge
+ const centerFontSize = Math.min(sizes.centerFontSize, (sizes.centerSize * 1.7) / (9 * 0.6));
centerGroup
.append('text')
.attr('x', 0)
- .attr('y', -centerFontSize * 0.25)
+ .attr('y', 0)
.attr('text-anchor', 'middle')
+ .attr('dominant-baseline', 'middle')
.attr('fill', darkMode ? '#f1f5f9' : '#1e293b')
- .attr('font-weight', '600')
+ .attr('font-weight', '700')
.attr('font-size', centerFontSize)
- .text(sizes.isMobile ? 'Top' : 'Most');
-
- centerGroup
- .append('text')
- .attr('x', 0)
- .attr('y', centerFontSize * 0.8)
- .attr('text-anchor', 'middle')
- .attr('fill', darkMode ? '#f1f5f9' : '#1e293b')
- .attr('font-weight', '600')
- .attr('font-size', centerFontSize)
- .text(sizes.isMobile ? 'Words' : 'Frequent');
+ .text('Top Words');
};
// Function to draw connection lines
@@ -1156,9 +1253,6 @@ function MostFrequentKeywords({ darkMode: propDarkMode } = {}) {
Math.min(height * (isMobile ? 0.37 : 0.35), height - (sizes.centerSize + 96)),
);
- // Draw center circle
- drawCenterCircle(svg, centerX, centerY, sizes);
-
// Get positions
const positions = getPositions(tags, width, height, centerX, centerY);
@@ -1168,6 +1262,9 @@ function MostFrequentKeywords({ darkMode: propDarkMode } = {}) {
// Draw bubbles
renderBubbles(svg, positions, sizes);
+ // Draw center circle last so nothing can ever cover it
+ drawCenterCircle(svg, centerX, centerY, sizes);
+
// Draw tooltip
renderTooltip(svg, tooltip, sizes, width, height);
}, [
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/MostFrequentKeywords/MostFrequentKeywords.module.css b/src/components/BMDashboard/WeeklyProjectSummary/MostFrequentKeywords/MostFrequentKeywords.module.css
index fd118bdb492..d5122497cc9 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/MostFrequentKeywords/MostFrequentKeywords.module.css
+++ b/src/components/BMDashboard/WeeklyProjectSummary/MostFrequentKeywords/MostFrequentKeywords.module.css
@@ -226,7 +226,7 @@
.mfkChartContainer {
width: 100%;
flex: 1;
- min-height: 340px;
+ min-height: 350px;
height: 100%;
position: relative;
border-radius: 0.5rem;
@@ -238,12 +238,72 @@
transition: all 0.3s ease;
}
+/* Extra small devices */
+@media (width < 360px) {
+ .mfkChartContainer {
+ min-height: 280px;
+ }
+}
+
+/* Small devices */
+@media (width <= 480px) {
+ .mfkChartContainer {
+ min-height: 300px;
+ }
+}
+
+/* Tablet */
+@media (width <= 768px) {
+ .mfkChartContainer {
+ min-height: 320px;
+ }
+}
+
+/* Large devices */
+@media (width > 768px) {
+ .mfkChartContainer {
+ min-height: 400px;
+ }
+}
+
.mfkChartContainer svg {
display: block;
overflow: visible;
pointer-events: none;
- transform: scale(1.18);
- transform-origin: center 38%;
+ transform: scale(1.15);
+ transform-origin: center 40%;
+}
+
+/* Adjust scaling for extra small devices */
+@media (width < 360px) {
+ .mfkChartContainer svg {
+ transform: scale(1.08);
+ transform-origin: center 45%;
+ }
+}
+
+/* Adjust scaling for small devices */
+@media (width <= 480px) {
+ .mfkChartContainer svg {
+ transform: scale(1.12);
+ transform-origin: center 42%;
+ }
+}
+
+/* Adjust scaling for tablet */
+@media (width <= 768px) {
+ .mfkChartContainer svg {
+ transform: scale(1.14);
+ transform-origin: center 40%;
+ }
+}
+
+/* Adjust scaling for large devices */
+@media (width > 768px) {
+ .mfkChartContainer svg {
+ transform: scale(1.18);
+ transform-origin: center 38%;
+ }
}
.mfkCalendarHeader {
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/PaidLaborCost/PaidLaborCost.jsx b/src/components/BMDashboard/WeeklyProjectSummary/PaidLaborCost/PaidLaborCost.jsx
index f98ad0ce38e..b06587888c6 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/PaidLaborCost/PaidLaborCost.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/PaidLaborCost/PaidLaborCost.jsx
@@ -24,6 +24,16 @@ import PropTypes from 'prop-types';
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
+// Short dollar labels ($52k, $1.2M) so they fit above narrow bars
+const formatCompactCurrency = value => {
+ const abs = Math.abs(value);
+ if (abs >= 1e6) return `$${Number((value / 1e6).toFixed(1))}M`;
+ if (abs >= 1e3) return `$${Number((value / 1e3).toFixed(1))}k`;
+ return `$${Math.round(value)}`;
+};
+
+const LABEL_GAP = 4;
+
const barValueLabelPlugin = {
id: 'barValueLabelPlugin',
afterDatasetsDraw(chart, _args, pluginOptions) {
@@ -31,34 +41,39 @@ const barValueLabelPlugin = {
if (!chartArea) return;
const { darkMode } = pluginOptions;
+ const fontSize = chart.width < 480 ? 10 : 11;
ctx.save();
- ctx.font = '600 11px Arial';
- ctx.textAlign = 'center';
+ ctx.font = `600 ${fontSize}px Arial`;
+ ctx.fillStyle = darkMode ? '#f5f5f5' : '#333333';
chart.data.datasets.forEach((dataset, datasetIndex) => {
- const meta = chart.getDatasetMeta(datasetIndex);
- if (meta.hidden) return;
+ if (!chart.isDatasetVisible(datasetIndex)) return;
- meta.data.forEach((bar, index) => {
+ chart.getDatasetMeta(datasetIndex).data.forEach((bar, index) => {
const value = dataset.data[index];
if (!value) return;
- const label = `$${(value * 1000).toLocaleString()}`;
- const { x, y, base } = bar.getProps(['x', 'y', 'base'], true);
-
- const barHeight = Math.abs(base - y);
- const isSmallBar = barHeight < 28;
-
- const insideColor = darkMode ? '#ffffff' : '#111111';
- const outsideColor = darkMode ? '#f5f5f5' : '#666666';
-
- ctx.fillStyle = isSmallBar ? outsideColor : insideColor;
-
- const textY = isSmallBar ? y - 8 : y + 8;
- ctx.textBaseline = isSmallBar ? 'bottom' : 'top';
-
- ctx.fillText(label, x, textY);
+ const label = formatCompactCurrency(value);
+ const { x, y, width } = bar.getProps(['x', 'y', 'width'], true);
+ const textWidth = ctx.measureText(label).width;
+
+ if (textWidth <= width + LABEL_GAP) {
+ // Wide bar: horizontal label centered above it
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'bottom';
+ ctx.fillText(label, x, y - LABEL_GAP);
+ } else if (fontSize <= width + 2) {
+ // Narrow bar: rotate the label upward so it never spills onto neighbouring bars
+ ctx.save();
+ ctx.translate(x, y - LABEL_GAP);
+ ctx.rotate(-Math.PI / 2);
+ ctx.textAlign = 'left';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(label, 0, 0);
+ ctx.restore();
+ }
+ // Bars too thin for even a rotated label rely on the tooltip instead
});
});
@@ -321,16 +336,23 @@ Option.propTypes = {
label: PropTypes.string.isRequired,
};
-const buildChartDatasets = (tasksToInclude, labels, aggregation, darkMode) => {
- return tasksToInclude.flatMap((task, idx) => {
- const hue = Math.round((idx * 360) / Math.max(1, tasksToInclude.length));
- const saturation = 65;
- const lightness = darkMode ? 65 : 50;
+const getTaskColors = (idx, taskCount, darkMode) => {
+ const hue = Math.round((idx * 360) / Math.max(1, taskCount));
+ const saturation = 65;
+ const lightness = darkMode ? 65 : 50;
- const actualColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
- const budgetColor = darkMode
+ return {
+ actualColor: `hsl(${hue}, ${saturation}%, ${lightness}%)`,
+ budgetColor: darkMode
? `hsla(${hue}, ${saturation}%, ${lightness + 20}%, 0.8)`
- : `hsla(${hue}, ${saturation}%, ${lightness}%, 0.4)`;
+ : `hsla(${hue}, ${saturation}%, ${lightness}%, 0.4)`,
+ };
+};
+
+const buildChartDatasets = (tasksToInclude, labels, aggregation, darkMode, hiddenTasks) => {
+ return tasksToInclude.flatMap((task, idx) => {
+ const { actualColor, budgetColor } = getTaskColors(idx, tasksToInclude.length, darkMode);
+ const hidden = hiddenTasks.includes(task);
return [
{
@@ -341,6 +363,7 @@ const buildChartDatasets = (tasksToInclude, labels, aggregation, darkMode) => {
maxBarThickness: 40,
categoryPercentage: 0.8,
barPercentage: 0.9,
+ hidden,
},
{
label: `${task} (Budget)`,
@@ -353,6 +376,7 @@ const buildChartDatasets = (tasksToInclude, labels, aggregation, darkMode) => {
maxBarThickness: 40,
categoryPercentage: 0.8,
barPercentage: 0.9,
+ hidden,
},
];
});
@@ -361,26 +385,12 @@ const buildChartDatasets = (tasksToInclude, labels, aggregation, darkMode) => {
const buildChartOptions = (textColor, darkMode) => ({
responsive: true,
maintainAspectRatio: false,
- layout: { padding: { top: 35, left: 15, right: 15, bottom: 10 } },
+ // Top padding leaves room for rotated value labels above the tallest bar
+ layout: { padding: { top: 48, left: 10, right: 10, bottom: 4 } },
plugins: {
barValueLabelPlugin: { darkMode },
- legend: {
- position: 'top',
- labels: { font: { size: 12 }, color: textColor, padding: 20, usePointStyle: true },
- },
- datalabels: {
- anchor: 'end',
- align: 'top',
- offset: 2,
- color: darkMode ? '#ffffff' : '#333333',
- font: { weight: '600', size: 11 },
- textStrokeColor: darkMode ? '#1e293b' : '#ffffff',
- textStrokeWidth: 3,
- formatter: value => {
- if (!value) return '';
- return value >= 1000 ? `$${(value / 1000).toFixed(1).replace('.0', '')}k` : `$${value}`;
- },
- },
+ // One entry per bar crowded out the plot; a per-task HTML legend is rendered below instead
+ legend: { display: false },
tooltip: {
backgroundColor: darkMode ? '#1e293b' : '#ffffff',
titleColor: darkMode ? '#f8fafc' : '#0f172a',
@@ -410,7 +420,7 @@ const buildChartOptions = (textColor, darkMode) => ({
ticks: {
font: { size: 12 },
color: textColor,
- callback: value => (value >= 1000 ? `$${value / 1000}k` : `$${value}`),
+ callback: value => formatCompactCurrency(value),
},
},
},
@@ -426,6 +436,7 @@ export default function PaidLaborCost() {
const [taskFilter, setTaskFilter] = useState([]); // Array of selected task names, empty = all tasks
const [projectFilter, setProjectFilter] = useState('All Projects');
const [dateRange, setDateRange] = useState({ startDate: null, endDate: null });
+ const [hiddenTasks, setHiddenTasks] = useState([]); // Tasks toggled off from the legend
useEffect(() => {
const abortController = new AbortController();
@@ -489,10 +500,13 @@ export default function PaidLaborCost() {
const selectStyles = useMemo(() => generateSelectStyles(darkMode), [darkMode]);
const taskDatasets = useMemo(
- () => buildChartDatasets(tasksToInclude, labels, aggregation, darkMode),
- [tasksToInclude, labels, aggregation, darkMode],
+ () => buildChartDatasets(tasksToInclude, labels, aggregation, darkMode, hiddenTasks),
+ [tasksToInclude, labels, aggregation, darkMode, hiddenTasks],
);
+ const toggleTaskVisibility = task =>
+ setHiddenTasks(prev => (prev.includes(task) ? prev.filter(t => t !== task) : [...prev, task]));
+
const chartData = { labels, datasets: taskDatasets };
const options = useMemo(() => buildChartOptions(textColor, darkMode), [textColor, darkMode]);
@@ -606,6 +620,45 @@ export default function PaidLaborCost() {
)}
+
+ {labels.length > 0 && (
+
+
+
+
+ Actual
+
+
+
+ Budget
+
+
+
+ {tasksToInclude.map((task, idx) => {
+ const { actualColor } = getTaskColors(idx, tasksToInclude.length, darkMode);
+ const isHidden = hiddenTasks.includes(task);
+ return (
+
+ toggleTaskVisibility(task)}
+ aria-pressed={!isHidden}
+ title={isHidden ? `Show ${task}` : `Hide ${task}`}
+ >
+
+ {task}
+
+
+ );
+ })}
+
+
+ )}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/PaidLaborCost/PaidLaborCost.module.css b/src/components/BMDashboard/WeeklyProjectSummary/PaidLaborCost/PaidLaborCost.module.css
index 7eef6002f6f..e6b46f81628 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/PaidLaborCost/PaidLaborCost.module.css
+++ b/src/components/BMDashboard/WeeklyProjectSummary/PaidLaborCost/PaidLaborCost.module.css
@@ -180,17 +180,156 @@
.paidLaborCostChartWrapper {
width: 100%;
- min-height: 400px;
margin-bottom: 24px;
flex-grow: 1;
}
+/* Explicit height: Chart.js (maintainAspectRatio: false) sizes the canvas to this box */
.paidLaborCostChartContainer {
- height: 100%;
+ height: clamp(320px, 45vh, 460px);
width: 100%;
position: relative;
}
+/* ---------- Legend (one entry per task; Actual vs Budget key) ---------- */
+.chartLegend {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 10px;
+ margin-top: 12px;
+ font-size: 13px;
+ color: #334155;
+}
+
+.legendKey {
+ display: flex;
+ gap: 18px;
+ font-size: 12px;
+ color: #64748b;
+}
+
+.legendKeyItem {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.legendKeySwatch {
+ width: 16px;
+ height: 11px;
+ border-radius: 3px;
+}
+
+.legendKeyActual {
+ background-color: #64748b;
+}
+
+.legendKeyBudget {
+ background-color: rgb(100 116 139 / 35%);
+ border: 2px dashed #64748b;
+ border-bottom: 0;
+}
+
+.legendTasks {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 6px 14px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ max-width: 100%;
+}
+
+.legendTask {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ max-width: 100%;
+ padding: 2px 4px;
+ border: 0;
+ border-radius: 4px;
+ background: transparent;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+}
+
+.legendTask:hover {
+ background-color: rgb(100 116 139 / 12%);
+}
+
+.legendTask:focus-visible {
+ outline: 2px solid #0d55b3;
+ outline-offset: 1px;
+}
+
+.legendTaskSwatch {
+ flex: 0 0 auto;
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+}
+
+.legendTaskLabel {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.legendTaskHidden {
+ opacity: 0.45;
+}
+
+.legendTaskHidden .legendTaskLabel {
+ text-decoration: line-through;
+}
+
+/* !important: a global dark-mode rule forces text white/inherits elsewhere */
+.darkMode .chartLegend {
+ color: #e2e8f0 !important;
+}
+
+.darkMode .legendKey {
+ color: #94a3b8 !important;
+}
+
+.darkMode .legendKeyActual {
+ background-color: #94a3b8;
+}
+
+.darkMode .legendKeyBudget {
+ background-color: rgb(148 163 184 / 35%);
+ border-color: #94a3b8;
+}
+
+.darkMode .legendTask {
+ color: #e2e8f0 !important;
+}
+
+.darkMode .legendTask:hover {
+ background-color: rgb(148 163 184 / 15%);
+}
+
+.darkMode .legendTask:focus-visible {
+ outline-color: #e8a71c;
+}
+
+@media (width <= 600px) {
+ .paidLaborCostChartContainer {
+ height: 300px;
+ }
+
+ .chartLegend {
+ font-size: 12px;
+ }
+
+ .legendTasks {
+ gap: 4px 10px;
+ }
+}
+
.emptyState {
display: flex;
justify-content: center;
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.jsx b/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.jsx
index 74d3813e8f9..84852526444 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.jsx
@@ -9,6 +9,7 @@ import {
CartesianGrid,
Tooltip,
Legend,
+ LabelList,
Line,
LineChart,
ResponsiveContainer,
@@ -18,10 +19,6 @@ import { Container } from 'reactstrap';
import httpService from '../../../services/httpService';
import styles from './ProjectRiskProfileOverview.module.css';
-function clamp(n, min, max) {
- return Math.max(min, Math.min(max, n));
-}
-
function hashString(str) {
let h = 2166136261;
for (let i = 0; i < str.length; i += 1) {
@@ -46,30 +43,31 @@ function buildDateRange(endDateISO, days) {
return dates;
}
+// The API only provides one current snapshot per project. Build an
+// illustrative trend that ENDS at the real current values (walking backwards
+// in time), so the bars always show the real numbers. Values are floored at 0
+// only - there is no artificial upper cap.
function generateSyntheticHistory(projectName, snapshot, days, endDateISO) {
const seed = hashString(projectName);
const dates = buildDateRange(endDateISO, days);
- const baseCost = Number(snapshot.predictedCostOverrun ?? 0);
- const baseDelay = Number(snapshot.predictedTimeDelay ?? 0);
- const baseIssues = Number(snapshot.totalOpenIssues ?? 0);
- let cost = baseCost,
- delay = baseDelay,
- issues = baseIssues;
-
- return dates.map((date, idx) => {
- const r1 = ((seed % 97) / 97 - 0.5) * 2;
- const r2 = (((seed >> 3) % 89) / 89 - 0.5) * 2;
- const r3 = (((seed >> 7) % 83) / 83 - 0.5) * 2;
- cost = clamp(cost + r1 * 1.2 + Math.sin((idx + seed) / 5) * 0.6, 0, 999);
- delay = clamp(delay + r2 * 0.4 + Math.cos((idx + seed) / 6) * 0.2, 0, 999);
- issues = clamp(Math.round(issues + r3 * 0.3 + Math.sin((idx + seed) / 7) * 0.1), 0, 999);
- return {
- date,
+ let cost = Number(snapshot.predictedCostOverrun ?? 0);
+ let delay = Number(snapshot.predictedTimeDelay ?? 0);
+ const issues = Number(snapshot.totalOpenIssues ?? 0);
+ const r1 = ((seed % 97) / 97 - 0.5) * 2;
+ const r2 = (((seed >> 3) % 89) / 89 - 0.5) * 2;
+
+ const points = new Array(dates.length);
+ for (let idx = dates.length - 1; idx >= 0; idx -= 1) {
+ points[idx] = {
+ date: dates[idx],
predictedCostOverrun: Number(cost.toFixed(2)),
predictedTimeDelay: Number(delay.toFixed(2)),
totalOpenIssues: issues,
};
- });
+ cost = Math.max(0, cost - (r1 * 1.2 + Math.sin((idx + seed) / 5) * 0.6));
+ delay = Math.max(0, delay - (r2 * 0.4 + Math.cos((idx + seed) / 6) * 0.2));
+ }
+ return points;
}
function getArrow(delta) {
@@ -108,6 +106,11 @@ const getTrendIndicator = (current, previous) => {
return { symbol: '→', color: '#FBBC05', label: 'Unchanged' };
};
+const formatPercent = (value, digits = 2) => {
+ const num = Number(value) || 0;
+ return `${Number.isInteger(num) ? num : num.toFixed(digits)}%`;
+};
+
const RiskTooltip = ({ active, payload, label, darkMode }) => {
if (!active || !payload?.length) return null;
const row = payload[0]?.payload;
@@ -130,14 +133,14 @@ const RiskTooltip = ({ active, payload, label, darkMode }) => {
return (
{label}
-
Cost overrun: {row.predictedCostOverrun}
-
Time delay: {row.predictedTimeDelay}
+
Cost overrun: {formatPercent(row.predictedCostOverrun)}
+
Time delay: {formatPercent(row.predictedTimeDelay)}
Issues: {row.totalOpenIssues}
Summary (selected period)
- Key drivers of change: Cost {getArrow(deltas.cost)} {deltas.cost.toFixed(2)}, Delay{' '}
- {getArrow(deltas.delay)} {deltas.delay.toFixed(2)}, Issues {getArrow(deltas.issues)}{' '}
+ Key drivers of change: Cost {getArrow(deltas.cost)} {formatPercent(deltas.cost)}, Delay{' '}
+ {getArrow(deltas.delay)} {formatPercent(deltas.delay)}, Issues {getArrow(deltas.issues)}{' '}
{deltas.issues.toFixed(0)}
Largest risk contributor: {contributor || 'N/A'}
@@ -166,6 +169,7 @@ export default function ProjectRiskProfileOverview() {
const [selectedProjects, setSelectedProjects] = useState([]);
const [showProjectDropdown, setShowProjectDropdown] = useState(false);
const projectWrapperRef = useRef(null);
+ const selectionInitialized = useRef(false);
useEffect(() => {
const handleClickOutside = event => {
@@ -177,13 +181,35 @@ export default function ProjectRiskProfileOverview() {
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
+ const effectiveWindow = useMemo(() => {
+ const fallbackEnd = formatDateISO(new Date());
+ const end = timeRange === 'custom' && customEnd ? customEnd : fallbackEnd;
+
+ if (timeRange === 'custom') {
+ const start = customStart || end;
+ const startD = new Date(start);
+ const endD = new Date(end);
+ const diffTime = Math.abs(endD - startD);
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
+
+ return { start, end, days: diffDays || 30 };
+ }
+
+ const days = Number(timeRange);
+ const startDate = new Date(end);
+ startDate.setDate(startDate.getDate() - (days - 1));
+ return { start: formatDateISO(startDate), end, days };
+ }, [timeRange, customStart, customEnd]);
+
+ // Reload when the time range changes so range-based counts (e.g. issues)
+ // can come from the API.
useEffect(() => {
async function fetchData() {
setLoading(true);
setError(null);
try {
const res = await httpService.get(
- `${process.env.REACT_APP_APIENDPOINT}/projects/risk-profile`,
+ `${process.env.REACT_APP_APIENDPOINT}/projects/risk-profile?startDate=${effectiveWindow.start}&endDate=${effectiveWindow.end}`,
);
let result = res.data;
@@ -198,7 +224,10 @@ export default function ProjectRiskProfileOverview() {
setRawProjects(normalized);
const names = normalized.map(p => p.projectName);
setAllProjects(names);
- setSelectedProjects(names);
+ if (selectionInitialized.current === false) {
+ setSelectedProjects(names);
+ selectionInitialized.current = true;
+ }
} catch (err) {
setError('Failed to fetch project risk profile data.');
} finally {
@@ -206,27 +235,7 @@ export default function ProjectRiskProfileOverview() {
}
}
fetchData();
- }, []);
-
- const effectiveWindow = useMemo(() => {
- const fallbackEnd = formatDateISO(new Date());
- const end = timeRange === 'custom' && customEnd ? customEnd : fallbackEnd;
-
- if (timeRange === 'custom') {
- const start = customStart || end;
- const startD = new Date(start);
- const endD = new Date(end);
- const diffTime = Math.abs(endD - startD);
- const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
-
- return { start, end, days: diffDays || 30 };
- }
-
- const days = Number(timeRange);
- const startDate = new Date(end);
- startDate.setDate(startDate.getDate() - (days - 1));
- return { start: formatDateISO(startDate), end, days };
- }, [timeRange, customStart, customEnd]);
+ }, [effectiveWindow.start, effectiveWindow.end]);
const projectsWithHistory = useMemo(() => {
if (!rawProjects || rawProjects.length === 0) return [];
@@ -261,7 +270,7 @@ export default function ProjectRiskProfileOverview() {
const filteredProjects = useMemo(() => {
return projectsWithHistory
- .filter(p => selectedProjects.length === 0 || selectedProjects.includes(p.projectName))
+ .filter(p => selectedProjects.includes(p.projectName))
.map(p => {
const hist = (p.history || []).filter(
h => h.date >= effectiveWindow.start && h.date <= effectiveWindow.end,
@@ -454,10 +463,14 @@ export default function ProjectRiskProfileOverview() {
{barChartData.length === 0 ? (
-
No data available for the selected filters.
+
+ {selectedProjects.length === 0 && allProjects.length > 0
+ ? 'No projects selected. Choose at least one project to see its risk profile.'
+ : 'No data available for the selected filters.'}
+
) : (
-
+
+ >
+ formatPercent(value, 0)}
+ fill={chartColors.text}
+ fontSize={11}
+ />
+
+ >
+ Math.round(Number(value) || 0)}
+ fill={chartColors.text}
+ fontSize={11}
+ />
+
+ >
+ formatPercent(value, 0)}
+ fill={chartColors.text}
+ fontSize={11}
+ />
+
)}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.module.css b/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.module.css
index a5b12eda57b..ca2e1448179 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.module.css
+++ b/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.module.css
@@ -476,4 +476,11 @@
.darkMode .dropdownMenu {
background: #2c2c2c;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
-}
\ No newline at end of file
+}
+/* Compact tooltip so it does not cover nearby bars and project names */
+.tooltip {
+ max-width: 240px;
+ padding: 8px 10px;
+ font-size: 0.75rem;
+ line-height: 1.35;
+}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/QuantityOfMaterialsUsed/QuantityOfMaterialsUsed.jsx b/src/components/BMDashboard/WeeklyProjectSummary/QuantityOfMaterialsUsed/QuantityOfMaterialsUsed.jsx
index d45465c2b60..576773e590b 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/QuantityOfMaterialsUsed/QuantityOfMaterialsUsed.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/QuantityOfMaterialsUsed/QuantityOfMaterialsUsed.jsx
@@ -68,7 +68,12 @@ function getRandomColor() {
return `#${Math.floor(Math.random() * 16777215).toString(16)}`;
}
-function QuantityOfMaterialsUsed({ data }) {
+function QuantityOfMaterialsUsed({
+ data,
+ comparisonMode = 'No Comparison',
+ currentDateRange = {},
+ comparisonDateRange = {},
+}) {
const dispatch = useDispatch();
const [chartData, setChartData] = useState(null);
const [selectedMaterials, setSelectedMaterials] = useState([]);
@@ -85,6 +90,7 @@ function QuantityOfMaterialsUsed({ data }) {
const [showModal, setShowModal] = useState(false);
const [isSmallScreen, setIsSmallScreen] = useState(false);
+ const comparisonEnabled = comparisonMode !== 'No Comparison';
useEffect(() => {
dispatch(fetchBMProjects());
@@ -216,6 +222,28 @@ function QuantityOfMaterialsUsed({ data }) {
[],
);
+ useEffect(() => {
+ if (comparisonMode !== 'No Comparison') {
+ const parseDate = date => (date ? new Date(date) : null);
+ setSelectedDate('Custom');
+ setDateRangeOne([parseDate(currentDateRange.startDate), parseDate(currentDateRange.endDate)]);
+ setDateRangeTwo([
+ parseDate(comparisonDateRange.comparisonStartDate),
+ parseDate(comparisonDateRange.comparisonEndDate),
+ ]);
+ } else {
+ setSelectedDate('Last Week');
+ setDateRangeOne([null, null]);
+ setDateRangeTwo([null, null]);
+ }
+ }, [
+ comparisonMode,
+ currentDateRange.startDate,
+ currentDateRange.endDate,
+ comparisonDateRange.comparisonStartDate,
+ comparisonDateRange.comparisonEndDate,
+ ]);
+
const getPeriodLabel = date => {
if (date === 'ALL') return 'Total Materials Used';
@@ -379,7 +407,7 @@ function QuantityOfMaterialsUsed({ data }) {
if (isPeriodOne) {
periodOneUsage += record?.quantityUsed || 0;
}
- if (isPeriodTwo) {
+ if (comparisonEnabled && isPeriodTwo) {
periodTwoUsage += record?.quantityUsed || 0;
}
});
@@ -420,7 +448,7 @@ function QuantityOfMaterialsUsed({ data }) {
return 'gray';
});
- if (periodOneUsageMap.size === 0 && periodTwoUsageMap.size > 0) {
+ if (comparisonEnabled && periodOneUsageMap.size === 0 && periodTwoUsageMap.size > 0) {
let sortedPreviousData = Array.from(periodTwoUsageMap);
if (selectedMaterials.length > 0) {
@@ -481,7 +509,7 @@ function QuantityOfMaterialsUsed({ data }) {
type: 'bar',
order: 2,
},
- ...(selectedDate !== 'ALL'
+ ...(comparisonEnabled && selectedDate !== 'ALL'
? [
{
label: `${getPeriodLabel(selectedDate).split(' vs ')[1]} Usage`,
@@ -503,7 +531,15 @@ function QuantityOfMaterialsUsed({ data }) {
: []),
],
});
- }, [data, selectedMaterials, selectedProjects, selectedDate, dateRangeOne, dateRangeTwo]);
+ }, [
+ data,
+ selectedMaterials,
+ selectedProjects,
+ selectedDate,
+ dateRangeOne,
+ dateRangeTwo,
+ comparisonEnabled,
+ ]);
const barWidth = 12;
// Subtract the 40-px y-axis offset
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/SupplierPerformanceGraph.jsx b/src/components/BMDashboard/WeeklyProjectSummary/SupplierPerformanceGraph.jsx
index 31d4bb673f5..e743f113752 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/SupplierPerformanceGraph.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/SupplierPerformanceGraph.jsx
@@ -165,7 +165,7 @@ const SupplierPerformanceDashboard = function({ height = 420, onDataLoaded }) {
ALL Projects
{projects.map(project => (
- {project._id}
+ {project.projectName}
))}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/Tools/ToolsHorizontalBarChart.jsx b/src/components/BMDashboard/WeeklyProjectSummary/Tools/ToolsHorizontalBarChart.jsx
index 60e9f3713f1..b7b361f5a74 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/Tools/ToolsHorizontalBarChart.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/Tools/ToolsHorizontalBarChart.jsx
@@ -6,6 +6,19 @@ import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGri
import axios from 'axios';
import { ENDPOINTS } from '../../../../utils/URL';
import styles from './ToolsHorizontalBarChart.module.css';
+
+const getChartFontSize = width => {
+ if (width <= 480) return 10;
+ if (width <= 768) return 11;
+ return 12;
+};
+
+const getYAxisWidth = width => {
+ if (width <= 480) return 90;
+ if (width <= 768) return 120;
+ return 150;
+};
+
function CustomTooltip({ active, payload, label, darkMode }) {
if (!active || !payload || !payload.length) {
return null;
@@ -51,6 +64,7 @@ CustomTooltip.propTypes = {
label: PropTypes.string,
darkMode: PropTypes.bool,
};
+
CustomTooltip.defaultProps = {
active: false,
payload: [],
@@ -63,6 +77,7 @@ function ToolsHorizontalBarChart({ darkMode: darkModeProp }) {
const darkMode = typeof darkModeProp === 'boolean' ? darkModeProp : reduxDarkMode;
const [data, setData] = useState([]);
+ const [toolsData, setToolsData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [allProjects, setAllProjects] = useState([]);
@@ -85,7 +100,6 @@ function ToolsHorizontalBarChart({ darkMode: darkModeProp }) {
return () => window.removeEventListener('resize', handleResize);
}, []);
- // Fetch projects list
useEffect(() => {
const fetchProjects = async () => {
try {
@@ -95,7 +109,7 @@ function ToolsHorizontalBarChart({ darkMode: darkModeProp }) {
if (projects && projects.length > 0) {
const projectOptions = projects.map(project => ({
value: project.projectId,
- label: project.projectId,
+ label: project.projectName || project.projectId,
}));
setAllProjects(projectOptions);
setSelectedProject(null);
@@ -121,6 +135,7 @@ function ToolsHorizontalBarChart({ darkMode: darkModeProp }) {
const fetchToolsData = async () => {
if (!selectedProject?.value) {
setData([]);
+ setToolsData([]);
setAllTools([]);
setError(null);
setLoading(false);
@@ -132,18 +147,15 @@ function ToolsHorizontalBarChart({ darkMode: darkModeProp }) {
setError(null);
const projectId = selectedProject.value;
-
const toolsResponseNoFilter = await axios.get(
ENDPOINTS.TOOLS_AVAILABILITY_BY_PROJECT(projectId),
);
-
const toolsResponse = await axios.get(
ENDPOINTS.TOOLS_AVAILABILITY_BY_PROJECT(projectId, startDate, endDate),
);
const toolsDataFiltered = toolsResponse.data;
const toolsDataUnfiltered = toolsResponseNoFilter.data;
-
const toolsData =
toolsDataFiltered && toolsDataFiltered.length > 0
? toolsDataFiltered
@@ -156,34 +168,18 @@ function ToolsHorizontalBarChart({ darkMode: darkModeProp }) {
label: tool,
value: tool,
}));
-
setAllTools(uniqueTools);
} else {
setAllTools([]);
}
- let filteredForChart = toolsData;
- if (selectedTools.length > 0) {
- const selectedNames = selectedTools.map(t => t.value);
- filteredForChart = toolsData.filter(item => selectedNames.includes(item.toolName));
- }
-
- if (filteredForChart.length > 0) {
- const formattedData = filteredForChart.map(item => ({
- name: item.toolName || 'Unknown Tool',
- inUse: item.inUse || 0,
- needsReplacement: item.needsReplacement || 0,
- yetToReceive: item.yetToReceive || 0,
- }));
- setData(formattedData);
- } else {
- setData([]);
- }
+ setToolsData(toolsData);
} catch (err) {
// eslint-disable-next-line no-console
console.error('Failed to load tools data:', err);
setError('Failed to load tools data');
setData([]);
+ setToolsData([]);
setAllTools([]);
} finally {
setLoading(false);
@@ -191,21 +187,38 @@ function ToolsHorizontalBarChart({ darkMode: darkModeProp }) {
};
fetchToolsData();
- }, [selectedProject, startDate, endDate, selectedTools]);
+ }, [selectedProject, startDate, endDate]);
+ useEffect(() => {
+ let filteredForChart = toolsData;
+
+ if (selectedTools.length > 0) {
+ const selectedNames = new Set(selectedTools.map(tool => tool.value));
+ filteredForChart = toolsData.filter(item => selectedNames.has(item.toolName));
+ }
+
+ if (filteredForChart.length > 0) {
+ setData(
+ filteredForChart.map(item => ({
+ name: item.toolName || 'Unknown Tool',
+ inUse: item.inUse || 0,
+ needsReplacement: item.needsReplacement || 0,
+ yetToReceive: item.yetToReceive || 0,
+ })),
+ );
+ } else {
+ setData([]);
+ }
+ }, [toolsData, selectedTools]);
const handleToolChange = selectedOption => setSelectedTools(selectedOption || []);
const handleProjectChange = selectedOption => {
setSelectedProject(selectedOption);
+ setSelectedTools([]);
};
- const handleStartDateChange = e => {
- setStartDate(e.target.value);
- };
-
- const handleEndDateChange = e => {
- setEndDate(e.target.value);
- };
+ const handleStartDateChange = event => setStartDate(event.target.value);
+ const handleEndDateChange = event => setEndDate(event.target.value);
const handleClearDates = () => {
const now = new Date();
@@ -213,10 +226,15 @@ function ToolsHorizontalBarChart({ darkMode: darkModeProp }) {
const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
setStartDate(start.toISOString().split('T')[0]);
setEndDate(end.toISOString().split('T')[0]);
- setSelectedProject(null);
+ };
+
+ const sharedSelectStyles = {
+ menuPortal: base => ({ ...base, zIndex: 9999 }),
+ menu: base => ({ ...base, zIndex: 9999 }),
};
const darkSelectStyles = {
+ ...sharedSelectStyles,
control: base => ({
...base,
backgroundColor: '#253342',
@@ -228,6 +246,7 @@ function ToolsHorizontalBarChart({ darkMode: darkModeProp }) {
...base,
backgroundColor: '#253342',
fontSize: '12px',
+ zIndex: 9999,
}),
option: (base, state) => ({
...base,
@@ -235,59 +254,25 @@ function ToolsHorizontalBarChart({ darkMode: darkModeProp }) {
color: '#e0e0e0',
fontSize: '12px',
}),
- multiValue: base => ({
- ...base,
- backgroundColor: '#3a506b',
- }),
- multiValueLabel: base => ({
- ...base,
- color: '#e0e0e0',
- fontSize: '12px',
- }),
- singleValue: base => ({
- ...base,
- color: '#e0e0e0',
- fontSize: '12px',
- }),
- placeholder: base => ({
- ...base,
- color: '#aaaaaa',
- fontSize: '12px',
- }),
+ multiValue: base => ({ ...base, backgroundColor: '#3a506b' }),
+ multiValueLabel: base => ({ ...base, color: '#e0e0e0', fontSize: '12px' }),
+ singleValue: base => ({ ...base, color: '#e0e0e0', fontSize: '12px' }),
+ placeholder: base => ({ ...base, color: '#aaaaaa', fontSize: '12px' }),
};
const lightSelectStyles = {
- control: base => ({
- ...base,
- minHeight: '32px',
- fontSize: '12px',
- }),
- menu: base => ({
- ...base,
- fontSize: '12px',
- }),
- option: base => ({
- ...base,
- fontSize: '12px',
- }),
- multiValue: base => ({
- ...base,
- backgroundColor: '#e6e6e6',
- }),
- multiValueLabel: base => ({
- ...base,
- fontSize: '12px',
- }),
- singleValue: base => ({
- ...base,
- fontSize: '12px',
- }),
- placeholder: base => ({
- ...base,
- fontSize: '12px',
- }),
+ ...sharedSelectStyles,
+ control: base => ({ ...base, minHeight: '32px', fontSize: '12px' }),
+ menu: base => ({ ...base, fontSize: '12px', zIndex: 9999 }),
+ option: base => ({ ...base, fontSize: '12px' }),
+ multiValue: base => ({ ...base, backgroundColor: '#e6e6e6' }),
+ multiValueLabel: base => ({ ...base, fontSize: '12px' }),
+ singleValue: base => ({ ...base, fontSize: '12px' }),
+ placeholder: base => ({ ...base, fontSize: '12px' }),
};
+ const selectStyles = darkMode ? darkSelectStyles : lightSelectStyles;
+
return (
Tools by Availability
-
- Tool(s)
-
-
-
Project
+
+
+
+ Tool(s)
+
@@ -340,7 +329,6 @@ function ToolsHorizontalBarChart({ darkMode: darkModeProp }) {
className={styles['tools-horizontal-bar-chart-date-picker']}
value={startDate}
onChange={handleStartDateChange}
- placeholder="Start date"
aria-label="Start date"
/>
to
@@ -350,14 +338,13 @@ function ToolsHorizontalBarChart({ darkMode: darkModeProp }) {
className={styles['tools-horizontal-bar-chart-date-picker']}
value={endDate}
onChange={handleEndDateChange}
- placeholder="End date"
aria-label="End date"
/>
↻
@@ -449,9 +436,9 @@ function ToolsHorizontalBarChart({ darkMode: darkModeProp }) {
>
Select a project to load data
- {[80, 55, 70].map(w => (
+ {[80, 55, 70].map(width => (
0 && (
-
+
{
- if (value === null || value === undefined) {
- return '0%';
- }
+ if (value === null || value === undefined) return '0%';
const numeric = Number(value);
- if (Number.isNaN(numeric)) {
- return '0%';
- }
-
- if (Number.isInteger(numeric)) {
- return `${numeric}%`;
- }
-
- return `${numeric.toFixed(1)}%`;
+ if (Number.isNaN(numeric)) return '0%';
+ return Number.isInteger(numeric) ? `${numeric}%` : `${numeric.toFixed(1)}%`;
};
const processResponseData = responseData => {
- if (responseData && responseData.length > 0) {
- return [...responseData].map(item => ({
- ...item,
- name: item.toolName || item.name,
- }));
- }
- return [];
+ if (!responseData?.length) return [];
+
+ return responseData.map(item => ({
+ ...item,
+ name: item.toolName || item.name,
+ }));
};
const fetchStoppageDataForProject = async (projectId, startDate, endDate) => {
@@ -73,83 +63,8 @@ const formatDatesForAPI = (startDate, endDate) => ({
formattedEnd: endDate ? new Date(endDate).toISOString() : null,
});
-const handleDataResponse = (sortedData, setData, setError, emptyData) => {
- setData(sortedData.length > 0 ? sortedData : emptyData);
- setError(null);
-};
-
-const handleSelectedProjectData = async (
- selectedProject,
- startDate,
- endDate,
- setData,
- setError,
- emptyData,
-) => {
- const { formattedStart, formattedEnd } = formatDatesForAPI(startDate, endDate);
- const sortedData = await fetchStoppageDataForProject(
- selectedProject?.value,
- formattedStart,
- formattedEnd,
- );
- handleDataResponse(sortedData, setData, setError, emptyData);
-};
-
-const handleFirstProjectSelection = async (
- projects,
- setSelectedProject,
- setData,
- setError,
- emptyData,
-) => {
- const firstProject = projects[0];
- setSelectedProject({ value: firstProject.projectId, label: firstProject.projectName });
- const sortedData = await fetchStoppageDataForProject(firstProject.projectId, null, null);
- handleDataResponse(sortedData, setData, setError, emptyData);
-};
-
-const handleDataFetching = async params => {
- const {
- selectedProject,
- projects,
- startDate,
- endDate,
- setSelectedProject,
- setData,
- setError,
- emptyData,
- } = params;
-
- if (selectedProject) {
- await handleSelectedProjectData(
- selectedProject,
- startDate,
- endDate,
- setData,
- setError,
- emptyData,
- );
- return;
- }
-
- if (projects.length > 0) {
- await handleFirstProjectSelection(projects, setSelectedProject, setData, setError, emptyData);
- return;
- }
-
- setData(emptyData);
- setError(null);
-};
-
const truncateToolName = name => (name.length > 20 ? `${name.substring(0, 18)}...` : name);
-const createDataset = (label, dataKey, backgroundColor) => ({
- label,
- data: data => data.map(item => Number(item[dataKey] ?? 0)),
- backgroundColor,
- barThickness: 30,
-});
-
const getChartData = data => ({
labels: data.map(item => truncateToolName(item.name)),
datasets: [
@@ -245,15 +160,23 @@ export default function ToolsStoppageHorizontalBarChart() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [data, setData] = useState([]);
- const emptyData = [];
useEffect(() => {
const fetchProjects = async () => {
setLoading(true);
setError(null);
+
try {
const response = await axios.get(ENDPOINTS.BM_TOOL_PROJECTS);
- setProjects(response.data.data);
+ const fetchedProjects = response.data.data || [];
+ setProjects(fetchedProjects);
+
+ if (fetchedProjects.length > 0) {
+ setSelectedProject({
+ value: fetchedProjects[0].projectId,
+ label: fetchedProjects[0].projectName,
+ });
+ }
} catch (err) {
const errorMessage = err?.response?.data?.message || err?.message || 'Please try again.';
setError(`Failed to load projects. ${errorMessage}`);
@@ -267,22 +190,30 @@ export default function ToolsStoppageHorizontalBarChart() {
useEffect(() => {
const fetchToolsStoppageData = async () => {
+ if (!selectedProject?.value) {
+ setData([]);
+ return;
+ }
+
+ // A range selection happens in two steps. Keep the existing chart visible
+ // while the user is choosing the end date and only query a complete range.
+ if (startDate && !endDate) {
+ return;
+ }
+
setLoading(true);
setError(null);
try {
- await handleDataFetching({
- selectedProject,
- projects,
- startDate,
- endDate,
- setSelectedProject,
- setData,
- setError,
- emptyData,
- });
+ const { formattedStart, formattedEnd } = formatDatesForAPI(startDate, endDate);
+ const responseData = await fetchStoppageDataForProject(
+ selectedProject.value,
+ formattedStart,
+ formattedEnd,
+ );
+ setData(responseData);
} catch (err) {
- setData(emptyData);
+ setData([]);
const errorMessage = err?.response?.data?.message || err?.message || 'Please try again.';
setError(`Failed to load tools stoppage reason data. ${errorMessage}`);
} finally {
@@ -291,14 +222,13 @@ export default function ToolsStoppageHorizontalBarChart() {
};
fetchToolsStoppageData();
- }, [selectedProject, startDate, endDate, projects]);
+ }, [selectedProject, startDate, endDate]);
const projectOptions = projects.map(project => ({
value: project.projectId,
label: project.projectName,
}));
- // Format date for display
const formatDate = date => date?.toISOString().split('T')[0];
const dateRangeLabel =
startDate && endDate ? `${formatDate(startDate)} - ${formatDate(endDate)}` : '';
@@ -312,26 +242,22 @@ export default function ToolsStoppageHorizontalBarChart() {
menu: base => ({
...base,
backgroundColor: '#2c3344',
+ zIndex: 9999,
}),
- option: (base, state) => ({
+ menuPortal: base => ({
...base,
- backgroundColor: state.isFocused ? '#364156' : '#2c3344',
- color: '#e0e0e0',
+ zIndex: 9999,
}),
- singleValue: base => ({
+ option: (base, state) => ({
...base,
+ backgroundColor: state.isFocused ? '#364156' : '#2c3344',
color: '#e0e0e0',
}),
- placeholder: base => ({
- ...base,
- color: '#aaaaaa',
- }),
+ singleValue: base => ({ ...base, color: '#e0e0e0' }),
+ placeholder: base => ({ ...base, color: '#aaaaaa' }),
};
- // ✅ Prepare Chart.js data
const chartData = getChartData(data);
-
- // ✅ Chart.js options for horizontal stacked bars
const chartOptions = getChartOptions(darkMode);
return (
@@ -339,6 +265,7 @@ export default function ToolsStoppageHorizontalBarChart() {
Reason of Stoppage of Tools
+
@@ -347,15 +274,14 @@ export default function ToolsStoppageHorizontalBarChart() {
selectsRange
startDate={startDate}
endDate={endDate}
- onChange={update => {
- setDateRange(update);
- }}
+ onChange={update => setDateRange(update)}
placeholderText={dateRangeLabel || 'Filter by Date Range'}
className={`${styles.datePickerInput} form-control ${darkMode ? 'darkTheme' : ''}`}
wrapperClassName={styles.datePickerControl}
calendarClassName={darkMode ? 'darkThemeCalendar' : 'customCalendar'}
/>
+
+
setSelectedProject(opt)}
+ onChange={option => {
+ setSelectedProject(option);
+ setDateRange([null, null]);
+ }}
options={projectOptions}
- placeholder="Select a project ID to view data"
+ placeholder="Select a project to view data"
isClearable={false}
isDisabled={projects.length === 0}
- styles={darkMode ? selectDarkStyles : {}}
+ styles={darkMode ? selectDarkStyles : undefined}
+ menuPortalTarget={document.body}
+ menuPosition="fixed"
/>
+
{
setSelectedProject(null);
setDateRange([null, null]);
- setData(emptyData);
+ setData([]);
setError(null);
}}
>
@@ -403,9 +336,10 @@ export default function ToolsStoppageHorizontalBarChart() {
-
+
{error &&
{error}
}
- {loading &&
Loading tools stoppage data...
}
+
+ {loading &&
Loading tools stoppage data...
}
{!loading && selectedProject && data.length > 0 && (
@@ -414,7 +348,7 @@ export default function ToolsStoppageHorizontalBarChart() {
)}
{!loading && selectedProject && data.length === 0 && (
-
+
No data available for the selected filters.
)}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/Tools/ToolsStoppageHorizontalBarChart/ToolsStoppageHorizontalBarChart.module.css b/src/components/BMDashboard/WeeklyProjectSummary/Tools/ToolsStoppageHorizontalBarChart/ToolsStoppageHorizontalBarChart.module.css
index bcbb1248044..a6d167f61ab 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/Tools/ToolsStoppageHorizontalBarChart/ToolsStoppageHorizontalBarChart.module.css
+++ b/src/components/BMDashboard/WeeklyProjectSummary/Tools/ToolsStoppageHorizontalBarChart/ToolsStoppageHorizontalBarChart.module.css
@@ -414,3 +414,28 @@
padding: 10px;
}
}
+
+/* Keep the chart area stable while loading, empty, or while a date range is being selected. */
+.chartStateContainer {
+ min-height: 400px;
+}
+
+.chartStateMessage {
+ min-height: 400px;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+}
+
+.chartStateMessage p {
+ margin: 0;
+}
+
+@media (width <= 480px) {
+ .chartStateContainer,
+ .chartStateMessage {
+ min-height: 300px;
+ }
+}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/TotalMaterialCostPerProject/TotalMaterialCostPerProject.jsx b/src/components/BMDashboard/WeeklyProjectSummary/TotalMaterialCostPerProject/TotalMaterialCostPerProject.jsx
index 6fc48cf9959..b067f85810c 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/TotalMaterialCostPerProject/TotalMaterialCostPerProject.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/TotalMaterialCostPerProject/TotalMaterialCostPerProject.jsx
@@ -154,8 +154,6 @@ function TotalMaterialCostPerProject() {
minHeight: 38,
boxShadow: 'none',
borderRadius: 8,
- overflowX: 'auto',
- whiteSpace: 'nowrap',
}),
menu: base => ({
...base,
@@ -192,14 +190,16 @@ function TotalMaterialCostPerProject() {
borderRadius: 6,
fontSize: 12,
marginRight: 4,
- maxWidth: 'none',
- flexShrink: 0,
+ maxWidth: '100%',
}),
multiValueLabel: base => ({
...base,
color: darkMode ? '#fff' : '#333',
fontSize: 12,
padding: '2px 6px',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
}),
multiValueRemove: base => ({
...base,
@@ -211,17 +211,17 @@ function TotalMaterialCostPerProject() {
borderRadius: 4,
padding: 2,
}),
+ // Chips wrap onto up to two rows, then scroll vertically, so every selected
+ // project stays visible instead of being cut off on one hidden-scrollbar row.
valueContainer: provided => ({
...provided,
- overflowX: 'auto',
- flexWrap: 'nowrap',
- display: 'flex',
+ flexWrap: 'wrap',
alignItems: 'center',
justifyContent: 'flex-start',
- whiteSpace: 'nowrap',
+ maxHeight: 64,
+ overflowY: 'auto',
maxWidth: '100%',
position: 'relative',
- scrollbarWidth: 'none',
}),
}),
[darkMode],
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx
index d1807fe3a48..050e2faa08d 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx
@@ -3,15 +3,20 @@
/* eslint-disable import/no-unresolved */
import { useState, useEffect, useMemo, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
-import { v4 as uuidv4 } from 'uuid';
+import { UncontrolledTooltip } from 'reactstrap';
import html2canvas from 'html2canvas';
import { jsPDF } from 'jspdf';
import { toast } from 'react-toastify';
import WeeklyProjectSummaryHeader from './WeeklyProjectSummaryHeader';
+import CardSelector from './CardSelector';
import PaidLaborCost from './PaidLaborCost/PaidLaborCost';
import { fetchAllMaterials } from '../../../actions/bmdashboard/materialsActions';
import { fetchBMProjects } from '../../../actions/bmdashboard/projectActions';
+import {
+ fetchWeeklyProjectSummaryProjectStatus,
+ getWeeklyProjectSummaryProjectStatusRequestKey,
+} from '../../../actions/bmdashboard/weeklyProjectSummaryActions';
import IssuesCharts from '../Issues/LongestOpenIssuesChart';
import ProjectRiskProfileOverview from './ProjectRiskProfileOverview';
import IssuesBreakdownChart from './IssuesBreakdownChart';
@@ -28,114 +33,120 @@ import SupplierPerformanceGraph from './SupplierPerformanceGraph.jsx';
import MostFrequentKeywords from './MostFrequentKeywords/MostFrequentKeywords';
import LessonsLearntChart from '../LessonsLearnt/LessonsLearntChart';
import DistributionLaborHours from './DistributionLaborHours/DistributionLaborHours';
-import ActualVsPlannedCost from './ActualVsPlannedCost/ActualVsPlannedCost';
import { MaterialConsumptionCards } from './MaterialConsumption/MaterialConsumption';
-
import ToolsStoppageHorizontalBarChart from './Tools/ToolsStoppageHorizontalBarChart/ToolsStoppageHorizontalBarChart';
-
import ToolStatusDonutChart from './ToolStatusDonutChart/ToolStatusDonutChart';
+import RentalChart from '../RentalChart/RentalChart';
+import ReturnedLateChart from '../RentalChart/ReturnedLateChart';
import InjurySeverityChart from '../Injuries/InjurySeverityChart';
-import CostPredictionChart from './CostPredictionChart';
-
-const projectStatusButtons = [
- {
- title: 'Total Projects',
- value: 426,
- change: '+16% week over week',
- bgColor: '#F0FFEE',
- buttonColor: '#BAF0B6',
- textColor: '#328D1B',
- },
- {
- title: 'Completed Projects',
- value: 127,
- change: '+14% week over week',
- bgColor: '#F3FCFF',
- buttonColor: '#C1EFFB',
- textColor: '#328D1B',
- },
- {
- title: 'Delayed Projects',
- value: 34,
- change: '-18% week over week',
- bgColor: '#FFE9FA',
- buttonColor: '#FECFF3',
- textColor: '#C82F2F',
- },
- {
- title: 'Active Projects',
- value: 265,
- change: '+3% week over week',
- bgColor: '#E8E8FF',
- buttonColor: '#CBCBFE',
- textColor: '#328D1B',
- },
- {
- title: 'Avg Project Duration',
- value: '17 hrs',
- change: '+13% week over week',
- bgColor: '#FFF6EE',
- buttonColor: '#FFD8A5',
- textColor: '#FFD8A5',
- },
- {
- title: 'Total Material Cost',
- value: '$27.6K',
- change: '+9% week over week',
- bgColor: '#FFF3F3',
- buttonColor: '#FBC1C2',
- textColor: '#328D1B',
- },
- {
- title: 'Total Material Used',
- value: '2714',
- change: '+11% week over week',
- bgColor: '#DAC8FF',
- buttonColor: '#B28ECC',
- textColor: '#328D1B',
- },
- {
- title: 'Active Projects',
- value: '265',
- change: '+3% week over week',
- bgColor: '#E8E8FF',
- buttonColor: '#CBCBFE',
- textColor: '#328D1B',
- },
- {
- title: 'Total Labor Hours Invested',
- value: '12.8K',
- change: '+17% week over week',
- bgColor: '#E5C1FC',
- buttonColor: '#F6E1FB',
- textColor: '#328D1B',
- },
- {
- title: 'Total Labor Cost',
- value: '$18.4K',
- change: '+14% week over week',
- bgColor: '#FFFDF3',
- buttonColor: '#FBF9C1',
- textColor: '#328D1B',
- },
- {
- title: 'Material Available',
- value: 693,
- change: '-8% week over week',
- bgColor: '#B4D9C5',
- buttonColor: '#31BD41',
- textColor: '#C82F2F',
- },
- {
- title: 'Material Wasted',
- value: 879,
- change: '+14% week over week',
- bgColor: '#EFBABB',
- buttonColor: '#F79395',
- textColor: '#328D1B',
- },
+import { calculateComparisonDates, parseWeeklySummaryDateRange } from './comparisonDateUtils';
+
+const projectStatusButtonData = [
+ ['Total Projects', 'totalProjects', '#F0FFEE', '#BAF0B6', '#328D1B'],
+ ['Completed Projects', 'completedProjects', '#F3FCFF', '#C1EFFB', '#328D1B'],
+ ['Delayed Projects', 'delayedProjects', '#FFE9FA', '#FECFF3', '#C82F2F'],
+ ['Active Projects', 'activeProjects', '#E8E8FF', '#CBCBFE', '#328D1B'],
+ ['Avg Project Duration', 'avgProjectDuration', '#FFF6EE', '#FFD8A5', '#FFD8A5'],
+ ['Total Material Cost', 'totalMaterialCost', '#FFF3F3', '#FBC1C2', '#328D1B'],
+ ['Total Material Used', 'totalMaterialUsed', '#DAC8FF', '#B28ECC', '#328D1B'],
+ ['Total Labor Hours Invested', 'totalLaborHoursInvested', '#E5C1FC', '#F6E1FB', '#328D1B'],
+ ['Total Labor Cost', 'totalLaborCost', '#FFFDF3', '#FBF9C1', '#328D1B'],
+ ['Material Available', 'materialAvailable', '#B4D9C5', '#31BD41', '#C82F2F'],
+ ['Material Wasted', 'materialWasted', '#EFBABB', '#F79395', '#328D1B'],
];
+const projectStatusButtons = projectStatusButtonData.map(
+ ([title, metricKey, bgColor, buttonColor, textColor]) => ({
+ title,
+ metricKey,
+ bgColor,
+ buttonColor,
+ textColor,
+ }),
+);
+
+const PERIOD_COMPARABLE = 'PERIOD_COMPARABLE';
+const UNAVAILABLE = 'UNAVAILABLE';
+const EMPTY_PROJECT_STATUS_METRICS = {};
+
+function formatProjectStatusValue(metric) {
+ if (!metric || metric.comparisonType === UNAVAILABLE || metric.value === null) {
+ return 'N/A';
+ }
+
+ if (metric.unit === 'USD') {
+ return new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 2,
+ }).format(metric.value);
+ }
+
+ if (metric.unit === 'hrs') {
+ return `${metric.value} hrs`;
+ }
+
+ if (typeof metric.value === 'number') {
+ return new Intl.NumberFormat('en-US').format(metric.value);
+ }
+
+ return metric.value;
+}
+
+function getProjectStatusUnavailableReason(metric) {
+ if (!metric || (metric.comparisonType !== UNAVAILABLE && metric.value !== null)) {
+ return null;
+ }
+
+ return metric.unavailableReason || 'Data does not exist for the selected period.';
+}
+
+function getProjectStatusComparisonUnavailableText(comparisonPeriodFilter) {
+ return `No ${comparisonPeriodFilter.toLowerCase()} comparison available`;
+}
+
+function formatProjectStatusChange(metric, comparisonPeriodFilter) {
+ const percentageChange = metric?.percentageChange;
+
+ if (comparisonPeriodFilter === 'No Comparison') {
+ return null;
+ }
+
+ if (
+ metric?.comparisonType !== PERIOD_COMPARABLE ||
+ percentageChange === null ||
+ percentageChange === undefined ||
+ typeof percentageChange === 'string'
+ ) {
+ return getProjectStatusComparisonUnavailableText(comparisonPeriodFilter);
+ }
+
+ const percent = Math.round(percentageChange * 100);
+ const prefix = percent > 0 ? '+' : '';
+ return `${prefix}${percent}% ${comparisonPeriodFilter.toLowerCase()}`;
+}
+
+function getProjectStatusChangeColor(metric) {
+ if (typeof metric?.percentageChange !== 'number') {
+ return '#C82F2F';
+ }
+
+ if (metric.percentageChange === 0) {
+ return '#000';
+ }
+
+ return metric.percentageChange > 0 ? '#328D1B' : '#C82F2F';
+}
+
+function getProjectStatusErrorMessage(error) {
+ return (
+ error?.response?.data?.message ||
+ error?.message ||
+ 'Unable to load Project Status data. Please try again.'
+ );
+}
+
function WeeklyProjectSummary() {
const dispatch = useDispatch();
const materials = useSelector(state => state.materials?.materialslist || []);
@@ -144,7 +155,50 @@ function WeeklyProjectSummary() {
const darkMode = useSelector(state => state.theme.darkMode);
const projectFilter = useSelector(state => state.weeklyProjectSummary?.projectFilter || '');
const dateRangeFilter = useSelector(state => state.weeklyProjectSummary?.dateRangeFilter || '');
+ const comparisonPeriodFilter = useSelector(
+ state => state.weeklyProjectSummary?.comparisonPeriodFilter || 'No Comparison',
+ );
+ const projectStatusData = useSelector(state => state.weeklyProjectSummary?.projectStatusData);
+ const projectStatusLoading = useSelector(
+ state => state.weeklyProjectSummary?.projectStatusLoading || false,
+ );
+ const projectStatusError = useSelector(state => state.weeklyProjectSummary?.projectStatusError);
+ const projectStatusDataRequestKey = useSelector(
+ state => state.weeklyProjectSummary?.projectStatusDataRequestKey,
+ );
+ const projectStatusErrorRequestKey = useSelector(
+ state => state.weeklyProjectSummary?.projectStatusErrorRequestKey,
+ );
const containerRef = useRef(null);
+ const userId = useSelector(state => state.auth?.user?.userid);
+ const cardPrefsKey = `weeklyProjectSummaryCards:${userId || 'guest'}`;
+ // Saved card selection per user: { visibleKeys, cardLimit }. null = show all.
+ const [cardPrefs, setCardPrefs] = useState(null);
+
+ useEffect(() => {
+ try {
+ const saved = JSON.parse(localStorage.getItem(cardPrefsKey) || 'null');
+ setCardPrefs(saved && Array.isArray(saved.visibleKeys) ? saved : null);
+ } catch {
+ setCardPrefs(null);
+ }
+ }, [cardPrefsKey]);
+
+ const comparisonEnabled = comparisonPeriodFilter !== 'No Comparison';
+
+ const currentDateRange = useMemo(() => parseWeeklySummaryDateRange(dateRangeFilter), [
+ dateRangeFilter,
+ ]);
+
+ const comparisonDateRange = useMemo(
+ () =>
+ calculateComparisonDates(
+ comparisonPeriodFilter,
+ currentDateRange.startDate,
+ currentDateRange.endDate,
+ ),
+ [comparisonPeriodFilter, currentDateRange.startDate, currentDateRange.endDate],
+ );
useEffect(() => {
if (materials.length === 0) {
@@ -167,6 +221,49 @@ function WeeklyProjectSummary() {
const bmProjects = useSelector(state => state.bmProjects || []);
+ const selectedProjectId = useMemo(() => {
+ const selectedProject = bmProjects.find(project => project.name === projectFilter);
+ return selectedProject?._id;
+ }, [bmProjects, projectFilter]);
+
+ const projectStatusRequestParams = useMemo(() => {
+ if (!currentDateRange.startDate || !currentDateRange.endDate) return null;
+
+ const params = {
+ startDate: currentDateRange.startDate,
+ endDate: currentDateRange.endDate,
+ };
+
+ if (selectedProjectId) {
+ params.projectId = selectedProjectId;
+ }
+
+ if (comparisonEnabled) {
+ if (!comparisonDateRange.comparisonStartDate || !comparisonDateRange.comparisonEndDate) {
+ return null;
+ }
+ params.comparisonStartDate = comparisonDateRange.comparisonStartDate;
+ params.comparisonEndDate = comparisonDateRange.comparisonEndDate;
+ }
+
+ return params;
+ }, [
+ currentDateRange.startDate,
+ currentDateRange.endDate,
+ selectedProjectId,
+ comparisonEnabled,
+ comparisonDateRange.comparisonStartDate,
+ comparisonDateRange.comparisonEndDate,
+ ]);
+
+ const projectStatusRequestKey = useMemo(
+ () =>
+ projectStatusRequestParams
+ ? getWeeklyProjectSummaryProjectStatusRequestKey(projectStatusRequestParams)
+ : null,
+ [projectStatusRequestParams],
+ );
+
// Fetch initial data
useEffect(() => {
if (materials.length === 0) {
@@ -178,6 +275,43 @@ function WeeklyProjectSummary() {
}
}, [dispatch, materials.length, bmProjects.length]);
+ useEffect(() => {
+ if (!projectStatusRequestParams) return;
+
+ dispatch(fetchWeeklyProjectSummaryProjectStatus(projectStatusRequestParams));
+ }, [dispatch, projectStatusRequestParams]);
+
+ const isProjectStatusDataCurrent =
+ Boolean(projectStatusData) && projectStatusDataRequestKey === projectStatusRequestKey;
+ const isProjectStatusErrorCurrent =
+ Boolean(projectStatusError) && projectStatusErrorRequestKey === projectStatusRequestKey;
+ const isProjectStatusLoading =
+ Boolean(projectStatusRequestParams) &&
+ (projectStatusLoading || (!isProjectStatusDataCurrent && !isProjectStatusErrorCurrent));
+ const projectStatusDisplayError = isProjectStatusErrorCurrent ? projectStatusError : null;
+ const showProjectStatusComparison =
+ comparisonEnabled && !isProjectStatusLoading && !projectStatusDisplayError;
+
+ const projectStatusMetrics =
+ isProjectStatusDataCurrent && !isProjectStatusLoading && !projectStatusDisplayError
+ ? projectStatusData?.current?.metrics || EMPTY_PROJECT_STATUS_METRICS
+ : EMPTY_PROJECT_STATUS_METRICS;
+
+ const projectStatusCards = useMemo(
+ () =>
+ projectStatusButtons.map(button => {
+ const metric = projectStatusMetrics[button.metricKey];
+ return {
+ ...button,
+ value: formatProjectStatusValue(metric),
+ unavailableReason: getProjectStatusUnavailableReason(metric),
+ change: formatProjectStatusChange(metric, comparisonPeriodFilter),
+ changeColor: getProjectStatusChangeColor(metric),
+ };
+ }),
+ [comparisonPeriodFilter, projectStatusMetrics],
+ );
+
const sections = useMemo(
() => [
{
@@ -191,29 +325,79 @@ function WeeklyProjectSummary() {
key: 'Project Status',
className: 'full',
content: (
-
- {projectStatusButtons.map(button => {
- const uniqueId = uuidv4();
- return (
-
-
{button.title}
+ <>
+ {isProjectStatusLoading && (
+
+ Loading Project Status data...
+
+ )}
+ {projectStatusDisplayError && (
+
{getProjectStatusErrorMessage(projectStatusDisplayError)}
+ )}
+
+ {projectStatusCards.map(button => {
+ const unavailableTooltipId = `project-status-unavailable-${button.metricKey}`;
+ return (
-
{button.value}
+
{button.title}
+
+
+ {button.value}
+ {button.unavailableReason && (
+ <>
+
+
+ i
+
+
+
+ {button.unavailableReason}
+
+ >
+ )}
+
+
+ {showProjectStatusComparison && button.change && (
+
+ {button.change}
+
+ )}
-
- {button.change}
-
-
- );
- })}
-
+ );
+ })}
+
+ >
),
},
{
@@ -242,7 +426,12 @@ function WeeklyProjectSummary() {
className: 'large',
// Shared with /bmdashboard/issuechart so the PR-required three-card grouping stays in sync.
content: (
-
+
),
},
{
@@ -254,29 +443,42 @@ function WeeklyProjectSummary() {
{
title: 'Tools and Equipment Tracking',
key: 'Tools and Equipment Tracking',
- className: 'half',
+ className: 'full',
content: (
- <>
- {/*
*/}
-
+
+
-
),
},
{
@@ -371,7 +573,7 @@ function WeeklyProjectSummary() {
content: (
@@ -410,6 +612,22 @@ function WeeklyProjectSummary() {
),
},
+ {
+ title: 'Rental Tracking',
+ key: 'Rental Tracking',
+ className: 'full',
+ content: (
+
+ ),
+ },
{
title: 'Financials Tracking',
key: 'Financials Tracking',
@@ -417,28 +635,39 @@ function WeeklyProjectSummary() {
content: (
),
},
],
- [quantityOfMaterialsUsedData, darkMode],
+ [
+ quantityOfMaterialsUsedData,
+ darkMode,
+ comparisonPeriodFilter,
+ projectStatusCards,
+ isProjectStatusLoading,
+ projectStatusDisplayError,
+ showProjectStatusComparison,
+ currentDateRange,
+ comparisonDateRange,
+ ],
);
+ const visibleKeys = cardPrefs?.visibleKeys ?? sections.map(section => section.key);
+ const cardLimit = cardPrefs?.cardLimit ?? 'all';
+ const selectedSections = sections.filter(section => visibleKeys.includes(section.key));
+ const visibleSections =
+ cardLimit === 'all' ? selectedSections : selectedSections.slice(0, Number(cardLimit));
+
+ const handleCardsChange = (keys, limit) => {
+ const next = { visibleKeys: keys, cardLimit: limit };
+ setCardPrefs(next);
+ try {
+ localStorage.setItem(cardPrefsKey, JSON.stringify(next));
+ } catch {
+ // Storage unavailable (e.g. private browsing); keep the in-memory choice.
+ }
+ };
+
const handleSaveAsPDF = async () => {
// Prevent multiple simultaneous PDF generations
if (isGeneratingPDF) {
@@ -624,8 +853,25 @@ function WeeklyProjectSummary() {
isGeneratingPDF={isGeneratingPDF}
/>
+ {isGeneratingPDF === false && (
+
+ ({ key, title }))}
+ visibleKeys={visibleKeys}
+ cardLimit={cardLimit}
+ shownCount={visibleSections.length}
+ onChange={handleCardsChange}
+ darkMode={darkMode}
+ />
+
+ )}
+ {visibleSections.length === 0 && (
+
+ No cards selected. Use Customize Cards to choose which cards to show.
+
+ )}
- {sections.map(({ title, key, className, content }) => (
+ {visibleSections.map(({ title, key, className, content }) => (
");
- background-color: var(--card-bg) !important;
- color: var(--text-color) !important;
- border-color: var(--card-shadow) !important;
+ background-color: var(--card-bg);
+ color: var(--text-color);
+ border-color: var(--card-shadow);
}
.weeklySummaryHeaderControls select:focus {
@@ -110,45 +112,120 @@
}
.weeklySummaryHeaderControls select:hover {
- background-color: var(--section-title-hover) !important;
+ background-color: var(--section-title-hover);
+}
+
+/* Match the Total Org Summary comparison dropdown interaction inside this header only. */
+.weeklySummaryComparisonDropdown {
+ flex: 0 0 auto;
+}
+
+.weeklySummaryComparisonDropdown :global(.dropdown-toggle) {
+ box-sizing: border-box;
+ height: 38px;
+ min-height: 38px;
+ padding: 0 14px;
+ border: none;
+ border-radius: 6px;
+ font-size: 14px;
+ font-weight: 500;
+ line-height: 1.25;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 150px;
+ background-color: #6f42c1;
+ color: white;
+ box-shadow: 0 2px 6px rgb(111 66 193 / 30%);
+ transition: all 0.3s ease;
+}
+
+.weeklySummaryComparisonDropdown :global(.dropdown-toggle):hover {
+ background-color: #5a359a;
+ box-shadow: 0 4px 12px rgb(111 66 193 / 40%);
+}
+
+.weeklySummaryComparisonDropdown :global(.dropdown-toggle):focus {
+ outline: none;
+ box-shadow: 0 0 0 3px rgb(111 66 193 / 30%);
+}
+
+.weeklySummaryComparisonDropdown :global(.dropdown-menu) {
+ min-width: 180px;
+ padding: 8px 0;
+ margin-top: 8px;
+ border: none;
+ border-radius: 12px;
+ background-color: white;
+ box-shadow: 0 8px 25px rgb(0 0 0 / 15%);
+}
+
+.weeklySummaryComparisonDropdown :global(.dropdown-item) {
+ padding: 12px 20px;
+ border: none;
+ background-color: transparent;
+ color: #333;
+ font-weight: 500;
+ transition: all 0.2s ease;
+}
+
+.weeklySummaryComparisonDropdown :global(.dropdown-item):hover {
+ background-color: #f8f9fa;
+ color: #6f42c1;
+ transform: translateX(4px);
+}
+
+.darkMode .weeklySummaryComparisonDropdown :global(.dropdown-menu) {
+ background-color: #34495e;
+ border: 1px solid #6f42c1;
+ box-shadow: 0 8px 25px rgb(0 0 0 / 30%);
+}
+
+.darkMode .weeklySummaryComparisonDropdown :global(.dropdown-item) {
+ color: #fff;
+}
+
+.darkMode .weeklySummaryComparisonDropdown :global(.dropdown-item):hover {
+ background-color: #6f42c1;
+ color: #fff;
}
/* Share Button — override Bootstrap .btn so it lines up with .form-control selects */
/* Bootstrap .btn defaults to flex-shrink: 1; when the row is tight the button shrinks and
the label overflows outside the painted background (overlapping the last dropdown). */
-.weeklySummaryShareBtn {
- height: 38px !important;
+.weeklySummaryHeaderControls .weeklySummaryShareBtn {
+ height: 38px;
min-height: 38px;
min-width: 7.25rem;
- padding: 0 14px !important;
- margin: 0 !important;
- display: inline-flex !important;
+ padding: 0 14px;
+ margin: 0;
+ display: inline-flex;
align-items: center;
justify-content: center;
- line-height: 1.25 !important;
+ line-height: 1.25;
box-sizing: border-box;
border-width: 1px;
vertical-align: middle;
align-self: center;
flex: 0 0 auto;
flex-shrink: 0;
- background-color: var(--button-bg) !important;
+ background-color: var(--button-bg);
color: white;
font-family: inherit;
white-space: nowrap;
- border-radius: 6px !important;
+ border-radius: 6px;
transition: background 0.2s ease-in-out;
}
/* Bootstrap .btn can add vertical offset; align with selects */
.weeklySummaryHeaderControls :global(button.btn) {
- margin: 0 !important;
+ margin: 0;
vertical-align: middle;
}
.darkMode .weeklySummaryShareBtn {
- background-color: var(--button-bg) !important;
+ background-color: var(--button-bg);
}
.weeklySummaryShareBtn:focus {
@@ -162,11 +239,11 @@
}
.darkMode .weeklySummaryShareBtn:hover {
- background-color: var(--button-hover) !important;
+ background-color: var(--button-hover);
}
.weeklySummaryShareBtn:hover:not(:disabled) {
- background-color: var(--button-hover) !important;
+ background-color: var(--button-hover);
}
/* Spinner for PDF generation */
@@ -221,9 +298,11 @@
border-radius: 6px;
text-align: center;
box-shadow: 0 2px 5px var(--card-shadow);
+ flex: 1 1 250px;
width: 100%;
max-width: 100%;
box-sizing: border-box;
+ min-width: 0;
overflow: hidden visible;
}
@@ -326,9 +405,7 @@
.normalCard {
width: 100%;
- max-width: 100%;
- min-height: 250px;
- box-sizing: border-box;
+ min-height: 200px;
}
.mapCard {
@@ -339,6 +416,16 @@
grid-column: span 4;
}
+/* Stacks cards in one column across every column of the section grid, so the
+ section's 2-column tablet layout can't squeeze them into half the width */
+.fullWidthStack {
+ grid-column: 1 / -1;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: 15px;
+ min-width: 0;
+}
+
/* ---------------- SECTION ---------------- */
.weeklyProjectSummaryDashboardSection {
background: var(--section-bg);
@@ -381,14 +468,12 @@
border-top: 1px solid rgb(255 255 255 / 20%);
}
-.weeklyProjectSummaryDashboardSection.half
- .weeklyProjectSummaryDashboardCategoryContent {
+.weeklyProjectSummaryDashboardSection.half .weeklyProjectSummaryDashboardCategoryContent {
grid-template-columns: repeat(2, 1fr);
gap: 15px;
}
-.weeklyProjectSummaryDashboardSection.small
- .weeklyProjectSummaryDashboardCategoryContent {
+.weeklyProjectSummaryDashboardSection.small .weeklyProjectSummaryDashboardCategoryContent {
display: grid;
grid-template-columns: 1fr;
gap: 15px;
@@ -521,13 +606,17 @@
justify-content: center;
border-radius: 25px;
width: 100%;
- max-width: 284px;
+
+ /* Fill the responsive grid track instead of capping cards at a fixed desktop width. */
+ max-width: none;
+ min-width: 0;
height: 190px;
text-align: center;
box-shadow: 0 4px 8px rgb(0 0 0 / 15%);
padding: 20px;
border: 1px solid rgb(0 0 0 / 10%);
position: relative;
+ overflow: visible;
transition: all 0.3s ease;
}
@@ -539,37 +628,97 @@
}
.weeklyStatusValue {
- color: #000;
- font-size: 40px;
+ /* Pills are always pastel, so the value stays dark in both themes */
+ color: #1a1a1a;
+ font-size: clamp(26px, 2.2vw, 36px);
font-weight: 600;
+ white-space: nowrap;
transition: color 0.3s ease;
}
+.projectStatusUnavailableInfo {
+ align-items: center;
+ border: 1px solid currentcolor;
+ border-radius: 50%;
+ color: inherit;
+ cursor: help;
+ display: inline-flex;
+ font-size: 10px;
+ font-weight: 700;
+ height: 16px;
+ justify-content: center;
+ line-height: 1;
+ margin-left: 6px;
+ vertical-align: middle;
+ width: 16px;
+}
+
+.projectStatusUnavailableButton {
+ align-items: center;
+ appearance: none;
+ background: transparent;
+ border: 0;
+ color: inherit;
+ cursor: help;
+ display: inline-flex;
+ font: inherit;
+ height: 100%;
+ justify-content: center;
+ line-height: 1;
+ padding: 0;
+ width: 100%;
+}
+
+.projectStatusUnavailableTooltip {
+ z-index: 1060;
+}
+
.weekly-status-change {
transition: color 0.3s ease;
font-size: 14px;
font-weight: 500;
+ line-height: 1.3;
+ max-width: 100%;
+ overflow-wrap: anywhere;
}
.darkMode .statusCard {
- background: var(--card-bg) !important;
color: var(--text-color) !important;
border-color: rgb(255 255 255 / 10%) !important;
}
-.darkMode .weeklyCardTitle,
-.darkMode .weeklyStatusValue,
-.darkMode .weekly-status-change {
- color: var(--text-color) !important;
+/* Keep all Project Status text readable on the preserved pastel status cards in dark mode. */
+.darkMode .statusCard .weeklyCardTitle,
+.darkMode .statusCard .weeklyStatusValue {
+ color: #000 !important;
+}
+
+.darkMode .statusCard .projectStatusUnavailableInfo {
+ color: #000 !important;
+}
+
+.darkMode .statusCard .projectStatusUnavailableButton {
+ color: #000 !important;
+}
+
+/* Preserve semantic comparison colors against broad dark-mode inherited text rules. */
+.darkMode .statusCard :global(.weekly-status-change) {
+ color: var(--project-status-change-color) !important;
}
/* Tooltip styles */
.quantityOfMaterialsUsedChartTooltip {
max-height: 80vh !important;
+
+ /* Limit to 80% of viewport height */
overflow-y: auto !important;
+
+ /* Enable vertical scrolling */
padding-right: 15px !important;
- /* Firefox */
+ /* Add some padding for the scrollbar */
+
+ /* Support for Firefox */
scrollbar-width: thin;
scrollbar-color: rgb(0 0 0 / 30%) rgb(0 0 0 / 10%);
}
@@ -627,8 +776,12 @@
/* MERGED: keep ONLY one projectStatusGrid */
.projectStatusGrid {
+ /* Span the full section even when the section content is a 2-column grid (tablet) */
+ grid-column: 1 / -1;
display: grid;
- grid-template-columns: repeat(5, 1fr);
+
+ /* Let status cards choose the largest safe column count at every viewport width. */
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
gap: 20px;
justify-content: center;
align-items: center;
@@ -637,6 +790,11 @@
margin: auto;
}
+/* Keep Project Status from being squeezed into one parent category column on tablets. */
+.weeklyProjectSummaryDashboardCategoryContent .projectStatusGrid {
+ grid-column: 1 / -1;
+}
+
/* .projectStatusGrid {
display: grid;
grid-template-columns: repeat(6, 1fr);
@@ -646,7 +804,10 @@
/* ---------------- OVAL STATUS BUTTON ---------------- */
.weeklyStatusButton {
- width: 130px;
+ /* Keep the original pill size for short values while letting longer values like "$27.6K" fit. */
+ width: auto;
+ min-width: 130px;
+ max-width: 100%;
height: 65px;
border-radius: 50px / 32px;
display: flex;
@@ -654,55 +815,56 @@
justify-content: center;
box-shadow: 0 2px 5px rgb(0 0 0 / 15%);
margin: 12px 0;
+ padding: 0 18px;
}
-/* ---------------- RESPONSIVE BREAKPOINTS ---------------- */
+/* ---------------- RESPONSIVE DESIGN ---------------- */
/* Large Screens */
-@media (width >= 1400px) {
+@media (width >=1400px) {
.lessonsLearnedGrid {
min-height: 650px;
}
-
+
.lessonsCard {
min-height: 650px;
}
}
-@media (width <= 1399px) and (width >= 1200px) {
+@media (width <=1399px) and (width >=1200px) {
.lessonsLearnedGrid {
min-height: 600px;
}
-
+
.lessonsCard {
min-height: 600px;
}
}
-@media (width <= 1199px) and (width >= 992px) {
+@media (width <=1199px) and (width >=992px) {
.lessonsLearnedGrid {
min-height: 550px;
gap: 20px;
}
-
+
.lessonsCard {
min-height: 550px;
}
}
-@media (width <= 991px) and (width >= 769px) {
+@media (width <=991px) and (width >=769px) {
.lessonsLearnedGrid {
min-height: 500px;
gap: 16px;
}
-
+
.lessonsCard {
min-height: 500px;
}
}
/* Medium Screens */
-@media (width <= 1024px) {
+@media (width <=1024px) {
.weeklySummaryHeaderContainer {
flex-direction: column;
align-items: center;
@@ -719,7 +881,9 @@
}
.darkMode .weeklySummaryHeaderTitle {
- color: #fff !important; /* bright white title */
+ color: #fff !important;
+
+ /* bright white title */
}
.darkMode .weeklySummaryHeaderControls select {
@@ -736,9 +900,9 @@
.darkMode .weeklySummaryShareBtn {
background: #e8a71c !important;
color: #000 !important; /* readable in dark mode */
- /* Tools and Equipment Tracking - 2 column layout on tablet */
}
-
+
+ /* Tools and Equipment Tracking - 2 column layout on tablet */
.weeklyProjectSummaryDashboardCategoryContent {
grid-template-columns: repeat(2, 1fr);
gap: 15px;
@@ -753,7 +917,7 @@
}
/* Small Screens */
-@media (width <= 768px) {
+@media (width <=768px) {
.weeklySummaryHeaderContainer {
flex-direction: column;
align-items: center;
@@ -771,7 +935,6 @@
}
.weeklySummaryShareBtn {
- width: 100%;
margin-top: 5px;
}
@@ -786,18 +949,14 @@
grid-column: span 1;
}
- .projectStatusGrid {
- grid-template-columns: 1fr;
- }
-
.weeklyProjectSummaryDashboardSection.half .weeklyProjectSummaryDashboardCategoryContent {
grid-template-columns: 1fr;
}
-
+
.weeklyProjectSummaryCard :global(.chartContainer) {
min-height: 240px;
}
-
+
.normalCard {
min-height: 280px;
}
@@ -807,31 +966,31 @@
gap: 16px;
min-height: auto;
}
-
+
.lessonsCard {
min-height: 500px;
}
-
+
.toolsTrackingLayout {
grid-template-columns: 1fr;
}
-
+
.toolsDonutWrap {
grid-column: span 1;
}
-
+
.financialsGrid {
grid-template-columns: 1fr;
}
-
+
.financialsGrid .financialBig {
grid-column: span 1;
}
-
+
.laborTimeGrid {
grid-template-columns: 1fr;
}
-
+
.financialsTrackingGrid {
grid-template-columns: 1fr;
}
@@ -842,15 +1001,15 @@
.lessonsCard {
min-height: 450px;
}
-
+
.weeklyProjectSummaryCard :global(.chartContainer) {
min-height: 220px;
}
-
+
.normalCard {
min-height: 260px;
}
-
+
.weeklySummaryHeaderTitle {
font-size: large;
flex-direction: column;
@@ -858,37 +1017,6 @@
}
}
-/* Status Grid Responsive */
-@media (width >= 1600px) {
- .projectStatusGrid {
- grid-template-columns: repeat(6, 1fr);
- }
-}
-
-@media (width <= 1400px) {
- .projectStatusGrid {
- grid-template-columns: repeat(4, 1fr);
- }
-}
-
-@media (width <= 1024px) {
- .projectStatusGrid {
- grid-template-columns: repeat(3, 1fr);
- }
-}
-
-@media (width <= 768px) {
- .projectStatusGrid {
- grid-template-columns: repeat(2, 1fr);
- }
-}
-
-@media (width <= 576px) {
- .projectStatusGrid {
- grid-template-columns: repeat(1, 1fr);
- }
-}
-
/* Dark mode fixes */
.darkMode .weeklyProjectSummaryContainer {
background-color: var(--bg-color);
@@ -916,12 +1044,12 @@
.weeklyProjectSummaryCard {
break-inside: avoid;
}
-
+
.weeklyProjectSummaryCard :global(.container) {
box-shadow: none;
border: 1px solid #ddd;
}
-
+
.lessonsLearnedGrid,
.toolsTrackingLayout,
.financialsGrid,
@@ -929,9 +1057,26 @@
.financialsTrackingGrid {
break-inside: avoid;
}
-
+
.weeklySummaryShareBtn,
.weeklySummaryHeaderControls select {
display: none;
}
}
+
+/* Customize Cards control */
+.cardSelectorBar {
+ display: flex;
+ justify-content: flex-end;
+ margin-bottom: 12px;
+}
+
+.noCardsMessage {
+ padding: 24px;
+ text-align: center;
+ color: #6c757d;
+}
+
+.darkMode .noCardsMessage {
+ color: #cfd7e3;
+}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.projectStatus.test.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.projectStatus.test.jsx
new file mode 100644
index 00000000000..0a1e3856535
--- /dev/null
+++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.projectStatus.test.jsx
@@ -0,0 +1,216 @@
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import configureMockStore from 'redux-mock-store';
+import WeeklyProjectSummary from './WeeklyProjectSummary';
+import {
+ fetchWeeklyProjectSummaryProjectStatus,
+ getWeeklyProjectSummaryProjectStatusRequestKey,
+} from '../../../actions/bmdashboard/weeklyProjectSummaryActions';
+
+const passthroughRequestKey = params => {
+ const queryParams = new URLSearchParams();
+ Object.entries(params || {})
+ .filter(([, value]) => value !== undefined && value !== null && value !== '')
+ .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
+ .forEach(([key, value]) => queryParams.append(key, value));
+
+ return queryParams.toString();
+};
+
+vi.mock('../../../actions/bmdashboard/weeklyProjectSummaryActions', () => ({
+ fetchWeeklyProjectSummaryProjectStatus: vi.fn(params => ({
+ type: 'FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS',
+ params,
+ })),
+ getWeeklyProjectSummaryProjectStatusRequestKey: vi.fn(params => passthroughRequestKey(params)),
+}));
+
+vi.mock('../../../actions/bmdashboard/materialsActions', () => ({
+ fetchAllMaterials: vi.fn(() => ({ type: 'FETCH_ALL_MATERIALS' })),
+}));
+
+vi.mock('../../../actions/bmdashboard/projectActions', () => ({
+ fetchBMProjects: vi.fn(() => ({ type: 'FETCH_BM_PROJECTS' })),
+}));
+
+vi.mock('html2canvas', () => ({ default: vi.fn() }));
+vi.mock('jspdf', () => ({ jsPDF: vi.fn() }));
+vi.mock('react-toastify', () => ({
+ toast: {
+ info: vi.fn(),
+ dismiss: vi.fn(),
+ success: vi.fn(),
+ error: vi.fn(),
+ },
+}));
+
+vi.mock('./WeeklyProjectSummaryHeader', () => ({ default: () =>
Header
}));
+vi.mock('./PaidLaborCost/PaidLaborCost', () => ({ default: () =>
Paid Labor Cost
}));
+vi.mock('../Issues/LongestOpenIssuesChart', () => ({ default: () =>
Issues Chart
}));
+vi.mock('./ProjectRiskProfileOverview', () => ({ default: () =>
Risk Profile
}));
+vi.mock('./IssuesBreakdownChart', () => ({ default: () =>
Issues Breakdown
}));
+vi.mock('./GroupedBarGraphInjurySeverity/InjuryCategoryBarChart', () => ({
+ default: () =>
Injury Category
,
+}));
+vi.mock('./Tools/ToolsHorizontalBarChart', () => ({ default: () =>
Tools Chart
}));
+vi.mock('./Financials/ExpenseBarChart', () => ({ default: () =>
Expense Chart
}));
+vi.mock('./Financials/CostVarianceTrendGraph', () => ({
+ default: () =>
Cost Variance
,
+}));
+vi.mock('./Financials/CostBreakDown/CostBreakDown', () => ({
+ default: () =>
Cost Breakdown
,
+}));
+vi.mock('./ExpenditureChart/FinancialsTrackingSection', () => ({
+ default: () =>
Financials Tracking
,
+}));
+vi.mock('../InteractiveMap/InteractiveMap', () => ({ default: () =>
Interactive Map
}));
+vi.mock('./Financials/LossTrackingLineCharts/LossTrackingLineChart', () => ({
+ default: () =>
Loss Tracking
,
+}));
+vi.mock('./SupplierPerformanceGraph.jsx', () => ({
+ default: () =>
Supplier Performance
,
+}));
+vi.mock('./MostFrequentKeywords/MostFrequentKeywords', () => ({
+ default: () =>
Most Frequent Keywords
,
+}));
+vi.mock('../LessonsLearnt/LessonsLearntChart', () => ({
+ default: () =>
Lessons Learnt
,
+}));
+vi.mock('./DistributionLaborHours/DistributionLaborHours', () => ({
+ default: () =>
Distribution Labor Hours
,
+}));
+vi.mock('./ActualVsPlannedCost/ActualVsPlannedCost', () => ({
+ default: () =>
Actual Vs Planned
,
+}));
+vi.mock('./MaterialConsumption/MaterialConsumption', () => ({
+ MaterialConsumptionCards: () =>
Material Consumption
,
+}));
+vi.mock('./Tools/ToolsStoppageHorizontalBarChart/ToolsStoppageHorizontalBarChart', () => ({
+ default: () =>
Tools Stoppage
,
+}));
+vi.mock('./ToolStatusDonutChart/ToolStatusDonutChart', () => ({
+ default: () =>
Tool Status
,
+}));
+vi.mock('../Injuries/InjurySeverityChart', () => ({
+ default: () =>
Injury Severity
,
+}));
+vi.mock('./CostPredictionChart', () => ({ default: () =>
Cost Prediction
}));
+
+const mockStore = configureMockStore([]);
+
+const currentParams = {
+ startDate: '2026-09-13',
+ endDate: '2026-09-19',
+ projectId: 'project-1',
+ comparisonStartDate: '2026-08-13',
+ comparisonEndDate: '2026-08-19',
+};
+
+const currentRequestKey = passthroughRequestKey(currentParams);
+
+const projectStatusData = {
+ current: {
+ metrics: {
+ completedProjects: {
+ value: 7,
+ unit: 'count',
+ comparisonType: 'PERIOD_COMPARABLE',
+ percentageChange: 0.16,
+ },
+ },
+ },
+};
+
+const createState = weeklyProjectSummaryOverrides => ({
+ materials: { materialslist: [{ _id: 'material-1' }] },
+ bmProjects: [{ _id: 'project-1', name: 'One Community' }],
+ theme: { darkMode: false },
+ weeklyProjectSummary: {
+ projectFilter: 'One Community',
+ dateRangeFilter: 'Sep 13, 26 - Sep 19, 26',
+ comparisonPeriodFilter: 'Month Over Month',
+ projectStatusData,
+ projectStatusLoading: false,
+ projectStatusError: null,
+ projectStatusDataRequestKey: currentRequestKey,
+ projectStatusErrorRequestKey: null,
+ ...weeklyProjectSummaryOverrides,
+ },
+});
+
+const renderWithState = state => {
+ const store = mockStore(state);
+ const view = render(
+
+
+ ,
+ );
+
+ fireEvent.click(screen.getAllByRole('button', { name: /Project Status/ })[0]);
+ return { ...view, store };
+};
+
+describe('WeeklyProjectSummary Project Status request state', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('renders API-backed comparison text only when the data matches the current filters', () => {
+ renderWithState(createState());
+
+ expect(screen.getByText('+16% month over month')).toBeInTheDocument();
+ expect(screen.queryByRole('status')).not.toBeInTheDocument();
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
+ });
+
+ it('shows loading and suppresses stale comparison text when filters change before useEffect dispatches', () => {
+ renderWithState(
+ createState({
+ projectStatusDataRequestKey: 'endDate=2026-08-19&startDate=2026-08-13',
+ }),
+ );
+
+ expect(screen.getByRole('status')).toHaveTextContent('Loading Project Status data...');
+ expect(screen.queryByText('+16% month over month')).not.toBeInTheDocument();
+ });
+
+ it('shows the latest request error and suppresses stale comparison text', () => {
+ renderWithState(
+ createState({
+ projectStatusData: null,
+ projectStatusDataRequestKey: null,
+ projectStatusError: new Error('Latest request failed'),
+ projectStatusErrorRequestKey: currentRequestKey,
+ }),
+ );
+
+ expect(screen.getByRole('alert')).toHaveTextContent('Latest request failed');
+ expect(screen.queryByText('+16% month over month')).not.toBeInTheDocument();
+ });
+
+ it('dispatches a new Project Status request when comparison filters change rapidly', async () => {
+ const { rerender } = renderWithState(createState({ comparisonPeriodFilter: 'Week Over Week' }));
+
+ await waitFor(() =>
+ expect(fetchWeeklyProjectSummaryProjectStatus).toHaveBeenCalledWith({
+ startDate: '2026-09-13',
+ endDate: '2026-09-19',
+ projectId: 'project-1',
+ comparisonStartDate: '2026-09-06',
+ comparisonEndDate: '2026-09-12',
+ }),
+ );
+
+ const monthState = createState({ comparisonPeriodFilter: 'Month Over Month' });
+ rerender(
+
+
+ ,
+ );
+
+ await waitFor(() =>
+ expect(fetchWeeklyProjectSummaryProjectStatus).toHaveBeenCalledWith(currentParams),
+ );
+ expect(getWeeklyProjectSummaryProjectStatusRequestKey).toHaveBeenCalled();
+ });
+});
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummaryHeader.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummaryHeader.jsx
index 8b4b00d4044..14327b0dc2b 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummaryHeader.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummaryHeader.jsx
@@ -1,5 +1,5 @@
-import { useEffect, useMemo } from 'react';
-import { Input, Button } from 'reactstrap';
+import { useEffect, useMemo, useState } from 'react';
+import { Input, Button, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { useSelector, useDispatch } from 'react-redux';
import moment from 'moment-timezone';
import {
@@ -7,10 +7,12 @@ import {
setDateRangeFilter,
setComparisonPeriodFilter,
} from '../../../actions/bmdashboard/weeklyProjectSummaryActions';
+import { COMPARISON_OPTIONS } from './comparisonDateUtils';
import styles from './WeeklyProjectSummary.module.css';
export default function WeeklyProjectSummaryHeader({ handleSaveAsPDF, isGeneratingPDF = false }) {
const dispatch = useDispatch();
+ const [comparisonDropdownOpen, setComparisonDropdownOpen] = useState(false);
const projectFilter = useSelector(state => state.weeklyProjectSummary?.projectFilter);
const dateRangeFilter = useSelector(state => state.weeklyProjectSummary?.dateRangeFilter);
const comparisonPeriodFilter = useSelector(
@@ -46,14 +48,14 @@ export default function WeeklyProjectSummaryHeader({ handleSaveAsPDF, isGenerati
};
useEffect(() => {
- const { lastWeek, prevWeek } = getLastTwoCompletedWeeks();
+ const { lastWeek } = getLastTwoCompletedWeeks();
dispatch(setDateRangeFilter(lastWeek));
- dispatch(setComparisonPeriodFilter(prevWeek));
+ dispatch(setComparisonPeriodFilter('No Comparison'));
}, [dispatch]);
const projectOptions = useMemo(() => ['One Community'], []);
const dateRangeOptions = useMemo(() => [getLastTwoCompletedWeeks().lastWeek], []);
- const comparisonOptions = useMemo(() => [getLastTwoCompletedWeeks().prevWeek], []);
+ const comparisonOptions = useMemo(() => COMPARISON_OPTIONS, []);
return (
@@ -87,18 +89,23 @@ export default function WeeklyProjectSummaryHeader({ handleSaveAsPDF, isGenerati
))}
-
dispatch(setComparisonPeriodFilter(e.target.value))}
- aria-label="Comparison Period"
+
setComparisonDropdownOpen(prev => !prev)}
>
- {comparisonOptions.map(option => (
-
- {option}
-
- ))}
-
+ {comparisonPeriodFilter || 'No Comparison'}
+
+ {comparisonOptions.map(option => (
+ dispatch(setComparisonPeriodFilter(option))}
+ >
+ {option}
+
+ ))}
+
+
{
+ it.each([
+ ['January 2026', 2026, 0],
+ ['March 2026', 2026, 2],
+ ['December 2025', 2025, 11],
+ ])('parses "%s" into year %i, month index %i', (label, year, monthIndex) => {
+ const result = parseMonthLabel(label);
+ expect(result.getFullYear()).toBe(year);
+ expect(result.getMonth()).toBe(monthIndex);
+ expect(result.getDate()).toBe(1);
+ });
+
+ it('is case-insensitive on the month name', () => {
+ const result = parseMonthLabel('march 2026');
+ expect(result.getMonth()).toBe(2);
+ });
+
+ it('returns an invalid Date for malformed input', () => {
+ expect(Number.isNaN(parseMonthLabel('not a date').getTime())).toBe(true);
+ expect(Number.isNaN(parseMonthLabel('').getTime())).toBe(true);
+ expect(Number.isNaN(parseMonthLabel(null).getTime())).toBe(true);
+ expect(Number.isNaN(parseMonthLabel('Marchtober 2026').getTime())).toBe(true);
+ });
+});
+
+describe('parseIsoDateOnly', () => {
+ it('parses a YYYY-MM-DD string into a local Date with matching Y/M/D', () => {
+ const result = parseIsoDateOnly('2026-03-17');
+ expect(result.getFullYear()).toBe(2026);
+ expect(result.getMonth()).toBe(2); // March = index 2
+ expect(result.getDate()).toBe(17);
+ });
+
+ it('does not shift the date across a month/day boundary regardless of local timezone', () => {
+ // This is the core regression check: new Date('2026-03-01') (native
+ // constructor) can resolve to Feb 28 in timezones behind UTC. Our
+ // parser must never do that.
+ const result = parseIsoDateOnly('2026-03-01');
+ expect(result.getFullYear()).toBe(2026);
+ expect(result.getMonth()).toBe(2);
+ expect(result.getDate()).toBe(1);
+ });
+
+ it('returns null for empty/undefined input', () => {
+ expect(parseIsoDateOnly('')).toBeNull();
+ expect(parseIsoDateOnly(undefined)).toBeNull();
+ expect(parseIsoDateOnly(null)).toBeNull();
+ });
+});
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/__tests__/CostPredictionChart.localDate.test.js b/src/components/BMDashboard/WeeklyProjectSummary/__tests__/CostPredictionChart.localDate.test.js
new file mode 100644
index 00000000000..24b83f122fd
--- /dev/null
+++ b/src/components/BMDashboard/WeeklyProjectSummary/__tests__/CostPredictionChart.localDate.test.js
@@ -0,0 +1,31 @@
+import { describe, it, expect, vi, afterEach } from 'vitest';
+import { getLocalDateString } from '../CostPredictionChart';
+
+describe('getLocalDateString (End Date limit / clamp)', () => {
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('uses the local calendar date at the UTC day boundary', () => {
+ // 6 p.m. and 11:59 p.m. local on Oct 5: in timezones behind UTC (e.g. Pacific)
+ // the UTC date is already Oct 6, but the limit must stay Oct 5.
+ expect(getLocalDateString(new Date(2026, 9, 5, 18, 0, 0))).toBe('2026-10-05');
+ expect(getLocalDateString(new Date(2026, 9, 5, 23, 59, 59))).toBe('2026-10-05');
+ });
+
+ it('uses the local date just after local midnight', () => {
+ // 12:30 a.m. local on Oct 6: in timezones ahead of UTC (e.g. India)
+ // the UTC date is still Oct 5, but the limit must be Oct 6.
+ expect(getLocalDateString(new Date(2026, 9, 6, 0, 30, 0))).toBe('2026-10-06');
+ });
+
+ it('pads single-digit months and days', () => {
+ expect(getLocalDateString(new Date(2026, 0, 3, 9, 0, 0))).toBe('2026-01-03');
+ });
+
+ it('defaults to the current local date', () => {
+ vi.useFakeTimers({ toFake: ['Date'] });
+ vi.setSystemTime(new Date(2026, 9, 5, 18, 0, 0));
+ expect(getLocalDateString()).toBe('2026-10-05');
+ });
+});
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/__tests__/WeeklyProjectSummary.test.jsx b/src/components/BMDashboard/WeeklyProjectSummary/__tests__/WeeklyProjectSummary.test.jsx
new file mode 100644
index 00000000000..6dc578912fc
--- /dev/null
+++ b/src/components/BMDashboard/WeeklyProjectSummary/__tests__/WeeklyProjectSummary.test.jsx
@@ -0,0 +1,167 @@
+import { render, screen } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import configureMockStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+import WeeklyProjectSummary from '../WeeklyProjectSummary';
+
+vi.mock('../../../../actions/bmdashboard/materialsActions', () => ({
+ fetchAllMaterials: vi.fn(() => ({
+ type: 'FETCH_ALL_MATERIALS',
+ })),
+}));
+
+vi.mock('../../../../actions/bmdashboard/projectActions', () => ({
+ fetchBMProjects: vi.fn(() => ({
+ type: 'FETCH_BM_PROJECTS',
+ })),
+}));
+
+// Keep this regression test focused on WeeklyProjectSummary itself.
+// The child visualizations have their own rendering/data requirements.
+vi.mock('../WeeklyProjectSummaryHeader', () => ({
+ default: () => Weekly Project Summary
,
+}));
+
+vi.mock('../ProjectRiskProfileOverview', () => ({
+ default: () => Risk Profile
,
+}));
+
+vi.mock('../IssuesBreakdownChart', () => ({
+ default: () => Issues Breakdown
,
+}));
+
+vi.mock('../GroupedBarGraphInjurySeverity/InjuryCategoryBarChart', () => ({
+ default: () => Injury Category
,
+}));
+
+vi.mock('../Tools/ToolsHorizontalBarChart', () => ({
+ default: () => Tools Chart
,
+}));
+
+vi.mock('../Financials/ExpenseBarChart', () => ({
+ default: () => Expense Chart
,
+}));
+
+vi.mock('../Financials/CostVarianceTrendGraph', () => ({
+ default: () => Cost Variance
,
+}));
+
+vi.mock('../Financials/CostBreakDown/CostBreakDown', () => ({
+ default: () => Cost Breakdown
,
+}));
+
+vi.mock('../ExpenditureChart/FinancialsTrackingSection', () => ({
+ default: () => Financial Tracking
,
+}));
+
+vi.mock('../../InteractiveMap/InteractiveMap', () => ({
+ default: () => Interactive Map
,
+}));
+
+vi.mock('../Financials/LossTrackingLineCharts/LossTrackingLineChart', () => ({
+ default: () => Loss Tracking
,
+}));
+
+vi.mock('../SupplierPerformanceGraph.jsx', () => ({
+ default: () => Supplier Performance
,
+}));
+
+vi.mock('../MostFrequentKeywords/MostFrequentKeywords', () => ({
+ default: () => Most Frequent Keywords
,
+}));
+
+vi.mock('../../LessonsLearnt/LessonsLearntChart', () => ({
+ default: () => Lessons Learnt
,
+}));
+
+vi.mock('../DistributionLaborHours/DistributionLaborHours', () => ({
+ default: () => Distribution Labor Hours
,
+}));
+
+vi.mock('../ActualVsPlannedCost/ActualVsPlannedCost', () => ({
+ default: () => Actual vs Planned Cost
,
+}));
+
+vi.mock('../MaterialConsumption/MaterialConsumption', () => ({
+ MaterialConsumptionCards: () => Material Consumption
,
+}));
+
+vi.mock('../Tools/ToolsStoppageHorizontalBarChart/ToolsStoppageHorizontalBarChart', () => ({
+ default: () => Tools Stoppage
,
+}));
+
+vi.mock('../ToolStatusDonutChart/ToolStatusDonutChart', () => ({
+ default: () => Tool Status
,
+}));
+
+vi.mock('../../Injuries/InjurySeverityChart', () => ({
+ default: () => Injury Severity
,
+}));
+
+vi.mock('../CostPredictionChart', () => ({
+ default: () => Cost Prediction
,
+}));
+
+vi.mock('../PaidLaborCost/PaidLaborCost', () => ({
+ default: () => Paid Labor Cost
,
+}));
+
+vi.mock('../../Issues/LongestOpenIssuesChart', () => ({
+ default: () => Issues Chart
,
+}));
+
+const middlewares = [thunk];
+const mockStore = configureMockStore(middlewares);
+
+describe('WeeklyProjectSummary regression tests', () => {
+ let store;
+
+ beforeEach(() => {
+ store = mockStore({
+ materials: {
+ materialslist: [],
+ },
+ bmProjects: [],
+ theme: {
+ darkMode: false,
+ },
+ weeklyProjectSummary: {
+ projectFilter: '',
+ dateRangeFilter: '',
+ },
+ });
+ });
+
+ it('renders WeeklyProjectSummary without crashing', () => {
+ expect(() => {
+ render(
+
+
+ ,
+ );
+ }).not.toThrow();
+
+ expect(screen.getByTestId('weekly-project-summary-header')).toBeInTheDocument();
+ });
+
+ it('dispatches initial data-loading actions when data is empty', () => {
+ render(
+
+
+ ,
+ );
+
+ const actions = store.getActions();
+
+ expect(actions).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ type: 'FETCH_ALL_MATERIALS',
+ }),
+ expect.objectContaining({
+ type: 'FETCH_BM_PROJECTS',
+ }),
+ ]),
+ );
+ });
+});
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/comparisonDateUtils.js b/src/components/BMDashboard/WeeklyProjectSummary/comparisonDateUtils.js
new file mode 100644
index 00000000000..487be6d0ac5
--- /dev/null
+++ b/src/components/BMDashboard/WeeklyProjectSummary/comparisonDateUtils.js
@@ -0,0 +1,60 @@
+import moment from 'moment';
+
+export const COMPARISON_OPTIONS = [
+ 'No Comparison',
+ 'Week Over Week',
+ 'Month Over Month',
+ 'Year Over Year',
+];
+
+export function shiftComparisonDate(date, diffDays, type) {
+ if (type === 'Week Over Week') return new Date(date.setUTCDate(date.getUTCDate() - diffDays));
+ if (type === 'Month Over Month')
+ return moment
+ .utc(date)
+ .subtract(1, 'month')
+ .toDate();
+ if (type === 'Year Over Year')
+ return moment
+ .utc(date)
+ .subtract(1, 'year')
+ .toDate();
+ return null;
+}
+
+export function calculateComparisonDates(comparisonType, fromDate, toDate) {
+ if (comparisonType === 'No Comparison' || !fromDate || !toDate) {
+ return {
+ comparisonStartDate: null,
+ comparisonEndDate: null,
+ };
+ }
+
+ const start = new Date(fromDate);
+ const end = new Date(toDate);
+ const diffDays = Math.ceil(Math.abs(end - start) / (1000 * 60 * 60 * 24)) + 1;
+ const shiftedStart = shiftComparisonDate(start, diffDays, comparisonType);
+ const shiftedEnd = shiftComparisonDate(end, diffDays, comparisonType);
+
+ return {
+ comparisonStartDate: shiftedStart ? shiftedStart.toISOString().split('T')[0] : null,
+ comparisonEndDate: shiftedEnd ? shiftedEnd.toISOString().split('T')[0] : null,
+ };
+}
+
+export function parseWeeklySummaryDateRange(dateRange) {
+ if (!dateRange || typeof dateRange !== 'string') return { startDate: null, endDate: null };
+
+ const [startText, endText] = dateRange.split(' - ');
+ if (!startText || !endText) return { startDate: null, endDate: null };
+
+ const normalizeDateText = value => {
+ const parsed = moment(value, 'MMM DD, YY');
+ return parsed.isValid() ? parsed.format('YYYY-MM-DD') : null;
+ };
+
+ return {
+ startDate: normalizeDateText(startText),
+ endDate: normalizeDateText(endText),
+ };
+}
diff --git a/src/components/Badge/AssignBadge.jsx b/src/components/Badge/AssignBadge.jsx
index cea701d6a5a..aa4fbb0e640 100644
--- a/src/components/Badge/AssignBadge.jsx
+++ b/src/components/Badge/AssignBadge.jsx
@@ -15,6 +15,7 @@ import {
import { connect, useSelector } from 'react-redux';
import { boxStyle, boxStyleDark } from '~/styles';
import AssignBadgePopup from './AssignBadgePopup';
+import BadgeStatistics from './BadgeStatistics';
import {
getFirstName,
getLastName,
@@ -178,6 +179,7 @@ function AssignBadge(props) {
className={`container-fluid ${darkMode ? 'bg-yinmn-blue text-light' : ''}`}
style={{ padding: 20 }}
>
+
{
props.fetchAllBadges();
}, [props.fetchAllBadges]);
- const handleTabChange = tabId => {
- setActiveTab(tabId);
- };
-
useEffect(() => {
if (!activeTab) {
setActiveTab(canAssignBadges ? '1' : '2');
@@ -32,13 +28,14 @@ function BadgeManagement(props) {
}
}, [activeTab, canAssignBadges, setActiveTab]);
+ if (loading) {
+ return Loading...
;
+ }
+
return (
Badge Management
@@ -55,10 +52,8 @@ function BadgeManagement(props) {
{canAssignBadges && (
handleTabChange('1')}
+ className={`mr-2 ${classnames({ active: activeTab === '1' })}`}
+ onClick={() => setActiveTab('1')}
style={
darkMode
? { ...boxStyleDark, cursor: 'pointer' }
@@ -71,10 +66,8 @@ function BadgeManagement(props) {
)}
handleTabChange('2')}
+ className={`${classnames({ active: activeTab === '2' })}`}
+ onClick={() => setActiveTab('2')}
style={
darkMode ? { ...boxStyleDark, cursor: 'pointer' } : { ...boxStyle, cursor: 'pointer' }
}
@@ -83,10 +76,11 @@ function BadgeManagement(props) {
+
{canAssignBadges && (
-
+
)}
@@ -102,6 +96,7 @@ const mapStateToProps = state => ({
role: state.userProfile.role,
darkMode: state.theme.darkMode,
activeTab: state.badge.activeTab,
+ loading: state.badge.loading,
});
const mapDispatchToProps = dispatch => ({
diff --git a/src/components/Badge/BadgeStatistics.jsx b/src/components/Badge/BadgeStatistics.jsx
new file mode 100644
index 00000000000..0f6a6e47108
--- /dev/null
+++ b/src/components/Badge/BadgeStatistics.jsx
@@ -0,0 +1,425 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { Pie } from 'react-chartjs-2';
+import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js';
+import {
+ Dropdown,
+ DropdownItem,
+ DropdownMenu,
+ DropdownToggle,
+ Input,
+ Label,
+ Button,
+} from 'reactstrap';
+import styles from './BadgeStatistics.module.css';
+
+ChartJS.register(ArcElement, Tooltip, Legend);
+
+const CHART_COLORS = ['#4F46E5', '#10B981', '#3B82F6', '#F59E0B', '#EF4444', '#8B5CF6'];
+
+const getDropdownToggleLabel = count => {
+ if (count === 0) {
+ return 'Select Badges';
+ }
+ const suffix = count > 1 ? 's' : '';
+ return `${count} Badge${suffix} Selected`;
+};
+
+const getTableRowBackground = (index, darkMode) => {
+ const isEven = index % 2 === 0;
+ if (darkMode) {
+ return isEven ? '#0F172A' : '#1E293B';
+ }
+ return isEven ? '#F1F5F9' : '#FFFFFF';
+};
+
+const processBadgeData = allBadgeData => {
+ const counts = {};
+ const usersMap = {};
+
+ allBadgeData.forEach(badge => {
+ if (Array.isArray(badge.users)) {
+ counts[badge._id] = new Set(badge.users.map(user => user.userId?._id)).size;
+ usersMap[badge._id] = badge.users
+ .map(user => ({
+ userId: user.userId?._id,
+ firstName: user.userId?.firstName || 'Unknown',
+ lastName: user.userId?.lastName || 'Unknown',
+ }))
+ .slice(0, 10);
+ } else {
+ counts[badge._id] = 0;
+ usersMap[badge._id] = [];
+ }
+ });
+
+ return { counts, usersMap };
+};
+
+const getChartData = (selectedBadges, allBadgeData, badgeUserCounts, darkMode) => {
+ if (!selectedBadges.length) return null;
+
+ return {
+ labels: selectedBadges.map(badgeId => {
+ const badge = allBadgeData.find(item => item._id === badgeId);
+ return badge?.badgeName || 'Unknown Badge';
+ }),
+ datasets: [
+ {
+ data: selectedBadges.map(badgeId => badgeUserCounts[badgeId] || 0),
+ backgroundColor: CHART_COLORS,
+ hoverBackgroundColor: CHART_COLORS.map(color => `${color}CC`),
+ borderWidth: 2,
+ borderColor: darkMode ? '#1F2937' : '#fff',
+ hoverOffset: 40,
+ },
+ ],
+ };
+};
+
+const getChartOptions = darkMode => ({
+ maintainAspectRatio: false,
+ responsive: true,
+ layout: {
+ padding: 20,
+ },
+ plugins: {
+ datalabels: {
+ color: '#ffffff',
+ font: {
+ weight: 'bold',
+ size: 14,
+ },
+ display: context => context.dataset.data[context.dataIndex] > 0,
+ },
+ legend: {
+ position: 'right',
+ labels: {
+ font: { size: 13, family: 'Inter, sans-serif' },
+ padding: 15,
+ usePointStyle: true,
+ generateLabels: chart => {
+ const datasets = chart.data.datasets;
+ return chart.data.labels.map((label, i) => {
+ const maxLength = 24;
+ const truncatedLabel =
+ label.length > maxLength ? `${label.slice(0, maxLength)}...` : label;
+
+ return {
+ text: truncatedLabel,
+ fontColor: darkMode ? '#ffffff' : '#374151',
+ fillStyle: datasets[0].backgroundColor[i],
+ strokeStyle: datasets[0].backgroundColor[i],
+ hidden: !chart.getDataVisibility(i),
+ index: i,
+ };
+ });
+ },
+ },
+ },
+ tooltip: {
+ backgroundColor: darkMode ? '#1F2937' : '#fff',
+ titleColor: darkMode ? '#fff' : '#111827',
+ bodyColor: darkMode ? '#E5E7EB' : '#374151',
+ borderColor: darkMode ? '#374151' : '#E5E7EB',
+ borderWidth: 1,
+ callbacks: {
+ label: context => {
+ const label = context.label || '';
+ const value = context.parsed || 0;
+ const total = context.dataset.data.reduce((sum, item) => sum + item, 0);
+ const percentage = total ? ((value / total) * 100).toFixed(1) : '0.0';
+ return `${label}: ${value} users (${percentage}%)`;
+ },
+ },
+ },
+ },
+ animation: { duration: 1000, easing: 'easeInOutQuad' },
+});
+
+const BadgeDropdownItems = ({ allBadgeData, selectedBadges, handleBadgeSelect, darkMode }) =>
+ allBadgeData.map(badge => {
+ const isChecked = selectedBadges.includes(badge._id);
+ const isDisabled = !isChecked && selectedBadges.length >= 10;
+
+ return (
+
+
+ handleBadgeSelect(badge._id)}
+ disabled={isDisabled}
+ />
+ {badge.badgeName}
+
+
+ );
+ });
+
+const ChartSection = ({ chartData, chartOptions, chartRef, darkMode }) => {
+ if (!chartData) {
+ return (
+
+ Select badges to view assignment statistics
+
+ );
+ }
+
+ return (
+
+
+ Badge Assignment Distribution
+
+
+
+ );
+};
+
+const UserTableRow = ({ user, index, badgeId, darkMode }) => (
+
+
+ {user.firstName} {user.lastName}
+
+
+);
+
+const BadgeUserCard = ({ badgeId, allBadgeData, badgeUsers, darkMode }) => {
+ const badge = allBadgeData.find(item => item._id === badgeId);
+ const users = badgeUsers[badgeId];
+ const hasUsers = users?.length > 0;
+
+ return (
+
+
+ {badge?.badgeName || 'Unknown Badge'}
+
+
+
+
+
+ User Name
+
+
+
+ {hasUsers ? (
+ users.map((user, index) => (
+
+ ))
+ ) : (
+
+
+ No users assigned
+
+
+ )}
+
+
+
+
+ );
+};
+
+const AssignedUsersSection = ({ selectedBadges, allBadgeData, badgeUsers, darkMode }) => {
+ if (selectedBadges.length === 0) return null;
+
+ return (
+
+
+ Assigned Users
+
+ {selectedBadges.length >= 10 && (
+
+ You can only select up to 10 badges. Deselect one or more to continue.
+
+ )}
+ {selectedBadges.map(badgeId => (
+
+ ))}
+
+ );
+};
+
+function BadgeStatistics({ allBadgeData = [], darkMode }) {
+ const [dropdownOpen, setDropdownOpen] = useState(false);
+ const [selectedBadges, setSelectedBadges] = useState([]);
+ const [badgeUserCounts, setBadgeUserCounts] = useState({});
+ const [badgeUsers, setBadgeUsers] = useState({});
+ const chartRef = useRef();
+
+ useEffect(() => {
+ const { counts, usersMap } = processBadgeData(allBadgeData);
+ setBadgeUserCounts(counts);
+ setBadgeUsers(usersMap);
+ }, [allBadgeData]);
+
+ const chartData = getChartData(selectedBadges, allBadgeData, badgeUserCounts, darkMode);
+ const chartOptions = getChartOptions(darkMode);
+
+ const handleBadgeSelect = badgeId => {
+ setSelectedBadges(previous =>
+ previous.includes(badgeId) ? previous.filter(id => id !== badgeId) : [...previous, badgeId],
+ );
+ };
+
+ const clearSelectedBadges = () => setSelectedBadges([]);
+ const toggleDropdown = () => setDropdownOpen(previous => !previous);
+
+ return (
+ <>
+
+ Select Badges
+
+ {getDropdownToggleLabel(selectedBadges.length)}
+
+
+
+
+
+ Clear Selected
+
+
+
+
+ >
+ );
+}
+
+export default BadgeStatistics;
diff --git a/src/components/Badge/BadgeStatistics.module.css b/src/components/Badge/BadgeStatistics.module.css
new file mode 100644
index 00000000000..b5efaef75fe
--- /dev/null
+++ b/src/components/Badge/BadgeStatistics.module.css
@@ -0,0 +1,13 @@
+.dropdown-menu {
+ padding: 0;
+}
+
+.badgeDropdownItem:hover,
+.badgeDropdownItem:hover label {
+ background-color: #E6EEF8 !important;
+}
+
+.badgeDropdownItem.dark:hover,
+.badgeDropdownItem.dark:hover label {
+ background-color: #2b3553 !important;
+}
\ No newline at end of file
diff --git a/src/components/Badge/__tests__/BadgeStatistics.test.jsx b/src/components/Badge/__tests__/BadgeStatistics.test.jsx
new file mode 100644
index 00000000000..c33ed22247c
--- /dev/null
+++ b/src/components/Badge/__tests__/BadgeStatistics.test.jsx
@@ -0,0 +1,151 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import BadgeStatistics from '../BadgeStatistics';
+
+vi.mock('react-chartjs-2', () => ({
+ Pie: React.forwardRef(function MockPie(props, ref) {
+ return
;
+ }),
+}));
+
+const mockBadgeData = [
+ {
+ _id: 'badge-1',
+ badgeName: 'Community Builder',
+ users: [
+ { userId: { _id: 'u1', firstName: 'Alex', lastName: 'Morgan' } },
+ { userId: { _id: 'u2', firstName: 'Taylor', lastName: 'Swift' } },
+ ],
+ },
+ {
+ _id: 'badge-2',
+ badgeName: 'Bug Hunter',
+ users: [{ userId: { _id: 'u3', firstName: 'Jordan', lastName: 'Lee' } }],
+ },
+ {
+ _id: 'badge-3',
+ badgeName: 'Code Ninja',
+ users: [],
+ },
+ {
+ _id: 'badge-4',
+ badgeName: 'Malformed Badge',
+ users: null, // Edge case: invalid users prop
+ },
+];
+
+describe('BadgeStatistics Component', () => {
+ test('renders initial empty state before selecting any badges', () => {
+ render( );
+
+ // Specify role/selector to avoid ambiguity between and
+ expect(screen.getByText('Select Badges', { selector: 'label' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Select Badges' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /clear selected/i })).toBeInTheDocument();
+ expect(screen.getByText('Select badges to view assignment statistics')).toBeInTheDocument();
+ expect(screen.queryByTestId('mock-pie-chart')).not.toBeInTheDocument();
+ });
+
+ test('opens dropdown and displays available badge options', () => {
+ render( );
+
+ const toggleButton = screen.getByRole('button', { name: 'Select Badges' });
+ fireEvent.click(toggleButton);
+
+ expect(screen.getByText('Community Builder')).toBeInTheDocument();
+ expect(screen.getByText('Bug Hunter')).toBeInTheDocument();
+ expect(screen.getByText('Code Ninja')).toBeInTheDocument();
+ });
+
+ test('updates UI, chart, and user list when selecting badges', () => {
+ render( );
+
+ // Open dropdown
+ fireEvent.click(screen.getByRole('button', { name: 'Select Badges' }));
+
+ // Select "Community Builder"
+ const checkbox = screen.getAllByRole('checkbox')[0];
+ fireEvent.click(checkbox);
+
+ // Verify Dropdown Toggle label update
+ expect(screen.getByRole('button', { name: '1 Badge Selected' })).toBeInTheDocument();
+
+ // Verify Chart & User details rendered
+ expect(screen.getByTestId('mock-pie-chart')).toBeInTheDocument();
+ expect(screen.getByText('Assigned Users')).toBeInTheDocument();
+ expect(screen.getByText('Alex Morgan')).toBeInTheDocument();
+ expect(screen.getByText('Taylor Swift')).toBeInTheDocument();
+ });
+
+ test('clears selected badges when "Clear Selected" button is clicked', () => {
+ render( );
+
+ // Select a badge
+ fireEvent.click(screen.getByRole('button', { name: 'Select Badges' }));
+ fireEvent.click(screen.getAllByRole('checkbox')[0]);
+
+ expect(screen.getByTestId('mock-pie-chart')).toBeInTheDocument();
+
+ // Click Clear Selected
+ fireEvent.click(screen.getByRole('button', { name: /clear selected/i }));
+
+ // Verify reset to empty state
+ expect(screen.getByText('Select badges to view assignment statistics')).toBeInTheDocument();
+ expect(screen.queryByTestId('mock-pie-chart')).not.toBeInTheDocument();
+ });
+
+ test('handles badges with no assigned users gracefully', () => {
+ render( );
+
+ // Open dropdown and select "Code Ninja" (empty users)
+ fireEvent.click(screen.getByRole('button', { name: 'Select Badges' }));
+ const emptyBadgeCheckbox = screen.getAllByRole('checkbox')[2];
+ fireEvent.click(emptyBadgeCheckbox);
+
+ expect(screen.getByText('No users assigned')).toBeInTheDocument();
+ });
+
+ test('disables additional checkboxes and shows warning when 10 badges are selected', () => {
+ // Generate 11 mock badges
+ const tenPlusBadges = Array.from({ length: 11 }, (_, i) => ({
+ _id: `badge-${i}`,
+ badgeName: `Badge ${i + 1}`,
+ users: [{ userId: { _id: `u${i}`, firstName: `User`, lastName: `${i}` } }],
+ }));
+
+ render( );
+
+ // Open dropdown
+ fireEvent.click(screen.getByRole('button', { name: 'Select Badges' }));
+
+ const checkboxes = screen.getAllByRole('checkbox');
+
+ // Select 10 badges
+ for (let i = 0; i < 10; i++) {
+ fireEvent.click(checkboxes[i]);
+ }
+
+ // Verify button toggle text pluralization
+ expect(screen.getByRole('button', { name: '10 Badges Selected' })).toBeInTheDocument();
+
+ // Verify 11th checkbox is disabled
+ expect(checkboxes[10]).toBeDisabled();
+
+ // Verify 10 badge limit warning banner appears
+ expect(
+ screen.getByText('You can only select up to 10 badges. Deselect one or more to continue.'),
+ ).toBeInTheDocument();
+ });
+
+ test('applies dark mode styles correctly when darkMode prop is true', () => {
+ render( );
+
+ // Select a badge to show dark mode containers
+ fireEvent.click(screen.getByRole('button', { name: 'Select Badges' }));
+ fireEvent.click(screen.getAllByRole('checkbox')[0]);
+
+ // Check header styling or container presence
+ expect(screen.getByText('Badge Assignment Distribution')).toBeInTheDocument();
+ expect(screen.getByText('Assigned Users')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/Collaboration/Collaboration.jsx b/src/components/Collaboration/Collaboration.jsx
index dd31682dfcc..86330f5e99e 100644
--- a/src/components/Collaboration/Collaboration.jsx
+++ b/src/components/Collaboration/Collaboration.jsx
@@ -185,9 +185,10 @@ function Collaboration() {
},
];
- // Find matching category
+ // Whole-word matching avoids false substring hits (e.g. 'it' inside 'Nutrition' or 'Writing')
+ const categoryWords = new Set(categoryLower.split(/\W+/).filter(Boolean));
for (const { keywords, url } of categoryImageMap) {
- if (keywords.some(keyword => categoryLower.includes(keyword))) {
+ if (keywords.some(keyword => categoryWords.has(keyword))) {
return url;
}
}
diff --git a/src/components/Collaboration/JobApplicationForm/JobApplicationForm.jsx b/src/components/Collaboration/JobApplicationForm/JobApplicationForm.jsx
index 589864fd14c..570e953edfc 100644
--- a/src/components/Collaboration/JobApplicationForm/JobApplicationForm.jsx
+++ b/src/components/Collaboration/JobApplicationForm/JobApplicationForm.jsx
@@ -10,10 +10,17 @@ import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { parsePhoneNumberFromString } from 'libphonenumber-js/max';
import moment from 'moment-timezone';
-import { isJobApplicationFileUploadQuestion } from './jobApplicationQuestionUtils';
+import TurnstileWidget from './TurnstileWidget';
+import { getTurnstileSiteKey } from './turnstile';
+import {
+ getUploadSizeError,
+ isJobApplicationFileUploadQuestion,
+} from './jobApplicationQuestionUtils';
+import { checkEmail } from '../../../utils/emailValidation';
import {
ensurePositionOption,
findFormByPositionSlug,
+ findPositionBySlug,
publicApplyRequestConfig,
titleHintFromSlug,
} from './jobApplicationPublicApi';
@@ -108,38 +115,113 @@ function findFormByDeveloperFamily(formsArr, jobTitle) {
return candidates[0];
}
-/** Match a job listing title to a saved application form (titles may differ slightly). */
-function findFormForJobTitle(formsArr, jobTitle) {
+/**
+ * Map roles like "Office Coordinator" and "Environmental Planner" to a Management/Administrative form
+ * when an exact match is not found. Does not treat bare "admin" as enough to include a form title
+ * (that incorrectly pulled in "ADMIN OF PR REVIEW TEAM AND FRONTEND TESTER").
+ */
+export function findFormByManagementFamily(formsArr, jobTitle) {
+ const j = normalizeTitleKey(jobTitle);
+ if (
+ !/\bcoordinator\b/.test(j) &&
+ !/\bplanner\b/.test(j) &&
+ !/\bmanager\b/.test(j) &&
+ !/\bmanagement\b/.test(j) &&
+ !/\badministrative\b/.test(j) &&
+ !/\badmin\b/.test(j)
+ ) {
+ return null;
+ }
+
+ const softwareLikeTitle = t =>
+ /\bfrontend\b/.test(t) ||
+ /\btester\b/.test(t) ||
+ /\breact\b/.test(t) ||
+ /\bjavascript\b/.test(t) ||
+ /\bpr review\b/.test(t) ||
+ /\breview team\b/.test(t);
+
+ const candidates = formsArr.filter(f => {
+ const t = normalizeTitleKey(f.title || '');
+ if (!t || softwareLikeTitle(t)) return false;
+ // Prefer real admin/management forms — bare "admin" alone matches PR-review software titles.
+ return /\bmanagement\b/.test(t) || /\bmanager\b/.test(t) || /\badministrative\b/.test(t);
+ });
+
+ if (!candidates.length) return null;
+ if (candidates.length === 1) return candidates[0];
+
+ const score = form => {
+ const t = normalizeTitleKey(form.title || '');
+ if (t.includes('administrative assistant')) return 400;
+ if (/\badministrative\b/.test(t)) return 300;
+ if (t === 'management' || t === 'job application management') return 200;
+ if (/\bmanagement\b/.test(t)) return 150;
+ if (/\bmanager\b/.test(t)) return 100;
+ return 0;
+ };
+
+ return [...candidates].sort((a, b) => {
+ const diff = score(b) - score(a);
+ if (diff !== 0) return diff;
+ return String(a.title || '').localeCompare(String(b.title || ''));
+ })[0];
+}
+
+/**
+ * Match a job listing title to a saved applicant-facing form (titles may differ slightly).
+ * Retired / internal forms are never matched. A fuzzy step that matches more than one form is
+ * treated as no match, so an ambiguous title (e.g. "T.A.S.T") falls back with a warning
+ * instead of a silent guess.
+ */
+export function findFormForJobTitle(formsArr, jobTitle, { includeManagementFamily = true } = {}) {
if (!jobTitle || !formsArr?.length) return null;
+ const candidates = formsArr.filter(isApplicantFacingForm);
+ if (!candidates.length) return null;
const t = String(jobTitle)
.trim()
.toLowerCase();
- let m = formsArr.find(f => (f.title || '').trim().toLowerCase() === t);
- if (m) return m;
- m = formsArr.find(f => {
+ const exact = candidates.find(f => (f.title || '').trim().toLowerCase() === t);
+ if (exact) return exact;
+ const key = normalizeTitleKey(jobTitle);
+ const normalized = key && candidates.find(f => normalizeTitleKey(f.title) === key);
+ if (normalized) return normalized;
+ const bySubstring = candidates.filter(f => {
const ft = (f.title || '').trim().toLowerCase();
return ft && (t.includes(ft) || ft.includes(t));
});
- if (m) return m;
- m = formsArr.find(f => titlesLikelyMatch(jobTitle, f.title));
- if (m) return m;
- m = findFormByTokenOverlap(formsArr, jobTitle);
- if (m) return m;
- m = findFormByDeveloperFamily(formsArr, jobTitle);
- return m || null;
+ if (bySubstring.length) return bySubstring.length === 1 ? bySubstring[0] : null;
+ const byLikely = candidates.filter(f => titlesLikelyMatch(jobTitle, f.title));
+ if (byLikely.length) return byLikely.length === 1 ? byLikely[0] : null;
+ const family =
+ findFormByTokenOverlap(candidates, jobTitle) ||
+ findFormByDeveloperFamily(candidates, jobTitle) ||
+ null;
+ if (family) return family;
+ if (!includeManagementFamily) return null;
+ return findFormByManagementFamily(candidates, jobTitle);
+}
+
+/** True when the only way this job title maps to the form is the management-family heuristic. */
+export function isManagementFamilySoftMatch(formsArr, jobTitle, matchedForm) {
+ if (!matchedForm || !jobTitle) return false;
+ const without = findFormForJobTitle(formsArr, jobTitle, { includeManagementFamily: false });
+ if (without) return false;
+ const management = findFormByManagementFamily(
+ (formsArr || []).filter(isApplicantFacingForm),
+ jobTitle,
+ );
+ if (!management) return false;
+ return String(management._id || management.id) === String(matchedForm._id || matchedForm.id);
}
-/** Role clicked on the job board: React Router state, ?jobTitle=, or /apply/:positionSlug. */
-function getJobTitleFromNavigation(location, positionSlug) {
+/** Role clicked on the job board: React Router state or ?jobTitle= (a bare slug is resolved later). */
+function getJobTitleFromNavigation(location) {
const fromState = location.state?.jobTitle;
if (fromState != null && String(fromState).trim()) return String(fromState).trim();
const q = new URLSearchParams(location.search || '');
const fromQuery = q.get('jobTitle') || q.get('role');
if (fromQuery != null && String(fromQuery).trim()) return String(fromQuery).trim();
- if (positionSlug) {
- const hint = titleHintFromSlug(positionSlug);
- if (hint) return hint;
- }
return '';
}
@@ -153,7 +235,7 @@ function getFormIdFromNavigation(location) {
}
/** Skip retired / internal ATS templates when picking applicant forms. */
-function isApplicantFacingForm(form) {
+export function isApplicantFacingForm(form) {
const title = String(form?.title || '')
.trim()
.toUpperCase();
@@ -165,7 +247,7 @@ function isApplicantFacingForm(form) {
}
/** Default questionnaire when a job listing has no dedicated form. */
-function findDefaultSoftwareForm(formsArr) {
+export function findDefaultSoftwareForm(formsArr) {
if (!formsArr?.length) return null;
const activeForms = formsArr.filter(isApplicantFacingForm);
const preferredTitles = ['SEEKING SOFTWARE POSITION', 'Software Developer', 'Software Engineer'];
@@ -188,7 +270,12 @@ function findDefaultSoftwareForm(formsArr) {
);
}
-function pickInitialForm(formsArr, navState, positionSlug) {
+/** Slug lookup restricted to applicant-facing forms. */
+export function findApplicantFormBySlug(formsArr, positionSlug) {
+ return findFormByPositionSlug((formsArr || []).filter(isApplicantFacingForm), positionSlug);
+}
+
+export function pickInitialForm(formsArr, navState, positionSlug) {
if (!formsArr?.length) return null;
if (navState?.formId) {
@@ -197,7 +284,7 @@ function pickInitialForm(formsArr, navState, positionSlug) {
}
if (positionSlug) {
- const bySlug = findFormByPositionSlug(formsArr, positionSlug);
+ const bySlug = findApplicantFormBySlug(formsArr, positionSlug);
if (bySlug) return bySlug;
}
@@ -219,20 +306,41 @@ function parseFormsResponse(res) {
return Array.isArray(res.data.forms) ? res.data.forms : [];
}
-function resolveNavigationJobTitle(jobDataFromRedirect, location, positionSlug) {
+function resolveNavigationJobTitle(jobDataFromRedirect, location) {
return (
(jobDataFromRedirect?.jobTitle && String(jobDataFromRedirect.jobTitle).trim()) ||
- getJobTitleFromNavigation(location, positionSlug)
+ getJobTitleFromNavigation(location)
);
}
-function notifyInitialFormSelection(navTitle, formMatch, chosen) {
- if (!navTitle || formMatch) return;
+function warnDefaultFormFallback(title, form) {
+ toast.warn(
+ `No form is linked to "${title}". Showing the default software engineer application ("${form.title}") — pick another role from the dropdown if this is not the right questionnaire.`,
+ { autoClose: false, closeOnClick: false, toastId: 'job-title-mismatch-warning' },
+ );
+}
+
+function warnManagementFamilyMatch(title, form) {
+ toast.warn(
+ `No exact form for "${title}". Showing "${form.title}" — pick another role from the dropdown if this is not the right questionnaire.`,
+ { autoClose: false, closeOnClick: false, toastId: 'job-title-management-family-warning' },
+ );
+}
+
+function notifyInitialFormSelection(
+ navTitle,
+ formMatch,
+ chosen,
+ managementFamilySoftMatch = false,
+) {
+ if (!navTitle) return;
+ if (managementFamilySoftMatch && formMatch) {
+ warnManagementFamilyMatch(navTitle, formMatch);
+ return;
+ }
+ if (formMatch) return;
if (chosen) {
- toast.warn(
- `No form is linked to "${navTitle}". Showing the default software engineer application ("${chosen.title}") — pick another role from the dropdown if this is not the right questionnaire.`,
- { autoClose: false, closeOnClick: false, toastId: 'job-title-mismatch-warning' },
- );
+ warnDefaultFormFallback(navTitle, chosen);
return;
}
toast.warn('No application form is available. Please contact support or try again later.', {
@@ -331,26 +439,52 @@ async function loadJobForms(formId) {
return parseFormsResponse(res);
}
-function buildInitialSelection({
+export function buildInitialSelection({
formsArr,
positions,
- navTitle,
+ navTitle: explicitTitle,
positionSlug,
routerLocation,
formId,
}) {
+ const baseOptions = positions.length
+ ? positions
+ : formsArr
+ .filter(isApplicantFacingForm)
+ .map(f => f.title)
+ .filter(Boolean);
+ const slugMatch = positionSlug ? findApplicantFormBySlug(formsArr, positionSlug) : null;
+ // A bare /apply/ link carries no title — map the slug back to the real position title.
+ const navTitle =
+ explicitTitle ||
+ (positionSlug
+ ? findPositionBySlug(baseOptions, positionSlug) ||
+ slugMatch?.title ||
+ titleHintFromSlug(positionSlug)
+ : '');
const navState = {
...routerLocation.state,
jobTitle: navTitle || routerLocation.state?.jobTitle,
formId: formId || routerLocation.state?.formId,
};
- const slugMatch = positionSlug ? findFormByPositionSlug(formsArr, positionSlug) : null;
const formMatch = slugMatch || (navTitle ? findFormForJobTitle(formsArr, navTitle) : null);
const chosen = pickInitialForm(formsArr, navState, positionSlug);
- notifyInitialFormSelection(navTitle, formMatch, chosen);
-
- const bannerTitle = navTitle || slugMatch?.title || chosen?.title || '';
- const baseOptions = positions.length ? positions : formsArr.map(f => f.title).filter(Boolean);
+ const managementFamilySoftMatch =
+ !slugMatch &&
+ Boolean(navTitle) &&
+ Boolean(formMatch) &&
+ isManagementFamilySoftMatch(formsArr, navTitle, formMatch);
+ notifyInitialFormSelection(navTitle, formMatch, chosen, managementFamilySoftMatch);
+
+ // Show a real position or form title, never the raw query / slug: near-miss titles select the
+ // matched position, and unmatched ones (e.g. "Applied through site", /apply/) select
+ // the default form they fell back to instead of adding a fake dropdown option.
+ const matchedTitle = formMatch ? formMatch.title : chosen?.title;
+ const bannerTitle =
+ matchPositionOption(baseOptions, navTitle) ||
+ matchPositionOption(baseOptions, matchedTitle) ||
+ matchedTitle ||
+ '';
const options = ensurePositionOption(baseOptions, bannerTitle);
const initial = getInitialFormState(chosen, bannerTitle);
const selected =
@@ -593,15 +727,7 @@ function validateName(name, label = 'Name') {
}
function validateEmail(email) {
- const trimmed = String(email || '').trim();
-
- if (!trimmed) return 'Email is required.';
-
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
- return 'Please enter a valid email address.';
- }
-
- return '';
+ return checkEmail(email).error;
}
function validatePhone(phone) {
@@ -761,7 +887,9 @@ function FileUploadField({
inputRef,
optional = false,
required = false,
+ error = '',
}) {
+ const errorId = `${id}-error`;
const statusText = file ? `${file.name} (${formatFileSize(file.size)})` : 'No file selected';
const statusClass = file ? styles.fileStatusSelected : styles.fileStatusEmpty;
@@ -792,6 +920,8 @@ function FileUploadField({
onChange={onChange}
className={styles.hiddenFileInput}
required={required && !file}
+ aria-invalid={Boolean(error)}
+ aria-describedby={error ? errorId : undefined}
/>
@@ -803,6 +933,11 @@ function FileUploadField({
)}
+ {error && (
+
+ {error}
+
+ )}
);
}
@@ -820,6 +955,7 @@ FileUploadField.propTypes = {
inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({ current: PropTypes.any })]),
optional: PropTypes.bool,
required: PropTypes.bool,
+ error: PropTypes.string,
};
FileUploadField.defaultProps = {
@@ -830,6 +966,7 @@ FileUploadField.defaultProps = {
inputRef: undefined,
optional: false,
required: false,
+ error: '',
};
function JobPositionSelect({ value, options, onChange }) {
@@ -942,8 +1079,14 @@ function JobApplicationForm() {
const [websiteSocial, setWebsiteSocial] = useState('');
const [resumeFile, setResumeFile] = useState(null);
const [questionFiles, setQuestionFiles] = useState({});
+ const [resumeFileError, setResumeFileError] = useState('');
+ const [questionFileErrors, setQuestionFileErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
+ const [hasAcceptedTerms, setHasAcceptedTerms] = useState(false);
+ const [termsError, setTermsError] = useState('');
+ const termsInputRef = useRef(null);
const resumeInputRef = useRef(null);
+ const emailInputRef = useRef(null);
const questionFileInputRefs = useRef({});
/** Shown in the page title — the role the user clicked, not only the matched DB form name. */
const [bannerJobTitle, setBannerJobTitle] = useState('');
@@ -955,6 +1098,13 @@ function JobApplicationForm() {
/** Owner/Admin: optional manual toggles on top of auto-calculated requirement flags. */
const [requirementPreviewOverrides, setRequirementPreviewOverrides] = useState({});
const [fieldErrors, setFieldErrors] = useState({});
+ const [emailSuggestion, setEmailSuggestion] = useState(null);
+ // Bot protection: time the form opened, optional Turnstile token, and a server-side submit error
+ const mountedAtRef = useRef(Date.now());
+ const turnstileRef = useRef(null);
+ const turnstileSiteKey = getTurnstileSiteKey();
+ const [turnstileToken, setTurnstileToken] = useState('');
+ const [submitError, setSubmitError] = useState('');
const locationTimezone = [applicantLocation.trim(), timeZone.trim()].filter(Boolean).join(' | ');
const darkMode = useSelector(state => state.theme?.darkMode);
@@ -1091,11 +1241,7 @@ function JobApplicationForm() {
if (cancelled) return;
setForms(formsArr);
- const navTitle = resolveNavigationJobTitle(
- jobDataFromRedirect,
- routerLocation,
- positionSlug,
- );
+ const navTitle = resolveNavigationJobTitle(jobDataFromRedirect, routerLocation);
const { options, initial, selected, bannerTitle } = buildInitialSelection({
formsArr,
positions,
@@ -1132,15 +1278,17 @@ function JobApplicationForm() {
useEffect(() => {
if (!selectedJob) return;
- const form =
- forms.find(f => f.title === selectedJob) ||
- findFormForJobTitle(forms, selectedJob) ||
- findDefaultSoftwareForm(forms);
+ let form = findFormForJobTitle(forms, selectedJob);
+ if (!form) {
+ form = findDefaultSoftwareForm(forms);
+ if (form) warnDefaultFormFallback(selectedJob, form);
+ }
if (!form) return;
setFilteredForm(form);
const qs = getVisibleQuestionsForForm(form);
setAnswers(initialAnswersForQuestions(qs));
setQuestionFiles({});
+ setQuestionFileErrors({});
questionFileInputRefs.current = {};
setFieldErrors({});
}, [selectedJob, forms]);
@@ -1162,6 +1310,7 @@ function JobApplicationForm() {
setFilteredForm(null);
setAnswers([]);
setQuestionFiles({});
+ setQuestionFileErrors({});
questionFileInputRefs.current = {};
setFieldErrors({});
return;
@@ -1183,22 +1332,26 @@ function JobApplicationForm() {
// Partial search (e.g. "CAD") should pick the nearest dropdown position.
const positionMatch = findNearestPositionOption(positionOptions, raw);
const lookupTitle = positionMatch || raw;
- let form = forms.find(f => f.title?.toLowerCase() === lookupTitle.toLowerCase());
- if (!form) form = findFormForJobTitle(forms, lookupTitle);
+ let form = findFormForJobTitle(forms, lookupTitle);
if (!form && lookupTitle !== raw) form = findFormForJobTitle(forms, raw);
const usedDefaultSoftware = !form;
if (!form) form = findDefaultSoftwareForm(forms);
if (positionMatch || form) {
- const selected = positionMatch || form.title;
- setSelectedJob(selected);
- setBannerJobTitle(selected);
- setJobTitleInput(selected);
if (usedDefaultSoftware && form) {
- toast.warn(
- `No form is linked to "${selected}". Showing the default software engineer application ("${form.title}") — pick another role from the dropdown if this is not the right questionnaire.`,
- { autoClose: false, closeOnClick: false, toastId: 'job-title-mismatch-warning' },
- );
+ // Load the default software form, but keep the typed query in the UI and toast.
+ setSelectedJob(form.title);
+ setBannerJobTitle(raw);
+ setJobTitleInput(raw);
+ warnDefaultFormFallback(raw, form);
+ } else {
+ const dropdownJob = positionMatch || form.title;
+ setSelectedJob(dropdownJob);
+ setBannerJobTitle(dropdownJob);
+ setJobTitleInput(dropdownJob);
+ if (form && isManagementFamilySoftMatch(forms, lookupTitle, form)) {
+ warnManagementFamilyMatch(raw, form);
+ }
}
} else {
toast.info('No position matches that search. Pick one from the dropdown.');
@@ -1290,22 +1443,49 @@ function JobApplicationForm() {
if (!allowedTypes.includes(f.type)) {
setResumeFile(null);
+ setResumeFileError('');
if (resumeInputRef.current) resumeInputRef.current.value = '';
toast.error('Invalid file type. Please upload PDF or DOC/DOCX.');
return;
}
+ const sizeError = getUploadSizeError(f);
+ setResumeFileError(sizeError);
+ if (sizeError) {
+ setResumeFile(null);
+ if (resumeInputRef.current) resumeInputRef.current.value = '';
+ return;
+ }
+
setResumeFile(f);
toast.success(`Resume selected: ${f.name}`);
};
const clearResumeFile = () => {
setResumeFile(null);
+ setResumeFileError('');
if (resumeInputRef.current) resumeInputRef.current.value = '';
};
+ const setQuestionFileError = (idx, message) => {
+ setQuestionFileErrors(prev => {
+ const next = { ...prev };
+ if (message) next[idx] = message;
+ else delete next[idx];
+ return next;
+ });
+ };
+
const handleQuestionFileChange = (idx, e) => {
const f = e.target.files?.[0] || null;
+ const sizeError = getUploadSizeError(f);
+ setQuestionFileError(idx, sizeError);
+ if (sizeError) {
+ const ref = questionFileInputRefs.current[idx];
+ if (ref) ref.value = '';
+ setQuestionFiles(prev => ({ ...prev, [idx]: null }));
+ return;
+ }
setQuestionFiles(prev => ({ ...prev, [idx]: f }));
};
@@ -1315,6 +1495,7 @@ function JobApplicationForm() {
delete next[idx];
return next;
});
+ setQuestionFileError(idx, '');
const ref = questionFileInputRefs.current[idx];
if (ref) ref.value = '';
};
@@ -1406,6 +1587,8 @@ function JobApplicationForm() {
};
const resetFormAfterSubmit = () => {
+ setHasAcceptedTerms(false);
+ setTermsError('');
setFirstName('');
setLastName('');
setApplicantEmail('');
@@ -1415,8 +1598,10 @@ function JobApplicationForm() {
setCompanyPosition('');
setWebsiteSocial('');
setResumeFile(null);
+ setResumeFileError('');
if (resumeInputRef.current) resumeInputRef.current.value = '';
setQuestionFiles({});
+ setQuestionFileErrors({});
questionFileInputRefs.current = {};
setFullTimeYears('');
setMonthsVolunteer('');
@@ -1425,17 +1610,50 @@ function JobApplicationForm() {
setAnswers(initialAnswersForQuestions(visibleQuestions));
setRequirementPreviewOverrides({});
setFieldErrors({});
+ setEmailSuggestion(null);
+ };
+
+ // Check the email when the applicant leaves the field (other fields still validate on submit)
+ const handleEmailBlur = () => {
+ if (!applicantEmail.trim()) return;
+ const { error, suggestion } = checkEmail(applicantEmail);
+ setEmailSuggestion(suggestion);
+ setFieldErrors(prev => {
+ const next = { ...prev };
+ if (error) next.applicantEmail = error;
+ else delete next.applicantEmail;
+ return next;
+ });
+ };
+
+ const applyEmailSuggestion = () => {
+ setApplicantEmail(emailSuggestion);
+ setEmailSuggestion(null);
+ // The suggestion button unmounts, so return focus to the field rather than the page.
+ emailInputRef.current?.focus();
+ setFieldErrors(prev => {
+ const next = { ...prev };
+ delete next.applicantEmail;
+ return next;
+ });
};
const handleSubmit = async e => {
e.preventDefault();
+ if (!hasAcceptedTerms) {
+ setTermsError('Please agree to the terms and conditions before submitting.');
+ termsInputRef.current?.focus();
+ return;
+ }
+
if (!filteredForm?._id) {
toast.error('No application form selected. Choose a role from the dropdown.');
return;
}
const { missing, profileErrors } = validateBeforeSubmit();
+ setEmailSuggestion(checkEmail(applicantEmail).suggestion);
setFieldErrors(prev => {
const next = { ...prev };
@@ -1466,6 +1684,7 @@ function JobApplicationForm() {
return;
}
+ setSubmitError('');
setIsSubmitting(true);
try {
const answersPayload = visibleQuestions.map((q, idx) => ({
@@ -1498,6 +1717,13 @@ function JobApplicationForm() {
}),
);
+ // Bot protection: honeypot stays empty for people, fillMs is how long the form was open
+ formData.append('website_url', '');
+ formData.append('fillMs', String(Math.max(0, Date.now() - mountedAtRef.current)));
+ if (turnstileSiteKey) {
+ formData.append('cf-turnstile-response', turnstileToken);
+ }
+
if (resumeFile) {
formData.append('resume', resumeFile);
}
@@ -1520,7 +1746,19 @@ function JobApplicationForm() {
resetFormAfterSubmit();
} catch (err) {
const apiMessage = err.response?.data?.message || err.response?.data?.error;
- if (err.response?.status === 409) {
+ const status = err.response?.status;
+ if (turnstileSiteKey) {
+ // A Turnstile token is single use, so every failed submit needs a fresh one
+ turnstileRef.current?.reset();
+ }
+ if (status === 429) {
+ setSubmitError(
+ err.response?.data?.error ||
+ 'Too many applications from this connection. Please try again later.',
+ );
+ } else if (status === 400 && err.response?.data?.error === VERIFICATION_FAILED_MESSAGE) {
+ setSubmitError(err.response.data.error);
+ } else if (status === 409) {
toast.error(apiMessage || 'You already applied for this position.', {
autoClose: 7000,
});
@@ -1622,7 +1860,7 @@ function JobApplicationForm() {
)}
-
@@ -1936,6 +2207,7 @@ function JobApplicationForm() {
inputRef={resumeInputRef}
optional={!resumeRequired}
required={resumeRequired}
+ error={resumeFileError}
/>
{visibleQuestions.map((q, idx) => {
@@ -2111,6 +2383,7 @@ function JobApplicationForm() {
questionFileInputRefs.current[idx] = el;
}}
required={req}
+ error={questionFileErrors[idx]}
/>
)}
{![
@@ -2138,7 +2411,77 @@ function JobApplicationForm() {
);
})}
-
+ {/* Honeypot: off-screen, unreachable by keyboard and hidden from screen readers.
+ Real people never fill it; bots that fill every input give themselves away. */}
+
+
+
+ {turnstileSiteKey && (
+
+ )}
+ {submitError && (
+
+ {submitError}
+
+ )}
+
+
+ {
+ setHasAcceptedTerms(e.target.checked);
+ setTermsError('');
+ }}
+ required
+ aria-invalid={Boolean(termsError)}
+ aria-describedby={termsError ? 'jaf-terms-error' : 'jaf-terms-hint'}
+ />
+
+ I agree to{' '}
+
+ terms and conditions
+
+
+ {' '}
+ *
+
+
+
+ {termsError ? (
+
+ {termsError}
+
+ ) : (
+
+ Required before submitting your application.
+
+ )}
+
+
{isSubmitting ? 'Submitting…' : 'Submit your application'}
@@ -2149,6 +2492,8 @@ function JobApplicationForm() {
);
}
+const VERIFICATION_FAILED_MESSAGE = 'Verification failed. Please try again.';
+
const requirementsPropType = PropTypes.shape({
reactExperience: PropTypes.bool,
twoMonthsCommitment: PropTypes.bool,
@@ -2158,7 +2503,14 @@ const requirementsPropType = PropTypes.shape({
});
const CheckIcon = () => (
-
+
({
+ reset() {
+ onTokenRef.current('');
+ if (widgetIdRef.current != null && window.turnstile) {
+ try {
+ window.turnstile.reset(widgetIdRef.current);
+ } catch {
+ // widget already gone; nothing to reset
+ }
+ }
+ },
+ }));
+
+ useEffect(() => {
+ let cancelled = false;
+ loadTurnstileScript()
+ .then(turnstile => {
+ if (cancelled || !containerRef.current) return;
+ widgetIdRef.current = turnstile.render(containerRef.current, {
+ sitekey: siteKey,
+ theme: darkMode ? 'dark' : 'light',
+ callback: token => onTokenRef.current(token),
+ 'expired-callback': () => onTokenRef.current(''),
+ 'error-callback': () => onTokenRef.current(''),
+ });
+ })
+ .catch(() => {
+ // Script blocked or offline: the server decides whether a missing token is acceptable.
+ });
+ return () => {
+ cancelled = true;
+ if (widgetIdRef.current != null && window.turnstile) {
+ try {
+ window.turnstile.remove(widgetIdRef.current);
+ } catch {
+ // ignore
+ }
+ }
+ widgetIdRef.current = null;
+ };
+ }, [siteKey, darkMode]);
+
+ return
;
+});
+
+TurnstileWidget.propTypes = {
+ siteKey: PropTypes.string.isRequired,
+ onToken: PropTypes.func.isRequired,
+ darkMode: PropTypes.bool,
+};
+
+TurnstileWidget.defaultProps = {
+ darkMode: false,
+};
+
+export default TurnstileWidget;
diff --git a/src/components/Collaboration/JobApplicationForm/jobApplicationPublicApi.js b/src/components/Collaboration/JobApplicationForm/jobApplicationPublicApi.js
index 9a5ffac7164..fd824410066 100644
--- a/src/components/Collaboration/JobApplicationForm/jobApplicationPublicApi.js
+++ b/src/components/Collaboration/JobApplicationForm/jobApplicationPublicApi.js
@@ -60,6 +60,15 @@ export function findFormByPositionSlug(formsArr, slug) {
return formsArr.find(f => slugifyPositionTitle(f.title) === target) || null;
}
+/** Canonical dropdown title for a URL slug (e.g. "t-a-s-t-hgn" → "T.A.S.T. - HGN"). */
+export function findPositionBySlug(options, slug) {
+ const target = String(slug || '')
+ .trim()
+ .toLowerCase();
+ if (!target || !options?.length) return '';
+ return options.find(o => slugifyPositionTitle(o) === target) || '';
+}
+
/** Decode slug into a rough job title hint for matching / banner display. */
export function titleHintFromSlug(slug) {
return String(slug || '')
diff --git a/src/components/Collaboration/JobApplicationForm/jobApplicationQuestionUtils.js b/src/components/Collaboration/JobApplicationForm/jobApplicationQuestionUtils.js
index bbf5ac18df0..5c23865330b 100644
--- a/src/components/Collaboration/JobApplicationForm/jobApplicationQuestionUtils.js
+++ b/src/components/Collaboration/JobApplicationForm/jobApplicationQuestionUtils.js
@@ -1,3 +1,12 @@
+/** Per-file upload cap; must match the server limit on POST /jobforms/:formId/responses. */
+export const MAX_UPLOAD_BYTES = 5 * 1024 * 1024;
+
+/** Returns a readable error for a file over the upload cap, or '' when it is fine. */
+export function getUploadSizeError(file) {
+ if (file && file.size > MAX_UPLOAD_BYTES) return 'File too large, max 5 MB.';
+ return '';
+}
+
/** Detect file-upload prompts without treating "document" used as a verb as an upload. */
export function isJobApplicationFileUploadQuestion(question) {
const label = String(question?.label || question?.questionText || '').toLowerCase();
diff --git a/src/components/Collaboration/JobApplicationForm/turnstile.js b/src/components/Collaboration/JobApplicationForm/turnstile.js
new file mode 100644
index 00000000000..9f91f136ab2
--- /dev/null
+++ b/src/components/Collaboration/JobApplicationForm/turnstile.js
@@ -0,0 +1,48 @@
+/**
+ * Optional Cloudflare Turnstile support for the public job application form.
+ * Everything here is inert unless VITE_TURNSTILE_SITE_KEY is set at build time.
+ */
+
+export const TURNSTILE_SCRIPT_URL =
+ 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
+
+/** Site key from the build environment, or '' when Turnstile is not configured. */
+export function getTurnstileSiteKey() {
+ return String(import.meta.env?.VITE_TURNSTILE_SITE_KEY || '').trim();
+}
+
+let scriptPromise = null;
+
+/** Load the Turnstile script once; resolves with window.turnstile. */
+export function loadTurnstileScript() {
+ if (typeof window === 'undefined') return Promise.reject(new Error('No window'));
+ if (window.turnstile) return Promise.resolve(window.turnstile);
+ if (scriptPromise) return scriptPromise;
+
+ scriptPromise = new Promise((resolve, reject) => {
+ const script = document.createElement('script');
+ script.src = TURNSTILE_SCRIPT_URL;
+ script.async = true;
+ script.defer = true;
+ script.onload = () => {
+ if (window.turnstile) {
+ resolve(window.turnstile);
+ } else {
+ scriptPromise = null;
+ reject(new Error('Turnstile did not initialise'));
+ }
+ };
+ script.onerror = () => {
+ scriptPromise = null; // allow a retry on the next mount
+ script.remove();
+ reject(new Error('Turnstile script failed to load'));
+ };
+ document.head.appendChild(script);
+ });
+ return scriptPromise;
+}
+
+/** Test helper: forget the cached script promise. */
+export function resetTurnstileLoaderForTests() {
+ scriptPromise = null;
+}
diff --git a/src/components/Collaboration/__tests__/JobApplicationForm.botProtection.test.jsx b/src/components/Collaboration/__tests__/JobApplicationForm.botProtection.test.jsx
new file mode 100644
index 00000000000..4f6790841f8
--- /dev/null
+++ b/src/components/Collaboration/__tests__/JobApplicationForm.botProtection.test.jsx
@@ -0,0 +1,281 @@
+/* eslint-disable testing-library/no-node-access -- the honeypot, script tag and form fields are checked by id/selector on purpose */
+import React from 'react';
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
+import { Provider } from 'react-redux';
+import { MemoryRouter } from 'react-router-dom';
+import axios from 'axios';
+import JobApplicationForm from '../JobApplicationForm/JobApplicationForm';
+import { resetTurnstileLoaderForTests } from '../JobApplicationForm/turnstile';
+
+vi.mock('axios');
+vi.mock('react-toastify', () => ({
+ toast: { error: vi.fn(), success: vi.fn(), info: vi.fn(), warn: vi.fn() },
+}));
+
+const mockStore = {
+ getState: () => ({ theme: { darkMode: false }, auth: { user: {} } }),
+ subscribe: () => () => {},
+ dispatch: vi.fn(),
+};
+
+const FORM = { _id: '507f1f77bcf86cd799439011', title: 'Software Engineer', questions: [] };
+
+const renderForm = () =>
+ render(
+
+
+
+
+ ,
+ );
+
+const fillProfile = async () => {
+ await screen.findByLabelText(/^First Name/i);
+ fireEvent.change(screen.getByLabelText(/^First Name/i), { target: { value: 'Ada' } });
+ fireEvent.change(screen.getByLabelText(/^Last Name/i), { target: { value: 'Lovelace' } });
+ fireEvent.change(screen.getByLabelText(/^Email/i), { target: { value: 'ada@example.com' } });
+ fireEvent.change(document.getElementById('jaf-location'), { target: { value: 'London, UK' } });
+ fireEvent.change(document.getElementById('jaf-timezone'), { target: { value: 'GMT' } });
+ fireEvent.change(document.getElementById('jaf-phone'), { target: { value: '+442071838750' } });
+};
+
+const fillAndSubmit = async () => {
+ await fillProfile();
+ fireEvent.click(screen.getByRole('checkbox', { name: /I agree to terms and conditions/i }));
+ fireEvent.click(screen.getByRole('button', { name: /submit your application/i }));
+};
+
+const submittedFormData = () => axios.post.mock.calls[0][1];
+
+describe('JobApplicationForm bot protection', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ axios.get.mockImplementation(url =>
+ Promise.resolve(
+ String(url).includes('positions')
+ ? { data: { positions: [] } }
+ : { data: { form: FORM, forms: [FORM] } },
+ ),
+ );
+ axios.post.mockResolvedValue({ data: {} });
+ });
+
+ afterEach(() => {
+ vi.unstubAllEnvs();
+ vi.restoreAllMocks();
+ delete window.turnstile;
+ document.querySelectorAll('script[src*="challenges.cloudflare.com"]').forEach(s => s.remove());
+ resetTurnstileLoaderForTests();
+ });
+
+ describe('honeypot', () => {
+ it('exists but is not reachable by keyboard or screen readers', () => {
+ renderForm();
+ const field = screen.getByTestId('jaf-honeypot');
+
+ expect(field).toHaveAttribute('name', 'website_url');
+ expect(field).toHaveAttribute('tabindex', '-1');
+ expect(field).toHaveAttribute('autocomplete', 'off');
+ expect(field.closest('[aria-hidden="true"]')).not.toBeNull();
+ expect(field).toHaveValue('');
+ // No accessible name and not exposed in the accessibility tree
+ expect(screen.getAllByRole('textbox')).not.toContain(field);
+ // Off-screen via a CSS-module class, not display:none
+ expect(field.parentElement.className).toMatch(/honeypot/);
+ });
+
+ it('is sent empty with the submit', async () => {
+ renderForm();
+ await fillAndSubmit();
+ await waitFor(() => expect(axios.post).toHaveBeenCalledTimes(1));
+
+ expect(submittedFormData().get('website_url')).toBe('');
+ });
+ });
+
+ describe('fillMs', () => {
+ it('is sent as a number of milliseconds since the form opened', async () => {
+ const nowSpy = vi.spyOn(Date, 'now').mockReturnValue(1_000_000);
+ renderForm();
+ await screen.findByLabelText(/^First Name/i);
+ nowSpy.mockReturnValue(1_005_500);
+ await fillAndSubmit();
+ await waitFor(() => expect(axios.post).toHaveBeenCalledTimes(1));
+
+ const raw = submittedFormData().get('fillMs');
+ expect(Number.isFinite(Number(raw))).toBe(true);
+ expect(Number(raw)).toBe(5500);
+ });
+ });
+
+ describe('Turnstile', () => {
+ it('adds no widget, no token and no script without a site key', async () => {
+ vi.stubEnv('VITE_TURNSTILE_SITE_KEY', '');
+ renderForm();
+ await fillAndSubmit();
+ await waitFor(() => expect(axios.post).toHaveBeenCalledTimes(1));
+
+ expect(screen.queryByTestId('turnstile-widget')).not.toBeInTheDocument();
+ expect(document.querySelector('script[src*="challenges.cloudflare.com"]')).toBeNull();
+ expect(submittedFormData().has('cf-turnstile-response')).toBe(false);
+ });
+
+ it('renders the widget and sends its token when a site key is set', async () => {
+ vi.stubEnv('VITE_TURNSTILE_SITE_KEY', 'test-site-key');
+ let options;
+ window.turnstile = {
+ render: vi.fn((el, opts) => {
+ options = opts;
+ return 'widget-1';
+ }),
+ reset: vi.fn(),
+ remove: vi.fn(),
+ };
+ renderForm();
+
+ expect(screen.getByTestId('turnstile-widget')).toBeInTheDocument();
+ await waitFor(() => expect(window.turnstile.render).toHaveBeenCalledTimes(1));
+ expect(options.sitekey).toBe('test-site-key');
+
+ options.callback('token-abc');
+ await fillAndSubmit();
+ await waitFor(() => expect(axios.post).toHaveBeenCalledTimes(1));
+
+ expect(submittedFormData().get('cf-turnstile-response')).toBe('token-abc');
+ await waitFor(() =>
+ expect(
+ screen.getByRole('checkbox', { name: /I agree to terms and conditions/i }),
+ ).not.toBeChecked(),
+ );
+ expect(screen.getByRole('button', { name: /submit your application/i })).toBeDisabled();
+ });
+
+ it('requires consent even after verification succeeds', async () => {
+ vi.stubEnv('VITE_TURNSTILE_SITE_KEY', 'test-site-key');
+ let options;
+ window.turnstile = {
+ render: vi.fn((el, opts) => {
+ options = opts;
+ return 'widget-1';
+ }),
+ reset: vi.fn(),
+ remove: vi.fn(),
+ };
+ renderForm();
+ await waitFor(() => expect(window.turnstile.render).toHaveBeenCalledTimes(1));
+ options.callback('token-abc');
+ await fillProfile();
+
+ const checkbox = screen.getByRole('checkbox', { name: /I agree to terms and conditions/i });
+ expect(screen.getByRole('button', { name: /submit your application/i })).toBeDisabled();
+ fireEvent.submit(screen.getByRole('form', { name: 'Job application' }));
+ expect(axios.post).not.toHaveBeenCalled();
+ expect(checkbox).toHaveFocus();
+ expect(screen.getByRole('alert')).toHaveTextContent(
+ 'Please agree to the terms and conditions',
+ );
+
+ fireEvent.click(checkbox);
+ fireEvent.click(screen.getByRole('button', { name: /submit your application/i }));
+ await waitFor(() => expect(axios.post).toHaveBeenCalledTimes(1));
+ expect(submittedFormData().get('cf-turnstile-response')).toBe('token-abc');
+ });
+
+ it('loads the script once when window.turnstile is not there yet', async () => {
+ vi.stubEnv('VITE_TURNSTILE_SITE_KEY', 'test-site-key');
+ renderForm();
+
+ await waitFor(() =>
+ expect(
+ document.querySelectorAll(
+ 'script[src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"]',
+ ),
+ ).toHaveLength(1),
+ );
+ });
+
+ it('resets the widget after a failed verification and shows the server text', async () => {
+ vi.stubEnv('VITE_TURNSTILE_SITE_KEY', 'test-site-key');
+ let options;
+ window.turnstile = {
+ render: vi.fn((el, opts) => {
+ options = opts;
+ return 'widget-1';
+ }),
+ reset: vi.fn(),
+ remove: vi.fn(),
+ };
+ axios.post.mockRejectedValueOnce({
+ response: { status: 400, data: { error: 'Verification failed. Please try again.' } },
+ });
+ renderForm();
+ await waitFor(() => expect(window.turnstile.render).toHaveBeenCalled());
+ await fillAndSubmit();
+
+ const alert = await screen.findByRole('alert');
+ expect(alert).toHaveTextContent('Verification failed. Please try again.');
+ expect(window.turnstile.reset).toHaveBeenCalledWith('widget-1');
+ expect(
+ screen.getByRole('checkbox', { name: /I agree to terms and conditions/i }),
+ ).toBeChecked();
+ expect(screen.getByLabelText(/^First Name/i)).toHaveValue('Ada');
+
+ options.callback('retry-token');
+ fireEvent.click(screen.getByRole('button', { name: /submit your application/i }));
+ await waitFor(() => expect(axios.post).toHaveBeenCalledTimes(2));
+ expect(axios.post.mock.calls[1][1].get('cf-turnstile-response')).toBe('retry-token');
+ await waitFor(() => expect(screen.queryByRole('alert')).not.toBeInTheDocument());
+ });
+ });
+
+ describe('rate limit', () => {
+ it('shows the server message on a 429', async () => {
+ axios.post.mockRejectedValue({
+ response: {
+ status: 429,
+ headers: { 'retry-after': '120' },
+ data: { error: 'Too many applications for this email address. Please try again later.' },
+ },
+ });
+ renderForm();
+ await fillAndSubmit();
+
+ const alert = await screen.findByRole('alert');
+ expect(alert).toHaveTextContent(
+ 'Too many applications for this email address. Please try again later.',
+ );
+ expect(alert.className).toMatch(/fieldError/);
+ });
+
+ it('falls back to a safe message when a 429 has no body', async () => {
+ axios.post.mockRejectedValue({ response: { status: 429, data: {} } });
+ renderForm();
+ await fillAndSubmit();
+
+ expect(await screen.findByRole('alert')).toHaveTextContent(
+ 'Too many applications from this connection. Please try again later.',
+ );
+ });
+
+ it('clears the message when the applicant submits again', async () => {
+ axios.post.mockRejectedValueOnce({
+ response: { status: 429, data: { error: 'Slow down.' } },
+ });
+ renderForm();
+ await fillAndSubmit();
+ await screen.findByRole('alert');
+
+ fireEvent.click(screen.getByRole('button', { name: /submit your application/i }));
+ await waitFor(() => expect(axios.post).toHaveBeenCalledTimes(2));
+ await waitFor(() => expect(screen.queryByRole('alert')).not.toBeInTheDocument());
+ });
+ });
+});
diff --git a/src/components/Collaboration/__tests__/JobApplicationForm.duplicateSubmit.test.jsx b/src/components/Collaboration/__tests__/JobApplicationForm.duplicateSubmit.test.jsx
new file mode 100644
index 00000000000..e904af58b13
--- /dev/null
+++ b/src/components/Collaboration/__tests__/JobApplicationForm.duplicateSubmit.test.jsx
@@ -0,0 +1,103 @@
+import React from 'react';
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { Provider } from 'react-redux';
+import { MemoryRouter } from 'react-router-dom';
+import axios from 'axios';
+import { toast } from 'react-toastify';
+import JobApplicationForm from '../JobApplicationForm/JobApplicationForm';
+
+vi.mock('axios');
+vi.mock('react-toastify', () => ({
+ toast: { success: vi.fn(), error: vi.fn(), warn: vi.fn(), info: vi.fn() },
+}));
+
+const mockStore = {
+ getState: () => ({ theme: { darkMode: false }, auth: { user: {} } }),
+ subscribe: () => () => {},
+ dispatch: vi.fn(),
+};
+
+const JOB_FORM = { _id: 'form-1', title: 'Test Role', questions: [] };
+
+const renderForm = () =>
+ render(
+
+
+
+
+ ,
+ );
+
+const fillValidApplication = async () => {
+ // Wait for the form list to load and the role to be selected.
+ await screen.findByLabelText(/^Email/);
+ await waitFor(() => expect(axios.get).toHaveBeenCalled());
+ fireEvent.change(screen.getByLabelText(/^First Name/), { target: { value: 'Ada' } });
+ fireEvent.change(screen.getByLabelText(/^Last Name/), { target: { value: 'Lovelace' } });
+ fireEvent.change(screen.getByLabelText(/^Email/), { target: { value: 'ada@example.com' } });
+ fireEvent.change(screen.getByLabelText(/^Location/), { target: { value: 'Charlotte, NC' } });
+ fireEvent.change(screen.getByLabelText(/^Time Zone/), { target: { value: 'America/New_York' } });
+ fireEvent.change(screen.getByLabelText(/^Phone/), { target: { value: '+1 213-456-7890' } });
+ fireEvent.click(screen.getByRole('checkbox', { name: /I agree to terms and conditions/i }));
+};
+
+const submit = async () => {
+ const button = await screen.findByRole('button', { name: 'Submit your application' });
+ fireEvent.click(button);
+};
+
+describe('JobApplicationForm duplicate application handling', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ axios.get.mockImplementation(url =>
+ Promise.resolve(
+ String(url).includes('positions')
+ ? { data: { positions: [] } }
+ : { data: { forms: [JOB_FORM] } },
+ ),
+ );
+ });
+
+ it('shows the success toast and resets the form when the server answers 201', async () => {
+ axios.post.mockResolvedValue({
+ status: 201,
+ data: { message: 'Application submitted successfully.' },
+ });
+ renderForm();
+ await fillValidApplication();
+ await submit();
+
+ await waitFor(() =>
+ expect(toast.success).toHaveBeenCalledWith(
+ 'Application submitted successfully. A confirmation email will be sent.',
+ ),
+ );
+ expect(axios.post).toHaveBeenCalledTimes(1);
+ expect(screen.getByLabelText(/^Email/)).toHaveValue('');
+ expect(screen.getByLabelText(/^First Name/)).toHaveValue('');
+ expect(toast.error).not.toHaveBeenCalled();
+ });
+
+ it('routes a 409 through the generic error path and never shows the old duplicate text', async () => {
+ axios.post.mockRejectedValue({
+ response: { status: 409, data: { error: 'Application already submitted.' } },
+ });
+ renderForm();
+ await fillValidApplication();
+ await submit();
+
+ await waitFor(() =>
+ expect(toast.error).toHaveBeenCalledWith('Application already submitted.', {
+ autoClose: 7000,
+ }),
+ );
+ const shown = [...toast.error.mock.calls, ...toast.success.mock.calls, ...toast.warn.mock.calls]
+ .map(call => String(call[0]))
+ .join('\n');
+ expect(shown).not.toContain('You already applied for this position.');
+ expect(toast.success).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/components/Collaboration/__tests__/JobApplicationForm.emailValidation.test.jsx b/src/components/Collaboration/__tests__/JobApplicationForm.emailValidation.test.jsx
new file mode 100644
index 00000000000..f2647b53b53
--- /dev/null
+++ b/src/components/Collaboration/__tests__/JobApplicationForm.emailValidation.test.jsx
@@ -0,0 +1,85 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { Provider } from 'react-redux';
+import { MemoryRouter } from 'react-router-dom';
+import axios from 'axios';
+import JobApplicationForm from '../JobApplicationForm/JobApplicationForm';
+
+vi.mock('axios');
+
+const mockStore = {
+ getState: () => ({ theme: { darkMode: false }, auth: { user: {} } }),
+ subscribe: () => () => {},
+ dispatch: vi.fn(),
+};
+
+const renderForm = () =>
+ render(
+
+
+
+
+ ,
+ );
+
+const typeEmailAndBlur = value => {
+ const input = screen.getByLabelText(/^Email/);
+ fireEvent.change(input, { target: { value } });
+ fireEvent.blur(input);
+ return input;
+};
+
+describe('JobApplicationForm email field', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ axios.get.mockResolvedValue({ data: [] });
+ });
+
+ it('shows an error on blur when the domain ending does not exist', () => {
+ renderForm();
+ const input = typeEmailAndBlur('rabstu@gmail.con');
+
+ // The hint below carries the fix, so the error doesn't repeat "check for a typo"
+ expect(screen.getByRole('alert').textContent).toBe(
+ '".con" isn\'t a valid email domain ending.',
+ );
+ expect(screen.getByText(/Did you mean/)).toHaveTextContent('Did you mean rabstu@gmail.com?');
+ expect(input).toHaveAttribute('aria-invalid', 'true');
+ expect(input).toHaveAttribute('maxLength', '254');
+ });
+
+ it('offers a one-click fix for a likely typo without blocking', () => {
+ renderForm();
+ const input = typeEmailAndBlur('ada@gmial.com');
+
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
+ // The address is plain text; the fix is an explicit, labelled button.
+ expect(screen.getByText('ada@gmail.com').tagName).toBe('STRONG');
+ fireEvent.click(screen.getByRole('button', { name: 'Use this email: ada@gmail.com' }));
+
+ expect(input).toHaveValue('ada@gmail.com');
+ expect(screen.queryByText(/Did you mean/)).not.toBeInTheDocument();
+ // The suggestion button disappears, so focus goes back to the field instead of the page.
+ expect(input).toHaveFocus();
+ });
+
+ it('clears the error and suggestion as soon as the applicant edits the email', () => {
+ renderForm();
+ const input = typeEmailAndBlur('rabstu@gmail.con');
+
+ fireEvent.change(input, { target: { value: 'rabstu@gmail.co' } });
+
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
+ expect(screen.queryByText(/Did you mean/)).not.toBeInTheDocument();
+ expect(input).toHaveAttribute('aria-invalid', 'false');
+ });
+
+ it('stays quiet on blur for a valid address', () => {
+ renderForm();
+ typeEmailAndBlur('ada@example.com');
+
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
+ expect(screen.queryByText(/Did you mean/)).not.toBeInTheDocument();
+ });
+});
diff --git a/src/components/Collaboration/__tests__/JobApplicationForm.managementFamily.test.js b/src/components/Collaboration/__tests__/JobApplicationForm.managementFamily.test.js
new file mode 100644
index 00000000000..e09fb3cdfcc
--- /dev/null
+++ b/src/components/Collaboration/__tests__/JobApplicationForm.managementFamily.test.js
@@ -0,0 +1,76 @@
+import { describe, it, expect } from 'vitest';
+import {
+ findFormByManagementFamily,
+ findFormForJobTitle,
+ isManagementFamilySoftMatch,
+} from '../JobApplicationForm/JobApplicationForm';
+
+const FORMS = [
+ {
+ _id: 'admin-pr-review',
+ title: 'ADMIN OF PR REVIEW TEAM AND FRONTEND TESTER',
+ questions: [{ questionText: 'React experience?' }],
+ },
+ {
+ _id: 'admin-assistant',
+ title: 'Administrative Assistant',
+ questions: [{ questionText: 'Office tools?' }],
+ },
+ {
+ _id: 'software',
+ title: 'SEEKING SOFTWARE POSITION',
+ questions: [{ questionText: 'Years of JS?' }],
+ },
+ {
+ _id: 'retired-admin',
+ title: '[RETIRED] APPLIED THROUGH SITE - ADMINISTRATIVE',
+ questions: [],
+ },
+];
+
+describe('findFormByManagementFamily', () => {
+ it('maps Office Coordinator to Administrative Assistant, not the PR-review form', () => {
+ const form = findFormByManagementFamily(FORMS, 'Office Coordinator');
+ expect(form?._id).toBe('admin-assistant');
+ expect(form?.title).toBe('Administrative Assistant');
+ });
+
+ it('maps Environmental Planner to Administrative Assistant, not the PR-review form', () => {
+ const form = findFormByManagementFamily(FORMS, 'Environmental Planner');
+ expect(form?._id).toBe('admin-assistant');
+ });
+
+ it('does not treat ADMIN OF PR REVIEW as a management-family candidate', () => {
+ const onlyPrReview = FORMS.filter(f => f._id === 'admin-pr-review');
+ expect(findFormByManagementFamily(onlyPrReview, 'Office Coordinator')).toBeNull();
+ });
+
+ it('returns null for unrelated software job titles', () => {
+ expect(findFormByManagementFamily(FORMS, 'Software Developer')).toBeNull();
+ });
+});
+
+describe('findFormForJobTitle + soft match', () => {
+ it('resolves Office Coordinator through management family to Administrative Assistant', () => {
+ const form = findFormForJobTitle(FORMS, 'Office Coordinator');
+ expect(form?._id).toBe('admin-assistant');
+ expect(isManagementFamilySoftMatch(FORMS, 'Office Coordinator', form)).toBe(true);
+ });
+
+ it('resolves Environmental Planner through management family to Administrative Assistant', () => {
+ const form = findFormForJobTitle(FORMS, 'Environmental Planner');
+ expect(form?._id).toBe('admin-assistant');
+ expect(isManagementFamilySoftMatch(FORMS, 'Environmental Planner', form)).toBe(true);
+ });
+
+ it('keeps exact software title matching', () => {
+ const form = findFormForJobTitle(FORMS, 'SEEKING SOFTWARE POSITION');
+ expect(form?._id).toBe('software');
+ expect(isManagementFamilySoftMatch(FORMS, 'SEEKING SOFTWARE POSITION', form)).toBe(false);
+ });
+
+ it('never matches retired applied-through-site forms', () => {
+ const form = findFormForJobTitle(FORMS, 'Administrative Assistant');
+ expect(form?._id).toBe('admin-assistant');
+ });
+});
diff --git a/src/components/Collaboration/__tests__/JobApplicationForm.terms.test.jsx b/src/components/Collaboration/__tests__/JobApplicationForm.terms.test.jsx
new file mode 100644
index 00000000000..93d231175a6
--- /dev/null
+++ b/src/components/Collaboration/__tests__/JobApplicationForm.terms.test.jsx
@@ -0,0 +1,212 @@
+import React from 'react';
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import { MemoryRouter } from 'react-router-dom';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import axios from 'axios';
+import { toast } from 'react-toastify';
+import JobApplicationForm from '../JobApplicationForm/JobApplicationForm';
+import { ENDPOINTS } from '../../../utils/URL';
+
+vi.mock('axios', () => ({ default: { get: vi.fn(), post: vi.fn() } }));
+vi.mock('react-toastify', () => ({
+ toast: { error: vi.fn(), success: vi.fn(), info: vi.fn(), warn: vi.fn() },
+}));
+
+const formId = '507f1f77bcf86cd799439011';
+const form = { _id: formId, title: 'Software Engineer', questions: [] };
+
+async function mountApplication(darkMode = false) {
+ const store = {
+ getState: () => ({ theme: { darkMode }, auth: { user: null } }),
+ subscribe: () => () => {},
+ dispatch: vi.fn(),
+ };
+ render(
+
+
+
+
+ ,
+ );
+ await waitFor(() =>
+ expect(axios.get).toHaveBeenCalledWith(ENDPOINTS.GET_ALL_JOB_FORMS, expect.any(Object)),
+ );
+ fireEvent.click(await screen.findByRole('button', { name: 'Select a position' }));
+ fireEvent.click(await screen.findByRole('button', { name: 'Software Engineer' }));
+ await waitFor(() =>
+ expect(screen.getByRole('button', { name: 'Select a position' })).toHaveTextContent(
+ 'Software Engineer',
+ ),
+ );
+}
+
+function fillProfile() {
+ fireEvent.change(screen.getByRole('textbox', { name: /First Name/i }), {
+ target: { value: 'Fixture' },
+ });
+ fireEvent.change(screen.getByRole('textbox', { name: /Last Name/i }), {
+ target: { value: 'Applicant' },
+ });
+ fireEvent.change(screen.getByRole('textbox', { name: /^Email/i }), {
+ target: { value: 'fixture@example.com' },
+ });
+ fireEvent.change(screen.getByRole('textbox', { name: /^Location/i }), {
+ target: { value: 'Los Angeles, United States' },
+ });
+ fireEvent.change(screen.getByRole('combobox', { name: /^Time Zone/i }), {
+ target: { value: 'America/Los_Angeles' },
+ });
+ fireEvent.change(screen.getByRole('textbox', { name: /^Phone/i }), {
+ target: { value: '+12133734253' },
+ });
+}
+
+beforeEach(() => {
+ vi.clearAllMocks();
+ localStorage.clear();
+ axios.get.mockImplementation(async url => {
+ if (url === ENDPOINTS.GET_JOB_POSITIONS) return { data: { positions: ['Software Engineer'] } };
+ if (url === ENDPOINTS.GET_ALL_JOB_FORMS) return { data: { forms: [form] } };
+ throw new Error(`Unexpected read: ${url}`);
+ });
+ axios.post.mockResolvedValue({ data: {} });
+});
+
+describe('job application terms acknowledgement', () => {
+ it.each([false, true])('starts unchecked and required in darkMode=%s', async darkMode => {
+ await mountApplication(darkMode);
+ const checkbox = screen.getByRole('checkbox', { name: /I agree to terms and conditions/i });
+ expect(checkbox).not.toBeChecked();
+ expect(checkbox).toBeRequired();
+ expect(screen.getByRole('button', { name: 'Submit your application' })).toBeDisabled();
+ const link = screen.getByRole('link', { name: 'terms and conditions' });
+ expect(link).toHaveAttribute('href', 'https://onecommunityglobal.org/terms-and-conditions/');
+ expect(link).toHaveAttribute('target', '_blank');
+ expect(link).toHaveAttribute('rel', 'noopener noreferrer');
+ expect(screen.getByRole('form', { name: 'Job application' })).toContainElement(link);
+ expect(
+ checkbox.compareDocumentPosition(
+ screen.getByRole('button', { name: 'Submit your application' }),
+ ) & Node.DOCUMENT_POSITION_FOLLOWING,
+ ).toBeTruthy();
+ });
+
+ it('enables submission only while the applicant agrees', async () => {
+ await mountApplication();
+ const checkbox = screen.getByRole('checkbox', { name: /I agree to terms and conditions/i });
+ fireEvent.click(checkbox);
+ expect(screen.getByRole('button', { name: 'Submit your application' })).toBeEnabled();
+ fireEvent.click(checkbox);
+ expect(screen.getByRole('button', { name: 'Submit your application' })).toBeDisabled();
+ expect(axios.post).not.toHaveBeenCalled();
+ });
+
+ it('also blocks a direct submit event, focuses the checkbox and explains the error', async () => {
+ await mountApplication();
+ const checkbox = screen.getByRole('checkbox', { name: /I agree to terms and conditions/i });
+ fillProfile();
+ fireEvent.submit(screen.getByRole('form', { name: 'Job application' }));
+ expect(axios.post).not.toHaveBeenCalled();
+ expect(checkbox).toHaveFocus();
+ expect(checkbox).toHaveAttribute('aria-invalid', 'true');
+ expect(screen.getByRole('alert')).toHaveTextContent('Please agree to the terms and conditions');
+ fireEvent.click(checkbox);
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
+ expect(checkbox).toHaveAttribute('aria-invalid', 'false');
+ });
+
+ it('keeps existing required-profile validation after agreement', async () => {
+ await mountApplication();
+ const checkbox = screen.getByRole('checkbox', { name: /I agree to terms and conditions/i });
+ fireEvent.click(checkbox);
+ fireEvent.submit(screen.getByRole('form', { name: 'Job application' }));
+ expect(toast.error).toHaveBeenCalled();
+ expect(axios.post).not.toHaveBeenCalled();
+ expect(checkbox).toBeChecked();
+ });
+
+ it('submits valid agreed data through the existing mocked API and resets agreement', async () => {
+ await mountApplication();
+ const checkbox = screen.getByRole('checkbox', { name: /I agree to terms and conditions/i });
+ fillProfile();
+ fireEvent.click(checkbox);
+ fireEvent.submit(screen.getByRole('form', { name: 'Job application' }));
+ await waitFor(() => expect(axios.post).toHaveBeenCalledTimes(1));
+ const [url, body, config] = axios.post.mock.calls[0];
+ expect(url).toBe(ENDPOINTS.SUBMIT_JOB_APPLICATION(formId));
+ expect(body.get('respondent')).toBe('Fixture Applicant');
+ expect(body.get('email')).toBe('fixture@example.com');
+ expect(JSON.parse(body.get('payload')).profile.jobTitle).toBe('Software Engineer');
+ expect(config.headers.Authorization).toBe(false);
+ await waitFor(() => expect(checkbox).not.toBeChecked());
+ expect(screen.getByRole('button', { name: 'Submit your application' })).toBeDisabled();
+ });
+
+ it('preserves agreement and profile data after an API failure so the applicant can retry', async () => {
+ axios.post.mockRejectedValueOnce(new Error('Synthetic network failure'));
+ await mountApplication();
+ const checkbox = screen.getByRole('checkbox', { name: /I agree to terms and conditions/i });
+ fillProfile();
+ fireEvent.click(checkbox);
+ fireEvent.submit(screen.getByRole('form', { name: 'Job application' }));
+ await waitFor(() =>
+ expect(toast.error).toHaveBeenCalledWith('Synthetic network failure', expect.any(Object)),
+ );
+ expect(checkbox).toBeChecked();
+ expect(screen.getByRole('textbox', { name: /First Name/i })).toHaveValue('Fixture');
+ expect(screen.getByRole('button', { name: 'Submit your application' })).toBeEnabled();
+ });
+
+ it.each([false, true])(
+ 'keeps email corrections and required agreement independent in darkMode=%s',
+ async darkMode => {
+ await mountApplication(darkMode);
+ fillProfile();
+ const email = screen.getByRole('textbox', { name: /^Email/i });
+ const checkbox = screen.getByRole('checkbox', { name: /I agree to terms and conditions/i });
+ fireEvent.change(email, { target: { value: 'fixture@gmial.com' } });
+ fireEvent.blur(email);
+ fireEvent.click(screen.getByRole('button', { name: 'Use this email: fixture@gmail.com' }));
+ expect(email).toHaveValue('fixture@gmail.com');
+ expect(email).toHaveFocus();
+ expect(checkbox).not.toBeChecked();
+ expect(screen.getByRole('button', { name: 'Submit your application' })).toBeDisabled();
+ fireEvent.submit(screen.getByRole('form', { name: 'Job application' }));
+ expect(axios.post).not.toHaveBeenCalled();
+ expect(checkbox).toHaveFocus();
+
+ fireEvent.click(checkbox);
+ fireEvent.change(email, { target: { value: 'fixture@gmail.con' } });
+ fireEvent.blur(email);
+ fireEvent.submit(screen.getByRole('form', { name: 'Job application' }));
+ expect(axios.post).not.toHaveBeenCalled();
+ expect(email).toHaveAttribute('aria-invalid', 'true');
+ fireEvent.click(screen.getByRole('button', { name: 'Use this email: fixture@gmail.com' }));
+ expect(checkbox).toBeChecked();
+ expect(email).toHaveAttribute('aria-invalid', 'false');
+ fireEvent.submit(screen.getByRole('form', { name: 'Job application' }));
+ await waitFor(() => expect(axios.post).toHaveBeenCalledTimes(1));
+ expect(axios.post.mock.calls[0][1].get('email')).toBe('fixture@gmail.com');
+ await waitFor(() => expect(checkbox).not.toBeChecked());
+ },
+ );
+
+ it('keeps submission disabled while the agreed application is in flight', async () => {
+ let resolve;
+ axios.post.mockImplementationOnce(
+ () =>
+ new Promise(done => {
+ resolve = done;
+ }),
+ );
+ await mountApplication();
+ const checkbox = screen.getByRole('checkbox', { name: /I agree to terms and conditions/i });
+ fillProfile();
+ fireEvent.click(checkbox);
+ fireEvent.submit(screen.getByRole('form', { name: 'Job application' }));
+ await waitFor(() => expect(screen.getByRole('button', { name: 'Submitting…' })).toBeDisabled());
+ resolve({ data: {} });
+ await waitFor(() => expect(checkbox).not.toBeChecked());
+ });
+});
diff --git a/src/components/Collaboration/__tests__/JobApplicationForm.uploadSizeLimit.test.jsx b/src/components/Collaboration/__tests__/JobApplicationForm.uploadSizeLimit.test.jsx
new file mode 100644
index 00000000000..96059a14272
--- /dev/null
+++ b/src/components/Collaboration/__tests__/JobApplicationForm.uploadSizeLimit.test.jsx
@@ -0,0 +1,81 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { Provider } from 'react-redux';
+import { MemoryRouter } from 'react-router-dom';
+import axios from 'axios';
+import JobApplicationForm from '../JobApplicationForm/JobApplicationForm';
+import {
+ MAX_UPLOAD_BYTES,
+ getUploadSizeError,
+} from '../JobApplicationForm/jobApplicationQuestionUtils';
+
+vi.mock('axios');
+
+const mockStore = {
+ getState: () => ({ theme: { darkMode: false }, auth: { user: {} } }),
+ subscribe: () => () => {},
+ dispatch: vi.fn(),
+};
+
+const renderForm = () =>
+ render(
+
+
+
+
+ ,
+ );
+
+const makeFile = (name, size, type = 'application/pdf') => {
+ const file = new File(['x'], name, { type });
+ Object.defineProperty(file, 'size', { value: size });
+ return file;
+};
+
+const chooseResume = file => {
+ const input = screen.getByLabelText('Choose file');
+ fireEvent.change(input, { target: { files: [file] } });
+ return input;
+};
+
+describe('getUploadSizeError', () => {
+ it('allows files up to exactly 5 MB', () => {
+ expect(MAX_UPLOAD_BYTES).toBe(5 * 1024 * 1024);
+ expect(getUploadSizeError(makeFile('ok.pdf', MAX_UPLOAD_BYTES))).toBe('');
+ expect(getUploadSizeError(null)).toBe('');
+ });
+
+ it('rejects files over 5 MB with a readable message', () => {
+ expect(getUploadSizeError(makeFile('big.pdf', MAX_UPLOAD_BYTES + 1))).toBe(
+ 'File too large, max 5 MB.',
+ );
+ });
+});
+
+describe('JobApplicationForm upload size limit', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ axios.get.mockResolvedValue({ data: [] });
+ });
+
+ it('shows an inline error and keeps no file when the resume is over 5 MB', () => {
+ renderForm();
+ const input = chooseResume(makeFile('huge.pdf', MAX_UPLOAD_BYTES + 1));
+
+ expect(screen.getByRole('alert')).toHaveTextContent('File too large, max 5 MB.');
+ expect(input).toHaveAttribute('aria-invalid', 'true');
+ expect(input).toHaveAttribute('aria-describedby', 'jaf-resume-upload-error');
+ expect(screen.queryByText(/huge\.pdf/)).not.toBeInTheDocument();
+ });
+
+ it('clears the error once a resume within the limit is chosen', () => {
+ renderForm();
+ chooseResume(makeFile('huge.pdf', MAX_UPLOAD_BYTES + 1));
+ const input = chooseResume(makeFile('resume.pdf', 1024));
+
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
+ expect(input).toHaveAttribute('aria-invalid', 'false');
+ expect(screen.getByText(/resume\.pdf/)).toBeInTheDocument();
+ });
+});
diff --git a/src/components/Collaboration/__tests__/fixtures/devJobCatalog.json b/src/components/Collaboration/__tests__/fixtures/devJobCatalog.json
new file mode 100644
index 00000000000..a61951334aa
--- /dev/null
+++ b/src/components/Collaboration/__tests__/fixtures/devJobCatalog.json
@@ -0,0 +1,88 @@
+{
+ "capturedFrom": "https://apidev.highestgood.com (GET /api/jobs/positions, GET /api/jobforms/all), 2026-10-16",
+ "positions": [
+ "4-DOME CLUSTER ELECTRICAL DESIGN",
+ "4-DOME CLUSTER ROOF DESIGN",
+ "ARCHITECT OR LANDSCAPE ARCHITECT FOR CITY CENTER SKETCHUP AND LUMION RENDER HELP",
+ "BOTANIST/HORTICULTURIST TO DESIGN LANDSCAPE FOR OPEN SOURCE SUSTAINABLE GREENHOUSE",
+ "CITY CENTER GEODESIC DOME AUTODESK INVENTOR SIMULATIONS",
+ "CITY CENTER PLUMBING DESIGNS",
+ "CIVIL ENGINEER FOR COST ANALYSIS OF FOOD PRODUCTION STRUCTURES",
+ "CIVIL ENGINEER TO FINALIZE TEST MATERIALS AND EQUIPMENT FOR FOOD PRODUCTION STRUCTURES",
+ "CIVIL OR MECHANICAL ENGINEER TO FINALIZE DESIGN FOR FOOD PRODUCTION STRUCTURES",
+ "DATA ANALYST APPLICATION",
+ "EARTHBAG 4-DOME CLUSTER PLUMBING DESIGNS",
+ "ELECTRICAL DESIGNER FOR EARTHBAG VILLAGE",
+ "ELECTRICAL DESIGNER FOR STRAW BALE CLASSROOM",
+ "ELECTRICAL ENGINEER/DESIGNER FOR DUPLICABLE CITY CENTER",
+ "FINAL CUT PRO VIDEO EDITOR",
+ "FUNDRAISING HELP",
+ "GENERAL",
+ "INDUSTRIAL DESIGNER FOR DORMER",
+ "LUMION 2024.1.1 OR HIGHER FOR CITY CENTER",
+ "MASTER CARPENTER",
+ "MECHANICAL ENGINEER FOR FEA AND MECHANICAL COVER DESIGN OF MAXIMALLY SUSTAINABLE ECO-HOT TUB FOR OPEN SOURCE CITY CENTER DESIGNS",
+ "MECHANICAL ENGINEER FOR HVAC FOR FOOD PRODUCTION STRUCTURES",
+ "MECHANICAL ENGINEER TO DESIGN CLIMATE CONTROL SYSTEMS FOR SUSTAINABLE GREENHOUSE",
+ "PASSIVE GREENHOUSE DESIGN",
+ "PERMACULTURALIST FOR SOIL AMENDMENT",
+ "PHOTOSHOP/GRAPHIC DESIGNER",
+ "PLUMBING ENGINEER/MEP FOR EARTHBAG VILLAGE",
+ "SEEKING ADMINISTRATIVE ASSISTANT",
+ "SEEKING SOFTWARE POSITION",
+ "SEEKING VIRTUAL ASSISTANT",
+ "STRAW BALE CLASSROOM STRUCTURAL",
+ "T.A.S.T. - ENGINEER/ARCHITECT OFFERING POSITION",
+ "T.A.S.T. - HGN",
+ "T.A.S.T. - NEED RESUME & WORK SAMPLES",
+ "VERMICULTURE"
+ ],
+ "formTitles": [
+ "DATA ANALYST APPLICATION",
+ "T.A.S.T. - ENGINEER/ARCHITECT OFFERING POSITION",
+ "T.A.S.T. - HGN",
+ "T.A.S.T. - NEED RESUME & WORK SAMPLES",
+ "SEEKING SOFTWARE POSITION",
+ "[RETIRED] APPLIED THROUGH SITE - SEEKING SOFTWARE POSITION",
+ "ADMIN OF PR REVIEW TEAM AND FRONTEND TESTER",
+ "ARCHITECT FOR GENERAL POSITION",
+ "[RETIRED] T.A.S.T",
+ "ARCHITECT OR LANDSCAPE ARCHITECT FOR CITY CENTER SKETCHUP AND LUMION RENDER HELP",
+ "INDUSTRIAL DESIGNER FOR DORMER",
+ "FINAL CUT PRO VIDEO EDITOR",
+ "MASTER CARPENTER",
+ "VERMICULTURE",
+ "LUMION 2024.1.1 OR HIGHER FOR CITY CENTER",
+ "CHEF OR CULINARY PROFESSIONAL TO HELP WITH REMOTE/DISASTER KITCHEN SUPPLY AND STORAGE PLAN",
+ "CHEF OR CULINARY PROFESSIONAL FOR MENU IMPLEMENTATION TUTORIALS",
+ "NUTRITIONIST FOR NUTRITION CALCULATIONS AND MENU PLANNING",
+ "PERMACULTURALIST FOR SOIL AMENDMENT",
+ "STRAW BALE CLASSROOM STRUCTURAL",
+ "FUNDRAISING HELP",
+ "SEEKING VIRTUAL ASSISTANT",
+ "LANDSCAPE ARCHITECT FOR AQUAPINI/WALIPINI STRUCTURES",
+ "MECHANICAL ENGINEER TO DESIGN CLIMATE CONTROL SYSTEMS FOR SUSTAINABLE GREENHOUSE",
+ "BOTANIST/HORTICULTURIST TO DESIGN LANDSCAPE FOR OPEN SOURCE SUSTAINABLE GREENHOUSE",
+ "PASSIVE GREENHOUSE DESIGN",
+ "PHOTOSHOP/GRAPHIC DESIGNER",
+ "4-DOME CLUSTER ELECTRICAL DESIGN",
+ "4-DOME CLUSTER ROOF DESIGN",
+ "CITY CENTER GEODESIC DOME AUTODESK INVENTOR SIMULATIONS",
+ "ELECTRICAL ENGINEER/DESIGNER FOR DUPLICABLE CITY CENTER",
+ "ELECTRICAL DESIGNER FOR STRAW BALE CLASSROOM",
+ "ELECTRICAL DESIGNER FOR EARTHBAG VILLAGE",
+ "CITY CENTER PLUMBING DESIGNS",
+ "MECHANICAL ENGINEER FOR HVAC FOR FOOD PRODUCTION STRUCTURES",
+ "MECHANICAL ENGINEER FOR FEA AND MECHANICAL COVER DESIGN OF MAXIMALLY SUSTAINABLE ECO-HOT TUB FOR OPEN SOURCE CITY CENTER DESIGNS",
+ "CIVIL OR MECHANICAL ENGINEER TO FINALIZE DESIGN FOR FOOD PRODUCTION STRUCTURES",
+ "CIVIL ENGINEER TO FINALIZE TEST MATERIALS AND EQUIPMENT FOR FOOD PRODUCTION STRUCTURES",
+ "CIVIL ENGINEER FOR COST ANALYSIS OF FOOD PRODUCTION STRUCTURES",
+ "PLUMBING ENGINEER/MEP FOR EARTHBAG VILLAGE",
+ "EARTHBAG 4-DOME CLUSTER PLUMBING DESIGNS",
+ "Graphic Designer",
+ "SEEKING ADMINISTRATIVE ASSISTANT",
+ "GENERAL",
+ "Software Developer",
+ "[RETIRED] Test Create Form"
+ ]
+}
diff --git a/src/components/Collaboration/__tests__/jobFormMatching.test.js b/src/components/Collaboration/__tests__/jobFormMatching.test.js
new file mode 100644
index 00000000000..fb361fe838d
--- /dev/null
+++ b/src/components/Collaboration/__tests__/jobFormMatching.test.js
@@ -0,0 +1,199 @@
+import { describe, expect, it, vi } from 'vitest';
+import {
+ buildInitialSelection,
+ findApplicantFormBySlug,
+ findDefaultSoftwareForm,
+ findFormForJobTitle,
+ isApplicantFacingForm,
+ pickInitialForm,
+} from '../JobApplicationForm/JobApplicationForm';
+import {
+ buildApplyPath,
+ findPositionBySlug,
+ slugifyPositionTitle,
+} from '../JobApplicationForm/jobApplicationPublicApi';
+import catalog from './fixtures/devJobCatalog.json';
+
+vi.mock('react-toastify', () => ({ toast: { warn: vi.fn(), info: vi.fn(), error: vi.fn() } }));
+
+/* Real shared-dev catalog: /api/jobs/positions titles and /api/jobforms/all titles (API order). */
+const { positions } = catalog;
+const forms = catalog.formTitles.map((title, idx) => ({ _id: `form-${idx}`, title }));
+const RETIRED_TITLES = forms.map(f => f.title).filter(t => t.startsWith('[RETIRED]'));
+const retiredFirst = [
+ ...forms.filter(f => !isApplicantFacingForm(f)),
+ ...forms.filter(isApplicantFacingForm),
+];
+
+/** Apply Now link the job board builds for a title — reported for every failing role. */
+const applyLink = title => `${buildApplyPath(title)}?jobTitle=${encodeURIComponent(title)}`;
+
+describe('isApplicantFacingForm', () => {
+ it('filters out retired / internal ATS templates only', () => {
+ expect(RETIRED_TITLES).toHaveLength(3);
+ expect(forms.filter(f => !isApplicantFacingForm(f)).map(f => f.title)).toEqual(RETIRED_TITLES);
+ });
+});
+
+describe('findFormForJobTitle', () => {
+ it('resolves every dev position to the form with the same title', () => {
+ const failures = positions
+ .map(title => ({
+ link: applyLink(title),
+ expected: title,
+ got: findFormForJobTitle(forms, title)?.title ?? null,
+ }))
+ .filter(r => r.got !== r.expected);
+ expect(failures).toEqual([]);
+ });
+
+ it('never returns a retired form for junk or partial titles', () => {
+ const inputs = [
+ 'Applied through site',
+ 'APPLIED THROUGH SITE - SEEKING SOFTWARE POSITION',
+ 'Seeking Software',
+ 'Test Create Form',
+ '[RETIRED] T.A.S.T',
+ ];
+ const failures = inputs.flatMap(title =>
+ [forms, retiredFirst]
+ .map(formList => findFormForJobTitle(formList, title))
+ .filter(match => match && !isApplicantFacingForm(match))
+ .map(match => ({
+ link: `/job-application?jobTitle=${encodeURIComponent(title)}`,
+ got: match.title,
+ })),
+ );
+ expect(failures).toEqual([]);
+ });
+
+ it('maps the old default title to the live software form', () => {
+ expect(
+ findFormForJobTitle(retiredFirst, 'APPLIED THROUGH SITE - SEEKING SOFTWARE POSITION')?.title,
+ ).toBe('SEEKING SOFTWARE POSITION');
+ });
+
+ it('treats an ambiguous partial title as no match instead of guessing', () => {
+ // Three live T.A.S.T. roles contain "t.a.s.t".
+ expect(findFormForJobTitle(forms, 'T.A.S.T')).toBeNull();
+ });
+
+ it('matches regardless of case and punctuation', () => {
+ expect(findFormForJobTitle(forms, 'photoshop graphic designer')?.title).toBe(
+ 'PHOTOSHOP/GRAPHIC DESIGNER',
+ );
+ expect(findFormForJobTitle(forms, 't a s t hgn')?.title).toBe('T.A.S.T. - HGN');
+ });
+});
+
+describe('slug round-trip for /apply/ deep links', () => {
+ it('maps every dev position slug back to the same form and dropdown title', () => {
+ const failures = positions
+ .map(title => {
+ const slug = slugifyPositionTitle(title);
+ return {
+ link: buildApplyPath(title),
+ expected: title,
+ form: findApplicantFormBySlug(forms, slug)?.title ?? null,
+ dropdown: findPositionBySlug(positions, slug) || null,
+ };
+ })
+ .filter(r => r.form !== r.expected || r.dropdown !== r.expected);
+ expect(failures).toEqual([]);
+ });
+
+ it('never resolves a slug to a retired form', () => {
+ const slug = slugifyPositionTitle('[RETIRED] APPLIED THROUGH SITE - SEEKING SOFTWARE POSITION');
+ expect(findApplicantFormBySlug(forms, slug)).toBeNull();
+ });
+});
+
+describe('findDefaultSoftwareForm', () => {
+ it('picks SEEKING SOFTWARE POSITION, not the retired ATS form', () => {
+ expect(findDefaultSoftwareForm(forms)?.title).toBe('SEEKING SOFTWARE POSITION');
+ expect(findDefaultSoftwareForm(retiredFirst)?.title).toBe('SEEKING SOFTWARE POSITION');
+ });
+
+ it('returns null when only retired forms exist', () => {
+ expect(findDefaultSoftwareForm(forms.filter(f => !isApplicantFacingForm(f)))).toBeNull();
+ });
+});
+
+describe('pickInitialForm', () => {
+ const byTitle = title => forms.find(f => f.title === title);
+
+ it('prefers an explicit formId', () => {
+ const target = byTitle('GENERAL');
+ expect(
+ pickInitialForm(forms, { formId: target._id, jobTitle: 'MASTER CARPENTER' }, 'vermiculture'),
+ ).toBe(target);
+ });
+
+ it('uses the slug before the title', () => {
+ expect(pickInitialForm(forms, { jobTitle: 'MASTER CARPENTER' }, 'vermiculture')?.title).toBe(
+ 'VERMICULTURE',
+ );
+ });
+
+ it('matches the navigated job title', () => {
+ expect(
+ pickInitialForm(forms, { jobTitle: 'T.A.S.T. - NEED RESUME & WORK SAMPLES' }, '')?.title,
+ ).toBe('T.A.S.T. - NEED RESUME & WORK SAMPLES');
+ });
+
+ it('falls back to the default software form for an unknown title or slug', () => {
+ expect(pickInitialForm(retiredFirst, { jobTitle: 'Applied through site' }, '')?.title).toBe(
+ 'SEEKING SOFTWARE POSITION',
+ );
+ expect(pickInitialForm(retiredFirst, {}, 'does-not-exist')?.title).toBe(
+ 'SEEKING SOFTWARE POSITION',
+ );
+ });
+
+ it('returns null with no navigation context', () => {
+ expect(pickInitialForm(forms, {}, '')).toBeNull();
+ });
+});
+
+describe('buildInitialSelection', () => {
+ const select = ({ navTitle = '', positionSlug = '', positionList = positions } = {}) =>
+ buildInitialSelection({
+ formsArr: retiredFirst,
+ positions: positionList,
+ navTitle,
+ positionSlug,
+ routerLocation: { state: null },
+ formId: '',
+ });
+
+ it.each([
+ ['?jobTitle=Applied through site', { navTitle: 'Applied through site' }],
+ ['?jobTitle=T.A.S.T', { navTitle: 'T.A.S.T' }],
+ ['/apply/dehllo', { positionSlug: 'dehllo' }],
+ ['/apply/dehllo with /jobs/positions down', { positionSlug: 'dehllo', positionList: [] }],
+ ])('%s selects the default form title with no fake dropdown option', (_link, args) => {
+ const { options, selected, bannerTitle, initial } = select(args);
+ expect(selected).toBe('SEEKING SOFTWARE POSITION');
+ expect(bannerTitle).toBe('SEEKING SOFTWARE POSITION');
+ expect(initial.filteredForm?.title).toBe('SEEKING SOFTWARE POSITION');
+ // Dropdown is exactly the real positions (or live form titles when /jobs/positions fails).
+ const expectedOptions = args.positionList
+ ? retiredFirst.filter(isApplicantFacingForm).map(f => f.title)
+ : positions;
+ expect(options).toEqual(expectedOptions);
+ });
+
+ it('keeps the exact position for a matched deep link', () => {
+ const { options, selected } = select({ positionSlug: 't-a-s-t-hgn' });
+ expect(selected).toBe('T.A.S.T. - HGN');
+ expect(options).toEqual(positions);
+ });
+
+ it('maps a near-miss title to the real position', () => {
+ const { options, selected } = select({
+ navTitle: 'APPLIED THROUGH SITE - SEEKING SOFTWARE POSITION',
+ });
+ expect(selected).toBe('SEEKING SOFTWARE POSITION');
+ expect(options).toEqual(positions);
+ });
+});
diff --git a/src/components/CommunityPortal/Activities/__tests__/ActivityList.test.jsx b/src/components/CommunityPortal/Activities/__tests__/ActivityList.test.jsx
index d5751c59432..e80b50daec6 100644
--- a/src/components/CommunityPortal/Activities/__tests__/ActivityList.test.jsx
+++ b/src/components/CommunityPortal/Activities/__tests__/ActivityList.test.jsx
@@ -1,4 +1,5 @@
import { render, screen, fireEvent } from '@testing-library/react';
+import { vi } from 'vitest';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import configureMockStore from 'redux-mock-store';
@@ -18,6 +19,16 @@ const renderActivityList = (initialState = { theme: { darkMode: false } }) => {
};
describe('ActivityList', () => {
+ // Mock events are dated; freeze today so past/upcoming filtering is stable over time.
+ beforeEach(() => {
+ vi.useFakeTimers({ toFake: ['Date'] });
+ vi.setSystemTime(new Date('2026-10-01T12:00:00'));
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
test('renders Activity List heading', () => {
renderActivityList();
expect(screen.getByText('Activity List')).toBeInTheDocument();
@@ -57,6 +68,7 @@ describe('ActivityList', () => {
test('sorts activities by date (latest to earliest)', () => {
renderActivityList();
+ fireEvent.click(screen.getByLabelText(/Show Past Events:/i));
fireEvent.change(screen.getByLabelText(/Sort By:/i), {
target: { value: 'latest' },
diff --git a/src/components/CommunityPortal/Reports/Participation/CreateEventModal.jsx b/src/components/CommunityPortal/Reports/Participation/CreateEventModal.jsx
index 2caac4b9005..0b7baddf563 100644
--- a/src/components/CommunityPortal/Reports/Participation/CreateEventModal.jsx
+++ b/src/components/CommunityPortal/Reports/Participation/CreateEventModal.jsx
@@ -1,12 +1,11 @@
import { useState, useEffect } from 'react';
import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap';
-import { useDispatch, useSelector } from 'react-redux';
+import { useSelector } from 'react-redux';
import moment from 'moment-timezone';
-import { createEvent } from '../../../../actions/communityPortal/eventActions';
+import { toast } from 'react-toastify';
import '../../../Header/DarkMode.module.css';
function CreateEventModal({ isOpen, toggle, onEventCreated = () => {} }) {
- const dispatch = useDispatch();
const darkMode = useSelector(state => state.theme.darkMode);
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState({});
@@ -61,12 +60,7 @@ function CreateEventModal({ isOpen, toggle, onEventCreated = () => {} }) {
const handleToggle = () => {
if (!loading) {
- if (isOpen) {
- toggle();
- } else {
- resetForm();
- toggle();
- }
+ toggle();
}
};
@@ -85,6 +79,11 @@ function CreateEventModal({ isOpen, toggle, onEventCreated = () => {} }) {
const validateForm = () => {
const newErrors = {};
+ const maxAttendees = Number(formData.maxAttendees);
+
+ if (!Number.isInteger(maxAttendees) || maxAttendees < 1) {
+ newErrors.maxAttendees = 'Max Attendees must be a positive integer';
+ }
if (!formData.title.trim()) {
newErrors.title = 'Title is required';
@@ -116,10 +115,6 @@ function CreateEventModal({ isOpen, toggle, onEventCreated = () => {} }) {
newErrors.endTime = 'End time is required';
}
- if (formData.maxAttendees < 1) {
- newErrors.maxAttendees = 'Max attendees must be at least 1';
- }
-
// Validate that end time is after start time
if (formData.startTime && formData.endTime) {
const start = moment(`${formData.date} ${formData.startTime}`, 'YYYY-MM-DD HH:mm');
@@ -133,7 +128,7 @@ function CreateEventModal({ isOpen, toggle, onEventCreated = () => {} }) {
return Object.keys(newErrors).length === 0;
};
- const handleSubmit = async e => {
+ const handleSubmit = e => {
e.preventDefault();
if (!validateForm()) {
@@ -144,6 +139,7 @@ function CreateEventModal({ isOpen, toggle, onEventCreated = () => {} }) {
// Format the event data according to the Event model
const eventData = {
+ id: `local-${Date.now()}`,
title: formData.title.trim(),
type: formData.type,
location: formData.location,
@@ -166,17 +162,12 @@ function CreateEventModal({ isOpen, toggle, onEventCreated = () => {} }) {
eventData.coverImage = formData.coverImage.trim();
}
- try {
- const result = await dispatch(createEvent(eventData));
- if (result?.success) {
- onEventCreated();
- handleToggle();
- }
- } catch (error) {
- setErrors('Unable to create a new event. Please try again later.');
- } finally {
- setLoading(false);
- }
+ toast.success(
+ 'Event created! Note: this preview event is not saved and will be lost on reload.',
+ );
+ onEventCreated(eventData);
+ resetForm();
+ toggle();
};
return (
diff --git a/src/components/CommunityPortal/Reports/Participation/MyCases.jsx b/src/components/CommunityPortal/Reports/Participation/MyCases.jsx
index 6f29bc3b28f..9304480dfaa 100644
--- a/src/components/CommunityPortal/Reports/Participation/MyCases.jsx
+++ b/src/components/CommunityPortal/Reports/Participation/MyCases.jsx
@@ -12,6 +12,7 @@ function MyCases() {
const [filter, setFilter] = useState('All Time');
const [expanded, setExpanded] = useState(false);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
+ const [events, setEvents] = useState(mockEvents);
const isExporting =
typeof document !== 'undefined' && document.documentElement?.dataset?.exporting === 'true';
@@ -20,9 +21,34 @@ function MyCases() {
const darkMode = useSelector(state => state.theme.darkMode);
- const filteredEvents = filterEventsByDate(mockEvents, filter).filter(
- event => new Date(event.eventDate).getTime() >= now.getTime(),
- );
+ const filteredEvents = filterEventsByDate(events, filter)
+ .filter(event => event.isNew || new Date(event.eventDate).getTime() >= now.getTime())
+ .sort((a, b) => (b.isNew ? 1 : 0) - (a.isNew ? 1 : 0));
+
+ const handleEventCreated = createdEvent => {
+ if (!createdEvent) return;
+ const eventDate = createdEvent.startTime || createdEvent.date || Date.now();
+ setEvents(prev => [
+ {
+ id: createdEvent._id || createdEvent.id || `local-${Date.now()}`,
+ isNew: true,
+ eventType: createdEvent.type || 'all',
+ eventDate: new Date(eventDate).toISOString(),
+ eventTime: new Date(eventDate).toLocaleString('en-US', {
+ hour: 'numeric',
+ minute: 'numeric',
+ hour12: true,
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric',
+ }),
+ eventName: createdEvent.title,
+ attendees: 0,
+ location: createdEvent.location,
+ },
+ ...prev,
+ ]);
+ };
let visibleEvents = filteredEvents;
@@ -209,6 +235,7 @@ function MyCases() {
setIsCreateModalOpen(!isCreateModalOpen)}
+ onEventCreated={handleEventCreated}
/>
);
diff --git a/src/components/CommunityPortal/Reports/Participation/MyCases.module.css b/src/components/CommunityPortal/Reports/Participation/MyCases.module.css
index 4ba9ddae8e0..f6d3dad696c 100644
--- a/src/components/CommunityPortal/Reports/Participation/MyCases.module.css
+++ b/src/components/CommunityPortal/Reports/Participation/MyCases.module.css
@@ -323,6 +323,30 @@
border: 1px solid #c084fc;
}
+.caseCardDark .eventBadge[data-type='Workshop'] {
+ background-color: #1e3a5f;
+ color: #60a5fa;
+ border: 1px solid #60a5fa;
+}
+
+.caseCardDark .eventBadge[data-type='Meeting'] {
+ background-color: #4d3d1e;
+ color: #fbbf24;
+ border: 1px solid #fbbf24;
+}
+
+.caseCardDark .eventBadge[data-type='Webinar'] {
+ background-color: #3d1e4d;
+ color: #c084fc;
+ border: 1px solid #c084fc;
+}
+
+.caseCardDark .eventBadge[data-type='Social Gathering'] {
+ background-color: #1e4d2e;
+ color: #4ade80;
+ border: 1px solid #4ade80;
+}
+
.eventTime {
font-size: 14px;
color: #555;
diff --git a/src/components/EductionPortal/AnalyticsDashboard/InsightsWidget.jsx b/src/components/EductionPortal/AnalyticsDashboard/InsightsWidget.jsx
index 2c1648e5838..e5ff438373c 100644
--- a/src/components/EductionPortal/AnalyticsDashboard/InsightsWidget.jsx
+++ b/src/components/EductionPortal/AnalyticsDashboard/InsightsWidget.jsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, useCallback, useMemo } from 'react';
+import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { useSelector } from 'react-redux';
import styles from './InsightsWidget.module.css';
@@ -34,33 +34,44 @@ const MOCK_ANALYTICS_DATA = {
id: 1,
strategy: 'Everything you do should increase choices',
impact: 92,
- color: '#10b981',
- changeType: 'high',
},
{
id: 2,
strategy: 'Ask "what would Love do?"',
impact: 88,
- color: '#10b981',
- changeType: 'good',
},
{
id: 3,
strategy: 'Choose to lead with observation',
impact: 76,
- color: '#d1d5db',
- changeType: 'moderate',
},
{
id: 4,
strategy: 'Practice improving your emotional intelligence',
impact: 79,
- color: '#fbbf24',
- changeType: 'moderate',
},
],
};
+const IMPACT_LEVELS = [
+ { min: 90, label: 'High Impact (90+)', color: '#10b981' },
+ { min: 80, label: 'Good Result (80-89)', color: '#84cc16' },
+ { min: 70, label: 'Moderate Impact (70-79)', color: '#fbbf24' },
+ { min: 0, label: 'Low Impact (<70)', color: '#ef4444' },
+];
+
+const getImpactColor = impact => IMPACT_LEVELS.find(level => impact >= level.min).color;
+
+const TREND_ICON = { up: '↑', down: '↓', flat: '–' };
+const TREND_CLASS = { up: 'Positive', down: 'Negative', flat: 'Neutral' };
+const CHANGE_CLASS = { up: 'changePositive', down: 'changeNegative', flat: 'changeNeutral' };
+
+const getTrend = value => {
+ if (value > 0) return 'up';
+ if (value < 0) return 'down';
+ return 'flat';
+};
+
const validateMetrics = data => {
if (!data || typeof data !== 'object') return false;
const required = ['averageScore', 'completionRate', 'totalStudents'];
@@ -79,13 +90,35 @@ const InsightsWidget = () => {
const [retryCount, setRetryCount] = useState(0);
const [animateIn, setAnimateIn] = useState(false);
const MAX_RETRIES = 3;
+ const isMountedRef = useRef(true);
+ const timeoutsRef = useRef(new Set());
+
+ // Tracked setTimeout so pending timers are cleared on unmount
+ const schedule = useCallback((fn, delay) => {
+ const id = setTimeout(() => {
+ timeoutsRef.current.delete(id);
+ fn();
+ }, delay);
+ timeoutsRef.current.add(id);
+ }, []);
+
+ useEffect(() => {
+ isMountedRef.current = true;
+ const timeouts = timeoutsRef.current;
+ return () => {
+ isMountedRef.current = false;
+ timeouts.forEach(clearTimeout);
+ timeouts.clear();
+ };
+ }, []);
const fetchAnalyticsOverview = useCallback(async () => {
try {
setLoading(true);
setError(null);
- await new Promise(resolve => setTimeout(resolve, FETCH_DELAY));
+ await new Promise(resolve => schedule(resolve, FETCH_DELAY));
+ if (!isMountedRef.current) return;
const mockData = { ...MOCK_ANALYTICS_DATA };
@@ -99,8 +132,9 @@ const InsightsWidget = () => {
setMetrics(mockData);
setRetryCount(0);
- setTimeout(() => setAnimateIn(true), 100);
+ schedule(() => setAnimateIn(true), 100);
} catch (err) {
+ if (!isMountedRef.current) return;
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
setError(errorMessage);
@@ -108,12 +142,12 @@ const InsightsWidget = () => {
console.error('Error fetching analytics:', err);
if (retryCount < MAX_RETRIES) {
- setTimeout(() => setRetryCount(prev => prev + 1), 2000);
+ schedule(() => setRetryCount(prev => prev + 1), 2000);
}
} finally {
- setLoading(false);
+ if (isMountedRef.current) setLoading(false);
}
- }, [retryCount]);
+ }, [retryCount, schedule]);
useEffect(() => {
fetchAnalyticsOverview();
@@ -125,6 +159,7 @@ const InsightsWidget = () => {
...metrics,
topStudents: [...metrics.topStudents].sort((a, b) => b.score - a.score),
topSubjects: [...metrics.topSubjects].sort((a, b) => b.averageScore - a.averageScore),
+ lifeStrategies: [...metrics.lifeStrategies].sort((a, b) => b.impact - a.impact),
};
}, [metrics]);
@@ -166,6 +201,7 @@ const InsightsWidget = () => {
return (
{
{student.name}
{student.score.toFixed(1)}%
- 0 ? styles.trendPositive : styles.trendNegative}
- >
- {student.trend > 0 ? '↑' : '↓'} {Math.abs(student.trend).toFixed(1)}%
+
+ {TREND_ICON[getTrend(student.trend)]} {Math.abs(student.trend).toFixed(1)}%
@@ -254,36 +288,31 @@ const InsightsWidget = () => {
{sortedMetrics.lifeStrategies.map(strategy => (
{strategy.strategy}
-
+
+ {`${strategy.impact}%`}
+
{strategy.impact}%
))}
-
-
- High Impact (90+)
-
-
-
- Good Result (80-89)
-
-
-
- Moderate Impact (70-79)
-
-
-
- Low Impact (<70)
-
+ {IMPACT_LEVELS.map(level => (
+
+
+ {level.label}
+
+ ))}
Actionable Insight: Consistently applying “Everything you do
@@ -305,7 +334,8 @@ const SummaryCard = ({ title, value, change, unit = '', isCount = false, darkMod
return null;
}
- const isPositive = change > 0;
+ const trend = getTrend(change);
+ const formattedChange = isCount ? Math.round(change).toLocaleString() : change.toFixed(1);
return (
@@ -315,14 +345,13 @@ const SummaryCard = ({ title, value, change, unit = '', isCount = false, darkMod
{unit && {unit} }
- {isPositive ? '↑' : '↓'}
+ {TREND_ICON[trend]}
- {isPositive ? '+' : ''}
- {change.toFixed(1)}
+ {trend === 'up' ? '+' : ''}
+ {formattedChange}
{isCount ? '' : unit} this month
@@ -330,4 +359,5 @@ const SummaryCard = ({ title, value, change, unit = '', isCount = false, darkMod
);
};
+export { SummaryCard, getTrend };
export default InsightsWidget;
diff --git a/src/components/EductionPortal/AnalyticsDashboard/InsightsWidget.module.css b/src/components/EductionPortal/AnalyticsDashboard/InsightsWidget.module.css
index 207b826e3f2..cf25c59999b 100644
--- a/src/components/EductionPortal/AnalyticsDashboard/InsightsWidget.module.css
+++ b/src/components/EductionPortal/AnalyticsDashboard/InsightsWidget.module.css
@@ -179,6 +179,24 @@
color: #ef4444 !important;
}
+.cardChange.changeNeutral {
+ color: #6b7280 !important;
+}
+
+.summaryCard.darkSummaryCard .cardChange.changeNeutral {
+ color: #94a3b8 !important;
+}
+
+/* Override the global dark-mode `body.dark-mode * { color: #fff !important }` rule */
+.cardChange.changePositive .changeIcon,
+.cardChange.changePositive .changeText,
+.cardChange.changeNegative .changeIcon,
+.cardChange.changeNegative .changeText,
+.cardChange.changeNeutral .changeIcon,
+.cardChange.changeNeutral .changeText {
+ color: inherit !important;
+}
+
.changeIcon {
font-size: 14px;
font-weight: bold;
@@ -319,6 +337,16 @@
font-weight: 600;
}
+.trendNeutral {
+ color: #6b7280 !important;
+ font-weight: 600;
+}
+
+.darkPanel .trendNeutral {
+ color: #94a3b8 !important;
+ font-weight: 600;
+}
+
/* Life Strategies */
.strategiesList {
display: flex;
@@ -345,24 +373,40 @@
}
.strategyBar {
+ display: block;
width: 200px;
height: 20px;
+ border: none;
background: #e5e7eb;
border-radius: 10px;
overflow: hidden;
- position: relative;
+ appearance: none;
}
-.insightPanel.darkPanel .strategyBar {
- background: #334155;
+.strategyBar::-webkit-progress-bar {
+ background: #e5e7eb;
+ border-radius: 10px;
}
-.strategyFill {
- height: 100%;
+.strategyBar::-webkit-progress-value {
+ background: var(--impact-color);
border-radius: 10px;
transition: width 0.6s ease;
}
+.strategyBar::-moz-progress-bar {
+ background: var(--impact-color);
+ border-radius: 10px;
+}
+
+.insightPanel.darkPanel .strategyBar {
+ background: #334155;
+}
+
+.insightPanel.darkPanel .strategyBar::-webkit-progress-bar {
+ background: #334155;
+}
+
.strategyValue {
font-size: 13px;
font-weight: 600;
diff --git a/src/components/EductionPortal/AnalyticsDashboard/__tests__/InsightsWidget.test.jsx b/src/components/EductionPortal/AnalyticsDashboard/__tests__/InsightsWidget.test.jsx
new file mode 100644
index 00000000000..d8063ae24ee
--- /dev/null
+++ b/src/components/EductionPortal/AnalyticsDashboard/__tests__/InsightsWidget.test.jsx
@@ -0,0 +1,205 @@
+import { render, screen, within } from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+import { Provider } from 'react-redux';
+import configureMockStore from 'redux-mock-store';
+import InsightsWidget, { SummaryCard, getTrend } from '../InsightsWidget';
+import styles from '../InsightsWidget.module.css';
+
+const mockStore = configureMockStore([]);
+
+const renderWidget = (darkMode = false) =>
+ render(
+
+
+ ,
+ );
+
+// Mock fetch resolves after 800ms
+const waitForData = () => screen.findByText('Key Insights', {}, { timeout: 3000 });
+
+describe('InsightsWidget', () => {
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('shows the loading spinner, then the insights', async () => {
+ renderWidget();
+ expect(screen.getByText('Loading insights...')).toBeInTheDocument();
+
+ await waitForData();
+ expect(screen.queryByText('Loading insights...')).not.toBeInTheDocument();
+ expect(screen.getByText('Impact of Life Strategies')).toBeInTheDocument();
+ expect(screen.getByText('Top Performing Students')).toBeInTheDocument();
+ });
+
+ describe('Impact of Life Strategies', () => {
+ it('sorts strategies in descending order of impact', async () => {
+ renderWidget();
+ await waitForData();
+
+ const values = screen
+ .getAllByRole('progressbar')
+ .map(bar => Number(bar.getAttribute('value')));
+
+ expect(values).toEqual([92, 88, 79, 76]);
+ });
+
+ it('colors each bar using the legend thresholds', async () => {
+ renderWidget();
+ await waitForData();
+
+ const colorFor = name =>
+ screen.getByRole('progressbar', { name }).style.getPropertyValue('--impact-color');
+
+ expect(colorFor('Everything you do should increase choices')).toBe('#10b981');
+ expect(colorFor('Ask "what would Love do?"')).toBe('#84cc16');
+ expect(colorFor('Practice improving your emotional intelligence')).toBe('#fbbf24');
+ expect(colorFor('Choose to lead with observation')).toBe('#fbbf24');
+ });
+
+ it('only uses bar colors that appear in the legend', async () => {
+ renderWidget();
+ await waitForData();
+
+ const legendColors = screen
+ .getAllByTestId('legend-dot')
+ .map(dot => dot.style.backgroundColor);
+ // Normalize the hex custom property to the rgb() form jsdom uses for the legend dots
+ const toRgb = color => {
+ const el = document.createElement('div');
+ el.style.backgroundColor = color;
+ return el.style.backgroundColor;
+ };
+ const barColors = screen
+ .getAllByRole('progressbar')
+ .map(bar => toRgb(bar.style.getPropertyValue('--impact-color')));
+
+ barColors.forEach(color => expect(legendColors).toContain(color));
+ });
+
+ it('renders a legend entry for every impact level', async () => {
+ renderWidget();
+ await waitForData();
+
+ [
+ 'High Impact (90+)',
+ 'Good Result (80-89)',
+ 'Moderate Impact (70-79)',
+ 'Low Impact (<70)',
+ ].forEach(label => expect(screen.getByText(label)).toBeInTheDocument());
+ });
+
+ it('exposes accessible progressbar attributes', async () => {
+ renderWidget();
+ await waitForData();
+
+ const bar = screen.getByRole('progressbar', {
+ name: 'Everything you do should increase choices',
+ });
+ expect(bar.tagName).toBe('PROGRESS');
+ expect(bar).toHaveAttribute('value', '92');
+ expect(bar).toHaveAttribute('max', '100');
+ expect(bar).toHaveAttribute('aria-valuetext', '92% impact');
+ });
+ });
+
+ describe('Top Performing Students', () => {
+ it('shows positive and negative trends with matching styles', async () => {
+ renderWidget();
+ await waitForData();
+
+ expect(screen.getByText('↑ 2.5%')).toHaveClass(styles.trendPositive);
+ expect(screen.getByText('↓ 0.5%')).toHaveClass(styles.trendNegative);
+ });
+ });
+
+ describe('summary cards', () => {
+ it('shows count changes as whole numbers', async () => {
+ renderWidget();
+ await waitForData();
+
+ expect(screen.getByText(/\+12 this month/)).toBeInTheDocument();
+ expect(screen.getByText(/\+8 this month/)).toBeInTheDocument();
+ expect(screen.queryByText(/\+12\.0/)).not.toBeInTheDocument();
+ });
+
+ it('shows percentage changes with one decimal place', async () => {
+ renderWidget();
+ await waitForData();
+
+ expect(screen.getByText(/\+5\.2% this month/)).toBeInTheDocument();
+ expect(screen.getByText(/\+3\.1% this month/)).toBeInTheDocument();
+ });
+ });
+
+ it('clears pending timers on unmount', () => {
+ vi.useFakeTimers();
+ const { unmount } = renderWidget();
+ expect(vi.getTimerCount()).toBeGreaterThan(0);
+
+ unmount();
+ expect(vi.getTimerCount()).toBe(0);
+ });
+
+ it('applies dark mode styles when enabled', async () => {
+ renderWidget(true);
+ await waitForData();
+
+ expect(screen.getByTestId('insights-widget')).toHaveClass(styles.dark);
+ });
+});
+
+describe('SummaryCard', () => {
+ const changeLine = () => screen.getByTestId('card-change');
+
+ it('renders a positive change as green with an up arrow', () => {
+ render(
);
+ const line = changeLine();
+
+ expect(line).toHaveClass(styles.changePositive);
+ expect(within(line).getByText('↑')).toBeInTheDocument();
+ expect(line).toHaveTextContent('+5.2% this month');
+ });
+
+ it('renders a negative change as red with a down arrow', () => {
+ render(
);
+ const line = changeLine();
+
+ expect(line).toHaveClass(styles.changeNegative);
+ expect(within(line).getByText('↓')).toBeInTheDocument();
+ expect(line).toHaveTextContent('-2.4% this month');
+ });
+
+ it('renders a zero change as neutral instead of a loss', () => {
+ render(
);
+ const line = changeLine();
+
+ expect(line).toHaveClass(styles.changeNeutral);
+ expect(line).not.toHaveClass(styles.changeNegative);
+ expect(within(line).getByText('–')).toBeInTheDocument();
+ expect(line).toHaveTextContent('0.0% this month');
+ });
+
+ it('formats counts without decimals or units', () => {
+ render(
);
+
+ expect(screen.getByText('1,245')).toBeInTheDocument();
+ expect(changeLine()).toHaveTextContent('-3 this month');
+ });
+
+ it('renders nothing without a title or value', () => {
+ const { container: noTitle } = render(
);
+ const { container: noValue } = render(
);
+
+ expect(noTitle).toBeEmptyDOMElement();
+ expect(noValue).toBeEmptyDOMElement();
+ });
+});
+
+describe('getTrend', () => {
+ it('classifies values as up, down or flat', () => {
+ expect(getTrend(2.5)).toBe('up');
+ expect(getTrend(-0.5)).toBe('down');
+ expect(getTrend(0)).toBe('flat');
+ });
+});
diff --git a/src/components/EmailManagement/email-sender/IntegratedEmailSender.jsx b/src/components/EmailManagement/email-sender/IntegratedEmailSender.jsx
index b88cd42a465..02719d60049 100644
--- a/src/components/EmailManagement/email-sender/IntegratedEmailSender.jsx
+++ b/src/components/EmailManagement/email-sender/IntegratedEmailSender.jsx
@@ -61,6 +61,7 @@ import {
validateEmail as validateEmailUtil,
} from './utils';
import { validateTemplateVariables, validateVariable, Validators } from './validation';
+import WeeklyUpdateComposer from './WeeklyUpdateComposer';
const LazyEditor = lazy(() =>
import('@tinymce/tinymce-react').then(module => ({ default: module.Editor })),
@@ -1540,66 +1541,68 @@ const IntegratedEmailSender = ({
📰 Weekly Update
-
- {/* Action Buttons */}
-
-
- selectedTemplate?.variables?.some(
- v => v?.name === key && !!validationErrors[key],
- ),
- )) ||
- (!useTemplate && !customContent) ||
- (useTemplate && !selectedTemplate)
- }
+ {emailMode !== EMAIL_MODES.WEEKLY_UPDATE && (
+
- {previewLoading ? (
- <>
-
- Loading Preview...
- >
- ) : (
- <>
-
- Preview & Send
- >
- )}
-
- {onClose && (
-
-
- Close
-
- )}
- {hasDraft() && (
+ selectedTemplate?.variables?.some(
+ v => v?.name === key && !!validationErrors[key],
+ ),
+ )) ||
+ (!useTemplate && !customContent) ||
+ (useTemplate && !selectedTemplate)
+ }
>
-
- Clear Draft
+ {previewLoading ? (
+ <>
+
+ Loading Preview...
+ >
+ ) : (
+ <>
+
+ Preview & Send
+ >
+ )}
- )}
-
+
+ {onClose && (
+
+
+ Close
+
+ )}
+
+ {hasDraft() && (
+
+
+ Clear Draft
+
+ )}
+
+ )}
@@ -1623,6 +1626,7 @@ const IntegratedEmailSender = ({
)}
+ {emailMode === EMAIL_MODES.WEEKLY_UPDATE &&
}
{/* Form */}
{emailMode !== EMAIL_MODES.WEEKLY_UPDATE && (
diff --git a/src/components/EmailManagement/email-sender/WeeklyUpdateComposer.jsx b/src/components/EmailManagement/email-sender/WeeklyUpdateComposer.jsx
index 5105b4b4348..31d9e5643ae 100644
--- a/src/components/EmailManagement/email-sender/WeeklyUpdateComposer.jsx
+++ b/src/components/EmailManagement/email-sender/WeeklyUpdateComposer.jsx
@@ -1,12 +1,13 @@
import axios from 'axios';
import PropTypes from 'prop-types';
-import { useCallback, useState } from 'react';
+import React, { useCallback, useRef, useState } from 'react';
import {
FaExclamationTriangle,
FaEye,
FaImage,
FaPaperPlane,
FaTimes,
+ FaTrash,
FaUpload,
} from 'react-icons/fa';
import { useSelector } from 'react-redux';
@@ -144,11 +145,25 @@ const EMAIL_TEMPLATE_STYLES = `
margin: 5px 0;
}
`;
+const getImageUploadPrompt = imageDragging => {
+ if (imageDragging) {
+ return 'Drop image here';
+ }
+
+ return 'Click to upload or drag and drop';
+};
+
+const getVideoUploadPrompt = videoDragging => {
+ if (videoDragging) {
+ return 'Drop YouTube link here!';
+ }
+ return 'Drag & drop YouTube link here';
+};
const WeeklyUpdateComposer = ({ onClose }) => {
const darkMode = useSelector(state => state.theme.darkMode);
const currentUser = useSelector(state => state.auth?.user);
-
+ const imageInputRef = useRef(null);
// Form state - only what changes each week
const [headerImage, setHeaderImage] = useState('');
const [introParagraph, setIntroParagraph] = useState('');
@@ -193,31 +208,40 @@ const WeeklyUpdateComposer = ({ onClose }) => {
}, []);
// Handle image file drop
- const handleImageDrop = useCallback(e => {
- e.preventDefault();
- e.stopPropagation();
- setImageDragging(false);
+ // Process an image file from either the file picker or drag-and-drop
+ const handleImageFile = useCallback(file => {
+ if (!file) return;
- const files = e.dataTransfer?.files;
- if (files && files.length > 0) {
- const file = files[0];
-
- // Check if it's an image
- if (file.type.startsWith('image/')) {
- const reader = new FileReader();
- reader.onload = event => {
- const base64Image = event.target.result;
- setHeaderImage(base64Image);
- setImagePreview(base64Image);
- toast.success('Image loaded successfully!');
- };
- reader.readAsDataURL(file);
- } else {
- toast.error('Please drop an image file (jpg, png, gif, etc.)');
- }
+ if (!file.type.startsWith('image/')) {
+ toast.error('Please drop an image file (jpg, png, gif, etc.)');
+ return;
}
+
+ const reader = new FileReader();
+
+ reader.onload = event => {
+ const base64Image = event.target.result;
+ setHeaderImage(base64Image);
+ setImagePreview(base64Image);
+ toast.success('Image loaded successfully!');
+ };
+
+ reader.readAsDataURL(file);
}, []);
+ // Handle image drag-and-drop events
+ const handleImageDrop = useCallback(
+ e => {
+ e.preventDefault();
+ e.stopPropagation();
+ setImageDragging(false);
+
+ const file = e.dataTransfer?.files?.[0];
+ handleImageFile(file);
+ },
+ [handleImageFile],
+ );
+
const handleImageDragOver = useCallback(e => {
e.preventDefault();
e.stopPropagation();
@@ -502,9 +526,54 @@ const WeeklyUpdateComposer = ({ onClose }) => {
setIsSending(false);
}
}, [emailDistribution, recipientList, generateEmailHTML, currentUser]);
+ let composerThemeClass = 'weekly-update-composer light-mode';
+
+ if (darkMode) {
+ composerThemeClass = 'weekly-update-composer dark-mode';
+ }
+
+ let imageDropZoneBackgroundColor = '#f9fafb';
+ let imageDropZoneBorder = '2px dashed #d1d5db';
+ let imageUploadIconColor = '#9ca3af';
+ let imageUploadTextColor = '#6b7280';
+ let imageUploadPrompt = getImageUploadPrompt();
+
+ if (darkMode) {
+ imageDropZoneBackgroundColor = '#2d2d2d';
+ imageDropZoneBorder = '2px dashed #666';
+ imageUploadIconColor = '#d1d5db';
+ imageUploadTextColor = '#e5e7eb';
+ }
+
+ if (imageDragging) {
+ imageDropZoneBackgroundColor = darkMode ? '#26374a' : '#eff6ff';
+ imageDropZoneBorder = '3px dashed #3b82f6';
+ imageUploadIconColor = '#60a5fa';
+ imageUploadTextColor = '#60a5fa';
+ }
+ let videoUploadPrompt = getVideoUploadPrompt();
+
+ let videoDropZoneBackgroundColor = '#f9fafb';
+ let videoDropZoneBorder = '2px dashed #d1d5db';
+ let videoUploadIconColor = '#9ca3af';
+ let videoUploadTextColor = '#6b7280';
+
+ if (darkMode) {
+ videoDropZoneBackgroundColor = '#2d2d2d';
+ videoDropZoneBorder = '2px dashed #666';
+ videoUploadIconColor = '#d1d5db';
+ videoUploadTextColor = '#e5e7eb';
+ }
+
+ if (videoDragging) {
+ videoDropZoneBackgroundColor = darkMode ? '#452626' : '#fef2f2';
+ videoDropZoneBorder = '3px dashed #ef4444';
+ videoUploadIconColor = '#f87171';
+ videoUploadTextColor = '#f87171';
+ }
return (
-
+
📰 Weekly Progress Update
Fill in the 4 fields below - everything else is automatic!
@@ -524,28 +593,58 @@ const WeeklyUpdateComposer = ({ onClose }) => {
1. Header Image *
{/* Drag & Drop Zone */}
-
imageInputRef.current?.click()}
onDrop={handleImageDrop}
onDragOver={handleImageDragOver}
onDragLeave={handleImageDragLeave}
style={{
- border: imageDragging ? '3px dashed #3b82f6' : '2px dashed #d1d5db',
+ display: 'block',
+ width: '100%',
+ border: imageDropZoneBorder,
borderRadius: '8px',
- padding: '20px',
+ padding: '30px',
textAlign: 'center',
- backgroundColor: imageDragging ? '#eff6ff' : '#f9fafb',
- transition: 'all 0.2s ease',
- marginBottom: '10px',
+ backgroundColor: imageDropZoneBackgroundColor,
cursor: 'pointer',
+ transition: 'all 0.2s',
+ font: 'inherit',
+ color: 'inherit',
}}
>
-
-
- {imageDragging ? 'Drop image here!' : 'Drag & drop an image here'}
+ e.stopPropagation()}
+ onChange={e => {
+ const file = e.target.files?.[0];
+
+ if (file) {
+ handleImageFile(file);
+ }
+
+ e.target.value = '';
+ }}
+ />
+
+
+
+
+ {imageUploadPrompt}
-
or paste URL below
-
+
JPG, PNG, GIF, etc.
+
{
invalid={!!validationErrors.headerImage}
placeholder="Or paste image URL here (e.g., https://example.com/image.jpg)"
/>
+
{validationErrors.headerImage && (
{validationErrors.headerImage}
)}
+
Supports: Drag & drop image files, or paste image URLs
@@ -564,7 +665,26 @@ const WeeklyUpdateComposer = ({ onClose }) => {
{/* Image Preview */}
{imagePreview && (
-
Preview:
+
+ Preview:
+ {
+ setHeaderImage('');
+ setImagePreview('');
+ setValidationErrors(prev => ({
+ ...prev,
+ headerImage: null,
+ }));
+ }}
+ >
+
+ Delete Image
+
+
+
{
onError={() => {
toast.error('Invalid image URL');
setImagePreview('');
+ setHeaderImage('');
}}
/>
@@ -610,21 +731,27 @@ const WeeklyUpdateComposer = ({ onClose }) => {
onDragOver={handleVideoDragOver}
onDragLeave={handleVideoDragLeave}
style={{
- border: videoDragging ? '3px dashed #ef4444' : '2px dashed #d1d5db',
+ border: videoDropZoneBorder,
borderRadius: '8px',
- padding: '20px',
+ padding: '30px',
textAlign: 'center',
- backgroundColor: videoDragging ? '#fef2f2' : '#f9fafb',
- transition: 'all 0.2s ease',
- marginBottom: '10px',
+ backgroundColor: videoDropZoneBackgroundColor,
cursor: 'pointer',
+ transition: 'all 0.2s',
}}
>
-
-
- {videoDragging ? 'Drop YouTube link here!' : 'Drag & drop YouTube link here'}
+
+
+
+ {videoUploadPrompt}
-
or paste URL below
+
+
or paste URL below
{
invalid={!!validationErrors.youtubeLink}
placeholder="Or paste YouTube URL: https://youtube.com/watch?v=..."
/>
+
{validationErrors.youtubeLink && (
{validationErrors.youtubeLink}
)}
+
{youtubeLink && extractYouTubeId(youtubeLink) && (
✓ Valid YouTube link detected
)}
diff --git a/src/components/HGNForm/pages/HGNFormListPage.jsx b/src/components/HGNForm/pages/HGNFormListPage.jsx
new file mode 100644
index 00000000000..528510e7a62
--- /dev/null
+++ b/src/components/HGNForm/pages/HGNFormListPage.jsx
@@ -0,0 +1,63 @@
+import axios from 'axios';
+import { useEffect, useState } from 'react';
+import { Link } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+import { ENDPOINTS } from '~/utils/URL';
+import styles from '../styles/HGNFormListPage.module.css';
+
+const HGN_QUESTIONNAIRE_NAME = 'HGN Development Team Questionnaire';
+const HGN_QUESTIONNAIRE_DESCRIPTION =
+ 'Your answers are used for team collaboration and placement based on your interests and strengths.';
+
+function HGNFormListPage() {
+ const [forms, setForms] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(false);
+ const darkMode = useSelector(state => state.theme.darkMode);
+
+ useEffect(() => {
+ const loadForms = async () => {
+ try {
+ const response = await axios.get(ENDPOINTS.HGN_FORMS);
+ setForms(Array.isArray(response.data?.data) ? response.data.data : []);
+ } catch {
+ setError(true);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ loadForms();
+ }, []);
+
+ return (
+
+ HGN Forms
+
+
+
{HGN_QUESTIONNAIRE_NAME}
+
{HGN_QUESTIONNAIRE_DESCRIPTION}
+
+
+ {loading &&
Loading forms...
}
+ {error && (
+
+ Unable to load additional forms. Please try again later.
+
+ )}
+ {!loading && !error && !forms.length && (
+
No additional forms are currently available.
+ )}
+
+ {forms.map(form => (
+
+ {form.formName}
+ {form.description || 'No description available.'}
+
+ ))}
+
+
+ );
+}
+
+export default HGNFormListPage;
diff --git a/src/components/HGNForm/pages/__tests__/HGNFormListPage.test.jsx b/src/components/HGNForm/pages/__tests__/HGNFormListPage.test.jsx
new file mode 100644
index 00000000000..24cc2703868
--- /dev/null
+++ b/src/components/HGNForm/pages/__tests__/HGNFormListPage.test.jsx
@@ -0,0 +1,85 @@
+import { render, screen } from '@testing-library/react';
+import { MemoryRouter } from 'react-router-dom';
+import { Provider } from 'react-redux';
+import configureMockStore from 'redux-mock-store';
+import axios from 'axios';
+import HGNFormListPage from '../HGNFormListPage';
+import { ENDPOINTS } from '~/utils/URL';
+
+vi.mock('axios');
+
+const mockStore = configureMockStore([]);
+
+const renderFormList = () =>
+ render(
+
+
+
+
+ ,
+ );
+
+describe('HGNFormListPage', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('renders generic form metadata and the built-in HGN questionnaire entry', async () => {
+ axios.get.mockResolvedValue({
+ data: {
+ data: [
+ {
+ _id: 'generic-form',
+ formName: 'Mern Stack Developer',
+ description: 'Find your best development team fit.',
+ },
+ ],
+ },
+ });
+
+ renderFormList();
+
+ expect(await screen.findByText('Mern Stack Developer')).toBeInTheDocument();
+ expect(screen.getByText('Find your best development team fit.')).toBeInTheDocument();
+ expect(screen.getByText('HGN Development Team Questionnaire')).toBeInTheDocument();
+ expect(axios.get).toHaveBeenCalledWith(ENDPOINTS.HGN_FORMS);
+ expect(
+ screen.getByRole('link', { name: /HGN Development Team Questionnaire/i }),
+ ).toHaveAttribute('href', '/hgnform/page1');
+ });
+
+ it('handles forms without descriptions safely', async () => {
+ axios.get.mockResolvedValue({ data: { data: [{ _id: 'form-2', formName: 'Existing Form' }] } });
+
+ renderFormList();
+
+ expect(await screen.findByText('Existing Form')).toBeInTheDocument();
+ expect(screen.getByText('No description available.')).toBeInTheDocument();
+ });
+
+ it('keeps the HGN questionnaire available when the generic form list is empty', async () => {
+ axios.get.mockResolvedValue({ data: { data: [] } });
+
+ renderFormList();
+
+ expect(
+ await screen.findByText('No additional forms are currently available.'),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByRole('link', { name: /HGN Development Team Questionnaire/i }),
+ ).toHaveAttribute('href', '/hgnform/page1');
+ });
+
+ it('handles an API failure without exposing backend errors', async () => {
+ axios.get.mockRejectedValue(new Error('Server error'));
+
+ renderFormList();
+
+ expect(
+ await screen.findByText('Unable to load additional forms. Please try again later.'),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByRole('link', { name: /HGN Development Team Questionnaire/i }),
+ ).toBeInTheDocument();
+ });
+});
diff --git a/src/components/HGNForm/pages/__tests__/Page1.test.jsx b/src/components/HGNForm/pages/__tests__/Page1.test.jsx
new file mode 100644
index 00000000000..b27782f06ba
--- /dev/null
+++ b/src/components/HGNForm/pages/__tests__/Page1.test.jsx
@@ -0,0 +1,67 @@
+import { render, screen } from '@testing-library/react';
+import { MemoryRouter } from 'react-router-dom';
+import { Provider } from 'react-redux';
+import { createStore, combineReducers, applyMiddleware } from 'redux';
+import thunk from 'redux-thunk';
+import axios from 'axios';
+import Page1 from '../Page1';
+import HGNFormReducer from '~/reducers/hgnFormReducers';
+import { allUserProfilesBasicInfoReducer } from '~/reducers/allUserProfilesBasicInfoReducer';
+import { ENDPOINTS } from '~/utils/URL';
+
+vi.mock('axios');
+
+const mockUser = {
+ userid: 'user-123',
+ firstName: 'Ada',
+ lastName: 'Lovelace',
+ email: 'ada@example.com',
+ role: 'Volunteer',
+};
+
+const buildStore = () =>
+ createStore(
+ combineReducers({
+ theme: (state = { darkMode: false }) => state,
+ auth: (state = { user: mockUser }) => state,
+ allUserProfilesBasicInfo: allUserProfilesBasicInfoReducer,
+ hgnForm: HGNFormReducer,
+ }),
+ applyMiddleware(thunk),
+ );
+
+const renderPage1 = () =>
+ render(
+
+
+
+
+ ,
+ );
+
+describe('Page1 basic info loading', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('renders the questionnaire once basic info resolves successfully', async () => {
+ axios.get.mockResolvedValueOnce({
+ data: { _id: 'user-123', firstName: 'Ada', lastName: 'Lovelace' },
+ });
+
+ renderPage1();
+
+ expect(await screen.findByRole('textbox', { name: /^Name/i })).toHaveValue('Ada Lovelace');
+ expect(axios.get).toHaveBeenCalledWith(ENDPOINTS.USER_PROFILE_BASIC_INFO_BY_ID('user-123'));
+ });
+
+ it('does not stay on the loading spinner when the basic info request is rejected (400)', async () => {
+ axios.get.mockRejectedValueOnce({
+ response: { status: 400, data: { error: 'Source parameter is required' } },
+ });
+
+ renderPage1();
+
+ expect(await screen.findByRole('textbox', { name: /^Name/i })).toBeInTheDocument();
+ });
+});
diff --git a/src/components/HGNForm/questionpages/FollowupQuestions.jsx b/src/components/HGNForm/questionpages/FollowupQuestions.jsx
index c2fa278204d..de597ae1115 100644
--- a/src/components/HGNForm/questionpages/FollowupQuestions.jsx
+++ b/src/components/HGNForm/questionpages/FollowupQuestions.jsx
@@ -232,7 +232,7 @@ function FollowupQuestions() {
const handleEdit = () => {
dispatch(setformData(newVolunteer));
- navigate.push('/hgnform');
+ navigate.push('/hgnform/page1');
};
const handleTextChange = e => {
diff --git a/src/components/HGNForm/questionpages/InfoForm.jsx b/src/components/HGNForm/questionpages/InfoForm.jsx
index de394f20c88..99c5c25ee18 100644
--- a/src/components/HGNForm/questionpages/InfoForm.jsx
+++ b/src/components/HGNForm/questionpages/InfoForm.jsx
@@ -28,6 +28,9 @@ function InfoForm() {
const [touched, setTouched] = useState(false);
const [isSlackNameWarning, setIsSlackNameWarning] = useState(false);
+ // Lock the name field only when it was prefilled from the profile,
+ // not whenever the user has typed something.
+ const [nameFromProfile, setNameFromProfile] = useState(false);
const isValid = newVolunteer.name.length >= 2;
const showError = touched && !isValid;
@@ -48,10 +51,14 @@ function InfoForm() {
if (user && formData) {
const firstName = userProfile?.firstName || user?.firstName || '';
const lastName = userProfile?.lastName || user?.lastName || '';
+ setNameFromProfile(Boolean(firstName && lastName));
setNewVolunteer(prevState => ({
...formData,
...prevState, // This preserves any user input
- name: firstName && lastName ? `${firstName} ${lastName}` : '',
+ name:
+ firstName && lastName
+ ? `${firstName} ${lastName}`
+ : prevState.name || formData.name || '',
email: user.email,
github: prevState.github || formData.github || '', // Preserve GitHub value
slack: prevState.slack || formData.slack || '', // Preserve
@@ -170,6 +177,7 @@ function InfoForm() {
pattern=".{2,}"
title="Name must be at least 2 characters long"
placeholder="Your First and Last Name"
+ disabled={nameFromProfile}
/>
{showError && (
@@ -189,7 +197,7 @@ function InfoForm() {
onChange={e => setNewVolunteer({ ...newVolunteer, email: e.target.value })}
required
placeholder="Your Email"
- disabled={!!(user.email !== undefined || user.email !== null)}
+ disabled={!!user.email}
/>
diff --git a/src/components/HGNForm/styles/HGNFormListPage.module.css b/src/components/HGNForm/styles/HGNFormListPage.module.css
new file mode 100644
index 00000000000..d382e4c5b79
--- /dev/null
+++ b/src/components/HGNForm/styles/HGNFormListPage.module.css
@@ -0,0 +1,49 @@
+.container {
+ max-width: 900px;
+ margin: 0 auto;
+ padding: 32px 16px;
+}
+
+.formList {
+ display: grid;
+ gap: 16px;
+}
+
+.formCard {
+ display: block;
+ padding: 20px;
+ color: inherit;
+ text-decoration: none;
+ background: #fff;
+ border: 1px solid #d9d9d9;
+ border-radius: 6px;
+}
+
+.formCard:hover,
+.formCard:focus {
+ border-color: #00754a;
+}
+
+.darkMode {
+ color: #f0f0f0;
+}
+
+.darkMode .formCard {
+ background: #2a2a2a;
+ border-color: #555;
+}
+
+.darkMode .formCard p {
+ color: #f0f0f0;
+}
+
+.message {
+ padding: 32px 16px;
+ text-align: center;
+}
+
+@media (width <= 600px) {
+ .container {
+ padding: 24px 12px;
+ }
+}
diff --git a/src/components/HGNHelpSkillsDashboard/CommunityMembersPage.jsx b/src/components/HGNHelpSkillsDashboard/CommunityMembersPage.jsx
index b37c24c70ba..91be446e23b 100644
--- a/src/components/HGNHelpSkillsDashboard/CommunityMembersPage.jsx
+++ b/src/components/HGNHelpSkillsDashboard/CommunityMembersPage.jsx
@@ -12,17 +12,16 @@ function CommunityMembersPage() {
const [selectedSkills, setSelectedSkills] = useState(location.state?.initialSkills || []);
const [selectedPreferences, setSelectedPreferences] = useState([]);
const [searchQuery, setSearchQuery] = useState('');
- const [sortBy, setSortBy] = useState('name');
const [sortOrder, setSortOrder] = useState('asc');
const [currentPage, setCurrentPage] = useState(1);
+ const softwareDevTeamOnly = Boolean(location.state?.softwareDevTeamOnly);
const darkMode = useSelector(state => state.theme.darkMode);
- const handleSortByChange = event => setSortBy(event.target.value);
const handleSortOrderChange = event => setSortOrder(event.target.value);
useEffect(() => {
setCurrentPage(1);
- }, [selectedSkills, selectedPreferences, searchQuery, sortBy, sortOrder]);
+ }, [selectedSkills, selectedPreferences, searchQuery, sortOrder, softwareDevTeamOnly]);
return (
@@ -31,21 +30,8 @@ function CommunityMembersPage() {
-
- Sort by:
-
-
-
- Order:
+ Sort by name:
@@ -79,10 +65,10 @@ function CommunityMembersPage() {
selectedSkills={selectedSkills}
selectedPreferences={selectedPreferences}
searchQuery={searchQuery.trim()}
- sortBy={sortBy}
sortOrder={sortOrder}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
+ softwareDevTeamOnly={softwareDevTeamOnly}
/>
diff --git a/src/components/HGNHelpSkillsDashboard/FeedbackModal.jsx b/src/components/HGNHelpSkillsDashboard/FeedbackModal.jsx
index f02248352dc..e15326b1548 100644
--- a/src/components/HGNHelpSkillsDashboard/FeedbackModal.jsx
+++ b/src/components/HGNHelpSkillsDashboard/FeedbackModal.jsx
@@ -2,13 +2,18 @@ import axios from 'axios';
import PropTypes from 'prop-types';
import { useEffect, useState } from 'react';
import { connect } from 'react-redux';
+import { useHistory } from 'react-router-dom';
import { toast } from 'react-toastify';
import { Button, Input, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import { ENDPOINTS } from '../../utils/URL';
import styles from './FeedbackModal.module.css';
function FeedbackModal({ authUser }) {
- const [isOpen, setIsOpen] = useState(false);
+ const history = useHistory();
+ // This component is only reached by navigating directly to /hgnhelp/feedback
+ // (see routes.jsx), so arriving here is itself the request to open the form -
+ // it must not stay hidden behind the "shouldShow" auto-popup check below.
+ const [isOpen, setIsOpen] = useState(true);
const [helpRequestId, setHelpRequestId] = useState(null);
const [receivedHelp, setReceivedHelp] = useState('');
const [activeMembers, setActiveMembers] = useState([{ name: '', rating: 0, selectedUser: null }]);
@@ -28,22 +33,22 @@ function FeedbackModal({ authUser }) {
const [showInactiveDropdown, setShowInactiveDropdown] = useState({});
useEffect(() => {
- const checkModal = async () => {
+ const fetchPendingHelpRequest = async () => {
try {
const userId = authUser?.userid || 'test-user-id';
const response = await axios.get(ENDPOINTS.HELP_REQUEST_CHECK_MODAL(userId));
- if (response.data.shouldShow) {
- setIsOpen(true);
+ if (response.data.helpRequestId) {
setHelpRequestId(response.data.helpRequestId);
}
} catch (error) {
- // Silently fail - modal simply won't show
+ // No pending help request found for this user - the form can still be
+ // submitted without one, so this is not fatal to showing the page.
}
};
- checkModal();
+ fetchPendingHelpRequest();
}, [authUser]);
// Fetch team members on mount
@@ -63,6 +68,7 @@ function FeedbackModal({ authUser }) {
const handleClose = () => {
setIsOpen(false);
+ history.push('/dashboard');
};
const handleClosePermanently = async () => {
@@ -72,6 +78,7 @@ function FeedbackModal({ authUser }) {
await axios.post(ENDPOINTS.FEEDBACK_CLOSE_PERMANENTLY, { userId });
toast.success('This modal will not appear again.');
setIsOpen(false);
+ history.push('/dashboard');
} catch (error) {
toast.error('Failed to save. Please try again.');
}
@@ -119,6 +126,7 @@ function FeedbackModal({ authUser }) {
await axios.post(ENDPOINTS.FEEDBACK_SUBMIT, feedbackData);
toast.success('Thank you for your feedback!');
setIsOpen(false);
+ history.push('/dashboard');
} catch (error) {
toast.error('Failed to submit feedback. Please try again.');
}
diff --git a/src/components/HGNHelpSkillsDashboard/RankedUserList.jsx b/src/components/HGNHelpSkillsDashboard/RankedUserList.jsx
index b852da737c2..ae6ff0e4a0b 100644
--- a/src/components/HGNHelpSkillsDashboard/RankedUserList.jsx
+++ b/src/components/HGNHelpSkillsDashboard/RankedUserList.jsx
@@ -1,35 +1,100 @@
-import axios from 'axios';
import PropTypes from 'prop-types';
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
+import httpService from '../../services/httpService';
+import { ENDPOINTS } from '~/utils/URL';
+import { formatSkillName } from './FilerData.js';
import styles from './style/RankedUserList.module.css';
import UserCard from './UserCard';
+const TOP_SKILLS_PER_SECTION = 4;
const PAGE_SIZE = 20;
+const SOFTWARE_DEV_TEAM_NAME = 'software development team';
+const SOFTWARE_DEV_TEAM_ALIASES = new Set([
+ SOFTWARE_DEV_TEAM_NAME,
+ 'hgn software development team',
+]);
+
+const getMemberFullName = member =>
+ `${member?.firstName || ''} ${member?.lastName || ''}`.trim().toLowerCase();
+
+/**
+ * Restrict ranked users to Software Development Team members.
+ * Fail closed: missing team or lookup errors return [] (never the full ranked list).
+ * Prefer email; only fall back to name when the ranked user has no email.
+ */
+const filterToSoftwareDevTeam = async users => {
+ try {
+ const teamsResponse = await httpService.get(ENDPOINTS.TEAM);
+ const teams = Array.isArray(teamsResponse.data) ? teamsResponse.data : [];
+ const softwareDevTeam = teams.find(team =>
+ SOFTWARE_DEV_TEAM_ALIASES.has(team.teamName?.trim().toLowerCase()),
+ );
+
+ if (!softwareDevTeam?._id) {
+ // Team not found — do not leak non-team ranked users.
+ return [];
+ }
+
+ const membersResponse = await httpService.get(ENDPOINTS.TEAM_MEMBERS(softwareDevTeam._id));
+ const members = Array.isArray(membersResponse.data) ? membersResponse.data : [];
+
+ const memberEmails = new Set(
+ members.map(member => (member.email || '').trim().toLowerCase()).filter(Boolean),
+ );
+ const memberNames = new Set(members.map(getMemberFullName).filter(Boolean));
+ const memberIds = new Set(
+ members.map(member => (member._id || member.userId || '').toString()).filter(Boolean),
+ );
+
+ return users.filter(user => {
+ const userId = (user.userId || user.user_id || '').toString();
+ if (userId && memberIds.has(userId)) return true;
+
+ const email = (user.email || '').trim().toLowerCase();
+ if (email) {
+ // Email is authoritative: never allow a name-only match to override a mismatch.
+ return memberEmails.has(email);
+ }
+
+ const name = (user.name || '').trim().toLowerCase();
+ return Boolean(name && memberNames.has(name));
+ });
+ } catch {
+ // Team membership lookup failed — show empty list rather than all ranked users.
+ return [];
+ }
+};
const extractSkillEntries = skillData => {
if (!skillData || typeof skillData !== 'object') return [];
if (Array.isArray(skillData)) {
return skillData.flatMap(skill => {
- if (typeof skill === 'string') return [{ name: skill, rating: undefined }];
+ if (typeof skill === 'string')
+ return [{ name: skill, rating: undefined, section: 'default' }];
if (skill && typeof skill === 'object') {
const name = skill.name || skill.skill || skill.label || skill.type;
- return name ? [{ name, rating: skill.rating ?? skill.score ?? undefined }] : [];
+ return name
+ ? [{ name, rating: skill.rating ?? skill.score ?? undefined, section: 'default' }]
+ : [];
}
return [];
});
}
- return Object.values(skillData).flatMap(section => {
+ return Object.entries(skillData).flatMap(([sectionName, section]) => {
if (!section || typeof section !== 'object') return [];
if (Array.isArray(section)) {
return section.flatMap(skill => {
- if (typeof skill === 'string') return [{ name: skill, rating: undefined }];
+ if (typeof skill === 'string')
+ return [{ name: skill, rating: undefined, section: sectionName }];
if (skill && typeof skill === 'object') {
const name = skill.name || skill.skill || skill.label || skill.type;
- return name ? [{ name, rating: skill.rating ?? skill.score ?? undefined }] : [];
+ return name
+ ? [{ name, rating: skill.rating ?? skill.score ?? undefined, section: sectionName }]
+ : [];
}
return [];
});
@@ -38,15 +103,16 @@ const extractSkillEntries = skillData => {
return Object.entries(section).map(([skillName, rating]) => ({
name: skillName,
rating: typeof rating === 'number' ? rating : undefined,
+ section: sectionName,
}));
});
};
const normalizeUser = user => {
- if (Array.isArray(user.topSkills) && user.topSkills.length > 0) return user;
-
const rawSkills = user.skills;
- const skillEntries = extractSkillEntries(rawSkills);
+ const skillEntries = Array.isArray(user.topSkills)
+ ? user.topSkills.map(name => ({ name, rating: undefined, section: 'default' }))
+ : extractSkillEntries(rawSkills);
const uniqueSkills = Array.from(
new Map(
@@ -54,64 +120,76 @@ const normalizeUser = user => {
).values(),
);
- const sortedSkills = uniqueSkills
- .sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0))
- .map(entry => entry.name);
+ // Filtering and search use every rated skill, matching the backend's /community rule;
+ // only the card's display list is trimmed to the top skills of each section.
+ const skillsBySection = uniqueSkills.reduce((acc, entry) => {
+ const section = entry.section || 'default';
+ if (!acc[section]) acc[section] = [];
+ acc[section].push(entry);
+ return acc;
+ }, {});
+
+ const topSkillNames = Object.values(skillsBySection).flatMap(entries =>
+ entries
+ .slice()
+ .sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0))
+ .slice(0, TOP_SKILLS_PER_SECTION)
+ .map(entry => entry.name),
+ );
return {
...user,
- topSkills: sortedSkills,
- skills: sortedSkills,
+ allSkills: uniqueSkills.map(entry => entry.name),
+ topSkills: topSkillNames,
};
};
+// Put the skills the viewer filtered by at the front so they are never pushed out of view.
+const withMatchedSkillsFirst = (user, selectedSkills) => {
+ const matched = selectedSkills.filter(skill =>
+ (user.allSkills || []).some(own => own.toLowerCase() === skill.toLowerCase()),
+ );
+ const names = Array.from(new Set([...matched, ...(user.topSkills || [])]));
+ return { ...user, displaySkills: names.map(formatSkillName) };
+};
+
function RankedUserList({
- selectedSkills,
- selectedPreferences,
- searchQuery,
- sortBy,
- sortOrder,
+ selectedSkills = [],
+ selectedPreferences = [],
+ searchQuery = '',
+ sortOrder = 'asc',
currentPage,
setCurrentPage,
+ softwareDevTeamOnly = false,
}) {
const [allUsers, setAllUsers] = useState([]);
const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
const darkMode = useSelector(state => state.theme.darkMode);
+ // Load every community member once, then filter/search/sort on the client so all
+ // skills in the filter list work regardless of the backend's skill-key handling.
useEffect(() => {
const fetchUsers = async () => {
setLoading(true);
-
+ setError(null);
try {
- const params = {};
- const hasFilters =
- (selectedSkills && selectedSkills.length > 0) ||
- (selectedPreferences && selectedPreferences.length > 0) ||
- (searchQuery && searchQuery.trim().length > 0);
-
- if (selectedSkills && selectedSkills.length > 0) {
- params.skills = selectedSkills.join(',');
- }
-
- if (selectedPreferences && selectedPreferences.length > 0) {
- params.preferences = selectedPreferences.join(',');
- }
-
- if (searchQuery && searchQuery.trim().length > 0) {
- params.search = searchQuery.trim();
- }
+ // Help-request flow always uses ranked questionnaire data (Software Dev skills survey).
+ const endpoint = softwareDevTeamOnly
+ ? ENDPOINTS.HGN_FORM_RANKED
+ : ENDPOINTS.HGN_COMMUNITY_MEMBERS;
- const endpoint = hasFilters
- ? `${process.env.REACT_APP_APIENDPOINT}/hgnform/ranked`
- : `${process.env.REACT_APP_APIENDPOINT}/hgnHelp/community`;
+ const response = await httpService.get(endpoint);
+ let users = Array.isArray(response.data) ? response.data.map(normalizeUser) : [];
- if (!hasFilters && sortBy === 'name' && sortOrder) {
- params.sortOrder = sortOrder;
+ if (softwareDevTeamOnly) {
+ users = await filterToSoftwareDevTeam(users);
}
- const response = await axios.get(endpoint, { params });
- setAllUsers(response.data.map(normalizeUser));
+ setAllUsers(users);
} catch (err) {
+ const detail = err.response?.status ? ` (${err.response.status})` : '';
+ setError(`Unable to load community members. Please try again later.${detail}`);
setAllUsers([]);
} finally {
setLoading(false);
@@ -119,27 +197,45 @@ function RankedUserList({
};
fetchUsers();
- }, [selectedSkills, selectedPreferences, searchQuery, sortOrder, sortBy]);
+ }, [softwareDevTeamOnly]);
- const filteredUsers = searchQuery
- ? allUsers.filter(user => {
- const name = (user.name || '').toLowerCase();
- const skills = (user.topSkills || []).join(' ').toLowerCase();
- const query = searchQuery.toLowerCase();
+ const query = searchQuery.trim().toLowerCase();
- return name.includes(query) || skills.includes(query);
- })
- : allUsers;
+ const filteredUsers = allUsers.filter(user => {
+ const userSkills = (user.allSkills || []).map(skill => skill.toLowerCase());
- const sortedUsers = [...filteredUsers].sort((a, b) => {
- if (sortBy === 'score') {
- const scoreA = typeof a.score === 'number' ? a.score : -Infinity;
- const scoreB = typeof b.score === 'number' ? b.score : -Infinity;
+ if (selectedSkills && selectedSkills.length > 0) {
+ const matchesSkills = selectedSkills.every(skill => userSkills.includes(skill.toLowerCase()));
+ if (!matchesSkills) return false;
+ }
+
+ if (selectedPreferences && selectedPreferences.length > 0) {
+ const userPreferences = new Set((user.preferences || []).map(pref => pref.toLowerCase()));
+ const matchesPreferences = selectedPreferences.every(pref =>
+ userPreferences.has(pref.toLowerCase()),
+ );
+ if (!matchesPreferences) return false;
+ }
- if (scoreA < scoreB) return sortOrder === 'desc' ? 1 : -1;
- if (scoreA > scoreB) return sortOrder === 'desc' ? -1 : 1;
+ if (query) {
+ const name = (user.name || '').toLowerCase();
+ const displaySkills = (user.allSkills || []).map(skill =>
+ formatSkillName(skill).toLowerCase(),
+ );
+ // Match against both the raw skill keys and their human-readable labels,
+ // since the keys (e.g. "UnitTest") don't contain the words a member
+ // would actually type (e.g. "testing").
+ const matchesQuery =
+ name.includes(query) ||
+ userSkills.some(skill => skill.includes(query)) ||
+ displaySkills.some(skill => skill.includes(query));
+ if (!matchesQuery) return false;
}
+ return true;
+ });
+
+ const sortedUsers = [...filteredUsers].sort((a, b) => {
const nameA = (a.name || '').toLowerCase();
const nameB = (b.name || '').toLowerCase();
@@ -151,32 +247,27 @@ function RankedUserList({
const totalPages = Math.ceil(sortedUsers.length / PAGE_SIZE);
const safeCurrentPage = Math.min(Math.max(currentPage, 1), Math.max(totalPages, 1));
-
const startIndex = (safeCurrentPage - 1) * PAGE_SIZE;
const paginatedUsers = sortedUsers.slice(startIndex, startIndex + PAGE_SIZE);
- const handlePrevious = () => {
- setCurrentPage(page => Math.max(page - 1, 1));
- };
-
- const handleNext = () => {
- setCurrentPage(page => Math.min(page + 1, totalPages));
- };
-
- if (loading) {
- return
Loading ranked users...
;
- }
+ const handlePrevious = () => setCurrentPage(page => Math.max(page - 1, 1));
+ const handleNext = () => setCurrentPage(page => Math.min(page + 1, totalPages));
+ if (loading) return
Loading community members...
;
+ if (error) return
{error}
;
if (!sortedUsers.length) {
- return
No users found.
;
+ const emptyMessage = softwareDevTeamOnly
+ ? 'No Software Development Team members match this skill yet'
+ : 'No members found.';
+ return
{emptyMessage}
;
}
return (
-
-
+
+
{paginatedUsers.map(user => (
-
@@ -214,10 +305,10 @@ RankedUserList.propTypes = {
selectedSkills: PropTypes.arrayOf(PropTypes.string),
selectedPreferences: PropTypes.arrayOf(PropTypes.string),
searchQuery: PropTypes.string,
- sortBy: PropTypes.string,
sortOrder: PropTypes.string,
currentPage: PropTypes.number.isRequired,
setCurrentPage: PropTypes.func.isRequired,
+ softwareDevTeamOnly: PropTypes.bool,
};
export default RankedUserList;
diff --git a/src/components/HGNHelpSkillsDashboard/SkillsOverviewPage.jsx b/src/components/HGNHelpSkillsDashboard/SkillsOverviewPage.jsx
index 191480bc0d2..aa38bfb825c 100644
--- a/src/components/HGNHelpSkillsDashboard/SkillsOverviewPage.jsx
+++ b/src/components/HGNHelpSkillsDashboard/SkillsOverviewPage.jsx
@@ -1,5 +1,9 @@
-import { useState } from 'react';
+import jwtDecode from 'jwt-decode';
+import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
+import config from '~/config.json';
+import httpService from '~/services/httpService';
+import { ENDPOINTS } from '~/utils/URL';
import RadarChart from '../HGNSkillsDashboard/SkillsProfilePage/components/RadarChart';
import Accordion from './Accordion';
import { PreferenceFilterButtons, SkillFilterButtons } from './FilterButtons';
@@ -11,12 +15,52 @@ function SkillsOverviewPage() {
const [selectedSkills, setSelectedSkills] = useState([]);
const [selectedPreferences, setSelectedPreferences] = useState([]);
const [searchQuery, setSearchQuery] = useState('');
+ const [currentPage, setCurrentPage] = useState(1);
const darkMode = useSelector(state => state.theme.darkMode);
- const userProfile = useSelector(state => state.userProfile);
+ const [profileData, setProfileData] = useState(null);
+ const [loadingProfile, setLoadingProfile] = useState(true);
+ // The skills survey lives behind its own endpoint and is not part of state.userProfile,
+ // so this page has to fetch it itself rather than read it from the store.
+ useEffect(() => {
+ const token = localStorage.getItem(config.tokenKey);
+ let userId;
+ try {
+ userId = token ? jwtDecode(token)?.userid : undefined;
+ } catch (_) {
+ userId = undefined;
+ }
+
+ if (!userId) {
+ setLoadingProfile(false);
+ return;
+ }
+
+ httpService.setjwt(token);
+ httpService
+ .get(ENDPOINTS.SKILLS_PROFILE(userId))
+ // isPlaceholder means the user has not filled in the survey yet
+ .then(({ data }) => setProfileData(data?.isPlaceholder ? null : data))
+ .catch(() => setProfileData(null))
+ .finally(() => setLoadingProfile(false));
+ }, []);
+ useEffect(() => {
+ setCurrentPage(1);
+ }, [selectedSkills, selectedPreferences, searchQuery]);
const hasFilters =
selectedSkills.length > 0 || selectedPreferences.length > 0 || searchQuery.trim().length > 0;
+ let radarContent;
+ if (loadingProfile) {
+ radarContent =
Loading your skills radar...
;
+ } else if (profileData?.skillInfo) {
+ radarContent =
;
+ } else {
+ radarContent = (
+
Complete the skills survey to view your radar chart.
+ );
+ }
+
return (
Skills Overview
@@ -24,13 +68,7 @@ function SkillsOverviewPage() {
{/* Radar Chart for logged-in user */}
Your Skills Radar
-
- {userProfile?.skillInfo ? (
-
- ) : (
-
Complete the skills survey to view your radar chart.
- )}
-
+
{radarContent}
{/* Search Bar */}
@@ -62,6 +100,8 @@ function SkillsOverviewPage() {
selectedSkills={selectedSkills}
selectedPreferences={selectedPreferences}
searchQuery={searchQuery.trim()}
+ currentPage={currentPage}
+ setCurrentPage={setCurrentPage}
/>
) : (
diff --git a/src/components/HGNHelpSkillsDashboard/TeamCard/TeamCard.jsx b/src/components/HGNHelpSkillsDashboard/TeamCard/TeamCard.jsx
index 7878b385d99..bde234c4b70 100644
--- a/src/components/HGNHelpSkillsDashboard/TeamCard/TeamCard.jsx
+++ b/src/components/HGNHelpSkillsDashboard/TeamCard/TeamCard.jsx
@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import { useState } from 'react';
import { toast } from 'react-toastify';
+import { useSelector } from 'react-redux';
import styles from './TeamCard.module.css';
import { TeamMemberRow } from './TeamMemberRow';
@@ -55,6 +56,7 @@ export default function TeamCard() {
const [selectedMembers, setSelectedMembers] = useState([]);
const [confirmAction, setConfirmAction] = useState(null);
+ const darkMode = useSelector(state => state.theme.darkMode);
const toggleSelect = id => {
setSelectedMembers(prev => (prev.includes(id) ? prev.filter(m => m !== id) : [...prev, id]));
@@ -88,7 +90,7 @@ export default function TeamCard() {
const noneSelected = selectedMembers.length === 0;
return (
-
+
diff --git a/src/components/HGNHelpSkillsDashboard/TeamCard/TeamCard.module.css b/src/components/HGNHelpSkillsDashboard/TeamCard/TeamCard.module.css
index 37d00552bec..17d5853dd2d 100644
--- a/src/components/HGNHelpSkillsDashboard/TeamCard/TeamCard.module.css
+++ b/src/components/HGNHelpSkillsDashboard/TeamCard/TeamCard.module.css
@@ -275,16 +275,34 @@
background: #5a6268;
}
-@media (prefers-color-scheme: dark) {
- .modal {
- background: #2b2b2b;
- }
-
- .modalTitle {
- color: #fff;
- }
-
- .modalText {
- color: #ccc;
- }
+/* ── Dark Mode ── */
+.darkMode .teamCardContainer {
+ border-color: #444;
+}
+
+.darkMode .teamCardTitle,
+.darkMode .teamCardRatingLabel {
+ color: #ccc;
+}
+
+.darkMode .teamMemberName,
+.darkMode .showMoreText {
+ color: #f0f0f0;
+}
+
+.darkMode .teamMemberRowSelected {
+ background: rgb(0 123 255 / 15%);
+}
+
+.darkMode .modal {
+ background: #2b2b2b;
+ box-shadow: 0 8px 24px rgb(0 0 0 / 60%);
+}
+
+.darkMode .modalTitle {
+ color: #fff;
+}
+
+.darkMode .modalText {
+ color: #ccc;
}
\ No newline at end of file
diff --git a/src/components/HGNHelpSkillsDashboard/UserCard.jsx b/src/components/HGNHelpSkillsDashboard/UserCard.jsx
index 3fd84c6ea6c..2e6b08f6ae6 100644
--- a/src/components/HGNHelpSkillsDashboard/UserCard.jsx
+++ b/src/components/HGNHelpSkillsDashboard/UserCard.jsx
@@ -1,24 +1,31 @@
+import { MdEmail } from 'react-icons/md';
import styles from './style/UserCard.module.css';
import avatar from './style/avatar.png';
-import emailIcon from './style/email_icon.png';
import slackIcon from './style/slack_icon.png';
import { Link } from 'react-router-dom';
import { useSelector } from 'react-redux';
+import { formatSkillName } from './FilerData.js';
+
+const getNormalizedSkills = ({ displaySkills, topSkills, skills }) => {
+ if (Array.isArray(displaySkills)) return displaySkills;
+ if (Array.isArray(topSkills)) return topSkills.map(formatSkillName);
+ if (Array.isArray(skills)) {
+ return skills
+ .map(skill => {
+ if (typeof skill === 'string') return skill;
+ return skill.name || skill.skill || skill.label || skill.type || '';
+ })
+ .filter(Boolean)
+ .map(formatSkillName);
+ }
+ return [];
+};
function UserCard({ user }) {
- const { userId, name, email, slack, score, topSkills, skills } = user;
+ const { userId, name, email, slack, score, topSkills, displaySkills, skills } = user;
const darkMode = useSelector(state => state.theme.darkMode);
- const normalizedSkills = Array.isArray(topSkills)
- ? topSkills
- : Array.isArray(skills)
- ? skills
- .map(skill => {
- if (typeof skill === 'string') return skill;
- return skill.name || skill.skill || skill.label || skill.type || '';
- })
- .filter(Boolean)
- : [];
+ const normalizedSkills = getNormalizedSkills({ displaySkills, topSkills, skills });
return (
@@ -36,7 +43,7 @@ function UserCard({ user }) {
)}
{email && (
-
+
{email}
)}
@@ -49,19 +56,23 @@ function UserCard({ user }) {
-
- Score:
- = 5 ? styles.scoreHigh : styles.scoreLow}`}
- >
- {score}
-
- / 10
-
+ {typeof score === 'number' && (
+
+ Score:
+ = 5 ? styles.scoreHigh : styles.scoreLow}`}
+ >
+ {score}
+
+ / 10
+
+ )}
Top Skills:
-
{normalizedSkills.join(', ')}
+
+ {normalizedSkills.length > 0 ? normalizedSkills.join(', ') : 'No skills listed'}
+
diff --git a/src/components/HGNHelpSkillsDashboard/style/UserCard.module.css b/src/components/HGNHelpSkillsDashboard/style/UserCard.module.css
index 6f1078c3251..0a919fec084 100644
--- a/src/components/HGNHelpSkillsDashboard/style/UserCard.module.css
+++ b/src/components/HGNHelpSkillsDashboard/style/UserCard.module.css
@@ -72,6 +72,8 @@
.contactIcon {
width: 18px;
height: 18px;
+ color: #616161;
+ flex-shrink: 0;
}
.scoreSkillsWrapper {
@@ -124,9 +126,6 @@
color: #616161;
line-height: 22px;
overflow-wrap: break-word;
- max-height: 60px;
- overflow: hidden;
- text-overflow: ellipsis;
}
/* --- DARK MODE --- */
@@ -152,6 +151,10 @@
color: #ccc;
}
+.darkMode .contactIcon {
+ color: #ccc;
+}
+
.darkMode .skillsText {
color: #dcdcdc;
}
diff --git a/src/components/HGNSkillsDashboard/SkillsProfilePage/components/ProfileDetails.jsx b/src/components/HGNSkillsDashboard/SkillsProfilePage/components/ProfileDetails.jsx
index 705ee699c3d..e9a2c72cfd8 100644
--- a/src/components/HGNSkillsDashboard/SkillsProfilePage/components/ProfileDetails.jsx
+++ b/src/components/HGNSkillsDashboard/SkillsProfilePage/components/ProfileDetails.jsx
@@ -50,9 +50,6 @@ function ProfileDetails() {
Years of Experience: {' '}
-
- {profileData.skillInfo?.general?.yearsOfExperience || 'N/A'}
-
{isEditing ? (
general?.leadership_skills || 0,
description: 'Skills in team leadership and management',
},
@@ -153,6 +154,7 @@ const SKILL_MAPPINGS = [
},
{
label: 'Markdown & Graphs',
+ shortLabel: 'Markdown/Graphs',
value: (general, frontend, backend) => frontend?.Documentation || general?.markdown_graphs || 0,
description: 'Markdown writing and data visualization',
},
@@ -234,7 +236,10 @@ function RadarChart({ profileData, compact = true, onSkillsDataReady }) {
const { general = {}, frontend = {}, backend = {} } = profileData?.skillInfo || {};
const chartData = {
- labels: skillsData.map(skill => (compact ? skill.shortLabel || skill.label : skill.label)),
+ // Always prefer the abbreviated label: with 28 dimensions, the wrap-around
+ // labels at the top (last <-> first) sit within a ~26deg arc and collide
+ // regardless of chart size, so shortening text is what actually buys room.
+ labels: skillsData.map(skill => skill.shortLabel || skill.label),
datasets: [
{
label: 'Skills',
@@ -300,6 +305,7 @@ function RadarChart({ profileData, compact = true, onSkillsDataReady }) {
},
pointLabels: {
+ centerPointLabels: true,
font: {
size: function(context) {
const w = context.chart.width;
@@ -321,7 +327,9 @@ function RadarChart({ profileData, compact = true, onSkillsDataReady }) {
},
ticks: {
stepSize: 2,
- display: compact ? false : true,
+ // Always shown: the radial scale numbers are information, not chrome,
+ // so (like labels above) they shouldn't be gated behind compact.
+ display: true,
color: darkMode ? '#dcdcdc' : '#666',
font: { size: 10 },
backdropColor: darkMode ? '#1f1f1f' : '#ffffff',
diff --git a/src/components/HGNSkillsDashboard/SkillsProfilePage/components/__tests__/ProfileDetails.test.jsx b/src/components/HGNSkillsDashboard/SkillsProfilePage/components/__tests__/ProfileDetails.test.jsx
new file mode 100644
index 00000000000..3a4657b6f01
--- /dev/null
+++ b/src/components/HGNSkillsDashboard/SkillsProfilePage/components/__tests__/ProfileDetails.test.jsx
@@ -0,0 +1,61 @@
+import { render, screen } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import configureMockStore from 'redux-mock-store';
+import ProfileDetails from '../ProfileDetails';
+
+const mockStore = configureMockStore([]);
+
+// Note: socialHandles fields are given real values (rather than left empty)
+// so that 'N/A' only ever appears for the field under test here - Slack and
+// GitHub also legitimately fall back to 'N/A' elsewhere on the page, which
+// would otherwise make a page-wide 'N/A' query ambiguous.
+const baseProfileData = {
+ userId: 'user-1',
+ loggedInUserId: 'someone-else',
+ teams: [],
+ skillInfo: {
+ general: {
+ yearsOfExperience: 12,
+ },
+ },
+ contactInfo: {
+ email: 'test@example.com',
+ phone: '555-1234',
+ },
+ socialHandles: {
+ slack: '@testuser',
+ github: 'https://github.com/testuser',
+ },
+};
+
+const renderWithStore = profileData => {
+ const store = mockStore({
+ userSkills: { profileData },
+ theme: { darkMode: false },
+ });
+ return render(
+
+
+ ,
+ );
+};
+
+describe('ProfileDetails - Years of Experience display', () => {
+ it('renders a numeric years-of-experience value exactly once, not doubled', () => {
+ renderWithStore(baseProfileData);
+
+ expect(screen.getByText('12')).toBeInTheDocument();
+ expect(screen.queryByText('1212')).not.toBeInTheDocument();
+ });
+
+ it('renders "N/A" exactly once when no value is set, not doubled', () => {
+ const profileData = {
+ ...baseProfileData,
+ skillInfo: { general: { yearsOfExperience: '' } },
+ };
+ renderWithStore(profileData);
+
+ expect(screen.getByText('N/A')).toBeInTheDocument();
+ expect(screen.queryByText('N/AN/A')).not.toBeInTheDocument();
+ });
+});
diff --git a/src/components/HGNSkillsDashboard/SkillsProfilePage/components/__tests__/RadarChart.test.jsx b/src/components/HGNSkillsDashboard/SkillsProfilePage/components/__tests__/RadarChart.test.jsx
new file mode 100644
index 00000000000..8a05d2d18ed
--- /dev/null
+++ b/src/components/HGNSkillsDashboard/SkillsProfilePage/components/__tests__/RadarChart.test.jsx
@@ -0,0 +1,55 @@
+import { render } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import configureMockStore from 'redux-mock-store';
+import RadarChart from '../RadarChart';
+
+// react-chartjs-2 renders to a real , which jsdom doesn't implement.
+// Capture the props chart.js would receive instead of rendering the chart.
+let lastRadarProps;
+vi.mock('react-chartjs-2', () => ({
+ Radar: props => {
+ lastRadarProps = props;
+ return null;
+ },
+}));
+
+const mockStore = configureMockStore([]);
+
+const profileData = {
+ skillInfo: {
+ general: { leadership_experience: 5, leadership_skills: 6, markdown_graphs: 7 },
+ frontend: {},
+ backend: {},
+ },
+};
+
+const renderChart = compact => {
+ const store = mockStore({ theme: { darkMode: false } });
+ render(
+
+
+ ,
+ );
+};
+
+describe('RadarChart', () => {
+ it('uses abbreviated labels even when compact is false, to avoid overlap at high dimension density', () => {
+ renderChart(false);
+ expect(lastRadarProps.data.labels).toContain('Leadership Skl');
+ expect(lastRadarProps.data.labels).toContain('Markdown/Graphs');
+ expect(lastRadarProps.data.labels).not.toContain('Leadership Skills');
+ });
+
+ it('centers point labels between grid lines to reduce crowding near the wrap-around seam', () => {
+ renderChart(true);
+ expect(lastRadarProps.options.scales.r.pointLabels.centerPointLabels).toBe(true);
+ });
+
+ it('shows the radial scale numbers regardless of compact, so /hgn/profile/skills matches skills-overview', () => {
+ renderChart(true);
+ expect(lastRadarProps.options.scales.r.ticks.display).toBe(true);
+
+ renderChart(false);
+ expect(lastRadarProps.options.scales.r.ticks.display).toBe(true);
+ });
+});
diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx
index 1d9caedad9d..45f9f937028 100644
--- a/src/components/Header/Header.jsx
+++ b/src/components/Header/Header.jsx
@@ -846,6 +846,16 @@ export function Header(props) {
{canAccessUserManagement && {USER_MANAGEMENT} }
{canAccessBadgeManagement && {BADGE_MANAGEMENT} }
{canAccessProjects && {PROJECTS} }
+ {(canAccessProjects || canUpdateTask) && (
+
+ Resolved Tasks
+
+ )}
{canAccessTeams && {TEAMS} }
{canAccessSendEmails && {SEND_EMAILS} }
{canAccessScheduleMeetings && (
diff --git a/src/components/KitchenandInventory/KitchenReports/KitchenReports.jsx b/src/components/KitchenandInventory/KitchenReports/KitchenReports.jsx
new file mode 100644
index 00000000000..8d0169b3c3d
--- /dev/null
+++ b/src/components/KitchenandInventory/KitchenReports/KitchenReports.jsx
@@ -0,0 +1,92 @@
+import { useState } from 'react';
+import { useSelector } from 'react-redux';
+import styles from './KitchenReports.module.css';
+import { REPORT_TABS, DATE_RANGE_OPTIONS, EXPORT_ENDPOINTS_READY } from './kitchenReportsData';
+import useReportExport from './useReportExport';
+
+const EXPORT_UNAVAILABLE_MESSAGE =
+ 'Export is unavailable until the backend export endpoints are deployed.';
+
+export function KitchenReports() {
+ const darkMode = useSelector(state => state.theme.darkMode);
+ const dm = darkMode ? styles.dark : '';
+
+ const [activeTab, setActiveTab] = useState(REPORT_TABS[0].id);
+ const [dateRange, setDateRange] = useState(DATE_RANGE_OPTIONS[0].value);
+ const { exportReport, loadingFormat } = useReportExport();
+
+ const activeTabLabel = REPORT_TABS.find(tab => tab.id === activeTab)?.label;
+
+ return (
+
+
+
+
+
Reports & Analytics
+
Comprehensive insights on usage, spending, waste, and sustainability
+
+
+
+ setDateRange(e.target.value)}
+ aria-label="Select date range"
+ >
+ {DATE_RANGE_OPTIONS.map(option => (
+
+ {option.label}
+
+ ))}
+
+
+ exportReport('pdf', dateRange)}
+ disabled={!EXPORT_ENDPOINTS_READY || loadingFormat !== null}
+ title={EXPORT_ENDPOINTS_READY ? undefined : EXPORT_UNAVAILABLE_MESSAGE}
+ aria-busy={loadingFormat === 'pdf'}
+ >
+ {loadingFormat === 'pdf' ? 'Exporting...' : 'Export PDF'}
+
+
+ exportReport('csv', dateRange)}
+ disabled={!EXPORT_ENDPOINTS_READY || loadingFormat !== null}
+ title={EXPORT_ENDPOINTS_READY ? undefined : EXPORT_UNAVAILABLE_MESSAGE}
+ aria-busy={loadingFormat === 'csv'}
+ >
+ {loadingFormat === 'csv' ? 'Exporting...' : 'Export CSV'}
+
+
+
+
+
+ {REPORT_TABS.map(tab => (
+ setActiveTab(tab.id)}
+ >
+ {tab.label}
+
+ ))}
+
+
+
+ {activeTabLabel} content coming soon.
+
+
+
+ );
+}
+
+export default KitchenReports;
diff --git a/src/components/KitchenandInventory/KitchenReports/KitchenReports.module.css b/src/components/KitchenandInventory/KitchenReports/KitchenReports.module.css
new file mode 100644
index 00000000000..110b43921dc
--- /dev/null
+++ b/src/components/KitchenandInventory/KitchenReports/KitchenReports.module.css
@@ -0,0 +1,137 @@
+.pageWrapper {
+ min-height: 100vh;
+ width: 100%;
+ background-color: #fff;
+}
+
+.pageWrapper.dark {
+ background-color: #1a1816;
+}
+
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 24px 16px;
+}
+
+.header {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 16px;
+ margin-bottom: 20px;
+}
+
+.titleBlock h1 {
+ font-size: 1.5rem;
+ font-weight: 700;
+ margin: 0 0 4px;
+ color: #1f2937;
+}
+
+.titleBlock p {
+ margin: 0;
+ font-size: 0.9rem;
+ color: #6b7280;
+}
+
+.dark .titleBlock h1 {
+ color: #f3f4f6;
+}
+
+.dark .titleBlock p {
+ color: #9ca3af;
+}
+
+.controls {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+.dateSelect {
+ padding: 8px 12px;
+ border-radius: 6px;
+ border: 1px solid #d1d5db;
+ background-color: #fff;
+ color: #1f2937;
+ font-size: 0.875rem;
+}
+
+.dark .dateSelect {
+ background-color: #2a2622;
+ border-color: #4b4642;
+ color: #f3f4f6;
+}
+
+.exportButton {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 14px;
+ border-radius: 6px;
+ border: 1px solid #d1d5db;
+ background-color: #fff;
+ color: #1f2937;
+ font-size: 0.875rem;
+ cursor: pointer;
+}
+
+.exportButton:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+.dark .exportButton {
+ background-color: #2a2622;
+ border-color: #4b4642;
+ color: #f3f4f6;
+}
+
+.tabs {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+ border-bottom: 1px solid #e5e7eb;
+ margin-bottom: 20px;
+}
+
+.dark .tabs {
+ border-bottom-color: #3a3632;
+}
+
+.tabButton {
+ padding: 10px 16px;
+ border: none;
+ background: none;
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: #4b5563;
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+}
+
+.tabButton.active {
+ color: #047857;
+ border-bottom-color: #047857;
+}
+
+.dark .tabButton {
+ color: #9ca3af;
+}
+
+.dark .tabButton.active {
+ color: #34d399;
+ border-bottom-color: #34d399;
+}
+
+.tabPanel {
+ min-height: 200px;
+ color: #374151;
+}
+
+.dark .tabPanel {
+ color: #d1d5db;
+}
diff --git a/src/components/KitchenandInventory/KitchenReports/kitchenReportsData.js b/src/components/KitchenandInventory/KitchenReports/kitchenReportsData.js
new file mode 100644
index 00000000000..577f52df3ef
--- /dev/null
+++ b/src/components/KitchenandInventory/KitchenReports/kitchenReportsData.js
@@ -0,0 +1,16 @@
+export const REPORT_TABS = [
+ { id: 'overview', label: 'Overview' },
+ { id: 'sustainability', label: 'Sustainability Dashboard' },
+ { id: 'preservation', label: 'Preservation Efficiency' },
+ { id: 'forecast', label: 'Forecast Accuracy' },
+];
+
+export const DATE_RANGE_OPTIONS = [
+ { value: 'thisMonth', label: 'This Month' },
+ { value: 'lastMonth', label: 'Last Month' },
+ { value: 'last3Months', label: 'Last 3 Months' },
+ { value: 'thisYear', label: 'This Year' },
+];
+
+// Flip to true once the backend /kitchenandinventory/reports/export/* routes exist in HGNRest.
+export const EXPORT_ENDPOINTS_READY = false;
diff --git a/src/components/KitchenandInventory/KitchenReports/useReportExport.js b/src/components/KitchenandInventory/KitchenReports/useReportExport.js
new file mode 100644
index 00000000000..f50d86908e2
--- /dev/null
+++ b/src/components/KitchenandInventory/KitchenReports/useReportExport.js
@@ -0,0 +1,56 @@
+import { useCallback, useState } from 'react';
+import axios from 'axios';
+import { toast } from 'react-toastify';
+import { ENDPOINTS } from '~/utils/URL';
+
+const EXPORT_CONFIG = {
+ pdf: {
+ url: ENDPOINTS.KITCHEN_REPORTS_EXPORT_PDF,
+ mimeType: 'application/pdf',
+ fileName: 'report-overview.pdf',
+ },
+ csv: {
+ url: ENDPOINTS.KITCHEN_REPORTS_EXPORT_CSV,
+ mimeType: 'text/csv',
+ fileName: 'report-overview.csv',
+ },
+};
+
+export default function useReportExport() {
+ const [loadingFormat, setLoadingFormat] = useState(null);
+
+ const exportReport = useCallback(async (format, dateRange) => {
+ const config = EXPORT_CONFIG[format];
+ setLoadingFormat(format);
+
+ try {
+ const response = await axios.get(config.url(), {
+ params: { dateRange },
+ responseType: 'blob',
+ });
+
+ if (!response.data || response.data.size === 0) {
+ throw new Error('Received an empty report file.');
+ }
+
+ const blob = new Blob([response.data], { type: config.mimeType });
+ const downloadUrl = window.URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = downloadUrl;
+ link.download = config.fileName;
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+ setTimeout(() => window.URL.revokeObjectURL(downloadUrl), 100);
+
+ toast.success(`${format.toUpperCase()} report downloaded successfully!`);
+ } catch (error) {
+ const message = error?.message || `Failed to export ${format.toUpperCase()} report.`;
+ toast.error(`${message} Please try again.`);
+ } finally {
+ setLoadingFormat(null);
+ }
+ }, []);
+
+ return { exportReport, loadingFormat };
+}
diff --git a/src/components/LandingPage/HelpModal.jsx b/src/components/LandingPage/HelpModal.jsx
index c10a2f5f4ef..0603f2702cc 100644
--- a/src/components/LandingPage/HelpModal.jsx
+++ b/src/components/LandingPage/HelpModal.jsx
@@ -1,13 +1,64 @@
-import axios from 'axios';
import PropTypes from 'prop-types';
import { useEffect, useState } from 'react';
import { Button, Modal } from 'react-bootstrap';
import { connect, useSelector } from 'react-redux';
+import { useHistory } from 'react-router-dom';
import { toast } from 'react-toastify';
+import { availableSkills, formatSkillName } from '../HGNHelpSkillsDashboard/FilerData';
+import httpService from '../../services/httpService';
import { ENDPOINTS } from '~/utils/URL';
import styles from './HelpModal.module.css';
+const SOFTWARE_DEV_TEAM_ALIASES = new Set([
+ 'software development team',
+ 'hgn software development team',
+]);
+
+/** Maps common help-category labels to questionnaire skill keys. */
+const TOPIC_TO_SKILL_KEY = {
+ figma: 'UIUXTools',
+ 'ui/ux': 'UIUXTools',
+ 'frontend and backend overall': 'combined_frontend_backend',
+ 'frontend/backend': 'combined_frontend_backend',
+ mern: 'mern_skills',
+ leadership: 'leadership_skills',
+};
+
+const buildSkillFallbackOptions = () =>
+ availableSkills.map(skillKey => ({
+ value: skillKey,
+ label: formatSkillName(skillKey),
+ }));
+
+const normalizeCategoryOptions = categories => {
+ if (!Array.isArray(categories) || categories.length === 0) return [];
+
+ return categories
+ .map(category => {
+ if (typeof category === 'string') {
+ return { value: category, label: category };
+ }
+ const name = category?.name;
+ if (!name) return null;
+ return { value: name, label: name };
+ })
+ .filter(Boolean);
+};
+
+const resolveSkillKey = selectedTopic => {
+ if (!selectedTopic) return null;
+ if (availableSkills.includes(selectedTopic)) return selectedTopic;
+
+ const byLabel = availableSkills.find(
+ skillKey => formatSkillName(skillKey).toLowerCase() === selectedTopic.toLowerCase(),
+ );
+ if (byLabel) return byLabel;
+
+ return TOPIC_TO_SKILL_KEY[selectedTopic.trim().toLowerCase()] || null;
+};
+
function HelpModal({ show, onHide, auth }) {
+ const history = useHistory();
const [selectedOption, setSelectedOption] = useState('');
const [isOpen, setIsOpen] = useState(false);
const [options, setOptions] = useState([]);
@@ -17,16 +68,29 @@ function HelpModal({ show, onHide, auth }) {
const [isSubmitting, setIsSubmitting] = useState(false);
const darkMode = useSelector(state => state.theme.darkMode);
-
const userId = auth?.user?.userid;
useEffect(() => {
const fetchHelpCategories = async () => {
+ setLoading(true);
+ setError(null);
+
try {
- const categoriesResponse = await axios.get(ENDPOINTS.HELP_CATEGORIES);
- setOptions(categoriesResponse.data.map(category => category.name));
+ const categoriesResponse = await httpService.get(ENDPOINTS.HELP_CATEGORIES);
+ const apiOptions = normalizeCategoryOptions(categoriesResponse.data);
+
+ if (apiOptions.length > 0) {
+ setOptions(apiOptions);
+ } else {
+ // DB may be empty — fall back to questionnaire skills so the dropdown is usable
+ setOptions(buildSkillFallbackOptions());
+ }
} catch {
- setError('Failed to load help categories');
+ // Still offer skill fallback, but surface that the categories endpoint failed.
+ setOptions(buildSkillFallbackOptions());
+ setError(
+ 'Failed to load help categories from the server. Showing questionnaire skills instead.',
+ );
} finally {
setLoading(false);
}
@@ -36,11 +100,11 @@ function HelpModal({ show, onHide, auth }) {
}, []);
useEffect(() => {
- if (!userId) return;
+ if (!userId) return undefined;
const fetchUserProfile = async () => {
try {
- const profileResponse = await axios.get(ENDPOINTS.USER_PROFILE(userId));
+ const profileResponse = await httpService.get(ENDPOINTS.USER_PROFILE(userId));
setTeams(profileResponse.data?.teams || []);
} catch {
setTeams([]);
@@ -48,10 +112,11 @@ function HelpModal({ show, onHide, auth }) {
};
fetchUserProfile();
+ return undefined;
}, [userId]);
- const handleSelect = option => {
- setSelectedOption(option);
+ const handleSelect = optionValue => {
+ setSelectedOption(optionValue);
setIsOpen(false);
};
@@ -69,17 +134,29 @@ function HelpModal({ show, onHide, auth }) {
setIsSubmitting(true);
try {
- await axios.post(ENDPOINTS.HELP_REQUEST_CREATE, {
+ await httpService.post(ENDPOINTS.HELP_REQUEST_CREATE, {
userId,
topic: selectedOption,
description: `Help request for: ${selectedOption}`,
});
toast.success('Help request submitted successfully!');
+
+ const helpTopic = selectedOption;
+ const selectedSkillKey = resolveSkillKey(helpTopic);
+
setSelectedOption('');
- onHide();
+
+ // Navigate to community helpers; do not call onHide() (HelpPage would redirect to dashboard).
+ history.push({
+ pathname: '/hgnhelp/community',
+ state: {
+ initialSkills: selectedSkillKey ? [selectedSkillKey] : [],
+ softwareDevTeamOnly: true,
+ helpTopic,
+ },
+ });
} catch (err) {
- console.error('Help request submission error:', err);
if (err.code === 'ERR_NETWORK' || !err.response) {
toast.error('Cannot connect to server. Please ensure the backend is running.');
} else {
@@ -97,23 +174,24 @@ function HelpModal({ show, onHide, auth }) {
onHide();
};
- /* ---------------- Access Logic ---------------- */
const role = auth?.user?.role?.trim().toLowerCase() || '';
const isSoftwareDevMember =
['owner', 'administrator'].includes(role) ||
- teams.some(team =>
- ['software development team', 'hgn software development team'].includes(
- team.teamName?.trim().toLowerCase(),
- ),
- );
+ teams.some(team => SOFTWARE_DEV_TEAM_ALIASES.has(team.teamName?.trim().toLowerCase()));
+
+ const selectedLabel =
+ options.find(option => option.value === selectedOption)?.label || selectedOption;
const renderContent = () => {
if (loading) return Loading categories...
;
- if (error) return {error}
;
+ if (!options.length) {
+ return {error || 'No help categories available.'}
;
+ }
return (
<>
+ {error && {error}
}
setIsOpen(!isOpen)}
@@ -130,7 +208,7 @@ function HelpModal({ show, onHide, auth }) {
${darkMode && selectedOption ? styles.selectedDark : ''}
`}
>
- {selectedOption || 'Select an option'}
+ {selectedLabel || 'Select an option'}
{options.map(option => (
handleSelect(option)}
+ onClick={() => handleSelect(option.value)}
role="option"
tabIndex={0}
onKeyDown={e => {
- if (e.key === 'Enter' || e.key === ' ') handleSelect(option);
+ if (e.key === 'Enter' || e.key === ' ') handleSelect(option.value);
}}
- aria-selected={selectedOption === option}
+ aria-selected={selectedOption === option.value}
>
- {option}
+ {option.label}
))}
@@ -211,7 +289,6 @@ function HelpModal({ show, onHide, auth }) {
);
}
-/* ---------------- PropTypes ---------------- */
HelpModal.propTypes = {
show: PropTypes.bool.isRequired,
onHide: PropTypes.func.isRequired,
diff --git a/src/components/LandingPage/__tests__/HelpModal.test.jsx b/src/components/LandingPage/__tests__/HelpModal.test.jsx
index 6783dc29783..bfa3e6cc801 100644
--- a/src/components/LandingPage/__tests__/HelpModal.test.jsx
+++ b/src/components/LandingPage/__tests__/HelpModal.test.jsx
@@ -2,16 +2,22 @@ import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
-import axios from 'axios';
+import { MemoryRouter } from 'react-router-dom';
import { ENDPOINTS } from '~/utils/URL';
+import httpService from '../../../services/httpService';
import HelpModal from '../HelpModal';
import styles from '../HelpModal.module.css';
-vi.mock('axios');
+vi.mock('../../../services/httpService', () => ({
+ default: {
+ get: vi.fn(),
+ post: vi.fn(),
+ },
+}));
test('supports an HGN software team volunteer in dark mode', async () => {
const userId = 'test-user';
- axios.get.mockImplementation(url => {
+ httpService.get.mockImplementation(url => {
if (url === ENDPOINTS.HELP_CATEGORIES) {
return Promise.resolve({ data: [{ name: 'Software Support' }] });
}
@@ -29,7 +35,9 @@ test('supports an HGN software team volunteer in dark mode', async () => {
render(
-
+
+
+
,
);
@@ -40,5 +48,5 @@ test('supports an HGN software team volunteer in dark mode', async () => {
await userEvent.click(screen.getByRole('option', { name: 'Software Support' }));
await waitFor(() => expect(screen.getByRole('button', { name: 'Submit' })).toBeEnabled());
- expect(axios.get).toHaveBeenCalledWith(ENDPOINTS.USER_PROFILE(userId));
+ expect(httpService.get).toHaveBeenCalledWith(ENDPOINTS.USER_PROFILE(userId));
});
diff --git a/src/components/MostWastedMaterials/MostWastedMaterials.jsx b/src/components/MostWastedMaterials/MostWastedMaterials.jsx
index 050bb64bf8e..62095a21340 100644
--- a/src/components/MostWastedMaterials/MostWastedMaterials.jsx
+++ b/src/components/MostWastedMaterials/MostWastedMaterials.jsx
@@ -223,6 +223,7 @@ export default function MostWastedMaterials() {
id="mw-from"
type="date"
value={dateRange.from}
+ max={dateRange.to}
onChange={e => setDateRange(r => ({ ...r, from: e.target.value }))}
className={styles.input}
/>
@@ -236,6 +237,7 @@ export default function MostWastedMaterials() {
id="mw-to"
type="date"
value={dateRange.to}
+ min={dateRange.from}
onChange={e => setDateRange(r => ({ ...r, to: e.target.value }))}
className={styles.input}
/>
diff --git a/src/components/PRGradingDashboard/GradingTable.jsx b/src/components/PRGradingDashboard/GradingTable.jsx
index 156b769640a..beb5d1bd443 100644
--- a/src/components/PRGradingDashboard/GradingTable.jsx
+++ b/src/components/PRGradingDashboard/GradingTable.jsx
@@ -10,7 +10,10 @@ function GradingTable({
onAddPRClick,
openAddModal,
onAddGradedPR,
+ onRemoveReviewer,
+ isCurrentWeek,
darkMode,
+ canEdit,
}) {
return (
//
@@ -30,7 +33,10 @@ function GradingTable({
grading={grading}
onUpdatePRsReviewed={onUpdatePRsReviewed}
onAddPRClick={onAddPRClick}
+ onRemoveReviewer={onRemoveReviewer}
+ isCurrentWeek={isCurrentWeek}
darkMode={darkMode}
+ canEdit={canEdit}
/>
{openAddModal === grading.reviewer && (
@@ -62,12 +68,17 @@ GradingTable.propTypes = {
onAddPRClick: PropTypes.func.isRequired,
openAddModal: PropTypes.string,
onAddGradedPR: PropTypes.func.isRequired,
+ onRemoveReviewer: PropTypes.func.isRequired,
+ isCurrentWeek: PropTypes.bool,
darkMode: PropTypes.bool,
+ canEdit: PropTypes.bool,
};
GradingTable.defaultProps = {
openAddModal: null,
+ isCurrentWeek: true,
darkMode: false,
+ canEdit: false,
};
export default GradingTable;
diff --git a/src/components/PRGradingDashboard/PRGradingDashboard.jsx b/src/components/PRGradingDashboard/PRGradingDashboard.jsx
index 82e8a9afe38..6c075ebbcc1 100644
--- a/src/components/PRGradingDashboard/PRGradingDashboard.jsx
+++ b/src/components/PRGradingDashboard/PRGradingDashboard.jsx
@@ -1,18 +1,57 @@
import axios from 'axios';
import { useEffect, useState } from 'react';
-import { useSelector } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { toast } from 'react-toastify';
import AddReviewerModal from './AddReviewerModal';
import ConfirmationModal from './ConfirmationModal';
import GradingTable from './GradingTable';
+import RemoveReviewerModal from './RemoveReviewerModal';
import { MOCK_WEEK_METADATA, MOCK_WEEK_OPTIONS } from './mockWeeklyAuditData';
import styles from './PRGradingDashboard.module.css';
import { SelectionProvider } from './SelectionContext';
import SummaryList from './SummaryList';
+import hasPermission from '~/utils/permissions';
-const TEAM_CODE = 'TeamA';
const TEAM_NAME = 'Team Alpha';
+const formatLocalDate = date => {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+ return `${year}-${month}-${day}`;
+};
+
+const getWeekStart = () => {
+ const now = new Date();
+ const day = now.getDay();
+ const monday = new Date(
+ now.getFullYear(),
+ now.getMonth(),
+ now.getDate() - day + (day === 0 ? -6 : 1),
+ );
+ return formatLocalDate(monday);
+};
+
+const WEEK_OPTION_OFFSETS = {
+ 'Current Week': 0,
+ '1 Week Ago': 1,
+ '2 Weeks Ago': 2,
+ '3 Weeks Ago': 3,
+ '4 Weeks Ago': 4,
+};
+
+const getWeekStartByOffset = weeksAgo => {
+ const now = new Date();
+ const day = now.getDay();
+ const mondayOffset = day === 0 ? -6 : 1 - day;
+ const monday = new Date(
+ now.getFullYear(),
+ now.getMonth(),
+ now.getDate() + mondayOffset - weeksAgo * 7,
+ );
+ return formatLocalDate(monday);
+};
+
// Mock data for fallback
const mockData = [
{
@@ -36,7 +75,10 @@ const mockData = [
];
function PRGradingDashboard() {
+ const dispatch = useDispatch();
const darkMode = useSelector(state => state.theme.darkMode);
+ const authUser = useSelector(state => state.auth.user);
+ const canEdit = dispatch(hasPermission('accessPRTeamDashboard'));
const [gradings, setGradings] = useState([]);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
@@ -44,18 +86,22 @@ function PRGradingDashboard() {
const [pendingPR, setPendingPR] = useState(null); // { reviewer, prNumbers, grade } or null
const [showConfirmation, setShowConfirmation] = useState(false);
const [showAddReviewerModal, setShowAddReviewerModal] = useState(false);
+ const [pendingRemoveReviewer, setPendingRemoveReviewer] = useState(null); // reviewer name or null
const [selectedMockWeek, setSelectedMockWeek] = useState('Current Week');
+ const [selectedTeam, setSelectedTeam] = useState('TeamA');
- // Fetch data on mount
+ // Fetch data on mount and whenever the selected week changes
useEffect(() => {
- fetchGradings();
- }, []);
+ void fetchGradings(selectedMockWeek);
+ }, [selectedMockWeek]);
- const fetchGradings = async () => {
+ const fetchGradings = async (weekLabel = 'Current Week') => {
+ const offset = WEEK_OPTION_OFFSETS[weekLabel] ?? 0;
+ const weekDate = getWeekStartByOffset(offset);
try {
setLoading(true);
const response = await axios.get(
- `${process.env.REACT_APP_APIENDPOINT}/weekly-grading?team=${TEAM_CODE}`,
+ `${process.env.REACT_APP_APIENDPOINT}/weekly-grading?team=${selectedTeam}&date=${weekDate}`,
);
if (response.data && Array.isArray(response.data)) {
// Mark all existing PRs as not new
@@ -153,6 +199,39 @@ function PRGradingDashboard() {
toast.success(`Reviewer "${newReviewer.reviewer}" added successfully.`);
};
+ // Request to remove a reviewer (shows confirmation modal)
+ const requestRemoveReviewer = reviewer => {
+ setPendingRemoveReviewer(reviewer);
+ };
+
+ // Actually remove the reviewer after confirmation
+ const confirmRemoveReviewer = async () => {
+ if (!pendingRemoveReviewer) return;
+ const offset = WEEK_OPTION_OFFSETS[selectedMockWeek] ?? 0;
+ const weekDate = getWeekStartByOffset(offset);
+ try {
+ await axios.delete(
+ `${
+ process.env.REACT_APP_APIENDPOINT
+ }/weekly-grading?team=${selectedTeam}&date=${weekDate}&reviewer=${encodeURIComponent(
+ pendingRemoveReviewer,
+ )}`,
+ { data: { requestor: { requestorId: authUser.userid } } },
+ );
+ setGradings(prev => prev.filter(g => g.reviewer !== pendingRemoveReviewer));
+ toast.success(`Reviewer "${pendingRemoveReviewer}" removed successfully.`);
+ } catch (error) {
+ toast.error('Failed to remove reviewer. Please try again.');
+ } finally {
+ setPendingRemoveReviewer(null);
+ }
+ };
+
+ // Cancel reviewer removal
+ const cancelRemoveReviewer = () => {
+ setPendingRemoveReviewer(null);
+ };
+
// Remove a graded PR (only new ones)
const removeGradedPR = (reviewer, prIndex) => {
// Check if PR can be removed before updating state
@@ -191,12 +270,11 @@ function PRGradingDashboard() {
const handleSave = async () => {
try {
setSaving(true);
- const currentDate = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
-
// Remove isNew flag before sending to API
+ const offset = WEEK_OPTION_OFFSETS[selectedMockWeek] ?? 0;
const payload = {
- teamCode: TEAM_CODE,
- date: currentDate,
+ teamCode: selectedTeam,
+ date: getWeekStartByOffset(offset),
gradings: gradings.map(g => ({
reviewer: g.reviewer,
prsReviewed: g.prsReviewed,
@@ -225,6 +303,8 @@ function PRGradingDashboard() {
}
};
+ const isCurrentWeek = (WEEK_OPTION_OFFSETS[selectedMockWeek] ?? 0) === 0;
+
const currentDate = new Date().toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
@@ -279,22 +359,27 @@ function PRGradingDashboard() {
{/* Main Table */}
-
- setShowAddReviewerModal(true)}
- className={styles.addReviewerButton}
- >
- + Add Reviewer
-
-
+ {isCurrentWeek && (
+
+ setShowAddReviewerModal(true)}
+ className={styles.addReviewerButton}
+ >
+ + Add Reviewer
+
+
+ )}
@@ -311,16 +396,18 @@ function PRGradingDashboard() {
{/* Footer */}
-
-
- {saving ? 'Saving...' : 'Save'}
-
-
+ {isCurrentWeek && (
+
+
+ {saving ? 'Saving...' : 'Save'}
+
+
+ )}
{/* Confirmation Modal */}
@@ -334,6 +421,15 @@ function PRGradingDashboard() {
/>
)}
+ {/* Remove Reviewer Modal */}
+ {pendingRemoveReviewer && (
+
+ )}
+
{/* Add Reviewer Modal */}
{showAddReviewerModal && (
setShowAddReviewerModal(false)} />
diff --git a/src/components/PRGradingDashboard/RemoveReviewerModal.jsx b/src/components/PRGradingDashboard/RemoveReviewerModal.jsx
new file mode 100644
index 00000000000..ce33d180797
--- /dev/null
+++ b/src/components/PRGradingDashboard/RemoveReviewerModal.jsx
@@ -0,0 +1,85 @@
+import { useEffect } from 'react';
+import { Trash2, X } from 'lucide-react';
+import styles from './ConfirmationModal.module.css';
+
+function RemoveReviewerModal({ reviewer, onConfirm, onCancel }) {
+ useEffect(() => {
+ const handleEscape = e => {
+ if (e.key === 'Escape') onCancel();
+ };
+ document.addEventListener('keydown', handleEscape);
+ return () => document.removeEventListener('keydown', handleEscape);
+ }, [onCancel]);
+
+ const handleOverlayClick = e => {
+ if (e.target === e.currentTarget) onCancel();
+ };
+
+ const handleOverlayKeyDown = e => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ onCancel();
+ }
+ };
+
+ return (
+
+
+
+
+ Remove Reviewer
+
+
+
+
+
+
+
+
+
+
+ Remove reviewer {reviewer} from this week?
+
+
+ Their past PR review history will remain visible in analytics.
+
+
+
+
+ Cancel
+
+
+ Remove
+
+
+
+
+ );
+}
+
+export default RemoveReviewerModal;
diff --git a/src/components/PRGradingDashboard/ReviewerRow.jsx b/src/components/PRGradingDashboard/ReviewerRow.jsx
index 6a7c197ec7d..55cfa0a001f 100644
--- a/src/components/PRGradingDashboard/ReviewerRow.jsx
+++ b/src/components/PRGradingDashboard/ReviewerRow.jsx
@@ -1,9 +1,17 @@
-import { Plus } from 'lucide-react';
+import { Plus, Trash2 } from 'lucide-react';
import PropTypes from 'prop-types';
import styles from './ReviewerRow.module.css';
import { useRowSelection } from './SelectionContext';
-function ReviewerRow({ grading, onUpdatePRsReviewed, onAddPRClick, darkMode }) {
+function ReviewerRow({
+ grading,
+ onUpdatePRsReviewed,
+ onAddPRClick,
+ onRemoveReviewer,
+ isCurrentWeek,
+ darkMode,
+ canEdit,
+}) {
const { activeId, selectRow } = useRowSelection();
const handlePRsReviewedChange = e => {
@@ -49,14 +57,29 @@ function ReviewerRow({ grading, onUpdatePRsReviewed, onAddPRClick, darkMode }) {
{grading.prsNeeded}
- onAddPRClick(grading.reviewer)}
- className={styles.addButton}
- type="button"
- >
-
- Add New
-
+ {isCurrentWeek && canEdit ? (
+
+
onAddPRClick(grading.reviewer)}
+ className={styles.addButton}
+ type="button"
+ >
+
+ Add New
+
+
onRemoveReviewer(grading.reviewer)}
+ className={styles.removeButton}
+ type="button"
+ aria-label={`Remove reviewer ${grading.reviewer}`}
+ title="Remove reviewer"
+ >
+
+
+
+ ) : (
+ Read only
+ )}
);
@@ -69,11 +92,16 @@ ReviewerRow.propTypes = {
}).isRequired,
onUpdatePRsReviewed: PropTypes.func.isRequired,
onAddPRClick: PropTypes.func.isRequired,
+ onRemoveReviewer: PropTypes.func.isRequired,
+ isCurrentWeek: PropTypes.bool,
darkMode: PropTypes.bool,
+ canEdit: PropTypes.bool,
};
ReviewerRow.defaultProps = {
+ isCurrentWeek: true,
darkMode: false,
+ canEdit: false,
};
export default ReviewerRow;
diff --git a/src/components/PRGradingDashboard/ReviewerRow.module.css b/src/components/PRGradingDashboard/ReviewerRow.module.css
index 5bb515de925..83e1af3de21 100644
--- a/src/components/PRGradingDashboard/ReviewerRow.module.css
+++ b/src/components/PRGradingDashboard/ReviewerRow.module.css
@@ -73,6 +73,47 @@
margin-right: 0.5rem;
}
+.actionsCell {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.removeButton {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0.5rem;
+ background-color: transparent;
+ color: #6b7280;
+ border: 1px solid #d1d5db;
+ border-radius: 0.375rem;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.removeButton:hover {
+ background-color: #fef2f2;
+ color: #dc2626;
+ border-color: #fca5a5;
+}
+
+.removeButton:focus {
+ outline: none;
+ box-shadow: 0 0 0 2px rgb(220 38 38 / 50%);
+}
+
+.removeButtonIcon {
+ width: 1rem;
+ height: 1rem;
+}
+
+.readOnlyLabel {
+ font-size: 0.75rem;
+ color: #9ca3af;
+ font-style: italic;
+}
+
/* Dark mode support */
:global(.dark-mode) .row:hover {
background-color: #374151;
@@ -108,4 +149,15 @@
:global(.dark-mode) .addButton:focus {
box-shadow: 0 0 0 2px rgb(37 99 235 / 50%), 0 0 0 4px rgb(37 99 235 / 10%);
+}
+
+:global(.dark-mode) .removeButton {
+ color: #9ca3af;
+ border-color: #4b5563;
+}
+
+:global(.dark-mode) .removeButton:hover {
+ background-color: #450a0a;
+ color: #f87171;
+ border-color: #991b1b;
}
\ No newline at end of file
diff --git a/src/components/PRGradingScreen/PRGradingScreen.duplicates.test.jsx b/src/components/PRGradingScreen/PRGradingScreen.duplicates.test.jsx
new file mode 100644
index 00000000000..c67eabfdf09
--- /dev/null
+++ b/src/components/PRGradingScreen/PRGradingScreen.duplicates.test.jsx
@@ -0,0 +1,196 @@
+import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
+import axios from 'axios';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import PRGradingScreen from './PRGradingScreen';
+
+vi.mock('axios');
+vi.mock('react-redux', () => ({
+ useSelector: selector => selector({ theme: { darkMode: false } }),
+}));
+
+const teamData = {
+ teamName: 'Test Team',
+ dateRange: { start: '5/18/2025', end: '5/25/2025' },
+};
+const reviewers = [
+ {
+ id: '1',
+ reviewer: 'Alice',
+ prsNeeded: 3,
+ gradedPrs: [{ id: 'pr1', prNumbers: '1234 + 5678', grade: 'Okay' }],
+ },
+ { id: '2', reviewer: 'Bob', prsNeeded: 3, gradedPrs: [] },
+];
+
+const addPR = async (row, numbers) => {
+ const addNewButton = within(row).getByRole('button', { name: '+ Add new' });
+ await waitFor(() => expect(addNewButton).toBeEnabled());
+ fireEvent.click(addNewButton);
+ fireEvent.change(within(row).getByPlaceholderText('1070 or 1070 + 1256'), {
+ target: { value: numbers },
+ });
+ fireEvent.click(within(row).getByRole('button', { name: 'Add' }));
+ await waitFor(() =>
+ expect(within(row).queryByRole('button', { name: 'Adding...' })).not.toBeInTheDocument(),
+ );
+};
+
+describe('Weekly PR grading duplicate entries', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ axios.get.mockResolvedValue({ data: [] });
+ axios.post.mockResolvedValue({ data: { status: 'ok' } });
+ });
+
+ it('allows a pair containing two unused PR numbers and counts it as one review', async () => {
+ render(
);
+ const aliceRow = screen.getByRole('row', { name: /Alice/ });
+
+ await addPR(aliceRow, '9981 + 9982');
+
+ expect(within(aliceRow).getByText('9981 + 9982')).toBeInTheDocument();
+ expect(within(aliceRow).getByRole('spinbutton')).toHaveValue(2);
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
+ });
+
+ it('rejects a PR already present inside a pair for the same reviewer', async () => {
+ render(
);
+ const aliceRow = screen.getByRole('row', { name: /Alice/ });
+
+ await addPR(aliceRow, '5678 + 9999');
+
+ expect(screen.getByRole('alert')).toHaveTextContent('already been added');
+ expect(within(aliceRow).getByRole('spinbutton')).toHaveValue(1);
+ expect(within(aliceRow).queryByText('5678 + 9999')).not.toBeInTheDocument();
+ });
+
+ it('rejects a repeated number within a new pair', async () => {
+ render(
);
+ const aliceRow = screen.getByRole('row', { name: /Alice/ });
+
+ await addPR(aliceRow, '4321 + 4321');
+
+ expect(screen.getByRole('alert')).toHaveTextContent('already been added');
+ expect(within(aliceRow).getByRole('spinbutton')).toHaveValue(1);
+ });
+
+ it('rejects a repeated addition but allows another reviewer to use that PR number', async () => {
+ render(
);
+ const aliceRow = screen.getByRole('row', { name: /Alice/ });
+ const bobRow = screen.getByRole('row', { name: /Bob/ });
+
+ await addPR(aliceRow, '4321');
+ await addPR(aliceRow, '4321');
+
+ expect(screen.getByRole('alert')).toHaveTextContent('already been added');
+ expect(within(aliceRow).getByRole('spinbutton')).toHaveValue(2);
+
+ await addPR(bobRow, '4321');
+ expect(within(bobRow).getByRole('spinbutton')).toHaveValue(1);
+ });
+
+ it('loads a saved PR again after the screen is remounted', async () => {
+ axios.get
+ .mockResolvedValueOnce({ data: [] })
+ .mockResolvedValueOnce({ data: [] })
+ .mockResolvedValueOnce({
+ data: [
+ {
+ reviewer: 'Alice',
+ gradedPrs: [{ _id: 'saved1', prNumbers: '9981', grade: 'Okay' }],
+ },
+ ],
+ });
+ const { unmount } = render(
);
+ await addPR(screen.getByRole('row', { name: /Alice/ }), '9981');
+ unmount();
+
+ render(
);
+ const aliceRow = screen.getByRole('row', { name: /Alice/ });
+ await waitFor(() => expect(within(aliceRow).getByText('9981')).toBeInTheDocument());
+ expect(within(aliceRow).getByRole('spinbutton')).toHaveValue(2);
+ });
+
+ it('keeps the input open when saving fails instead of showing an unsaved PR', async () => {
+ axios.post.mockRejectedValueOnce(new Error('Network error'));
+ render(
);
+ const aliceRow = screen.getByRole('row', { name: /Alice/ });
+
+ await addPR(aliceRow, '9981');
+
+ expect(within(aliceRow).getByRole('alert')).toHaveTextContent('Could not save');
+ expect(within(aliceRow).getByRole('spinbutton')).toHaveValue(1);
+ expect(within(aliceRow).queryByText('9981')).not.toBeInTheDocument();
+ });
+
+ it('saves a changed grade and restores it after remounting', async () => {
+ axios.get.mockResolvedValueOnce({ data: [] }).mockResolvedValueOnce({
+ data: [
+ {
+ reviewer: 'Alice',
+ gradedPrs: [{ prNumbers: '1234 + 5678', grade: 'Exceptional' }],
+ },
+ ],
+ });
+ const { unmount } = render(
);
+ await waitFor(() =>
+ expect(
+ within(screen.getByRole('row', { name: /Alice/ })).getByText('+ Add new'),
+ ).toBeEnabled(),
+ );
+ fireEvent.click(within(screen.getByRole('row', { name: /Alice/ })).getByText('1234 + 5678'));
+ fireEvent.click(screen.getAllByRole('checkbox')[0]);
+
+ await waitFor(() => expect(axios.post).toHaveBeenCalledTimes(1));
+ expect(axios.post.mock.calls[0][1].gradings[0].gradedPrs).toEqual([
+ { prNumbers: '1234 + 5678', grade: 'Exceptional' },
+ ]);
+ await waitFor(() => expect(screen.getAllByRole('checkbox')[0]).toBeChecked());
+ unmount();
+
+ render(
);
+ await waitFor(() => expect(axios.get).toHaveBeenCalledTimes(2));
+ fireEvent.click(within(screen.getByRole('row', { name: /Alice/ })).getByText('1234 + 5678'));
+ await waitFor(() => expect(screen.getAllByRole('checkbox')[0]).toBeChecked());
+ expect(within(screen.getByRole('row', { name: /Alice/ })).getByRole('spinbutton')).toHaveValue(
+ 1,
+ );
+ });
+
+ it('keeps the previous grade and shows an error if saving fails', async () => {
+ axios.post.mockRejectedValueOnce(new Error('Network error'));
+ render(
);
+ await waitFor(() =>
+ expect(
+ within(screen.getByRole('row', { name: /Alice/ })).getByText('+ Add new'),
+ ).toBeEnabled(),
+ );
+ fireEvent.click(within(screen.getByRole('row', { name: /Alice/ })).getByText('1234 + 5678'));
+ fireEvent.click(screen.getAllByRole('checkbox')[0]);
+
+ expect(await screen.findByRole('alert')).toHaveTextContent('Could not save this grade');
+ expect(axios.post.mock.calls[0][1].gradings[0].gradedPrs).toEqual([
+ { prNumbers: '1234 + 5678', grade: 'Exceptional' },
+ ]);
+ expect(screen.getAllByRole('checkbox')[0]).not.toBeChecked();
+ expect(screen.getAllByRole('checkbox')[1]).toBeChecked();
+ });
+
+ it('sends the backend-supported value for Cannot find image', async () => {
+ render(
);
+ await waitFor(() =>
+ expect(
+ within(screen.getByRole('row', { name: /Alice/ })).getByText('+ Add new'),
+ ).toBeEnabled(),
+ );
+ fireEvent.click(within(screen.getByRole('row', { name: /Alice/ })).getByText('1234 + 5678'));
+ fireEvent.click(screen.getAllByRole('checkbox')[3]);
+
+ await waitFor(() =>
+ expect(axios.post.mock.calls[0][1].gradings[0].gradedPrs).toEqual([
+ { prNumbers: '1234 + 5678', grade: 'No Correct Image' },
+ ]),
+ );
+ await waitFor(() => expect(screen.getAllByRole('checkbox')[3]).toBeChecked());
+ });
+});
diff --git a/src/components/PRGradingScreen/PRGradingScreen.jsx b/src/components/PRGradingScreen/PRGradingScreen.jsx
index 1145610706f..489a6adf9b3 100644
--- a/src/components/PRGradingScreen/PRGradingScreen.jsx
+++ b/src/components/PRGradingScreen/PRGradingScreen.jsx
@@ -1,53 +1,93 @@
-import React, { useMemo, useState } from 'react';
import PropTypes from 'prop-types';
-import { Button, Card, Col, Container, Row } from 'react-bootstrap';
+import { useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { v4 as uuidv4 } from 'uuid';
-import styles from './PRGradingScreen.module.css';
-import InlinePRSummary from './InlinePRSummary';
-// Shared with InlinePRSummary so both tables stay in sync. See gradeOptions.js.
-import GRADE_OPTIONS from './gradeOptions';
+import PRGradingView from './PRGradingView';
+import {
+ loadSavedGradings,
+ mergeSavedGradings,
+ saveAddedPR,
+ saveGradeChange,
+} from './prGradingPersistence';
+import { usePRPromotion } from './usePRPromotion';
+
+const hasDuplicatePR = (gradedPrs, value) => {
+ const enteredNumbers = value.split('+').map(number => number.trim());
+ const existingNumbers = new Set(
+ gradedPrs.flatMap(pr => pr.prNumbers.split('+').map(number => number.trim())),
+ );
+ return (
+ new Set(enteredNumbers).size !== enteredNumbers.length ||
+ enteredNumbers.some(number => existingNumbers.has(number))
+ );
+};
const PRGradingScreen = ({ teamData, reviewers }) => {
const darkMode = useSelector(state => state.theme.darkMode);
-
const [reviewerData, setReviewerData] = useState(reviewers || []);
const [activeInput, setActiveInput] = useState(null);
const [inputValue, setInputValue] = useState('');
const [inputError, setInputError] = useState('');
const [showGradingModal, setShowGradingModal] = useState(null);
const [isFinalized, setIsFinalized] = useState(false);
-
- // Search state
+ const [isLoadingSaved, setIsLoadingSaved] = useState(true);
+ const [isSavingPR, setIsSavingPR] = useState(false);
+ const [isSavingGrade, setIsSavingGrade] = useState(false);
+ const [gradeError, setGradeError] = useState('');
+ const [loadError, setLoadError] = useState('');
const [searchTerm, setSearchTerm] = useState('');
const [roleFilter, setRoleFilter] = useState('');
- /* ---------------- SEARCH FILTER ---------------- */
+ const {
+ promotingReviewer,
+ promotedReviewerIds,
+ handlePromoteClick,
+ handleConfirmPromotion,
+ handleCancelPromotion,
+ } = usePRPromotion(teamData?.teamName);
+
+ useEffect(() => {
+ if (!teamData || !reviewers) return undefined;
+ let active = true;
+ setIsLoadingSaved(true);
+ loadSavedGradings(teamData)
+ .then(saved => {
+ if (active) {
+ setReviewerData(mergeSavedGradings(reviewers, saved));
+ setLoadError('');
+ }
+ })
+ .catch(() => {
+ if (active) setLoadError('Could not load saved PRs. Try again before adding a PR.');
+ })
+ .finally(() => {
+ if (active) setIsLoadingSaved(false);
+ });
+ return () => {
+ active = false;
+ };
+ }, [teamData, reviewers]);
const availableRoles = useMemo(() => {
- const roles = reviewerData.map(r => r.role).filter(Boolean);
+ const roles = reviewerData.map(reviewer => reviewer.role).filter(Boolean);
return [...new Set(roles)];
}, [reviewerData]);
- const filteredReviewers = useMemo(() => {
- return reviewerData.filter(r => {
- const nameMatch = r.reviewer.toLowerCase().includes(searchTerm.toLowerCase());
- const roleMatch = roleFilter ? r.role === roleFilter : true;
- return nameMatch && roleMatch;
- });
- }, [reviewerData, searchTerm, roleFilter]);
+ const filteredReviewers = useMemo(
+ () =>
+ reviewerData.filter(reviewer => {
+ const nameMatch = reviewer.reviewer.toLowerCase().includes(searchTerm.toLowerCase());
+ const roleMatch = roleFilter ? reviewer.role === roleFilter : true;
+ return nameMatch && roleMatch;
+ }),
+ [reviewerData, searchTerm, roleFilter],
+ );
const handleClearSearch = () => {
setSearchTerm('');
setRoleFilter('');
};
- if (!teamData || !reviewers) {
- return
Error: Missing required props
;
- }
-
- /* ---------------- VALIDATION ---------------- */
-
const validatePRNumber = value => {
const trimmed = value.trim();
const pattern = /^\d+(\s*\+\s*\d+)?$/;
@@ -56,300 +96,153 @@ const PRGradingScreen = ({ teamData, reviewers }) => {
return { isValid: true, error: '' };
};
- /* ---------------- ADD PR ---------------- */
-
const handleAddNewClick = reviewerId => {
- if (isFinalized) return;
+ if (isFinalized || isLoadingSaved || loadError) return;
setActiveInput(reviewerId);
setInputValue('');
setInputError('');
};
- const handleInputSubmit = reviewerId => {
- if (isFinalized) return;
+ const handleInputSubmit = async reviewerId => {
+ if (isFinalized || isLoadingSaved || isSavingPR || loadError) return;
const validation = validatePRNumber(inputValue);
if (!validation.isValid) {
setInputError(validation.error);
return;
}
- const newPREntry = { id: uuidv4(), prNumbers: inputValue.trim(), grade: 'Okay' };
- setReviewerData(prev =>
- prev.map(r =>
- r.id === reviewerId
- ? { ...r, gradedPrs: [...r.gradedPrs, newPREntry], prsReviewed: r.gradedPrs.length + 1 }
- : r,
- ),
- );
- setActiveInput(null);
- setInputValue('');
- setInputError('');
+ const reviewer = reviewerData.find(item => item.id === reviewerId);
+ if (!reviewer) return;
+ if (hasDuplicatePR(reviewer.gradedPrs, inputValue)) {
+ setInputError('This PR number has already been added for this reviewer.');
+ return;
+ }
+
+ setIsSavingPR(true);
+ try {
+ const saved = await loadSavedGradings(teamData);
+ const currentReviewers = mergeSavedGradings(reviewerData, saved);
+ const currentReviewer = currentReviewers.find(item => item.id === reviewerId);
+ if (!currentReviewer) return;
+ if (hasDuplicatePR(currentReviewer.gradedPrs, inputValue)) {
+ setReviewerData(currentReviewers);
+ setInputError('This PR number has already been added for this reviewer.');
+ return;
+ }
+
+ const prNumbers = inputValue.trim();
+ await saveAddedPR(teamData, currentReviewer, prNumbers);
+ const newPREntry = { id: uuidv4(), prNumbers, grade: 'Okay' };
+ setReviewerData(
+ currentReviewers.map(item =>
+ item.id === reviewerId
+ ? {
+ ...item,
+ gradedPrs: [...item.gradedPrs, newPREntry],
+ prsReviewed: item.gradedPrs.length + 1,
+ }
+ : item,
+ ),
+ );
+ setActiveInput(null);
+ setInputValue('');
+ setInputError('');
+ } catch {
+ setInputError('Could not save this PR. Please try again.');
+ } finally {
+ setIsSavingPR(false);
+ }
};
- const handleCancel = () => {
- if (isFinalized) return;
+ const handleCancelInput = () => {
+ if (isFinalized || isSavingPR) return;
setActiveInput(null);
setInputValue('');
setInputError('');
};
- /* ---------------- MODAL ---------------- */
-
const handlePRNumberClick = reviewerId => {
- if (isFinalized) return;
+ if (isFinalized || isLoadingSaved || loadError) return;
+ setGradeError('');
setShowGradingModal(reviewerId);
};
- const handleGradeChange = (reviewerId, prId, newGrade) => {
- if (isFinalized) return;
- setReviewerData(prev =>
- prev.map(r =>
- r.id === reviewerId
- ? {
- ...r,
- gradedPrs: r.gradedPrs.map(pr => (pr.id === prId ? { ...pr, grade: newGrade } : pr)),
- }
- : r,
- ),
- );
+ const handleGradeChange = async (reviewerId, prId, newGrade) => {
+ if (isFinalized || isLoadingSaved || isSavingGrade || loadError) return;
+ const reviewer = reviewerData.find(item => item.id === reviewerId);
+ const pr = reviewer?.gradedPrs.find(item => item.id === prId);
+ if (!pr || pr.grade === newGrade) return;
+
+ setIsSavingGrade(true);
+ setGradeError('');
+ try {
+ const savedGrade = newGrade === 'Cannot find image' ? 'No Correct Image' : newGrade;
+ await saveGradeChange(teamData, reviewer, pr.prNumbers, savedGrade);
+ setReviewerData(prev =>
+ prev.map(item =>
+ item.id === reviewerId
+ ? {
+ ...item,
+ gradedPrs: item.gradedPrs.map(gradedPR =>
+ gradedPR.id === prId ? { ...gradedPR, grade: newGrade } : gradedPR,
+ ),
+ }
+ : item,
+ ),
+ );
+ } catch {
+ setGradeError('Could not save this grade. Please try again.');
+ } finally {
+ setIsSavingGrade(false);
+ }
};
- const handleCloseGradingModal = () => setShowGradingModal(null);
- const handleFinalize = () => setIsFinalized(true);
-
- /* ---------------- RENDER ---------------- */
+ const handleCloseGradingModal = () => {
+ if (isSavingGrade) return;
+ setShowGradingModal(null);
+ setGradeError('');
+ };
- const dm = darkMode ? styles['dark-mode'] : '';
+ if (!teamData || !reviewers) return
Error: Missing required props
;
return (
-
-
-
-
-
-
-
-
- Weekly PR grading screen
-
-
- {teamData.teamName} - {teamData.dateRange.start} to {teamData.dateRange.end}
-
-
-
- {isFinalized ? 'Finalized' : 'Done'}
-
-
-
-
-
- {/* ── Search Bar ── */}
-
- setSearchTerm(e.target.value)}
- className={`${styles['pr-grading-screen-search-input']} ${dm}`}
- />
-
- {availableRoles.length > 0 && (
- setRoleFilter(e.target.value)}
- className={`${styles['pr-grading-screen-role-select']} ${dm}`}
- >
- All roles
- {availableRoles.map(role => (
-
- {role}
-
- ))}
-
- )}
-
- {(searchTerm || roleFilter) && (
-
- ✕ Clear
-
- )}
-
-
-
-
-
-
-
-
- {showGradingModal && (
-
-
-
-
Grade PR
-
- ×
-
-
-
-
-
-
- )}
-
+
setIsFinalized(true)}
+ searchTerm={searchTerm}
+ onSearchTermChange={setSearchTerm}
+ roleFilter={roleFilter}
+ onRoleFilterChange={setRoleFilter}
+ availableRoles={availableRoles}
+ onClearSearch={handleClearSearch}
+ filteredReviewers={filteredReviewers}
+ promotedReviewerIds={promotedReviewerIds}
+ onPromoteClick={handlePromoteClick}
+ activeInput={activeInput}
+ inputValue={inputValue}
+ inputError={inputError}
+ loadError={loadError}
+ gradeError={gradeError}
+ isLoadingSaved={isLoadingSaved}
+ isSavingPR={isSavingPR}
+ isSavingGrade={isSavingGrade}
+ onInputValueChange={value => {
+ setInputValue(value);
+ setInputError('');
+ }}
+ onAddNewClick={handleAddNewClick}
+ onInputSubmit={handleInputSubmit}
+ onCancelInput={handleCancelInput}
+ onPRNumberClick={handlePRNumberClick}
+ promotingReviewer={promotingReviewer}
+ onConfirmPromotion={handleConfirmPromotion}
+ onCancelPromotion={handleCancelPromotion}
+ showGradingModal={showGradingModal}
+ onCloseGradingModal={handleCloseGradingModal}
+ gradingReviewer={reviewerData.find(reviewer => reviewer.id === showGradingModal)}
+ onGradeChange={handleGradeChange}
+ />
);
};
diff --git a/src/components/PRGradingScreen/PRGradingScreen.module.css b/src/components/PRGradingScreen/PRGradingScreen.module.css
index cea7ce1ed2f..38e40fb9641 100644
--- a/src/components/PRGradingScreen/PRGradingScreen.module.css
+++ b/src/components/PRGradingScreen/PRGradingScreen.module.css
@@ -381,7 +381,7 @@
}
.pr-grading-screen-error-message {
- color: #f8d7da;
+ color: #b02a37;
font-size: 0.75rem;
margin-top: 4px;
text-align: center;
@@ -463,9 +463,6 @@
/* Grading Table Styles */
.pr-grading-screen-grading-table {
width: 100%;
-
- /* Same four grade columns as the inline summary, so it clips the same way on
- narrow screens. Keep a usable minimum and let the modal body scroll. */
min-width: 380px;
border-collapse: collapse;
margin-bottom: 20px;
@@ -578,7 +575,6 @@
font-size: 0.8rem;
}
- /* Modal responsive styles */
.pr-grading-screen-modal {
width: 95%;
max-height: 90vh;
@@ -620,7 +616,6 @@
/* ===== DARK MODE STYLES ===== */
-/* Container and Card */
.pr-grading-screen-container.dark-mode {
background-color: #1b2a41;
}
@@ -678,7 +673,6 @@
color: #fff;
}
-/* Table */
.pr-grading-screen-table-container.dark-mode {
box-shadow: 0 1px 3px rgb(255 255 255 / 10%);
}
@@ -721,7 +715,6 @@
border-color: #6a7b98;
}
-/* PR Number Pills */
.pr-grading-screen-table.dark-mode .pr-grading-screen-pr-number {
background-color: #2d4059;
color: #fff;
@@ -740,7 +733,6 @@
box-shadow: 0 2px 4px rgb(255 255 255 / 10%);
}
-/* Buttons */
.pr-grading-screen-add-btn.dark-mode {
background-color: #28a745;
border-color: #28a745;
@@ -751,7 +743,6 @@
border-color: #1e7e34;
}
-/* Modal */
.pr-grading-screen-modal-overlay.dark-mode {
background-color: rgb(27 42 65 / 80%);
}
@@ -808,7 +799,6 @@
background-color: #2d4059;
}
-/* Input and Form Elements */
.pr-grading-screen-pr-number-input.dark-mode {
background-color: #2d4059;
border-color: #5a6b88;
@@ -824,11 +814,6 @@
border-color: #dc3545;
}
-/* .pr-grading-screen-pair-message {
- color: #a7f3d0;
-} */
-
-/* Modal Buttons */
.pr-grading-screen-done-btn.dark-mode {
background-color: #007bff;
border-color: #007bff;
@@ -840,12 +825,10 @@
border-color: #004085;
}
-/* Input Container */
.pr-grading-screen-input-container.dark-mode {
background-color: transparent;
}
-/* Button variants for dark mode */
.btn.dark-mode {
border: 1px solid #5a6b88;
}
@@ -871,7 +854,6 @@
border-color: #4e555b;
}
-/* Grade badges in dark mode */
.dark-mode .pr-grading-screen-grade-exceptional {
background-color: #155724;
color: #d4edda;
@@ -963,7 +945,6 @@
font-style: italic;
}
-/* Search bar dark mode */
.pr-grading-screen-search-bar.dark-mode {
background-color: transparent;
}
@@ -1008,7 +989,89 @@
color: #9ab0cc;
}
-/* ---------------- INLINE PR SUMMARY ---------------- */
+.pr-grading-screen-error-message.dark-mode {
+ color: #f8d7da;
+}
+
+/* Promotion Confirmation Modal Actions (From Your Branch) */
+.pr-promotion-no-history {
+ margin-bottom: 16px;
+ padding: 12px;
+ border: 1px dashed #ced4da;
+ border-radius: 6px;
+ background-color: #f8f9fa;
+ color: #6c757d;
+ text-align: center;
+}
+
+.pr-promotion-no-history.dark-mode {
+ border-color: #4a5a77;
+ background-color: #1b2a41;
+ color: #b0b8c4;
+}
+
+.pr-promotion-footer {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 16px;
+ padding: 18px 24px 24px !important;
+}
+
+.pr-promotion-btn-cancel {
+ padding: 9px 24px;
+ border-radius: 6px;
+ border: 1px solid #6c757d;
+ background-color: #6c757d;
+ color: #fff;
+ cursor: pointer;
+ font-weight: 600;
+ font-size: 14px;
+ transition: all 0.2s ease-in-out;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 10%);
+}
+
+.pr-promotion-btn-cancel:hover {
+ background-color: #5a6268;
+ border-color: #545b62;
+ transform: translateY(-1px);
+ box-shadow: 0 4px 8px rgb(0 0 0 / 20%);
+}
+
+.pr-promotion-btn-confirm {
+ padding: 9px 24px;
+ border-radius: 6px;
+ border: none;
+ background-color: #28a745;
+ color: #fff;
+ cursor: pointer;
+ font-weight: 600;
+ font-size: 14px;
+ transition: all 0.2s ease-in-out;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 10%);
+}
+
+.pr-promotion-btn-confirm:hover {
+ background-color: #218838;
+ transform: translateY(-1px);
+ box-shadow: 0 4px 10px rgb(40 167 69 / 40%);
+}
+
+.dark-mode .pr-promotion-btn-cancel {
+ border-color: #5a6b88;
+ background-color: #475569;
+}
+
+.dark-mode .pr-promotion-btn-cancel:hover {
+ background-color: #5a6b88;
+ border-color: #8a9bb8;
+}
+
+.dark-mode .pr-promotion-btn-confirm:hover {
+ background-color: #1e7e34;
+}
+
+/* ---------------- INLINE PR SUMMARY (From Development) ---------------- */
.pr-grading-inline-summary-row {
background-color: transparent;
@@ -1019,11 +1082,6 @@
border-top: none !important;
}
-/* ==========================================================
- Inline PR Summary Styles (PR #5493)
- Scoped strictly with pr-grading-inline-* to avoid global pollution
- ========================================================== */
-
.pr-grading-inline-summary-container {
padding: 16px 20px;
background-color: #fff;
@@ -1032,29 +1090,21 @@
box-shadow: 0 2px 6px rgb(0 0 0 / 4%);
}
-/* Dark mode support for inline summary container */
.pr-grading-inline-summary-container.dark-mode {
background-color: #2b2b2b;
border-color: rgb(255 255 255 / 15%);
box-shadow: none;
}
-/* Horizontal scroll rail. Without this the four grade columns are squeezed until
- the rightmost ones ("Unsatisfactory" / "Cannot find image") clip off the edge
- on narrow screens. The table keeps a usable minimum width and scrolls instead. */
.pr-grading-inline-table-scroll {
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior-x: contain;
-
- /* Firefox. Webkit equivalents below — without this Firefox keeps the default
- near-invisible overlay bar that prompted this change. */
scrollbar-width: thin;
scrollbar-color: #b9b9b9 #ededed;
}
-/* 10px tall so the bar is an easy pointer target on touch and trackpad. */
.pr-grading-inline-table-scroll::-webkit-scrollbar {
height: 10px;
}
@@ -1064,8 +1114,6 @@
border-radius: 999px;
}
-/* The transparent border + padding-box clip insets the thumb so it reads as a
- separate object instead of fusing with the track at the ends. */
.pr-grading-inline-table-scroll::-webkit-scrollbar-thumb {
background-color: #b9b9b9;
border: 2px solid transparent;
@@ -1077,15 +1125,10 @@
background-color: #9a9a9a;
}
-/* Dark mode: the light thumb above all but disappears on the #2b2b2b card, so
- go high-contrast. Scoped through the container, which already carries the
- dark-mode class, so the scroll wrapper itself needs no extra class. */
.pr-grading-inline-summary-container.dark-mode .pr-grading-inline-table-scroll {
scrollbar-color: #666 #2b2b2b;
}
-/* Darker than the #2b2b2b card, so the track and thumb separate from each other
- as well as from the card behind them. */
.pr-grading-inline-summary-container.dark-mode
.pr-grading-inline-table-scroll::-webkit-scrollbar-track {
background-color: #1f1f1f;
@@ -1103,9 +1146,6 @@
.pr-grading-inline-summary-table {
width: 100%;
-
- /* Floor for the five columns. Below this the table stops shrinking and the
- scroll rail above takes over rather than crushing the grade columns. */
min-width: 380px;
margin-bottom: 0;
border-collapse: collapse;
@@ -1183,9 +1223,6 @@
accent-color: #59359a;
}
-/* Narrow phones (~420px and below). Reclaim horizontal space so the four grade
- columns fit without scrolling where possible; the scroll rail above is the
- fallback when they still don't. */
@media (width <= 480px) {
.pr-grading-inline-summary-cell {
padding: 6px 8px 12px !important;
@@ -1205,8 +1242,6 @@
line-height: 1.25;
}
- /* The PR column is fixed-width on desktop; let the grade columns take the
- remaining space instead of it hogging a quarter of a 420px viewport. */
.pr-grading-inline-pr-cell {
width: auto;
white-space: nowrap;
diff --git a/src/components/PRGradingScreen/PRGradingScreenContainer.test.jsx b/src/components/PRGradingScreen/PRGradingScreenContainer.test.jsx
new file mode 100644
index 00000000000..448cef10efd
--- /dev/null
+++ b/src/components/PRGradingScreen/PRGradingScreenContainer.test.jsx
@@ -0,0 +1,49 @@
+import { render, screen } from '@testing-library/react';
+import axios from 'axios';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import PRGradingScreenContainer from './index';
+
+const route = vi.hoisted(() => ({ search: '', state: null }));
+
+vi.mock('axios');
+vi.mock('react-router-dom', () => ({ useLocation: () => route }));
+vi.mock('./PRGradingScreen', () => ({
+ default: ({ teamData, reviewers }) => (
+
+ {teamData.teamName} - {teamData.dateRange.start} - {reviewers.length} reviewers
+
+ ),
+}));
+
+describe('PR grading screen team selection', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ route.search = '';
+ route.state = null;
+ });
+
+ it('loads the built-in team when opened directly', () => {
+ render( );
+
+ expect(screen.getByText(/91NePRT - 5\/18\/2025/)).toBeInTheDocument();
+ expect(axios.get).not.toHaveBeenCalled();
+ });
+
+ it('reloads a custom team from its URL after a refresh', async () => {
+ route.search = '?teamId=custom1&weekStart=2026-10-04';
+ axios.get.mockResolvedValue({
+ data: [
+ {
+ _id: 'custom1',
+ teamName: 'Custom Team',
+ reviewerCount: 2,
+ reviewerNames: ['Alice', 'Bob'],
+ },
+ ],
+ });
+
+ render( );
+
+ expect(await screen.findByText(/Custom Team - 10\/4\/2026 - 2 reviewers/)).toBeInTheDocument();
+ });
+});
diff --git a/src/components/PRGradingScreen/PRGradingTest.jsx b/src/components/PRGradingScreen/PRGradingTest.jsx
index fd33f7d78e7..44bc714296c 100644
--- a/src/components/PRGradingScreen/PRGradingTest.jsx
+++ b/src/components/PRGradingScreen/PRGradingTest.jsx
@@ -50,7 +50,18 @@ const PRGradingTest = () => {
const handleTeamSelect = teamId => {
const team = allTeams.find(t => t.id === teamId);
- history.push('/pr-grading-screen', { teamId, config: team });
+ const params = new URLSearchParams({ teamId });
+ if (team?.fromDB) {
+ const start = new Date();
+ start.setDate(start.getDate() - start.getDay());
+ const date = [
+ start.getFullYear(),
+ String(start.getMonth() + 1).padStart(2, '0'),
+ String(start.getDate()).padStart(2, '0'),
+ ].join('-');
+ params.set('weekStart', date);
+ }
+ history.push(`/pr-grading-screen?${params.toString()}`, { teamId, config: team });
};
const handleDeleteConfig = async (e, teamId) => {
diff --git a/src/components/PRGradingScreen/PRGradingView.jsx b/src/components/PRGradingScreen/PRGradingView.jsx
new file mode 100644
index 00000000000..a5ad8671bb2
--- /dev/null
+++ b/src/components/PRGradingScreen/PRGradingView.jsx
@@ -0,0 +1,470 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Button, Card, Col, Container, Row } from 'react-bootstrap';
+import styles from './PRGradingScreen.module.css';
+import InlinePRSummary from './InlinePRSummary';
+import GRADE_OPTIONS from './gradeOptions';
+
+const PRGradingView = ({
+ darkMode,
+ teamData,
+ isFinalized,
+ onFinalize,
+ searchTerm,
+ onSearchTermChange,
+ roleFilter,
+ onRoleFilterChange,
+ availableRoles,
+ onClearSearch,
+ filteredReviewers,
+ promotedReviewerIds,
+ onPromoteClick,
+ activeInput,
+ inputValue,
+ inputError,
+ loadError,
+ gradeError,
+ isLoadingSaved,
+ isSavingPR,
+ isSavingGrade,
+ onInputValueChange,
+ onAddNewClick,
+ onInputSubmit,
+ onCancelInput,
+ onPRNumberClick,
+ promotingReviewer,
+ onConfirmPromotion,
+ onCancelPromotion,
+ showGradingModal,
+ onCloseGradingModal,
+ gradingReviewer,
+ onGradeChange,
+}) => {
+ const dm = darkMode ? 'dark-mode' : '';
+
+ return (
+
+
+
+
+
+
+
+
+ Weekly PR grading screen
+
+
+
+ {teamData.teamName} | {teamData.dateRange.start} to {teamData.dateRange.end}
+
+
+
+
+ {isFinalized ? 'Finalized' : 'Done'}
+
+
+ {loadError && (
+
+ {loadError}
+
+ )}
+
+
+
+ {gradeError && (
+
+ {gradeError}
+
+ )}
+ {/* Search and Filter Bar */}
+
+ onSearchTermChange(e.target.value)}
+ className={`${styles['pr-grading-screen-search-input']} ${dm}`}
+ />
+
+ {availableRoles.length > 0 && (
+ onRoleFilterChange(e.target.value)}
+ className={`${styles['pr-grading-screen-role-select']} ${dm}`}
+ >
+ All roles
+ {availableRoles.map(role => (
+
+ {role}
+
+ ))}
+
+ )}
+
+ {(searchTerm || roleFilter) && (
+
+ ✕ Clear
+
+ )}
+
+
+ {/* Reviewer Table */}
+
+
+
+
+ Reviewer Name
+ PR reviewed
+ PRs Needed
+ PR Numbers
+
+
+
+
+ {filteredReviewers.length === 0 ? (
+
+
+ No reviewers found
+
+
+ ) : (
+ filteredReviewers.map(reviewer => {
+ const isPromoted = promotedReviewerIds.has(reviewer.id);
+
+ return (
+
+
+ {/* Reviewer Name and Promote Button Cell */}
+
+
+
+ {reviewer.reviewer}
+
+ {reviewer.role && (
+
+ {reviewer.role}
+
+ )}
+ onPromoteClick(reviewer)}
+ style={{ marginTop: '8px' }}
+ >
+ {isPromoted ? '✅ Promoted' : '🏆 Promote'}
+
+
+
+
+ {/* PR Reviewed Count */}
+
+
+
+
+ {/* PRs Needed */}
+
+ {reviewer.prsNeeded}
+
+
+ {/* PR Numbers & Actions */}
+
+
+ {reviewer.gradedPrs.map(pr => (
+ onPRNumberClick(reviewer.id)}
+ onKeyDown={e => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ onPRNumberClick(reviewer.id);
+ }
+ }}
+ >
+ {pr.prNumbers}
+
+ ))}
+
+
+ {!isFinalized && activeInput !== reviewer.id && (
+ onAddNewClick(reviewer.id)}
+ >
+ + Add new
+
+ )}
+
+ {!isFinalized && activeInput === reviewer.id && (
+
+
+ onInputValueChange(e.target.value)}
+ className={styles['pr-grading-screen-pr-number-input']}
+ placeholder="1070 or 1070 + 1256"
+ />
+ {inputError && (
+
+ {inputError}
+
+ )}
+
+
+ onInputSubmit(reviewer.id)}
+ >
+ {isSavingPR ? 'Adding...' : 'Add'}
+
+
+ Cancel
+
+
+
+ )}
+
+
+ {/* Render inline summary component below each reviewer row for state synchronization and unit tests */}
+
+
+ );
+ })
+ )}
+
+
+
+
+
+
+
+
+ {/* PR Grading Modal */}
+ {showGradingModal && gradingReviewer && (
+
+
+
+
Grade PR for {gradingReviewer.reviewer}
+
+ ×
+
+
+
+
+
+
+ )}
+
+ {/* Promotion Confirmation Modal */}
+ {promotingReviewer && (
+
+
+
+
Confirm Promotion
+
+ ×
+
+
+
+
+
+ Reviewer: {promotingReviewer.reviewerName}
+
+
Team: {promotingReviewer.teamCode}
+
Status: {promotingReviewer.isConsistent ? '✅ Consistent' : '⚠️ Inconsistent'}
+
+ {promotingReviewer.weeklyPRs && promotingReviewer.weeklyPRs.length > 0 ? (
+
+
+
+ Week
+ PR Count
+
+
+
+ {promotingReviewer.weeklyPRs.map((count, index) => (
+
+ Week {index + 1}
+ {count}
+
+ ))}
+
+
+ ) : (
+
+ No weekly PR history is available for this reviewer.
+
+ )}
+
+
+
+ Cancel
+
+
+ Confirm Promotion
+
+
+
+
+
+ )}
+
+ );
+};
+
+PRGradingView.propTypes = {
+ darkMode: PropTypes.bool.isRequired,
+ teamData: PropTypes.shape({
+ teamName: PropTypes.string.isRequired,
+ dateRange: PropTypes.shape({
+ start: PropTypes.string.isRequired,
+ end: PropTypes.string.isRequired,
+ }).isRequired,
+ }).isRequired,
+ isFinalized: PropTypes.bool.isRequired,
+ onFinalize: PropTypes.func.isRequired,
+ searchTerm: PropTypes.string.isRequired,
+ onSearchTermChange: PropTypes.func.isRequired,
+ roleFilter: PropTypes.string.isRequired,
+ onRoleFilterChange: PropTypes.func.isRequired,
+ availableRoles: PropTypes.arrayOf(PropTypes.string).isRequired,
+ onClearSearch: PropTypes.func.isRequired,
+ filteredReviewers: PropTypes.arrayOf(PropTypes.object).isRequired,
+ promotedReviewerIds: (props, propName) => {
+ const val = props[propName];
+ // Accept Set or array-like objects with a has or includes method
+ if (
+ val &&
+ typeof val === 'object' &&
+ (typeof val.has === 'function' || typeof val.includes === 'function')
+ ) {
+ return null;
+ }
+ return new Error(`Invalid prop ${propName}: expected Set or Array, got ${typeof val}`);
+ },
+ onPromoteClick: PropTypes.func.isRequired,
+ activeInput: PropTypes.string,
+ inputValue: PropTypes.string.isRequired,
+ inputError: PropTypes.string.isRequired,
+ loadError: PropTypes.string.isRequired,
+ gradeError: PropTypes.string.isRequired,
+ isLoadingSaved: PropTypes.bool.isRequired,
+ isSavingPR: PropTypes.bool.isRequired,
+ isSavingGrade: PropTypes.bool.isRequired,
+ onInputValueChange: PropTypes.func.isRequired,
+ onAddNewClick: PropTypes.func.isRequired,
+ onInputSubmit: PropTypes.func.isRequired,
+ onCancelInput: PropTypes.func.isRequired,
+ onPRNumberClick: PropTypes.func.isRequired,
+ promotingReviewer: PropTypes.object,
+ onConfirmPromotion: PropTypes.func.isRequired,
+ onCancelPromotion: PropTypes.func.isRequired,
+ showGradingModal: PropTypes.string,
+ onCloseGradingModal: PropTypes.func.isRequired,
+ gradingReviewer: PropTypes.object,
+ onGradeChange: PropTypes.func.isRequired,
+};
+
+export default PRGradingView;
diff --git a/src/components/PRGradingScreen/PromotionConfirmationBox.jsx b/src/components/PRGradingScreen/PromotionConfirmationBox.jsx
index 2244539b6cd..952c408733c 100644
--- a/src/components/PRGradingScreen/PromotionConfirmationBox.jsx
+++ b/src/components/PRGradingScreen/PromotionConfirmationBox.jsx
@@ -4,9 +4,12 @@ import styles from './PRGradingScreen.module.css';
function PromotionConfirmationBox({ reviewer, onConfirm, onCancel, darkMode }) {
const { reviewerName, teamCode, teamReviewerName, weeklyPRs } = reviewer;
- const totalPRs = weeklyPRs.reduce((sum, w) => sum + w.count, 0);
- const avgPRs = weeklyPRs.length > 0 ? (totalPRs / weeklyPRs.length).toFixed(1) : 0;
- const isConsistent = weeklyPRs.every(w => w.count >= 8);
+ // With no recorded weekly history, show that plainly instead of computing stats:
+ // `[].every(...)` is true, which would otherwise label an unknown history "Consistent".
+ const hasWeeklyHistory = Array.isArray(weeklyPRs) && weeklyPRs.length > 0;
+ const totalPRs = hasWeeklyHistory ? weeklyPRs.reduce((sum, w) => sum + w.count, 0) : 0;
+ const avgPRs = hasWeeklyHistory ? (totalPRs / weeklyPRs.length).toFixed(1) : 0;
+ const isConsistent = hasWeeklyHistory && weeklyPRs.every(w => w.count >= 8);
return (
Weekly PR Stats
-
-
-
-
+
+
+
+
+ Week
+
+
+ PR Count
+
+
+
+
+ {weeklyPRs.map(w => (
+
+
+ {w.week}
+
+
+ {w.count}
+
+
+ ))}
+
+
+
+ {/* Summary Stats */}
+
-
+ {totalPRs}
+
+
+ Total PRs
+
+
+
- PR Count
-
-
-
-
- {weeklyPRs.map(w => (
-
-
- {w.week}
-
-
+
+ Avg PRs/Week
+
+
+
-
- ))}
-
-
-
- {/* Summary Stats */}
-
-
-
- {totalPRs}
-
-
- Total PRs
-
-
-
-
- {avgPRs}
-
-
- Avg PRs/Week
+ {isConsistent ? '✅' : '⚠️'}
+
+
+ {isConsistent ? 'Consistent' : 'Inconsistent'}
+
+
-
-
+ ) : (
+
-
- {isConsistent ? '✅' : '⚠️'}
-
-
- {isConsistent ? 'Consistent' : 'Inconsistent'}
-
-
-
+ No weekly PR history is available for this reviewer.
+
+ )}
{/* Confirmation question */}
-
+
Cancel
onConfirm(reviewerName, reviewer.reviewerId)}
- style={{
- padding: '8px 20px',
- borderRadius: '4px',
- border: 'none',
- background: '#28a745',
- color: '#fff',
- cursor: 'pointer',
- fontWeight: '600',
- }}
+ className={styles['pr-promotion-btn-confirm']}
+ onClick={() => onConfirm(reviewerName, reviewer.reviewerId || reviewer.id)}
>
- ✅ Confirm Promotion
+ Confirm Promotion
diff --git a/src/components/PRGradingScreen/ReviewerCell.jsx b/src/components/PRGradingScreen/ReviewerCell.jsx
new file mode 100644
index 00000000000..24069df947e
--- /dev/null
+++ b/src/components/PRGradingScreen/ReviewerCell.jsx
@@ -0,0 +1,49 @@
+import PropTypes from 'prop-types';
+import { Button } from 'react-bootstrap';
+import styles from './PRGradingScreen.module.css';
+
+const ReviewerCell = ({ reviewer, isPromoted, promoted, onPromoteClick }) => {
+ // Compatibility fallback for promotion status props
+ const promotedState = isPromoted !== undefined ? isPromoted : promoted;
+
+ return (
+ /* Vertical container anchoring the reviewer name and action button */
+
+ {/* Display the reviewer name */}
+ {reviewer.reviewer}
+
+ {/* Render the Promote / Promoted button directly beneath the reviewer name */}
+ onPromoteClick(reviewer)}
+ style={{
+ fontSize: '12px',
+ padding: '3px 10px',
+ borderRadius: '4px',
+ display: 'inline-flex',
+ alignItems: 'center',
+ gap: '4px',
+ }}
+ >
+ {promotedState ? 'Promoted' : 'Promote'}
+
+
+ );
+};
+
+ReviewerCell.propTypes = {
+ reviewer: PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ reviewer: PropTypes.string.isRequired,
+ }).isRequired,
+ isPromoted: PropTypes.bool,
+ promoted: PropTypes.bool,
+ onPromoteClick: PropTypes.func.isRequired,
+};
+
+export default ReviewerCell;
diff --git a/src/components/PRGradingScreen/__tests__/PRGradingScreen.test.jsx b/src/components/PRGradingScreen/__tests__/PRGradingScreen.test.jsx
index baa51aa6bf5..a226731c49f 100644
--- a/src/components/PRGradingScreen/__tests__/PRGradingScreen.test.jsx
+++ b/src/components/PRGradingScreen/__tests__/PRGradingScreen.test.jsx
@@ -1,8 +1,17 @@
-import { fireEvent, render, screen } from '@testing-library/react';
-import { describe, expect, it, vi } from 'vitest';
+import axios from 'axios';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
import PRGradingScreen from '../PRGradingScreen';
import GRADE_OPTIONS from '../gradeOptions';
+vi.mock('axios');
+
+beforeEach(() => {
+ vi.clearAllMocks();
+ axios.get.mockResolvedValue({ data: [] });
+ axios.post.mockResolvedValue({ data: { status: 'ok' } });
+});
+
vi.mock('react-redux', async () => {
const actual = await vi.importActual('react-redux');
return { ...actual, useSelector: () => false };
@@ -24,8 +33,10 @@ const makeReviewers = () => [
},
];
-const renderScreen = () =>
+const renderScreen = async () => {
render(
);
+ await waitFor(() => expect(screen.getByRole('button', { name: '+ Add new' })).toBeEnabled());
+};
// Both tables label their checkboxes `${prNumbers} ${label}`. Before the modal
// opens only the inline summary is mounted, so there is exactly one match;
@@ -37,42 +48,45 @@ const checkboxes = label => screen.queryAllByLabelText(`1070 ${label}`);
const openModal = () => fireEvent.click(screen.getByRole('button', { name: '1070' }));
describe('PRGradingScreen grade sync between inline summary and modal', () => {
- it.each(GRADE_OPTIONS.map(o => o.label))('reflects an inline "%s" grade in the modal', label => {
- renderScreen();
-
- // Only the inline summary is mounted before the modal opens.
- const [inline] = checkboxes(label);
- fireEvent.click(inline);
- expect(inline).toBeChecked();
-
- openModal();
-
- // Both tables are mounted now; the second match is the modal's.
- const all = checkboxes(label);
- expect(all).toHaveLength(2);
- expect(all[1]).toBeChecked();
- });
+ it.each(GRADE_OPTIONS.map(o => o.label))(
+ 'reflects an inline "%s" grade in the modal',
+ async label => {
+ await renderScreen();
+
+ // Only the inline summary is mounted before the modal opens.
+ const [inline] = checkboxes(label);
+ fireEvent.click(inline);
+ await waitFor(() => expect(inline).toBeChecked());
+
+ openModal();
+
+ // Both tables are mounted now; the second match is the modal's.
+ const all = checkboxes(label);
+ expect(all).toHaveLength(2);
+ expect(all[1]).toBeChecked();
+ },
+ );
- it('keeps a single grade selected when switching options inline', () => {
- renderScreen();
+ it('keeps a single grade selected when switching options inline', async () => {
+ await renderScreen();
fireEvent.click(checkboxes('Cannot find image')[0]);
- expect(checkboxes('Cannot find image')[0]).toBeChecked();
+ await waitFor(() => expect(checkboxes('Cannot find image')[0]).toBeChecked());
expect(checkboxes('Okay')[0]).not.toBeChecked();
fireEvent.click(checkboxes('Exceptional')[0]);
- expect(checkboxes('Exceptional')[0]).toBeChecked();
+ await waitFor(() => expect(checkboxes('Exceptional')[0]).toBeChecked());
expect(checkboxes('Cannot find image')[0]).not.toBeChecked();
});
- it('propagates a modal grade back to the inline summary', () => {
- renderScreen();
+ it('propagates a modal grade back to the inline summary', async () => {
+ await renderScreen();
openModal();
const [inline, modal] = checkboxes('Cannot find image');
fireEvent.click(modal);
- expect(modal).toBeChecked();
+ await waitFor(() => expect(modal).toBeChecked());
expect(inline).toBeChecked();
});
});
diff --git a/src/components/PRGradingScreen/index.jsx b/src/components/PRGradingScreen/index.jsx
index 37aed6ec1eb..f3eaef45d50 100644
--- a/src/components/PRGradingScreen/index.jsx
+++ b/src/components/PRGradingScreen/index.jsx
@@ -1,5 +1,8 @@
+import axios from 'axios';
+import { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { v4 as uuidv4 } from 'uuid';
+import { ENDPOINTS } from '../../utils/URL';
import { getDataByTeamId } from './mockData';
import PRGradingScreen from './PRGradingScreen';
@@ -7,8 +10,43 @@ const STATIC_IDS = ['team1', 'team2', 'team3'];
const PRGradingScreenContainer = () => {
const location = useLocation();
- const teamId = location.state?.teamId || 'team1';
- const config = location.state?.config || null;
+ const params = new URLSearchParams(location.search);
+ const teamId = params.get('teamId') || location.state?.teamId || 'team1';
+ const [savedConfig, setSavedConfig] = useState(null);
+ const [configError, setConfigError] = useState('');
+ const locationConfig = location.state?.teamId === teamId ? location.state?.config : null;
+ const config = locationConfig || (savedConfig?.id === teamId ? savedConfig : null);
+
+ useEffect(() => {
+ if (STATIC_IDS.includes(teamId) || locationConfig) return undefined;
+ let active = true;
+ axios
+ .get(ENDPOINTS.PR_GRADING_CONFIG)
+ .then(response => {
+ if (!active) return;
+ const match = response.data.find(item => item._id === teamId);
+ if (match) {
+ setSavedConfig({
+ id: match._id,
+ name: match.teamName,
+ reviewerCount: match.reviewerCount,
+ reviewerNames: match.reviewerNames,
+ });
+ } else {
+ setConfigError('Team configuration not found.');
+ }
+ })
+ .catch(() => {
+ if (active) setConfigError('Could not load the team configuration.');
+ });
+ return () => {
+ active = false;
+ };
+ }, [teamId, locationConfig]);
+
+ if (!STATIC_IDS.includes(teamId) && !config) {
+ return
{configError || 'Loading team configuration...'}
;
+ }
if (!STATIC_IDS.includes(teamId) && config) {
const reviewers = Array.from({ length: config.reviewerCount }, (_, i) => ({
@@ -19,11 +57,20 @@ const PRGradingScreenContainer = () => {
gradedPrs: [],
}));
+ const weekStart = params.get('weekStart');
+ if (weekStart && !/^\d{4}-\d{2}-\d{2}$/.test(weekStart)) {
+ return
Invalid week start date.
;
+ }
+ const start = weekStart ? new Date(`${weekStart}T12:00:00`) : new Date();
+ if (Number.isNaN(start.getTime())) return
Invalid week start date.
;
+ if (!weekStart) start.setDate(start.getDate() - start.getDay());
+ const end = new Date(start);
+ end.setDate(start.getDate() + 7);
const teamData = {
teamName: config.name,
dateRange: {
- start: new Date().toLocaleDateString(),
- end: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toLocaleDateString(),
+ start: start.toLocaleDateString('en-US'),
+ end: end.toLocaleDateString('en-US'),
},
};
diff --git a/src/components/PRGradingScreen/prGradingPersistence.js b/src/components/PRGradingScreen/prGradingPersistence.js
new file mode 100644
index 00000000000..8342bb7b345
--- /dev/null
+++ b/src/components/PRGradingScreen/prGradingPersistence.js
@@ -0,0 +1,85 @@
+import axios from 'axios';
+import { ENDPOINTS } from '../../utils/URL';
+
+const getPRNumbers = value => value.split('+').map(number => number.trim());
+const toDisplayGrade = grade => (grade === 'No Correct Image' ? 'Cannot find image' : grade);
+
+const getGradingKey = teamData => {
+ const [month, day, year] = teamData.dateRange.start.split('/').map(Number);
+ const date = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
+ return { teamKey: `pr-grading-screen:${teamData.teamName}`, date };
+};
+
+export const loadSavedGradings = async teamData => {
+ const { teamKey, date } = getGradingKey(teamData);
+ const response = await axios.get(ENDPOINTS.WEEKLY_GRADING, {
+ params: { team: teamKey, date, weekStart: date },
+ });
+ return response.data;
+};
+
+export const saveAddedPR = (teamData, reviewer, prNumbers) => {
+ const { teamKey, date } = getGradingKey(teamData);
+ return axios.post(ENDPOINTS.WEEKLY_GRADING_SAVE, {
+ teamCode: teamKey,
+ teamName: teamKey,
+ date,
+ gradings: [
+ {
+ reviewer: reviewer.reviewer,
+ prsNeeded: reviewer.prsNeeded,
+ prsReviewed: reviewer.gradedPrs.length + 1,
+ gradedPrs: [{ prNumbers, grade: 'Okay' }],
+ },
+ ],
+ });
+};
+
+export const saveGradeChange = (teamData, reviewer, prNumbers, grade) => {
+ const { teamKey, date } = getGradingKey(teamData);
+ return axios.post(ENDPOINTS.WEEKLY_GRADING_SAVE, {
+ teamCode: teamKey,
+ teamName: teamKey,
+ date,
+ gradings: [
+ {
+ reviewer: reviewer.reviewer,
+ prsNeeded: reviewer.prsNeeded,
+ prsReviewed: reviewer.gradedPrs.length,
+ gradedPrs: [{ prNumbers, grade }],
+ },
+ ],
+ });
+};
+
+export const mergeSavedGradings = (reviewers, savedGradings) => {
+ const savedByReviewer = new Map(savedGradings.map(grading => [grading.reviewer, grading]));
+
+ return reviewers.map(reviewer => {
+ const savedPrs = savedByReviewer.get(reviewer.reviewer)?.gradedPrs || [];
+ const savedGrades = new Map(
+ savedPrs.map(pr => [getPRNumbers(pr.prNumbers).join('+'), toDisplayGrade(pr.grade)]),
+ );
+ const seen = new Set(reviewer.gradedPrs.flatMap(pr => getPRNumbers(pr.prNumbers)));
+ const additions = savedPrs
+ .filter(pr => {
+ const numbers = getPRNumbers(pr.prNumbers);
+ if (numbers.some(number => seen.has(number))) return false;
+ numbers.forEach(number => seen.add(number));
+ return true;
+ })
+ .map(pr => ({
+ ...pr,
+ id: String(pr._id || pr.id || pr.prNumbers),
+ grade: toDisplayGrade(pr.grade),
+ }));
+ const gradedPrs = [
+ ...reviewer.gradedPrs.map(pr => ({
+ ...pr,
+ grade: savedGrades.get(getPRNumbers(pr.prNumbers).join('+')) || pr.grade,
+ })),
+ ...additions,
+ ];
+ return { ...reviewer, gradedPrs, prsReviewed: gradedPrs.length };
+ });
+};
diff --git a/src/components/PRGradingScreen/prGradingPersistence.test.js b/src/components/PRGradingScreen/prGradingPersistence.test.js
new file mode 100644
index 00000000000..f5e59393289
--- /dev/null
+++ b/src/components/PRGradingScreen/prGradingPersistence.test.js
@@ -0,0 +1,154 @@
+import axios from 'axios';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { ENDPOINTS } from '../../utils/URL';
+import {
+ loadSavedGradings,
+ mergeSavedGradings,
+ saveAddedPR,
+ saveGradeChange,
+} from './prGradingPersistence';
+
+vi.mock('axios');
+
+const teamData = {
+ teamName: '91NePRT',
+ dateRange: { start: '5/18/2025', end: '5/25/2025' },
+};
+
+describe('PR grading persistence', () => {
+ beforeEach(() => vi.clearAllMocks());
+
+ it('loads saved PRs for the displayed team and week', async () => {
+ axios.get.mockResolvedValue({ data: [{ reviewer: 'Alice', gradedPrs: [] }] });
+
+ await expect(loadSavedGradings(teamData)).resolves.toHaveLength(1);
+ expect(axios.get).toHaveBeenCalledWith(ENDPOINTS.WEEKLY_GRADING, {
+ params: {
+ team: 'pr-grading-screen:91NePRT',
+ date: '2025-05-18',
+ weekStart: '2025-05-18',
+ },
+ });
+ });
+
+ it('saves only the newly added PR for one reviewer', async () => {
+ const reviewer = {
+ reviewer: 'Alice',
+ prsNeeded: 3,
+ gradedPrs: [{ prNumbers: '1234', grade: 'Okay' }],
+ };
+
+ await saveAddedPR(teamData, reviewer, '5678 + 9999');
+
+ expect(axios.post).toHaveBeenCalledWith(ENDPOINTS.WEEKLY_GRADING_SAVE, {
+ teamCode: 'pr-grading-screen:91NePRT',
+ teamName: 'pr-grading-screen:91NePRT',
+ date: '2025-05-18',
+ gradings: [
+ {
+ reviewer: 'Alice',
+ prsNeeded: 3,
+ prsReviewed: 2,
+ gradedPrs: [{ prNumbers: '5678 + 9999', grade: 'Okay' }],
+ },
+ ],
+ });
+ });
+
+ it('saves only the changed grade without increasing the reviewed count', async () => {
+ const reviewer = {
+ reviewer: 'Alice',
+ prsNeeded: 3,
+ gradedPrs: [{ prNumbers: '1234', grade: 'Okay' }],
+ };
+
+ await saveGradeChange(teamData, reviewer, '1234', 'Exceptional');
+
+ expect(axios.post).toHaveBeenCalledWith(ENDPOINTS.WEEKLY_GRADING_SAVE, {
+ teamCode: 'pr-grading-screen:91NePRT',
+ teamName: 'pr-grading-screen:91NePRT',
+ date: '2025-05-18',
+ gradings: [
+ {
+ reviewer: 'Alice',
+ prsNeeded: 3,
+ prsReviewed: 1,
+ gradedPrs: [{ prNumbers: '1234', grade: 'Exceptional' }],
+ },
+ ],
+ });
+ });
+
+ it('merges saved PRs with built-in entries without duplicating them', () => {
+ const reviewers = [
+ {
+ id: '1',
+ reviewer: 'Alice',
+ prsNeeded: 3,
+ gradedPrs: [{ id: 'pr1', prNumbers: '1234', grade: 'Okay' }],
+ },
+ ];
+ const saved = [
+ {
+ reviewer: 'Alice',
+ gradedPrs: [
+ { _id: 'saved1', prNumbers: '1234', grade: 'Okay' },
+ { _id: 'saved2', prNumbers: '5678 + 9999', grade: 'Okay' },
+ ],
+ },
+ ];
+
+ expect(mergeSavedGradings(reviewers, saved)[0]).toMatchObject({
+ prsReviewed: 2,
+ gradedPrs: [
+ { id: 'pr1', prNumbers: '1234' },
+ { id: 'saved2', prNumbers: '5678 + 9999' },
+ ],
+ });
+ });
+
+ it('uses the saved grade for a built-in PR after reloading', () => {
+ const reviewers = [
+ {
+ id: '1',
+ reviewer: 'Alice',
+ gradedPrs: [{ id: 'pr1', prNumbers: '1234 + 5678', grade: 'Okay' }],
+ },
+ ];
+ const saved = [
+ {
+ reviewer: 'Alice',
+ gradedPrs: [{ prNumbers: '1234+5678', grade: 'Exceptional' }],
+ },
+ ];
+
+ expect(mergeSavedGradings(reviewers, saved)[0]).toMatchObject({
+ prsReviewed: 1,
+ gradedPrs: [{ id: 'pr1', prNumbers: '1234 + 5678', grade: 'Exceptional' }],
+ });
+ });
+
+ it('displays the backend image grade for built-in and newly saved PRs', () => {
+ const reviewers = [
+ {
+ id: '1',
+ reviewer: 'Alice',
+ gradedPrs: [{ id: 'pr1', prNumbers: '1234', grade: 'Okay' }],
+ },
+ ];
+ const saved = [
+ {
+ reviewer: 'Alice',
+ gradedPrs: [
+ { prNumbers: '1234', grade: 'No Correct Image' },
+ { prNumbers: '5678', grade: 'No Correct Image' },
+ ],
+ },
+ ];
+
+ expect(mergeSavedGradings(reviewers, saved)[0].gradedPrs).toMatchObject([
+ { prNumbers: '1234', grade: 'Cannot find image' },
+ { prNumbers: '5678', grade: 'Cannot find image' },
+ ]);
+ });
+});
diff --git a/src/components/PRGradingScreen/usePRPromotion.js b/src/components/PRGradingScreen/usePRPromotion.js
new file mode 100644
index 00000000000..fe1a3facb16
--- /dev/null
+++ b/src/components/PRGradingScreen/usePRPromotion.js
@@ -0,0 +1,53 @@
+import { useState, useCallback } from 'react';
+
+export const usePRPromotion = (defaultTeamName = 'PR Review Team') => {
+ // State to track the reviewer currently undergoing promotion confirmation
+ const [promotingReviewer, setPromotingReviewer] = useState(null);
+
+ // Set to track IDs of reviewers who have been successfully promoted
+ const [promotedReviewerIds, setPromotedReviewerIds] = useState(new Set());
+
+ // Handler triggered when the Promote button is clicked
+ const handlePromoteClick = useCallback(
+ reviewer => {
+ setPromotingReviewer({
+ id: reviewer.id,
+ reviewerId: reviewer.id,
+ reviewerName: reviewer.reviewer,
+ teamCode: reviewer.teamCode || defaultTeamName,
+ teamReviewerName: reviewer.teamLeader || reviewer.teamReviewerName || 'Team Lead',
+ // Pass through real history only; an empty list means "no history available".
+ weeklyPRs: reviewer.weeklyPRs || [],
+ });
+ },
+ [defaultTeamName],
+ );
+
+ // Handler triggered when the promotion is confirmed in the modal
+ const handleConfirmPromotion = useCallback(() => {
+ setPromotedReviewerIds(prev => {
+ const next = new Set(prev);
+ if (promotingReviewer) {
+ const targetId = promotingReviewer.reviewerId || promotingReviewer.id;
+ if (targetId) {
+ next.add(targetId);
+ }
+ }
+ return next;
+ });
+ setPromotingReviewer(null);
+ }, [promotingReviewer]);
+
+ // Handler triggered when the promotion confirmation is cancelled
+ const handleCancelPromotion = useCallback(() => {
+ setPromotingReviewer(null);
+ }, []);
+
+ return {
+ promotingReviewer,
+ promotedReviewerIds,
+ handlePromoteClick,
+ handleConfirmPromotion,
+ handleCancelPromotion,
+ };
+};
diff --git a/src/components/PRPromotions/DisplayBox.jsx b/src/components/PRPromotions/DisplayBox.jsx
index 502d265187c..b9447e4ed91 100644
--- a/src/components/PRPromotions/DisplayBox.jsx
+++ b/src/components/PRPromotions/DisplayBox.jsx
@@ -1,47 +1,115 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
-import { useEffect, useRef, useState } from 'react';
+import { useCallback, useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
+import { useSelector } from 'react-redux';
+import { toast } from 'react-toastify';
+import { getPromotionPending, postPromotionEligibility } from '../../actions/promotionActions';
import styles from './DisplayBox.module.css';
const VIBGYOR_COLOR_COUNT = 7;
+const MONGO_ID_PATTERN = /^[a-fA-F0-9]{24}$/;
-const MOCK_PROMOTION_DATA = [
- {
- prReviewer: 'Akshay - Jayram',
- teamCode: '123',
- teamReviewerName: 'Team Leader 1',
- weeklyPRs: [
- { week: '2024-06-01', prCount: 12 },
- { week: '2024-06-08', prCount: 15 },
- { week: '2024-06-15', prCount: 10 },
- { week: '2024-06-22', prCount: 18 },
- { week: '2024-06-29', prCount: 14 },
- { week: '2024-07-06', prCount: 16 },
- { week: '2024-07-13', prCount: 20 },
- ],
- },
- {
- prReviewer: 'Ghazi1212',
- teamCode: '456',
- teamReviewerName: 'Team Leader 2',
- weeklyPRs: [
- { week: '2024-06-01', prCount: 12 },
- { week: '2024-06-08', prCount: 15 },
- { week: '2024-06-15', prCount: 10 },
- { week: '2024-06-22', prCount: 18 },
- { week: '2024-06-29', prCount: 14 },
- ],
- },
-];
+function buildRequestor(user) {
+ if (!user?.userid) {
+ return null;
+ }
+
+ return {
+ requestorId: user.userid,
+ role: user.role,
+ };
+}
+
+function normalizeId(value) {
+ if (value == null || value === '') {
+ return undefined;
+ }
+ if (typeof value === 'string') {
+ return value;
+ }
+ if (typeof value === 'object') {
+ if (value.$oid) {
+ return String(value.$oid);
+ }
+ if (value._id) {
+ return normalizeId(value._id);
+ }
+ }
+ return String(value);
+}
+
+function getPromotableIds(promotions, checkedItems) {
+ return promotions
+ .filter((_, index) => checkedItems[index])
+ .map(promotion => normalizeId(promotion.reviewerId))
+ .filter(id => id && MONGO_ID_PATTERN.test(id));
+}
+
+function getWeeklyPrCount(pr) {
+ return pr?.count ?? pr?.prCount ?? 0;
+}
export default function DisplayBox({ onClose, darkMode = false }) {
- const [checkedItems, setCheckedItems] = useState(() => MOCK_PROMOTION_DATA.map(() => true));
+ const authUser = useSelector(state => state.auth?.user);
+ const [promotions, setPromotions] = useState([]);
+ const [checkedItems, setCheckedItems] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [isConfirming, setIsConfirming] = useState(false);
+ const [statusMessage, setStatusMessage] = useState('');
const masterCheckboxRef = useRef(null);
- const allChecked = checkedItems.length > 0 && checkedItems.every(Boolean);
-
+ const allChecked =
+ promotions.length > 0 && checkedItems.length > 0 && checkedItems.every(Boolean);
const someChecked = checkedItems.some(Boolean);
+ const confirmDisabled = isLoading || isConfirming || !someChecked;
+
+ const applyPendingRows = useCallback(rows => {
+ const nextPromotions = Array.isArray(rows) ? rows : [];
+ setPromotions(nextPromotions);
+ setCheckedItems(nextPromotions.map(() => true));
+ }, []);
+
+ const loadPendingPromotions = useCallback(async () => {
+ const requestor = buildRequestor(authUser);
+ if (!requestor) {
+ throw new Error('Missing requestor');
+ }
+
+ const rows = await getPromotionPending(requestor);
+ applyPendingRows(rows);
+ return Array.isArray(rows) ? rows : [];
+ }, [applyPendingRows, authUser]);
+
+ useEffect(() => {
+ let cancelled = false;
+
+ const load = async () => {
+ setIsLoading(true);
+ setStatusMessage('');
+
+ try {
+ await loadPendingPromotions();
+ } catch {
+ if (!cancelled) {
+ applyPendingRows([]);
+ const message = 'Failed to load pending promotions.';
+ setStatusMessage(message);
+ toast.error(message);
+ }
+ } finally {
+ if (!cancelled) {
+ setIsLoading(false);
+ }
+ }
+ };
+
+ void load();
+
+ return () => {
+ cancelled = true;
+ };
+ }, [applyPendingRows, loadPendingPromotions]);
useEffect(() => {
if (masterCheckboxRef.current) {
@@ -51,7 +119,7 @@ export default function DisplayBox({ onClose, darkMode = false }) {
useEffect(() => {
const handleKeyDown = event => {
- if (event.key === 'Escape') {
+ if (event.key === 'Escape' && !isConfirming) {
onClose();
}
};
@@ -61,7 +129,7 @@ export default function DisplayBox({ onClose, darkMode = false }) {
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
- }, [onClose]);
+ }, [isConfirming, onClose]);
const handleCheckedBoxChange = index => {
setCheckedItems(currentItems =>
@@ -75,16 +143,43 @@ export default function DisplayBox({ onClose, darkMode = false }) {
setCheckedItems(currentItems => currentItems.map(() => shouldSelectAll));
};
- const handleConfirm = () => {
- const selectedReviewers = MOCK_PROMOTION_DATA.filter((_, index) => checkedItems[index]);
+ const handleConfirm = async () => {
+ const selectedCount = checkedItems.filter(Boolean).length;
+ const selectedIds = getPromotableIds(promotions, checkedItems);
+ const requestor = buildRequestor(authUser);
+
+ if (selectedIds.length === 0) {
+ const message = 'No valid reviewers selected for promotion.';
+ setStatusMessage(message);
+ toast.error(message);
+ return;
+ }
+
+ setIsConfirming(true);
+ setStatusMessage('');
+
+ try {
+ await postPromotionEligibility(selectedIds, requestor);
+ const nextPromotions = await loadPendingPromotions();
- console.log('Selected reviewers:', selectedReviewers);
+ const message = `Successfully promoted ${selectedCount} reviewer(s).`;
+ setStatusMessage(message);
+ toast.success(message);
- onClose();
+ if (nextPromotions.length === 0) {
+ onClose();
+ }
+ } catch {
+ const message = 'Failed to process promotions.';
+ setStatusMessage(message);
+ toast.error(message);
+ } finally {
+ setIsConfirming(false);
+ }
};
const handleOverlayMouseDown = event => {
- if (event.target === event.currentTarget) {
+ if (event.target === event.currentTarget && !isConfirming) {
onClose();
}
};
@@ -96,12 +191,17 @@ export default function DisplayBox({ onClose, darkMode = false }) {
const getBadgeClassName = index =>
[styles.prCountBadge, styles[`color-${index % VIBGYOR_COLOR_COUNT}`]].filter(Boolean).join(' ');
+ const emptyMessage = isLoading
+ ? 'Loading pending promotions...'
+ : 'No PR reviewers left to promote.';
+
return (
@@ -120,6 +222,7 @@ export default function DisplayBox({ onClose, darkMode = false }) {
type="checkbox"
checked={allChecked}
onChange={handleSelectAll}
+ disabled={promotions.length === 0 || isLoading || isConfirming}
aria-label="Select all reviewers"
/>
@@ -127,37 +230,59 @@ export default function DisplayBox({ onClose, darkMode = false }) {
PR Reviewer
Team Code
Team Leader Name
- Weekly PR Counts
+ Weekly PRs
- {MOCK_PROMOTION_DATA.map((promotion, index) => (
-
+ {promotions.length === 0 && (
+
+
+ {emptyMessage}
+
+
+ )}
+ {promotions.map((promotion, index) => (
+
handleCheckedBoxChange(index)}
+ disabled={isLoading || isConfirming}
aria-label={`Select reviewer ${promotion.prReviewer}`}
/>
{promotion.prReviewer}
{promotion.teamCode}
- {promotion.teamReviewerName}
+ {promotion.teamLeaderName}
-
- {promotion.weeklyPRs.map((pr, prIndex) => (
-
- {pr.prCount}
-
- ))}
+
+ {(promotion.weeklyPRs || []).map((pr, prIndex) => {
+ const prCount = getWeeklyPrCount(pr);
+ return (
+
+ {prCount}
+
+ );
+ })}
@@ -170,6 +295,7 @@ export default function DisplayBox({ onClose, darkMode = false }) {
type="button"
className={`${styles.button} ${styles.cancelButton}`}
onClick={onClose}
+ disabled={isConfirming}
>
Cancel
@@ -177,10 +303,10 @@ export default function DisplayBox({ onClose, darkMode = false }) {
- Confirm
+ {isConfirming ? 'Confirming...' : 'Confirm'}
diff --git a/src/components/PRPromotions/DisplayBox.module.css b/src/components/PRPromotions/DisplayBox.module.css
index 64be6b09538..d307e563f98 100644
--- a/src/components/PRPromotions/DisplayBox.module.css
+++ b/src/components/PRPromotions/DisplayBox.module.css
@@ -1,6 +1,7 @@
.overlay {
position: fixed;
inset: 0;
+ z-index: 2000;
display: flex;
justify-content: center;
align-items: center;
@@ -46,6 +47,22 @@
color: #e5e7eb;
}
+.statusMessage {
+ display: block;
+ margin: 0 0 12px;
+ color: #1d4ed8;
+ font-size: 14px;
+ text-align: center;
+}
+
+.popupDark .statusMessage {
+ color: #93c5fd;
+}
+
+.messageCell {
+ text-align: center;
+}
+
/* ======================================================
TABLE
====================================================== */
diff --git a/src/components/PRPromotions/__tests__/DisplayBox.test.jsx b/src/components/PRPromotions/__tests__/DisplayBox.test.jsx
new file mode 100644
index 00000000000..6e81807c777
--- /dev/null
+++ b/src/components/PRPromotions/__tests__/DisplayBox.test.jsx
@@ -0,0 +1,211 @@
+import { render, screen, waitFor, within } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { Provider } from 'react-redux';
+import configureMockStore from 'redux-mock-store';
+import { vi } from 'vitest';
+import { toast } from 'react-toastify';
+import DisplayBox from '../DisplayBox';
+import { getPromotionPending, postPromotionEligibility } from '../../../actions/promotionActions';
+
+vi.mock('../../../actions/promotionActions', () => ({
+ getPromotionPending: vi.fn(),
+ postPromotionEligibility: vi.fn(),
+}));
+
+vi.mock('react-toastify', () => ({
+ toast: {
+ success: vi.fn(),
+ error: vi.fn(),
+ info: vi.fn(),
+ },
+}));
+
+const mockStore = configureMockStore([]);
+
+const SAMPLE_PENDING = [
+ {
+ reviewerId: '637af0c0fb9bbc1e308cff01',
+ prReviewer: 'Akshay Jayaram',
+ teamCode: 'ABC12',
+ teamLeaderName: 'Chris Martinez',
+ weeklyPRs: [
+ { week: '2026-W31', count: 7 },
+ { week: '2026-W32', count: 7 },
+ ],
+ },
+ {
+ reviewerId: '637af0c0fb9bbc1e308cff02',
+ prReviewer: 'Ghazi1212',
+ teamCode: '456',
+ teamLeaderName: 'Sam Patel',
+ weeklyPRs: [
+ { week: '2026-W31', count: 2 },
+ { week: '2026-W32', count: 10 },
+ { week: '2026-W33', count: 11 },
+ ],
+ },
+];
+
+function renderDisplayBox(darkMode = false) {
+ const onClose = vi.fn();
+ const store = mockStore({
+ auth: { user: { userid: 'aaaaaaaaaaaaaaaaaaaaaaaa', role: 'Administrator' } },
+ theme: { darkMode },
+ });
+
+ const view = render(
+
+
+ ,
+ );
+
+ return { onClose, unmount: view.unmount };
+}
+
+describe('DisplayBox promotion confirmation modal', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ getPromotionPending.mockResolvedValue(SAMPLE_PENDING);
+ postPromotionEligibility.mockResolvedValue({ message: 'Members promoted successfully.' });
+ });
+
+ it('loads pending reviewers and selects every checkbox by default', async () => {
+ renderDisplayBox();
+
+ expect(await screen.findByText('Akshay Jayaram')).toBeInTheDocument();
+ expect(screen.getByText('Ghazi1212')).toBeInTheDocument();
+
+ const checkboxes = screen.getAllByRole('checkbox');
+ expect(checkboxes).toHaveLength(3);
+ checkboxes.forEach(checkbox => {
+ expect(checkbox).toBeChecked();
+ });
+
+ expect(getPromotionPending).toHaveBeenCalledWith({
+ requestorId: 'aaaaaaaaaaaaaaaaaaaaaaaa',
+ role: 'Administrator',
+ });
+ expect(screen.getByRole('button', { name: 'Confirm' })).toBeEnabled();
+ });
+
+ it('uses Team Leader Name and Weekly PRs column titles', async () => {
+ renderDisplayBox();
+
+ await screen.findByText('Akshay Jayaram');
+
+ expect(screen.getByText('Team Leader Name')).toBeInTheDocument();
+ expect(screen.queryByText('Team Reviewer Name')).not.toBeInTheDocument();
+ expect(screen.getByText('Weekly PRs')).toBeInTheDocument();
+ expect(screen.queryByText('Weekly PR Counts')).not.toBeInTheDocument();
+ });
+
+ it('shows team leader names and weekly PR counts in circles', async () => {
+ renderDisplayBox();
+
+ await screen.findByText('Akshay Jayaram');
+
+ expect(screen.getByText('Chris Martinez')).toBeInTheDocument();
+ expect(screen.getByText('Sam Patel')).toBeInTheDocument();
+
+ const firstReviewerRow = screen.getByRole('row', { name: /Akshay Jayaram/i });
+ expect(within(firstReviewerRow).getAllByText('7')).toHaveLength(2);
+ });
+
+ it('disables Confirm while loading and when none are selected', async () => {
+ let resolvePending;
+ getPromotionPending.mockReturnValue(
+ new Promise(resolve => {
+ resolvePending = resolve;
+ }),
+ );
+
+ renderDisplayBox();
+
+ expect(screen.getByRole('button', { name: 'Confirm' })).toBeDisabled();
+ expect(screen.getByText('Loading pending promotions...')).toBeInTheDocument();
+
+ resolvePending(SAMPLE_PENDING);
+
+ await screen.findByText('Akshay Jayaram');
+ expect(screen.getByRole('button', { name: 'Confirm' })).toBeEnabled();
+
+ await userEvent.click(screen.getByLabelText('Select all reviewers'));
+ expect(screen.getByRole('button', { name: 'Confirm' })).toBeDisabled();
+ });
+
+ it('closes without promoting when Cancel is clicked', async () => {
+ const { onClose } = renderDisplayBox();
+
+ await screen.findByText('Akshay Jayaram');
+ await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
+
+ expect(onClose).toHaveBeenCalledTimes(1);
+ expect(postPromotionEligibility).not.toHaveBeenCalled();
+ });
+
+ it('promotes selected reviewers then refetches the pending list', async () => {
+ const { onClose } = renderDisplayBox();
+
+ await screen.findByText('Akshay Jayaram');
+
+ // Uncheck Akshay so only Ghazi is promoted; refetch returns Akshay still pending.
+ getPromotionPending.mockResolvedValueOnce([SAMPLE_PENDING[0]]);
+
+ await userEvent.click(screen.getByLabelText('Select reviewer Akshay Jayaram'));
+ await userEvent.click(screen.getByRole('button', { name: 'Confirm' }));
+
+ await waitFor(() => {
+ expect(postPromotionEligibility).toHaveBeenCalledWith(['637af0c0fb9bbc1e308cff02'], {
+ requestorId: 'aaaaaaaaaaaaaaaaaaaaaaaa',
+ role: 'Administrator',
+ });
+ });
+
+ await waitFor(() => {
+ expect(toast.success).toHaveBeenCalled();
+ });
+
+ expect(screen.getByText('Akshay Jayaram')).toBeInTheDocument();
+ expect(screen.queryByText('Ghazi1212')).not.toBeInTheDocument();
+ expect(onClose).not.toHaveBeenCalled();
+ expect(getPromotionPending).toHaveBeenCalledTimes(2);
+ });
+
+ it('closes when every remaining reviewer is promoted', async () => {
+ const { onClose } = renderDisplayBox();
+
+ await screen.findByText('Akshay Jayaram');
+ getPromotionPending.mockResolvedValueOnce([]);
+
+ await userEvent.click(screen.getByRole('button', { name: 'Confirm' }));
+
+ await waitFor(() => {
+ expect(postPromotionEligibility).toHaveBeenCalledWith(
+ ['637af0c0fb9bbc1e308cff01', '637af0c0fb9bbc1e308cff02'],
+ {
+ requestorId: 'aaaaaaaaaaaaaaaaaaaaaaaa',
+ role: 'Administrator',
+ },
+ );
+ });
+
+ await waitFor(() => {
+ expect(onClose).toHaveBeenCalledTimes(1);
+ });
+
+ expect(toast.success).toHaveBeenCalled();
+ });
+
+ it('shows an error toast when loading pending promotions fails', async () => {
+ getPromotionPending.mockRejectedValueOnce(new Error('network'));
+
+ renderDisplayBox();
+
+ await waitFor(() => {
+ expect(toast.error).toHaveBeenCalledWith('Failed to load pending promotions.');
+ });
+
+ expect(screen.getByText('No PR reviewers left to promote.')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Confirm' })).toBeDisabled();
+ });
+});
diff --git a/src/components/PermissionsManagement/NewRolePopUp.jsx b/src/components/PermissionsManagement/NewRolePopUp.jsx
index c2c05b2d757..686ffc94b1c 100644
--- a/src/components/PermissionsManagement/NewRolePopUp.jsx
+++ b/src/components/PermissionsManagement/NewRolePopUp.jsx
@@ -5,6 +5,7 @@ import { connect, useDispatch } from 'react-redux';
import { boxStyle, boxStyleDark } from '~/styles';
import { getAllRoles } from '../../actions/role';
import PermissionList from './PermissionList';
+import appStyles from '~/App.module.css';
function CreateNewRolePopup({ toggle, roleNames, darkMode, addRole }) {
const [permissionsChecked, setPermissionsChecked] = useState([]);
@@ -89,7 +90,7 @@ function CreateNewRolePopup({ toggle, roleNames, darkMode, addRole }) {
placeholder="Please enter a new role name"
value={newRoleName}
onChange={handleRoleName}
- className={darkMode ? 'bg-darkmode-liblack text-light border-0' : ''}
+ className={darkMode ? `${appStyles['bg-darkmode-liblack']} text-light border-0` : ''}
/>
{isValidRole === false || isNotDuplicateRole === false ? (
diff --git a/src/components/PermissionsManagement/PermissionListItem.jsx b/src/components/PermissionsManagement/PermissionListItem.jsx
index 29a8bc2636d..a03bce1b032 100644
--- a/src/components/PermissionsManagement/PermissionListItem.jsx
+++ b/src/components/PermissionsManagement/PermissionListItem.jsx
@@ -7,6 +7,7 @@ import { boxStyle, boxStyleDark } from '~/styles';
import { ModalContext } from '~/context/ModalContext';
import PermissionList from './PermissionList';
import hasPermission from '../../utils/permissions';
+import appStyles from '~/App.module.css';
import styles from './UserRoleTab.module.css';
import { permissions } from '../../utils/constants';
@@ -294,11 +295,16 @@ function PermissionListItem(props) {
id="#modal2-body_new-role--padding"
className={darkMode ? 'text-light dark-mode' : ''}
>
-
+
Permission Info
- {modalContent}
-
+
+ {modalContent}
+
+
{' '}
Ok{' '}
diff --git a/src/components/PermissionsManagement/PermissionsManagement.jsx b/src/components/PermissionsManagement/PermissionsManagement.jsx
index 681830249fe..13bbe56c6e1 100644
--- a/src/components/PermissionsManagement/PermissionsManagement.jsx
+++ b/src/components/PermissionsManagement/PermissionsManagement.jsx
@@ -2,6 +2,7 @@ import { useEffect, useState, useContext } from 'react';
import axios from 'axios';
import { Button, Modal, ModalBody, ModalHeader, Spinner } from 'reactstrap';
+import appStyles from '~/App.module.css';
import styles from './PermissionsManagement.module.css';
import { connect, useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
@@ -114,7 +115,7 @@ function PermissionsManagement({
return (
Create New Role
Manage User Permissions
= 300px) {
- .ContainerReminder {
- position: absolute;
- width: 400px;
- top: 100%;
- right: 50%;
- }
-}
-
-@media only screen and (width >= 1200px) {
- .ContainerReminder {
- position: fixed;
- width: 200px;
- top: 35%;
- right: 11%;
- }
-
- .ReminderTitle {
- display: flex;
- justify-content: center;
- flex: 10;
- padding-left: 10px;
- }
-
- .ReminderButton button:first-child {
- height: 100%;
- width: 140px;
- background-color: #007bff;
- color: #fff;
- border-radius: 5px;
- cursor: pointer;
- font-weight: 500;
- }
-}
-
-@media only screen and (width >= 1400px) {
- .ContainerReminder {
- width: 300px;
- right: 13%;
- }
-
- .ReminderTitle {
- display: flex;
- justify-content: center;
- flex: 1;
- padding-left: 20px;
- }
-}
-
-@media only screen and (width >= 1600px) {
- .ContainerReminder {
- width: 400px;
- right: 15%;
- }
-
- .ReminderTitle {
- display: flex;
- justify-content: center;
- flex: 1;
- padding-left: 1px;
- }
-}
-
-@media only screen and (width >= 1900px) {
- .ContainerReminder {
- right: 20%;
- }
-}
-
-@media (width <= 600px) {
- .ContainerReminder {
- width: 95%;
- max-width: none;
- padding: 15px;
- margin: 20px auto;
- position: relative;
- }
-}
+/* stylelint-disable declaration-property-value-keyword-no-deprecated, declaration-property-value-no-unknown */
+.permissions-management {
+ padding: 0.5rem 1rem;
+ width: 40%;
+ min-width: 350px;
+ margin: 1.5rem auto;
+ box-shadow: 1px 1px 8px rgb(158 158 158);
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ border-radius: 8px;
+}
+
+.dark-box-shadow {
+ box-shadow: 1px 1px 8px black;
+}
+
+.permissions-management__header {
+ display: flex;
+ align-items: center;
+ gap: 30px;
+}
+
+.permissions-management__title {
+ font-size: 1.8rem;
+}
+
+.createRole__alert {
+ margin-top: 0.4rem;
+}
+
+#modal-content__new-role {
+ position: relative;
+ width: 100%;
+ max-width: 700px;
+}
+
+#modal-body_new-role--padding {
+ padding: 1.5rem 3rem;
+}
+
+.dropdown__user-perms {
+ max-height: 420px;
+ width: '100%';
+ overflow-y: auto;
+}
+
+.user__auto-complete {
+ cursor: pointer;
+ padding: 5px;
+}
+
+.user__auto-complete:hover {
+ background: #1d62f0;
+}
+
+.user-permissions-pop-up__title {
+ padding-top: 0.5rem;
+ font-size: 1.2rem;
+}
+
+.name-container__btn_columns {
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+ align-items: flex-start;
+}
+
+.name-container__btns {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.btn-with-info {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.btn-with-info .info-icon {
+ cursor: pointer;
+ line-height: 1;
+ font-size: 18px;
+ margin: 0;
+}
+
+.user-role-tab__permission {
+ border-bottom: solid 1px #ced4da;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-end;
+ padding-bottom: 5px;
+ padding-top: 5px;
+ overflow-wrap: break-word;
+ max-width: 100%;
+}
+
+.permission-label {
+ overflow-wrap: break-word;
+ white-space: normal;
+ max-width: calc(100% - 60px);
+ margin-right: 10px;
+}
+
+#info-icon__permissions {
+ cursor: pointer;
+ align-self: flex-end;
+}
+
+.permissions-management--flex {
+ display: flex;
+ justify-content: space-between;
+ flex-direction: row-reverse;
+}
+
+.buttons-container {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.role-name-container {
+ display: flex;
+ flex-direction: column;
+ border: solid 1px lightgrey;
+ border-radius: 8px;
+ min-width: 300px;
+ height: 450px;
+ max-height: 480px;
+ overflow: auto;
+}
+
+.role-name-container::-webkit-scrollbar {
+ width: 5px;
+}
+
+.role-name-container::-webkit-scrollbar-track {
+ background: rgb(0 0 0 / 0%);
+}
+
+.role-name-container::-webkit-scrollbar-thumb {
+ background: rgb(206 212 218);
+ border-radius: 8px;
+}
+
+.role-name {
+ margin: 0;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ font-size: 1.2rem;
+ font-weight: 500;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+}
+
+.role-btn {
+ border: none !important;
+ outline: none !important;
+ box-shadow: none !important;
+ background: transparent;
+ text-align: center;
+ flex-grow: 1;
+}
+
+.role-btn:focus,
+.role-btn:active,
+.role-btn:focus-visible {
+ outline: none !important;
+ box-shadow: none !important;
+}
+
+.infos {
+ margin-left: auto;
+ text-align: center;
+ align-items: center;
+ padding-bottom: 8px;
+}
+
+.role-name:hover {
+ background-color: lightgrey;
+}
+
+.role-name-dark:hover {
+ background-color: #2f4157;
+}
+
+.loading-message-div {
+ width: 100%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+}
+
+.loading-message {
+ font-size: 30px;
+}
+
+.ContainerReminder {
+ width: 90%;
+ max-width: 400px;
+ padding: 20px;
+ margin: 0 auto;
+ position: fixed;
+ top: 30%;
+ right: 10%;
+ transform: translate(50%, -50%);
+ background-color: white;
+ z-index: 1000;
+ border: 1px solid #ccc;
+ border-radius: 10px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ box-shadow: 0 2px 10px black;
+}
+
+.ReminderTitle {
+ display: flex;
+ justify-content: center;
+ flex: 1;
+}
+
+.ReminderTitle h2 {
+ color: black;
+ font-size: 22px;
+ align-items: center;
+ display: flex;
+}
+
+.ReminderBody {
+ padding: 10px 0;
+ border-bottom: 1px solid #ccc;
+ border-top: 1px solid #ccc;
+ text-align: center;
+ margin-bottom: 10px;
+ overflow-wrap: break-word;
+}
+
+.ReminderButton {
+ display: flex;
+ gap: 10px;
+ justify-content: center;
+ width: 100%;
+ padding-top: 10px;
+}
+
+.ReminderButton button:first-child {
+ height: 80%;
+ width: 140px;
+ background-color: #007bff;
+ color: #fff;
+ border-radius: 5px;
+ cursor: pointer;
+ font-weight: 500;
+}
+
+.ReminderButton button:nth-child(2) {
+ background-color: red;
+ color: #fff;
+ height: 80%;
+ width: 70px;
+ margin-right: 25px;
+ border-radius: 5px;
+ cursor: pointer;
+ font-weight: 500;
+}
+
+.ContainerReminderDark {
+ background-color: #1c2541;
+ border-color: #3a506b;
+ color: #fff;
+}
+
+.ContainerReminderDark .ReminderTitle h2 {
+ color: #fff;
+}
+
+.ContainerReminderDark .ReminderBody {
+ border-color: #3a506b;
+}
+
+@media only screen and (width <= 1408px) {
+ .permissions-management__header {
+ flex-direction: column;
+ }
+}
+
+@media only screen and (width <= 1024px) {
+ .navbar {
+ flex-wrap: wrap;
+ }
+}
+
+@media only screen and (width >= 300px) {
+ .ContainerReminder {
+ position: absolute;
+ width: 400px;
+ top: 100%;
+ right: 50%;
+ }
+}
+
+@media only screen and (width >= 1200px) {
+ .ContainerReminder {
+ position: fixed;
+ width: 200px;
+ top: 35%;
+ right: 11%;
+ }
+
+ .ReminderTitle {
+ display: flex;
+ justify-content: center;
+ flex: 10;
+ padding-left: 10px;
+ }
+
+ .ReminderButton button:first-child {
+ height: 100%;
+ width: 140px;
+ background-color: #007bff;
+ color: #fff;
+ border-radius: 5px;
+ cursor: pointer;
+ font-weight: 500;
+ }
+}
+
+@media only screen and (width >= 1400px) {
+ .ContainerReminder {
+ width: 300px;
+ right: 13%;
+ }
+
+ .ReminderTitle {
+ display: flex;
+ justify-content: center;
+ flex: 1;
+ padding-left: 20px;
+ }
+}
+
+@media only screen and (width >= 1600px) {
+ .ContainerReminder {
+ width: 400px;
+ right: 15%;
+ }
+
+ .ReminderTitle {
+ display: flex;
+ justify-content: center;
+ flex: 1;
+ padding-left: 1px;
+ }
+}
+
+@media only screen and (width >= 1900px) {
+ .ContainerReminder {
+ right: 20%;
+ }
+}
+
+@media (width <= 600px) {
+ .ContainerReminder {
+ width: 95%;
+ max-width: none;
+ padding: 15px;
+ margin: 20px auto;
+ position: relative;
+ }
+}
diff --git a/src/components/PermissionsManagement/ReminderModal.jsx b/src/components/PermissionsManagement/ReminderModal.jsx
index 67ec96804c2..696f391d941 100644
--- a/src/components/PermissionsManagement/ReminderModal.jsx
+++ b/src/components/PermissionsManagement/ReminderModal.jsx
@@ -13,11 +13,15 @@ function ReminderModal({
const { modalStatus, updateModalStatus } = useContext(ModalContext);
return (
-
+
Remember to Save Your Changes!
-
+
Please log out and log back in to your account, {changedAccount} to apply the changes.
diff --git a/src/components/PermissionsManagement/UserPermissionsPopUp.jsx b/src/components/PermissionsManagement/UserPermissionsPopUp.jsx
index dff7f6758a4..22838f37a74 100644
--- a/src/components/PermissionsManagement/UserPermissionsPopUp.jsx
+++ b/src/components/PermissionsManagement/UserPermissionsPopUp.jsx
@@ -19,6 +19,7 @@ import PermissionList from './PermissionList';
import { addNewRole, getAllRoles } from '../../actions/role';
import CircularProgress from '@mui/material/CircularProgress';
import ReminderModal from './ReminderModal';
+import appStyles from '~/App.module.css';
function UserPermissionsPopUp({
allUserProfiles,
@@ -221,7 +222,7 @@ function UserPermissionsPopUp({
setIsOpen(true);
}}
placeholder="Shows only ACTIVE users"
- className={darkMode ? styles['bg-darkmode-liblack text-light border-0'] : ''}
+ className={darkMode ? `${appStyles['bg-darkmode-liblack']} text-light border-0` : ''}
autoComplete="off"
name="user-search"
/>
@@ -231,7 +232,7 @@ function UserPermissionsPopUp({
role="menu"
aria-hidden="false"
className={`dropdown-menu${isOpen ? ` show ${styles['dropdown__user-perms']}` : ''} ${
- darkMode ? styles['bg-darkmode-liblack text-light'] : ''
+ darkMode ? `${appStyles['bg-darkmode-liblack']} text-light` : ''
}`}
style={{ marginTop: '0px', width: '100%' }}
>
diff --git a/src/components/PermissionsManagement/__tests__/PermissionsManagement.test.jsx b/src/components/PermissionsManagement/__tests__/PermissionsManagement.test.jsx
index a88e1fade4d..0b5f2bd386c 100644
--- a/src/components/PermissionsManagement/__tests__/PermissionsManagement.test.jsx
+++ b/src/components/PermissionsManagement/__tests__/PermissionsManagement.test.jsx
@@ -13,6 +13,7 @@ import { ModalContext } from '~/context/ModalContext';
import PermissionsManagement from '../PermissionsManagement';
import { ENDPOINTS } from '~/utils/URL';
import { permissions } from '~/utils/constants';
+import appStyles from '~/App.module.css';
vi.mock('axios');
const mockStore = configureMockStore([thunk]);
@@ -175,7 +176,7 @@ describe('PermissionsManagement', () => {
it('applies dark-mode styles when darkMode is true', async () => {
const { container } = await renderComponent({ darkMode: true });
- expect(container.firstChild).toHaveClass('bg-oxford-blue');
+ expect(container.firstChild).toHaveClass(appStyles['bg-oxford-blue']);
expect(container.firstChild).toHaveClass('text-light');
});
diff --git a/src/components/Projects/Project/Project.jsx b/src/components/Projects/Project/Project.jsx
index 067bd7f9ced..afabbd1d29e 100644
--- a/src/components/Projects/Project/Project.jsx
+++ b/src/components/Projects/Project/Project.jsx
@@ -190,7 +190,12 @@ const Project = props => {
placement="top"
overlay={
Active members }
>
-
+ {/*
+ Applied the dedicated modular CSS class 'badgeTextBlack' to permanently
+ lock the active member count text color to black, ensuring high contrast
+ and satisfying requirement 1.3 across both light and dark modes.
+ */}
+
{props.activeMemberCounts}
diff --git a/src/components/Projects/ProjectTableHeader/ProjectTableHeader.jsx b/src/components/Projects/ProjectTableHeader/ProjectTableHeader.jsx
index 0e82cc6768f..cdc0059cc5e 100644
--- a/src/components/Projects/ProjectTableHeader/ProjectTableHeader.jsx
+++ b/src/components/Projects/ProjectTableHeader/ProjectTableHeader.jsx
@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
-import './../projects.module.css';
+import styles from '../projects.module.css';
import {
PROJECT_NAME,
ACTIVE,
@@ -14,16 +14,14 @@ import hasPermission from '~/utils/permissions';
import { connect } from 'react-redux';
import EditableInfoModal from '~/components/UserProfile/EditableModal/EditableInfoModal';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faArrowUp, faArrowDown, faSortDown, faChevronDown } from '@fortawesome/free-solid-svg-icons';
-import { Dropdown, DropdownButton } from 'react-bootstrap';
-
-import { permissions } from '../../../utils/constants';
+import { faArrowUp, faArrowDown, faSortDown } from '@fortawesome/free-solid-svg-icons';
+import { Dropdown } from 'react-bootstrap';
+import { boxStyle, boxStyleDark } from '~/styles';
import { Button } from 'reactstrap';
-
const ProjectTableHeader = props => {
const { role, darkMode } = props;
- const canDeleteProject = hasPermission(permissions.deleteProject)
+ const canDeleteProject = hasPermission('deleteProject');
const categoryList = ['Unspecified', 'Food', 'Energy', 'Housing', 'Education', 'Society', 'Economics', 'Stewardship', 'Other'];
const statusList = ['Active', 'Inactive'];
@@ -35,25 +33,48 @@ const ProjectTableHeader = props => {
return faSortDown;
};
- // One muted, theme-aware style for every sort/filter control in the header so
- // they read as a matched set instead of a row of clashing coloured chips.
- // Outline while idle, filled once that column's sort/filter is active.
- const baseColor = darkMode ? 'light' : 'secondary';
- const filterVariant = active => (active ? baseColor : `outline-${baseColor}`);
+ // Sort and filter controls share one style so size (30x30px) and color match.
+ const controlStyle = {
+ ...(darkMode ? boxStyleDark : boxStyle),
+ width: '30px',
+ height: '30px',
+ border: 'none',
+ };
+ const controlClass =
+ 'btn btn-secondary btn-sm px-0 py-0 d-flex align-items-center justify-content-center';
+
+ const renderSortButton = column => (
+ props.handleSort(column)}
+ >
+
+
+ );
- const renderSortButton = column => {
- const active = props.sorted.column === column && props.sorted.direction !== 'DEFAULT';
+ const renderDropdownFilterButton = (id, selectedValue, onChange, list, placeholder) => {
+ const itemClass = darkMode ? 'bg-darkmode-liblack text-light border-0' : '';
return (
- props.handleSort(column)}
- >
-
-
+
+
+
+
+
+
+ {selectedValue ? 'Clear filter' : placeholder}
+
+
+ {list.map(item => (
+
+ {item}
+
+ ))}
+
+
);
};
@@ -62,87 +83,68 @@ const ProjectTableHeader = props => {
#
- {/*
{PROJECT_NAME} */}
-
-
- {PROJECT_NAME}
-
- {renderSortButton('PROJECTS')}
-
+
+ {/* 1. Project Name column header */}
+
+
+ {PROJECT_NAME}
+ {renderSortButton('PROJECTS')}
+
+ {/* 2. Category column header */}
- {/* This span holds the header-name and a filter dropdown */}
-
- {PROJECT_CATEGORY}
- }
- size="sm"
- variant={filterVariant(Boolean(props.selectedValue))}
- className="ml-2"
- value={props.selectedValue}
- onSelect={props.onChange}
- menuAlign="right"
- >
- {props.selectedValue ? 'Clear filter' : 'Choose category'}
-
- {categoryList.map((category, index) => (
- {category}
- ))}
-
+
+ {PROJECT_CATEGORY}
+ {renderDropdownFilterButton('project-category-filter', props.selectedValue, props.onChange, categoryList, 'Choose category')}
-
-
- {ACTIVE}
- }
- size="sm"
- variant={filterVariant(Boolean(props.showStatus))}
- className="ml-2 align-middle"
- value={props.showStatus}
- onSelect={props.selectStatus}
- menuAlign="right"
- >
- {props.showStatus ? 'Clear filter' : 'Choose Status'}
- {statusList.map((status, index) => (
- {status}
- ))}
-
+
+ {/* 3. Active status column header */}
+
+
+ {ACTIVE}
+ {renderDropdownFilterButton('project-status-filter', props.showStatus, props.selectStatus, statusList, 'Choose Status')}
-
-
- {INVENTORY}
-
- {renderSortButton('INVENTORY')}
-
+
+ {/* 4. Inventory column header */}
+
+
+ {INVENTORY}
+ {renderSortButton('INVENTORY')}
-
-
- {MEMBERS}
- {renderSortButton('MEMBERS')}
+
+ {/* 5. Members column header */}
+
+
+ {MEMBERS}
+
+ {renderSortButton('MEMBERS')}
+
-
-
-
{WBS}
+
+ {/* 6. WBS column header */}
+
+
+ {WBS}
+
+ {/* 7. Archive column header */}
{canDeleteProject ? (
-
+
{ARCHIVE}
) : null}
@@ -165,8 +167,7 @@ ProjectTableHeader.propTypes = {
};
const mapStateToProps = state => ({
- role: state.userProfile.role, // Map 'role' from Redux state to 'role' prop
+ role: state.userProfile.role,
});
-export default connect(mapStateToProps)(ProjectTableHeader)
-
+export default connect(mapStateToProps)(ProjectTableHeader);
\ No newline at end of file
diff --git a/src/components/Projects/ProjectTableHeader/__tests__/ProjectTableHeader.test.jsx b/src/components/Projects/ProjectTableHeader/__tests__/ProjectTableHeader.test.jsx
index 5639dc0559e..91402b54885 100644
--- a/src/components/Projects/ProjectTableHeader/__tests__/ProjectTableHeader.test.jsx
+++ b/src/components/Projects/ProjectTableHeader/__tests__/ProjectTableHeader.test.jsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { render } from '@testing-library/react'
+import { render, screen } from '@testing-library/react'
import { Provider } from 'react-redux'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
@@ -87,9 +87,9 @@ describe('ProjectTableHeader Component', () => {
};
const hasPermission = vi.fn((a) => true)
stateWithDeletePermission.hasPermission = hasPermission;
- const { getByText } = renderProjectTableHeader(stateWithDeletePermission);
- // eslint-disable-next-line testing-library/prefer-screen-queries
- expect(getByText('Archive')).toBeInTheDocument();
+ renderProjectTableHeader(stateWithDeletePermission);
+
+ expect(screen.getByText('Archive')).toBeInTheDocument();
});
// Test case to check if the delete column is not shown for users without delete permission
@@ -103,9 +103,9 @@ describe('ProjectTableHeader Component', () => {
};
const hasPermission = vi.fn((a) => false)
stateWithoutDeletePermission.hasPermission = hasPermission;
- const { queryByText } = renderProjectTableHeader(stateWithoutDeletePermission);
- // eslint-disable-next-line testing-library/prefer-screen-queries
- expect(queryByText('Delete')).not.toBeInTheDocument();
+ renderProjectTableHeader(stateWithoutDeletePermission);
+
+ expect(screen.queryByText('Delete')).not.toBeInTheDocument();
});
-});
+});
\ No newline at end of file
diff --git a/src/components/Projects/Projects.jsx b/src/components/Projects/Projects.jsx
index 72038297225..0bb440b225a 100644
--- a/src/components/Projects/Projects.jsx
+++ b/src/components/Projects/Projects.jsx
@@ -240,14 +240,23 @@ const Projects = function(props) {
}
}
- if (column === "MEMBERS") {
+ if (column === "MEMBERS") {
+ // Helper function to safely retrieve active member count, supporting various ID types (string, number, or object)
+ const getCount = (projectId) => {
+ if (!projectId) return 0;
+ return activeMemberCounts[projectId] ||
+ activeMemberCounts[String(projectId)] ||
+ activeMemberCounts[projectId.toString()] || 0;
+ };
+
+ const countA = getCount(a._id);
+ const countB = getCount(b._id);
+
if (direction === "ASC") {
- const countA = activeMemberCounts[a._id] || 0;
- const countB = activeMemberCounts[b._id] || 0;
+ // Sort in ascending order based on active member counts
return countA - countB;
} else if (direction === "DESC") {
- const countA = activeMemberCounts[a._id] || 0;
- const countB = activeMemberCounts[b._id] || 0;
+ // Sort in descending order based on active member counts
return countB - countA;
} else {
return 0; // Default: keep same order as PROJECT sorting
diff --git a/src/components/Projects/WBS/SingleTask/SingleTask.jsx b/src/components/Projects/WBS/SingleTask/SingleTask.jsx
index 18ac28de493..56401817b46 100644
--- a/src/components/Projects/WBS/SingleTask/SingleTask.jsx
+++ b/src/components/Projects/WBS/SingleTask/SingleTask.jsx
@@ -93,7 +93,7 @@ function SingleTask(props) {
)}
-
+
diff --git a/src/components/Projects/WBS/SingleTask/SingleTask.module.css b/src/components/Projects/WBS/SingleTask/SingleTask.module.css
index 89abb2e7ee6..efd741139a8 100644
--- a/src/components/Projects/WBS/SingleTask/SingleTask.module.css
+++ b/src/components/Projects/WBS/SingleTask/SingleTask.module.css
@@ -12,4 +12,10 @@
display: inline-flex;
align-items: center;
justify-content: center;
+}
+.singleTaskTableDark tbody tr:hover,
+.singleTaskTableDark tbody tr:hover > th,
+.singleTaskTableDark tbody tr:hover > td {
+ background-color: #111831 !important;
+ color: #fff !important;
}
\ No newline at end of file
diff --git a/src/components/Projects/WBS/WBSDetail/components/TagsSearch.jsx b/src/components/Projects/WBS/WBSDetail/components/TagsSearch.jsx
index 20054384683..a6a993e78e2 100644
--- a/src/components/Projects/WBS/WBSDetail/components/TagsSearch.jsx
+++ b/src/components/Projects/WBS/WBSDetail/components/TagsSearch.jsx
@@ -1,6 +1,7 @@
import React, { useState, useMemo } from 'react';
import { connect } from 'react-redux';
import TagSent from './TagSent';
+import appStyles from '~/App.module.css';
import styles from './TagsSearch.module.css';
import ReadOnlySectionWrapper from '../EditTask/ReadOnlySectionWrapper';
import { findProjectMembers } from '../../../../../actions/projectMembers';
@@ -110,7 +111,7 @@ function TagsSearch(props) {
type="text"
placeholder={placeholder}
className={`border border-dark rounded form-control px-2 ${
- darkMode ? 'bg-darkmode-liblack text-light border-0' : ''
+ darkMode ? `${appStyles['bg-darkmode-liblack']} text-light border-0` : ''
}`}
value={searchWord}
onChange={e => handleFilter(e)}
@@ -159,6 +160,7 @@ function TagsSearch(props) {
const mapStateToProps = state => ({
// ✅ do NOT overwrite `members` prop anymore
membersFromStore: state.projectMembers.members,
+ darkMode: state.theme.darkMode,
foundProjectMembers: state.projectMembers.foundProjectMembers,
});
diff --git a/src/components/Projects/WBS/WBSDetail/components/TagsSearch.module.css b/src/components/Projects/WBS/WBSDetail/components/TagsSearch.module.css
index 09315534689..7067cf0e3ec 100644
--- a/src/components/Projects/WBS/WBSDetail/components/TagsSearch.module.css
+++ b/src/components/Projects/WBS/WBSDetail/components/TagsSearch.module.css
@@ -15,8 +15,9 @@
}
-:global(body.dark-mode:not(.no-global-theme)) .darkDropdown {
+.darkDropdown {
background-color: #1e293b;
+ border-color: #334155;
}
.darkDropdownItem {
diff --git a/src/components/Projects/WBS/WBSDetail/components/__tests__/TagsSearch.test.jsx b/src/components/Projects/WBS/WBSDetail/components/__tests__/TagsSearch.test.jsx
index 9cfe6c6a05a..fbae47fb155 100644
--- a/src/components/Projects/WBS/WBSDetail/components/__tests__/TagsSearch.test.jsx
+++ b/src/components/Projects/WBS/WBSDetail/components/__tests__/TagsSearch.test.jsx
@@ -10,10 +10,16 @@ import { findProjectMembers } from '~/actions/projectMembers';
// Mock member data
const allMembers = [
- { firstName: 'aaa', isActive: true, lastName: 'volunteer', _id: 'aaa123' },
- { firstName: 'bbb', isActive: true, lastName: 'test', _id: 'bbb456' },
- { firstName: 'ccc', isActive: false, lastName: 'manager', _id: 'ccc789' },
- { firstName: 'aaa', isActive: true, lastName: 'owner', _id: 'aaa067' },
+ {
+ firstName: 'aaa',
+ isActive: true,
+ lastName: 'volunteer',
+ _id: 'aaa123',
+ profilePic: 'pic1.jpg',
+ },
+ { firstName: 'bbb', isActive: true, lastName: 'test', _id: 'bbb456', profilePic: 'pic2.jpg' },
+ { firstName: 'ccc', isActive: false, lastName: 'manager', _id: 'ccc789', profilePic: 'pic3.jpg' },
+ { firstName: 'aaa', isActive: true, lastName: 'owner', _id: 'aaa067', profilePic: 'pic4.jpg' },
];
const middlewares = [thunk];
@@ -42,6 +48,7 @@ const mockFoundProjectMembers = searchQuery => {
const renderTagsSearchComponent = props => {
const store = mockStore({
projectMembers: { foundProjectMembers: mockFoundProjectMembers('') }, // Initial empty search
+ theme: { darkMode: false },
});
return render(
diff --git a/src/components/Projects/WBS/WBSDetail/wbs.module.css b/src/components/Projects/WBS/WBSDetail/wbs.module.css
index caac3b3bdb0..607b1058e5b 100644
--- a/src/components/Projects/WBS/WBSDetail/wbs.module.css
+++ b/src/components/Projects/WBS/WBSDetail/wbs.module.css
@@ -406,8 +406,9 @@
justify-content: center;
}
-.editTaskTableDark tr:hover {
- background: #111831;
+.editTaskTableDark tbody tr:hover > td {
+ background-color: #111831 !important;
+ color: #fff !important;
}
.editTaskTextareaDark {
diff --git a/src/components/Projects/projects.module.css b/src/components/Projects/projects.module.css
index 2a3ba4428e2..117d94ac999 100644
--- a/src/components/Projects/projects.module.css
+++ b/src/components/Projects/projects.module.css
@@ -14,31 +14,31 @@
whatever is left (~25%). Keeping them as percentages lets the table shrink to
its wrapper instead of overflowing it. */
:global(#projects__order) {
- width: 5%;
+ width: 4%;
}
:global(#projects__active) {
- width: 8%;
+ width: 12%;
}
:global(#projects__category) {
- width: 15%;
+ width: 14%;
}
:global(#projects__members) {
- width: 12%;
+ width: 13%;
}
:global(#projects__inv) {
- width: 12%;
+ width: 16%;
}
:global(#projects__wbs) {
- width: 12%;
+ width: 10%;
}
:global(#projects__delete) {
- width: 11%;
+ width: 10%;
}
/* Fixed layout ties column widths to the header row, so long values in a cell
@@ -200,12 +200,15 @@
.project-member-badge {
position: absolute;
top: -10px;
- right: -32px;
- min-width: 22px;
- height: 22px;
+ right: -48px;
+ /* Replaced fixed dimensions with padding so the badge expands gracefully for multi-digit numbers */
+ padding: 2px 5px;
+ /* min-width: 22px; */
+ min-height: 22px;
background: lawngreen;
- color: #111;
- border-radius: 50%;
+ color: #111 !important;
+ border-radius: 50px; /* Adapts from a circle to a clean pill shape as text length increases */
+
display: flex;
align-items: center;
justify-content: center;
@@ -215,6 +218,12 @@
z-index: 2;
}
+/* Increased specificity to ensure badge text remains strictly black in both light and dark modes */
+ .project-member-badge.badgeTextBlack {
+ color: #111 !important;
+}
+
+
.project-member-btn .fa-users {
font-size: 1.5rem;
line-height: 1;
@@ -232,12 +241,12 @@
.suggestion-button {
display: block;
- width: 100%;
- padding: 10px;
- text-align: left;
- background: none;
- border: none;
- cursor: pointer;
+ width: 100%;
+ padding: 10px;
+ text-align: left;
+ background: none;
+ border: none;
+ cursor: pointer;
}
.suggestion-button:hover {
@@ -271,6 +280,24 @@
background-color: #0056b3;
}
+
+/* Header filter toggles only (Category / Active): fixed 30x30 box, no Bootstrap caret.
+ Scoped to .projectFilterDropdown so other dropdowns (account, nav menus) keep their size and arrow. */
+.projectFilterDropdown :global(.dropdown-toggle) {
+ width: 30px !important;
+ height: 30px !important;
+ padding: 0 !important;
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+}
+
+.projectFilterDropdown :global(.dropdown-toggle)::after {
+ display: none !important;
+ content: none !important;
+ border: none !important;
+}
+
@media (width <= 375px) {
#new_project .form-control {
flex: 1 1 auto;
diff --git a/src/components/Reports/PeopleReport/CompletedTasksPieChart.jsx b/src/components/Reports/PeopleReport/CompletedTasksPieChart.jsx
index 5c2e4e746de..ccb1b907c28 100644
--- a/src/components/Reports/PeopleReport/CompletedTasksPieChart.jsx
+++ b/src/components/Reports/PeopleReport/CompletedTasksPieChart.jsx
@@ -1,15 +1,17 @@
-import React, { useEffect, useMemo, useRef, useState } from 'react';
+import React, { useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useTable } from 'react-table';
import { Cell, Label, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts';
-import { FiFolder } from 'react-icons/fi';
+import { FiChevronDown, FiChevronUp, FiFolder } from 'react-icons/fi';
import { CHART_RADIUS, CHART_SIZE } from '../../common/PieChart/constants';
import { generateArrayOfUniqColors } from '../../common/PieChart/colorsGenerator';
import { peopleTasksPieChartViewData } from './selectors';
import styles from './CompletedTasksPieChart.module.css';
-// Reserve space for the "Show more" footer so it doesn't push the last visible row offscreen.
-const FOOTER_RESERVED_ROWS = 1;
+// Hard cap on the rows rendered in the collapsed legend. Fixed rather than derived
+// from the viewport so the card is the same height on every screen — these reports
+// get screenshotted into work confirmation letters and the bottom edge has to line up.
+export const MAX_VISIBLE_TASKS = 20;
export function ColorSwatchCell({ value, column }) {
return (
@@ -80,57 +82,13 @@ function CompletedTasksPieChart({ darkMode }) {
const pieChartId = 'completedTasksPieChart';
- // How many rows we can fit in the available height. When `expanded` is true we
- // render every row regardless of this number. The renderer slices in half: the
- // measurement effect owns the cap, the JSX owns the slice.
- const tbodyRef = useRef(null);
- const [visibleCount, setVisibleCount] = useState(tasks.length);
const [expanded, setExpanded] = useState(false);
- // Measure how many rows fit in the clamped tbody height. Uses the height of the
- // first measured row — every row in the table has the same layout, so a single
- // sample is enough. `expanded` short-circuits the measurement so all rows render.
- useEffect(() => {
- if (expanded) return undefined;
-
- const tbody = tbodyRef.current;
- if (!tbody) return undefined;
-
- const recompute = () => {
- // Budget comes from CSS (max-height on .legend-scroll-area tbody), NOT from
- // the tbody's rendered height — that would feed back on itself because the
- // tbody's height is a function of how many rows we render into it.
- const tbodyStyles = window.getComputedStyle(tbody);
- const maxHeightPx = Number.parseFloat(tbodyStyles.maxHeight);
- if (!Number.isFinite(maxHeightPx) || maxHeightPx <= 0) {
- setVisibleCount(tasks.length);
- return;
- }
- const firstCell = tbody.querySelector('td');
- const rowHeight = firstCell ? firstCell.getBoundingClientRect().height : 0;
- if (!rowHeight) {
- setVisibleCount(tasks.length);
- return;
- }
- const fits = Math.max(
- 0,
- Math.floor(maxHeightPx / rowHeight) - FOOTER_RESERVED_ROWS,
- );
- setVisibleCount(Math.min(tasks.length, fits));
- };
-
- recompute();
- const ro = new ResizeObserver(recompute);
- ro.observe(tbody);
- window.addEventListener('resize', recompute);
- return () => {
- ro.disconnect();
- window.removeEventListener('resize', recompute);
- };
- }, [tasks.length, expanded]);
-
- const hiddenCount = expanded ? 0 : Math.max(0, tasks.length - visibleCount);
- const tasksView = expanded ? tasks : tasks.slice(0, visibleCount);
+ const hiddenCount = Math.max(0, tasks.length - MAX_VISIBLE_TASKS);
+ const tasksView = expanded ? tasks : tasks.slice(0, MAX_VISIBLE_TASKS);
+ const moreTasksLabel = `+ ${hiddenCount} more task${hiddenCount === 1 ? '' : 's'}`;
+ const toggleLabel = expanded ? 'Show less' : moreTasksLabel;
+ const ToggleIcon = expanded ? FiChevronUp : FiChevronDown;
const colorScale = useMemo(() => {
const domain = tasks.map(t => t.projectId);
@@ -236,7 +194,7 @@ function CompletedTasksPieChart({ darkMode }) {
-
+
{headerGroups.map(headerGroup => {
@@ -259,7 +217,7 @@ function CompletedTasksPieChart({ darkMode }) {
);
})}
-
+
{rows.map(row => {
prepareRow(row);
const { key, ...rowProps } = row.getRowProps();
@@ -287,21 +245,13 @@ function CompletedTasksPieChart({ darkMode }) {
setExpanded(true)}
- >
- + {hiddenCount} more task{hiddenCount === 1 ? '' : 's'}
-
-
- )}
- {expanded && (
-
- setExpanded(false)}
+ aria-expanded={expanded}
+ onClick={() => setExpanded(prev => !prev)}
>
- Show less
+ {toggleLabel}
+
)}
diff --git a/src/components/Reports/PeopleReport/CompletedTasksPieChart.module.css b/src/components/Reports/PeopleReport/CompletedTasksPieChart.module.css
index 202af563fd6..fc92f7778f6 100644
--- a/src/components/Reports/PeopleReport/CompletedTasksPieChart.module.css
+++ b/src/components/Reports/PeopleReport/CompletedTasksPieChart.module.css
@@ -15,7 +15,12 @@
border-radius: 12px;
background-color: #fff;
padding: 1rem;
- margin: 2px;
+
+ /* No margin: this is the last card in PeopleReport's left column, so any margin here
+ lifts its visible bottom edge above the column's bottom and it no longer lines up
+ with the profile card on the right. It also keeps the card flush with the metric
+ cards and pie chart above it. */
+ margin: 0;
display: flex;
flex-direction: column;
box-shadow: 0 0 12px #eae1f6;
@@ -71,10 +76,8 @@
.completedTasksTable .taskNameRow {
color: #888;
overflow: hidden;
- display: -webkit-box;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 2;
- line-clamp: 2;
+ white-space: nowrap; /* Prevent text from wrapping */
+ text-overflow: ellipsis; /* Display ellipsis (...) for overflowing text */
padding: 0;
margin: 8px;
}
@@ -88,10 +91,6 @@
background-color: transparent;
}
-.legend-scroll-area tbody {
- max-height: calc(70vh);
-}
-
.more-rows-footer {
display: flex;
justify-content: center;
@@ -99,6 +98,9 @@
}
.show-more-btn {
+ display: inline-flex;
+ align-items: center;
+ column-gap: 0.35rem;
border: 1px solid #b58cdb;
background: #f4ecff;
color: #5b2a86;
@@ -109,6 +111,10 @@
transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
+.show-more-icon {
+ flex-shrink: 0;
+}
+
.show-more-btn:hover {
background: #ead7ff;
border-color: #7b3fad;
@@ -131,11 +137,14 @@
border-radius: 50%;
}
+/* Pinned to the bottom of the legend column so the card's bottom edge lines up
+ with the pie chart whether the list is collapsed or expanded. */
.data-total-hours {
display: flex;
justify-content: center;
align-items: center;
column-gap: 0.5rem;
+ margin-top: auto;
}
@media(width >= 768px) {
@@ -168,12 +177,6 @@
}
}
-@media (width >= 2000px) {
- .legend-scroll-area tbody {
- max-height: 65vh;
- }
-}
-
:global(.dark-mode) {
.report-block {
background-color: #1a2639;
diff --git a/src/components/Reports/PeopleReport/PeopleReport.jsx b/src/components/Reports/PeopleReport/PeopleReport.jsx
index 1b9fc529617..16e6ac40163 100644
--- a/src/components/Reports/PeopleReport/PeopleReport.jsx
+++ b/src/components/Reports/PeopleReport/PeopleReport.jsx
@@ -18,8 +18,8 @@ import { getTimeEntriesForPeriod } from '../../../actions/timeEntries';
import { ReportPage } from '../sharedComponents/ReportPage';
import { getPeopleReportData } from './selectors';
import { PeopleTasksPieChart } from './components';
-import { Checkbox } from '../../common/Checkbox';
import { updateRehireableStatus } from '../../../actions/userManagement';
+import RehireableStatusControl from '../../UserProfile/RehireableStatusControl';
import InfringementsViz from '../InfringementsViz';
import TimeEntriesViz from '../TimeEntriesViz';
import BadgeSummaryViz from '../BadgeSummaryViz';
@@ -86,15 +86,17 @@ function PeopleReport(props) {
}, [userProfile?._id, userProfile?.isRehireable]);
const setRehireable = useCallback(
- async rehireValue => {
+ async (rehireValue, reason) => {
const previousValue = isRehireable;
setIsRehireableState(rehireValue);
try {
- await updateRehireableStatus(userProfile, rehireValue);
+ await updateRehireableStatus(userProfile, rehireValue, reason);
toast.success(`You have changed the rehireable status of this user to ${rehireValue}`);
+ return true;
} catch (err) {
setIsRehireableState(previousValue);
- alert('An error occurred while attempting to save the rehireable status of this user.');
+ toast.error('An error occurred while attempting to save the rehireable status of this user.');
+ return false;
}
},
[isRehireable, updateRehireableStatus, userProfile],
@@ -256,14 +258,13 @@ function PeopleReport(props) {
- setRehireable(event.target.checked)}
- label="Rehireable"
+
diff --git a/src/components/Reports/PeopleReport/PeopleReport.module.css b/src/components/Reports/PeopleReport/PeopleReport.module.css
index b8a0b0901d6..0775613ec21 100644
--- a/src/components/Reports/PeopleReport/PeopleReport.module.css
+++ b/src/components/Reports/PeopleReport/PeopleReport.module.css
@@ -292,6 +292,19 @@ body {
stroke: rgb(255 255 255 / 15%);
}
+/* When there are only 3 metrics (e.g., for deactivated users): keep each card's width identical to the 4-metric layout without stretching, center them to make the overall section narrower, and eliminate dead space */
+.metrics.threeMetrics {
+ justify-content: center;
+ gap: 1.rem;
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.metrics.threeMetrics > .metricCard {
+ flex: 0 1 calc((100% - 3rem) / 4);
+ min-width: 220px;
+}
+
@media (width <= 850px) {
.introDate {
margin: 16px 0;
@@ -334,7 +347,9 @@ body {
@media (width <= 1488px) {
.peopleReportTimeLogsWrapper > .peopleReportTimeLogBlock {
- flex: 0 1 calc((100% - 1rem) / 2);
+ /* flex: 0 1 calc((100% - 1rem) / 2); */
+ flex: 0 1 calc((100% - 2rem) / 3);
+ max-width: none;
}
}
@@ -351,6 +366,7 @@ body {
.peopleReportTimeLogsWrapper.threeBoxes > .peopleReportTimeLogBlock {
flex: 0 1 calc((100% - 2rem) / 3); /* 3 per row */
+ flex: 0 1 100%;
}
/* Prevent wrapping weirdly at mid-screen sizes for 3-box layout */
@@ -462,40 +478,50 @@ body {
width: 100%;
}
+/* Right-side stats card with flexbox layout for even vertical distribution */
.reportStats {
- flex-grow: 1;
+ flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
+ align-items: center;
background-color: white;
padding: 2rem;
border-radius: 16px;
box-shadow: 0 0 12px rgb(0 0 0 / 8%);
- height: 698px;
- margin-top: 2rem;
+ height: 100%;
+ margin-top: 0;
+ width: 100%;
+ gap: 12px; /* Uniform spacing between internal elements (name, role, title, dates) */
+}
+
+/* Ensure all direct children of reportStats participate in flex layout */
+.reportStats > * {
+ width: 100%;
+ flex-shrink: 0;
}
@media (width >= 1489px) and (width <= 1784px) {
.reportStats {
- height: 721px;
+ /* height removed - now 100% from parent */
}
}
@media (width >= 1156px) and (width <= 1488px) {
.reportStats {
- height: 869px;
+ /* height removed - now 100% from parent */
}
}
@media (width >= 1025px) and (width <= 1155px) {
.reportStats {
- height: 873px;
+ /* height removed - now 100% from parent */
}
}
@media (width >= 960px) and (width <= 1024px) {
.reportStats {
- height: 895px;
+ /* height removed - now 100% from parent */
}
}
@@ -648,8 +674,32 @@ body {
flex-direction: row-reverse;
}
+ /* .userProfile is already stretched to the row's height (align-items: stretch).
+ Making it a flex column lets the ReportBlock inside fill that height exactly,
+ instead of relying on percentage heights. */
.userProfile {
max-width: 20rem;
+ display: flex;
+ flex-direction: column;
+ }
+
+ /* The shared ReportBlock sets its inner content to height: 100% plus margin: 2px, so
+ the content overflows its wrapper by 4px and the visible card ends below the left
+ column. Here we swap the percentage heights for flex: 1 and drop the margin so the
+ card's bottom edge sits exactly on the row's bottom edge.
+ ReportBlock's class names are hashed by CSS Modules and can't be referenced from
+ this file, so we target its data-testid attributes instead. */
+ .userProfile > [data-testid='report-block-wrapper'] {
+ flex: 1;
+ height: auto;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .userProfile [data-testid='report-block-content'] {
+ flex: 1;
+ height: auto;
+ margin: 0;
}
.metrics.fourMetrics {
diff --git a/src/components/Reports/PeopleReport/__tests__/CompletedTasksPieChart.test.jsx b/src/components/Reports/PeopleReport/__tests__/CompletedTasksPieChart.test.jsx
index 3f586d7bc75..5a8d2e177f1 100644
--- a/src/components/Reports/PeopleReport/__tests__/CompletedTasksPieChart.test.jsx
+++ b/src/components/Reports/PeopleReport/__tests__/CompletedTasksPieChart.test.jsx
@@ -1,11 +1,100 @@
-import { render, screen } from '@testing-library/react';
-import { describe, expect, it } from 'vitest';
-import {
+import { fireEvent, render, screen } from '@testing-library/react';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import CompletedTasksPieChart, {
ColorSwatchCell,
+ MAX_VISIBLE_TASKS,
PieTooltip,
TotalHoursLabel,
} from '../CompletedTasksPieChart';
+const mockUseSelector = vi.fn();
+
+vi.mock('react-redux', async () => {
+ const actual = await vi.importActual('react-redux');
+ return {
+ ...actual,
+ useSelector: selector => mockUseSelector(selector),
+ };
+});
+
+const makeTasks = count =>
+ Array.from({ length: count }, (unused, i) => ({
+ projectId: `proj-${i}`,
+ projectName: `Task ${i}`,
+ totalTime: 1,
+ }));
+
+const renderChart = taskCount => {
+ mockUseSelector.mockReturnValue({ tasksWithLoggedHoursById: makeTasks(taskCount) });
+ return render(
);
+};
+
+const countTaskRows = () =>
+ screen.getAllByTestId('color-swatch').length;
+
+describe('CompletedTasksPieChart task list cap', () => {
+ beforeEach(() => {
+ mockUseSelector.mockReset();
+ });
+
+ // Pinned to the literal on purpose: the other tests reference MAX_VISIBLE_TASKS
+ // symbolically, so they stay green if the cap is lowered for local debugging.
+ // This one fails if a temporary value gets committed.
+ it('caps the collapsed list at 20, the agreed product limit', () => {
+ expect(MAX_VISIBLE_TASKS).toBe(20);
+ });
+
+ it('caps the collapsed list at the limit', () => {
+ renderChart(MAX_VISIBLE_TASKS + 13);
+
+ expect(countTaskRows()).toBe(MAX_VISIBLE_TASKS);
+ });
+
+ it('renders every task without a toggle when at or under the cap', () => {
+ renderChart(MAX_VISIBLE_TASKS);
+
+ expect(countTaskRows()).toBe(MAX_VISIBLE_TASKS);
+ expect(screen.queryByTestId('toggle-more-tasks')).not.toBeInTheDocument();
+ });
+
+ it('labels the toggle with the number of hidden tasks', () => {
+ renderChart(MAX_VISIBLE_TASKS + 13);
+
+ expect(screen.getByTestId('toggle-more-tasks')).toHaveTextContent('+ 13 more tasks');
+ });
+
+ it('singularizes the label when exactly one task is hidden', () => {
+ renderChart(MAX_VISIBLE_TASKS + 1);
+
+ expect(screen.getByTestId('toggle-more-tasks')).toHaveTextContent('+ 1 more task');
+ });
+
+ it('expands to every task and collapses back to the cap', () => {
+ renderChart(MAX_VISIBLE_TASKS + 13);
+ const toggle = screen.getByTestId('toggle-more-tasks');
+
+ expect(toggle).toHaveAttribute('aria-expanded', 'false');
+
+ fireEvent.click(toggle);
+ expect(countTaskRows()).toBe(MAX_VISIBLE_TASKS + 13);
+ expect(toggle).toHaveAttribute('aria-expanded', 'true');
+ expect(toggle).toHaveTextContent('Show less');
+
+ fireEvent.click(toggle);
+ expect(countTaskRows()).toBe(MAX_VISIBLE_TASKS);
+ expect(toggle).toHaveAttribute('aria-expanded', 'false');
+ expect(toggle).toHaveTextContent('+ 13 more tasks');
+ });
+
+ it('keeps the total hours row rendered after the list in both states', () => {
+ renderChart(MAX_VISIBLE_TASKS + 5);
+
+ expect(screen.getByText('Total Hours:')).toBeInTheDocument();
+ fireEvent.click(screen.getByTestId('toggle-more-tasks'));
+ expect(screen.getByText('Total Hours:')).toBeInTheDocument();
+ });
+});
+
describe('ColorSwatchCell', () => {
const colorScale = { proj1: '#ff00aa', proj2: '#00aaff' };
diff --git a/src/components/Reports/PeopleTableDetails.jsx b/src/components/Reports/PeopleTableDetails.jsx
index 17d29ea540e..745236d3760 100644
--- a/src/components/Reports/PeopleTableDetails.jsx
+++ b/src/components/Reports/PeopleTableDetails.jsx
@@ -4,6 +4,12 @@ import { useTable } from 'react-table';
import styles from './PeopleTableDetails.module.css';
import TableFilter from './TableFilter/TableFilter';
+/**
+ * TaskResourceCell Component
+ * Renders the primary profile pictures for assigned resources (up to 2),
+ * a collapse/expand toggle button for additional resources, and a hidden/visible
+ * container for extra resources when expanded.
+ */
export function TaskResourceCell({
row,
column,
@@ -16,9 +22,9 @@ export function TaskResourceCell({
return (
<>
- {taskResources.slice(0, 2).map((resource, index) => (
+ {taskResources.slice(0, 2).map((resource) => (
- {taskResources.slice(2).map((resource, index) => (
+ {taskResources.slice(2).map((resource) => (
@@ -65,11 +75,21 @@ export function YesNoCell({ value }) {
);
}
+/**
+ * CenteredValueCell Component
+ * Renders any standard text value centered within its table cell.
+ */
export function CenteredValueCell({ value }) {
return
{value}
;
}
+/**
+ * PeopleTableDetails Main Component
+ * Manages filtering states, table data configuration using react-table,
+ * desktop table layout, and responsive mobile card view.
+ */
function PeopleTableDetails(props) {
+ // Filter search states
const [name, setName] = useState('');
const [priority, setPriority] = useState('');
const [status, setStatus] = useState('');
@@ -140,7 +160,6 @@ function PeopleTableDetails(props) {
return filteredList;
};
- // REFACTORED: Toggle using state instead of direct DOM manipulation
const toggleMoreResources = useCallback((id) => {
setExpandedTasks((prev) => ({
...prev,
@@ -244,6 +263,7 @@ function PeopleTableDetails(props) {
Clear Filters
+
@@ -283,8 +303,53 @@ function PeopleTableDetails(props) {
+
+
+ {filteredTasks.map((task) => (
+
+
+ {task.taskName}
+ {task.status || 'Started'}
+
+
+
+
Priority
+
{task.priority}
+
+
+
Resources
+
+ {(task.resources || []).flat().map((res) => (
+
+ ))}
+
+
+
+
Active
+
{task.active}
+
+
+
Assign
+
{task.assign}
+
+
+
Estimated Hours
+
{task.estimatedHours}
+
+
+
Start Date
+
{task.startDate}
+
+
+
End Date
+
{task.endDate}
+
+
+
+ ))}
+
>
);
}
-export default PeopleTableDetails;
+export default PeopleTableDetails;
\ No newline at end of file
diff --git a/src/components/Reports/PeopleTableDetails.module.css b/src/components/Reports/PeopleTableDetails.module.css
index 2a293a9ed93..8103592e245 100644
--- a/src/components/Reports/PeopleTableDetails.module.css
+++ b/src/components/Reports/PeopleTableDetails.module.css
@@ -1,4 +1,3 @@
-/* stylelint-disable declaration-property-value-no-unknown, no-descending-specificity */
.wrapper {
overflow-x: auto;
width: 100%;
@@ -55,7 +54,6 @@
overflow: hidden;
}
-/* Profile picture styling */
.img-circle {
border-radius: 50%;
height: 30px;
@@ -95,11 +93,16 @@
color: #f1f1f1;
}
+.people-table-row-dark {
+ background-color: transparent !important;
+ color: #fff !important;
+ border-bottom: 1px solid #444 !important;
+}
+
.people-table-row:hover {
background-color: #fafafa;
}
-/* ✅ merged hover style */
.people-table-row-dark:hover {
background-color: #232946 !important;
color: #fff !important;
@@ -153,7 +156,7 @@
.extra {
display: none;
- border: collapse;
+ border: none;
}
.extra1 {
@@ -173,32 +176,54 @@
}
.task-card-dark {
- color: black;
+ color: #fff;
+ background-color: #232946;
+ border: 1px solid #444;
}
.task-header {
background-color: #eee7f8;
display: flex;
justify-content: space-between;
+ align-items: flex-start;
+ gap: 10px;
padding: 15px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
- align-items: flex-start;
+}
+
+.task-card-dark .task-header {
+ background-color: #1a1a2e;
+ color: #fff;
}
.task-status {
display: flex;
align-items: center;
- padding: 5px 10px;
+ justify-content: center;
+ padding: 4px 10px;
border-radius: 20px;
background-color: #ffc107;
color: #fff;
- width: fit-content;
- margin-left: 5px;
+ flex-shrink: 0;
+ white-space: nowrap;
+ margin-left: 0;
+}
+
+.task-card-dark .task-status {
+ background-color: #ca50db;
+ color: #fff;
}
.task-info {
display: flex;
+ padding: 6px 0;
+ border-bottom: 1px solid #f2f2f2;
+}
+
+.task-card-dark .task-info {
+ border-bottom: 1px solid #374151;
+ color: #fff;
}
.sub-head {
@@ -206,12 +231,22 @@
text-align: end;
width: 50%;
padding-right: 2%;
+ color: #4b5563;
+}
+
+.task-card-dark .sub-head {
+ color: #9ca3af;
}
.sub-details {
text-align: start;
width: 50%;
padding-left: 2%;
+ color: #1f2937;
+}
+
+.task-card-dark .sub-details {
+ color: #fff;
}
.table-filter-container {
@@ -249,130 +284,99 @@
color: #e7b1ef;
}
-/* Dark mode styling for Clear Filters button */
-.wrapperDark .tasks-table-clear-filter-button {
- background-color: #232946 !important;
- border: 2px solid #5A67D8 !important;
- color: #fff !important;
-}
-
-.wrapperDark .tasks-table-clear-filter-button:hover {
- background-color: #1C2541 !important;
- border-color: #ca50db !important;
-}
-
.people-report-task-name {
- overflow-wrap: break-word;
- min-width: 200px;
+ flex: 1;
+ /* Flex items default to min-width: auto (their longest word), so a space-free name
+ would refuse to shrink and push .task-status out of the card. */
+ min-width: 0;
+ font-weight: 600;
+ font-size: 16px;
text-align: left;
-}
-
-.task-name-word-break {
- word-break: break-all;
+ overflow-wrap: break-word;
}
.people-table-scrollable {
- max-height: 600px;
- overflow-y: auto;
width: 100%;
+ max-height: 600px;
+ overflow: auto;
+ -webkit-overflow-scrolling: touch;
+ box-sizing: border-box;
}
-.peopleTableReact {
- width: 100%;
- min-width: 720px;
- border-collapse: collapse;
-}
-.peopleTableReact th,
-.peopleTableReact td {
- padding: 8px;
- text-align: left;
- overflow-wrap: break-word;
- vertical-align: middle;
-}
+/* ========== FIX: Scrollbar styling for both light and dark modes ========== Light Mode: Display scrollbar with light theme colors for better visibility */
-.peopleTableReact th {
- background-color: #eee7f8;
- font-size: 18px;
+.people-table-scrollable::-webkit-scrollbar {
+ height: 8px;
+ width: 8px;
}
-.peopleTableReact td {
- border-bottom: 1px solid #f8f7f8;
- font-size: 16px;
+.people-table-scrollable::-webkit-scrollbar-track {
+ background: #f0f0f0;
+ border-radius: 4px;
}
-.peopleTableReact th:first-child,
-.peopleTableReact td:first-child {
- width: 28%;
+.people-table-scrollable::-webkit-scrollbar-thumb {
+ background: #999;
+ border-radius: 4px;
}
-.peopleTableReact th:nth-child(4),
-.peopleTableReact td:nth-child(4) {
- width: 14%;
+.people-table-scrollable::-webkit-scrollbar-thumb:hover {
+ background: #666;
}
-.peopleTableReact tbody tr:hover {
- background-color: #fafafa;
-}
-.people-table-row-dark td {
- border-color: #374151;
-}
+/* Dark Mode: Override light mode styles with dark theme colors */
-.reports-table-head-dark th {
- background-color: #1b2a41;
- color: #f9fafb;
+:global(.dark-mode) .people-table-scrollable::-webkit-scrollbar-track {
+ background: #162032;
}
-.wrapperDark .people-table-scrollable {
- background-color: #162032;
- border: 1px solid #3a4460;
- border-radius: 8px;
- padding: 4px 0;
+:global(.dark-mode) .people-table-scrollable::-webkit-scrollbar-thumb {
+ background: #4b5563;
}
-/* Dark mode for mobile modal cards */
-.task-card.task-card-dark {
- background-color: #232946;
- color: #fff;
- border: 1px solid #444;
+:global(.dark-mode) .people-table-scrollable::-webkit-scrollbar-thumb:hover {
+ background: #6b7280;
}
-.task-card.task-card-dark .task-header {
- background-color: #1a1a2e;
- color: #fff;
-}
+/* ========== END: Scrollbar styling ========== */
-.task-card.task-card-dark .task-status {
- background-color: #ca50db;
- color: #fff;
+.reports-table-head-dark th {
+ background-color: #1b2a41;
+ color: #f9fafb;
}
-.task-card.task-card-dark .task-info {
- color: #fff;
+.reports-table-head-dark {
+ background-color: #1a1a2e !important;
+ color: #fff !important;
+ border-bottom: 1px solid #444 !important;
}
-.task-card.task-card-dark .sub-head,
-.task-card.task-card-dark .sub-details {
- color: #fff;
+.people-table-row-dark td {
+ border-color: #374151;
}
-/* Modal textarea dark mode */
-.task-card-dark .rectangle {
- background: #232946;
- color: #fff;
- border: 1.5px solid #444;
+.peopleTableReact {
+ width: 100%;
+ min-width: 850px;
+ border-collapse: collapse;
}
-/* Desktop table dark mode */
-.reports-table-head-dark {
- background-color: #1a1a2e !important;
- color: #fff !important;
+.peopleTableReact th,
+.peopleTableReact td {
+ padding: 8px;
+ text-align: left;
+ overflow-wrap: break-word;
+ vertical-align: middle;
}
-.people-table-row-dark {
- background-color: transparent !important;
- color: #fff !important;
+.peopleTableReact th:first-child,
+.peopleTableReact td:first-child {
+ width: 28%;
+ min-width: 260px;
+ max-width: 350px;
+ overflow-wrap: break-word;
}
.people-table-row-dark > div,
@@ -380,25 +384,31 @@
color: #fff !important;
}
-.people-table-row-dark .people-table-center-cell,
-.people-table-row-dark .people-table-end-cell {
- color: #fff !important;
+.centerColumn {
+ text-align: center !important;
}
-/* Table cell border for dark mode */
-.people-table-row-dark,
-.reports-table-head-dark {
- border-bottom: 1px solid #444 !important;
+:global(.dark-mode) .peopleTableReact {
+ background-color: #1b2a41;
+ color: #f9fafb;
}
-/* Table textarea dark mode */
-.people-table-row-dark .rectangle,
-.reports-table-head-dark .rectangle {
- background: #232946;
- color: #fff;
- border: 1px solid #444;
+:global(.dark-mode) .people-table-row-dark {
+ background-color: #1b2a41 !important;
+ color: #f9fafb !important;
}
-.centerColumn {
- text-align: center !important;
+.mobileCardContainer {
+ display: none;
+}
+
+@media (width <= 600px) {
+ .people-table-scrollable {
+ display: none !important;
+ }
+
+ .mobileCardContainer {
+ display: block !important;
+ width: 100%;
+ }
}
\ No newline at end of file
diff --git a/src/components/Reports/__tests__/PeopleTableDetails.test.jsx b/src/components/Reports/__tests__/PeopleTableDetails.test.jsx
index b7bfd66c849..6589022558c 100644
--- a/src/components/Reports/__tests__/PeopleTableDetails.test.jsx
+++ b/src/components/Reports/__tests__/PeopleTableDetails.test.jsx
@@ -64,9 +64,9 @@ describe('Unit Test case for PeopleTableDetails component', () => {
it('Test 3 : Verify row data renders', () => {
render( );
- expect(screen.getByText('Task 1')).toBeInTheDocument();
- expect(screen.getByText('Task 2')).toBeInTheDocument();
- expect(screen.getByText('Task 3')).toBeInTheDocument();
+ expect(screen.getAllByText('Task 1')[0]).toBeInTheDocument();
+ expect(screen.getAllByText('Task 2')[0]).toBeInTheDocument();
+ expect(screen.getAllByText('Task 3')[0]).toBeInTheDocument();
});
it('displays a task with no resources and no start date', () => {
@@ -78,7 +78,7 @@ describe('Unit Test case for PeopleTableDetails component', () => {
render( );
- expect(screen.getByText('Unassigned task')).toBeInTheDocument();
+ expect(screen.getAllByText('Unassigned task')[0]).toBeInTheDocument();
});
it('Test 5 : Verify no toggle button if resources < 2', () => {
@@ -88,14 +88,14 @@ describe('Unit Test case for PeopleTableDetails component', () => {
it('Test 6 : Verify button renders if resources > 2', () => {
render( );
- expect(screen.getByRole('button', { name: /^\d+\+$/ })).toBeInTheDocument();
+ expect(screen.getAllByRole('button', { name: /^\d+\+$/ })[0]).toBeInTheDocument();
});
it('shows resource toggle button when there are more than 2 resources', () => {
render( );
- expect(screen.getByText('Project 2')).toBeInTheDocument();
- const toggleButton = screen.getByText('1+');
+ expect(screen.getAllByText('Project 2')[0]).toBeInTheDocument();
+ const toggleButton = screen.getAllByText('1+')[0];
expect(toggleButton).toBeInTheDocument();
});
@@ -103,7 +103,7 @@ describe('Unit Test case for PeopleTableDetails component', () => {
const taskId = taskFixtureThreeNamedResources[0]._id;
render( );
- const toggleButton = screen.getByRole('button', { name: /^\d+\+$/ });
+ const toggleButton = screen.getAllByRole('button', { name: /^\d+\+$/ })[0];
expect(toggleButton).toBeInTheDocument();
expect(screen.getByTestId(`extra-resources-${taskId}`)).toBeInTheDocument();
@@ -112,13 +112,13 @@ describe('Unit Test case for PeopleTableDetails component', () => {
display: 'table-cell',
});
- fireEvent.click(screen.getByRole('button', { name: /^\d+\+$/ }));
+ fireEvent.click(screen.getAllByRole('button', { name: /^\d+\+$/ })[0]);
expect(screen.getByTestId(`extra-resources-${taskId}`)).toHaveStyle({ display: 'none' });
});
it('Test 8 : Verify remaining resource count displayed', () => {
render( );
- expect(screen.getByText('2+')).toBeInTheDocument();
+ expect(screen.getAllByText('2+')[0]).toBeInTheDocument();
});
});
@@ -137,7 +137,7 @@ describe('TaskResourceCell', () => {
{...baseProps}
/>,
);
- expect(screen.getByText('1+')).toBeInTheDocument();
+ expect(screen.getAllByText('1+')[0]).toBeInTheDocument();
});
it('shows the extra-resources block when the task is in expandedTasks', () => {
@@ -163,7 +163,7 @@ describe('TaskResourceCell', () => {
toggleMoreResources={toggleMoreResources}
/>,
);
- fireEvent.click(screen.getByText('1+'));
+ fireEvent.click(screen.getAllByText('1+')[0]);
expect(toggleMoreResources).toHaveBeenCalledWith(task._id);
});
});
@@ -185,4 +185,4 @@ describe('CenteredValueCell', () => {
render( );
expect(screen.getByText('2022-01-01')).toHaveStyle({ textAlign: 'center' });
});
-});
+});
\ No newline at end of file
diff --git a/src/components/Reports/sharedComponents/ReportPage/components/ReportBlock/ReportBlock.module.css b/src/components/Reports/sharedComponents/ReportPage/components/ReportBlock/ReportBlock.module.css
index ee239fa557e..4539aabcf3b 100644
--- a/src/components/Reports/sharedComponents/ReportPage/components/ReportBlock/ReportBlock.module.css
+++ b/src/components/Reports/sharedComponents/ReportPage/components/ReportBlock/ReportBlock.module.css
@@ -9,9 +9,18 @@
.report-block-content {
height: 100%;
+ display: flex;
+ flex-direction: column;
padding: 1rem;
border-radius: 12px;
margin: 2px;
+ gap: 12px; /* Consistent spacing for better visual hierarchy */
+}
+
+/* Ensure children distribute evenly with flex layout and no shrinking */
+.report-block-content > * {
+ flex: 1;
+ width: 100%;
}
.report-block-wrapper-dark {
diff --git a/src/components/ResolvedTasks/ResolvedTasks.jsx b/src/components/ResolvedTasks/ResolvedTasks.jsx
new file mode 100644
index 00000000000..77dba93b7d9
--- /dev/null
+++ b/src/components/ResolvedTasks/ResolvedTasks.jsx
@@ -0,0 +1,125 @@
+import React, { useState, useEffect } from 'react';
+import { Container, Row, Col, Table } from 'reactstrap';
+import { connect } from 'react-redux';
+import axios from 'axios';
+import styles from './ResolvedTasks.module.css';
+import moment from 'moment';
+import { Link } from 'react-router-dom';
+import { ENDPOINTS } from '~/utils/URL';
+import PropTypes from 'prop-types';
+
+const ResolvedTasks = props => {
+ const [tasks, setTasks] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const { darkMode } = props.theme;
+
+ useEffect(() => {
+ const fetchResolvedTasks = async () => {
+ try {
+ const response = await axios.get(ENDPOINTS.RESOLVED_TASKS(1, 100));
+ setTasks(response.data.tasks);
+ setLoading(false);
+ } catch (err) {
+ setError(err.message);
+ setLoading(false);
+ }
+ };
+
+ void fetchResolvedTasks();
+ }, []);
+
+ if (loading) return Loading...
;
+ if (error) return Error: {error}
;
+
+ return (
+
+
+
+ Resolved and Closed Tasks
+
+
+
+ #
+ Task Name
+ Assigned Resources
+ Resolved/Closed By
+ Date
+
+
+
+ {tasks.length === 0 ? (
+
+
+ No resolved tasks found.
+
+
+ ) : (
+ tasks.map(item => (
+
+ {item.taskId?.num || 'N/A'}
+
+ {item.taskId ? (
+
+ {item.taskId.taskName}
+
+ ) : (
+ Task deleted/unavailable
+ )}
+
+
+ {item.taskId?.resources?.map((res, index) => (
+
+
{res.name}
+
+ {res.userID?.email || 'No email available'}
+
+
+ ))}
+
+
+
+ {item.userId
+ ? `${item.userId.firstName} ${item.userId.lastName}`
+ : item.userName || 'Unknown'}
+
+
+ {item.userId?.email || item.userEmail || 'No email available'}
+
+
+ {moment(item.timestamp).format('LLL')}
+
+ ))
+ )}
+
+
+
+
+
+ );
+};
+
+const mapStateToProps = state => ({
+ theme: state.theme,
+});
+
+ResolvedTasks.propTypes = {
+ theme: PropTypes.shape({
+ darkMode: PropTypes.bool,
+ }),
+};
+
+export default connect(mapStateToProps)(ResolvedTasks);
diff --git a/src/components/ResolvedTasks/ResolvedTasks.module.css b/src/components/ResolvedTasks/ResolvedTasks.module.css
new file mode 100644
index 00000000000..a0b40ec2059
--- /dev/null
+++ b/src/components/ResolvedTasks/ResolvedTasks.module.css
@@ -0,0 +1,10 @@
+:global(body.dark-mode) a.resolvedTaskLinkDark {
+ color: #66bfff !important;
+ text-decoration: underline;
+ font-weight: 500;
+}
+
+:global(body.dark-mode) a.resolvedTaskLinkDark:hover {
+ color: #9dd7ff !important;
+ text-decoration: underline;
+}
diff --git a/src/components/ResolvedTasks/__tests__/ResolvedTasks.test.jsx b/src/components/ResolvedTasks/__tests__/ResolvedTasks.test.jsx
new file mode 100644
index 00000000000..526da1dac70
--- /dev/null
+++ b/src/components/ResolvedTasks/__tests__/ResolvedTasks.test.jsx
@@ -0,0 +1,102 @@
+import { render, screen, waitFor } from '@testing-library/react';
+import axios from 'axios';
+import { Provider } from 'react-redux';
+import configureMockStore from 'redux-mock-store';
+import { MemoryRouter } from 'react-router-dom';
+import ResolvedTasks from '../ResolvedTasks';
+
+vi.mock('axios');
+
+const mockStore = configureMockStore([]);
+
+const renderComponent = () => {
+ const store = mockStore({
+ theme: { darkMode: false },
+ });
+
+ return render(
+
+
+
+
+ ,
+ );
+};
+
+describe('ResolvedTasks', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('loads and displays resolved tasks', async () => {
+ axios.get.mockResolvedValue({
+ data: {
+ tasks: [
+ {
+ _id: 'log-1',
+ timestamp: '2026-09-30T12:00:00.000Z',
+ taskId: {
+ _id: 'task-1',
+ num: 123,
+ taskName: 'Test resolved task',
+ resources: [
+ {
+ name: 'Assigned User',
+ userID: {
+ _id: 'user-1',
+ email: 'assigned@example.com',
+ },
+ },
+ ],
+ },
+ userId: {
+ firstName: 'Admin',
+ lastName: 'User',
+ email: 'admin@example.com',
+ },
+ },
+ ],
+ },
+ });
+
+ renderComponent();
+
+ expect(screen.getByText('Loading...')).toBeInTheDocument();
+
+ await waitFor(() => {
+ expect(screen.getByText('Test resolved task')).toBeInTheDocument();
+ });
+
+ expect(axios.get).toHaveBeenCalledWith(
+ expect.stringContaining('/tasks/resolved?page=1&limit=100'),
+ );
+ expect(screen.getByText('Assigned User')).toBeInTheDocument();
+ expect(screen.getByText('assigned@example.com')).toBeInTheDocument();
+ expect(screen.getByText('Admin User')).toBeInTheDocument();
+ expect(screen.getByText('admin@example.com')).toBeInTheDocument();
+ });
+
+ it('shows empty state when no resolved tasks exist', async () => {
+ axios.get.mockResolvedValue({
+ data: {
+ tasks: [],
+ },
+ });
+
+ renderComponent();
+
+ await waitFor(() => {
+ expect(screen.getByText('No resolved tasks found.')).toBeInTheDocument();
+ });
+ });
+
+ it('shows an error when loading fails', async () => {
+ axios.get.mockRejectedValue(new Error('Network error'));
+
+ renderComponent();
+
+ await waitFor(() => {
+ expect(screen.getByText('Error: Network error')).toBeInTheDocument();
+ });
+ });
+});
diff --git a/src/components/TeamLocations/TeamLocations.countryCounts.test.jsx b/src/components/TeamLocations/TeamLocations.countryCounts.test.jsx
new file mode 100644
index 00000000000..19bcea782af
--- /dev/null
+++ b/src/components/TeamLocations/TeamLocations.countryCounts.test.jsx
@@ -0,0 +1,67 @@
+import axios from 'axios';
+import { render, screen } from '@testing-library/react';
+import TeamLocations from './TeamLocations';
+
+const testState = vi.hoisted(() => ({ darkMode: false }));
+
+vi.mock('axios', () => ({ default: { get: vi.fn() } }));
+vi.mock('react-redux', () => ({
+ useSelector: selector =>
+ selector({ auth: { user: { role: 'Volunteer' } }, theme: { darkMode: testState.darkMode } }),
+}));
+vi.mock('react-leaflet', async () => {
+ const { forwardRef } = await import('react');
+ return {
+ MapContainer: forwardRef(({ children }, ref) => {children}
),
+ TileLayer: () => null,
+ useMapEvents: () => ({ getZoom: () => 3 }),
+ };
+});
+vi.mock('./TestSafeMarkerCluster', () => ({ default: ({ children }) => {children}
}));
+vi.mock('./AddOrEditPopup', () => ({ default: () => null }));
+vi.mock('./ListUsersPopUp', () => ({ default: () => null }));
+vi.mock('./MarkerPopup', () => ({ default: () => null }));
+vi.mock('./TeamLocationsTable', () => ({ default: () => null }));
+
+describe('Team Locations country counts', () => {
+ afterEach(() => {
+ vi.clearAllMocks();
+ testState.darkMode = false;
+ });
+
+ it('shows the all-time and active counts from the map locations response', async () => {
+ axios.get.mockResolvedValue({
+ data: { users: [], mUsers: [], countryCounts: { allTime: 76, active: 23 } },
+ });
+
+ render( );
+
+ const counts = await screen.findByLabelText('Volunteer country counts');
+ expect(screen.getByRole('heading', { level: 5 })).toHaveTextContent('Total Countries:0');
+ expect(counts).toHaveTextContent('All-time contributing countries: 76');
+ expect(counts).toHaveTextContent('Currently active countries: 23');
+ });
+
+ it('shows the counts in dark mode', async () => {
+ testState.darkMode = true;
+ axios.get.mockResolvedValue({
+ data: { users: [], mUsers: [], countryCounts: { allTime: 76, active: 23 } },
+ });
+
+ render( );
+
+ const counts = await screen.findByLabelText('Volunteer country counts');
+ expect(counts).toHaveTextContent('All-time contributing countries: 76');
+ expect(counts).toHaveTextContent('Currently active countries: 23');
+ });
+
+ it('keeps the two labels visible when the backend has not supplied counts', async () => {
+ axios.get.mockResolvedValue({ data: { users: [], mUsers: [] } });
+
+ render( );
+
+ const counts = await screen.findByLabelText('Volunteer country counts');
+ expect(counts).toHaveTextContent('All-time contributing countries: —');
+ expect(counts).toHaveTextContent('Currently active countries: —');
+ });
+});
diff --git a/src/components/TeamLocations/TeamLocations.jsx b/src/components/TeamLocations/TeamLocations.jsx
index b5d52b1efb9..0db414f4a76 100644
--- a/src/components/TeamLocations/TeamLocations.jsx
+++ b/src/components/TeamLocations/TeamLocations.jsx
@@ -4,7 +4,7 @@ import { useEffect, useRef, useState, forwardRef, Suspense } from 'react';
import { MapContainer, TileLayer, useMapEvents } from 'react-leaflet';
import MarkerClusterGroup from './TestSafeMarkerCluster';
import { Button, Container, Spinner } from 'reactstrap';
-import './TeamLocations.module.css';
+import styles from './TeamLocations.module.css';
import { useSelector } from 'react-redux';
import { toast } from 'react-toastify';
@@ -36,6 +36,7 @@ const TeamLocations = forwardRef(() => {
const [searchText, setSearchText] = useState('');
const [popupsOpen, setPopupsOpen] = useState(false);
const [mapMarkers, setMapMarkers] = useState([]);
+ const [countryCounts, setCountryCounts] = useState(null);
const [tableVisible, setTableVisible] = useState(false);
const [markerPopupVisible, setMarkerPopupVisible] = useState(false);
const role = useSelector(state => state.auth.user.role);
@@ -71,6 +72,7 @@ const TeamLocations = forwardRef(() => {
setUserProfiles(users);
setManuallyAddedProfiles(mUsers);
+ setCountryCounts(locations.countryCounts ?? null);
const allMapMarkers = [...users, ...mUsers];
const allMapMarkersOffset = allMapMarkers.map(ele => ({
...ele,
@@ -190,7 +192,7 @@ const TeamLocations = forwardRef(() => {
return (
{
Total Countries:
{totalUniqueCountries}
+
+
+ All-time contributing countries: {countryCounts?.allTime ?? '—'}
+
+
+ Currently active countries: {countryCounts?.active ?? '—'}
+
+
diff --git a/src/components/TeamMemberTasks/style.module.css b/src/components/TeamMemberTasks/style.module.css
index 964b54ce09c..3a9b6b63e45 100644
--- a/src/components/TeamMemberTasks/style.module.css
+++ b/src/components/TeamMemberTasks/style.module.css
@@ -13,6 +13,16 @@
background-color: white;
overflow: hidden;
max-width: 100%;
+ height: 100%;
+}
+
+.team-member-tasks table {
+ height: 100%;
+}
+
+.team-member-tasks tbody,
+.team-member-tasks tr {
+ height: 100%;
}
.truncateTasksBtn {
@@ -70,6 +80,7 @@
.task_table-container {
width: 100%;
+ height: 100%;
overflow: hidden auto;
-webkit-overflow-scrolling: touch;
}
@@ -190,13 +201,50 @@
}
.team-member-tasks-user-name {
- width: 50%;
+ width: 100%;
+ height: 100%;
text-align: center;
- vertical-align: top !important;
+ vertical-align: stretch !important;
display: flex;
flex-direction: column;
- justify-content: flex-start !important;
+ justify-content: flex-start;
align-items: center;
+ padding: 10px 5px;
+ gap: 12px;
+}
+
+/* Ensure all direct child elements of user card have proper flex behavior */
+.team-member-tasks-user-name > * {
+ flex-shrink: 0;
+ width: 100%;
+}
+
+/* Push team-clocks to the bottom of the card */
+.team-member-tasks-user-name .team-clocks {
+ margin-top: auto;
+ margin-bottom: 0;
+}
+
+/* User name link styling */
+.team-member-tasks-user-name-link {
+ display: block;
+ margin: 0;
+ line-height: 1.3;
+ width: 100%;
+}
+
+/* Ensure subtable inside user card stretches to fill parent */
+.team-member-tasks-subtable {
+ height: 100%;
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.team-member-tasks-subtable tbody,
+.team-member-tasks-subtable tbody tr,
+.team-member-tasks-subtable tbody tr td {
+ height: 100%;
+ vertical-align: stretch !important;
}
.team-member-tasks td {
@@ -248,10 +296,12 @@
}
.team-clocks {
- display: table-cell;
+ display: block;
text-align: center;
- vertical-align: top;
- padding-left: 10px !important;
+ width: 100%;
+ padding: 8px 0;
+ margin-top: auto;
+ flex-shrink: 0;
}
@@ -360,6 +410,8 @@
.table-row {
position: relative;
+ height: 100%;
+ vertical-align: stretch !important;
}
.hidden {
@@ -383,6 +435,14 @@
.row-content {
position: relative;
height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+/* Ensure the remove-padding td (containing row-content) stretches */
+.remove-padding {
+ height: 100% !important;
+ vertical-align: stretch !important;
}
.no-bottom-margin {
@@ -506,6 +566,8 @@
.grid-container {
display: grid;
grid-template-rows: 1fr auto;
+ height: 100%;
+ min-height: 100%;
}
.expand-time-off-detail-button {
@@ -893,6 +955,7 @@
.container {
width: 100%;
+ height: 100%;
margin-left: auto;
margin-right: auto;
}
diff --git a/src/components/Timelog/TimeEntryForm/TimeEntryForm.jsx b/src/components/Timelog/TimeEntryForm/TimeEntryForm.jsx
index a646a127aa7..cfadb2d52b2 100644
--- a/src/components/Timelog/TimeEntryForm/TimeEntryForm.jsx
+++ b/src/components/Timelog/TimeEntryForm/TimeEntryForm.jsx
@@ -385,8 +385,13 @@ function TimeEntryForm(props) {
const handlePostSubmitActions = async () => {
switch (from) {
- case 'Timer':
- sendStop();
+ case 'Timer': {
+ const timerStopped = await sendStop();
+ if (!timerStopped) {
+ toast.warning(
+ 'Time was logged, but the timer stop could not be confirmed. Refresh the timer before continuing.',
+ );
+ }
clearForm();
// Enhanced callback with additional data
if (onTimeSubmitted) {
@@ -406,6 +411,7 @@ function TimeEntryForm(props) {
}),
);
break;
+ }
case 'WeekEnd':
// Handle week-end time log completion
if (props.onComplete) {
diff --git a/src/components/Timer/Countdown.jsx b/src/components/Timer/Countdown.jsx
index 586ed0e9049..f8b36bd66f4 100644
--- a/src/components/Timer/Countdown.jsx
+++ b/src/components/Timer/Countdown.jsx
@@ -1,6 +1,6 @@
import cs from 'classnames';
import moment from 'moment';
-import { useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import { CircularProgressbarWithChildren, buildStyles } from 'react-circular-progressbar';
import {
BsArrowCounterclockwise,
@@ -57,6 +57,16 @@ export default function Countdown({
const hourRef = useRef(null);
const minRef = useRef(null);
+ // initialHours/initialMinutes only seeded initialGoal once, at mount, so they went
+ // stale whenever initialGoal changed afterwards (e.g. the idle +/- buttons now
+ // moving the baseline, or another tab/the pop-out saving a new goal). Skip the
+ // sync while the user is actively editing so it doesn't clobber their input.
+ useEffect(() => {
+ if (editing) return;
+ setInitialHours(moment.duration(initialGoal).hours());
+ setInitialMinutes(moment.duration(initialGoal).minutes());
+ }, [initialGoal, editing]);
+
const remainingHours = moment.duration(remaining).hours();
const remainingMinutes = moment.duration(remaining).minutes();
const remainingSeconds = moment.duration(remaining).seconds();
diff --git a/src/components/Timer/Timer.jsx b/src/components/Timer/Timer.jsx
index 7a5fa722aca..a227f21aec1 100644
--- a/src/components/Timer/Timer.jsx
+++ b/src/components/Timer/Timer.jsx
@@ -120,6 +120,8 @@ function Timer({ authUser, darkMode, isPopout }) {
const [viewingUserId, setViewingUserId] = useState(null);
const [weekEndModal, setWeekEndModal] = useState(false);
const isWSOpenRef = useRef(0);
+ const pendingStopRef = useRef(null);
+ const stopRequestSequenceRef = useRef(0);
const timeIsOverAudioRef = useRef(null);
const forcedPausedAudioRef = useRef(null);
const audioUnlockedRef = useRef(false);
@@ -133,6 +135,50 @@ function Timer({ authUser, darkMode, isPopout }) {
const sendJsonMessageNoQueue = useCallback(msg => sendJsonMessage(msg, false), [sendJsonMessage]);
+ const sendStopAndConfirm = useCallback(
+ targetUserId =>
+ new Promise(resolve => {
+ const socket = getWebSocket();
+ if (!socket || socket.readyState !== ReadyState.OPEN) {
+ resolve(false);
+ return;
+ }
+
+ if (pendingStopRef.current) {
+ clearTimeout(pendingStopRef.current.timeoutId);
+ pendingStopRef.current.resolve(false);
+ }
+
+ stopRequestSequenceRef.current += 1;
+ const requestId = `stop-${Date.now()}-${stopRequestSequenceRef.current}`;
+ const timeoutId = setTimeout(() => {
+ if (pendingStopRef.current?.requestId === requestId) {
+ pendingStopRef.current = null;
+ resolve(false);
+ }
+ }, 10000);
+
+ pendingStopRef.current = {
+ requestId,
+ timeoutId,
+ resolve,
+ };
+
+ try {
+ sendJsonMessageNoQueue({
+ action: 'STOP_TIMER',
+ ...(targetUserId == null ? {} : { userId: targetUserId }),
+ requestId,
+ });
+ } catch (_error) {
+ clearTimeout(timeoutId);
+ pendingStopRef.current = null;
+ resolve(false);
+ }
+ }),
+ [getWebSocket, sendJsonMessageNoQueue],
+ );
+
// Enhanced function to clear submitted time with better logging
const clearSubmittedTime = useCallback(() => {
setLastSubmittedTime(null);
@@ -335,10 +381,11 @@ function Timer({ authUser, darkMode, isPopout }) {
sendJsonMessageNoQueue({ action: action.PAUSE_TIMER });
},
sendClear: () => sendJsonMessageNoQueue({ action: action.CLEAR_TIMER }),
- sendStop: () => {
- sendJsonMessageNoQueue({ action: action.STOP_TIMER });
+ sendStop: async () => {
+ const stopped = await sendStopAndConfirm(null);
// Clear the submitted time when stopping
clearSubmittedTime();
+ return stopped;
},
sendAckForced: () => sendJsonMessageNoQueue({ action: action.ACK_FORCED }),
sendGetTimer: () => sendJsonMessageNoQueue({ action: action.GET_TIMER }),
@@ -362,10 +409,11 @@ function Timer({ authUser, darkMode, isPopout }) {
},
sendClear: () =>
sendJsonMessageNoQueue({ action: action.CLEAR_TIMER, userId: viewingUserId }),
- sendStop: () => {
- sendJsonMessageNoQueue({ action: action.STOP_TIMER, userId: viewingUserId });
+ sendStop: async () => {
+ const stopped = await sendStopAndConfirm(viewingUserId);
// Clear the submitted time when stopping
clearSubmittedTime();
+ return stopped;
},
sendAckForced: () =>
sendJsonMessageNoQueue({ action: action.ACK_FORCED, userId: viewingUserId }),
@@ -390,7 +438,7 @@ function Timer({ authUser, darkMode, isPopout }) {
sendHeartbeat: () =>
sendJsonMessageNoQueue({ action: action.HEARTBEAT, userId: viewingUserId }),
};
- }, [sendJsonMessageNoQueue, viewingUserId, clearSubmittedTime]);
+ }, [sendJsonMessageNoQueue, sendStopAndConfirm, viewingUserId, clearSubmittedTime]);
const {
sendStart,
@@ -457,6 +505,14 @@ function Timer({ authUser, darkMode, isPopout }) {
...prev,
started: true,
paused: false,
+ // The server resets startAt to now on every START_TIMER, and only resets time to goal
+ // on a *first* start (mirrored here from startTimer() in clientsHandler.js). Without this,
+ // updateRemaining() runs against the stale/invalid startAt left over from before the click
+ // and immediately computes remaining as 0, which pops the time-over modal before the
+ // server's real response arrives - most visible on short goals (1-2 min) since any amount
+ // of staleness is likely to exceed them.
+ startAt: Date.now(),
+ time: prev.started ? prev.time : prev.goal,
}));
setRunning(true);
sendStart();
@@ -577,6 +633,15 @@ function Timer({ authUser, darkMode, isPopout }) {
if (lastJsonMessage && lastJsonMessage.type === 'TIMELOG_EVENT') {
return;
}
+ if (
+ lastJsonMessage?.requestId &&
+ pendingStopRef.current?.requestId === lastJsonMessage.requestId
+ ) {
+ const { timeoutId, resolve } = pendingStopRef.current;
+ clearTimeout(timeoutId);
+ pendingStopRef.current = null;
+ resolve(lastJsonMessage.started === false);
+ }
// Handle explicit week close pause action messages
if (lastJsonMessage && lastJsonMessage.action === 'WEEK_CLOSE_PAUSE') {
handleWeekEndPause();
@@ -656,12 +721,21 @@ function Timer({ authUser, darkMode, isPopout }) {
}
}, [remaining, logHours, logMinutes, goal, lastSubmittedTime, validateTimeForSubmission]);
- // Enhanced useEffect for timer state changes
+ // Reset the submission guard once the timer is no longer started (e.g. after
+ // Reset/Clear, whose server response flips started to false). This used to also
+ // fire on "goal !== lastSubmittedTime?.goal", but lastSubmittedTime is a plain
+ // dedup-key string (see trackTimeSubmission), not an object, so .goal was always
+ // undefined - making that half of the condition always true. That wiped logTimer
+ // back to 0/0 on every goal/started change, including right after a reload synced
+ // the real goal/remaining from the server and the effect above had just computed
+ // the correct hours/minutes still owed. The goal check was also redundant: the
+ // dedup key below already embeds goal, so a changed goal naturally produces a
+ // different key without needing separate invalidation here.
useEffect(() => {
- if (!started || goal !== lastSubmittedTime?.goal) {
+ if (!started) {
clearSubmittedTime();
}
- }, [started, goal, clearSubmittedTime]);
+ }, [started, clearSubmittedTime]);
useEffect(() => {
if (timeIsOverModalOpen) {
diff --git a/src/components/Timer/__test__/Timer.test.jsx b/src/components/Timer/__test__/Timer.test.jsx
new file mode 100644
index 00000000000..24f61c39d99
--- /dev/null
+++ b/src/components/Timer/__test__/Timer.test.jsx
@@ -0,0 +1,159 @@
+// eslint-disable-next-line no-unused-vars
+import '@testing-library/jest-dom';
+import { act, fireEvent, render, screen } from '@testing-library/react';
+import { useEffect, useState } from 'react';
+import { Provider } from 'react-redux';
+import configureMockStore from 'redux-mock-store';
+import Timer from '../Timer';
+
+// Shared, hoisted so the vi.mock factory below (which Vitest hoists above this
+// file's imports) can read/write it, and the test body can push fake server
+// messages through it.
+const mocks = vi.hoisted(() => ({
+ lastJsonMessage: null,
+ setLastJsonMessage: null,
+}));
+
+// Timer.jsx talks to the backend only through react-use-websocket. Mocking it
+// here lets us drive "server" messages directly and assert on Timer's local
+// (optimistic) state updates in isolation, without a real websocket/server.
+// Named useMockWebSocket (not "default") so eslint's react-hooks/rules-of-hooks
+// recognizes the useState/useEffect calls inside as belonging to a hook.
+function useMockWebSocket(_url, options) {
+ const [lastJsonMessage, setLastJsonMessage] = useState(mocks.lastJsonMessage);
+ mocks.setLastJsonMessage = setLastJsonMessage;
+
+ useEffect(() => {
+ options.onOpen?.();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return {
+ sendMessage: vi.fn(),
+ sendJsonMessage: vi.fn(),
+ lastJsonMessage,
+ getWebSocket: () => ({ readyState: 1, close: vi.fn() }),
+ };
+}
+
+vi.mock('react-use-websocket', async () => {
+ const actual = await vi.importActual('react-use-websocket');
+ return {
+ ...actual,
+ default: useMockWebSocket,
+ };
+});
+
+// TimeEntryForm is the actual Log Time form - heavy (its own API calls, child
+// modals, etc.) and not what these tests are about. Stubbing it lets us assert
+// on exactly the one prop Timer.jsx got wrong: the `data` (hours/minutes) it
+// prefills the form with.
+vi.mock('../../Timelog/TimeEntryForm', () => ({
+ // eslint-disable-next-line react/prop-types
+ default: ({ data, isOpen }) =>
+ isOpen ?
{`${data.hours}:${data.minutes}`}
: null,
+}));
+
+const mockStore = configureMockStore([]);
+
+const renderTimer = () => {
+ const store = mockStore({
+ auth: { user: { userid: 'user-1', role: 'Volunteer' } },
+ userProfile: {},
+ });
+
+ // isPopout renders the Countdown (Start/Stop buttons) as soon as the socket
+ // is "open", without needing to click through the header dropdown toggle first.
+ return render(
+
+
+ ,
+ );
+};
+
+let originalPlay;
+let originalPause;
+
+beforeAll(() => {
+ // jsdom has no real
playback; Timer plays a chime element on
+ // mount/modal-open and this avoids noisy "not implemented" errors.
+ originalPlay = window.HTMLMediaElement.prototype.play;
+ originalPause = window.HTMLMediaElement.prototype.pause;
+ window.HTMLMediaElement.prototype.play = vi.fn().mockResolvedValue(undefined);
+ window.HTMLMediaElement.prototype.pause = vi.fn();
+});
+
+afterAll(() => {
+ window.HTMLMediaElement.prototype.play = originalPlay;
+ window.HTMLMediaElement.prototype.pause = originalPause;
+});
+
+beforeEach(() => {
+ mocks.lastJsonMessage = null;
+ mocks.setLastJsonMessage = null;
+});
+
+describe('Timer handleStartButton', () => {
+ it('does not trigger the time-over modal when starting a fresh 1-minute timer', async () => {
+ renderTimer();
+
+ const oneMinute = 60000;
+
+ // Simulate the GET_TIMER response the server sends for a timer that was
+ // just set to a 1-minute goal via the pencil/edit-goal control but has
+ // never been started this session. startAt is deliberately stale (the
+ // server only refreshes it on an actual START_TIMER) - this is the exact
+ // shape that used to trip the bug.
+ act(() => {
+ mocks.setLastJsonMessage({
+ started: false,
+ paused: false,
+ chiming: false,
+ forcedPause: false,
+ weekEndPause: false,
+ time: oneMinute,
+ goal: oneMinute,
+ initialGoal: oneMinute,
+ startAt: Date.now() - 60 * 60 * 1000, // 1 hour stale
+ });
+ });
+
+ const startButton = await screen.findByLabelText('Start timer');
+ fireEvent.click(startButton);
+
+ expect(screen.queryByText('Time Complete!')).not.toBeInTheDocument();
+ });
+});
+
+describe('Timer Log Time form after a reload', () => {
+ it('prefills the Log Time form with the real elapsed time, not 0:0, after a reload', async () => {
+ renderTimer();
+
+ const oneMinute = 60000;
+
+ // Simulate the GET_TIMER response after a page reload for a 1-minute timer
+ // that already ran out and auto-paused itself (started stays true - only an
+ // explicit Stop/Clear sets it false - but paused is true and time is 0).
+ // This is bug #6's exact repro: run a 1-min timer out, close the Time
+ // Complete popup, refresh, then click Log Time.
+ act(() => {
+ mocks.setLastJsonMessage({
+ started: true,
+ paused: true,
+ chiming: true,
+ forcedPause: false,
+ weekEndPause: false,
+ time: 0,
+ goal: oneMinute,
+ initialGoal: oneMinute,
+ startAt: null,
+ });
+ });
+
+ const stopButton = await screen.findByLabelText('Stop timer and log timer');
+ fireEvent.click(stopButton);
+
+ const formData = await screen.findByTestId('time-entry-form-data');
+ expect(formData).toHaveTextContent('0:1');
+ });
+});
diff --git a/src/components/TotalOrgSummary/AccordianWrapper/AccordianWrapper.jsx b/src/components/TotalOrgSummary/AccordianWrapper/AccordianWrapper.jsx
index 37d27133c9d..2ddb7ecac4e 100644
--- a/src/components/TotalOrgSummary/AccordianWrapper/AccordianWrapper.jsx
+++ b/src/components/TotalOrgSummary/AccordianWrapper/AccordianWrapper.jsx
@@ -2,8 +2,11 @@ import Collapsible from 'react-collapsible';
import { useSelector } from 'react-redux';
import styles from './AccordianWrapper.module.css';
-export default function AccordianWrapper({ children, title }) {
+export default function AccordianWrapper({ children, title, section }) {
const darkMode = useSelector(state => state.theme.darkMode);
+ const triggerClasses = `accordian-trigger ${
+ section ? `org-section-header org-section-${section}` : ''
+ } ${darkMode ? 'text-light' : ''}`;
return (
{children}
diff --git a/src/components/TotalOrgSummary/AccordianWrapper/AccordianWrapper.module.css b/src/components/TotalOrgSummary/AccordianWrapper/AccordianWrapper.module.css
index dfafd80e4c4..f8097a69b24 100644
--- a/src/components/TotalOrgSummary/AccordianWrapper/AccordianWrapper.module.css
+++ b/src/components/TotalOrgSummary/AccordianWrapper/AccordianWrapper.module.css
@@ -37,6 +37,58 @@
font-size: 30px;
}
+:global(.Collapsible__trigger.org-section-header) {
+ padding: 8px 12px;
+ border-radius: 10px;
+ font-size: 26px;
+ line-height: 1.3;
+ color: #1b2a41 !important;
+}
+
+:global(.Collapsible__trigger.org-section-volunteer-status) {
+ background-color: #d9eaf7 !important;
+}
+
+:global(.Collapsible__trigger.org-section-volunteer-activities) {
+ background-color: #e5f3db !important;
+}
+
+:global(.Collapsible__trigger.org-section-global-distribution) {
+ background-color: #fbe7cf !important;
+}
+
+:global(.Collapsible__trigger.org-section-volunteer-workload) {
+ background-color: #f4def0 !important;
+}
+
+:global(.Collapsible__trigger.org-section-teams-blue-squares) {
+ background-color: #e3e5ff !important;
+}
+
+:global(.bg-oxford-blue .Collapsible__trigger.org-section-header) {
+ color: #fff !important;
+}
+
+:global(.bg-oxford-blue .Collapsible__trigger.org-section-volunteer-status) {
+ background-color: #214a67 !important;
+}
+
+:global(.bg-oxford-blue .Collapsible__trigger.org-section-volunteer-activities) {
+ background-color: #355334 !important;
+}
+
+:global(.bg-oxford-blue .Collapsible__trigger.org-section-global-distribution) {
+ background-color: #714b27 !important;
+}
+
+:global(.bg-oxford-blue .Collapsible__trigger.org-section-volunteer-workload) {
+ background-color: #653b60 !important;
+}
+
+:global(.bg-oxford-blue .Collapsible__trigger.org-section-teams-blue-squares) {
+ background-color: #5e70b0 !important;
+}
+
:global(.Collapsible__trigger::before) {
display: inline-block;
font-family: FontAwesome, sans-serif;
@@ -48,6 +100,11 @@
font-weight: normal;
}
+:global(.Collapsible__trigger.org-section-header::before) {
+ margin-right: 18px;
+ font-size: 32px;
+}
+
:global(.Collapsible__trigger.is-open::before) {
transform: rotate(180deg);
}
diff --git a/src/components/TotalOrgSummary/HoursCompleted/HoursCompletedBarChart.jsx b/src/components/TotalOrgSummary/HoursCompleted/HoursCompletedBarChart.jsx
index 90b4238a6b3..62cdb8e61be 100644
--- a/src/components/TotalOrgSummary/HoursCompleted/HoursCompletedBarChart.jsx
+++ b/src/components/TotalOrgSummary/HoursCompleted/HoursCompletedBarChart.jsx
@@ -56,8 +56,8 @@ export default function HoursCompletedBarChart({ isLoading, data, darkMode, comp
return raw > 1 ? raw : raw * 100;
};
- const taskPercentage = normalizePercentage(taskHours.submittedToCommittedHoursPercentage);
- const projectPercentage = normalizePercentage(projectHours.submittedToCommittedHoursPercentage);
+ const taskPercentage = taskHours.percentageOfTotal ?? 0;
+ const projectPercentage = projectHours.percentageOfTotal ?? 0;
const taskChangePercentage = normalizePercentage(taskHours.comparisonPercentage);
const projectChangePercentage = normalizePercentage(projectHours.comparisonPercentage);
const stats = [
@@ -185,11 +185,15 @@ export default function HoursCompletedBarChart({ isLoading, data, darkMode, comp
}}
>
{(() => {
- const raw = data.hoursSubmittedToTasksPercentage ?? 0;
- const normalized = raw > 1 ? raw : raw * 100;
- const formatted = `${normalized.toFixed(1)}%`;
- return `${formatted} of Committed Hours Submitted (Tasks)`;
+ const formatted = `${(taskHours.percentageOfTotal ?? 0).toFixed(1)}%`;
+ return `${formatted} of Total Logged Hours (Tasks)`;
})()}
+
+ {(() => {
+ const formatted = `${(projectHours.percentageOfTotal ?? 0).toFixed(1)}%`;
+ return ` | ${formatted} of Total Logged Hours (Projects)`;
+ })()}
+
{(() => {
const raw = data.hoursSubmittedToTasksComparisonPercentage;
if (raw === undefined || raw === null) {
diff --git a/src/components/TotalOrgSummary/TotalOrgSummary.jsx b/src/components/TotalOrgSummary/TotalOrgSummary.jsx
index dfcde5c8109..f6d26d46f0b 100644
--- a/src/components/TotalOrgSummary/TotalOrgSummary.jsx
+++ b/src/components/TotalOrgSummary/TotalOrgSummary.jsx
@@ -223,7 +223,7 @@ function buildPDFStyles(darkMode) {
img, svg { max-width: 100% !important; height: auto !important; page-break-inside: avoid !important; }
.recharts-wrapper { width: 100% !important; height: auto !important; }
table { page-break-inside: avoid !important; }
- .Collapsible__trigger {
+ .Collapsible__trigger:not(.org-section-header) {
background-color: ${darkMode ? '#1C2541' : '#fff'} !important;
color: ${darkMode ? '#fff' : '#000'} !important;
}
@@ -315,6 +315,7 @@ async function generateTotalOrgPdf({ rootRef, darkMode, volunteerStats, isLoadin
);
const originalCanvases = replaceCanvasesWithImages(chartCanvases);
const pdfContainer = buildPDFContainer();
+ pdfContainer.classList.toggle('bg-oxford-blue', darkMode);
const clonedContent = rootRef.current.cloneNode(true);
clonedContent
.querySelectorAll('[data-pdf-hide], .controls, .no-print')
@@ -478,22 +479,15 @@ function TotalOrgSummary(props) {
const [showDatePicker, setShowDatePicker] = useState(false);
const [startDate, setStartDate] = useState(null);
const [endDate, setEndDate] = useState(null);
- /*
- BUG FIX: fromDate/toDate now correctly represent the CURRENT week (see
- calculateStartDate/calculateEndDate above). Since the default label here
- is 'Previous Week', the initial currentFromDate/currentToDate must be
- computed via getPreviousWeekDates(fromDate, toDate) - matching what the
- "Previous Week" dropdown option itself computes - rather than reusing
- fromDate/toDate directly, which would (after the fix above) describe the
- CURRENT week instead and mismatch the 'Previous Week' label on first
- render.
+
+ /* BUG FIX:
+ fromDate/toDate are already the previous week (computed above via getPreviousWeekDates() above).
+ Use them directly as the initial date range to match the 'Previous Week' default label.
*/
- const [currentFromDate, setCurrentFromDate] = useState(
- () => getPreviousWeekDates(fromDate, toDate).start,
- );
- const [currentToDate, setCurrentToDate] = useState(
- () => getPreviousWeekDates(fromDate, toDate).end,
- );
+
+ const [currentFromDate, setCurrentFromDate] = useState(fromDate);
+ const [currentToDate, setCurrentToDate] = useState(toDate);
+
const rootRef = useRef(null);
const cacheRef = useRef({});
@@ -655,7 +649,7 @@ function TotalOrgSummary(props) {
onApply={handleDatePickerSubmit}
/>
-
+
-
+
-
+
Global Volunteer Network: Uniting Communities Worldwide
+ {!isLoading && volunteerStats?.countryCounts && (
+
+
+ All-time contributing countries:{' '}
+ {volunteerStats.countryCounts.allTime}
+
+
+ Currently active countries:{' '}
+ {volunteerStats.countryCounts.active}
+
+
+ )}
-
+
-
+
{
);
// slices are volunteers (2 and 3), not hours split across buckets
- expect(screen.getByText('10-19 hrs: 2 volunteers (40.0%)')).toBeInTheDocument();
- expect(screen.getByText('20-29 hrs: 3 volunteers (60.0%)')).toBeInTheDocument();
+ expect(screen.getByText('0-10 hrs: 2 volunteers (40.0%)')).toBeInTheDocument();
+ expect(screen.getByText('Over 10 to 20 hrs: 3 volunteers (60.0%)')).toBeInTheDocument();
// centre still shows the headline total hours
expect(screen.getByText('1234')).toBeInTheDocument();
const computed = computeDistribution(hoursData, totalHoursData);
expect(computed).toEqual({
userData: [
- { name: '10-19 hrs', value: 2, percentage: 40, valueType: 'volunteers' },
- { name: '20-29 hrs', value: 3, percentage: 60, valueType: 'volunteers' },
+ { name: '0-10 hrs', value: 2, percentage: 40, valueType: 'volunteers' },
+ { name: 'Over 10 to 20 hrs', value: 3, percentage: 60, valueType: 'volunteers' },
],
totalVolunteers: 5,
totalHoursWorked: 1234,
@@ -76,7 +76,7 @@ describe('VolunteerHoursDistribution wrapper', () => {
);
expect(screen.getByText('Weekly Committed Hours')).toBeInTheDocument();
- expect(screen.getByText('40 hrs: 1 volunteer (12.5%)')).toBeInTheDocument();
+ expect(screen.getByText('Over 30 to 40 hrs: 1 volunteer (12.5%)')).toBeInTheDocument();
expect(screen.getByText('Over 40 hrs: 1 volunteer (12.5%)')).toBeInTheDocument();
expect(screen.getByText('TOTAL')).toBeInTheDocument();
expect(screen.getByText('VOLUNTEERS')).toBeInTheDocument();
@@ -84,10 +84,10 @@ describe('VolunteerHoursDistribution wrapper', () => {
expect(computeDistribution(committedHoursData, undefined, true)).toEqual({
userData: [
- { name: '10-19 hrs', value: 2, percentage: 25, valueType: 'volunteers' },
- { name: '20-29 hrs', value: 3, percentage: 38, valueType: 'volunteers' },
- { name: '30-39 hrs', value: 1, percentage: 13, valueType: 'volunteers' },
- { name: '40 hrs', value: 1, percentage: 13, valueType: 'volunteers' },
+ { name: '0-10 hrs', value: 2, percentage: 25, valueType: 'volunteers' },
+ { name: 'Over 10 to 20 hrs', value: 3, percentage: 38, valueType: 'volunteers' },
+ { name: 'Over 20 to 30 hrs', value: 1, percentage: 13, valueType: 'volunteers' },
+ { name: 'Over 30 to 40 hrs', value: 1, percentage: 13, valueType: 'volunteers' },
{ name: 'Over 40 hrs', value: 1, percentage: 13, valueType: 'volunteers' },
],
totalVolunteers: 8,
@@ -123,6 +123,6 @@ describe('bucket ordering', () => {
],
{ current: 999 },
);
- expect(userData.map(d => d.name)).toEqual(['10-19 hrs', '50-59 hrs', '50+ hrs']);
+ expect(userData.map(d => d.name)).toEqual(['0-10 hrs', 'Over 40 to 50 hrs', 'Over 50 hrs']);
});
});
diff --git a/src/components/TotalOrgSummary/VolunteerHoursDistribution/__tests__/formatRangeLabel.test.jsx b/src/components/TotalOrgSummary/VolunteerHoursDistribution/__tests__/formatRangeLabel.test.jsx
index fc416204c11..dc6ecb4adc9 100644
--- a/src/components/TotalOrgSummary/VolunteerHoursDistribution/__tests__/formatRangeLabel.test.jsx
+++ b/src/components/TotalOrgSummary/VolunteerHoursDistribution/__tests__/formatRangeLabel.test.jsx
@@ -1,16 +1,17 @@
import { formatRangeLabel } from '../VolunteerHoursDistribution';
describe('formatRangeLabel helper', () => {
- it('formats simple numeric ranges correctly', () => {
- expect(formatRangeLabel('10')).toBe('10-19 hrs');
- expect(formatRangeLabel('20')).toBe('20-29 hrs');
- expect(formatRangeLabel('30')).toBe('30-39 hrs');
- expect(formatRangeLabel('40')).toBe('40-49 hrs');
+ it('formats backend upper-limit buckets correctly', () => {
+ expect(formatRangeLabel('10')).toBe('0-10 hrs');
+ expect(formatRangeLabel('20')).toBe('Over 10 to 20 hrs');
+ expect(formatRangeLabel('30')).toBe('Over 20 to 30 hrs');
+ expect(formatRangeLabel('40')).toBe('Over 30 to 40 hrs');
+ expect(formatRangeLabel('50')).toBe('Over 40 to 50 hrs');
});
it('formats plus ranges correctly', () => {
- expect(formatRangeLabel('40+')).toBe('40+ hrs');
- expect(formatRangeLabel('50+')).toBe('50+ hrs');
+ expect(formatRangeLabel('40+')).toBe('Over 40 hrs');
+ expect(formatRangeLabel('50+')).toBe('Over 50 hrs');
});
it('handles empty or undefined input gracefully', () => {
@@ -18,4 +19,12 @@ describe('formatRangeLabel helper', () => {
expect(formatRangeLabel(null)).toBe('');
expect(formatRangeLabel(undefined)).toBe('');
});
+
+ it('labels cover fractional weekly averages placed in each bucket', () => {
+ // Backend assignToBucket does not round: 10.5 -> '20', 20.5 -> '30', 30.5 -> '40', 40.5 -> '50'
+ expect(formatRangeLabel('20')).toBe('Over 10 to 20 hrs');
+ expect(formatRangeLabel('30')).toBe('Over 20 to 30 hrs');
+ expect(formatRangeLabel('40')).toBe('Over 30 to 40 hrs');
+ expect(formatRangeLabel('50')).toBe('Over 40 to 50 hrs');
+ });
});
diff --git a/src/components/UserManagement/UserSearchPanel.jsx b/src/components/UserManagement/UserSearchPanel.jsx
index 9a815f74aa7..0611ca5d8d0 100644
--- a/src/components/UserManagement/UserSearchPanel.jsx
+++ b/src/components/UserManagement/UserSearchPanel.jsx
@@ -7,6 +7,7 @@ import { Tooltip, OverlayTrigger } from 'react-bootstrap';
import { boxStyle, boxStyleDark } from '../../styles';
import hasPermission from '../../utils/permissions';
import { SEARCH, SHOW, CREATE_NEW_USER, SEND_SETUP_LINK } from '../../languages/en/ui';
+import appStyles from '~/App.module.css';
import styles from './usermanagement.module.css';
import { permissions } from '../../utils/constants';
@@ -31,6 +32,8 @@ function UserSearchPanel({
const canCreateUsers = hasPermission(permissions.postUserProfile);
const [tooltipCreateNewUserOpen, setTooltipCreateNewUserOpen] = useState(false);
const toggleCreateNewUserTooltip = () => setTooltipCreateNewUserOpen(!tooltipCreateNewUserOpen);
+ const labelDarkClass = darkMode ? `${appStyles['bg-yinmn-blue']} text-light` : '';
+ const inputDarkClass = darkMode ? `${appStyles['bg-darkmode-liblack']} text-light` : '';
return (
-
+
{SEARCH}
-
+
{SHOW}
All
Active
diff --git a/src/components/UserProfile/EditableModal/EditableInfoModal.jsx b/src/components/UserProfile/EditableModal/EditableInfoModal.jsx
index aad4c037f33..4b827ba25dc 100644
--- a/src/components/UserProfile/EditableModal/EditableInfoModal.jsx
+++ b/src/components/UserProfile/EditableModal/EditableInfoModal.jsx
@@ -13,6 +13,7 @@ import {
} from '../../../actions/information';
import { boxStyle, boxStyleDark } from '~/styles';
import RichTextEditor from './RichTextEditor';
+import appStyles from '~/App.module.css';
import styles from './EditableInfoModal.module.css';
const options = [
@@ -257,6 +258,7 @@ export class EditableInfoModal extends Component {
render() {
const { infoContent, editableModalOpen, fontSize, CanRead, CanEdit } = this.state;
const { darkMode } = this.props;
+ const headerDarkClass = darkMode ? `${appStyles['bg-space-cadet']} text-light` : '';
const sanitizedContent = infoContent;
@@ -280,11 +282,11 @@ export class EditableInfoModal extends Component {
size="lg"
className={darkMode ? 'text-light' : ''}
>
-
+
Welcome to the {this.sanitizeText(this.props.areaTitle)} Information Page!
-
+
{this.state.editing ? (
-
+
{titles.map(title => {
- const titleTeamCode = title.teamCode?.trim();
- const isTeamCodeInList =!titleTeamCode||teamCodes.some(code => code.value?.trim() === titleTeamCode);
+ const normalizeTeamCode = (value) =>
+ typeof value === 'string' ? value.trim().toLowerCase() : '';
+
+ const titleTeamCode = normalizeTeamCode(title.teamCode);
+ const isTeamCodeInList =
+ !titleTeamCode ||
+ teamCodes.some(
+ (code) => normalizeTeamCode(code.value) === titleTeamCode
+ );
+
return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus
{
+ if (!value) return 'Date unavailable';
+ const date = new Date(value);
+ return Number.isNaN(date.getTime()) ? 'Date unavailable' : date.toLocaleString();
+};
+
+function RehireableStatusControl({
+ isRehireable,
+ notRehireableReason,
+ notRehireableReasonHistory,
+ darkMode,
+ onConfirm,
+ showLabel,
+}) {
+ const [isOpen, setIsOpen] = useState(false);
+ const [mode, setMode] = useState('submit');
const [reason, setReason] = useState('');
+ const [error, setError] = useState('');
const hasReason =
!isRehireable &&
typeof notRehireableReason === 'string' &&
notRehireableReason.trim().length > 0;
- const handleChange = () => {
- const nextStatus = !isRehireable;
- setPendingRehireableStatus(nextStatus);
+ const openControl = () => {
+ setError('');
setReason('');
- setShowConfirmDialog(true);
+ if (isRehireable) {
+ setMode('submit');
+ } else if (hasReason) {
+ setMode('details');
+ } else {
+ // Older records may have a not-rehireable status without a saved reason.
+ setMode('edit');
+ }
+ setIsOpen(true);
};
- const handleConfirm = () => {
- setShowConfirmDialog(false);
- onConfirm(pendingRehireableStatus, reason.trim());
+ const handleConfirm = async () => {
+ if (mode === 'submit' || mode === 'edit') {
+ const trimmedReason = reason.trim();
+ if (!trimmedReason) {
+ setError('A reason is required.');
+ return;
+ }
+ const saved = await onConfirm(false, trimmedReason);
+ if (saved === false) return;
+ } else if (mode === 'confirmRehireable') {
+ const saved = await onConfirm(true, '');
+ if (saved === false) return;
+ }
+ setIsOpen(false);
};
- const handleKeyDown = event => {
- if (event.key === 'Enter' || event.key === ' ') {
- event.preventDefault();
- handleChange();
- }
+ const startEdit = () => {
+ setReason(notRehireableReason || '');
+ setError('');
+ setMode('edit');
};
+ const startRehireableConfirmation = () => {
+ setError('');
+ setMode('confirmRehireable');
+ };
+
+ const title = {
+ submit: 'Confirm Status Change',
+ details: 'Not Rehireable Details',
+ edit: 'Edit Not Rehireable Reason',
+ confirmRehireable: 'Confirm Status Change',
+ }[mode];
+
return (
<>
- Rehireable }
+
+ >
+
+
setShowConfirmDialog(false)}
+ isOpen={isOpen}
+ toggle={() => setIsOpen(false)}
className={darkMode ? 'text-light dark-mode' : ''}
>
setShowConfirmDialog(false)}
+ toggle={() => setIsOpen(false)}
className={darkMode ? 'bg-space-cadet' : ''}
>
- Confirm Status Change
+ {title}
- {pendingRehireableStatus ? (
- 'Are you sure you want to change the user status to Rehireable?'
- ) : (
+ {mode === 'details' && (
<>
- You are about to change this user's status to 'not rehireable'. Please
- enter the reason below.
+ Reason: {notRehireableReason}
- Reason (optional)
+ Reason history
+ {notRehireableReasonHistory.length > 0 ? (
+
+ {[...notRehireableReasonHistory].reverse().map((entry, index) => {
+ const actor = entry.changedBy || {};
+ const actorName = [actor.firstName, actor.lastName].filter(Boolean).join(' ');
+ const actionLabel = entry.action === 'edited' ? 'Reason edited by' : 'Reason submitted by';
+ return (
+
+
+ {actionLabel} {actorName || 'Unknown user'}
+
+ {actor.username && Username: {actor.username}
}
+ {formatHistoryDate(entry.changedAt || entry.date)}
+ {entry.reason}
+
+ );
+ })}
+
+ ) : (
+ No change history is available for this saved reason.
+ )}
+ >
+ )}
+ {(mode === 'submit' || mode === 'edit') && (
+ <>
+
+ {mode === 'edit'
+ ? 'Update the reason for this not-rehireable status.'
+ : "You are about to change this user's status to 'not rehireable'. Enter the required reason."}
+
+ Reason (required)
setReason(event.target.value)}
+ onChange={event => {
+ setReason(event.target.value);
+ if (event.target.value.trim()) setError('');
+ }}
/>
+ {error && {error}
}
>
)}
+ {mode === 'confirmRehireable' &&
+ 'Are you sure you want to change the user status to Rehireable?'}
-
- Confirm
- {' '}
- setShowConfirmDialog(false)}>
- Cancel
-
+ {mode === 'details' ? (
+ <>
+
+ Edit reason
+ {' '}
+
+ Mark Rehireable
+ {' '}
+ setIsOpen(false)}>
+ Close
+
+ >
+ ) : (
+ <>
+
+ Confirm
+ {' '}
+ setIsOpen(false)}>
+ Cancel
+
+ >
+ )}
>
@@ -105,14 +211,27 @@ function RehireableStatusControl({ isRehireable, notRehireableReason, darkMode,
RehireableStatusControl.propTypes = {
isRehireable: PropTypes.bool,
notRehireableReason: PropTypes.string,
+ notRehireableReasonHistory: PropTypes.arrayOf(PropTypes.shape({
+ action: PropTypes.string,
+ reason: PropTypes.string,
+ changedAt: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]),
+ changedBy: PropTypes.shape({
+ firstName: PropTypes.string,
+ lastName: PropTypes.string,
+ username: PropTypes.string,
+ }),
+ })),
darkMode: PropTypes.bool,
onConfirm: PropTypes.func.isRequired,
+ showLabel: PropTypes.bool,
};
RehireableStatusControl.defaultProps = {
isRehireable: true,
notRehireableReason: '',
+ notRehireableReasonHistory: [],
darkMode: false,
+ showLabel: false,
};
-export default RehireableStatusControl;
\ No newline at end of file
+export default RehireableStatusControl;
diff --git a/src/components/UserProfile/TeamsAndProjects/UserTeamsTable.jsx b/src/components/UserProfile/TeamsAndProjects/UserTeamsTable.jsx
index e04dd5a0230..63609310bc1 100644
--- a/src/components/UserProfile/TeamsAndProjects/UserTeamsTable.jsx
+++ b/src/components/UserProfile/TeamsAndProjects/UserTeamsTable.jsx
@@ -2,12 +2,10 @@ import { useEffect, useRef, useState } from 'react';
import { Button, Col, Input } from 'reactstrap';
import hasPermission from '../../../utils/permissions';
import './TeamsAndProjects.module.css';
-// import styles from './UserTeamsTable.css';
+import styles from './UserTeamsTable.module.css';
import { connect } from 'react-redux';
import { boxStyle } from '~/styles';
-import './UserTeamsTable.module.css';
-
import { AutoCompleteTeamCode } from './AutoCompleteTeamCode';
import ToggleSwitch from '../UserProfileEdit/ToggleSwitch';
@@ -278,17 +276,38 @@ const UserTeamsTable = props => {
{props.role && (
-
- #
- Team Name
- {canAssignTeamToUsers ? (
- <>
- Members
-
- {}
-
- >
- ) : null}
+
+
+ #
+
+
+ Team Name
+
+
+ {canAssignTeamToUsers ? (
+ <>
+
+ Members
+
+
+
+ {/* Keep the existing header content here */}
+
+ >
+ ) : null}
+
)}
@@ -296,13 +315,11 @@ const UserTeamsTable = props => {
{teams.length > 0 ? (
teams.map((team, index) => (
- {index + 1}
- {`${team.teamName}`}
+ {index + 1}
+ {`${team.teamName}`}
{props.edit && props.role && (
<>
-
+
{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
+ textAlign: 'center',
}}
>
{
onChange={props.onChange}
defaultValue={props.selected}
>
- {Object.keys(timeZoneMap).map(timeZoneName => {
+ {Object.keys(timeZoneMap)
+ .sort((a, b) => a.localeCompare(b))
+ .map(timeZoneName => {
const timeZoneString = `${timeZoneName} (UTC${timeZoneMap[timeZoneName].utcOffset})`;
if (
!props.filter ||
diff --git a/src/components/UserProfile/TimeZoneDropDown/__tests__/TimeZoneDropDown.test.jsx b/src/components/UserProfile/TimeZoneDropDown/__tests__/TimeZoneDropDown.test.jsx
index 46bc172e8f7..ecbcdecd530 100644
--- a/src/components/UserProfile/TimeZoneDropDown/__tests__/TimeZoneDropDown.test.jsx
+++ b/src/components/UserProfile/TimeZoneDropDown/__tests__/TimeZoneDropDown.test.jsx
@@ -54,6 +54,12 @@ describe('TimeZoneDropDown Component', () => {
test('handles invalid time zone names provided', () => {
render( );
- expect(screen.getByTestId('time_zone_dropdown')).toHaveValue('Asia/Kabul');
+ expect(screen.getByTestId('time_zone_dropdown')).toHaveValue('Africa/Abidjan');
+ });
+
+ test("lists time zones in alphabetical order", () => {
+ render( );
+ const values = screen.getAllByTestId("time_zone_option").map(o => o.value);
+ expect(values).toEqual([...values].sort((a, b) => a.localeCompare(b)));
});
});
diff --git a/src/components/UserProfile/UserProfile.jsx b/src/components/UserProfile/UserProfile.jsx
index b40e4778f41..c6c839a175a 100644
--- a/src/components/UserProfile/UserProfile.jsx
+++ b/src/components/UserProfile/UserProfile.jsx
@@ -1138,19 +1138,25 @@ setUpdatedTasks(prev => {
...userProfile,
isRehireable: newRehireableStatus,
};
- const savedReason = !newRehireableStatus ? notRehireableReason.trim() : '';
+ const savedReason =
+ !newRehireableStatus && typeof notRehireableReason === 'string'
+ ? notRehireableReason.trim()
+ : '';
if (savedReason) updatedUserProfile.notRehireableReason = savedReason;
else delete updatedUserProfile.notRehireableReason;
try {
- await dispatch(
+ const savedUserProfile = await dispatch(
updateRehireableStatus(updatedUserProfile, newRehireableStatus, savedReason),
);
setIsRehireable(newRehireableStatus);
- setUserProfile(updatedUserProfile);
- setOriginalUserProfile(updatedUserProfile);
+ const profileWithSavedHistory = savedUserProfile || updatedUserProfile;
+ setUserProfile(profileWithSavedHistory);
+ setOriginalUserProfile(profileWithSavedHistory);
+ return true;
} catch (error) {
toast.error('Unable change rehireable status');
+ return false;
}
};
@@ -1576,6 +1582,7 @@ setUpdatedTasks(prev => {
diff --git a/src/components/UserProfile/VolunteeringTimeTab/VolunteeringTimeTab.jsx b/src/components/UserProfile/VolunteeringTimeTab/VolunteeringTimeTab.jsx
index e6b5fe9c848..37a7be8b927 100644
--- a/src/components/UserProfile/VolunteeringTimeTab/VolunteeringTimeTab.jsx
+++ b/src/components/UserProfile/VolunteeringTimeTab/VolunteeringTimeTab.jsx
@@ -13,6 +13,12 @@ const MINIMUM_WEEK_HOURS = 0;
const MAXIMUM_WEEK_HOURS = 168;
const darkModeClass = "bg-darkmode-liblack text-light border-0";
+const blurHoursOnWheel = event => {
+ // Scrolling toward Save must not step a focused number input's hours.
+ // Blur instead of cancelling the event so the page can still scroll.
+ event.currentTarget.blur();
+};
+
const startEndDateValidation = props => {
return (
props.userProfile.startDate > props.userProfile.endDate && props.userProfile.endDate !== ''
@@ -194,6 +200,7 @@ const WeeklyCommittedHours = props => {
id="weeklycommittedHours"
data-testid="weeklycommittedHours"
value={props.userProfile.weeklycommittedHours}
+ onWheel={blurHoursOnWheel}
onChange={e => handleChange(e)}
placeholder="Weekly Committed Hours"
className={darkMode ? darkModeClass : ''}
@@ -214,6 +221,7 @@ const MissedHours = props => {
id="missedHours"
data-testid="missedHours"
value={props.userProfile.missedHours ?? 0}
+ onWheel={blurHoursOnWheel}
className={darkMode ? darkModeClass : ''}
onChange={e => {
props.setUserProfile({
@@ -240,6 +248,7 @@ const TotalIntangibleHours = props => {
step=".01"
data-testid="totalIntangibleHours"
value={props.userProfile.totalIntangibleHrs ?? 0}
+ onWheel={blurHoursOnWheel}
className={darkMode ? darkModeClass : ''}
onChange={e => {
const newValue = Math.max(Number(e.target.value), 0).toFixed(2);
@@ -560,6 +569,7 @@ const ViewTab = props => {
min="0"
className={darkMode ? darkModeClass : ''}
value={roundToTwo(userProfile.hoursByCategory[key])}
+ onWheel={blurHoursOnWheel}
onChange={e => handleOnChangeHours(e, key)}
placeholder={`Total Tangible ${capitalize(key)} Hours`}
/>
diff --git a/src/components/UserProfile/VolunteeringTimeTab/__tests__/VolunteeringTimeTab.hours.test.jsx b/src/components/UserProfile/VolunteeringTimeTab/__tests__/VolunteeringTimeTab.hours.test.jsx
new file mode 100644
index 00000000000..021d56891d1
--- /dev/null
+++ b/src/components/UserProfile/VolunteeringTimeTab/__tests__/VolunteeringTimeTab.hours.test.jsx
@@ -0,0 +1,123 @@
+import React, { useState } from 'react';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import axios from 'axios';
+import VolunteeringTimeTab from '../VolunteeringTimeTab';
+import { updateUserProfile } from '../../../../actions/userProfile';
+import { ENDPOINTS } from '../../../../utils/URL';
+
+vi.mock('axios');
+
+const initialProfile = {
+ _id: 'volunteer-fixture',
+ firstName: 'Fixture',
+ role: 'Core Team',
+ createdDate: '2026-01-01',
+ startDate: '2026-01-04',
+ endDate: '',
+ weeklycommittedHours: 20,
+ missedHours: 2,
+ totalIntangibleHrs: 4.25,
+ hoursByCategory: { education: 8.5, society: 6 },
+ weeklycommittedHoursHistory: [],
+};
+
+function Fixture({ canEdit = true, darkMode = false }) {
+ const [profile, setProfile] = useState(initialProfile);
+ const requestorRole = 'Administrator';
+ const store = {
+ getState: () => ({ theme: { darkMode } }),
+ subscribe: () => () => {},
+ dispatch: vi.fn(),
+ };
+ return (
+
+
+ updateUserProfile(profile)(store.dispatch)}>
+ Save fixture
+
+
+ );
+}
+
+beforeEach(() => {
+ vi.clearAllMocks();
+ axios.get.mockResolvedValue({ data: [] });
+ axios.put.mockResolvedValue({ status: 200 });
+});
+
+describe('Profile hours scroll protection', () => {
+ it.each([
+ 'Weekly Committed Hours',
+ 'Additional Make-up Hours This Week',
+ 'Total Intangible Hours',
+ 'Total Tangible Education Hours',
+ 'Total Tangible Society Hours',
+ ])('blurs %s on wheel without cancelling page scrolling or changing hours', async placeholder => {
+ render( );
+ const input = screen.getByPlaceholderText(placeholder);
+ const value = input.value;
+ input.focus();
+ expect(input).toHaveFocus();
+ // jsdom does not implement native wheel stepping; Chromium covers that separately.
+ expect(fireEvent.wheel(input, { deltaY: 120, cancelable: true })).toBe(true);
+ expect(input).not.toHaveFocus();
+ expect(input.value).toBe(value);
+ fireEvent.click(screen.getByRole('button', { name: 'Save fixture' }));
+ await waitFor(() => expect(axios.put).toHaveBeenCalledWith(
+ ENDPOINTS.USER_PROFILE(initialProfile._id), initialProfile,
+ ));
+ });
+
+ it.each([false, true])('saves the entered weekly hours for the target volunteer (dark=%s)', async darkMode => {
+ render( );
+ const input = screen.getByPlaceholderText('Weekly Committed Hours');
+ fireEvent.change(input, { target: { value: '21' } });
+ input.focus();
+ fireEvent.wheel(input, { deltaY: -120 });
+ fireEvent.click(screen.getByRole('button', { name: 'Save fixture' }));
+ await waitFor(() => expect(axios.put).toHaveBeenCalledWith(
+ ENDPOINTS.USER_PROFILE(initialProfile._id),
+ { ...initialProfile, weeklycommittedHours: 21 },
+ ));
+ });
+
+ it('preserves intentional decimal edits to intangible and category hours', async () => {
+ render( );
+ fireEvent.change(screen.getByPlaceholderText('Total Intangible Hours'), { target: { value: '4.75' } });
+ fireEvent.change(screen.getByPlaceholderText('Total Tangible Education Hours'), { target: { value: '9.25' } });
+ fireEvent.click(screen.getByRole('button', { name: 'Save fixture' }));
+ await waitFor(() => expect(axios.put).toHaveBeenCalledWith(
+ ENDPOINTS.USER_PROFILE(initialProfile._id),
+ { ...initialProfile, totalIntangibleHrs: 4.75, hoursByCategory: { education: 9.25, society: 6 } },
+ ));
+ });
+
+ it('keeps read-only hours non-editable', () => {
+ render( );
+ expect(screen.queryByRole('spinbutton')).not.toBeInTheDocument();
+ expect(axios.put).not.toHaveBeenCalled();
+ });
+
+ it.each([0, 168])('preserves the valid weekly-hours boundary %s when scrolling', async hours => {
+ render( );
+ const input = screen.getByPlaceholderText('Weekly Committed Hours');
+ fireEvent.change(input, { target: { value: String(hours) } });
+ input.focus();
+ fireEvent.wheel(input, { deltaY: 120 });
+ fireEvent.click(screen.getByRole('button', { name: 'Save fixture' }));
+ await waitFor(() => expect(axios.put).toHaveBeenCalledWith(
+ ENDPOINTS.USER_PROFILE(initialProfile._id),
+ { ...initialProfile, weeklycommittedHours: hours },
+ ));
+ });
+});
diff --git a/src/components/UserProfile/__tests__/RehireableStatusControl.test.jsx b/src/components/UserProfile/__tests__/RehireableStatusControl.test.jsx
index 1d3b2ab08e0..46ac5166568 100644
--- a/src/components/UserProfile/__tests__/RehireableStatusControl.test.jsx
+++ b/src/components/UserProfile/__tests__/RehireableStatusControl.test.jsx
@@ -2,29 +2,21 @@ import { fireEvent, render, screen } from '@testing-library/react';
import RehireableStatusControl from '../RehireableStatusControl';
describe('RehireableStatusControl', () => {
- it('confirms a non-empty optional reason', () => {
+ it('requires a nonblank reason before confirming a not-rehireable status', () => {
const onConfirm = vi.fn();
render( );
fireEvent.click(screen.getByRole('button', { name: 'Rehireable' }));
- expect(screen.getByText(/please enter the reason below/i)).toBeInTheDocument();
+ expect(screen.getByText(/enter the required reason/i)).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Confirm' })).toBeDisabled();
+
fireEvent.change(screen.getByRole('textbox', { name: /reason for not rehireable status/i }), {
target: { value: ' Not a good fit ' },
});
- fireEvent.click(screen.getByRole('button', { name: 'Confirm' }));
-
- expect(onConfirm).toHaveBeenCalledWith(false, 'Not a good fit');
- });
-
- it('allows confirmation with an empty reason', () => {
- const onConfirm = vi.fn();
- render( );
-
- fireEvent.click(screen.getByRole('button', { name: 'Rehireable' }));
expect(screen.getByRole('button', { name: 'Confirm' })).toBeEnabled();
fireEvent.click(screen.getByRole('button', { name: 'Confirm' }));
- expect(onConfirm).toHaveBeenCalledWith(false, '');
+ expect(onConfirm).toHaveBeenCalledWith(false, 'Not a good fit');
});
it('does not change status or save reason when cancelled', () => {
@@ -40,65 +32,101 @@ describe('RehireableStatusControl', () => {
expect(onConfirm).not.toHaveBeenCalled();
});
- it('renders each rehireable visual state from persisted props', () => {
+ it('opens the saved reason and audit history from the orange status control', () => {
const onConfirm = vi.fn();
- const { rerender } = render(
-
-
-
,
- );
- const checkedControl = screen.getByRole('button', { name: 'Rehireable' });
- expect(checkedControl).toHaveClass('fa-check-square-o');
- expect(checkedControl.style.border).toBe('');
-
- rerender(
-
-
-
,
- );
- const uncheckedControl = screen.getByRole('button', { name: 'Not rehireable' });
- expect(uncheckedControl).toHaveClass('fa-square-o');
- expect(uncheckedControl.title).toBe('Click to change rehirable status');
- expect(uncheckedControl.style.border).toBe('');
-
- rerender(
-
-
-
,
- );
- const whitespaceReasonControl = screen.getByRole('button', { name: 'Not rehireable' });
- expect(whitespaceReasonControl).toHaveClass('fa-square-o');
- expect(whitespaceReasonControl.title).toBe('Click to change rehirable status');
- expect(whitespaceReasonControl.style.border).toBe('');
-
- rerender(
-
-
-
,
+ render(
+ ,
);
+
const control = screen.getByRole('button', { name: 'Not rehireable' });
- expect(control.className).not.toContain('fa-square-o');
- expect(control.title).toBe('Saved reason');
expect(control.style.border).toBe('2px solid rgb(240, 140, 0)');
- expect(window.getComputedStyle(control).borderColor).toBe('rgb(240, 140, 0)');
- expect(window.getComputedStyle(control).borderStyle).toBe('solid');
- expect(window.getComputedStyle(control).borderWidth).toBe('2px');
-
fireEvent.click(control);
- expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
+
+ expect(screen.getByRole('heading', { name: 'Not Rehireable Details' })).toBeInTheDocument();
+ expect(screen.getAllByText('Saved reason')).toHaveLength(2);
+ expect(
+ screen.getByText((_, element) => element?.textContent === 'Reason submitted by Alex Admin'),
+ ).toBeInTheDocument();
+ expect(screen.getByText('Username: admin@example.org')).toBeInTheDocument();
+ expect(screen.getByText(new Date('2026-01-02T03:04:00.000Z').toLocaleString())).toBeInTheDocument();
+ });
+
+ it('opens edit mode and submits the updated reason', () => {
+ const onConfirm = vi.fn();
+ render(
+ ,
+ );
+
+ fireEvent.click(screen.getByRole('button', { name: 'Not rehireable' }));
+ fireEvent.click(screen.getByRole('button', { name: 'Edit reason' }));
+ const reasonInput = screen.getByRole('textbox', { name: /reason for not rehireable status/i });
+ expect(reasonInput).toHaveValue('Old reason');
+ fireEvent.change(reasonInput, { target: { value: ' Corrected reason ' } });
+ fireEvent.click(screen.getByRole('button', { name: 'Confirm' }));
+
+ expect(onConfirm).toHaveBeenCalledWith(false, 'Corrected reason');
+ });
+
+ it('allows a user to mark the profile rehireable from the reason details', () => {
+ const onConfirm = vi.fn();
+ render(
+ ,
+ );
+
+ fireEvent.click(screen.getByRole('button', { name: 'Not rehireable' }));
+ fireEvent.click(screen.getByRole('button', { name: 'Mark Rehireable' }));
fireEvent.click(screen.getByRole('button', { name: 'Confirm' }));
+
expect(onConfirm).toHaveBeenCalledWith(true, '');
});
-});
\ No newline at end of file
+
+ it('opens an edit prompt for legacy not-rehireable records without a saved reason', () => {
+ const onConfirm = vi.fn();
+ render( );
+
+ fireEvent.click(screen.getByRole('button', { name: 'Not rehireable' }));
+
+ expect(screen.getByRole('heading', { name: 'Edit Not Rehireable Reason' })).toBeInTheDocument();
+ expect(screen.getByRole('textbox', { name: /reason for not rehireable status/i })).toBeInTheDocument();
+ });
+
+ it('applies dark-mode classes to the details modal', () => {
+ render(
+ ,
+ );
+
+ fireEvent.click(screen.getByRole('button', { name: 'Not rehireable' }));
+
+ expect(screen.getByRole('document')).toHaveClass('text-light', 'dark-mode');
+ expect(screen.getByText('Reason history')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/WeeklySummariesReport/BioFunction.jsx b/src/components/WeeklySummariesReport/BioFunction.jsx
index 5b580db97de..f8fbb95e4cc 100644
--- a/src/components/WeeklySummariesReport/BioFunction.jsx
+++ b/src/components/WeeklySummariesReport/BioFunction.jsx
@@ -9,7 +9,7 @@ function BioFunction(props) {
const {
bioPosted,
totalTangibleHrs,
- daysInTeam,
+ weeklySummariesCount,
textColors,
summary,
bioCanEdit,
@@ -19,7 +19,7 @@ function BioFunction(props) {
const [bioStatus, setBioStatus] = useState(bioPosted);
- const isMeetCriteria = isQualifiedForBio({ totalTangibleHrs, daysInTeam, bioPosted });
+ const isMeetCriteria = isQualifiedForBio({ totalTangibleHrs, weeklySummariesCount, bioPosted });
const style = {
color: textColors[summary?.weeklySummaryOption] || textColors.Default,
};
@@ -59,7 +59,7 @@ function BioFunction(props) {
BioFunction.propTypes = {
bioPosted: PropTypes.string,
totalTangibleHrs: PropTypes.number,
- daysInTeam: PropTypes.number,
+ weeklySummariesCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
textColors: PropTypes.object,
summary: PropTypes.object,
bioCanEdit: PropTypes.bool,
diff --git a/src/components/WeeklySummariesReport/FormattedReport.jsx b/src/components/WeeklySummariesReport/FormattedReport.jsx
index 94f7fbd808c..de785d1cd9c 100644
--- a/src/components/WeeklySummariesReport/FormattedReport.jsx
+++ b/src/components/WeeklySummariesReport/FormattedReport.jsx
@@ -50,6 +50,7 @@ import hasPermission, { cantUpdateDevAdminDetails } from '../../utils/permission
import GoogleDocIcon from '../common/GoogleDocIcon';
import TriStateToggleSwitch from '../UserProfile/UserProfileEdit/ToggleSwitch/TriStateToggleSwitch';
import styles from './WeeklySummariesReport.module.scss';
+import getActiveTimeOffRequest from './getActiveTimeOffRequest';
const TZ = 'America/Los_Angeles';
@@ -95,6 +96,7 @@ function FormattedReport({
handleTeamCodeChange,
handleBioStatusChange,
handleSpecialColorDotClick,
+ timeOffRequests,
}) {
const dispatch = useDispatch();
const isEditCount = dispatch(hasPermission(permissions.totalValidWeeklySummaries));
@@ -123,6 +125,24 @@ function FormattedReport({
);
}
const loggedInUserEmail = auth?.user?.email ? auth.user.email : '';
+ const activeTeamCodes = [
+ ...new Set(
+ summaries
+ .filter(summary => {
+ if (
+ !summary ||
+ !Array.isArray(summary.totalSeconds) ||
+ !Array.isArray(summary.promisedHoursByWeek)
+ ) {
+ return false;
+ }
+
+ return shouldShowSummaryOnTab(summary, weekIndex);
+ })
+ .map(summary => (typeof summary.teamCode === 'string' ? summary.teamCode.trim() : ''))
+ .filter(Boolean),
+ ),
+ ].sort((a, b) => a.localeCompare(b));
return (
<>
@@ -147,6 +167,7 @@ function FormattedReport({
key={summary._id}
summary={summary}
weekIndex={weekIndex}
+ activeTeamCodes={activeTeamCodes}
bioCanEdit={bioCanEdit}
canEditSummaryCount={isEditCount}
allRoleInfo={allRoleInfo}
@@ -161,6 +182,7 @@ function FormattedReport({
auth={auth}
handleSpecialColorDotClick={handleSpecialColorDotClick}
isFinalWeek={isFinalWeek}
+ timeOffRequests={timeOffRequests}
/>
);
})}
@@ -265,6 +287,7 @@ function getTextColorForHoursLogged(hoursLogged, promisedHours, darkMode) {
function ReportDetails({
summary,
weekIndex,
+ activeTeamCodes,
bioCanEdit,
canEditSummaryCount,
allRoleInfo,
@@ -280,6 +303,7 @@ function ReportDetails({
auth,
handleSpecialColorDotClick,
isFinalWeek,
+ timeOffRequests,
}) {
// eslint-disable-next-line no-console
// console.log('DEBUG ReportDetails:', {
@@ -299,6 +323,7 @@ function ReportDetails({
// No bar for anyone unqualified, whatever the toggle says. 'default' and 'requested'
// both count as "still to do"; only 'posted' ends the workflow and clears the bar.
+ const activeTimeOffRequest = getActiveTimeOffRequest(timeOffRequests, summary._id, weekIndex);
const isMeetCriteria = canSeeBioHighlight && isQualifiedForBio(summary);
return (
@@ -327,7 +352,7 @@ function ReportDetails({
backgroundColor: isMeetCriteria ? '#FFF200' : 'transparent',
color: isMeetCriteria ? '#000000' : 'inherit',
width: '100%',
- padding: '6px 12px 6px 0px',
+ padding: '2px 12px 2px 0px',
}}
>
{/* Dev-admin protected records stay read-only here, same as team code and
@@ -353,9 +378,17 @@ function ReportDetails({
>
+ }
canEditTeamCode={canEditTeamCode && !cantEditJaeRelatedRecord}
summary={summary}
handleTeamCodeChange={handleTeamCodeChange}
+ activeTeamCodes={activeTeamCodes}
darkMode={darkMode}
/>
@@ -467,10 +500,15 @@ function WeeklySummaryMessage({ summary, weekIndex, darkMode }) {
{
+ onClick={async () => {
const parsedSummary = summaryText.replace(/<\/?[^>]+>| /g, '');
- navigator.clipboard.writeText(parsedSummary);
- toast.success('Summary Copied!');
+
+ try {
+ await navigator.clipboard.writeText(parsedSummary);
+ toast.success('Summary Copied!');
+ } catch {
+ toast.error('Failed to copy summary.');
+ }
}}
/>
@@ -495,21 +533,35 @@ function WeeklySummaryMessage({ summary, weekIndex, darkMode }) {
);
}
-function TeamCodeRow({ canEditTeamCode, summary, handleTeamCodeChange, darkMode }) {
+function TeamCodeRow({
+ canEditTeamCode,
+ summary,
+ handleTeamCodeChange,
+ activeTeamCodes,
+ darkMode,
+ weekOffBadge,
+}) {
const [teamCode, setTeamCode] = useState(summary.teamCode);
const [savedTeamCode, setSavedTeamCode] = useState(summary.teamCode);
const [hasError, setHasError] = useState(false);
+ const [showSuggestions, setShowSuggestions] = useState(false);
const fullCodeRegex = /^.{5,7}$/;
- const dispatch = useDispatch();
+ const filteredTeamCodes = activeTeamCodes.filter(code =>
+ code.toLowerCase().includes((teamCode || '').toLowerCase()),
+ );
const handleOnChange = async (userProfileSummary, newStatus) => {
const url = ENDPOINTS.USERS_ALLTEAMCODE_CHANGE;
try {
- await axios.patch(url, { userIds: [userProfileSummary._id], replaceCode: newStatus });
- handleTeamCodeChange(userProfileSummary.teamCode, newStatus, {
- [userProfileSummary._id]: true,
- }); // Update the team code dynamically
+ const response = await axios.patch(url, {
+ userIds: [userProfileSummary._id],
+ replaceCode: newStatus,
+ });
+ const updatedUser = response.data?.updatedUsers?.find(
+ user => user.userId === userProfileSummary._id,
+ );
+ handleTeamCodeChange(userProfileSummary._id, newStatus, updatedUser?.teamCodeWarning);
} catch (err) {
// eslint-disable-next-line no-alert
alert(
@@ -525,7 +577,7 @@ function TeamCodeRow({ canEditTeamCode, summary, handleTeamCodeChange, darkMode
setHasError(false);
setTeamCode(value);
setSavedTeamCode(value);
- handleOnChange(summary, value);
+ void handleOnChange(summary, value);
} else {
setTeamCode(savedTeamCode);
setHasError(true);
@@ -539,31 +591,73 @@ function TeamCodeRow({ canEditTeamCode, summary, handleTeamCodeChange, darkMode
return (
<>
- {canEditTeamCode ? (
-
- setTeamCode(e.target.value)}
- onBlur={e => {
- handleCodeChange(e);
- }}
- onKeyDown={e => {
- if (e.key === 'Enter') {
- e.currentTarget.blur(); // triggers onBlur
- }
- }}
- placeholder="X-XXX"
- className={`${styles.weeklySummariesCodeInput} ${
- darkMode ? 'bg-darkmode-liblack text-light border-0' : ''
- }`}
- />
-
- ) : (
-
- {teamCode === '' ? 'No assigned team code!' : teamCode}
-
- )}
+
+ {canEditTeamCode ? (
+
+
+
setShowSuggestions(true)}
+ onChange={e => {
+ setTeamCode(e.target.value);
+ setShowSuggestions(true);
+ }}
+ onBlur={e => {
+ handleCodeChange(e);
+ setShowSuggestions(false);
+ }}
+ onKeyDown={e => {
+ if (e.key === 'Enter') {
+ e.currentTarget.blur();
+ }
+
+ if (e.key === 'Escape') {
+ setShowSuggestions(false);
+ }
+ }}
+ placeholder="X-XXX"
+ className={`${styles.weeklySummariesCodeInput} ${
+ darkMode ? 'bg-darkmode-liblack text-light border-0' : ''
+ }`}
+ />
+
+ {showSuggestions && filteredTeamCodes.length > 0 && (
+
+ {filteredTeamCodes.map(code => (
+ {
+ e.preventDefault();
+ setTeamCode(code);
+ setShowSuggestions(false);
+ }}
+ >
+ {code}
+
+ ))}
+
+ )}
+
+
+ ) : (
+
+ {teamCode === '' ? 'No assigned team code!' : teamCode}
+
+ )}
+
+ {weekOffBadge}
+
+
Media URL:
@@ -633,22 +727,25 @@ function TotalValidWeeklySummaries({ summary, canEditSummaryCount, darkMode }) {
const handleWeeklySummaryCountChange = e => {
setWeeklySummariesCount(e.target.value);
- handleOnChange(summary, e.target.value);
+ void handleOnChange(summary, e.target.value);
};
return (
-
+
{weeklySummariesCount === 8 ? (
-
+
Total Valid Weekly Summaries: {' '}
) : (
-
+
Total Valid Weekly Summaries:
)}
{canEditSummaryCount ? (
-
+
handleWeeklySummaryCountChange(e)}
className={darkMode ? 'bg-darkmode-liblack text-light border-0' : ''}
+ style={{ width: '65px', height: '28px', padding: '2px 6px' }}
min="0"
+ max="9999"
/>
) : (
@@ -824,6 +923,39 @@ function WeeklyBadge({ summary, weekIndex, badges }) {
);
}
+// Shows "Requested Week Off" for the viewed week. Clicking (or pressing Enter/Space on)
+// the info button toggles the logged reason, as the ticket asks for; it closes on blur
+// or Escape.
+function WeekOffBadge({ request, summaryId, weekIndex }) {
+ const [showReason, setShowReason] = useState(false);
+ if (!request) return null;
+ const reasonId = `week-off-reason-${summaryId}-${weekIndex}`;
+ return (
+
+ Requested Week Off{' '}
+ setShowReason(prev => !prev)}
+ onBlur={() => setShowReason(false)}
+ onKeyDown={e => {
+ if (e.key === 'Escape') setShowReason(false);
+ }}
+ >
+
+
+ {showReason && (
+
+ {request.reason || 'No reason provided'}
+
+ )}
+
+ );
+}
+
function Index({
summary,
weekIndex,
@@ -958,11 +1090,25 @@ function Index({
marginLeft: '10px',
fontSize: '25px',
cursor: 'pointer',
+ position: 'relative',
+ lineHeight: 1,
+ marginRight: '16px',
color: summary?.trophyFollowedUp === true ? '#ffbb00' : '#FF0800',
}}
onClick={trophyIconToggle}
>
-
+
{handleIconContent(durationSinceStarted)}
@@ -1121,13 +1267,18 @@ FormattedReport.propTypes = {
}),
}),
}),
+ // Keyed by userId, each value an array of that user's time-off requests
+ // ({ _id, reason, startingDate, endingDate, ... }), from timeOffRequestReducer.
+ timeOffRequests: PropTypes.objectOf(PropTypes.array),
};
FormattedReport.defaultProps = {
auth: {},
+ timeOffRequests: {},
};
ReportDetails.propTypes = {
+ activeTeamCodes: PropTypes.arrayOf(PropTypes.string).isRequired,
summary: PropTypes.shape({
_id: PropTypes.string,
email: PropTypes.string,
@@ -1142,10 +1293,12 @@ ReportDetails.propTypes = {
}),
}),
}),
+ timeOffRequests: PropTypes.objectOf(PropTypes.array),
};
ReportDetails.defaultProps = {
auth: {},
+ timeOffRequests: {},
};
export default FormattedReport;
diff --git a/src/components/WeeklySummariesReport/TeamCodeWarningButton.jsx b/src/components/WeeklySummariesReport/TeamCodeWarningButton.jsx
new file mode 100644
index 00000000000..336e361629f
--- /dev/null
+++ b/src/components/WeeklySummariesReport/TeamCodeWarningButton.jsx
@@ -0,0 +1,36 @@
+import PropTypes from 'prop-types';
+import ReactTooltip from 'react-tooltip';
+import styles from './WeeklySummariesReport.module.css';
+
+const TeamCodeWarningButton = ({ count, active, onClick }) => {
+ if (count === 0) return null;
+
+ return (
+ <>
+
+
+
+
+ {`${count} users have mismatched team codes! Smash this “i” button to see who they are 👊`}
+
+ >
+ );
+};
+
+TeamCodeWarningButton.propTypes = {
+ count: PropTypes.number.isRequired,
+ active: PropTypes.bool.isRequired,
+ onClick: PropTypes.func.isRequired,
+};
+
+export default TeamCodeWarningButton;
diff --git a/src/components/WeeklySummariesReport/WeeklySummariesReport.jsx b/src/components/WeeklySummariesReport/WeeklySummariesReport.jsx
index 3661bc93b06..f65f8c56bc0 100644
--- a/src/components/WeeklySummariesReport/WeeklySummariesReport.jsx
+++ b/src/components/WeeklySummariesReport/WeeklySummariesReport.jsx
@@ -6,13 +6,11 @@ import axios from 'axios';
import moment from 'moment';
import 'moment-timezone';
import PropTypes from 'prop-types';
-import { useEffect, useState } from 'react';
-// import { useEffect, useState, startTransition } from 'react'; // Unused import removed
+import { useEffect, useRef, useState } from 'react';
import { MultiSelect } from 'react-multi-select-component';
import { connect } from 'react-redux';
import Select, { components } from 'react-select';
import { toast } from 'react-toastify';
-import ReactTooltip from 'react-tooltip';
import {
Alert,
Button,
@@ -42,8 +40,10 @@ import {
updateSummaryReportFromServerAction,
} from '../../actions/weeklySummaries';
import { getWeeklySummariesReport } from '../../actions/weeklySummariesReport';
+import { getAllTimeOffRequests } from '../../actions/timeOffRequestAction';
import SkeletonLoading from '../common/SkeletonLoading';
import TeamChart from './TeamChart';
+import TeamCodeWarningButton from './TeamCodeWarningButton';
import {
createSavedFilter,
@@ -141,6 +141,7 @@ const initialState = {
replaceCodeLoading: false,
allRoleInfo: [],
teamCodeWarningUsers: [],
+ showOnlyMismatched: false,
loadedTabs: [navItems[1]],
summariesByTab: {},
tabsLoading: { [navItems[1]]: false },
@@ -290,6 +291,7 @@ const WeeklySummariesReport = props => {
const { loading, getInfoCollections } = props;
const weekDates = getWeekDates();
const [state, setState] = useState(initialState);
+ const reportResultsRef = useRef(null);
const [permissionState, setPermissionState] = useState(intialPermissionState);
const [isTeamCodeFocused, setIsTeamCodeFocused] = useState(false);
@@ -601,7 +603,7 @@ const WeeklySummariesReport = props => {
COLORS,
colorOptions,
teamCodes,
- teamCodeWarningUsers: summariesCopy.filter(s => s.teamCodeWarning),
+ teamCodeWarningUsers: summariesCopy.filter(s => s.isActive && s.teamCodeWarning),
auth,
tabsLoading: {
[activeTab]: false,
@@ -965,6 +967,9 @@ const WeeklySummariesReport = props => {
...prevState,
summaries: summariesCopy,
filteredSummaries: summariesCopy,
+ teamCodeWarningUsers: summariesCopy.filter(
+ summary => summary.isActive && summary.teamCodeWarning,
+ ),
teamCodes,
colorOptions,
tableData,
@@ -980,6 +985,7 @@ const WeeklySummariesReport = props => {
...prevState,
summaries: [],
filteredSummaries: [],
+ teamCodeWarningUsers: [],
teamCodes: [],
colorOptions: [],
tableData: {},
@@ -1029,6 +1035,7 @@ const WeeklySummariesReport = props => {
setState(prevState => ({
...prevState,
activeTab: tab,
+ showOnlyMismatched: false,
tabsLoading: {
...prevState.tabsLoading,
[tab]: true,
@@ -1077,11 +1084,11 @@ const WeeklySummariesReport = props => {
}));
};
- const handleTeamCodeChange = (userId, newTeamCode) => {
+ const handleTeamCodeChange = (userId, newTeamCode, teamCodeWarning) => {
setState(prevState => {
const summaries = prevState.summaries.map(summary => {
if (summary._id === userId) {
- return { ...summary, teamCode: newTeamCode };
+ return { ...summary, teamCode: newTeamCode, teamCodeWarning: !!teamCodeWarning };
}
return summary;
});
@@ -1141,6 +1148,9 @@ const WeeklySummariesReport = props => {
return {
...prevState,
summaries,
+ teamCodeWarningUsers: summaries.filter(
+ summary => summary.isActive && summary.teamCodeWarning,
+ ),
summariesByTab: {
...prevState.summariesByTab,
[prevState.activeTab]: summaries,
@@ -1264,26 +1274,11 @@ const WeeklySummariesReport = props => {
_ids: updatedSummaries.filter(s => s.teamCode === replaceCode).map(s => s._id),
});
- const updatedWarningUsers = [...teamCodeWarningUsers];
- updatedUsers.forEach(({ userId, teamCodeWarning }) => {
- const existingIndex = updatedWarningUsers.findIndex(user => user._id === userId);
-
- if (teamCodeWarning) {
- if (existingIndex !== -1) {
- updatedWarningUsers[existingIndex].teamCodeWarning = true;
- } else {
- const userProfile = summaries.find(summary => summary._id === userId);
- if (userProfile) {
- userProfile.teamCodeWarning = true;
- updatedWarningUsers.push({ ...userProfile });
- }
- }
- } else if (existingIndex !== -1) {
- updatedWarningUsers.splice(existingIndex, 1);
- }
- });
+ const updatedWarningUsers = updatedSummaries.filter(
+ summary => summary.isActive && summary.teamCodeWarning,
+ );
- const updatedTableData = tableData;
+ const updatedTableData = { ...tableData };
updatedTableData[replaceCode] = updatedSummaries.filter(s => s.teamCode === replaceCode);
oldTeamCodes.forEach(code => {
updatedTableData[code] = updatedSummaries.filter(s => s.teamCode === code);
@@ -1314,8 +1309,6 @@ const WeeklySummariesReport = props => {
teamCodeWarningUsers: updatedWarningUsers,
tableData: updatedTableData,
}));
-
- filterWeeklySummaries();
} else {
setState(prev => ({
...prev,
@@ -1698,6 +1691,7 @@ const WeeklySummariesReport = props => {
const fetchInitialPermissions = async () => {
try {
await props.fetchAllBadges();
+ props.getAllTimeOffRequests();
setPermissionState(prev => ({
...prev,
// Keep the Owner/Administrator fallbacks identical to the other place that
@@ -1742,6 +1736,16 @@ const WeeklySummariesReport = props => {
const { error } = props;
const hasPermissionToFilter = role === 'Owner' || role === 'Administrator';
const { authEmailWeeklySummaryRecipient } = props;
+ const warningUsers = state.summaries.filter(
+ summary => summary.isActive && summary.teamCodeWarning,
+ );
+ const showOnlyMismatched = state.showOnlyMismatched && warningUsers.length > 0;
+ const displayedSummaries = showOnlyMismatched ? warningUsers : state.filteredSummaries;
+
+ const handleToggleMismatchedUsers = () => {
+ setState(prev => ({ ...prev, showOnlyMismatched: !prev.showOnlyMismatched }));
+ requestAnimationFrame(() => reportResultsRef.current?.scrollIntoView({ behavior: 'smooth' }));
+ };
if (error) {
return (
@@ -1944,25 +1948,11 @@ const WeeklySummariesReport = props => {
- {state.teamCodeWarningUsers.length > 0 && (
- <>
-
-
- {state.teamCodeWarningUsers.length} users have mismatched team codes!
-
- >
- )}
+
{
onBlur={() => setIsTeamCodeFocused(false)}
classNamePrefix="custom-select"
className={`custom-select-container ${darkMode ? 'dark-mode' : ''} ${
- state.teamCodeWarningUsers.length > 0 ? 'warning-border' : ''
+ warningUsers.length > 0 ? 'warning-border' : ''
}`}
styles={customStyles}
/>
@@ -2255,6 +2245,7 @@ const WeeklySummariesReport = props => {
)}
+
{navItems.map(item => (
@@ -2294,7 +2285,7 @@ const WeeklySummariesReport = props => {
style={{ display: 'flex', justifyContent: 'flex-end', gap: '8px' }}
>
{
- {state.filteredSummaries && state.filteredSummaries.length > 0 ? (
+ {showOnlyMismatched && (
+
+
+
+ Showing {displayedSummaries.length} users with mismatched team codes.{' '}
+ Click the red i again to show all users.
+
+
+
+ )}
+ {displayedSummaries.length > 0 ? (
<>
- Total Team Members: {state.filteredSummaries.length}
+ Total Team Members: {displayedSummaries.length}
{
handleBioStatusChange={handleBioStatusChange}
loadTrophies={state.loadTrophies}
handleSpecialColorDotClick={handleSpecialColorDotClick}
+ timeOffRequests={props.timeOffRequests}
/>
@@ -2421,6 +2423,7 @@ const mapStateToProps = state => {
auth: state?.auth || {},
darkMode: state?.theme?.darkMode || false,
authEmailWeeklySummaryRecipient: state?.auth?.user?.email || '',
+ timeOffRequests: state.timeOffRequests?.requests || {},
};
};
@@ -2444,6 +2447,7 @@ const mapDispatchToProps = dispatch => ({
dispatch(updateOneSummaryReport(userId, updatedField)),
updateSummaryReport: payload => dispatch(updateSummaryReport(payload)),
updateSummaryReportFromServerAction: user => dispatch(updateSummaryReportFromServerAction(user)),
+ getAllTimeOffRequests: () => dispatch(getAllTimeOffRequests()),
});
function WeeklySummariesReportTab({ tabId, hidden, children }) {
diff --git a/src/components/WeeklySummariesReport/WeeklySummariesReport.module.css b/src/components/WeeklySummariesReport/WeeklySummariesReport.module.css
index 842f4c03137..4305b5a1780 100644
--- a/src/components/WeeklySummariesReport/WeeklySummariesReport.module.css
+++ b/src/components/WeeklySummariesReport/WeeklySummariesReport.module.css
@@ -184,6 +184,46 @@
justify-content: space-between;
}
+.teamCodeSelectRow {
+ display: flex;
+ align-items: center;
+ width: 100%;
+}
+
+.teamCodeSelect {
+ flex: 1;
+ min-width: 0;
+}
+
+.teamCodeWarningButton {
+ flex: none;
+ padding: 0;
+ margin-right: 8px;
+ border: 0;
+ background: transparent;
+ font-size: 20px;
+ line-height: 1;
+ cursor: pointer;
+}
+
+.teamCodeWarningButton:focus-visible {
+ outline: 2px solid currentColor;
+ outline-offset: 3px;
+}
+
+.teamCodeWarningButtonActive {
+ border-radius: 50%;
+ box-shadow: 0 0 0 2px currentColor;
+}
+
+.mismatchFilterStatus {
+ margin: 8px 0;
+ padding: 8px 12px;
+ border-left: 4px solid #dc3545;
+ background-color: #fce9ec;
+ color: #842029;
+}
+
.filterStyle {
display: flex;
align-items: center;
@@ -714,7 +754,6 @@
background-color: #3a506b !important;
color: #ffffff !important;
}
-
/* Bio Status Button - prevent any movement on click */
.bioStatusButton {
display: inline-block;
@@ -734,4 +773,4 @@
.bioStatusButton:active {
background-color: inherit !important;
border: inherit !important;
-}
\ No newline at end of file
+}
diff --git a/src/components/WeeklySummariesReport/WeeklySummariesReport.module.scss b/src/components/WeeklySummariesReport/WeeklySummariesReport.module.scss
index cbef0a7fd59..c0c11041fae 100644
--- a/src/components/WeeklySummariesReport/WeeklySummariesReport.module.scss
+++ b/src/components/WeeklySummariesReport/WeeklySummariesReport.module.scss
@@ -20,7 +20,7 @@
.bioToggle {
display: inline-flex;
margin: 0;
-
+
button {
margin-top: 0;
}
@@ -151,10 +151,94 @@
width: 150px !important;
}
+.teamCodeInput {
+ width: 90px !important;
+ height: 28px;
+ padding: 2px 6px;
+}
+
+.summaryCountInputWrapper {
+ flex: 0 0 auto;
+ padding-left: 6px;
+}
+
+/* "Requested Week Off" notice beside the team code, and its click-to-show reason.
+ Same colors in light and dark mode. In dark mode the global `body.dark-mode *` rule
+ (public/index.css) forces white text on every element with !important, so the
+ notice, its button and icon repeat their colors under that selector to win. */
+.weekOffBadge {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ padding: 1px 6px;
+ border-radius: 4px;
+ font-size: 0.75rem;
+ white-space: nowrap;
+ background-color: #f8d7da;
+ color: #000;
+}
+
+.weekOffInfoButton {
+ /* A flex container drops the trailing space after the text, so the gap
+ before the icon comes from this margin. */
+ margin-left: 4px;
+ padding: 0;
+ border: 0;
+ background: none;
+ color: #000;
+ line-height: 1;
+ cursor: pointer;
+
+ &:focus-visible {
+ outline: 2px solid #000;
+ outline-offset: 1px;
+ border-radius: 50%;
+ }
+}
+
+/* Opens to the right of the notice, level with it, so it does not cover the
+ name and Bio lines above; on narrow screens it opens below instead. */
+.weekOffReason {
+ position: absolute;
+ top: 0;
+ left: calc(100% + 6px);
+ z-index: 10;
+ min-width: 220px;
+ max-width: 320px;
+ padding: 6px 8px;
+ border-radius: 4px;
+ font-size: 0.8rem;
+ white-space: normal;
+ overflow-wrap: anywhere;
+ background-color: #222;
+ color: #fff;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
+}
+
+@media screen and (width <= 576px) {
+ .weekOffReason {
+ top: calc(100% + 6px);
+ left: 0;
+ }
+}
+
+:global(body.dark-mode),
+:global(body.bm-dashboard-dark) {
+ .weekOffBadge,
+ .weekOffInfoButton,
+ .weekOffInfoButton i {
+ color: #000 !important;
+ }
+
+ .weekOffReason {
+ color: #fff !important;
+ }
+}
+
.teamcodeWrapper {
- display: flex;
- white-space: wrap;
- flex-direction: column;
+ display: flex;
+ white-space: wrap;
+ flex-direction: column;
}
@media screen and (width <= 544px) {
@@ -170,11 +254,60 @@
width: 100%;
}
+ .teamCodeInputWrapper {
+ width: 100%;
+ }
.weeklySummariesCodeInput {
width: 100% !important;
}
}
+.teamCodeInputWrapper {
+ position: relative;
+ width: 150px;
+}
+
+.teamCodeSuggestions {
+ position: absolute;
+ top: calc(100% + 2px);
+ left: 0;
+ z-index: 1000;
+ width: 100%;
+ max-height: 180px;
+ overflow-y: auto;
+ background: #fff;
+ border: 1px solid #ced4da;
+ border-radius: 4px;
+ box-shadow: 0 4px 8px rgb(0 0 0 / 15%);
+}
+
+.teamCodeSuggestionsDark {
+ background: #343a40;
+ border-color: #5a6268;
+}
+
+.teamCodeSuggestionItem {
+ display: block;
+ width: 100%;
+ padding: 6px 10px;
+ background: transparent;
+ border: 0;
+ text-align: left;
+ cursor: pointer;
+}
+
+.teamCodeSuggestionItem:hover {
+ background: #f1f1f1;
+}
+
+.teamCodeSuggestionItemDark {
+ color: #fff;
+}
+
+.teamCodeSuggestionItemDark:hover {
+ background: #495057;
+}
+
@media screen and (width <= 490px) {
.responsiveFontSize {
font-size: 0.5rem !important;
@@ -195,7 +328,6 @@
}
}
-
.codeAlert {
margin: 10px 0 0;
padding: 8px;
diff --git a/src/components/WeeklySummariesReport/__tests__/BioFunction.test.jsx b/src/components/WeeklySummariesReport/__tests__/BioFunction.test.jsx
index f6a5b81cf1e..06504fdfae2 100644
--- a/src/components/WeeklySummariesReport/__tests__/BioFunction.test.jsx
+++ b/src/components/WeeklySummariesReport/__tests__/BioFunction.test.jsx
@@ -9,7 +9,7 @@ const colors = {
const baseProps = {
bioPosted: 'default',
totalTangibleHrs: 100,
- daysInTeam: 70,
+ weeklySummariesCount: 10,
textColors: colors,
summary: { weeklySummaryOption: 'option1' },
bioCanEdit: true,
diff --git a/src/components/WeeklySummariesReport/__tests__/FormattedReport.test.jsx b/src/components/WeeklySummariesReport/__tests__/FormattedReport.test.jsx
index 3264f98fe0e..762687d7b41 100644
--- a/src/components/WeeklySummariesReport/__tests__/FormattedReport.test.jsx
+++ b/src/components/WeeklySummariesReport/__tests__/FormattedReport.test.jsx
@@ -1,10 +1,12 @@
import React from 'react';
-import { render, screen } from '@testing-library/react';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { MemoryRouter } from 'react-router-dom';
import moment from 'moment';
+import axios from 'axios';
+import momentTz from 'moment-timezone';
import FormattedReport from '../FormattedReport';
import '@testing-library/jest-dom/extend-expect';
@@ -71,7 +73,6 @@ const dummySummary = {
.add(1, 'days')
.format(),
totalTangibleHrs: 100,
- daysInTeam: 70,
bioPosted: 'default',
weeklySummaryOption: 'Default',
};
@@ -146,7 +147,181 @@ describe('FormattedReport minimal test', () => {
);
expect(screen.queryByText('Emails')).toBeNull();
});
+ it('only suggests team codes currently in use on the visible weekly summaries page', () => {
+ const activeSummaryOne = {
+ ...dummySummary,
+ _id: 'active-1',
+ email: 'active1@example.com',
+ teamCode: 'ACT01',
+ endDate: null,
+ };
+
+ const activeSummaryTwo = {
+ ...dummySummary,
+ _id: 'active-2',
+ email: 'active2@example.com',
+ teamCode: 'ACT02',
+ endDate: null,
+ };
+
+ const expiredSummary = {
+ ...dummySummary,
+ _id: 'expired-1',
+ email: 'expired@example.com',
+ teamCode: 'OLD01',
+ isActive: false,
+ endDate: moment()
+ .subtract(6, 'weeks')
+ .toISOString(),
+ finalWeekIndex: 6,
+ };
+
+ render(
+
+
+
+
+ ,
+ );
+
+ const inputs = screen.getAllByPlaceholderText('X-XXX');
+ fireEvent.focus(inputs[0]);
+ fireEvent.change(inputs[0], {
+ target: { value: '' },
+ });
+
+ expect(screen.getByText('ACT01')).toBeInTheDocument();
+ expect(screen.getByText('ACT02')).toBeInTheDocument();
+ expect(screen.queryByText('OLD01')).not.toBeInTheDocument();
+ });
+ it('includes an active user with a scheduled end date in team code suggestions', () => {
+ const activeScheduledEndSummary = {
+ ...dummySummary,
+ _id: 'future-end-user',
+ email: 'future@example.com',
+ teamCode: 'FUT01',
+ isActive: true,
+ endDate: moment()
+ .add(2, 'weeks')
+ .toISOString(),
+ };
+ render(
+
+
+
+
+ ,
+ );
+
+ expect(screen.getByText(/John Doe/)).toBeInTheDocument();
+
+ const input = screen.getByPlaceholderText('X-XXX');
+ fireEvent.focus(input);
+ fireEvent.change(input, { target: { value: '' } });
+
+ expect(screen.getByText('FUT01')).toBeInTheDocument();
+ });
+
+ it('does not suggest the team code of an inactive user hidden from the current tab', () => {
+ const visibleSummary = {
+ ...dummySummary,
+ _id: 'visible-user',
+ email: 'visible@example.com',
+ teamCode: 'ACT01',
+ isActive: true,
+ endDate: null,
+ };
+
+ const hiddenInactiveSummary = {
+ ...dummySummary,
+ _id: 'inactive-user',
+ email: 'inactive@example.com',
+ teamCode: 'OLD01',
+ isActive: false,
+ endDate: null,
+ finalWeekIndex: 1,
+ };
+
+ render(
+
+
+
+
+ ,
+ );
+
+ const input = screen.getByPlaceholderText('X-XXX');
+ fireEvent.focus(input);
+ fireEvent.change(input, { target: { value: '' } });
+
+ expect(screen.getByText('ACT01')).toBeInTheDocument();
+ expect(screen.queryByText('OLD01')).not.toBeInTheDocument();
+ });
+ it('updates active team code suggestions after a successful save', async () => {
+ function StatefulReport() {
+ const [summaries, setSummaries] = React.useState([dummySummary]);
+
+ const handleTeamCodeChange = (userId, newTeamCode) => {
+ setSummaries(prevSummaries =>
+ prevSummaries.map(summary =>
+ summary._id === userId ? { ...summary, teamCode: newTeamCode } : summary,
+ ),
+ );
+ };
+
+ return (
+
+ );
+ }
+
+ render(
+
+
+
+
+ ,
+ );
+
+ const input = screen.getByPlaceholderText('X-XXX');
+
+ fireEvent.focus(input);
+
+ expect(screen.getByText('ABC123')).toBeInTheDocument();
+
+ fireEvent.change(input, {
+ target: { value: 'NEW01' },
+ });
+
+ fireEvent.blur(input);
+
+ await waitFor(() => {
+ expect(axios.patch).toHaveBeenCalledWith(expect.any(String), {
+ userIds: ['1'],
+ replaceCode: 'NEW01',
+ });
+ });
+
+ fireEvent.focus(input);
+
+ fireEvent.change(input, {
+ target: { value: '' },
+ });
+
+ expect(screen.getByText('NEW01')).toBeInTheDocument();
+ expect(screen.queryByText('ABC123')).not.toBeInTheDocument();
+ });
it('renders fallback text when weekly summary text is missing', () => {
const summaryNoText = {
...dummySummary,
@@ -228,7 +403,7 @@ describe('FormattedReport minimal test', () => {
,
);
expect(screen.queryByPlaceholderText('X-XXX')).toBeNull();
- expect(screen.getByText('ABC123')).toBeInTheDocument();
+ expect(screen.getByText('ABC123', { selector: 'div' })).toBeInTheDocument();
});
});
@@ -284,3 +459,100 @@ describe('FormattedReport final week banner', () => {
expect(screen.queryByText(FINAL_WEEK_TEXT)).toBeNull();
});
});
+
+describe('FormattedReport requested week off notice', () => {
+ // A request covering the current week, keyed by user id like the reducer state.
+ // Dates are built in Pacific time, like the report, so the test does not depend
+ // on the machine's timezone (CI runs in UTC).
+ const timeOffRequests = {
+ [dummySummary._id]: [
+ {
+ _id: 'req1',
+ reason: 'Family trip',
+ startingDate: momentTz
+ .tz('America/Los_Angeles')
+ .startOf('week')
+ .toISOString(),
+ endingDate: momentTz
+ .tz('America/Los_Angeles')
+ .endOf('week')
+ .toISOString(),
+ },
+ ],
+ };
+
+ const renderReport = (weekIndex = 0) =>
+ render(
+
+
+
+
+ ,
+ );
+
+ it('shows the notice on the week the request covers, with the reason hidden', () => {
+ renderReport(0);
+ expect(screen.getByText('Requested Week Off')).toBeInTheDocument();
+ expect(screen.queryByText('Family trip')).toBeNull();
+ });
+
+ it('does not show the notice on a week the request does not cover', () => {
+ renderReport(1);
+ expect(screen.queryByText('Requested Week Off')).toBeNull();
+ });
+
+ it('toggles the reason when the info button is clicked', () => {
+ renderReport(0);
+ const button = screen.getByRole('button', { name: 'Show time off reason' });
+ expect(button).toHaveAttribute('aria-expanded', 'false');
+
+ fireEvent.click(button);
+ expect(screen.getByRole('tooltip')).toHaveTextContent('Family trip');
+ expect(button).toHaveAttribute('aria-expanded', 'true');
+
+ fireEvent.click(button);
+ expect(screen.queryByText('Family trip')).toBeNull();
+ });
+
+ it('closes the reason on Escape', () => {
+ renderReport(0);
+ const button = screen.getByRole('button', { name: 'Show time off reason' });
+ fireEvent.click(button);
+ fireEvent.keyDown(button, { key: 'Escape' });
+ expect(screen.queryByText('Family trip')).toBeNull();
+ });
+
+ it('updates the correct report user and applies the warning returned by the API', async () => {
+ const handleTeamCodeChange = vi.fn();
+ axios.patch.mockResolvedValueOnce({
+ data: { updatedUsers: [{ userId: '1', teamCodeWarning: true }] },
+ });
+
+ render(
+
+
+
+
+ ,
+ );
+
+ const codeInput = screen.getByPlaceholderText('X-XXX');
+ fireEvent.change(codeInput, { target: { value: 'HaHUS' } });
+ fireEvent.blur(codeInput);
+
+ await waitFor(() => expect(handleTeamCodeChange).toHaveBeenCalledWith('1', 'HaHUS', true));
+ });
+});
diff --git a/src/components/WeeklySummariesReport/__tests__/TeamCodeWarningButton.test.jsx b/src/components/WeeklySummariesReport/__tests__/TeamCodeWarningButton.test.jsx
new file mode 100644
index 00000000000..31120e1afec
--- /dev/null
+++ b/src/components/WeeklySummariesReport/__tests__/TeamCodeWarningButton.test.jsx
@@ -0,0 +1,38 @@
+import { fireEvent, render, screen } from '@testing-library/react';
+import TeamCodeWarningButton from '../TeamCodeWarningButton';
+
+vi.mock('react-tooltip', () => ({
+ default: ({ children }) => {children} ,
+}));
+
+describe('TeamCodeWarningButton', () => {
+ it('stays hidden when the report has no mismatched users', () => {
+ render( );
+
+ expect(screen.queryByRole('button')).toBeNull();
+ });
+
+ it('shows the count and toggles the mismatch view', () => {
+ const onClick = vi.fn();
+ const { rerender } = render(
+ ,
+ );
+
+ const button = screen.getByRole('button', { name: 'Show users with mismatched team codes' });
+ expect(button.getAttribute('aria-pressed')).toBe('false');
+ expect(button.className).toContain('text-danger');
+ expect(
+ screen.getByText(
+ '2 users have mismatched team codes! Smash this “i” button to see who they are 👊',
+ ),
+ ).toBeTruthy();
+
+ fireEvent.click(button);
+ expect(onClick).toHaveBeenCalledTimes(1);
+
+ rerender( );
+ expect(button.getAttribute('aria-pressed')).toBe('true');
+ fireEvent.click(button);
+ expect(onClick).toHaveBeenCalledTimes(2);
+ });
+});
diff --git a/src/components/WeeklySummariesReport/__tests__/getActiveTimeOffRequest.test.js b/src/components/WeeklySummariesReport/__tests__/getActiveTimeOffRequest.test.js
new file mode 100644
index 00000000000..e5bfdb32094
--- /dev/null
+++ b/src/components/WeeklySummariesReport/__tests__/getActiveTimeOffRequest.test.js
@@ -0,0 +1,62 @@
+import moment from 'moment-timezone';
+import getActiveTimeOffRequest from '../getActiveTimeOffRequest';
+
+const TZ = 'America/Los_Angeles';
+// A fixed Wednesday, so "This Week" is Sun Oct 11 - Sat Oct 17, 2026 (Pacific).
+const NOW = moment.tz('2026-10-14T12:00:00', TZ).toDate();
+
+// Mirrors how the backend stores a request: starts on a Sunday (Pacific) and
+// ends on the Saturday of its last week.
+const request = (startDay, weeks, reason = 'Vacation') => {
+ const start = moment.tz(startDay, TZ).startOf('day');
+ return {
+ _id: `${startDay}-${weeks}`,
+ reason,
+ startingDate: start.toISOString(),
+ endingDate: start
+ .clone()
+ .add(weeks, 'weeks')
+ .subtract(1, 'day')
+ .toISOString(),
+ };
+};
+
+describe('getActiveTimeOffRequest', () => {
+ it('returns the request on the week it covers', () => {
+ const req = request('2026-10-11', 1);
+ const requests = { u1: [req] };
+ expect(getActiveTimeOffRequest(requests, 'u1', 0, NOW)).toBe(req);
+ expect(getActiveTimeOffRequest(requests, 'u1', 1, NOW)).toBeUndefined();
+ });
+
+ it('moves to Last Week as time passes', () => {
+ const req = request('2026-10-04', 1);
+ const requests = { u1: [req] };
+ expect(getActiveTimeOffRequest(requests, 'u1', 0, NOW)).toBeUndefined();
+ expect(getActiveTimeOffRequest(requests, 'u1', 1, NOW)).toBe(req);
+ expect(getActiveTimeOffRequest(requests, 'u1', 2, NOW)).toBeUndefined();
+ });
+
+ it('shows a multi-week request on every week it overlaps', () => {
+ const req = request('2026-10-04', 2);
+ const requests = { u1: [req] };
+ expect(getActiveTimeOffRequest(requests, 'u1', 0, NOW)).toBe(req);
+ expect(getActiveTimeOffRequest(requests, 'u1', 1, NOW)).toBe(req);
+ expect(getActiveTimeOffRequest(requests, 'u1', 2, NOW)).toBeUndefined();
+ });
+
+ it('does not show a request that starts next week', () => {
+ const requests = { u1: [request('2026-10-18', 1)] };
+ expect(getActiveTimeOffRequest(requests, 'u1', 0, NOW)).toBeUndefined();
+ });
+
+ it('only matches the given user', () => {
+ const requests = { u1: [request('2026-10-11', 1)] };
+ expect(getActiveTimeOffRequest(requests, 'u2', 0, NOW)).toBeUndefined();
+ });
+
+ it('handles missing data', () => {
+ expect(getActiveTimeOffRequest(undefined, 'u1', 0, NOW)).toBeUndefined();
+ expect(getActiveTimeOffRequest({ u1: [{ reason: 'x' }] }, 'u1', 0, NOW)).toBeUndefined();
+ });
+});
diff --git a/src/components/WeeklySummariesReport/components/WeeklySummariesToggleFilter.jsx b/src/components/WeeklySummariesReport/components/WeeklySummariesToggleFilter.jsx
index 6ef63b005c9..bc4ceb4538a 100644
--- a/src/components/WeeklySummariesReport/components/WeeklySummariesToggleFilter.jsx
+++ b/src/components/WeeklySummariesReport/components/WeeklySummariesToggleFilter.jsx
@@ -3,7 +3,7 @@ import ReactTooltip from 'react-tooltip';
import styles from '../WeeklySummariesReport.module.css';
import { toggleField } from '~/utils/stateHelper';
import { permissions } from '~/utils/constants';
-import { BIO_MIN_DAYS_IN_TEAM, BIO_MIN_TANGIBLE_HOURS } from '~/utils/bioQualification';
+import { BIO_MIN_TANGIBLE_HOURS, BIO_MIN_VALID_SUMMARIES } from '~/utils/bioQualification';
import { SlideToggle } from '../components';
export default function WeeklySummariesToggleFilter({
@@ -23,9 +23,8 @@ export default function WeeklySummariesToggleFilter({
};
// Bio Status is a plain on/off filter: off shows everyone, on narrows the list to
- // users still eligible to post a bio. The rule lives in isQualifiedForBio and is
- // tenure-based, not summary-count based — see the note there on the 2026-05-12
- // regression. The thresholds below are read from that module so this label cannot
+ // users still eligible to post a bio. The rule lives in isQualifiedForBio (tangible
+ // hours plus valid weekly summaries, not tenure). The thresholds below are read from that module so this label cannot
// drift away from the filter it describes.
const handleBioStatusToggle = () => toggle('selectedBioStatus');
const handleTrophyToggleChange = () => toggle('selectedTrophies');
@@ -57,8 +56,9 @@ export default function WeeklySummariesToggleFilter({
- Show users eligible for bio posting (tangible hours > {BIO_MIN_TANGIBLE_HOURS},
- days in team > {BIO_MIN_DAYS_IN_TEAM}, bio not yet posted)
+ Show users eligible for bio posting (tangible hours at least{' '}
+ {BIO_MIN_TANGIBLE_HOURS}, valid weekly summaries at least{' '}
+ {BIO_MIN_VALID_SUMMARIES}, bio not yet posted)
diff --git a/src/components/WeeklySummariesReport/getActiveTimeOffRequest.js b/src/components/WeeklySummariesReport/getActiveTimeOffRequest.js
new file mode 100644
index 00000000000..f9cd603c9ff
--- /dev/null
+++ b/src/components/WeeklySummariesReport/getActiveTimeOffRequest.js
@@ -0,0 +1,22 @@
+import moment from 'moment-timezone';
+
+const TZ = 'America/Los_Angeles';
+
+// Returns the user's time-off request (if any) that overlaps the week being viewed
+// (weekIndex: 0 = This Week, 1 = Last Week, ...), so the notice moves across the
+// tabs as weeks pass. `now` is only passed in tests.
+export default function getActiveTimeOffRequest(timeOffRequests, userId, weekIndex, now) {
+ const userRequests = timeOffRequests?.[userId] || [];
+ const viewedWeek = moment(now)
+ .tz(TZ)
+ .subtract(weekIndex, 'week');
+ const weekStart = viewedWeek.clone().startOf('week');
+ const weekEnd = viewedWeek.clone().endOf('week');
+
+ return userRequests.find(request => {
+ if (!request?.startingDate) return false;
+ const start = moment(request.startingDate);
+ const end = request.endingDate ? moment(request.endingDate) : start;
+ return weekStart.isSameOrBefore(end) && weekEnd.isSameOrAfter(start);
+ });
+}
diff --git a/src/components/common/PieChart/ProjectPieChart.jsx b/src/components/common/PieChart/ProjectPieChart.jsx
index 3a152f68809..d1d31967152 100644
--- a/src/components/common/PieChart/ProjectPieChart.jsx
+++ b/src/components/common/PieChart/ProjectPieChart.jsx
@@ -1,10 +1,18 @@
/* eslint-disable import/prefer-default-export */
-import { useMemo } from 'react';
+import { useMemo, useState } from 'react';
import { Cell, Label, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts';
import { useTable } from 'react-table';
+import { FiChevronDown, FiChevronUp } from 'react-icons/fi';
import { CHART_RADIUS, CHART_SIZE } from './constants'; // use same numbers as the D3 chart
import styles from './UserProjectPieChart.module.css';
+// Hard cap on the legend rows rendered while collapsed. Half the tasks chart's
+// limit of 20 because a person logs time to far fewer projects than tasks, and
+// this card sits above it — a long project list pushes the whole report down.
+// Fixed rather than viewport-derived so the card is the same height on every
+// screen; these reports get screenshotted into work confirmation letters.
+export const MAX_VISIBLE_PROJECTS = 10;
+
const BASE_COLORS = [
'#3366CC',
'#DC3912',
@@ -112,6 +120,16 @@ export default function UserProjectD3PieChart({
[colors, data],
);
+ const [expanded, setExpanded] = useState(false);
+
+ // Only the legend table is capped. The pie itself always renders every slice,
+ // so the chart and the "Total Hours" figure stay consistent with each other.
+ const hiddenCount = Math.max(0, tableData.length - MAX_VISIBLE_PROJECTS);
+ const visibleRows = expanded ? tableData : tableData.slice(0, MAX_VISIBLE_PROJECTS);
+ const moreProjectsLabel = `+ ${hiddenCount} more project${hiddenCount === 1 ? '' : 's'}`;
+ const toggleLabel = expanded ? 'Show less' : moreProjectsLabel;
+ const ToggleIcon = expanded ? FiChevronUp : FiChevronDown;
+
const columns = useMemo(
() => [
{
@@ -140,7 +158,7 @@ export default function UserProjectD3PieChart({
);
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable({
columns,
- data: tableData,
+ data: visibleRows,
});
if (!data.length || total === 0) return null;
@@ -217,6 +235,21 @@ export default function UserProjectD3PieChart({
+ {hiddenCount > 0 && (
+
+ setExpanded(prev => !prev)}
+ >
+ {toggleLabel}
+
+
+
+ )}
+
+ Array.from({ length: count }, (unused, i) => ({
+ projectId: `proj-${i}`,
+ projectName: `Project ${i}`,
+ totalTime: 1,
+ }));
+
+const renderChart = count =>
+ render(
);
+
+const legendRowCount = () => screen.getAllByTestId('project-color-cell').length;
+
+describe('UserProjectD3PieChart legend cap', () => {
+ // Pinned to the literal: the other assertions reference MAX_VISIBLE_PROJECTS
+ // symbolically and would stay green if the cap were lowered for debugging.
+ it('caps the collapsed legend at 10, the agreed product limit', () => {
+ expect(MAX_VISIBLE_PROJECTS).toBe(10);
+ });
+
+ it('caps the collapsed legend at the limit', () => {
+ renderChart(MAX_VISIBLE_PROJECTS + 7);
+
+ expect(legendRowCount()).toBe(MAX_VISIBLE_PROJECTS);
+ });
+
+ it('renders every project without a toggle when at or under the cap', () => {
+ renderChart(MAX_VISIBLE_PROJECTS);
+
+ expect(legendRowCount()).toBe(MAX_VISIBLE_PROJECTS);
+ expect(screen.queryByTestId('toggle-more-projects')).not.toBeInTheDocument();
+ });
+
+ it('labels the toggle with the number of hidden projects', () => {
+ renderChart(MAX_VISIBLE_PROJECTS + 7);
+
+ expect(screen.getByTestId('toggle-more-projects')).toHaveTextContent('+ 7 more projects');
+ });
+
+ it('singularizes the label when exactly one project is hidden', () => {
+ renderChart(MAX_VISIBLE_PROJECTS + 1);
+
+ expect(screen.getByTestId('toggle-more-projects')).toHaveTextContent('+ 1 more project');
+ });
+
+ it('expands to every project and collapses back to the cap', () => {
+ renderChart(MAX_VISIBLE_PROJECTS + 7);
+ const toggle = screen.getByTestId('toggle-more-projects');
+
+ expect(toggle).toHaveAttribute('aria-expanded', 'false');
+
+ fireEvent.click(toggle);
+ expect(legendRowCount()).toBe(MAX_VISIBLE_PROJECTS + 7);
+ expect(toggle).toHaveAttribute('aria-expanded', 'true');
+ expect(toggle).toHaveTextContent('Show less');
+
+ fireEvent.click(toggle);
+ expect(legendRowCount()).toBe(MAX_VISIBLE_PROJECTS);
+ expect(toggle).toHaveAttribute('aria-expanded', 'false');
+ });
+
+ it('totals every project, not just the visible ones', () => {
+ renderChart(MAX_VISIBLE_PROJECTS + 7);
+
+ // 17 projects x 1 hour each — the cap must not change the reported total.
+ expect(screen.getByText('17.00')).toBeInTheDocument();
+ });
+});
describe('ProjectColorCell', () => {
it('paints the swatch with the supplied color', () => {
diff --git a/src/constants/bmdashboard/weeklyProjectSummaryConstants.js b/src/constants/bmdashboard/weeklyProjectSummaryConstants.js
index 29c6ff48e7d..d56eab3b805 100644
--- a/src/constants/bmdashboard/weeklyProjectSummaryConstants.js
+++ b/src/constants/bmdashboard/weeklyProjectSummaryConstants.js
@@ -1,3 +1,9 @@
export const SET_PROJECT_FILTER = 'SET_PROJECT_FILTER';
export const SET_DATE_RANGE_FILTER = 'SET_DATE_RANGE_FILTER';
export const SET_COMPARISON_PERIOD_FILTER = 'SET_COMPARISON_PERIOD_FILTER';
+export const FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_BEGIN =
+ 'FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_BEGIN';
+export const FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_SUCCESS =
+ 'FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_SUCCESS';
+export const FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_ERROR =
+ 'FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_ERROR';
diff --git a/src/languages/en/ui.js b/src/languages/en/ui.js
index 6fe7b523a0f..08953146891 100644
--- a/src/languages/en/ui.js
+++ b/src/languages/en/ui.js
@@ -49,6 +49,7 @@ export const BADGE_MANAGEMENT = 'Badge Management';
export const VIEW_PROFILE = 'View Profile';
export const WBS = 'WBS';
export const WELCOME = 'Welcome';
+export const TASKS = 'Tasks';
export const NOTICE = 'Notice';
export const FIRST_NAME = 'First Name';
export const LAST_NAME = 'Last Name';
diff --git a/src/reducers/__tests__/weeklyProjectSummaryReducer.test.js b/src/reducers/__tests__/weeklyProjectSummaryReducer.test.js
new file mode 100644
index 00000000000..79da02249de
--- /dev/null
+++ b/src/reducers/__tests__/weeklyProjectSummaryReducer.test.js
@@ -0,0 +1,186 @@
+import { weeklyProjectSummaryReducer } from '../bmdashboard/weeklyProjectSummaryReducer';
+import {
+ FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_BEGIN,
+ FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_ERROR,
+ FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_SUCCESS,
+} from '../../constants/bmdashboard/weeklyProjectSummaryConstants';
+
+const requestA = { requestId: 'request-a', requestKey: 'startDate=2026-09-01' };
+const requestB = { requestId: 'request-b', requestKey: 'startDate=2026-10-01' };
+const requestC = { requestId: 'request-c', requestKey: 'startDate=2026-11-01' };
+const beginAction = meta => ({
+ type: FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_BEGIN,
+ meta,
+});
+const successAction = (meta, value) => ({
+ type: FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_SUCCESS,
+ payload: { current: { metrics: { totalProjects: { value } } } },
+ meta,
+});
+const errorAction = (meta, message = 'request failed') => ({
+ type: FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_ERROR,
+ payload: new Error(message),
+ meta,
+});
+const createLoadingLatestState = () =>
+ weeklyProjectSummaryReducer(
+ weeklyProjectSummaryReducer(undefined, beginAction(requestA)),
+ beginAction(requestB),
+ );
+
+describe('weeklyProjectSummaryReducer Project Status requests', () => {
+ it('clears previous Project Status data when a new request begins', () => {
+ const state = {
+ ...weeklyProjectSummaryReducer(undefined, {}),
+ projectStatusData: { current: { metrics: { totalProjects: { value: 150 } } } },
+ projectStatusDataRequestKey: requestA.requestKey,
+ projectStatusError: new Error('old error'),
+ };
+
+ const nextState = weeklyProjectSummaryReducer(state, {
+ ...beginAction(requestB),
+ });
+
+ expect(nextState).toMatchObject({
+ projectStatusLoading: true,
+ projectStatusData: null,
+ projectStatusError: null,
+ projectStatusActiveRequestId: requestB.requestId,
+ projectStatusActiveRequestKey: requestB.requestKey,
+ projectStatusDataRequestKey: null,
+ projectStatusErrorRequestKey: null,
+ });
+ });
+
+ it('ignores stale Project Status success and error actions after a newer request begins', () => {
+ const loadingLatestState = createLoadingLatestState();
+
+ [
+ {
+ type: FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_SUCCESS,
+ payload: { current: { metrics: { totalProjects: { value: 1 } } } },
+ meta: requestA,
+ },
+ {
+ type: FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_ERROR,
+ payload: new Error('stale failure'),
+ meta: requestA,
+ },
+ ].forEach(action => {
+ expect(weeklyProjectSummaryReducer(loadingLatestState, action)).toBe(loadingLatestState);
+ });
+ });
+
+ it('accepts the latest Project Status success after an older response is ignored', () => {
+ const staleState = weeklyProjectSummaryReducer(createLoadingLatestState(), {
+ type: FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_SUCCESS,
+ payload: { current: { metrics: { totalProjects: { value: 1 } } } },
+ meta: requestA,
+ });
+ const latestData = { current: { metrics: { totalProjects: { value: 2 } } } };
+ const nextState = weeklyProjectSummaryReducer(staleState, {
+ type: FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_SUCCESS,
+ payload: latestData,
+ meta: requestB,
+ });
+
+ expect(nextState).toMatchObject({
+ projectStatusLoading: false,
+ projectStatusData: latestData,
+ projectStatusError: null,
+ projectStatusActiveRequestId: requestB.requestId,
+ projectStatusActiveRequestKey: requestB.requestKey,
+ projectStatusDataRequestKey: requestB.requestKey,
+ });
+ });
+
+ it('clears previous Project Status data when the active request fails', () => {
+ const state = weeklyProjectSummaryReducer(
+ {
+ ...weeklyProjectSummaryReducer(undefined, {}),
+ projectStatusData: { current: { metrics: { totalProjects: { value: 150 } } } },
+ projectStatusDataRequestKey: requestA.requestKey,
+ },
+ {
+ ...beginAction(requestB),
+ },
+ );
+ const error = new Error('latest failure');
+
+ const nextState = weeklyProjectSummaryReducer(state, {
+ type: FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_ERROR,
+ payload: error,
+ meta: requestB,
+ });
+
+ expect(nextState).toMatchObject({
+ projectStatusLoading: false,
+ projectStatusData: null,
+ projectStatusError: error,
+ projectStatusActiveRequestId: requestB.requestId,
+ projectStatusActiveRequestKey: requestB.requestKey,
+ projectStatusDataRequestKey: null,
+ projectStatusErrorRequestKey: requestB.requestKey,
+ });
+ });
+
+ it('does not let request A success overwrite request B after request B succeeds first', () => {
+ const loadingBState = createLoadingLatestState();
+ const requestBSuccessState = weeklyProjectSummaryReducer(
+ loadingBState,
+ successAction(requestB, 2),
+ );
+
+ const nextState = weeklyProjectSummaryReducer(requestBSuccessState, successAction(requestA, 1));
+
+ expect(nextState).toBe(requestBSuccessState);
+ expect(nextState.projectStatusData.current.metrics.totalProjects.value).toBe(2);
+ });
+
+ it('does not let request A failure overwrite request B after request B succeeds first', () => {
+ const loadingBState = createLoadingLatestState();
+ const requestBSuccessState = weeklyProjectSummaryReducer(
+ loadingBState,
+ successAction(requestB, 2),
+ );
+
+ const nextState = weeklyProjectSummaryReducer(
+ requestBSuccessState,
+ errorAction(requestA, 'late request A failure'),
+ );
+
+ expect(nextState).toBe(requestBSuccessState);
+ expect(nextState.projectStatusData.current.metrics.totalProjects.value).toBe(2);
+ expect(nextState.projectStatusError).toBeNull();
+ });
+
+ it('does not let late request A or B responses overwrite request C after request C succeeds first', () => {
+ const loadingCState = weeklyProjectSummaryReducer(
+ createLoadingLatestState(),
+ beginAction(requestC),
+ );
+ const requestCSuccessState = weeklyProjectSummaryReducer(
+ loadingCState,
+ successAction(requestC, 3),
+ );
+
+ const afterLateASuccess = weeklyProjectSummaryReducer(
+ requestCSuccessState,
+ successAction(requestA, 1),
+ );
+ const afterLateBSuccess = weeklyProjectSummaryReducer(
+ afterLateASuccess,
+ successAction(requestB, 2),
+ );
+ const afterLateBFailure = weeklyProjectSummaryReducer(
+ afterLateBSuccess,
+ errorAction(requestB, 'late request B failure'),
+ );
+
+ expect(afterLateASuccess).toBe(requestCSuccessState);
+ expect(afterLateBSuccess).toBe(requestCSuccessState);
+ expect(afterLateBFailure).toBe(requestCSuccessState);
+ expect(afterLateBFailure.projectStatusData.current.metrics.totalProjects.value).toBe(3);
+ expect(afterLateBFailure.projectStatusError).toBeNull();
+ });
+});
diff --git a/src/reducers/badgeReducer.js b/src/reducers/badgeReducer.js
index 814d980b611..5cbc2536d96 100644
--- a/src/reducers/badgeReducer.js
+++ b/src/reducers/badgeReducer.js
@@ -13,6 +13,7 @@ import {
RESET_BADGE_COUNT,
SET_ACTIVE_TAB,
} from '../constants/badge';
+import { FETCH_ALL_BADGES, FETCH_BADGES_FAILURE } from '../actions/actionTypes';
const badgeInitial = {
allBadgeData: [],
@@ -25,6 +26,8 @@ const badgeInitial = {
alertVisible: false,
badgeCount: 0,
activeTab: '1',
+ loading: false,
+ error: null,
};
// eslint-disable-next-line default-param-last
@@ -40,6 +43,18 @@ export const badgeReducer = (state = badgeInitial, action) => {
...state,
selectedBadges: [...state.selectedBadges, action.badgeId],
};
+ case FETCH_ALL_BADGES:
+ return {
+ ...state,
+ allBadgeData: action.payload,
+ loading: false,
+ };
+ case FETCH_BADGES_FAILURE:
+ return {
+ ...state,
+ error: action.payload,
+ loading: false,
+ };
case REMOVE_SELECT_BADGE:
return {
...state,
diff --git a/src/reducers/bmdashboard/weeklyProjectSummaryReducer.js b/src/reducers/bmdashboard/weeklyProjectSummaryReducer.js
index f94d30ab5a0..e2e1d0af75b 100644
--- a/src/reducers/bmdashboard/weeklyProjectSummaryReducer.js
+++ b/src/reducers/bmdashboard/weeklyProjectSummaryReducer.js
@@ -2,12 +2,29 @@ import {
SET_PROJECT_FILTER,
SET_DATE_RANGE_FILTER,
SET_COMPARISON_PERIOD_FILTER,
+ FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_BEGIN,
+ FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_SUCCESS,
+ FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_ERROR,
} from '../../constants/bmdashboard/weeklyProjectSummaryConstants';
const initialState = {
projectFilter: 'One Community',
dateRangeFilter: '',
- comparisonPeriodFilter: '',
+ comparisonPeriodFilter: 'No Comparison',
+ projectStatusData: null,
+ projectStatusLoading: false,
+ projectStatusError: null,
+ projectStatusActiveRequestId: null,
+ projectStatusActiveRequestKey: null,
+ projectStatusDataRequestKey: null,
+ projectStatusErrorRequestKey: null,
+};
+
+const isStaleProjectStatusAction = (state, action) => {
+ const activeRequestId = state.projectStatusActiveRequestId;
+ const actionRequestId = action.meta?.requestId;
+
+ return Boolean(activeRequestId && actionRequestId && actionRequestId !== activeRequestId);
};
// eslint-disable-next-line default-param-last, import/prefer-default-export
@@ -31,6 +48,52 @@ export const weeklyProjectSummaryReducer = (state = initialState, action) => {
comparisonPeriodFilter: action.payload,
};
+ case FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_BEGIN:
+ return {
+ ...state,
+ projectStatusLoading: true,
+ projectStatusError: null,
+ projectStatusData: null,
+ projectStatusActiveRequestId: action.meta?.requestId || null,
+ projectStatusActiveRequestKey: action.meta?.requestKey || null,
+ projectStatusDataRequestKey: null,
+ projectStatusErrorRequestKey: null,
+ };
+
+ case FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_SUCCESS:
+ if (isStaleProjectStatusAction(state, action)) {
+ return state;
+ }
+
+ return {
+ ...state,
+ projectStatusLoading: false,
+ projectStatusData: action.payload,
+ projectStatusError: null,
+ projectStatusActiveRequestId: action.meta?.requestId || state.projectStatusActiveRequestId,
+ projectStatusActiveRequestKey:
+ action.meta?.requestKey || state.projectStatusActiveRequestKey,
+ projectStatusDataRequestKey: action.meta?.requestKey || null,
+ projectStatusErrorRequestKey: null,
+ };
+
+ case FETCH_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS_ERROR:
+ if (isStaleProjectStatusAction(state, action)) {
+ return state;
+ }
+
+ return {
+ ...state,
+ projectStatusLoading: false,
+ projectStatusData: null,
+ projectStatusError: action.payload,
+ projectStatusActiveRequestId: action.meta?.requestId || state.projectStatusActiveRequestId,
+ projectStatusActiveRequestKey:
+ action.meta?.requestKey || state.projectStatusActiveRequestKey,
+ projectStatusDataRequestKey: null,
+ projectStatusErrorRequestKey: action.meta?.requestKey || null,
+ };
+
default:
return state;
}
diff --git a/src/registerServiceWorker.js b/src/registerServiceWorker.js
index 8f11253be70..47905897c53 100644
--- a/src/registerServiceWorker.js
+++ b/src/registerServiceWorker.js
@@ -10,10 +10,10 @@
const isLocalhost = Boolean(
globalThis.location.hostname === 'localhost' ||
- // [::1] is the IPv6 localhost address.
- globalThis.location.hostname === '[::1]' ||
- // 127.0.0.1/8 is considered localhost for IPv4.
- globalThis.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/),
+ // [::1] is the IPv6 localhost address.
+ globalThis.location.hostname === '[::1]' ||
+ // 127.0.0.1/8 is considered localhost for IPv4.
+ globalThis.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/),
);
function registerValidSW(swUrl) {
@@ -66,13 +66,14 @@ function checkValidServiceWorker(swUrl) {
export default function register() {
if (import.meta.env.PROD && 'serviceWorker' in navigator) {
- const publicUrl = new URL(import.meta.env.BASE_URL, globalThis.location);
+ const baseUrl = import.meta.env.BASE_URL || '/';
+ const publicUrl = new URL(baseUrl, globalThis.location);
if (publicUrl.origin !== globalThis.location.origin) {
return;
}
globalThis.addEventListener('load', () => {
- const swUrl = `${import.meta.env.BASE_URL}service-worker.js`;
+ const swUrl = `${baseUrl}service-worker.js`;
if (isLocalhost) {
checkValidServiceWorker(swUrl);
@@ -80,7 +81,7 @@ export default function register() {
// eslint-disable-next-line no-console
console.log(
'This web app is being served cache-first by a service ' +
- 'worker. To learn more, visit https://goo.gl/SC7cgQ',
+ 'worker. To learn more, visit https://goo.gl/SC7cgQ',
);
});
} else {
diff --git a/src/routes.jsx b/src/routes.jsx
index 4d8725cad8b..3a90ee43292 100644
--- a/src/routes.jsx
+++ b/src/routes.jsx
@@ -63,6 +63,7 @@ import ToolsAvailabilityPage from './components/BMDashboard/WeeklyProjectSummary
import ResourceUsage from './components/CommunityPortal/ResourceUsage/ResourceUsage';
import DatabaseDesign from './components/CommunityPortal/DatabaseDesign/DatabaseDesign';
import Page1 from './components/HGNForm/pages/Page1';
+import HGNFormListPage from './components/HGNForm/pages/HGNFormListPage';
import Page2 from './components/HGNForm/pages/Page2';
import Page3 from './components/HGNForm/pages/Page3';
import Page4 from './components/HGNForm/pages/Page4';
@@ -88,6 +89,7 @@ import FeedbackModal from './components/HGNHelpSkillsDashboard/FeedbackModal';
import Activity from './components/CommunityPortal/Activities/activityId/Activity';
import ActivityAttendance from './components/CommunityPortal/Activities/ActivityAttendance';
import ActivityAgenda from './components/CommunityPortal/Activities/ActivityAgenda';
+import ResolvedTasks from './components/ResolvedTasks/ResolvedTasks';
import EventList from './components/CommunityPortal/Event/EventList/EventList';
import ResourcesUsage from './components/CommunityPortal/Activities/activityId/ResourcesUsage';
import EventParticipation from './components/CommunityPortal/Reports/Participation/EventParticipation';
@@ -179,6 +181,7 @@ import GardenManagement from './components/KitchenandInventory/GardenManagement/
import RecipesLandingPage from './components/KitchenandInventory/Recipes';
import KIDashboard from './components/KitchenandInventory/KIDashboard/KIDashboard';
+import KitchenReports from './components/KitchenandInventory/KitchenReports/KitchenReports';
import KIINVENTORY from './components/KitchenandInventory/KIInventory/KIInventory';
import KICalendar from './components/KitchenandInventory/KICalendar/KICalendar';
@@ -399,6 +402,7 @@ export default (
+
+
@@ -1166,7 +1171,8 @@ export default (
-
+
+
@@ -1261,7 +1267,7 @@ export default (
path="/pr-grading-dashboard"
exact
component={PRGradingDashboard}
- fallback
+ routePermissions={RoutePermissions.prDashboard}
/>
diff --git a/src/utils/URL.js b/src/utils/URL.js
index 7ba42ec5e0c..57df9efcd63 100644
--- a/src/utils/URL.js
+++ b/src/utils/URL.js
@@ -6,6 +6,7 @@ export const ENDPOINTS = {
APIEndpoint: () => APIEndpoint,
USER_PROFILES: `${APIEndpoint}/userProfile`,
USER_PROFILE: userId => `${APIEndpoint}/userprofile/${userId}`,
+ HGN_FORMS: `${APIEndpoint}/form`,
USER_PROFILE_FIXED: userId => `${APIEndpoint}/userProfile/${userId}`,
USER_PROFILE_PROPERTY: userId => `${APIEndpoint}/userprofile/${userId}/property`,
USER_PAUSE: userId => `${APIEndpoint}/userProfile/${userId}/pause`,
@@ -46,6 +47,8 @@ export const ENDPOINTS = {
USERS_REMOVE_PROFILE_IMAGE: `${APIEndpoint}/userProfile/profileImage/remove`,
USERS_UPDATE_PROFILE_FROM_WEBSITE: `${APIEndpoint}/userProfile/profileImage/imagefromwebsite`,
USER_PROFILE_BASIC_INFO: source => `${APIEndpoint}/userProfile/basicInfo/${source}`,
+ // Single-user lookup contract: backend reads this as req.query.userId, not a path segment.
+ USER_PROFILE_BASIC_INFO_BY_ID: userId => `${APIEndpoint}/userProfile/basicInfo?userId=${userId}`,
USER_AUTOCOMPLETE: searchText => `${APIEndpoint}/userProfile/autocomplete/${searchText}`,
SEARCH_USER: `${APIEndpoint}/users/search`,
TOGGLE_BIO_STATUS: userId => `${APIEndpoint}/userProfile/${userId}/toggleBio`,
@@ -114,6 +117,7 @@ export const ENDPOINTS = {
TASK_WBS: wbsId => `${APIEndpoint}/task/wbs/${wbsId}`,
TASKS_UPDATE: `${APIEndpoint}/tasks/update`,
TASKS_BY_USERID: userId => `${APIEndpoint}/tasks/user/${userId}`,
+ RESOLVED_TASKS: (page, limit) => `${APIEndpoint}/tasks/resolved?page=${page}&limit=${limit}`,
// TASKS_BY_userID: userId => `${APIEndpoint}/tasks/userProfile/${userId}`,
TASK_DEL: (taskId, motherId) => `${APIEndpoint}/task/del/${taskId}/${motherId}`,
GET_TASK: taskId => `${APIEndpoint}/task/${taskId}`,
@@ -134,6 +138,8 @@ export const ENDPOINTS = {
WEEKLY_SUMMARIES_FILTER_REPLACE_INDIVIDUAL_CODES: `${APIEndpoint}/weeklySummariesFilters/replaceIndividualTeamcodes`,
SAVE_SUMMARY_RECEPIENTS: userid => `${APIEndpoint}/reports/recepients/${userid}`,
GET_SUMMARY_RECEPIENTS: () => `${APIEndpoint}/reports/getrecepients`,
+ KITCHEN_REPORTS_EXPORT_PDF: () => `${APIEndpoint}/kitchenandinventory/reports/export/pdf`,
+ KITCHEN_REPORTS_EXPORT_CSV: () => `${APIEndpoint}/kitchenandinventory/reports/export/csv`,
GET_CURRENT_WARNINGS: () => `${APIEndpoint}/currentWarnings`,
POST_NEW_WARNING: () => `${APIEndpoint}/currentWarnings`,
UPDATE_WARNING_DESCRIPTION: warningId => `${APIEndpoint}/currentWarnings/${warningId}`,
@@ -165,7 +171,8 @@ export const ENDPOINTS = {
TEAM_BY_ID: teamId => `${APIEndpoint}/team/${teamId}`,
APPLICANT_VOLUNTEER_RATIO: `${APIEndpoint}/applicant-volunteer-ratio`,
USER_UNREAD_TASK_NOTIFICATIONS: userId => `${APIEndpoint}/tasknotification/user/${userId}`,
- BADGE: () => `${APIEndpoint}/badge`,
+ // BADGE: () => `${APIEndpoint}/badge`,
+ BADGE: () => `${APIEndpoint}/badge/getAllBadges`,
BADGE_ASSIGN_MULTIPLE: `${APIEndpoint}/badge/assign`,
BADGE_ASSIGN: userId => `${APIEndpoint}/badge/assign/${userId}`,
BADGE_BY_ID: badgeId => `${APIEndpoint}/badge/${badgeId}`,
@@ -380,9 +387,9 @@ export const ENDPOINTS = {
BM_PROJECTS_WITH_LOCATION: `${APIEndpoint}/bm/projects/location`,
BM_PROJECT_EXPENSE_BY_ID: projectId => `${APIEndpoint}/bm/project/${projectId}/expenses`,
BM_PROJECT_BY_ID: projectId => `${APIEndpoint}/bm/project/${projectId}`,
+ BM_WEEKLY_PROJECT_SUMMARY_PROJECT_STATUS: `${APIEndpoint}/bm/weekly-project-summary/project-status`,
BM_DELETE_INVENTORTY_TYPE_BY_ID: id => `${APIEndpoint}/bm/invtypes/${id}`,
BM_UPDATE_INVENTORY_TYPE_BY_ID: id => `${APIEndpoint}/bm/invtypes/${id}`,
-
BM_PROJECT_MATERIALS_COST: `${APIEndpoint}/material-costs`,
BM_UPDATE_MATERIAL: `${APIEndpoint}/bm/updateMaterialRecord`,
BM_UPDATE_MATERIAL_BULK: `${APIEndpoint}/bm/updateMaterialRecordBulk`,
@@ -649,15 +656,15 @@ export const ENDPOINTS = {
APPLICANT_SOURCES: `${APIEndpoint}/applicant-analytics/applicant-sources`,
OPT_STATUS_BREAKDOWN: (startDate, endDate, role) => {
- let url = `${APIEndpoint}/analytics/opt-status`;
- const params = [];
+ let url = `${APIEndpoint}/analytics/opt-status`;
+ const params = [];
- if (startDate) params.push(`startDate=${startDate}`);
- if (endDate) params.push(`endDate=${endDate}`);
- if (role) params.push(`role=${role}`);
+ if (startDate) params.push(`startDate=${startDate}`);
+ if (endDate) params.push(`endDate=${endDate}`);
+ if (role) params.push(`role=${role}`);
- return params.length > 0 ? `${url}?${params.join("&")}` : url;
-},
+ return params.length > 0 ? `${url}?${params.join("&")}` : url;
+ },
@@ -694,6 +701,7 @@ export const ENDPOINTS = {
// pr dashboard endpoints
PROMOTION_ELIGIBILITY: `${APIEndpoint}/promotion-eligibility`,
+ PROMOTION_PENDING: `${APIEndpoint}/promotion-pending`,
PROMOTE_MEMBERS: `${APIEndpoint}/promote-members`,
// LinkedIn autoposter endpoints
@@ -770,6 +778,7 @@ export const ENDPOINTS = {
// Help Request & Feedback Modal endpoints
HGN_FORM_RANKED: `${APIEndpoint}/hgnform/ranked`,
+ HGN_COMMUNITY_MEMBERS: `${APIEndpoint}/hgnHelp/community`,
HELP_REQUEST_CHECK_MODAL: userId => `${APIEndpoint}/helprequest/check-modal/${userId}`,
FEEDBACK_CLOSE_PERMANENTLY: `${APIEndpoint}/feedback/close-permanently`,
FEEDBACK_SUBMIT: `${APIEndpoint}/feedback/submit`,
diff --git a/src/utils/__tests__/bioQualification.test.js b/src/utils/__tests__/bioQualification.test.js
index 0bf176e8c66..c33f2faa8fd 100644
--- a/src/utils/__tests__/bioQualification.test.js
+++ b/src/utils/__tests__/bioQualification.test.js
@@ -1,17 +1,17 @@
import {
isQualifiedForBio,
BIO_MIN_TANGIBLE_HOURS,
- BIO_MIN_DAYS_IN_TEAM,
+ BIO_MIN_VALID_SUMMARIES,
} from '../bioQualification';
const qualified = {
totalTangibleHrs: 100,
- daysInTeam: 70,
+ weeklySummariesCount: 10,
bioPosted: 'default',
};
describe('isQualifiedForBio()', () => {
- it('qualifies a user over both thresholds whose bio is not posted', () => {
+ it('qualifies a user meeting both thresholds whose bio is not posted', () => {
expect(isQualifiedForBio(qualified)).toBe(true);
});
@@ -23,32 +23,46 @@ describe('isQualifiedForBio()', () => {
expect(isQualifiedForBio({ ...qualified, bioPosted: 'posted' })).toBe(false);
});
- it('requires strictly more than the hour threshold', () => {
+ it('requires at least the minimum tangible hours', () => {
+ expect(
+ isQualifiedForBio({ ...qualified, totalTangibleHrs: BIO_MIN_TANGIBLE_HOURS - 0.01 }),
+ ).toBe(false);
expect(isQualifiedForBio({ ...qualified, totalTangibleHrs: BIO_MIN_TANGIBLE_HOURS })).toBe(
- false,
- );
- expect(isQualifiedForBio({ ...qualified, totalTangibleHrs: BIO_MIN_TANGIBLE_HOURS + 1 })).toBe(
true,
);
});
- it('requires strictly more than the tenure threshold', () => {
- expect(isQualifiedForBio({ ...qualified, daysInTeam: BIO_MIN_DAYS_IN_TEAM })).toBe(false);
- expect(isQualifiedForBio({ ...qualified, daysInTeam: BIO_MIN_DAYS_IN_TEAM + 1 })).toBe(true);
+ it('requires at least the minimum valid weekly summaries', () => {
+ expect(
+ isQualifiedForBio({ ...qualified, weeklySummariesCount: BIO_MIN_VALID_SUMMARIES - 1 }),
+ ).toBe(false);
+ expect(
+ isQualifiedForBio({ ...qualified, weeklySummariesCount: BIO_MIN_VALID_SUMMARIES }),
+ ).toBe(true);
});
- // Tenure, not weekly summary output: 24e1c6ba2 swapped these and changed who qualifies.
- it('ignores weeklySummariesCount entirely', () => {
- expect(isQualifiedForBio({ ...qualified, daysInTeam: 10, weeklySummariesCount: 99 })).toBe(
- false,
- );
- expect(isQualifiedForBio({ ...qualified, weeklySummariesCount: 0 })).toBe(true);
+ // Reported 2026-10-13: 80+ hours and 60+ days in team, but only 3 valid summaries.
+ it('does not qualify on hours and tenure alone', () => {
+ expect(
+ isQualifiedForBio({ ...qualified, daysInTeam: 400, weeklySummariesCount: 3 }),
+ ).toBe(false);
+ });
+
+ it('ignores daysInTeam entirely', () => {
+ expect(isQualifiedForBio({ ...qualified, daysInTeam: 1 })).toBe(true);
+ });
+
+ it('accepts a numeric string for weeklySummariesCount', () => {
+ expect(isQualifiedForBio({ ...qualified, weeklySummariesCount: '8' })).toBe(true);
+ expect(isQualifiedForBio({ ...qualified, weeklySummariesCount: '3' })).toBe(false);
});
it('does not qualify when the row is missing or the fields are absent', () => {
expect(isQualifiedForBio(null)).toBe(false);
expect(isQualifiedForBio(undefined)).toBe(false);
expect(isQualifiedForBio({})).toBe(false);
- expect(isQualifiedForBio({ ...qualified, daysInTeam: undefined })).toBe(false);
+ expect(isQualifiedForBio({ ...qualified, weeklySummariesCount: undefined })).toBe(false);
+ expect(isQualifiedForBio({ ...qualified, weeklySummariesCount: null })).toBe(false);
+ expect(isQualifiedForBio({ ...qualified, totalTangibleHrs: undefined })).toBe(false);
});
});
diff --git a/src/utils/__tests__/emailValidation.test.js b/src/utils/__tests__/emailValidation.test.js
new file mode 100644
index 00000000000..8b816722352
--- /dev/null
+++ b/src/utils/__tests__/emailValidation.test.js
@@ -0,0 +1,140 @@
+import { checkEmail } from '../emailValidation';
+
+// With a suggestion on screen the error doesn't repeat "check for a typo"; the hint says it.
+const badEnding = (ending, suggestion) =>
+ `"${ending}" isn't a valid email domain ending.${suggestion ? '' : ' Check for a typo.'}`;
+
+describe('checkEmail', () => {
+ it('rejects a non-existent TLD and suggests the likely address', () => {
+ expect(checkEmail('rabstu@gmail.con')).toEqual({
+ error: badEnding('.con', true),
+ suggestion: 'rabstu@gmail.com',
+ });
+ });
+
+ it.each([
+ ['ada@yahoo.cmo', '.cmo', 'ada@yahoo.com'],
+ ['ada@outlook.comm', '.comm', 'ada@outlook.com'],
+ ['ada@company.con', '.con', 'ada@company.com'],
+ ['ada@school.ogr', '.ogr', 'ada@school.org'],
+ ['ada@company.qwzx', '.qwzx', null],
+ ['ada@gmial.con', '.con', 'ada@gmail.com'],
+ ['ada@hotmial.cmo', '.cmo', 'ada@hotmail.com'],
+ ['ada@gmail.CON', '.CON', 'ada@gmail.com'],
+ ])('blocks %s (%s) and suggests %s', (email, ending, suggestion) => {
+ expect(checkEmail(email)).toEqual({ error: badEnding(ending, suggestion), suggestion });
+ });
+
+ it.each([
+ ['ada@gmial.com', 'ada@gmail.com'],
+ ['ada@gamil.com', 'ada@gmail.com'],
+ ['ada@gmail.co', 'ada@gmail.com'],
+ ['ada@gmail.cm', 'ada@gmail.com'],
+ ['ada@hotmial.com', 'ada@hotmail.com'],
+ ['ada@yahooo.com', 'ada@yahoo.com'],
+ ['ada@outlok.com', 'ada@outlook.com'],
+ ['ada@iclod.com', 'ada@icloud.com'],
+ ['ada@yhoo.com', 'ada@yahoo.com'],
+ ['ada@hotmial.co.uk', 'ada@hotmail.co.uk'],
+ ['ada@hotmial.ca', 'ada@hotmail.ca'],
+ ['ada@yahoo.co', 'ada@yahoo.com'],
+ ])('allows %s but suggests %s', (email, suggestion) => {
+ expect(checkEmail(email)).toEqual({ error: '', suggestion });
+ });
+
+ it.each([
+ 'ada@gmail.com',
+ 'Ada.Lovelace+jobs@Gmail.com',
+ 'ada@mail.com',
+ 'ada@email.com',
+ 'ada@ymail.com',
+ 'ada@gm.com',
+ 'ada@me.com',
+ 'ada@company.co',
+ 'ada@startup.io',
+ 'ada@uni.ac.uk',
+ 'ada@yahoo.co.uk',
+ 'ada@例子.中国',
+ ' ada@example.org ',
+ // Real regional or company domains that sit close to a provider
+ 'ada@hotmail.ca',
+ 'ada@hotmail.cl',
+ 'ada@hotmail.co.jp',
+ 'ada@hotmail.fr',
+ 'ada@yahoo.co.jp',
+ 'ada@yahoo.co.id',
+ 'ada@yahoo.co.nz',
+ 'ada@yahoo.com.br',
+ 'ada@outlook.cl',
+ 'ada@outlook.co.uk',
+ 'ada@protonmail.ch',
+ 'ada@live.ca',
+ 'ada@gmx.de',
+ 'ada@cloud.com',
+ 'ada@unicloud.com',
+ 'ada@comcast.com',
+ ])('accepts %s without a suggestion', email => {
+ expect(checkEmail(email)).toEqual({ error: '', suggestion: null });
+ });
+
+ it('requires an email', () => {
+ expect(checkEmail(' ')).toEqual({ error: 'Email is required.', suggestion: null });
+ expect(checkEmail(undefined)).toEqual({ error: 'Email is required.', suggestion: null });
+ });
+
+ it.each([
+ ['ada@gmail', 'ada@gmail.com'],
+ ['ada@hotmail', 'ada@hotmail.com'],
+ ['ada@company', null],
+ ['ada', null],
+ ['ada@gmail.com.', 'ada@gmail.com'],
+ ['ada@gmail.com..', 'ada@gmail.com'],
+ ['ada@gmail.con.', 'ada@gmail.com'],
+ ['ada @gmail.com', null],
+ ])('flags malformed %s with suggestion %s', (email, suggestion) => {
+ expect(checkEmail(email)).toEqual({
+ error: 'Please enter a valid email address.',
+ suggestion,
+ });
+ });
+
+ it.each([
+ ['ada@gmail.c', 'ada@gmail.com'],
+ ['ada@gmail.xyzq', 'ada@gmail.com'],
+ ])('blocks %s and suggests the provider domain %s', (email, suggestion) => {
+ expect(checkEmail(email)).toEqual({ error: badEnding(`.${email.split('.').pop()}`, true), suggestion });
+ });
+
+ it('only ever suggests an address that passes the check itself', () => {
+ [
+ 'ada@gmail.c',
+ 'ada@gmail.con.',
+ 'ada@gmail.com..',
+ 'ada@gmial.con',
+ 'ada@school.ogr',
+ 'ada@hotmial.cmo',
+ 'ada@gmail',
+ ].forEach(email => {
+ const { suggestion } = checkEmail(email);
+ expect(suggestion).not.toBeNull();
+ expect(checkEmail(suggestion)).toEqual({ error: '', suggestion: null });
+ });
+ });
+
+ it('rejects hidden characters pasted into the address', () => {
+ expect(checkEmail('ada@hotmail.com\u200B')).toEqual({
+ error: 'This email has a hidden character in it. Please retype it.',
+ suggestion: null,
+ });
+ expect(checkEmail('a\u2060da@gmail.com').error).toBe(
+ 'This email has a hidden character in it. Please retype it.',
+ );
+ });
+
+ it('rejects addresses longer than 254 characters without slowing down', () => {
+ const long = `ada@${'a'.repeat(100000)}.com`;
+ const started = Date.now();
+ expect(checkEmail(long)).toEqual({ error: 'Please enter a valid email address.', suggestion: null });
+ expect(Date.now() - started).toBeLessThan(50);
+ });
+});
diff --git a/src/utils/__tests__/ianaTlds.test.js b/src/utils/__tests__/ianaTlds.test.js
new file mode 100644
index 00000000000..a0bc5af7836
--- /dev/null
+++ b/src/utils/__tests__/ianaTlds.test.js
@@ -0,0 +1,15 @@
+import { IANA_TLDS } from '../ianaTlds';
+
+describe('IANA_TLDS', () => {
+ it('holds every entry from the IANA file, each one whole', () => {
+ expect(IANA_TLDS.size).toBe(1437);
+ IANA_TLDS.forEach(tld => {
+ expect(tld).toMatch(/^([a-z]{2,}|xn--[a-z0-9-]+)$/);
+ });
+ });
+
+ it('includes internationalized endings such as .한국 and .中国', () => {
+ expect(IANA_TLDS.has('xn--3e0b707e')).toBe(true);
+ expect(IANA_TLDS.has('xn--fiqs8s')).toBe(true);
+ });
+});
diff --git a/src/utils/bioQualification.js b/src/utils/bioQualification.js
index d912f6e9d71..450ff122890 100644
--- a/src/utils/bioQualification.js
+++ b/src/utils/bioQualification.js
@@ -1,20 +1,21 @@
/**
* Single source of truth for "is this person qualified for a bio".
*
- * The rule below is the one that ran unchanged from 2023-07-15 (14078de29) until
- * 2026-05-12, when 24e1c6ba2 swapped tenure (`daysInTeam`) for weekly summary output
- * (`weeklySummariesCount`). Those measure different things — someone can be six months
- * in the team with three summaries — so the tenure rule is restored here.
+ * Business rule (confirmed by Jae, 2026-10-13): a team member qualifies for the yellow
+ * bio-announcement strip once they have
+ * 1. a minimum of 80 tangible hours logged, and
+ * 2. a minimum of 8 Total Valid Weekly Summaries (`weeklySummariesCount`),
+ * and their bio has not been posted yet.
*
- * `daysInTeam` is computed server-side in HGNRest (`reporthelper.weeklySummaries`) as a
- * $dateDiff in days from the user's createdDate, and survives `formatSummaries`.
+ * Tenure (`daysInTeam`) is not part of the rule: someone 60+ days in with only three
+ * valid summaries must not qualify.
*
* Keep every consumer on this function: the yellow highlight bar and the Bio Status
* filter must agree, or a user shows a bar while being absent from the filter list.
*/
export const BIO_MIN_TANGIBLE_HOURS = 80;
-export const BIO_MIN_DAYS_IN_TEAM = 60;
+export const BIO_MIN_VALID_SUMMARIES = 8;
/**
* @param {object} summary A weekly summaries report row.
@@ -23,9 +24,13 @@ export const BIO_MIN_DAYS_IN_TEAM = 60;
export const isQualifiedForBio = summary => {
if (!summary) return false;
+ // weeklySummariesCount can arrive as a string from the report payload.
+ const tangibleHrs = Number(summary.totalTangibleHrs);
+ const validSummaries = Number(summary.weeklySummariesCount);
+
return (
- summary.totalTangibleHrs > BIO_MIN_TANGIBLE_HOURS &&
- summary.daysInTeam > BIO_MIN_DAYS_IN_TEAM &&
+ tangibleHrs >= BIO_MIN_TANGIBLE_HOURS &&
+ validSummaries >= BIO_MIN_VALID_SUMMARIES &&
summary.bioPosted !== 'posted'
);
};
diff --git a/src/utils/emailValidation.js b/src/utils/emailValidation.js
new file mode 100644
index 00000000000..a33783366d7
--- /dev/null
+++ b/src/utils/emailValidation.js
@@ -0,0 +1,204 @@
+import { IANA_TLDS } from './ianaTlds';
+
+const MAX_EMAIL_LENGTH = 254;
+const WHITESPACE = /\s/;
+// Zero-width and word-joiner characters ride along with copy-paste and are invisible on screen
+const HIDDEN_CHARACTERS = /[\u200B-\u200D\u2060\uFEFF]/;
+const INVALID_EMAIL = 'Please enter a valid email address.';
+const HIDDEN_CHARACTER_ERROR = 'This email has a hidden character in it. Please retype it.';
+
+// Popular mailbox providers. A near miss of one of these is almost always a typo.
+// Regional domains are listed so their own typos get caught (hotmial.ca -> hotmail.ca).
+const POPULAR_DOMAINS = [
+ 'gmail.com',
+ 'googlemail.com',
+ 'yahoo.com',
+ 'yahoo.co.uk',
+ 'yahoo.co.in',
+ 'yahoo.ca',
+ 'ymail.com',
+ 'rocketmail.com',
+ 'outlook.com',
+ 'hotmail.com',
+ 'hotmail.co.uk',
+ 'hotmail.ca',
+ 'live.com',
+ 'msn.com',
+ 'icloud.com',
+ 'me.com',
+ 'mac.com',
+ 'aol.com',
+ 'protonmail.com',
+ 'proton.me',
+ 'mail.com',
+ 'email.com',
+ 'gmx.com',
+ 'gmx.net',
+ 'zoho.com',
+ 'yandex.com',
+ 'comcast.net',
+ 'verizon.net',
+ 'att.net',
+ 'sbcglobal.net',
+];
+
+// Endings offered when the typed one doesn't exist (".con" -> ".com").
+const COMMON_TLDS = ['com', 'net', 'org', 'edu', 'gov', 'io', 'us', 'uk', 'ca', 'in', 'de', 'info'];
+
+// Split "hotmail.co.uk" into the provider name and the rest.
+function splitDomain(domain) {
+ const dot = domain.indexOf('.');
+ return { name: domain.slice(0, dot), suffix: domain.slice(dot + 1) };
+}
+
+// Optimal string alignment distance: insertions, deletions, substitutions and adjacent swaps
+// each count as one edit, so "gmial" is one edit from "gmail".
+function editDistance(a, b) {
+ const d = Array.from({ length: a.length + 1 }, (_, i) => [i]);
+ for (let j = 1; j <= b.length; j += 1) d[0][j] = j;
+ for (let i = 1; i <= a.length; i += 1) {
+ for (let j = 1; j <= b.length; j += 1) {
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
+ d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
+ }
+ }
+ }
+ return d[a.length][b.length];
+}
+
+function closest(value, candidates, maxEdits) {
+ let best = null;
+ let bestDistance = maxEdits + 1;
+ candidates.forEach(candidate => {
+ const distance = editDistance(value, candidate);
+ if (distance < bestDistance) {
+ best = candidate;
+ bestDistance = distance;
+ }
+ });
+ return best;
+}
+
+function toAsciiTld(domain) {
+ try {
+ // The URL parser converts internationalized domains to punycode, which is how IANA lists them
+ return new URL(`http://${domain}`).hostname.split('.').pop();
+ } catch {
+ return domain.split('.').pop();
+ }
+}
+
+function hasRealTld(domain) {
+ return IANA_TLDS.has(toAsciiTld(domain));
+}
+
+// Provider names close enough to count as a typo. Short names allow one edit, long ones two,
+// and the first letter must match so different words ("cloud" vs "icloud") aren't "corrected".
+function isProviderTypo(name, providerName) {
+ if (name === providerName) return true;
+ if (name.length < 4 || name[0] !== providerName[0]) return false;
+ return editDistance(name, providerName) <= (name.length < 9 ? 1 : 2);
+}
+
+// Only the provider name is fuzzy-matched. The ending is kept unless it doesn't exist
+// (gmail.con) or is a one-letter slip of ".com" (gmail.co, gmail.cm), so real regional
+// domains like hotmail.ca, hotmail.cl or yahoo.co.jp are left alone.
+function suggestProvider(domain) {
+ const { name, suffix } = splitDomain(domain);
+ const providers = POPULAR_DOMAINS.filter(d => isProviderTypo(name, splitDomain(d).name));
+ const sameSuffix = providers.find(d => splitDomain(d).suffix === suffix);
+ if (sameSuffix) return sameSuffix;
+ if (!hasRealTld(domain)) {
+ const nearest = closest(suffix, providers.map(d => splitDomain(d).suffix), Infinity);
+ return providers.find(d => splitDomain(d).suffix === nearest) || null;
+ }
+ const dotCom = providers.find(d => splitDomain(d).suffix === 'com');
+ return dotCom && editDistance(suffix, 'com') === 1 ? dotCom : null;
+}
+
+function suggestDomain(domain) {
+ if (POPULAR_DOMAINS.includes(domain)) return null;
+ const provider = suggestProvider(domain);
+ if (provider) return provider;
+ if (hasRealTld(domain)) return null;
+ // Not a provider: fix only the ending ("school.ogr" -> "school.org")
+ const dot = domain.lastIndexOf('.');
+ const fixedTld = closest(domain.slice(dot + 1), COMMON_TLDS, 1);
+ return fixedTld ? `${domain.slice(0, dot + 1)}${fixedTld}` : null;
+}
+
+// Same rule as /^[^\s@]+@[^\s@]+\.[^\s@]+$/ (exactly one @, a non-empty local part, and a
+// dot inside the domain with characters on both sides), checked without a backtracking regex.
+function hasEmailFormat(value) {
+ if (WHITESPACE.test(value)) return false;
+ const parts = value.split('@');
+ if (parts.length !== 2 || !parts[0]) return false;
+ const domain = parts[1];
+ const dot = domain.indexOf('.', 1);
+ return dot > 0 && dot < domain.length - 1;
+}
+
+function stripTrailingDots(value) {
+ let end = value.length;
+ while (end > 0 && value[end - 1] === '.') end -= 1;
+ return value.slice(0, end);
+}
+
+// A suggestion is only offered if it would pass the check itself.
+function validSuggestion(local, domain) {
+ const suggestion = `${local}@${domain}`;
+ return hasEmailFormat(suggestion) && hasRealTld(domain) ? suggestion : null;
+}
+
+// A malformed address can still have an obvious intended form worth offering
+function suggestForMalformed(local, lowerDomain, at) {
+ if (at <= 0) return null;
+ // "name@gmail.com." only has stray trailing dots
+ const undotted = stripTrailingDots(lowerDomain);
+ if (undotted !== lowerDomain && hasEmailFormat(`${local}@${undotted}`)) {
+ const fixed = hasRealTld(undotted) ? undotted : suggestDomain(undotted);
+ return fixed ? validSuggestion(local, fixed) : null;
+ }
+ // "name@gmail" is missing its ending; offer the provider's full domain
+ if (!lowerDomain || lowerDomain.includes('.')) return null;
+ const provider = POPULAR_DOMAINS.find(d => d.split('.')[0] === lowerDomain);
+ return provider ? `${local}@${provider}` : null;
+}
+
+/**
+ * Validate an applicant's email address.
+ * `error` blocks submission: the address is malformed or its ending isn't a real top-level
+ * domain (name@gmail.con). `suggestion` is a likely intended address for common typos
+ * (name@gmial.com -> name@gmail.com); it never blocks, since the typed address may be real.
+ * @param {string} rawEmail
+ * @returns {{ error: string, suggestion: string | null }}
+ */
+export function checkEmail(rawEmail) {
+ const email = String(rawEmail ?? '').trim();
+ if (!email) return { error: 'Email is required.', suggestion: null };
+ if (email.length > MAX_EMAIL_LENGTH) return { error: INVALID_EMAIL, suggestion: null };
+ if (HIDDEN_CHARACTERS.test(email)) return { error: HIDDEN_CHARACTER_ERROR, suggestion: null };
+
+ const at = email.lastIndexOf('@');
+ const local = email.slice(0, at);
+ const domain = email.slice(at + 1);
+ const lowerDomain = domain.toLowerCase();
+
+ if (!hasEmailFormat(email) || lowerDomain.endsWith('.')) {
+ return { error: INVALID_EMAIL, suggestion: suggestForMalformed(local, lowerDomain, at) };
+ }
+
+ const suggestedDomain = suggestDomain(lowerDomain);
+ const suggestion = suggestedDomain ? validSuggestion(local, suggestedDomain) : null;
+
+ if (!hasRealTld(lowerDomain)) {
+ const tld = domain.split('.').pop();
+ // With a suggestion on screen, the hint already says how to fix it
+ const advice = suggestion ? '' : ' Check for a typo.';
+ return { error: `".${tld}" isn't a valid email domain ending.${advice}`, suggestion };
+ }
+
+ return { error: '', suggestion };
+}
diff --git a/src/utils/ianaTlds.js b/src/utils/ianaTlds.js
new file mode 100644
index 00000000000..1b47fef28d1
--- /dev/null
+++ b/src/utils/ianaTlds.js
@@ -0,0 +1,109 @@
+// Top-level domains from IANA: https://data.iana.org/TLD/tlds-alpha-by-domain.txt
+// Version 2026101300, Last Updated Tue Oct 13 07:07:02 2026 UTC. 1437 entries.
+// New TLDs are rare; regenerate from that file if a real domain ending is ever rejected.
+export const IANA_TLDS = new Set(
+ `
+aaa aarp abb abbott abbvie abc able abogado abudhabi ac academy accenture accountant accountants
+aco actor ad ads adult ae aeg aero aetna af afl africa ag agakhan agency ai aig airbus airforce
+airtel akdn al alibaba alipay allfinanz allstate ally alsace alstom am amazon americanexpress
+americanfamily amex amfam amica amsterdam analytics android anquan anz ao aol apartments app apple
+aq aquarelle ar arab aramco archi army arpa art arte as asda asia associates at athleta attorney
+au auction audi audible audio auspost author auto autos aw aws ax axa az azure ba baby baidu
+banamex band bank bar barcelona barclaycard barclays barefoot bargains baseball basketball bauhaus
+bayern bb bbc bbt bbva bcg bcn bd be beats beauty beer berlin best bestbuy bet bf bg bh bharti bi
+bible bid bike bing bingo bio biz bj black blackfriday blockbuster blog bloomberg blue bm bms bmw
+bn bnpparibas bo boats boehringer bofa bom bond boo book booking bosch bostik boston bot boutique
+box br bradesco bridgestone broadway broker brother brussels bs bt build builders business buy
+buzz bv bw by bz bzh ca cab cafe cal call calvinklein cam camera camp canon capetown capital
+capitalone car caravan cards care career careers cars casa case cash casino cat catering catholic
+cba cbn cbre cc cd center ceo cern cf cfa cfd cg ch chanel channel charity chase chat cheap
+chintai christmas chrome church ci cipriani circle cisco citadel citi citic city ck cl claims
+cleaning click clinic clinique clothing cloud club clubmed cm cn co coach codes coffee college
+cologne com commbank community company compare computer comsec condos construction consulting
+contact contractors cooking cool coop corsica country coupon coupons courses cpa cr credit
+creditcard creditunion cricket crown crs cruise cruises cu cuisinella cv cw cx cy cymru cyou cz
+dad dance data date dating datsun day dclk dds de deal dealer deals degree delivery dell deloitte
+delta democrat dental dentist desi design dev dhl diamonds diet digital direct directory discount
+discover dish diy dj dk dm dnp do docs doctor dog domains dot download drive dtv dubai dupont
+durban dvag dvr dz earth eat ec eco edeka edu education ee eg email emerck energy engineer
+engineering enterprises epson equipment er ericsson erni es esq estate et eu eurovision eus events
+exchange expert exposed express extraspace fage fail fairwinds faith family fan fans farm farmers
+fashion fast fedex feedback ferrari ferrero fi fidelity fido film final finance financial fire
+firestone firmdale fish fishing fit fitness fj fk flickr flights flir florist flowers fly fm fo
+foo food football ford forex forsale forum foundation fox fr free fresenius frl frogans frontier
+ftr fujitsu fun fund furniture futbol fyi ga gal gallery gallo gallup game games gap garden gay gb
+gbiz gd gdn ge gea gent genting george gf gg ggee gh gi gift gifts gives giving gl glass gle
+global globo gm gmail gmbh gmo gmx gn godaddy gold goldpoint golf goodyear goog google gop got gov
+gp gq gr grainger graphics gratis green gripe grocery group gs gt gu gucci guge guide guitars guru
+gw gy hair hamburg hangout haus hbo hdfc hdfcbank health healthcare help helsinki here hermes
+hiphop hisamitsu hitachi hiv hk hkt hm hn hockey holdings holiday homedepot homegoods homes
+homesense honda horse hospital host hosting hot hotels hotmail house how hr hsbc ht hu hughes
+hyatt hyundai ibm icbc ice icu id ie ieee ifm ikano il im imamat imdb immo immobilien in inc
+industries infiniti info ing ink institute insurance insure int international intuit investments
+io ipiranga iq ir irish is ismaili ist istanbul it itau itv jaguar java jcb je jeep jetzt jewelry
+jio jll jm jmp jnj jo jobs joburg jot joy jp jpmorgan jprs juegos kaufen kddi ke kerryhotels
+kerryproperties kfh kg kh ki kia kids kim kindle kitchen kiwi km kn koeln komatsu kosher kp kpmg
+kpn kr krd kred kuokgroup kw ky kyoto kz la lacaixa lamborghini lamer land landrover lanxess
+lasalle lat latino latrobe law lawyer lb lc lds lease leclerc lefrak legal lego lexus lgbt li lidl
+life lifeinsurance lifestyle lighting like lilly limited limo lincoln link live living lk llc llp
+loan loans locker locus lol london lotte lotto love lpl lplfinancial lr ls lt ltd ltda lu lundbeck
+luxe luxury lv ly ma madrid maif maison makeup man management mango map market marketing markets
+marriott marshalls mattel mba mc mckinsey md me med media meet melbourne meme memorial men menu
+merck merckmsd mg mh miami microsoft mil mini mint mit mitsubishi mk ml mlb mls mm mma mn mo mobi
+mobile moda moe moi mom monash money monster mormon mortgage moscow moto motorcycles mov movie mp
+mq mr ms msd mt mtn mtr mu museum music mv mw mx my mz na nab nagoya name navy nba nc ne nec net
+netbank netflix network neustar new news next nextdirect nexus nf nfl ng ngo nhk ni nico nike
+nikon ninja nissan nissay nl no nokia norton now nowruz nowtv np nr nra nrw ntt nu nyc nz obi
+observer office okinawa olayan olayangroup ollo om omega one ong onl online ooo open oracle orange
+org organic origins osaka otsuka ott ovh pa page panasonic paris pars partners parts party pay
+pccw pe pet pf pfizer pg ph pharmacy phd philips phone photo photography photos physio pics pictet
+pictures pid pin ping pink pioneer pizza pk pl place play playstation plumbing plus pm pn pnc pohl
+poker politie porn post pr praxi press prime pro prod productions prof progressive promo
+properties property protection pru prudential ps pt pub pw pwc py qa qpon quebec quest racing
+radio re read realestate realtor realty recipes red redumbrella rehab reise reisen reit reliance
+ren rent rentals repair report republican rest restaurant review reviews rexroth rich richardli
+ricoh ril rio rip ro rocks rodeo rogers room rs rsvp ru rugby ruhr run rw rwe ryukyu sa saarland
+safe safety sakura sale salon samsclub samsung sandvik sandvikcoromant sanofi sap sarl sas save
+saxo sb sbi sbs sc scb schaeffler schmidt scholarships school schule schwarz science scot sd se
+search seat secure security seek select sener services seven sew sex sexy sfr sg sh shangrila
+sharp shell shia shiksha shoes shop shopping shouji show si silk sina singles site sj sk ski skin
+sky skype sl sling sm smart smile sn sncf so soccer social softbank software sohu solar solutions
+song sony soy spa space sport spot sr srl ss st stada staples star statebank statefarm stc
+stcgroup stockholm storage store stream studio study style su sucks supplies supply support surf
+surgery suzuki sv swatch swiss sx sy sydney systems sz tab taipei talk taobao target tatamotors
+tatar tattoo tax taxi tc tci td tdk team tech technology tel temasek tennis teva tf tg th thd
+theater theatre tiaa tickets tienda tips tires tirol tj tjmaxx tjx tk tkmaxx tl tm tmall tn to
+today tokyo tools top toray toshiba total tours town toyota toys tr trade trading training travel
+travelers travelersinsurance trust trv tt tube tui tunes tushu tv tvs tw tz ua ubank ubs ug uk
+unicom university uno uol ups us uy uz va vacations vana vanguard vc ve vegas ventures verisign
+versicherung vet vg vi viajes video vig viking villas vin vip virgin visa vision viva vivo
+vlaanderen vn vodka volvo vote voting voto voyage vu wales walmart walter wang wanggou watch
+watches weather weatherchannel web webcam weber website wed wedding weibo weir wf whoswho wien
+wiki williamhill win windows wine winners wme woodside work works world wow ws wtc wtf xbox xerox
+xihuan xin xn--11b4c3d xn--1ck2e1b xn--1qqw23a xn--2scrj9c xn--30rr7y xn--3bst00m xn--3ds443g
+xn--3e0b707e xn--3hcrj9c xn--3pxu8k xn--42c2d9a xn--45br5cyl xn--45brj9c xn--45q11c xn--4dbrk0ce
+xn--4gbrim xn--54b7fta0cc xn--55qw42g xn--55qx5d xn--5su34j936bgsg xn--5tzm5g xn--6frz82g
+xn--6qq986b3xl xn--80adxhks xn--80ao21a xn--80aqecdr1a xn--80asehdb xn--80aswg xn--8y0a063a
+xn--90a3ac xn--90ae xn--90ais xn--9dbq2a xn--9et52u xn--9krt00a xn--b4w605ferd xn--bck1b9a5dre4c
+xn--c1avg xn--c2br7g xn--cck2b3b xn--cckwcxetd xn--cg4bki xn--clchc0ea0b2g2a9gcd xn--czr694b
+xn--czrs0t xn--czru2d xn--d1acj3b xn--d1alf xn--e1a4c xn--eckvdtc9d xn--efvy88h xn--fct429k
+xn--fhbei xn--fiq228c5hs xn--fiq64b xn--fiqs8s xn--fiqz9s xn--fjq720a xn--flw351e xn--fpcrj9c3d
+xn--fzc2c9e2c xn--fzys8d69uvgm xn--g2xx48c xn--gckr3f0f xn--gecrj9c xn--gk3at1e xn--h2breg3eve
+xn--h2brj9c xn--h2brj9c8c xn--hxt814e xn--i1b6b1a6a2e xn--imr513n xn--io0a7i xn--j1aef xn--j1amh
+xn--j6w193g xn--jlq480n2rg xn--jvr189m xn--kcrx77d1x4a xn--kprw13d xn--kpry57d xn--kput3i
+xn--l1acc xn--lgbbat1ad8j xn--mgb9awbf xn--mgba3a3ejt xn--mgba3a4f16a xn--mgba7c0bbn0a
+xn--mgbaam7a8h xn--mgbab2bd xn--mgbah1a3hjkrd xn--mgbai9azgqp6j xn--mgbayh7gpa xn--mgbbh1a
+xn--mgbbh1a71e xn--mgbc0a9azcg xn--mgbca7dzdo xn--mgbcpq6gpa1a xn--mgberp4a5d4ar xn--mgbgu82a
+xn--mgbi4ecexp xn--mgbpl2fh xn--mgbt3dhd xn--mgbtx2b xn--mgbx4cd0ab xn--mix891f xn--mk1bu44c
+xn--mxtq1m xn--ngbc5azd xn--ngbe9e0a xn--ngbrx xn--node xn--nqv7f xn--nqv7fs00ema xn--nyqy26a
+xn--o3cw4h xn--ogbpf8fl xn--otu796d xn--p1acf xn--p1ai xn--pgbs0dh xn--pssy2u xn--q7ce6a
+xn--q9jyb4c xn--qcka1pmc xn--qxa6a xn--qxam xn--rhqv96g xn--rovu88b xn--rvc1e0am3e xn--s9brj9c
+xn--ses554g xn--t60b56a xn--tckwe xn--tiq49xqyj xn--unup4y xn--vermgensberater-ctb
+xn--vermgensberatung-pwb xn--vhquv xn--vuq861b xn--w4r85el8fhu5dnra xn--w4rs40l xn--wgbh1c
+xn--wgbl6a xn--xhq521b xn--xkc2al3hye2a xn--xkc2dl3a5ee0h xn--y9a3aq xn--yfro4i67o xn--ygbi2ammx
+xn--zfr164b xxx xyz yachts yahoo yamaxun yandex ye yodobashi yoga yokohama you youtube yt yun za
+zappos zara zero zip zm zone zuerich zw
+`
+ .trim()
+ .split(/\s+/),
+);
diff --git a/yarn.lock b/yarn.lock
index 1576ab7fb3d..08282b80582 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -209,6 +209,19 @@
gl-matrix "^3.4.3"
tslib "^2.5.3"
+"@antv/g-lite@2.7.2":
+ version "2.7.2"
+ resolved "https://registry.npmjs.org/@antv/g-lite/-/g-lite-2.7.2.tgz"
+ integrity sha512-hno43swzUX3jBSi26O0l9YJxRVTiZ5HcNb3K6YKf8DRyT/hDo1zvnyD3vm/AmNa9va+3W76VznKgJThn4zRhYw==
+ dependencies:
+ "@antv/g-math" "3.1.0"
+ "@antv/util" "^3.3.5"
+ "@antv/vendor" "^1.0.3"
+ "@babel/runtime" "^7.25.6"
+ eventemitter3 "^5.0.1"
+ gl-matrix "^3.4.3"
+ tslib "^2.5.3"
+
"@antv/g-math@3.1.0":
version "3.1.0"
resolved "https://registry.npmjs.org/@antv/g-math/-/g-math-3.1.0.tgz"
@@ -240,6 +253,17 @@
gl-matrix "^3.4.3"
tslib "^2.5.3"
+"@antv/g@^6.1.11", "@antv/g@^6.1.24", "@antv/g@^6.1.28", "@antv/g@^6.1.7":
+ version "6.3.3"
+ resolved "https://registry.npmjs.org/@antv/g/-/g-6.3.3.tgz"
+ integrity sha512-Fs07ACQ7LqIpGFhL1wrVhdNbpsezL+vZKzKnKQj3dFzmEHu3S0sSumJtx2iCjy5shQPDWFu8vqSRdYrZdTVlGg==
+ dependencies:
+ "@antv/g-lite" "2.7.2"
+ "@antv/util" "^3.3.5"
+ "@babel/runtime" "^7.25.6"
+ gl-matrix "^3.4.3"
+ html2canvas "^1.4.1"
+
"@antv/g2-extension-plot@^0.2.1":
version "0.2.2"
resolved "https://registry.npmjs.org/@antv/g2-extension-plot/-/g2-extension-plot-0.2.2.tgz"
@@ -275,7 +299,7 @@
"@antv/g" "^6.1.24"
"@antv/g-svg" "^2.0.38"
-"@antv/g6@^5.0.28", "@antv/g6@^5.0.44":
+"@antv/g6@^5.0.28", "@antv/g6@^5.0.44", "@antv/g6@^5.1.0":
version "5.1.1"
resolved "https://registry.npmjs.org/@antv/g6/-/g6-5.1.1.tgz"
integrity sha512-50bXxMUf4mChyOv4ePVeWZLwotih9VunKfp0a++Wofv/wCyY8fb9+CV2wouIBCOZnd5ydBRA4NNaX9yLJzqa2w==
@@ -292,17 +316,6 @@
"@antv/util" "^3.3.11"
bubblesets-js "^2.3.4"
-"@antv/g@^6.1.11", "@antv/g@^6.1.24", "@antv/g@^6.1.28", "@antv/g@^6.1.7":
- version "6.3.1"
- resolved "https://registry.npmjs.org/@antv/g/-/g-6.3.1.tgz"
- integrity sha512-WYEKqy86LHB2PzTmrZXrIsIe+3Epeds2f68zceQ+BJtRoGki7Sy4IhlC8LrUMztgfT1t3d/0L745NWZwITroKA==
- dependencies:
- "@antv/g-lite" "2.7.0"
- "@antv/util" "^3.3.5"
- "@babel/runtime" "^7.25.6"
- gl-matrix "^3.4.3"
- html2canvas "^1.4.1"
-
"@antv/graphin@^3.0.4":
version "3.0.5"
resolved "https://registry.npmjs.org/@antv/graphin/-/graphin-3.0.5.tgz"
@@ -340,7 +353,16 @@
ml-matrix "^6.10.4"
tslib "^2.8.1"
-"@antv/scale@^0.4.12", "@antv/scale@^0.4.16":
+"@antv/scale@^0.4.12":
+ version "0.4.16"
+ resolved "https://registry.npmjs.org/@antv/scale/-/scale-0.4.16.tgz"
+ integrity sha512-5wg/zB5kXHxpTV5OYwJD3ja6R8yTiqIOkjOhmpEJiowkzRlbEC/BOyMvNUq5fqFIHnMCE9woO7+c3zxEQCKPjw==
+ dependencies:
+ "@antv/util" "^3.3.7"
+ color-string "^1.5.5"
+ fecha "^4.2.1"
+
+"@antv/scale@^0.4.16":
version "0.4.16"
resolved "https://registry.npmjs.org/@antv/scale/-/scale-0.4.16.tgz"
integrity sha512-5wg/zB5kXHxpTV5OYwJD3ja6R8yTiqIOkjOhmpEJiowkzRlbEC/BOyMvNUq5fqFIHnMCE9woO7+c3zxEQCKPjw==
@@ -433,50 +455,41 @@
"@csstools/css-tokenizer" "^3.0.3"
lru-cache "^10.4.3"
-"@babel/code-frame@7.12.11":
- version "7.12.11"
- resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz"
- integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==
- dependencies:
- "@babel/highlight" "^7.10.4"
-
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1":
- version "7.27.1"
- resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz"
- integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==
- dependencies:
- "@babel/helper-validator-identifier" "^7.27.1"
- js-tokens "^4.0.0"
- picocolors "^1.1.1"
-
-"@babel/code-frame@^7.29.0", "@babel/code-frame@^7.29.7":
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1", "@babel/code-frame@^7.29.7":
version "7.29.7"
- resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7"
+ resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz"
integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==
dependencies:
"@babel/helper-validator-identifier" "^7.29.7"
js-tokens "^4.0.0"
picocolors "^1.1.1"
+"@babel/code-frame@7.12.11":
+ version "7.12.11"
+ resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz"
+ integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==
+ dependencies:
+ "@babel/highlight" "^7.10.4"
+
"@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.0", "@babel/compat-data@^7.29.7":
version "7.29.7"
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz"
integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==
-"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.27.4", "@babel/core@^7.29.6":
- version "7.29.6"
- resolved "https://registry.npmjs.org/@babel/core/-/core-7.29.6.tgz"
- integrity sha512-QdxmAo/ikZqqRGA8s43ww8lcql6naWRvEz0FFrl6MIlc7Gi6TroXnSdWa5U/kq6fzcpqpHesicQxFZIieZbyIA==
- dependencies:
- "@babel/code-frame" "^7.29.0"
- "@babel/generator" "^7.29.6"
- "@babel/helper-compilation-targets" "^7.28.6"
- "@babel/helper-module-transforms" "^7.28.6"
- "@babel/helpers" "^7.29.2"
- "@babel/parser" "^7.29.3"
- "@babel/template" "^7.28.6"
- "@babel/traverse" "^7.29.0"
- "@babel/types" "^7.29.0"
+"@babel/core@^7.0.0", "@babel/core@^7.0.0 || ^8.0.0-0", "@babel/core@^7.0.0-0", "@babel/core@^7.0.0-0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.11.0", "@babel/core@^7.11.0 || ^8.0.0-0", "@babel/core@^7.11.0 || ^8.0.0-beta.1 || ^8.0.0", "@babel/core@^7.11.6", "@babel/core@^7.12.0", "@babel/core@^7.12.3", "@babel/core@^7.13.0", "@babel/core@^7.23.9", "@babel/core@^7.27.4", "@babel/core@^7.28.0", "@babel/core@^7.29.6", "@babel/core@^7.4.0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.8.0":
+ version "7.29.7"
+ resolved "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz"
+ integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==
+ dependencies:
+ "@babel/code-frame" "^7.29.7"
+ "@babel/generator" "^7.29.7"
+ "@babel/helper-compilation-targets" "^7.29.7"
+ "@babel/helper-module-transforms" "^7.29.7"
+ "@babel/helpers" "^7.29.7"
+ "@babel/parser" "^7.29.7"
+ "@babel/template" "^7.29.7"
+ "@babel/traverse" "^7.29.7"
+ "@babel/types" "^7.29.7"
"@jridgewell/remapping" "^2.3.5"
convert-source-map "^2.0.0"
debug "^4.1.0"
@@ -484,15 +497,6 @@
json5 "^2.2.3"
semver "^6.3.1"
-"@babel/core@^7.28.0":
- version "7.28.0"
- resolved "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz"
- integrity sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==
- dependencies:
- "@babel/helper-validator-identifier" "^7.29.7"
- js-tokens "^4.0.0"
- picocolors "^1.1.1"
-
"@babel/eslint-parser@^7.28.0":
version "7.28.0"
resolved "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.28.0.tgz"
@@ -502,7 +506,7 @@
eslint-visitor-keys "^2.1.0"
semver "^6.3.1"
-"@babel/generator@^7.27.5", "@babel/generator@^7.29.6", "@babel/generator@^7.29.8", "@babel/generator@^7.7.2":
+"@babel/generator@^7.27.5", "@babel/generator@^7.29.7", "@babel/generator@^7.29.8":
version "7.29.8"
resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz"
integrity sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==
@@ -520,7 +524,7 @@
dependencies:
"@babel/types" "^7.27.3"
-"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2", "@babel/helper-compilation-targets@^7.28.6":
+"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2", "@babel/helper-compilation-targets@^7.29.7":
version "7.29.7"
resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz"
integrity sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==
@@ -585,7 +589,7 @@
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
-"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.6":
+"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.29.7":
version "7.29.7"
resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz"
integrity sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==
@@ -601,10 +605,10 @@
dependencies:
"@babel/types" "^7.27.1"
-"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0":
- version "7.27.1"
- resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz"
- integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==
+"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.29.7", "@babel/helper-plugin-utils@^7.8.0":
+ version "7.29.7"
+ resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz"
+ integrity sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==
"@babel/helper-remap-async-to-generator@^7.27.1":
version "7.27.1"
@@ -637,7 +641,7 @@
resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz"
integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==
-"@babel/helper-validator-identifier@^7.12.11", "@babel/helper-validator-identifier@^7.25.9", "@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.29.7":
+"@babel/helper-validator-identifier@^7.12.11", "@babel/helper-validator-identifier@^7.25.9", "@babel/helper-validator-identifier@^7.29.7":
version "7.29.7"
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz"
integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==
@@ -656,13 +660,13 @@
"@babel/traverse" "^7.28.3"
"@babel/types" "^7.28.2"
-"@babel/helpers@^7.29.2":
- version "7.29.7"
- resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz"
- integrity sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==
+"@babel/helpers@^7.29.7":
+ version "7.29.10"
+ resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz"
+ integrity sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==
dependencies:
"@babel/template" "^7.29.7"
- "@babel/types" "^7.29.7"
+ "@babel/types" "^7.29.8"
"@babel/highlight@^7.10.4":
version "7.25.9"
@@ -674,10 +678,10 @@
js-tokens "^4.0.0"
picocolors "^1.0.0"
-"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.4", "@babel/parser@^7.29.3", "@babel/parser@^7.29.7", "@babel/parser@^7.29.8":
- version "7.29.8"
- resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz"
- integrity sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==
+"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.4", "@babel/parser@^7.29.7", "@babel/parser@^7.29.8", "@babel/parser@^7.29.9":
+ version "7.29.9"
+ resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz"
+ integrity sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==
dependencies:
"@babel/types" "^7.29.8"
@@ -789,7 +793,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
-"@babel/plugin-syntax-jsx@^7.27.1", "@babel/plugin-syntax-jsx@^7.7.2":
+"@babel/plugin-syntax-jsx@^7.27.1":
version "7.27.1"
resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz"
integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==
@@ -852,12 +856,12 @@
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"
-"@babel/plugin-syntax-typescript@^7.27.1", "@babel/plugin-syntax-typescript@^7.7.2":
- version "7.27.1"
- resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz"
- integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==
+"@babel/plugin-syntax-typescript@^7.27.1":
+ version "7.29.7"
+ resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz"
+ integrity sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==
dependencies:
- "@babel/helper-plugin-utils" "^7.27.1"
+ "@babel/helper-plugin-utils" "^7.29.7"
"@babel/plugin-syntax-unicode-sets-regex@^7.18.6":
version "7.18.6"
@@ -1064,14 +1068,14 @@
"@babel/helper-plugin-utils" "^7.27.1"
"@babel/plugin-transform-modules-systemjs@^7.27.1":
- version "7.27.1"
- resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz"
- integrity sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==
+ version "7.29.8"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.8.tgz"
+ integrity sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==
dependencies:
- "@babel/helper-module-transforms" "^7.27.1"
- "@babel/helper-plugin-utils" "^7.27.1"
- "@babel/helper-validator-identifier" "^7.27.1"
- "@babel/traverse" "^7.27.1"
+ "@babel/helper-module-transforms" "^7.29.7"
+ "@babel/helper-plugin-utils" "^7.29.7"
+ "@babel/helper-validator-identifier" "^7.29.7"
+ "@babel/traverse" "^7.29.8"
"@babel/plugin-transform-modules-umd@^7.27.1":
version "7.27.1"
@@ -1420,7 +1424,7 @@
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz"
integrity sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==
-"@babel/template@^7.27.1", "@babel/template@^7.27.2", "@babel/template@^7.28.6", "@babel/template@^7.29.7", "@babel/template@^7.3.3":
+"@babel/template@^7.27.1", "@babel/template@^7.27.2", "@babel/template@^7.29.7", "@babel/template@^7.3.3":
version "7.29.7"
resolved "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz"
integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==
@@ -1429,20 +1433,20 @@
"@babel/parser" "^7.29.7"
"@babel/types" "^7.29.7"
-"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.29.0", "@babel/traverse@^7.29.7":
- version "7.29.8"
- resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz"
- integrity sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==
+"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.29.7", "@babel/traverse@^7.29.8":
+ version "7.29.10"
+ resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz"
+ integrity sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==
dependencies:
"@babel/code-frame" "^7.29.7"
"@babel/generator" "^7.29.8"
"@babel/helper-globals" "^7.29.7"
- "@babel/parser" "^7.29.8"
+ "@babel/parser" "^7.29.9"
"@babel/template" "^7.29.7"
"@babel/types" "^7.29.8"
debug "^4.3.1"
-"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.4", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.29.0", "@babel/types@^7.29.7", "@babel/types@^7.29.8", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
+"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.4", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.29.7", "@babel/types@^7.29.8", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
version "7.29.8"
resolved "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz"
integrity sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==
@@ -1551,9 +1555,9 @@
integrity sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==
"@csstools/css-syntax-patches-for-csstree@^1.0.19", "@csstools/css-syntax-patches-for-csstree@^1.1.4":
- version "1.1.8"
- resolved "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.8.tgz"
- integrity sha512-CpMLjAvwQg3BL5S0IeqsZNMH7EQrEWi0kLKOC13ZBF0ZwERiLWlibNPJr8G1kdU3Ms/r2KiNrF81pUh2HwAHdg==
+ version "1.1.9"
+ resolved "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.9.tgz"
+ integrity sha512-iGGw4OsAYsS6pD29MdJ2bX/nJx65a04ZZiw6x+VwWlP2DdXf6f++Zmuv/OzALpdyfVhjbduIIF2cXM7HWBIe9A==
"@csstools/css-tokenizer@^3.0.3", "@csstools/css-tokenizer@^3.0.4":
version "3.0.4"
@@ -1592,28 +1596,6 @@
resolved "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.2.1.tgz"
integrity sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==
-"@emnapi/core@1.10.0":
- version "1.10.0"
- resolved "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz"
- integrity sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==
- dependencies:
- "@emnapi/wasi-threads" "1.2.1"
- tslib "^2.4.0"
-
-"@emnapi/runtime@1.10.0":
- version "1.10.0"
- resolved "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz"
- integrity sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==
- dependencies:
- tslib "^2.4.0"
-
-"@emnapi/wasi-threads@1.2.1":
- version "1.2.1"
- resolved "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz"
- integrity sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==
- dependencies:
- tslib "^2.4.0"
-
"@emotion/babel-plugin@^11.13.5":
version "11.13.5"
resolved "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz"
@@ -1652,7 +1634,7 @@
resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz"
integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==
-"@emotion/is-prop-valid@1.4.0", "@emotion/is-prop-valid@^1.3.0":
+"@emotion/is-prop-valid@^1.3.0", "@emotion/is-prop-valid@1.4.0":
version "1.4.0"
resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz"
integrity sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==
@@ -1664,7 +1646,7 @@
resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz"
integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==
-"@emotion/react@^11.8.1":
+"@emotion/react@^11.0.0-rc.0", "@emotion/react@^11.4.1", "@emotion/react@^11.5.0", "@emotion/react@^11.8.1", "@emotion/react@^11.9.0":
version "11.14.0"
resolved "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz"
integrity sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==
@@ -1694,7 +1676,7 @@
resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz"
integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==
-"@emotion/styled@^11.14.0":
+"@emotion/styled@^11.14.0", "@emotion/styled@^11.3.0", "@emotion/styled@^11.8.1":
version "11.14.1"
resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz"
integrity sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==
@@ -1753,7 +1735,7 @@
"@ephox/bedrock-common" "^13.0.0"
"@ephox/dispute" "^1.0.3"
-"@ephox/bedrock-common@11 || 12 || 13", "@ephox/bedrock-common@^13.0.0":
+"@ephox/bedrock-common@^13.0.0", "@ephox/bedrock-common@11 || 12 || 13":
version "13.0.0"
resolved "https://registry.npmjs.org/@ephox/bedrock-common/-/bedrock-common-13.0.0.tgz"
integrity sha512-aJ3gtezZ0duGJAniT/+uWi0fFCJRqNdJjumxhCwOVTc2MSsXcyqn70s6FIlrp/Fw2QuyiEqxgormgrmBUbWNWA==
@@ -1807,136 +1789,11 @@
"@ephox/katamari" "^9.1.6"
"@ephox/sand" "^6.0.10"
-"@esbuild/aix-ppc64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz"
- integrity sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==
-
-"@esbuild/android-arm64@0.25.9":
- 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==
-
-"@esbuild/android-arm@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz"
- integrity sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==
-
-"@esbuild/android-x64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz"
- integrity sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==
-
"@esbuild/darwin-arm64@0.25.9":
version "0.25.9"
resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz"
integrity sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==
-"@esbuild/darwin-x64@0.25.9":
- 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==
-
-"@esbuild/freebsd-arm64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz"
- integrity sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==
-
-"@esbuild/freebsd-x64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz"
- integrity sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==
-
-"@esbuild/linux-arm64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz"
- integrity sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==
-
-"@esbuild/linux-arm@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz"
- integrity sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==
-
-"@esbuild/linux-ia32@0.25.9":
- 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==
-
-"@esbuild/linux-loong64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz"
- integrity sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==
-
-"@esbuild/linux-mips64el@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz"
- integrity sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==
-
-"@esbuild/linux-ppc64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz"
- integrity sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==
-
-"@esbuild/linux-riscv64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz"
- integrity sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==
-
-"@esbuild/linux-s390x@0.25.9":
- 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==
-
-"@esbuild/linux-x64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz"
- integrity sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==
-
-"@esbuild/netbsd-arm64@0.25.9":
- 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==
-
-"@esbuild/netbsd-x64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz"
- integrity sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==
-
-"@esbuild/openbsd-arm64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz"
- integrity sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==
-
-"@esbuild/openbsd-x64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz"
- integrity sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==
-
-"@esbuild/openharmony-arm64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz"
- integrity sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==
-
-"@esbuild/sunos-x64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz"
- integrity sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==
-
-"@esbuild/win32-arm64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz"
- integrity sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==
-
-"@esbuild/win32-ia32@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz"
- integrity sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==
-
-"@esbuild/win32-x64@0.25.9":
- version "0.25.9"
- resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz"
- integrity sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==
-
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0", "@eslint-community/eslint-utils@^4.9.1":
version "4.9.1"
resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz"
@@ -2068,7 +1925,7 @@
resolved "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.0.1.tgz"
integrity sha512-0VpNtO5cNe1/HQWMkl4OdncYK/mv9hnBte0Ew0n6DMzmo3Q3WzDFABHm6LeNTipt5zAyhQ6Ugjiu8aLaEjh1gg==
-"@fortawesome/fontawesome-svg-core@^6.7.2":
+"@fortawesome/fontawesome-svg-core@^6.7.2", "@fortawesome/fontawesome-svg-core@~1 || ~6 || ~7":
version "6.7.2"
resolved "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz"
integrity sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==
@@ -2235,29 +2092,29 @@
resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz"
integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
-"@jest/console@30.4.1":
- version "30.4.1"
- resolved "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz"
- integrity sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==
+"@jest/console@30.5.2":
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/@jest/console/-/console-30.5.2.tgz"
+ integrity sha512-e8sQC4pCMHPBrPX7Hk8TJr+DM5dkUnImSTNag8SEWFAFAF6xIz4AJvCKapnrdHnZxep4bxw4PhG+ZbimYBMyKw==
dependencies:
- "@jest/types" "30.4.1"
+ "@jest/types" "30.5.1"
"@types/node" "*"
chalk "^4.1.2"
- jest-message-util "30.4.1"
- jest-util "30.4.1"
+ jest-message-util "30.5.1"
+ jest-util "30.5.1"
slash "^3.0.0"
-"@jest/core@30.4.2":
- version "30.4.2"
- resolved "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz"
- integrity sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==
- dependencies:
- "@jest/console" "30.4.1"
- "@jest/pattern" "30.4.0"
- "@jest/reporters" "30.4.1"
- "@jest/test-result" "30.4.1"
- "@jest/transform" "30.4.1"
- "@jest/types" "30.4.1"
+"@jest/core@30.5.2":
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/@jest/core/-/core-30.5.2.tgz"
+ integrity sha512-/jqvFWoJF7LV1a6AUpYMbMm+2yIYsHJfR474H0SCzr9k8tnO/jl9wRg6zSG6lxgf6EozNlCG4amFzWsbZSvvZA==
+ dependencies:
+ "@jest/console" "30.5.2"
+ "@jest/pattern" "30.5.0"
+ "@jest/reporters" "30.5.2"
+ "@jest/test-result" "30.5.2"
+ "@jest/transform" "30.5.2"
+ "@jest/types" "30.5.1"
"@types/node" "*"
ansi-escapes "^4.3.2"
chalk "^4.1.2"
@@ -2265,41 +2122,26 @@
exit-x "^0.2.2"
fast-json-stable-stringify "^2.1.0"
graceful-fs "^4.2.11"
- jest-changed-files "30.4.1"
- jest-config "30.4.2"
- jest-haste-map "30.4.1"
- jest-message-util "30.4.1"
- jest-regex-util "30.4.0"
- jest-resolve "30.4.1"
- jest-resolve-dependencies "30.4.2"
- jest-runner "30.4.2"
- jest-runtime "30.4.2"
- jest-snapshot "30.4.1"
- jest-util "30.4.1"
- jest-validate "30.4.1"
- jest-watcher "30.4.1"
- pretty-format "30.4.1"
+ jest-changed-files "30.5.1"
+ jest-config "30.5.2"
+ jest-haste-map "30.5.1"
+ jest-message-util "30.5.1"
+ jest-regex-util "30.5.0"
+ jest-resolve "30.5.1"
+ jest-resolve-dependencies "30.5.2"
+ jest-runner "30.5.2"
+ jest-runtime "30.5.2"
+ jest-snapshot "30.5.2"
+ jest-util "30.5.1"
+ jest-validate "30.5.1"
+ jest-watcher "30.5.2"
+ pretty-format "30.5.1"
slash "^3.0.0"
-"@jest/diff-sequences@30.0.1":
- version "30.0.1"
- resolved "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz"
- integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==
-
-"@jest/diff-sequences@30.4.0":
- version "30.4.0"
- resolved "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz"
- integrity sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==
-
-"@jest/environment@30.4.1":
- version "30.4.1"
- resolved "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz"
- integrity sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==
- dependencies:
- "@jest/fake-timers" "30.4.1"
- "@jest/types" "30.4.1"
- "@types/node" "*"
- jest-mock "30.4.1"
+"@jest/diff-sequences@30.5.0":
+ version "30.5.0"
+ resolved "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.5.0.tgz"
+ integrity sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==
"@jest/environment@^29.7.0":
version "29.7.0"
@@ -2311,54 +2153,42 @@
"@types/node" "*"
jest-mock "^29.7.0"
-"@jest/expect-utils@30.1.1":
- version "30.1.1"
- resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.1.1.tgz"
- integrity sha512-5YUHr27fpJ64dnvtu+tt11ewATynrHkGYD+uSFgRr8V2eFJis/vEXgToyLwccIwqBihVfz9jwio+Zr1ab1Zihw==
- dependencies:
- "@jest/get-type" "30.1.0"
-
-"@jest/expect-utils@30.4.1":
- version "30.4.1"
- resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz"
- integrity sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==
- dependencies:
- "@jest/get-type" "30.1.0"
-
-"@jest/expect-utils@^29.7.0":
- version "29.7.0"
- resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz"
- integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==
+"@jest/environment@30.5.2":
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/@jest/environment/-/environment-30.5.2.tgz"
+ integrity sha512-nwFPOUMbmsjNNRCpnJpB9HbSZu4C07wDDds++5j+j1MezzPLxMPDdbdUVjr2o4RULDOValnDd70taPFI1EtxAg==
dependencies:
- jest-get-type "^29.6.3"
+ "@jest/fake-timers" "30.5.2"
+ "@jest/types" "30.5.1"
+ "@types/node" "*"
+ jest-mock "30.5.2"
-"@jest/expect@30.4.1":
- version "30.4.1"
- resolved "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz"
- integrity sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==
+"@jest/expect-utils@30.5.2":
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.5.2.tgz"
+ integrity sha512-d/HOLSrJUlBIl6n3LzOa7cjW3xdePm5H5gQw2lixZ/0h157l9sw3mLblzFqnPcTbhDzhRQdBOX3A3KcG73nm5Q==
dependencies:
- expect "30.4.1"
- jest-snapshot "30.4.1"
+ "@jest/get-type" "30.5.0"
"@jest/expect@^29.7.0":
version "29.7.0"
- resolved "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz"
- integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==
+ resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz"
+ integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==
dependencies:
- expect "^29.7.0"
- jest-snapshot "^29.7.0"
+ "@jest/types" "^29.6.3"
+ "@sinonjs/fake-timers" "^10.0.2"
+ "@types/node" "*"
+ jest-message-util "^29.7.0"
+ jest-mock "^29.7.0"
+ jest-util "^29.7.0"
-"@jest/fake-timers@30.4.1":
- version "30.4.1"
- resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz"
- integrity sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==
+"@jest/expect@30.5.2":
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/@jest/expect/-/expect-30.5.2.tgz"
+ integrity sha512-2E+1Pg6jJxbRjLNLpciC71iri/3qsKJL222/aXJEFwC0VdUSvfS+JZQQLEVjUYYc8t8pQu2MGE3cF2+bNTYRsg==
dependencies:
- "@jest/types" "30.4.1"
- "@sinonjs/fake-timers" "^15.4.0"
- "@types/node" "*"
- jest-message-util "30.4.1"
- jest-mock "30.4.1"
- jest-util "30.4.1"
+ expect "30.5.2"
+ jest-snapshot "30.5.2"
"@jest/fake-timers@^29.7.0":
version "29.7.0"
@@ -2372,20 +2202,22 @@
jest-mock "^29.7.0"
jest-util "^29.7.0"
-"@jest/get-type@30.1.0":
- version "30.1.0"
- resolved "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz"
- integrity sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==
-
-"@jest/globals@30.4.1":
- version "30.4.1"
- resolved "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz"
- integrity sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==
+"@jest/fake-timers@30.5.2":
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.5.2.tgz"
+ integrity sha512-GKk0h0tKT5SpDPxDhPg3r9mm5s8/YCBzVNMOGdRiQXwUiMsjU10mVPlMaQstH6/cu50vOb8N9oQUMETHuwwxKw==
dependencies:
- "@jest/environment" "30.4.1"
- "@jest/expect" "30.4.1"
- "@jest/types" "30.4.1"
- jest-mock "30.4.1"
+ "@jest/types" "30.5.1"
+ "@sinonjs/fake-timers" "^15.4.0"
+ "@types/node" "*"
+ jest-message-util "30.5.1"
+ jest-mock "30.5.2"
+ jest-util "30.5.1"
+
+"@jest/get-type@30.5.0":
+ version "30.5.0"
+ resolved "https://registry.npmjs.org/@jest/get-type/-/get-type-30.5.0.tgz"
+ integrity sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q==
"@jest/globals@^29.7.0":
version "29.7.0"
@@ -2397,65 +2229,63 @@
"@jest/types" "^29.6.3"
jest-mock "^29.7.0"
-"@jest/pattern@30.0.1":
- version "30.0.1"
- resolved "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz"
- integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==
+"@jest/globals@30.5.2":
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/@jest/globals/-/globals-30.5.2.tgz"
+ integrity sha512-62zfS+dL+M5aTNHXCNLyqD+j4oqxTIzDYrZbpqMP8Yn+gvlAGyzC6BSPNb1ZmFOakV1RtFYX/O3FCQXSWmQdgg==
dependencies:
- "@types/node" "*"
- jest-regex-util "30.0.1"
+ "@jest/environment" "30.5.2"
+ "@jest/expect" "30.5.2"
+ "@jest/types" "30.5.1"
+ jest-mock "30.5.2"
-"@jest/pattern@30.4.0":
- version "30.4.0"
- resolved "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz"
- integrity sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==
+"@jest/pattern@30.5.0":
+ version "30.5.0"
+ resolved "https://registry.npmjs.org/@jest/pattern/-/pattern-30.5.0.tgz"
+ integrity sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w==
dependencies:
"@types/node" "*"
- jest-regex-util "30.4.0"
+ jest-regex-util "30.5.0"
+
+"@jest/react-is-18@npm:react-is@^18.3.1":
+ version "18.3.1"
+ resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz"
+ integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
-"@jest/reporters@30.4.1":
- version "30.4.1"
- resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz"
- integrity sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==
+"@jest/react-is-19@npm:react-is@^19.2.5":
+ version "19.3.1"
+ resolved "https://registry.npmjs.org/react-is/-/react-is-19.3.1.tgz"
+ integrity sha512-RRieF6IyxQBkcK6pP8xOHprLTPW6QjRkbGdImtqNme+/hYVwCZkLQLdlZGOhUqXwkS9IEynaGvPJtv6XB/cz0Q==
+
+"@jest/reporters@30.5.2":
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-30.5.2.tgz"
+ integrity sha512-ev6E9iG73twQcfAfoTnviuYkr3yOrYbzAtMFCimv2fxHXPIp9PBt2u1wNMt0aYWgl8FPwIcS8oqRJg6alnht4Q==
dependencies:
"@bcoe/v8-coverage" "^0.2.3"
- "@jest/console" "30.4.1"
- "@jest/test-result" "30.4.1"
- "@jest/transform" "30.4.1"
- "@jest/types" "30.4.1"
- "@jridgewell/trace-mapping" "^0.3.25"
+ "@jest/console" "30.5.2"
+ "@jest/test-result" "30.5.2"
+ "@jest/transform" "30.5.2"
+ "@jest/types" "30.5.1"
+ "@jridgewell/trace-mapping" "^0.3.31"
"@types/node" "*"
chalk "^4.1.2"
collect-v8-coverage "^1.0.2"
exit-x "^0.2.2"
- glob "^10.5.0"
+ glob "^13.0.6"
graceful-fs "^4.2.11"
istanbul-lib-coverage "^3.0.0"
istanbul-lib-instrument "^6.0.0"
istanbul-lib-report "^3.0.0"
istanbul-lib-source-maps "^5.0.0"
istanbul-reports "^3.1.3"
- jest-message-util "30.4.1"
- jest-util "30.4.1"
- jest-worker "30.4.1"
+ jest-message-util "30.5.1"
+ jest-util "30.5.1"
+ jest-worker "30.5.1"
slash "^3.0.0"
string-length "^4.0.2"
v8-to-istanbul "^9.0.1"
-"@jest/schemas@30.0.5":
- version "30.0.5"
- resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz"
- integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==
- dependencies:
- "@sinclair/typebox" "^0.34.0"
-
-"@jest/schemas@30.4.1":
- version "30.4.1"
- resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz"
- integrity sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==
- dependencies:
- "@sinclair/typebox" "^0.34.0"
-
"@jest/schemas@^29.6.3":
version "29.6.3"
resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz"
@@ -2463,64 +2293,52 @@
dependencies:
"@sinclair/typebox" "^0.27.8"
-"@jest/snapshot-utils@30.4.1":
- version "30.4.1"
- resolved "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz"
- integrity sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==
+"@jest/schemas@30.5.0":
+ version "30.5.0"
+ resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz"
+ integrity sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==
+ dependencies:
+ "@sinclair/typebox" "^0.34.0"
+
+"@jest/snapshot-utils@30.5.1":
+ version "30.5.1"
+ resolved "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.5.1.tgz"
+ integrity sha512-V3wnxNtiVmw5PPVg433Cn3VdXnsOeu/ofLw3KC04Bn2y1wIlU5kozXQn48rhrgj/02LrCVtntTEF1yBha0XSUw==
dependencies:
- "@jest/types" "30.4.1"
+ "@jest/types" "30.5.1"
chalk "^4.1.2"
graceful-fs "^4.2.11"
natural-compare "^1.4.0"
-"@jest/source-map@30.0.1":
- version "30.0.1"
- resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz"
- integrity sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==
+"@jest/source-map@30.5.2":
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-30.5.2.tgz"
+ integrity sha512-c5CehvkbfHX6yyNg7bMn2ylqlp9AHXuZ/3hPH/Lh5bxD/vefd/lV9HBjMqXef9yQNqkdwyZvkLSVSwB38hy8Qw==
dependencies:
- "@jridgewell/trace-mapping" "^0.3.25"
+ "@jridgewell/trace-mapping" "^0.3.31"
callsites "^3.1.0"
+ convert-source-map "^2.0.0"
graceful-fs "^4.2.11"
-"@jest/test-result@30.4.1":
- version "30.4.1"
- resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz"
- integrity sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==
+"@jest/test-result@30.5.2":
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-30.5.2.tgz"
+ integrity sha512-eO6YU5rsLfs60ne694e0IAmRgeBnoA8mu0nlxXDbl/1j5km0o2vO9/VbbUIKIH+WRKKEI+ELgwraRGn2Lhep8g==
dependencies:
- "@jest/console" "30.4.1"
- "@jest/types" "30.4.1"
+ "@jest/console" "30.5.2"
+ "@jest/types" "30.5.1"
"@types/istanbul-lib-coverage" "^2.0.6"
collect-v8-coverage "^1.0.2"
-"@jest/test-sequencer@30.4.1":
- version "30.4.1"
- resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz"
- integrity sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==
- dependencies:
- "@jest/test-result" "30.4.1"
- graceful-fs "^4.2.11"
- jest-haste-map "30.4.1"
- slash "^3.0.0"
-
-"@jest/transform@30.4.1":
- version "30.4.1"
- resolved "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz"
- integrity sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==
+"@jest/test-sequencer@30.5.2":
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.5.2.tgz"
+ integrity sha512-bmij8jZyYAC47ExT2GeP7PcrlwSNS+Bp3KeRuBH88gpcxqDv6fljN8PemS4J/lLZ79xb6mpaennQTTq0zUAVVg==
dependencies:
- "@babel/core" "^7.27.4"
- "@jest/types" "30.4.1"
- "@jridgewell/trace-mapping" "^0.3.25"
- babel-plugin-istanbul "^7.0.1"
- chalk "^4.1.2"
- convert-source-map "^2.0.0"
- fast-json-stable-stringify "^2.1.0"
+ "@jest/test-result" "30.5.2"
graceful-fs "^4.2.11"
- jest-haste-map "30.4.1"
- jest-regex-util "30.4.0"
- jest-util "30.4.1"
- pirates "^4.0.7"
+ jest-haste-map "30.5.1"
slash "^3.0.0"
- write-file-atomic "^5.0.1"
"@jest/transform@^29.7.0":
version "29.7.0"
@@ -2543,31 +2361,25 @@
slash "^3.0.0"
write-file-atomic "^4.0.2"
-"@jest/types@30.0.5":
- version "30.0.5"
- resolved "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz"
- integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==
- dependencies:
- "@jest/pattern" "30.0.1"
- "@jest/schemas" "30.0.5"
- "@types/istanbul-lib-coverage" "^2.0.6"
- "@types/istanbul-reports" "^3.0.4"
- "@types/node" "*"
- "@types/yargs" "^17.0.33"
- chalk "^4.1.2"
-
-"@jest/types@30.4.1":
- version "30.4.1"
- resolved "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz"
- integrity sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==
+"@jest/transform@30.5.2":
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/@jest/transform/-/transform-30.5.2.tgz"
+ integrity sha512-LBGwmaE886C41jDZ65eqR1YbXM7642MK3ZhTowZd5+xDlrDLuf4ePuDdDmVSvcr8NTVJ3MqxLkMbUCzuigUaoQ==
dependencies:
- "@jest/pattern" "30.4.0"
- "@jest/schemas" "30.4.1"
- "@types/istanbul-lib-coverage" "^2.0.6"
- "@types/istanbul-reports" "^3.0.4"
- "@types/node" "*"
- "@types/yargs" "^17.0.33"
+ "@babel/core" "^7.27.4"
+ "@jest/types" "30.5.1"
+ "@jridgewell/trace-mapping" "^0.3.31"
+ babel-plugin-istanbul "^8.0.0"
chalk "^4.1.2"
+ convert-source-map "^2.0.0"
+ fast-json-stable-stringify "^2.1.0"
+ graceful-fs "^4.2.11"
+ jest-haste-map "30.5.1"
+ jest-regex-util "30.5.0"
+ jest-util "30.5.1"
+ pirates "^4.0.7"
+ slash "^3.0.0"
+ write-file-atomic "^5.0.1"
"@jest/types@^26.6.2":
version "26.6.2"
@@ -2592,6 +2404,19 @@
"@types/yargs" "^17.0.8"
chalk "^4.0.0"
+"@jest/types@30.5.1":
+ version "30.5.1"
+ resolved "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz"
+ integrity sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==
+ dependencies:
+ "@jest/pattern" "30.5.0"
+ "@jest/schemas" "30.5.0"
+ "@types/istanbul-lib-coverage" "^2.0.6"
+ "@types/istanbul-reports" "^3.0.4"
+ "@types/node" "*"
+ "@types/yargs" "^17.0.33"
+ chalk "^4.1.2"
+
"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5":
version "0.3.13"
resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz"
@@ -2675,7 +2500,7 @@
resolved "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz"
integrity sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg==
-"@mapbox/point-geometry@0.1.0", "@mapbox/point-geometry@^0.1.0", "@mapbox/point-geometry@~0.1.0":
+"@mapbox/point-geometry@^0.1.0", "@mapbox/point-geometry@~0.1.0", "@mapbox/point-geometry@0.1.0":
version "0.1.0"
resolved "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz"
integrity sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==
@@ -2754,7 +2579,7 @@
clsx "^2.1.1"
prop-types "^15.8.1"
-"@mui/material@^7.1.0":
+"@mui/material@^5.15.14 || ^6.0.0 || ^7.0.0", "@mui/material@^7.1.0", "@mui/material@^7.3.1":
version "7.3.1"
resolved "https://registry.npmjs.org/@mui/material/-/material-7.3.1.tgz"
integrity sha512-Xf6Shbo03YmcBedZMwSpEFOwpYDtU7tC+rhAHTrA9FHk0FpsDqiQ9jUa1j/9s3HLs7KWb5mDcGnlwdh9Q9KAag==
@@ -2793,10 +2618,10 @@
csstype "^3.2.3"
prop-types "^15.8.1"
-"@mui/system@^7.3.1":
- version "7.3.1"
- resolved "https://registry.npmjs.org/@mui/system/-/system-7.3.1.tgz"
- integrity sha512-mIidecvcNVpNJMdPDmCeoSL5zshKBbYPcphjuh6ZMjhybhqhZ4mX6k9zmIWh6XOXcqRQMg5KrcjnO0QstrNj3w==
+"@mui/system@^5.15.14 || ^6.0.0 || ^7.0.0", "@mui/system@^7.3.1":
+ version "7.3.11"
+ resolved "https://registry.npmjs.org/@mui/system/-/system-7.3.11.tgz"
+ integrity sha512-7izwGWdNawAKpBKcRlx7f2gFnAAjmASBWvMcyX4YYEeLOFsbfGRbUYGInvnAcUeql3rPxI7F9Ft4oY2OLRz44g==
dependencies:
"@babel/runtime" "^7.28.6"
"@mui/private-theming" "^7.3.11"
@@ -2849,13 +2674,6 @@
reselect "^5.1.1"
use-sync-external-store "^1.5.0"
-"@napi-rs/wasm-runtime@^1.1.4":
- version "1.1.4"
- resolved "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz"
- integrity sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==
- dependencies:
- "@tybys/wasm-util" "^0.10.1"
-
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
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"
@@ -2871,7 +2689,7 @@
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
-"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
+"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
version "2.0.5"
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -2902,94 +2720,33 @@
resolved "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz"
integrity sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==
-"@parcel/watcher-android-arm64@2.5.1":
- 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==
-
-"@parcel/watcher-darwin-arm64@2.5.1":
- version "2.5.1"
- resolved "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz"
- integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==
-
-"@parcel/watcher-darwin-x64@2.5.1":
- 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==
-
-"@parcel/watcher-freebsd-x64@2.5.1":
- 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==
-
-"@parcel/watcher-linux-arm-glibc@2.5.1":
- 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==
-
-"@parcel/watcher-linux-arm-musl@2.5.1":
- 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==
-
-"@parcel/watcher-linux-arm64-glibc@2.5.1":
- 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==
-
-"@parcel/watcher-linux-arm64-musl@2.5.1":
- 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==
-
-"@parcel/watcher-linux-x64-glibc@2.5.1":
- 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==
-
-"@parcel/watcher-linux-x64-musl@2.5.1":
- 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==
-
-"@parcel/watcher-win32-arm64@2.5.1":
- version "2.5.1"
- resolved "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz"
- integrity sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==
-
-"@parcel/watcher-win32-ia32@2.5.1":
- version "2.5.1"
- resolved "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz"
- integrity sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==
-
-"@parcel/watcher-win32-x64@2.5.1":
- version "2.5.1"
- resolved "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz"
- integrity sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==
+"@parcel/watcher-darwin-arm64@2.6.0":
+ version "2.6.0"
+ resolved "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz"
+ integrity sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==
-"@parcel/watcher@^2.4.1":
- version "2.5.1"
- resolved "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz"
- integrity sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==
+"@parcel/watcher@^2.4.1", "@parcel/watcher@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz"
+ integrity sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==
dependencies:
- detect-libc "^1.0.3"
+ detect-libc "^2.0.3"
is-glob "^4.0.3"
- micromatch "^4.0.5"
node-addon-api "^7.0.0"
+ picomatch "^4.0.4"
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"
+ "@parcel/watcher-android-arm64" "2.6.0"
+ "@parcel/watcher-darwin-arm64" "2.6.0"
+ "@parcel/watcher-darwin-x64" "2.6.0"
+ "@parcel/watcher-freebsd-x64" "2.6.0"
+ "@parcel/watcher-linux-arm-glibc" "2.6.0"
+ "@parcel/watcher-linux-arm-musl" "2.6.0"
+ "@parcel/watcher-linux-arm64-glibc" "2.6.0"
+ "@parcel/watcher-linux-arm64-musl" "2.6.0"
+ "@parcel/watcher-linux-x64-glibc" "2.6.0"
+ "@parcel/watcher-linux-x64-musl" "2.6.0"
+ "@parcel/watcher-win32-arm64" "2.6.0"
+ "@parcel/watcher-win32-x64" "2.6.0"
"@pkgjs/parseargs@^0.11.0":
version "0.11.0"
@@ -3079,7 +2836,7 @@
resolved "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz"
integrity sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==
-"@popperjs/core@^2.11.6", "@popperjs/core@^2.11.8":
+"@popperjs/core@^2.0.0", "@popperjs/core@^2.11.6", "@popperjs/core@^2.11.8":
version "2.11.8"
resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz"
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
@@ -3164,7 +2921,7 @@
"@radix-ui/react-use-is-hydrated" "0.1.3"
"@radix-ui/react-use-layout-effect" "1.1.4"
-"@radix-ui/react-slot@1.3.3", "@radix-ui/react-slot@^1.2.4":
+"@radix-ui/react-slot@^1.2.4", "@radix-ui/react-slot@1.3.3":
version "1.3.3"
resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz"
integrity sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==
@@ -3338,105 +3095,10 @@
resolved "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz"
integrity sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==
-"@rollup/rollup-android-arm-eabi@4.49.0":
- 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==
-
-"@rollup/rollup-android-arm64@4.49.0":
- 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==
-
-"@rollup/rollup-darwin-arm64@4.49.0":
- version "4.49.0"
- resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.49.0.tgz"
- integrity sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==
-
-"@rollup/rollup-darwin-x64@4.49.0":
- 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==
-
-"@rollup/rollup-freebsd-arm64@4.49.0":
- 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==
-
-"@rollup/rollup-freebsd-x64@4.49.0":
- 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==
-
-"@rollup/rollup-linux-arm-gnueabihf@4.49.0":
- 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==
-
-"@rollup/rollup-linux-arm-musleabihf@4.49.0":
- 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==
-
-"@rollup/rollup-linux-arm64-gnu@4.49.0":
- 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==
-
-"@rollup/rollup-linux-arm64-musl@4.49.0":
- 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==
-
-"@rollup/rollup-linux-loongarch64-gnu@4.49.0":
- 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==
-
-"@rollup/rollup-linux-ppc64-gnu@4.49.0":
- 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==
-
-"@rollup/rollup-linux-riscv64-gnu@4.49.0":
- 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==
-
-"@rollup/rollup-linux-riscv64-musl@4.49.0":
- 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==
-
-"@rollup/rollup-linux-s390x-gnu@4.49.0":
- 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==
-
-"@rollup/rollup-linux-x64-gnu@4.49.0":
- 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==
-
-"@rollup/rollup-linux-x64-musl@4.49.0":
- 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==
-
-"@rollup/rollup-win32-arm64-msvc@4.49.0":
- 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==
-
-"@rollup/rollup-win32-ia32-msvc@4.49.0":
- 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==
-
-"@rollup/rollup-win32-x64-msvc@4.49.0":
- version "4.49.0"
- resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.49.0.tgz"
- integrity sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==
+"@rollup/rollup-darwin-arm64@4.66.0":
+ version "4.66.0"
+ resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.66.0.tgz"
+ integrity sha512-6vkDCfyDjNLokh+Rt1D2KtNgFFMEPdkWha/mbtP4cSjh9xiN8T/JBS/A7nDYKjmMAXHrvR9J3AzUk2LtFbIBLg==
"@rtsao/scc@^1.1.0":
version "1.1.0"
@@ -3495,9 +3157,9 @@
integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
"@sinclair/typebox@^0.34.0":
- version "0.34.49"
- resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz"
- integrity sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==
+ version "0.34.52"
+ resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz"
+ integrity sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==
"@sinonjs/commons@^3.0.0", "@sinonjs/commons@^3.0.1":
version "3.0.1"
@@ -3543,7 +3205,7 @@
"@tanstack/query-core" "4.41.0"
use-sync-external-store "^1.2.0"
-"@testing-library/dom@^10.4.0":
+"@testing-library/dom@^10.4.0", "@testing-library/dom@>=7.21.4":
version "10.4.1"
resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz"
integrity sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==
@@ -3665,13 +3327,6 @@
"@types/geojson" "^7946.0.10"
tslib "^2.8.1"
-"@tybys/wasm-util@^0.10.1":
- version "0.10.2"
- resolved "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz"
- integrity sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==
- dependencies:
- tslib "^2.4.0"
-
"@types/aria-query@^4.2.0":
version "4.2.2"
resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz"
@@ -3838,10 +3493,10 @@
resolved "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz"
integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==
-"@types/estree@1.0.8", "@types/estree@^1.0.0", "@types/estree@^1.0.8":
- version "1.0.8"
- resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz"
- integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
+"@types/estree@^1.0.0", "@types/estree@^1.0.8", "@types/estree@1.0.9":
+ version "1.0.9"
+ resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz"
+ integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==
"@types/geojson-vt@3.2.5":
version "3.2.5"
@@ -3947,7 +3602,7 @@
"@types/mapbox__point-geometry" "*"
"@types/pbf" "*"
-"@types/node@*":
+"@types/node@*", "@types/node@^18.0.0 || ^20.0.0 || >=22.0.0", "@types/node@>=18":
version "24.3.0"
resolved "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz"
integrity sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==
@@ -4035,10 +3690,10 @@
resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz"
integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==
-"@types/react@*", "@types/react@>=16.14.8", "@types/react@>=16.9.11":
- version "19.1.12"
- resolved "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz"
- integrity sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==
+"@types/react@*", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react@^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react@^18.2.25 || ^19", "@types/react@>=16.14.8", "@types/react@>=16.9.11":
+ version "19.3.0"
+ resolved "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz"
+ integrity sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==
dependencies:
csstype "^3.2.2"
@@ -4097,9 +3752,9 @@
integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==
"@types/yargs@^15.0.0":
- version "15.0.19"
- resolved "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz"
- integrity sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==
+ version "15.0.20"
+ resolved "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.20.tgz"
+ integrity sha512-KIkX+/GgfFitlASYCGoSF+T4XRXhOubJLhkLVtSfsRTe9jWMmuM2g28zQ41BtPTG7TRBb2xHW+LCNVE9QR/vsg==
dependencies:
"@types/yargs-parser" "*"
@@ -4119,6 +3774,14 @@
"@typescript-eslint/types" "^8.60.0"
debug "^4.4.3"
+"@typescript-eslint/scope-manager@^8.15.0":
+ version "8.72.1"
+ resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.1.tgz"
+ integrity sha512-lC/t6eMCZZQAAKZEYGL6RDVSuFehpdmMT6h+nJIjIa6oylN/PwDgDVtEIbB7O9xFfZYtZAYLoLlDNWKJhtF+uQ==
+ dependencies:
+ "@typescript-eslint/types" "8.72.1"
+ "@typescript-eslint/visitor-keys" "8.72.1"
+
"@typescript-eslint/scope-manager@7.18.0":
version "7.18.0"
resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz"
@@ -4127,7 +3790,7 @@
"@typescript-eslint/types" "7.18.0"
"@typescript-eslint/visitor-keys" "7.18.0"
-"@typescript-eslint/scope-manager@8.60.0", "@typescript-eslint/scope-manager@^8.15.0":
+"@typescript-eslint/scope-manager@8.60.0":
version "8.60.0"
resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz"
integrity sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==
@@ -4135,17 +3798,22 @@
"@typescript-eslint/types" "8.60.0"
"@typescript-eslint/visitor-keys" "8.60.0"
-"@typescript-eslint/tsconfig-utils@8.60.0", "@typescript-eslint/tsconfig-utils@^8.60.0":
+"@typescript-eslint/tsconfig-utils@^8.60.0", "@typescript-eslint/tsconfig-utils@8.60.0":
version "8.60.0"
resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz"
integrity sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==
+"@typescript-eslint/types@^8.60.0", "@typescript-eslint/types@8.72.1":
+ version "8.72.1"
+ resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.1.tgz"
+ integrity sha512-LmE5IhQet1BWOWEXqBrVtBPsMAOQ6Nzk35d+WLh3mk4EUF33sqlfxjio1MdYeSvKSG5skactKKuixBIKXhL4Rw==
+
"@typescript-eslint/types@7.18.0":
version "7.18.0"
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz"
integrity sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==
-"@typescript-eslint/types@8.60.0", "@typescript-eslint/types@^8.60.0":
+"@typescript-eslint/types@8.60.0":
version "8.60.0"
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz"
integrity sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==
@@ -4215,125 +3883,24 @@
"@typescript-eslint/types" "8.60.0"
eslint-visitor-keys "^5.0.0"
+"@typescript-eslint/visitor-keys@8.72.1":
+ version "8.72.1"
+ resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.1.tgz"
+ integrity sha512-SB8xqipSzZHUePCCLNeYxqnxq83X61/qzNhjQGbTfjxGZIeOEP7tBgWYX05Tcd6CgE02WdTJnh5b8kMZJ/+bnQ==
+ dependencies:
+ "@typescript-eslint/types" "8.72.1"
+ eslint-visitor-keys "^5.0.0"
+
"@ungap/structured-clone@^1.2.0", "@ungap/structured-clone@^1.3.0":
version "1.3.0"
resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz"
integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
-"@unrs/resolver-binding-android-arm-eabi@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz"
- integrity sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==
-
-"@unrs/resolver-binding-android-arm64@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz"
- integrity sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==
-
"@unrs/resolver-binding-darwin-arm64@1.12.2":
version "1.12.2"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz"
integrity sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==
-"@unrs/resolver-binding-darwin-x64@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz"
- integrity sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==
-
-"@unrs/resolver-binding-freebsd-x64@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz"
- integrity sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==
-
-"@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz"
- integrity sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==
-
-"@unrs/resolver-binding-linux-arm-musleabihf@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz"
- integrity sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==
-
-"@unrs/resolver-binding-linux-arm64-gnu@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz"
- integrity sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==
-
-"@unrs/resolver-binding-linux-arm64-musl@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz"
- integrity sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==
-
-"@unrs/resolver-binding-linux-loong64-gnu@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz"
- integrity sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==
-
-"@unrs/resolver-binding-linux-loong64-musl@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz"
- integrity sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==
-
-"@unrs/resolver-binding-linux-ppc64-gnu@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz"
- integrity sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==
-
-"@unrs/resolver-binding-linux-riscv64-gnu@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz"
- integrity sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==
-
-"@unrs/resolver-binding-linux-riscv64-musl@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz"
- integrity sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==
-
-"@unrs/resolver-binding-linux-s390x-gnu@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz"
- integrity sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==
-
-"@unrs/resolver-binding-linux-x64-gnu@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz"
- integrity sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==
-
-"@unrs/resolver-binding-linux-x64-musl@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz"
- integrity sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==
-
-"@unrs/resolver-binding-openharmony-arm64@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz"
- integrity sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==
-
-"@unrs/resolver-binding-wasm32-wasi@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz"
- integrity sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==
- dependencies:
- "@emnapi/core" "1.10.0"
- "@emnapi/runtime" "1.10.0"
- "@napi-rs/wasm-runtime" "^1.1.4"
-
-"@unrs/resolver-binding-win32-arm64-msvc@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz"
- integrity sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==
-
-"@unrs/resolver-binding-win32-ia32-msvc@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz"
- integrity sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==
-
-"@unrs/resolver-binding-win32-x64-msvc@1.12.2":
- version "1.12.2"
- resolved "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz"
- integrity sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==
-
"@vitejs/plugin-react@^4.5.0":
version "4.7.0"
resolved "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz"
@@ -4347,9 +3914,9 @@
react-refresh "^0.17.0"
"@vitest/coverage-v8@^3.2.4":
- version "3.2.4"
- resolved "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz"
- integrity sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==
+ version "3.2.7"
+ resolved "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.7.tgz"
+ integrity sha512-NEGWJS2XNu2PfRLQwOO3CTKj1tTETxNBdk454vDxVBhxJYhPaA/eS0nAI0c+1El1P7a60z8+i+ZrQoGESweGKg==
dependencies:
"@ampproject/remapping" "^2.3.0"
"@bcoe/v8-coverage" "^1.0.2"
@@ -4365,64 +3932,64 @@
test-exclude "^7.0.1"
tinyrainbow "^2.0.0"
-"@vitest/expect@3.2.4":
- version "3.2.4"
- resolved "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz"
- integrity sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==
+"@vitest/expect@3.2.7":
+ version "3.2.7"
+ resolved "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz"
+ integrity sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==
dependencies:
"@types/chai" "^5.2.2"
- "@vitest/spy" "3.2.4"
- "@vitest/utils" "3.2.4"
+ "@vitest/spy" "3.2.7"
+ "@vitest/utils" "3.2.7"
chai "^5.2.0"
tinyrainbow "^2.0.0"
-"@vitest/mocker@3.2.4":
- version "3.2.4"
- resolved "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz"
- integrity sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==
+"@vitest/mocker@3.2.7":
+ version "3.2.7"
+ resolved "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz"
+ integrity sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==
dependencies:
- "@vitest/spy" "3.2.4"
+ "@vitest/spy" "3.2.7"
estree-walker "^3.0.3"
magic-string "^0.30.17"
-"@vitest/pretty-format@3.2.4", "@vitest/pretty-format@^3.2.4":
- version "3.2.4"
- resolved "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz"
- integrity sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==
+"@vitest/pretty-format@^3.2.7", "@vitest/pretty-format@3.2.7":
+ version "3.2.7"
+ resolved "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz"
+ integrity sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==
dependencies:
tinyrainbow "^2.0.0"
-"@vitest/runner@3.2.4":
- version "3.2.4"
- resolved "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz"
- integrity sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==
+"@vitest/runner@3.2.7":
+ version "3.2.7"
+ resolved "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz"
+ integrity sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==
dependencies:
- "@vitest/utils" "3.2.4"
+ "@vitest/utils" "3.2.7"
pathe "^2.0.3"
strip-literal "^3.0.0"
-"@vitest/snapshot@3.2.4":
- version "3.2.4"
- resolved "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz"
- integrity sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==
+"@vitest/snapshot@3.2.7":
+ version "3.2.7"
+ resolved "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz"
+ integrity sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==
dependencies:
- "@vitest/pretty-format" "3.2.4"
+ "@vitest/pretty-format" "3.2.7"
magic-string "^0.30.17"
pathe "^2.0.3"
-"@vitest/spy@3.2.4":
- version "3.2.4"
- resolved "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz"
- integrity sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==
+"@vitest/spy@3.2.7":
+ version "3.2.7"
+ resolved "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz"
+ integrity sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==
dependencies:
tinyspy "^4.0.3"
-"@vitest/ui@^3.2.4":
- version "3.2.4"
- resolved "https://registry.npmjs.org/@vitest/ui/-/ui-3.2.4.tgz"
- integrity sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==
+"@vitest/ui@^3.2.4", "@vitest/ui@3.2.7":
+ version "3.2.7"
+ resolved "https://registry.npmjs.org/@vitest/ui/-/ui-3.2.7.tgz"
+ integrity sha512-eVtcpJXGhS0GjMuHROfbXLhlxooyUcuip8GNzzjDD5jzafZqzanJH4W3VGmUxHNx4fv6qQGUGJHRpGUdj+9D6Q==
dependencies:
- "@vitest/utils" "3.2.4"
+ "@vitest/utils" "3.2.7"
fflate "^0.8.2"
flatted "^3.3.3"
pathe "^2.0.3"
@@ -4430,16 +3997,16 @@
tinyglobby "^0.2.14"
tinyrainbow "^2.0.0"
-"@vitest/utils@3.2.4":
- version "3.2.4"
- resolved "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz"
- integrity sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==
+"@vitest/utils@3.2.7":
+ version "3.2.7"
+ resolved "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz"
+ integrity sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==
dependencies:
- "@vitest/pretty-format" "3.2.4"
+ "@vitest/pretty-format" "3.2.7"
loupe "^3.1.4"
tinyrainbow "^2.0.0"
-"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1":
+"@webassemblyjs/ast@^1.14.1", "@webassemblyjs/ast@1.14.1":
version "1.14.1"
resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz"
integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==
@@ -4540,7 +4107,7 @@
"@webassemblyjs/wasm-gen" "1.14.1"
"@webassemblyjs/wasm-parser" "1.14.1"
-"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1":
+"@webassemblyjs/wasm-parser@^1.14.1", "@webassemblyjs/wasm-parser@1.14.1":
version "1.14.1"
resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz"
integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==
@@ -4566,9 +4133,9 @@
integrity sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==
"@xmldom/xmldom@^0.8.2":
- version "0.8.11"
- resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz"
- integrity sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==
+ version "0.8.15"
+ resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.15.tgz"
+ integrity sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
@@ -4595,21 +4162,31 @@ acorn-jsx@^5.3.2:
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
+"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.14.0, acorn@^8.15.0, acorn@^8.16.0, acorn@^8.9.0:
+ version "8.19.0"
+ resolved "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz"
+ integrity sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==
+
acorn@^7.1.1:
version "7.4.1"
resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
-acorn@^8.15.0, acorn@^8.16.0, acorn@^8.9.0:
- version "8.16.0"
- resolved "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz"
- integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
-
adler-32@~1.3.0:
version "1.3.1"
resolved "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz"
integrity sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==
+agent-base@^7.1.0:
+ version "7.1.4"
+ resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz"
+ integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==
+
+agent-base@^7.1.2:
+ version "7.1.4"
+ resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz"
+ integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==
+
agent-base@6:
version "6.0.2"
resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz"
@@ -4617,11 +4194,6 @@ agent-base@6:
dependencies:
debug "4"
-agent-base@^7.1.0, agent-base@^7.1.2:
- version "7.1.4"
- resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz"
- integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==
-
ajv-formats@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz"
@@ -4637,16 +4209,16 @@ ajv-keywords@^5.1.0:
fast-deep-equal "^3.1.3"
ajv@^6.12.4:
- version "6.12.6"
- resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
- integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ version "6.15.0"
+ resolved "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz"
+ integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
-ajv@^8.0.0, ajv@^8.0.1, ajv@^8.18.0, ajv@^8.9.0:
+ajv@^8.0.0, ajv@^8.0.1, ajv@^8.18.0, ajv@^8.8.2, ajv@^8.9.0:
version "8.20.0"
resolved "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz"
integrity sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==
@@ -4665,7 +4237,7 @@ ansi-escapes@^4.3.2:
ansi-escapes@^7.0.0:
version "7.3.0"
- resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.3.0.tgz#5395bb74b2150a4a1d6e3c2565f4aeca78d28627"
+ resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz"
integrity sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==
dependencies:
environment "^1.0.0"
@@ -4675,7 +4247,12 @@ ansi-regex@^5.0.0, ansi-regex@^5.0.1:
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
-ansi-regex@^6.0.1, ansi-regex@^6.2.2:
+ansi-regex@^6.0.1:
+ version "6.2.2"
+ resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz"
+ integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==
+
+ansi-regex@^6.2.2:
version "6.2.2"
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz"
integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==
@@ -4694,12 +4271,22 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
dependencies:
color-convert "^2.0.1"
-ansi-styles@^5.0.0, ansi-styles@^5.2.0:
+ansi-styles@^5.0.0:
version "5.2.0"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz"
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
-ansi-styles@^6.1.0, ansi-styles@^6.2.1:
+ansi-styles@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz"
+ integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
+
+ansi-styles@^6.1.0:
+ version "6.2.3"
+ resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz"
+ integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==
+
+ansi-styles@^6.2.1:
version "6.2.3"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz"
integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==
@@ -4784,13 +4371,6 @@ argparse@^2.0.1:
resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
-aria-query@5.3.0, aria-query@^5.0.0:
- version "5.3.0"
- resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz"
- integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==
- dependencies:
- dequal "^2.0.3"
-
aria-query@^4.2.2:
version "4.2.2"
resolved "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz"
@@ -4799,6 +4379,13 @@ aria-query@^4.2.2:
"@babel/runtime" "^7.10.2"
"@babel/runtime-corejs3" "^7.10.2"
+aria-query@^5.0.0, aria-query@5.3.0:
+ version "5.3.0"
+ resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz"
+ integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==
+ dependencies:
+ dequal "^2.0.3"
+
aria-query@^5.3.2:
version "5.3.2"
resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz"
@@ -5001,10 +4588,10 @@ axios-mock-adapter@^1.22.0:
fast-deep-equal "^3.1.3"
is-buffer "^2.0.5"
-axios@^1.18.0:
- version "1.20.0"
- resolved "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz"
- integrity sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==
+axios@^1.18.0, "axios@>= 0.17.0":
+ version "1.21.0"
+ resolved "https://registry.npmjs.org/axios/-/axios-1.21.0.tgz"
+ integrity sha512-YCzXMB53Q9PZBsh7yN7wtapit4WepwcHS2fP8QhGsBxSz5YtgYobV7GGlWlAJi+RxfU77GQTQi2DSLUma42EYA==
dependencies:
follow-redirects "^1.16.0"
form-data "^4.0.6"
@@ -5016,19 +4603,6 @@ axobject-query@^4.1.0:
resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz"
integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==
-babel-jest@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz"
- integrity sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==
- dependencies:
- "@jest/transform" "30.4.1"
- "@types/babel__core" "^7.20.5"
- babel-plugin-istanbul "^7.0.1"
- babel-preset-jest "30.4.0"
- chalk "^4.1.2"
- graceful-fs "^4.2.11"
- slash "^3.0.0"
-
babel-jest@^29.7.0:
version "29.7.0"
resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz"
@@ -5042,6 +4616,19 @@ babel-jest@^29.7.0:
graceful-fs "^4.2.9"
slash "^3.0.0"
+babel-jest@30.5.2:
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-30.5.2.tgz"
+ integrity sha512-ES8WeJ2fNWPEDunyAtUfJ12nHyaWUloCHdkfK2oW5uSoQSUmjNKdWiGaXJITJwvFEWoD/evHEg/QoCXAvLaQqQ==
+ dependencies:
+ "@jest/transform" "30.5.2"
+ "@types/babel__core" "^7.20.5"
+ babel-plugin-istanbul "^8.0.0"
+ babel-preset-jest "30.5.0"
+ chalk "^4.1.2"
+ graceful-fs "^4.2.11"
+ slash "^3.0.0"
+
babel-plugin-istanbul@^6.1.1:
version "6.1.1"
resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz"
@@ -5053,23 +4640,16 @@ babel-plugin-istanbul@^6.1.1:
istanbul-lib-instrument "^5.0.4"
test-exclude "^6.0.0"
-babel-plugin-istanbul@^7.0.1:
- version "7.0.1"
- resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz"
- integrity sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==
+babel-plugin-istanbul@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-8.0.0.tgz"
+ integrity sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@istanbuljs/load-nyc-config" "^1.0.0"
"@istanbuljs/schema" "^0.1.3"
istanbul-lib-instrument "^6.0.2"
- test-exclude "^6.0.0"
-
-babel-plugin-jest-hoist@30.4.0:
- version "30.4.0"
- resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz"
- integrity sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==
- dependencies:
- "@types/babel__core" "^7.20.5"
+ test-exclude "^7.0.1"
babel-plugin-jest-hoist@^29.6.3:
version "29.6.3"
@@ -5081,6 +4661,13 @@ babel-plugin-jest-hoist@^29.6.3:
"@types/babel__core" "^7.1.14"
"@types/babel__traverse" "^7.0.6"
+babel-plugin-jest-hoist@30.5.0:
+ version "30.5.0"
+ resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.5.0.tgz"
+ integrity sha512-gtGo1B+u14jrZQv6TdSWIWkTqclboo7Qn+dFAGUOIuXLFuJKAdg3U5MIWzZnW4vfUb4dX9skmAMiby86e/SF4A==
+ dependencies:
+ "@types/babel__core" "^7.20.5"
+
babel-plugin-macros@^3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz"
@@ -5135,14 +4722,6 @@ babel-preset-current-node-syntax@^1.0.0, babel-preset-current-node-syntax@^1.2.0
"@babel/plugin-syntax-private-property-in-object" "^7.14.5"
"@babel/plugin-syntax-top-level-await" "^7.14.5"
-babel-preset-jest@30.4.0:
- version "30.4.0"
- resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz"
- integrity sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==
- dependencies:
- babel-plugin-jest-hoist "30.4.0"
- babel-preset-current-node-syntax "^1.2.0"
-
babel-preset-jest@^29.6.3:
version "29.6.3"
resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz"
@@ -5151,6 +4730,14 @@ babel-preset-jest@^29.6.3:
babel-plugin-jest-hoist "^29.6.3"
babel-preset-current-node-syntax "^1.0.0"
+babel-preset-jest@30.5.0:
+ version "30.5.0"
+ resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.5.0.tgz"
+ integrity sha512-ZGPn5ClP4lBDpuOK8W1yQIOy359HmbnZv3suucAlIe+SEE9yDsxV4S2PjSbH2Vc97U+WmzYD7vw3kr8NsQ/i6w==
+ dependencies:
+ babel-plugin-jest-hoist "30.5.0"
+ babel-preset-current-node-syntax "^1.2.0"
+
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
@@ -5171,20 +4758,20 @@ base64-arraybuffer@^1.0.2:
resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz"
integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
-base64-js@1.3.1:
- version "1.3.1"
- resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz"
- integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
-
base64-js@^1.1.2, base64-js@^1.3.0, base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
-baseline-browser-mapping@^2.11.23:
- version "2.11.25"
- resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.25.tgz#88b942ce372bdf2d6a2652136bbf7826c65c77be"
- integrity sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==
+base64-js@1.3.1:
+ version "1.3.1"
+ resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz"
+ integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
+
+baseline-browser-mapping@^2.11.33:
+ version "2.11.34"
+ resolved "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.34.tgz"
+ integrity sha512-Aqr8mA1kIvyJ3UY5YAY7ib8LSo1DmDMbMnVqGPtsKW1LxaFQFwn9Eb3yvq2fUKJsRvaXjnAV7R5osqETapzNiA==
binary-search-bounds@^2.0.4:
version "2.0.5"
@@ -5214,15 +4801,25 @@ bluebird@~3.7.2:
resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
-bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
- version "4.12.2"
- resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz"
- integrity sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==
+bn.js@^4.0.0:
+ version "4.12.5"
+ resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz"
+ integrity sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==
-bn.js@^5.2.1:
- version "5.2.2"
- resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz"
- integrity sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==
+bn.js@^4.1.0:
+ version "4.12.5"
+ resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz"
+ integrity sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==
+
+bn.js@^4.11.9:
+ version "4.12.5"
+ resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz"
+ integrity sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==
+
+bn.js@^5.2.1, bn.js@^5.2.3:
+ version "5.2.5"
+ resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.5.tgz"
+ integrity sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==
boolbase@^1.0.0:
version "1.0.0"
@@ -5235,24 +4832,31 @@ bootstrap@^4.5.3:
integrity sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==
brace-expansion@^1.1.7:
- version "1.1.12"
- resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz"
- integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==
+ version "1.1.21"
+ resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz"
+ integrity sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
brace-expansion@^2.0.2:
- version "2.1.1"
- resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz"
- integrity sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==
+ version "2.1.7"
+ resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz"
+ integrity sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==
dependencies:
balanced-match "^1.0.0"
brace-expansion@^5.0.5:
- version "5.0.6"
- resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz"
- integrity sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==
+ version "5.0.12"
+ resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz"
+ integrity sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==
+ dependencies:
+ balanced-match "^4.0.2"
+
+brace-expansion@^5.0.8:
+ version "5.0.12"
+ resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz"
+ integrity sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==
dependencies:
balanced-match "^4.0.2"
@@ -5306,7 +4910,7 @@ browserify-des@^1.0.0:
inherits "^2.0.1"
safe-buffer "^5.1.2"
-browserify-rsa@^4.0.0, browserify-rsa@^4.1.0:
+browserify-rsa@^4.0.0, browserify-rsa@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz"
integrity sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==
@@ -5316,31 +4920,30 @@ browserify-rsa@^4.0.0, browserify-rsa@^4.1.0:
safe-buffer "^5.2.1"
browserify-sign@^4.2.3:
- version "4.2.3"
- resolved "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz"
- integrity sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==
+ version "4.2.6"
+ resolved "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.6.tgz"
+ integrity sha512-sd+Q65fjlWCYWtZKXiKfrUc8d+4jtp/8f0W2NkwzLtoW4bI6UDnWusLWIurHnmurW0XShIRxpwiOX4EoPtXUAg==
dependencies:
- bn.js "^5.2.1"
- browserify-rsa "^4.1.0"
+ bn.js "^5.2.3"
+ browserify-rsa "^4.1.1"
create-hash "^1.2.0"
create-hmac "^1.1.7"
- elliptic "^6.5.5"
- hash-base "~3.0"
+ elliptic "^6.6.1"
inherits "^2.0.4"
- parse-asn1 "^5.1.7"
+ parse-asn1 "^5.1.9"
readable-stream "^2.3.8"
safe-buffer "^5.2.1"
-browserslist@^4.24.0, browserslist@^4.25.3, browserslist@^4.28.1:
- version "4.29.0"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.29.0.tgz#e50e72c30c4b5f7ec894c09feb6ed525167f5344"
- integrity sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==
+browserslist@^4.24.0, browserslist@^4.25.3, browserslist@^4.28.1, "browserslist@>= 4.21.0":
+ version "4.29.5"
+ resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.29.5.tgz"
+ integrity sha512-khTmKu5NGYTotqcYo8Bew2k4RAE9PuXRSFRXG2lQBrCEQHyeQ/vX6gKMxfokwywBsLiXZMnHsmWMG1pnO8J7uQ==
dependencies:
- baseline-browser-mapping "^2.11.23"
- caniuse-lite "^1.0.30001810"
- electron-to-chromium "^1.5.427"
- node-releases "^2.0.55"
- update-browserslist-db "^1.3.3"
+ baseline-browser-mapping "^2.11.33"
+ caniuse-lite "^1.0.30001819"
+ electron-to-chromium "^1.5.464"
+ node-releases "^2.0.60"
+ update-browserslist-db "^1.3.4"
bser@2.1.1:
version "2.1.1"
@@ -5439,10 +5042,10 @@ camelize@^1.0.0:
resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz"
integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==
-caniuse-lite@^1.0.30001810:
- version "1.0.30001810"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz#4970b477dea3278374de9bc43aa8f5d39fc3cda2"
- integrity sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==
+caniuse-lite@^1.0.30001819:
+ version "1.0.30001820"
+ resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001820.tgz"
+ integrity sha512-gfWD6MxqVCqAOwmODfZf7DchWvlrA+SRdNX7ceLfg3xRqe5ssjjpF7EgslC/0tiLiNTajdaNN/q8c/5u/7Gz8w==
canvas-confetti@^1.9.3:
version "1.9.3"
@@ -5512,7 +5115,7 @@ char-regex@^1.0.2:
resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz"
integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
-chart.js@^4.5.0:
+chart.js@^4.1.1, chart.js@^4.5.0, chart.js@>=3.0.0:
version "4.5.0"
resolved "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz"
integrity sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==
@@ -5588,10 +5191,10 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
inherits "^2.0.4"
safe-buffer "^5.2.1"
-cjs-module-lexer@^2.1.0:
- version "2.2.0"
- resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz"
- integrity sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==
+cjs-module-lexer@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.3.0.tgz"
+ integrity sha512-lmNyBzi6iYiqyrExTctfmjd8tInx+dHu9PBomTfH5RXS53c27JMFJgzNv5TyAnm3PgGf+MLy0+Ws41/HlVxdKQ==
clamp@^1.0.1:
version "1.0.1"
@@ -5605,7 +5208,7 @@ class-variance-authority@^0.7.1:
dependencies:
clsx "^2.1.1"
-classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2, classnames@^2.5.1:
+classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2, classnames@^2.5.1, classnames@2.x:
version "2.5.1"
resolved "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz"
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
@@ -5692,11 +5295,6 @@ color-id@^1.1.0:
dependencies:
clamp "^1.0.1"
-color-name@1.1.3:
- version "1.1.3"
- resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
- integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
-
color-name@^1.0.0, color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
@@ -5707,7 +5305,12 @@ color-name@^2.0.0:
resolved "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz"
integrity sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==
-color-normalize@1.5.0, color-normalize@^1.5.0:
+color-name@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
+ integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
+
+color-normalize@^1.5.0, color-normalize@1.5.0:
version "1.5.0"
resolved "https://registry.npmjs.org/color-normalize/-/color-normalize-1.5.0.tgz"
integrity sha512-rUT/HDXMr6RFffrR53oX3HGWkDOP9goSAQGBkUaAYKjOE2JxozccdGyufageWDlInRAjm/jYPrf/Y38oa+7obw==
@@ -5716,7 +5319,14 @@ color-normalize@1.5.0, color-normalize@^1.5.0:
color-rgba "^2.1.1"
dtype "^2.0.0"
-color-parse@^1.4.1, color-parse@^1.4.2:
+color-parse@^1.4.1:
+ version "1.4.3"
+ resolved "https://registry.npmjs.org/color-parse/-/color-parse-1.4.3.tgz"
+ integrity sha512-BADfVl/FHkQkyo8sRBwMYBqemqsgnu7JZAwUgvBvuwwuNUZAhSvLTbsEErS5bQXzOjDR0dWzJ4vXN2Q+QoPx0A==
+ dependencies:
+ color-name "^1.0.0"
+
+color-parse@^1.4.2:
version "1.4.3"
resolved "https://registry.npmjs.org/color-parse/-/color-parse-1.4.3.tgz"
integrity sha512-BADfVl/FHkQkyo8sRBwMYBqemqsgnu7JZAwUgvBvuwwuNUZAhSvLTbsEErS5bQXzOjDR0dWzJ4vXN2Q+QoPx0A==
@@ -5730,14 +5340,6 @@ color-parse@^2.0.0:
dependencies:
color-name "^2.0.0"
-color-rgba@3.0.0:
- version "3.0.0"
- resolved "https://registry.npmjs.org/color-rgba/-/color-rgba-3.0.0.tgz"
- integrity sha512-PPwZYkEY3M2THEHHV6Y95sGUie77S7X8v+h1r6LSAPF3/LL2xJ8duUXSrkic31Nzc4odPwHgUbiX/XuTYzQHQg==
- dependencies:
- color-parse "^2.0.0"
- color-space "^2.0.0"
-
color-rgba@^2.1.1:
version "2.4.0"
resolved "https://registry.npmjs.org/color-rgba/-/color-rgba-2.4.0.tgz"
@@ -5746,6 +5348,14 @@ color-rgba@^2.1.1:
color-parse "^1.4.2"
color-space "^2.0.0"
+color-rgba@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/color-rgba/-/color-rgba-3.0.0.tgz"
+ integrity sha512-PPwZYkEY3M2THEHHV6Y95sGUie77S7X8v+h1r6LSAPF3/LL2xJ8duUXSrkic31Nzc4odPwHgUbiX/XuTYzQHQg==
+ dependencies:
+ color-parse "^2.0.0"
+ color-space "^2.0.0"
+
color-space@^2.0.0:
version "2.3.2"
resolved "https://registry.npmjs.org/color-space/-/color-space-2.3.2.tgz"
@@ -5760,9 +5370,9 @@ color-string@^1.5.5:
simple-swizzle "^0.2.2"
colord@^2.9.3:
- version "2.9.3"
- resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz"
- integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
+ version "2.10.0"
+ resolved "https://registry.npmjs.org/colord/-/colord-2.10.0.tgz"
+ integrity sha512-AidJptpBJmjTclAp9BkLwJi0T93fo5epJnbaZslpg6QVzpHjAiveF55mE9AcUJiGMqRHgMDY8soMsQtuNYMHfw==
colorette@^2.0.20:
version "2.0.20"
@@ -5781,7 +5391,22 @@ comlink@^4.4.1:
resolved "https://registry.npmjs.org/comlink/-/comlink-4.4.2.tgz"
integrity sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==
-commander@2, commander@^2.15.1, commander@^2.20.0:
+commander@^14.0.1:
+ version "14.0.1"
+ resolved "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz"
+ integrity sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==
+
+commander@^2.15.1:
+ version "2.20.3"
+ resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
+ integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
+commander@^2.20.0:
+ version "2.20.3"
+ resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
+ integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
+commander@2:
version "2.20.3"
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@@ -5791,11 +5416,6 @@ commander@7:
resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz"
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
-commander@^14.0.1:
- version "14.0.1"
- resolved "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz"
- integrity sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==
-
compute-scroll-into-view@^3.0.2:
version "3.1.1"
resolved "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz"
@@ -5910,16 +5530,6 @@ create-hash@^1.1.0, create-hash@^1.2.0:
ripemd160 "^2.0.1"
sha.js "^2.4.0"
-create-hash@~1.1.3:
- version "1.1.3"
- resolved "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz"
- integrity sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==
- dependencies:
- cipher-base "^1.0.1"
- inherits "^2.0.1"
- ripemd160 "^2.0.0"
- sha.js "^2.4.0"
-
create-hmac@^1.1.7:
version "1.1.7"
resolved "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz"
@@ -6107,23 +5717,24 @@ cssstyle@^4.2.1:
"@asamuzakjp/css-color" "^3.2.0"
rrweb-cssom "^0.8.0"
-csstype@3.2.3, csstype@^3.0.2, csstype@^3.0.8, csstype@^3.1.3:
+csstype@^3.0.2, csstype@^3.0.8, csstype@^3.1.3, csstype@^3.2.2, csstype@^3.2.3, csstype@3.2.3:
version "3.2.3"
resolved "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz"
integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==
-d3-array@1, d3-array@^1.2.1:
+d@^1.0.1, d@^1.0.2, d@1:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/d/-/d-1.0.2.tgz"
+ integrity sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==
+ dependencies:
+ es5-ext "^0.10.64"
+ type "^2.7.2"
+
+d3-array@^1.2.1:
version "1.2.4"
resolved "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz"
integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==
-"d3-array@1 - 3", "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.1.6, d3-array@^3.2.0, d3-array@^3.2.4:
- version "3.2.4"
- resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz"
- integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
- dependencies:
- internmap "1 - 2"
-
d3-array@^2.5.0:
version "2.12.1"
resolved "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz"
@@ -6131,6 +5742,18 @@ d3-array@^2.5.0:
dependencies:
internmap "^1.0.0"
+d3-array@^3.1.6, d3-array@^3.2.0, d3-array@^3.2.4, "d3-array@1 - 3", "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3:
+ version "3.2.4"
+ resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz"
+ integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
+ dependencies:
+ internmap "1 - 2"
+
+d3-array@1:
+ version "1.2.4"
+ resolved "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz"
+ integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==
+
d3-axis@3:
version "3.0.0"
resolved "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz"
@@ -6159,21 +5782,21 @@ d3-chord@3:
dependencies:
d3-path "1 - 3"
-d3-collection@1, d3-collection@^1.0.4:
+d3-collection@^1.0.4, d3-collection@1:
version "1.0.7"
resolved "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz"
integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==
+d3-color@^3.1.0, "d3-color@1 - 3", d3-color@3:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz"
+ integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
+
"d3-color@1 - 2":
version "2.0.0"
resolved "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz"
integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==
-"d3-color@1 - 3", d3-color@3, d3-color@^3.1.0:
- version "3.1.0"
- resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz"
- integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
-
d3-contour@4:
version "4.0.2"
resolved "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz"
@@ -6188,28 +5811,20 @@ d3-delaunay@6:
dependencies:
delaunator "5"
-d3-dispatch@1:
- version "1.0.6"
- resolved "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz"
- integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==
+d3-dispatch@^3.0.1, "d3-dispatch@1 - 3", d3-dispatch@3:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz"
+ integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
"d3-dispatch@1 - 2":
version "2.0.0"
resolved "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz"
integrity sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA==
-"d3-dispatch@1 - 3", d3-dispatch@3, d3-dispatch@^3.0.1:
- version "3.0.1"
- resolved "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz"
- integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
-
-d3-drag@2:
- version "2.0.0"
- resolved "https://registry.npmjs.org/d3-drag/-/d3-drag-2.0.0.tgz"
- integrity sha512-g9y9WbMnF5uqB9qKqwIIa/921RYWzlUDv9Jl1/yONQwxbOfszAWTCm8u7HOTgJgRDXiRZN56cHT9pd24dmXs8w==
- dependencies:
- d3-dispatch "1 - 2"
- d3-selection "2"
+d3-dispatch@1:
+ version "1.0.6"
+ resolved "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz"
+ integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==
"d3-drag@2 - 3", d3-drag@3:
version "3.0.0"
@@ -6219,7 +5834,15 @@ d3-drag@2:
d3-dispatch "1 - 3"
d3-selection "3"
-"d3-dsv@1 - 3", d3-dsv@3, d3-dsv@^3.0.1:
+d3-drag@2:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/d3-drag/-/d3-drag-2.0.0.tgz"
+ integrity sha512-g9y9WbMnF5uqB9qKqwIIa/921RYWzlUDv9Jl1/yONQwxbOfszAWTCm8u7HOTgJgRDXiRZN56cHT9pd24dmXs8w==
+ dependencies:
+ d3-dispatch "1 - 2"
+ d3-selection "2"
+
+d3-dsv@^3.0.1, "d3-dsv@1 - 3", d3-dsv@3:
version "3.0.1"
resolved "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz"
integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==
@@ -6228,17 +5851,17 @@ d3-drag@2:
iconv-lite "0.6"
rw "1"
+d3-ease@^3.0.1, "d3-ease@1 - 3", d3-ease@3:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz"
+ integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
+
"d3-ease@1 - 2":
version "2.0.0"
resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-2.0.0.tgz"
integrity sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ==
-"d3-ease@1 - 3", d3-ease@3, d3-ease@^3.0.1:
- version "3.0.1"
- resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz"
- integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
-
-d3-fetch@3, d3-fetch@^3.0.1:
+d3-fetch@^3.0.1, d3-fetch@3:
version "3.0.1"
resolved "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz"
integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==
@@ -6256,15 +5879,6 @@ d3-force-3d@^3.0.5:
d3-quadtree "1 - 3"
d3-timer "1 - 3"
-d3-force@3, d3-force@^3.0.0:
- version "3.0.0"
- resolved "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz"
- integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==
- dependencies:
- d3-dispatch "1 - 3"
- d3-quadtree "1 - 3"
- d3-timer "1 - 3"
-
d3-force@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz"
@@ -6275,16 +5889,25 @@ d3-force@^1.2.1:
d3-quadtree "1"
d3-timer "1"
-"d3-format@1 - 3", d3-format@3, d3-format@^3.1.0:
- version "3.1.0"
- resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz"
- integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
+d3-force@^3.0.0, d3-force@3:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz"
+ integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==
+ dependencies:
+ d3-dispatch "1 - 3"
+ d3-quadtree "1 - 3"
+ d3-timer "1 - 3"
d3-format@^1.4.5:
version "1.4.5"
resolved "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz"
integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==
+d3-format@^3.1.0, "d3-format@1 - 3", d3-format@3:
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz"
+ integrity sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==
+
d3-geo-projection@^2.9.0:
version "2.9.0"
resolved "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-2.9.0.tgz"
@@ -6304,13 +5927,6 @@ d3-geo-projection@^4.0.0:
d3-array "1 - 3"
d3-geo "1.12.0 - 3"
-"d3-geo@1.12.0 - 3", d3-geo@3, d3-geo@^3.1.1:
- version "3.1.1"
- resolved "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz"
- integrity sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==
- dependencies:
- d3-array "2.5.0 - 3"
-
d3-geo@^1.12.0, d3-geo@^1.12.1:
version "1.12.1"
resolved "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz"
@@ -6325,61 +5941,68 @@ d3-geo@^2.0.2:
dependencies:
d3-array "^2.5.0"
-d3-hierarchy@3, d3-hierarchy@^3.1.2:
- version "3.1.2"
- resolved "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz"
- integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==
+d3-geo@^3.1.1, "d3-geo@1.12.0 - 3", d3-geo@3:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz"
+ integrity sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==
+ dependencies:
+ d3-array "2.5.0 - 3"
d3-hierarchy@^1.1.9:
version "1.1.9"
resolved "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz"
integrity sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==
-"d3-interpolate@1 - 2":
- version "2.0.1"
- resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz"
- integrity sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==
- dependencies:
- d3-color "1 - 2"
+d3-hierarchy@^3.1.2, d3-hierarchy@3:
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz"
+ integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==
-"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3, d3-interpolate@^3.0.1:
+d3-interpolate@^3.0.1, "d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3:
version "3.0.1"
resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz"
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
dependencies:
d3-color "1 - 3"
-d3-octree@1, d3-octree@^1.0.2:
+"d3-interpolate@1 - 2":
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz"
+ integrity sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==
+ dependencies:
+ d3-color "1 - 2"
+
+d3-octree@^1.0.2, d3-octree@1:
version "1.1.0"
resolved "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz"
integrity sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==
+d3-path@^3.1.0, "d3-path@1 - 3", d3-path@3:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz"
+ integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
+
d3-path@1:
version "1.0.9"
resolved "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz"
integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
-"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0:
- version "3.1.0"
- resolved "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz"
- integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
-
d3-polygon@3:
version "3.0.1"
resolved "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz"
integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==
+d3-quadtree@^3.0.1, "d3-quadtree@1 - 3", d3-quadtree@3:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz"
+ integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==
+
d3-quadtree@1:
version "1.0.7"
resolved "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz"
integrity sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==
-"d3-quadtree@1 - 3", d3-quadtree@3, d3-quadtree@^3.0.1:
- version "3.0.1"
- resolved "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz"
- integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==
-
-d3-random@3, d3-random@^3.0.1:
+d3-random@^3.0.1, d3-random@3:
version "3.0.1"
resolved "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz"
integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==
@@ -6389,7 +6012,7 @@ d3-regression@^1.3.10:
resolved "https://registry.npmjs.org/d3-regression/-/d3-regression-1.3.10.tgz"
integrity sha512-PF8GWEL70cHHWpx2jUQXc68r1pyPHIA+St16muk/XRokETzlegj5LriNKg7o4LR0TySug4nHYPJNNRz/W+/Niw==
-d3-scale-chromatic@3, d3-scale-chromatic@^3.1.0:
+d3-scale-chromatic@^3.1.0, d3-scale-chromatic@3:
version "3.1.0"
resolved "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz"
integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==
@@ -6397,7 +6020,7 @@ d3-scale-chromatic@3, d3-scale-chromatic@^3.1.0:
d3-color "1 - 3"
d3-interpolate "1 - 3"
-d3-scale@4, d3-scale@^4.0.2:
+d3-scale@^4.0.2, d3-scale@4:
version "4.0.2"
resolved "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz"
integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
@@ -6408,7 +6031,7 @@ d3-scale@4, d3-scale@^4.0.2:
d3-time "2.1.1 - 3"
d3-time-format "2 - 4"
-d3-selection@2, d3-selection@^2.0.0:
+d3-selection@^2.0.0, d3-selection@2:
version "2.0.0"
resolved "https://registry.npmjs.org/d3-selection/-/d3-selection-2.0.0.tgz"
integrity sha512-XoGGqhLUN/W14NmaqcO/bb1nqjDAw5WtSYb2X8wiuQWvSZUsUVYsOSkOybUrNvcBjaywBdYPy03eXHMXjk9nZA==
@@ -6418,13 +6041,6 @@ d3-selection@2, d3-selection@^2.0.0:
resolved "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz"
integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
-d3-shape@3, d3-shape@^3.1.0, d3-shape@^3.2.0:
- version "3.2.0"
- resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz"
- integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==
- dependencies:
- d3-path "^3.1.0"
-
d3-shape@^1.2.0:
version "1.3.7"
resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz"
@@ -6432,12 +6048,12 @@ d3-shape@^1.2.0:
dependencies:
d3-path "1"
-"d3-time-format@2 - 4", d3-time-format@4:
- version "4.1.0"
- resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz"
- integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
+d3-shape@^3.1.0, d3-shape@^3.2.0, d3-shape@3:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz"
+ integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==
dependencies:
- d3-time "1 - 3"
+ d3-path "^3.1.0"
d3-time-format@^2.2.3:
version "2.3.0"
@@ -6446,32 +6062,50 @@ d3-time-format@^2.2.3:
dependencies:
d3-time "1"
-d3-time@1, d3-time@^1.1.0:
+"d3-time-format@2 - 4", d3-time-format@4:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz"
+ integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
+ dependencies:
+ d3-time "1 - 3"
+
+d3-time@^1.1.0, d3-time@1:
version "1.1.0"
resolved "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz"
integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==
-"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3, d3-time@^3.0.0, d3-time@^3.1.0:
+d3-time@^3.0.0, d3-time@^3.1.0, "d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3:
version "3.1.0"
resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz"
integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
dependencies:
d3-array "2 - 3"
-d3-timer@1:
- version "1.0.10"
- resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz"
- integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==
+d3-timer@^3.0.1, "d3-timer@1 - 3", d3-timer@3:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz"
+ integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
"d3-timer@1 - 2":
version "2.0.0"
resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-2.0.0.tgz"
integrity sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==
-"d3-timer@1 - 3", d3-timer@3, d3-timer@^3.0.1:
+d3-timer@1:
+ version "1.0.10"
+ resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz"
+ integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==
+
+"d3-transition@2 - 3", d3-transition@3:
version "3.0.1"
- resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz"
- integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
+ resolved "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz"
+ integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==
+ dependencies:
+ d3-color "1 - 3"
+ d3-dispatch "1 - 3"
+ d3-ease "1 - 3"
+ d3-interpolate "1 - 3"
+ d3-timer "1 - 3"
d3-transition@2:
version "2.0.0"
@@ -6484,16 +6118,16 @@ d3-transition@2:
d3-interpolate "1 - 2"
d3-timer "1 - 2"
-"d3-transition@2 - 3", d3-transition@3:
- version "3.0.1"
- resolved "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz"
- integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==
+d3-zoom@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/d3-zoom/-/d3-zoom-2.0.0.tgz"
+ integrity sha512-fFg7aoaEm9/jf+qfstak0IYpnesZLiMX6GZvXtUSdv8RH2o4E2qeelgdU09eKS6wGuiGMfcnMI0nTIqWzRHGpw==
dependencies:
- d3-color "1 - 3"
- d3-dispatch "1 - 3"
- d3-ease "1 - 3"
- d3-interpolate "1 - 3"
- d3-timer "1 - 3"
+ d3-dispatch "1 - 2"
+ d3-drag "2"
+ d3-interpolate "1 - 2"
+ d3-selection "2"
+ d3-transition "2"
d3-zoom@3:
version "3.0.0"
@@ -6506,17 +6140,6 @@ d3-zoom@3:
d3-selection "2 - 3"
d3-transition "2 - 3"
-d3-zoom@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/d3-zoom/-/d3-zoom-2.0.0.tgz"
- integrity sha512-fFg7aoaEm9/jf+qfstak0IYpnesZLiMX6GZvXtUSdv8RH2o4E2qeelgdU09eKS6wGuiGMfcnMI0nTIqWzRHGpw==
- dependencies:
- d3-dispatch "1 - 2"
- d3-drag "2"
- d3-interpolate "1 - 2"
- d3-selection "2"
- d3-transition "2"
-
d3@^7.8.5:
version "7.9.0"
resolved "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz"
@@ -6547,19 +6170,11 @@ d3@^7.8.5:
d3-scale-chromatic "3"
d3-selection "3"
d3-shape "3"
- d3-time "3"
- d3-time-format "4"
- d3-timer "3"
- d3-transition "3"
- d3-zoom "3"
-
-d@1, d@^1.0.1, d@^1.0.2:
- version "1.0.2"
- resolved "https://registry.npmjs.org/d/-/d-1.0.2.tgz"
- integrity sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==
- dependencies:
- es5-ext "^0.10.64"
- type "^2.7.2"
+ d3-time "3"
+ d3-time-format "4"
+ d3-timer "3"
+ d3-transition "3"
+ d3-zoom "3"
dagre@^0.8.5:
version "0.8.5"
@@ -6614,7 +6229,7 @@ date-fns-tz@^2.0.1:
resolved "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-2.0.1.tgz"
integrity sha512-fJCG3Pwx8HUoLhkepdsP7Z5RsucUi+ZBOxyM5d0ZZ6c4SdYustq0VMmOu6Wf7bli+yS/Jwp91TOCqn9jMcVrUA==
-date-fns@^2.14.0, date-fns@^2.30.0:
+date-fns@^2.14.0, "date-fns@^2.25.0 || ^3.2.0 || ^4.0.0", "date-fns@^2.28.0 || ^3.0.0", date-fns@^2.30.0, "date-fns@>= 2.x", date-fns@2.x:
version "2.30.0"
resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz"
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
@@ -6626,32 +6241,32 @@ date-fns@^3.3.1:
resolved "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz"
integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==
-dayjs@^1.11.11, dayjs@^1.11.13:
+dayjs@^1.10.7, dayjs@^1.11.11, dayjs@^1.11.13, dayjs@^1.8.17, "dayjs@>= 1.x":
version "1.11.18"
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz"
integrity sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==
-debug@2:
- version "2.6.9"
- resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
- integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
- dependencies:
- ms "2.0.0"
-
-debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.1, debug@^4.4.3:
- version "4.4.3"
- resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz"
- integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
+debug@^3.2.6:
+ version "3.2.7"
+ resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
+ integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
dependencies:
- ms "^2.1.3"
+ ms "^2.1.1"
-debug@^3.2.6, debug@^3.2.7:
+debug@^3.2.7:
version "3.2.7"
resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
dependencies:
ms "^2.1.1"
+debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.1, debug@^4.4.3, debug@4:
+ version "4.4.3"
+ resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz"
+ integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
+ dependencies:
+ ms "^2.1.3"
+
decimal.js-light@^2.4.1:
version "2.5.1"
resolved "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz"
@@ -6747,10 +6362,10 @@ detect-kerning@^2.1.2:
resolved "https://registry.npmjs.org/detect-kerning/-/detect-kerning-2.1.2.tgz"
integrity sha512-I3JIbrnKPAntNLl1I6TpSQQdQ4AutYzv/sKMFKbepawV/hlH0GmYKhUoOEMd4xqaUHT+Bm0f4127lh5qs1m1tw==
-detect-libc@^1.0.3:
- version "1.0.3"
- resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz"
- integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==
+detect-libc@^2.0.3:
+ version "2.1.2"
+ resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz"
+ integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==
detect-newline@^3.1.0:
version "3.1.0"
@@ -6762,11 +6377,6 @@ dfa@^1.2.0:
resolved "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz"
integrity sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==
-diff-sequences@^29.6.3:
- version "29.6.3"
- resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz"
- integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==
-
diff@^5.0.0, diff@^5.2.2:
version "5.2.2"
resolved "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz"
@@ -6845,7 +6455,7 @@ domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
-domhandler@4.3.1, domhandler@^4.2.0, domhandler@^4.2.2:
+domhandler@^4.2.0, domhandler@^4.2.2, domhandler@4.3.1:
version "4.3.1"
resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz"
integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==
@@ -6860,14 +6470,14 @@ domhandler@^5.0.2, domhandler@^5.0.3:
domelementtype "^2.3.0"
dompurify@^2.5.4:
- version "2.5.8"
- resolved "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz"
- integrity sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==
+ version "2.5.9"
+ resolved "https://registry.npmjs.org/dompurify/-/dompurify-2.5.9.tgz"
+ integrity sha512-i6mvVmWN4xo9LrhCOZrDgSs9noW6nOahbrmzjRbPF36YPyj5Ue5lgok0MHDWkG7xzpWFO2OYttXdzM7rJxHvNA==
dompurify@^3.3.1, dompurify@^3.4.16:
- version "3.4.16"
- resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.4.16.tgz#c51b3709ea15ee9abb07e804f2ef30ea9b953128"
- integrity sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==
+ version "3.4.17"
+ resolved "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz"
+ integrity sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==
optionalDependencies:
"@types/trusted-types" "^2.0.7"
@@ -6948,10 +6558,10 @@ eastasianwidth@^0.2.0:
resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
-electron-to-chromium@^1.5.427:
- version "1.5.430"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.430.tgz#788eada5bbfda8095a95149184368a2bb4fb8e4a"
- integrity sha512-e1QEj72Y4zd8RlNZVmoTg+iCOSVwpk05IOiiQwdrkwCSVlZfPthevErhE+nckGd2YbsXfp1SkisznhGVIXP2NQ==
+electron-to-chromium@^1.5.464:
+ version "1.5.468"
+ resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.468.tgz"
+ integrity sha512-8XdEp3sIqO+PzmVPxhzqHq/3+yCxp44m27cm2WY1sRn3ZhwKyKmqSi2hx6gEhmnOXmh8NUuYE00z6Onzn5m6DA==
elementary-circuits-directed-graph@^1.0.4:
version "1.3.1"
@@ -6960,7 +6570,7 @@ elementary-circuits-directed-graph@^1.0.4:
dependencies:
strongly-connected-components "^1.0.1"
-elliptic@^6.5.3, elliptic@^6.5.5, elliptic@^6.6.1:
+elliptic@^6.5.3, elliptic@^6.6.1:
version "6.6.1"
resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz"
integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==
@@ -7151,9 +6761,9 @@ es-module-lexer@^1.7.0:
integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==
es-module-lexer@^2.1.0:
- version "2.1.0"
- resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz"
- integrity sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==
+ version "2.3.2"
+ resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz"
+ integrity sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
version "1.1.1"
@@ -7421,14 +7031,6 @@ eslint-plugin-vitest@^0.5.4:
dependencies:
"@typescript-eslint/utils" "^7.7.1"
-eslint-scope@5.1.1:
- version "5.1.1"
- resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz"
- integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
- dependencies:
- esrecurse "^4.3.0"
- estraverse "^4.1.1"
-
eslint-scope@^7.2.2:
version "7.2.2"
resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz"
@@ -7437,6 +7039,14 @@ eslint-scope@^7.2.2:
esrecurse "^4.3.0"
estraverse "^5.2.0"
+eslint-scope@5.1.1:
+ version "5.1.1"
+ resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz"
+ integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^4.1.1"
+
eslint-visitor-keys@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz"
@@ -7457,7 +7067,7 @@ eslint-visitor-keys@^5.0.0:
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz"
integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==
-eslint@^8.57.1:
+"eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9", "eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9", "eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7", "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.5.0 || ^8.0.0 || ^9.0.0", eslint@^8.56.0, "eslint@^8.57.0 || ^9.0.0", "eslint@^8.57.0 || ^9.0.0 || ^10.0.0", eslint@^8.57.1, eslint@>=3.14.1, eslint@>=5.0.0:
version "8.57.1"
resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz"
integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==
@@ -7626,40 +7236,17 @@ expect-type@^1.2.1:
resolved "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz"
integrity sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==
-expect@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz"
- integrity sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==
- dependencies:
- "@jest/expect-utils" "30.4.1"
- "@jest/get-type" "30.1.0"
- jest-matcher-utils "30.4.1"
- jest-message-util "30.4.1"
- jest-mock "30.4.1"
- jest-util "30.4.1"
-
-expect@^29.7.0:
- version "29.7.0"
- resolved "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz"
- integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==
- dependencies:
- "@jest/expect-utils" "^29.7.0"
- jest-get-type "^29.6.3"
- jest-matcher-utils "^29.7.0"
- jest-message-util "^29.7.0"
- jest-util "^29.7.0"
-
-expect@^30.0.0:
- version "30.1.1"
- resolved "https://registry.npmjs.org/expect/-/expect-30.1.1.tgz"
- integrity sha512-OKe7cdic4qbfWd/CcgwJvvCrNX2KWfuMZee9AfJHL1gTYmvqjBjZG1a2NwfhspBzxzlXwsN75WWpKTYfsJpBxg==
+expect@^30.0.0, expect@30.5.2:
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/expect/-/expect-30.5.2.tgz"
+ integrity sha512-0LNuMvs8/5mRYhnCR4k+lGV7fqPMs6Bnksda4S3zsCUmdxBpAn4zU4NNzXdq1pMKe/H4W5t/zVIDSJ/H3e0vDA==
dependencies:
- "@jest/expect-utils" "30.1.1"
- "@jest/get-type" "30.1.0"
- jest-matcher-utils "30.1.1"
- jest-message-util "30.1.0"
- jest-mock "30.0.5"
- jest-util "30.0.5"
+ "@jest/expect-utils" "30.5.2"
+ "@jest/get-type" "30.5.0"
+ jest-matcher-utils "30.5.2"
+ jest-message-util "30.5.1"
+ jest-mock "30.5.2"
+ jest-util "30.5.1"
ext@^1.7.0:
version "1.7.0"
@@ -7736,9 +7323,9 @@ fast-png@^6.2.0:
pako "^2.1.0"
fast-uri@^3.0.1:
- version "3.1.8"
- resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.8.tgz#f7db8d942e20eead3cfe93b0beeb4eb0169e938b"
- integrity sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==
+ version "3.1.9"
+ resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz"
+ integrity sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==
fastest-levenshtein@^1.0.16:
version "1.0.16"
@@ -7759,7 +7346,12 @@ fb-watchman@^2.0.0, fb-watchman@^2.0.2:
dependencies:
bser "2.1.1"
-fdir@^6.4.4, fdir@^6.5.0:
+fdir@^6.4.4:
+ version "6.5.0"
+ resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz"
+ integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
+
+fdir@^6.5.0:
version "6.5.0"
resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz"
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
@@ -7770,14 +7362,14 @@ fecha@^4.2.1:
integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==
fflate@^0.7.3:
- version "0.7.4"
- resolved "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz"
- integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==
+ version "0.7.5"
+ resolved "https://registry.npmjs.org/fflate/-/fflate-0.7.5.tgz"
+ integrity sha512-QieYf//cis6ywHNi5qW1+PXPQ4bC+XVJAtS4AXIML8P76GroEiOxm/oQtn1f02UkJY1+KsXMJcC+R2v/Eg4G3g==
fflate@^0.8.1, fflate@^0.8.2:
- version "0.8.2"
- resolved "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz"
- integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==
+ version "0.8.3"
+ resolved "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz"
+ integrity sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==
file-entry-cache@^11.1.1:
version "11.1.5"
@@ -7857,9 +7449,9 @@ flru@^1.0.2:
integrity sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==
follow-redirects@^1.16.0:
- version "1.16.0"
- resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz"
- integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==
+ version "1.16.2"
+ resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.2.tgz"
+ integrity sha512-/kVP2U8z596Ia0gwFrwyIeo74ta5aVmSeU1FfAUMAmcEP5EP7zydmag3SLiN64NaRKef3lxsq7i/g7ltJQDGow==
font-atlas@^2.1.0:
version "2.1.0"
@@ -7933,7 +7525,7 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
-fsevents@^2.3.2, fsevents@^2.3.3, fsevents@~2.3.2, fsevents@~2.3.3:
+fsevents@^2.3.2, fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
@@ -8110,7 +7702,7 @@ glob-to-regexp@^0.4.1:
resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
-glob@^10.4.1, glob@^10.5.0:
+glob@^10.4.1:
version "10.5.0"
resolved "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz"
integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==
@@ -8122,17 +7714,14 @@ glob@^10.4.1, glob@^10.5.0:
package-json-from-dist "^1.0.0"
path-scurry "^1.11.1"
-glob@^10.5.0:
- version "10.5.0"
- resolved "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz"
- integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==
+glob@^13.0.6:
+ version "13.0.6"
+ resolved "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz"
+ integrity sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==
dependencies:
- foreground-child "^3.1.0"
- jackspeak "^3.1.2"
- minimatch "^9.0.4"
- minipass "^7.1.2"
- package-json-from-dist "^1.0.0"
- path-scurry "^1.11.1"
+ minimatch "^10.2.2"
+ minipass "^7.1.3"
+ path-scurry "^2.0.2"
glob@^7.1.3:
version "7.2.3"
@@ -8439,14 +8028,7 @@ has-tostringtag@^1.0.2:
dependencies:
has-symbols "^1.0.3"
-hash-base@^2.0.0:
- version "2.0.2"
- resolved "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz"
- integrity sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==
- dependencies:
- inherits "^2.0.1"
-
-hash-base@^3.0.0, hash-base@~3.0, hash-base@~3.0.4:
+hash-base@^3.0.0, hash-base@~3.0.4:
version "3.0.5"
resolved "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz"
integrity sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==
@@ -8454,6 +8036,16 @@ hash-base@^3.0.0, hash-base@~3.0, hash-base@~3.0.4:
inherits "^2.0.4"
safe-buffer "^5.2.1"
+hash-base@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz"
+ integrity sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==
+ dependencies:
+ inherits "^2.0.4"
+ readable-stream "^2.3.8"
+ safe-buffer "^5.2.1"
+ to-buffer "^1.2.1"
+
hash.js@^1.0.0, hash.js@^1.0.3:
version "1.1.7"
resolved "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz"
@@ -8567,16 +8159,6 @@ html2canvas@^1.0.0-rc.5, html2canvas@^1.4.1:
css-line-break "^2.1.0"
text-segmentation "^1.0.3"
-htmlparser2@7.2.0:
- version "7.2.0"
- resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz"
- integrity sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==
- dependencies:
- domelementtype "^2.0.1"
- domhandler "^4.2.2"
- domutils "^2.8.0"
- entities "^3.0.1"
-
htmlparser2@^10.0.0:
version "10.0.0"
resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz"
@@ -8587,6 +8169,16 @@ htmlparser2@^10.0.0:
domutils "^3.2.1"
entities "^6.0.0"
+htmlparser2@7.2.0:
+ version "7.2.0"
+ resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz"
+ integrity sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==
+ dependencies:
+ domelementtype "^2.0.1"
+ domhandler "^4.2.2"
+ domutils "^2.8.0"
+ entities "^3.0.1"
+
http-proxy-agent@^7.0.2:
version "7.0.2"
resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz"
@@ -8621,13 +8213,6 @@ husky@^7.0.4:
resolved "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz"
integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==
-iconv-lite@0.6, iconv-lite@0.6.3, iconv-lite@^0.6.3:
- version "0.6.3"
- resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz"
- integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
- dependencies:
- safer-buffer ">= 2.1.2 < 3.0.0"
-
iconv-lite@^0.4.4:
version "0.4.24"
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz"
@@ -8635,6 +8220,13 @@ iconv-lite@^0.4.4:
dependencies:
safer-buffer ">= 2.1.2 < 3"
+iconv-lite@^0.6.3, iconv-lite@0.6, iconv-lite@0.6.3:
+ version "0.6.3"
+ resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz"
+ integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3.0.0"
+
ieee754@^1.1.12, ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
@@ -8656,9 +8248,9 @@ immer@^10.0.3:
integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==
immutable@^5.0.2:
- version "5.1.3"
- resolved "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz"
- integrity sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==
+ version "5.1.9"
+ resolved "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz"
+ integrity sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==
import-fresh@^3.2.1, import-fresh@^3.3.0:
version "3.3.1"
@@ -8694,7 +8286,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4:
+inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4, inherits@2:
version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -8723,16 +8315,16 @@ internal-slot@^1.1.0:
hasown "^2.0.2"
side-channel "^1.1.0"
-"internmap@1 - 2":
- version "2.0.3"
- resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz"
- integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
-
internmap@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz"
integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==
+"internmap@1 - 2":
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz"
+ integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
+
invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz"
@@ -9050,12 +8642,12 @@ is-weakset@^2.0.3:
call-bound "^1.0.3"
get-intrinsic "^1.2.6"
-isarray@0.0.1:
- version "0.0.1"
- resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
- integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
+isarray@^2.0.1:
+ version "2.0.5"
+ resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz"
+ integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
-isarray@^2.0.1, isarray@^2.0.5:
+isarray@^2.0.5:
version "2.0.5"
resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz"
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
@@ -9065,6 +8657,11 @@ isarray@~1.0.0:
resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
+isarray@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+ integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
+
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
@@ -9149,169 +8746,127 @@ jackspeak@^3.1.2:
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
-jest-changed-files@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz"
- integrity sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==
+jest-changed-files@30.5.1:
+ version "30.5.1"
+ resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.5.1.tgz"
+ integrity sha512-0+bvMM/ENhDI29Z8q1r4HxiDIi4G5tnBmSw4esfPQoj9q8Nik7KIyuxHkTVnnniJQf05SxpGaKDQ+4h28ynGPg==
dependencies:
execa "^5.1.1"
- jest-util "30.4.1"
+ jest-util "30.5.1"
p-limit "^3.1.0"
-jest-circus@30.4.2:
- version "30.4.2"
- resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz"
- integrity sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==
+jest-circus@30.5.2:
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-30.5.2.tgz"
+ integrity sha512-vffFgjs5JSJ9q4ds1vNW3klKYPJfYfIY61LT/zaZgvTeASJOWZUt8LBfHgIcV4rwxlPBLl/ePBGccHEclS4PlQ==
dependencies:
- "@jest/environment" "30.4.1"
- "@jest/expect" "30.4.1"
- "@jest/test-result" "30.4.1"
- "@jest/types" "30.4.1"
+ "@jest/environment" "30.5.2"
+ "@jest/expect" "30.5.2"
+ "@jest/test-result" "30.5.2"
+ "@jest/types" "30.5.1"
"@types/node" "*"
chalk "^4.1.2"
co "^4.6.0"
dedent "^1.6.0"
is-generator-fn "^2.1.0"
- jest-each "30.4.1"
- jest-matcher-utils "30.4.1"
- jest-message-util "30.4.1"
- jest-runtime "30.4.2"
- jest-snapshot "30.4.1"
- jest-util "30.4.1"
+ jest-each "30.5.2"
+ jest-matcher-utils "30.5.2"
+ jest-message-util "30.5.1"
+ jest-runtime "30.5.2"
+ jest-snapshot "30.5.2"
+ jest-util "30.5.1"
p-limit "^3.1.0"
- pretty-format "30.4.1"
+ pretty-format "30.5.1"
pure-rand "^7.0.0"
slash "^3.0.0"
stack-utils "^2.0.6"
-jest-cli@30.4.2:
- version "30.4.2"
- resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz"
- integrity sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==
+jest-cli@30.5.2:
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-30.5.2.tgz"
+ integrity sha512-YYYxUUVjFgPvgEleKNKMaYGIFgR3l3832Cl0kRL/oCiDZ03v7wImUtquHl2OOBCCNgprjZgBN5bczS/JklPBDg==
dependencies:
- "@jest/core" "30.4.2"
- "@jest/test-result" "30.4.1"
- "@jest/types" "30.4.1"
+ "@jest/core" "30.5.2"
+ "@jest/test-result" "30.5.2"
+ "@jest/types" "30.5.1"
chalk "^4.1.2"
exit-x "^0.2.2"
import-local "^3.2.0"
- jest-config "30.4.2"
- jest-util "30.4.1"
- jest-validate "30.4.1"
+ jest-config "30.5.2"
+ jest-util "30.5.1"
+ jest-validate "30.5.1"
yargs "^17.7.2"
-jest-config@30.4.2:
- version "30.4.2"
- resolved "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz"
- integrity sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==
+jest-config@30.5.2:
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/jest-config/-/jest-config-30.5.2.tgz"
+ integrity sha512-U6221OZekabePvGcGPf4raIBKMvYQWFHvhobDJRojOYaFMj2HaGytAdknjDhYc6JTz2fdHW9+6k0wWUDaNeOFQ==
dependencies:
"@babel/core" "^7.27.4"
- "@jest/get-type" "30.1.0"
- "@jest/pattern" "30.4.0"
- "@jest/test-sequencer" "30.4.1"
- "@jest/types" "30.4.1"
- babel-jest "30.4.1"
+ "@jest/get-type" "30.5.0"
+ "@jest/pattern" "30.5.0"
+ "@jest/test-sequencer" "30.5.2"
+ "@jest/types" "30.5.1"
+ babel-jest "30.5.2"
chalk "^4.1.2"
ci-info "^4.2.0"
deepmerge "^4.3.1"
- glob "^10.5.0"
+ glob "^13.0.6"
graceful-fs "^4.2.11"
- jest-circus "30.4.2"
- jest-docblock "30.4.0"
- jest-environment-node "30.4.1"
- jest-regex-util "30.4.0"
- jest-resolve "30.4.1"
- jest-runner "30.4.2"
- jest-util "30.4.1"
- jest-validate "30.4.1"
+ jest-circus "30.5.2"
+ jest-docblock "30.5.0"
+ jest-environment-node "30.5.2"
+ jest-regex-util "30.5.0"
+ jest-resolve "30.5.1"
+ jest-runner "30.5.2"
+ jest-util "30.5.1"
+ jest-validate "30.5.1"
parse-json "^5.2.0"
- pretty-format "30.4.1"
+ pretty-format "30.5.1"
slash "^3.0.0"
strip-json-comments "^3.1.1"
-jest-diff@30.1.1:
- version "30.1.1"
- resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-30.1.1.tgz"
- integrity sha512-LUU2Gx8EhYxpdzTR6BmjL1ifgOAQJQELTHOiPv9KITaKjZvJ9Jmgigx01tuZ49id37LorpGc9dPBPlXTboXScw==
- dependencies:
- "@jest/diff-sequences" "30.0.1"
- "@jest/get-type" "30.1.0"
- chalk "^4.1.2"
- pretty-format "30.0.5"
-
-jest-diff@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz"
- integrity sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==
+jest-diff@30.5.2:
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-30.5.2.tgz"
+ integrity sha512-rBtHnI6BWTiWj3lM7fOLVfChx7R9B0u9VV3PRHawKs79x6ZjW1QoSrKenaigNGWlvhtoi2BqvDbABDie+Os3xQ==
dependencies:
- "@jest/diff-sequences" "30.4.0"
- "@jest/get-type" "30.1.0"
+ "@jest/diff-sequences" "30.5.0"
+ "@jest/get-type" "30.5.0"
chalk "^4.1.2"
- pretty-format "30.4.1"
-
-jest-diff@^29.7.0:
- version "29.7.0"
- resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz"
- integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==
- dependencies:
- chalk "^4.0.0"
- diff-sequences "^29.6.3"
- jest-get-type "^29.6.3"
- pretty-format "^29.7.0"
+ pretty-format "30.5.1"
-jest-docblock@30.4.0:
- version "30.4.0"
- resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz"
- integrity sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==
+jest-docblock@30.5.0:
+ version "30.5.0"
+ resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.5.0.tgz"
+ integrity sha512-NwDqcxtoZi33RhuW+zJS/RVA3rmheQ8BnwpYZuc/Eruaz6seQb7+aoCeDZu/3X7W2XmD8DbSo9Pn72DbKsBFYw==
dependencies:
detect-newline "^3.1.0"
-jest-each@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz"
- integrity sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==
+jest-each@30.5.2:
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/jest-each/-/jest-each-30.5.2.tgz"
+ integrity sha512-GRrW+7fmmgmkvyfEBqc1QFdWnF1Ex5yG6Y6AWL4TTxnkWlKIT98dsKh3P3UuWm6v4XsQwVE6DEzXIPMzWJ3ZbA==
dependencies:
- "@jest/get-type" "30.1.0"
- "@jest/types" "30.4.1"
+ "@jest/get-type" "30.5.0"
+ "@jest/types" "30.5.1"
chalk "^4.1.2"
- jest-util "30.4.1"
- pretty-format "30.4.1"
-
-jest-environment-node@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz"
- integrity sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==
- dependencies:
- "@jest/environment" "30.4.1"
- "@jest/fake-timers" "30.4.1"
- "@jest/types" "30.4.1"
- "@types/node" "*"
- jest-mock "30.4.1"
- jest-util "30.4.1"
- jest-validate "30.4.1"
-
-jest-get-type@^29.6.3:
- version "29.6.3"
- resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz"
- integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==
-
-jest-haste-map@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz"
- integrity sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==
- dependencies:
- "@jest/types" "30.4.1"
+ jest-regex-util "30.5.0"
+ jest-util "30.5.1"
+ pretty-format "30.5.1"
+
+jest-environment-node@30.5.2:
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.5.2.tgz"
+ integrity sha512-Ciz4KgTGIbojxSKpuFImgWJj5EyqfE8xMq9NxWmOXnQywHHS03H9Mv+vaCB29J5pr3JBrY27WbRV18rBdjh8Dg==
+ dependencies:
+ "@jest/environment" "30.5.2"
+ "@jest/fake-timers" "30.5.2"
+ "@jest/types" "30.5.1"
"@types/node" "*"
- anymatch "^3.1.3"
- fb-watchman "^2.0.2"
- graceful-fs "^4.2.11"
- jest-regex-util "30.4.0"
- jest-util "30.4.1"
- jest-worker "30.4.1"
- picomatch "^4.0.3"
- walker "^1.0.8"
- optionalDependencies:
- fsevents "^2.3.3"
+ jest-mock "30.5.2"
+ jest-util "30.5.1"
+ jest-validate "30.5.1"
jest-haste-map@^29.7.0:
version "29.7.0"
@@ -9332,74 +8887,40 @@ jest-haste-map@^29.7.0:
optionalDependencies:
fsevents "^2.3.2"
-jest-leak-detector@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz"
- integrity sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==
- dependencies:
- "@jest/get-type" "30.1.0"
- pretty-format "30.4.1"
-
-jest-matcher-utils@30.1.1:
- version "30.1.1"
- resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.1.1.tgz"
- integrity sha512-SuH2QVemK48BNTqReti6FtjsMPFsSOD/ZzRxU1TttR7RiRsRSe78d03bb4Cx6D4bQC/80Q8U4VnaaAH9FlbZ9w==
- dependencies:
- "@jest/get-type" "30.1.0"
- chalk "^4.1.2"
- jest-diff "30.1.1"
- pretty-format "30.0.5"
-
-jest-matcher-utils@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz"
- integrity sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==
- dependencies:
- "@jest/get-type" "30.1.0"
- chalk "^4.1.2"
- jest-diff "30.4.1"
- pretty-format "30.4.1"
-
-jest-matcher-utils@^29.7.0:
- version "29.7.0"
- resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz"
- integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==
+jest-haste-map@30.5.1:
+ version "30.5.1"
+ resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.5.1.tgz"
+ integrity sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==
dependencies:
- chalk "^4.0.0"
- jest-diff "^29.7.0"
- jest-get-type "^29.6.3"
- pretty-format "^29.7.0"
+ "@jest/types" "30.5.1"
+ "@parcel/watcher" "^2.6.0"
+ "@types/node" "*"
+ anymatch "^3.1.3"
+ fb-watchman "^2.0.2"
+ fdir "^6.5.0"
+ graceful-fs "^4.2.11"
+ jest-regex-util "30.5.0"
+ jest-util "30.5.1"
+ jest-worker "30.5.1"
+ picomatch "^4.0.3"
-jest-message-util@30.1.0:
- version "30.1.0"
- resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.1.0.tgz"
- integrity sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==
+jest-leak-detector@30.5.1:
+ version "30.5.1"
+ resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.5.1.tgz"
+ integrity sha512-gt4GT2aWgEoCNTcBe4rqS74xTIUJxi+UD9SNSu9aOk5LmTEd6fS5KSTmAKAfitCCktQHNN+upUuL6EkBfFbMDQ==
dependencies:
- "@babel/code-frame" "^7.27.1"
- "@jest/types" "30.0.5"
- "@types/stack-utils" "^2.0.3"
- chalk "^4.1.2"
- graceful-fs "^4.2.11"
- micromatch "^4.0.8"
- pretty-format "30.0.5"
- slash "^3.0.0"
- stack-utils "^2.0.6"
+ "@jest/get-type" "30.5.0"
+ pretty-format "30.5.1"
-jest-message-util@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz"
- integrity sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==
+jest-matcher-utils@30.5.2:
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.5.2.tgz"
+ integrity sha512-lFkB365PTIHOhPzwrb9T6gvlDnPpOZ7/c3ewOKKB7bzztqjVrtlyRL2MkyfJXpyY2u2SKw001JKrTx14fBUFcQ==
dependencies:
- "@babel/code-frame" "^7.27.1"
- "@jest/types" "30.4.1"
- "@types/stack-utils" "^2.0.3"
+ "@jest/get-type" "30.5.0"
chalk "^4.1.2"
- graceful-fs "^4.2.11"
- jest-util "30.4.1"
- picomatch "^4.0.3"
- pretty-format "30.4.1"
- slash "^3.0.0"
- stack-utils "^2.0.6"
+ jest-diff "30.5.2"
+ pretty-format "30.5.1"
jest-message-util@^29.7.0:
version "29.7.0"
@@ -9416,23 +8937,21 @@ jest-message-util@^29.7.0:
slash "^3.0.0"
stack-utils "^2.0.3"
-jest-mock@30.0.5:
- version "30.0.5"
- resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz"
- integrity sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==
- dependencies:
- "@jest/types" "30.0.5"
- "@types/node" "*"
- jest-util "30.0.5"
-
-jest-mock@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz"
- integrity sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==
+jest-message-util@30.5.1:
+ version "30.5.1"
+ resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz"
+ integrity sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==
dependencies:
- "@jest/types" "30.4.1"
- "@types/node" "*"
- jest-util "30.4.1"
+ "@babel/code-frame" "^7.27.1"
+ "@jest/types" "30.5.1"
+ "@types/stack-utils" "^2.0.3"
+ chalk "^4.1.2"
+ graceful-fs "^4.2.11"
+ jest-util "30.5.1"
+ picomatch "^4.0.3"
+ pretty-format "30.5.1"
+ slash "^3.0.0"
+ stack-utils "^2.0.6"
jest-mock@^29.7.0:
version "29.7.0"
@@ -9443,230 +8962,181 @@ jest-mock@^29.7.0:
"@types/node" "*"
jest-util "^29.7.0"
-jest-pnp-resolver@^1.2.3:
- version "1.2.3"
- resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz"
- integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==
-
-jest-regex-util@30.0.1:
- version "30.0.1"
- resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz"
- integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==
-
-jest-regex-util@30.4.0:
- version "30.4.0"
- resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz"
- integrity sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==
+jest-mock@30.5.2:
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-30.5.2.tgz"
+ integrity sha512-KTHb37Fhj8xY8qPFvTFaFJHcdsumQp+ExsUlgoX232Agmc706gagBs53+CYvulV7MVQo9AgXOUxsqzzVBoJlrA==
+ dependencies:
+ "@jest/expect-utils" "30.5.2"
+ "@jest/types" "30.5.1"
+ "@types/node" "*"
+ jest-util "30.5.1"
jest-regex-util@^29.6.3:
version "29.6.3"
resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz"
integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==
-jest-resolve-dependencies@30.4.2:
- version "30.4.2"
- resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz"
- integrity sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==
+jest-regex-util@30.5.0:
+ version "30.5.0"
+ resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz"
+ integrity sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==
+
+jest-resolve-dependencies@30.5.2:
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.5.2.tgz"
+ integrity sha512-GMI0DP5enX9Z2qW2zFy1bOkrCfWAOPRJQ7qHt0pdfrxBPsgWxglMPrRSK2TX/wRWYxcPRSLFg3Z88qPOzHx7FQ==
dependencies:
- jest-regex-util "30.4.0"
- jest-snapshot "30.4.1"
+ jest-regex-util "30.5.0"
+ jest-snapshot "30.5.2"
-jest-resolve@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz"
- integrity sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==
+jest-resolve@30.5.1:
+ version "30.5.1"
+ resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.5.1.tgz"
+ integrity sha512-wprhLejRtwN6h8ZgaqC0eYjGJ1uMdGPT/+b3eFncbG4NWuuP9QL+vWwRinu9waw7hkOLcpMJGVJAMgBwsPssMQ==
dependencies:
chalk "^4.1.2"
graceful-fs "^4.2.11"
- jest-haste-map "30.4.1"
- jest-pnp-resolver "^1.2.3"
- jest-util "30.4.1"
- jest-validate "30.4.1"
+ jest-haste-map "30.5.1"
+ jest-util "30.5.1"
+ jest-validate "30.5.1"
slash "^3.0.0"
- unrs-resolver "^1.7.11"
-
-jest-runner@30.4.2:
- version "30.4.2"
- resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz"
- integrity sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==
- dependencies:
- "@jest/console" "30.4.1"
- "@jest/environment" "30.4.1"
- "@jest/test-result" "30.4.1"
- "@jest/transform" "30.4.1"
- "@jest/types" "30.4.1"
+ unrs-resolver "^1.12.1"
+
+jest-runner@30.5.2:
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-30.5.2.tgz"
+ integrity sha512-/B6px7hiiNhVSRwuv9AGn0nawYvwsHeQZ3ItTd5Gp2diFd0EOKaWVeWqcbw3L88vx1GfpCKjQkxWItZCTl11Rw==
+ dependencies:
+ "@jest/console" "30.5.2"
+ "@jest/environment" "30.5.2"
+ "@jest/source-map" "30.5.2"
+ "@jest/test-result" "30.5.2"
+ "@jest/transform" "30.5.2"
+ "@jest/types" "30.5.1"
"@types/node" "*"
chalk "^4.1.2"
emittery "^0.13.1"
exit-x "^0.2.2"
graceful-fs "^4.2.11"
- jest-docblock "30.4.0"
- jest-environment-node "30.4.1"
- jest-haste-map "30.4.1"
- jest-leak-detector "30.4.1"
- jest-message-util "30.4.1"
- jest-resolve "30.4.1"
- jest-runtime "30.4.2"
- jest-util "30.4.1"
- jest-watcher "30.4.1"
- jest-worker "30.4.1"
+ jest-docblock "30.5.0"
+ jest-environment-node "30.5.2"
+ jest-haste-map "30.5.1"
+ jest-leak-detector "30.5.1"
+ jest-message-util "30.5.1"
+ jest-resolve "30.5.1"
+ jest-runtime "30.5.2"
+ jest-util "30.5.1"
+ jest-watcher "30.5.2"
+ jest-worker "30.5.1"
p-limit "^3.1.0"
- source-map-support "0.5.13"
-
-jest-runtime@30.4.2:
- version "30.4.2"
- resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz"
- integrity sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==
- dependencies:
- "@jest/environment" "30.4.1"
- "@jest/fake-timers" "30.4.1"
- "@jest/globals" "30.4.1"
- "@jest/source-map" "30.0.1"
- "@jest/test-result" "30.4.1"
- "@jest/transform" "30.4.1"
- "@jest/types" "30.4.1"
+
+jest-runtime@30.5.2:
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.5.2.tgz"
+ integrity sha512-0paUAHBlheai10A3XSy9Xh+YBG0IROy+/qzcDPzQ/nbbXrznQzeWy5qniICWK6d3W3scaRQCVAbXdcK0ibBq4w==
+ dependencies:
+ "@jest/environment" "30.5.2"
+ "@jest/fake-timers" "30.5.2"
+ "@jest/globals" "30.5.2"
+ "@jest/source-map" "30.5.2"
+ "@jest/test-result" "30.5.2"
+ "@jest/transform" "30.5.2"
+ "@jest/types" "30.5.1"
"@types/node" "*"
chalk "^4.1.2"
- cjs-module-lexer "^2.1.0"
+ cjs-module-lexer "^2.2.0"
collect-v8-coverage "^1.0.2"
- glob "^10.5.0"
+ es-module-lexer "^2.1.0"
+ glob "^13.0.6"
graceful-fs "^4.2.11"
- jest-haste-map "30.4.1"
- jest-message-util "30.4.1"
- jest-mock "30.4.1"
- jest-regex-util "30.4.0"
- jest-resolve "30.4.1"
- jest-snapshot "30.4.1"
- jest-util "30.4.1"
+ jest-haste-map "30.5.1"
+ jest-message-util "30.5.1"
+ jest-mock "30.5.2"
+ jest-regex-util "30.5.0"
+ jest-resolve "30.5.1"
+ jest-snapshot "30.5.2"
+ jest-util "30.5.1"
slash "^3.0.0"
strip-bom "^4.0.0"
-jest-snapshot@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz"
- integrity sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==
+jest-snapshot@30.5.2:
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.5.2.tgz"
+ integrity sha512-jKIdes25AnAH73dOCi2qE6J6vHJ6L2vyBS11M89kT50DVJcLTf2AOjvcc+2vVaN8lUbY5v2R83aqXo3Ro9fqYA==
dependencies:
"@babel/core" "^7.27.4"
"@babel/generator" "^7.27.5"
"@babel/plugin-syntax-jsx" "^7.27.1"
"@babel/plugin-syntax-typescript" "^7.27.1"
"@babel/types" "^7.27.3"
- "@jest/expect-utils" "30.4.1"
- "@jest/get-type" "30.1.0"
- "@jest/snapshot-utils" "30.4.1"
- "@jest/transform" "30.4.1"
- "@jest/types" "30.4.1"
+ "@jest/expect-utils" "30.5.2"
+ "@jest/get-type" "30.5.0"
+ "@jest/snapshot-utils" "30.5.1"
+ "@jest/transform" "30.5.2"
+ "@jest/types" "30.5.1"
babel-preset-current-node-syntax "^1.2.0"
chalk "^4.1.2"
- expect "30.4.1"
+ expect "30.5.2"
graceful-fs "^4.2.11"
- jest-diff "30.4.1"
- jest-matcher-utils "30.4.1"
- jest-message-util "30.4.1"
- jest-util "30.4.1"
- pretty-format "30.4.1"
+ jest-diff "30.5.2"
+ jest-matcher-utils "30.5.2"
+ jest-message-util "30.5.1"
+ jest-util "30.5.1"
+ pretty-format "30.5.1"
semver "^7.7.2"
synckit "^0.11.8"
-jest-snapshot@^29.7.0:
+jest-util@^29.7.0:
version "29.7.0"
- resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz"
- integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==
+ resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz"
+ integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==
dependencies:
- "@babel/core" "^7.11.6"
- "@babel/generator" "^7.7.2"
- "@babel/plugin-syntax-jsx" "^7.7.2"
- "@babel/plugin-syntax-typescript" "^7.7.2"
- "@babel/types" "^7.3.3"
- "@jest/expect-utils" "^29.7.0"
- "@jest/transform" "^29.7.0"
"@jest/types" "^29.6.3"
- babel-preset-current-node-syntax "^1.0.0"
+ "@types/node" "*"
chalk "^4.0.0"
- expect "^29.7.0"
+ ci-info "^3.2.0"
graceful-fs "^4.2.9"
- jest-diff "^29.7.0"
- jest-get-type "^29.6.3"
- jest-matcher-utils "^29.7.0"
- jest-message-util "^29.7.0"
- jest-util "^29.7.0"
- natural-compare "^1.4.0"
- pretty-format "^29.7.0"
- semver "^7.5.3"
-
-jest-util@30.0.5:
- version "30.0.5"
- resolved "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz"
- integrity sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==
- dependencies:
- "@jest/types" "30.0.5"
- "@types/node" "*"
- chalk "^4.1.2"
- ci-info "^4.2.0"
- graceful-fs "^4.2.11"
- picomatch "^4.0.2"
+ picomatch "^2.2.3"
-jest-util@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz"
- integrity sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==
+jest-util@30.5.1:
+ version "30.5.1"
+ resolved "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz"
+ integrity sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==
dependencies:
- "@jest/types" "30.4.1"
+ "@jest/types" "30.5.1"
"@types/node" "*"
chalk "^4.1.2"
ci-info "^4.2.0"
graceful-fs "^4.2.11"
picomatch "^4.0.3"
-jest-util@^29.7.0:
- version "29.7.0"
- resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz"
- integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==
- dependencies:
- "@jest/types" "^29.6.3"
- "@types/node" "*"
- chalk "^4.0.0"
- ci-info "^3.2.0"
- graceful-fs "^4.2.9"
- picomatch "^2.2.3"
-
-jest-validate@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz"
- integrity sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==
+jest-validate@30.5.1:
+ version "30.5.1"
+ resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-30.5.1.tgz"
+ integrity sha512-i/buJ56wTpxihE93hQYNMfdOThS87+HGvLZzZJmU4xggPcdTYQq051iwALLCHp3q+SkCGH+EFejQZz5EbBSkkg==
dependencies:
- "@jest/get-type" "30.1.0"
- "@jest/types" "30.4.1"
+ "@jest/get-type" "30.5.0"
+ "@jest/types" "30.5.1"
camelcase "^6.3.0"
chalk "^4.1.2"
leven "^3.1.0"
- pretty-format "30.4.1"
+ pretty-format "30.5.1"
-jest-watcher@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz"
- integrity sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==
+jest-watcher@30.5.2:
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.5.2.tgz"
+ integrity sha512-rqRgg4R11GA9m1UsEZwQeixHsgZReCn6TOHAHtIH33yXrIvoX0OGPA20Yzzjkz7X40fZvcRSxEpQppYPcWzmcQ==
dependencies:
- "@jest/test-result" "30.4.1"
- "@jest/types" "30.4.1"
+ "@jest/test-result" "30.5.2"
+ "@jest/types" "30.5.1"
"@types/node" "*"
ansi-escapes "^4.3.2"
chalk "^4.1.2"
emittery "^0.13.1"
- jest-util "30.4.1"
+ jest-util "30.5.1"
string-length "^4.0.2"
-jest-worker@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz"
- integrity sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==
- dependencies:
- "@types/node" "*"
- "@ungap/structured-clone" "^1.3.0"
- jest-util "30.4.1"
- merge-stream "^2.0.0"
- supports-color "^8.1.1"
-
jest-worker@^27.4.5:
version "27.5.1"
resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz"
@@ -9686,15 +9156,26 @@ jest-worker@^29.7.0:
merge-stream "^2.0.0"
supports-color "^8.0.0"
+jest-worker@30.5.1:
+ version "30.5.1"
+ resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-30.5.1.tgz"
+ integrity sha512-Cbxh5v7AoLuFRmFJSM4/aHdQ68rjXvUWr716EE0Dh3I7T+T/3FgFKhOERGXHcU2Meftq9+9zxPM3TSNyI9D+HA==
+ dependencies:
+ "@types/node" "*"
+ "@ungap/structured-clone" "^1.3.0"
+ jest-util "30.5.1"
+ merge-stream "^2.0.0"
+ supports-color "^8.1.1"
+
jest@^30.2.0:
- version "30.4.2"
- resolved "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz"
- integrity sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==
+ version "30.5.2"
+ resolved "https://registry.npmjs.org/jest/-/jest-30.5.2.tgz"
+ integrity sha512-3gnpdBIza8ijCl3iMV9ChE3hSt1+46865qqod863gQtJr1DixOkAU5DoGbi3u+XlRcw++BhZQMu/y4xBdHBvSQ==
dependencies:
- "@jest/core" "30.4.2"
- "@jest/types" "30.4.1"
+ "@jest/core" "30.5.2"
+ "@jest/types" "30.5.1"
import-local "^3.2.0"
- jest-cli "30.4.2"
+ jest-cli "30.5.2"
joi-browser@^13.4.0:
version "13.4.0"
@@ -9702,9 +9183,9 @@ joi-browser@^13.4.0:
integrity sha512-TfzJd2JaJ/lg/gU+q5j9rLAjnfUNF9DUmXTP9w+GfmG79LjFOXFeM7hIFuXCBcZCivUDFwd9l1btTV9rhHumtQ==
joi@^18.2.5:
- version "18.2.5"
- resolved "https://registry.yarnpkg.com/joi/-/joi-18.2.5.tgz#aae38f2a7e056d88aaf5d62c6f414f5fd6516be0"
- integrity sha512-+gEA7rLfaNWx9JzawWPrPetSZwT16NUqHtECDgjyAJreXcs4TM7tx2Pa+VVJJK0YHM83ybrVdaT6UekHH50FJQ==
+ version "18.3.0"
+ resolved "https://registry.npmjs.org/joi/-/joi-18.3.0.tgz"
+ integrity sha512-/RwAK4BAfzcdbdFg073tzq4/xhY6Q7lMd5AmjqTL6pu0QFJdEmtswgm8BvgwXvOKuPNYQ8XrdQrrW/gF65LiNw==
dependencies:
"@hapi/address" "^5.1.1"
"@hapi/formula" "^3.0.2"
@@ -9719,7 +9200,7 @@ jpeg-exif@^1.1.4:
resolved "https://registry.npmjs.org/jpeg-exif/-/jpeg-exif-1.1.4.tgz"
integrity sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==
-jquery@^3.7.1:
+jquery@^3.7.1, "jquery@1.9.1 - 3":
version "3.7.1"
resolved "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz"
integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==
@@ -9740,21 +9221,21 @@ js-tokens@^9.0.1:
integrity sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==
js-yaml@^3.13.1:
- version "3.14.1"
- resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz"
- integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
+ version "3.15.2"
+ resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz"
+ integrity sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
js-yaml@^4.1.0:
- version "4.1.0"
- resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz"
- integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+ version "4.3.2"
+ resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz"
+ integrity sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==
dependencies:
argparse "^2.0.1"
-jsdom@^26.1.0:
+jsdom@*, jsdom@^26.1.0:
version "26.1.0"
resolved "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz"
integrity sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==
@@ -9853,6 +9334,20 @@ jspdf-autotable@^5.0.7:
resolved "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-5.0.8.tgz"
integrity sha512-Hy05N86yBO7CXBrnSLOge7i1ZYpKH2DjQ94iybaP7vBhSInjvRBgDc99ngKzSbSO8Jc98ZCally8I6n0tj2RJQ==
+"jspdf@^2 || ^3 || ^4", jspdf@^4.2.1:
+ version "4.2.1"
+ resolved "https://registry.npmjs.org/jspdf/-/jspdf-4.2.1.tgz"
+ integrity sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==
+ dependencies:
+ "@babel/runtime" "^7.28.6"
+ fast-png "^6.2.0"
+ fflate "^0.8.1"
+ optionalDependencies:
+ canvg "^3.0.11"
+ core-js "^3.6.0"
+ dompurify "^3.3.1"
+ html2canvas "^1.0.0-rc.5"
+
jspdf@^2.5.1:
version "2.5.2"
resolved "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz"
@@ -9868,20 +9363,6 @@ jspdf@^2.5.1:
dompurify "^2.5.4"
html2canvas "^1.0.0-rc.5"
-jspdf@^4.2.1:
- version "4.2.1"
- resolved "https://registry.npmjs.org/jspdf/-/jspdf-4.2.1.tgz"
- integrity sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==
- dependencies:
- "@babel/runtime" "^7.28.6"
- fast-png "^6.2.0"
- fflate "^0.8.1"
- optionalDependencies:
- canvg "^3.0.11"
- core-js "^3.6.0"
- dompurify "^3.3.1"
- html2canvas "^1.0.0-rc.5"
-
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5:
version "3.3.5"
resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz"
@@ -9958,7 +9439,7 @@ leaflet.markercluster@^1.5.3:
resolved "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz"
integrity sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==
-leaflet@^1.8.0, leaflet@^1.9.4:
+leaflet@^1.3.1, leaflet@^1.8.0, leaflet@^1.9.0, leaflet@^1.9.4:
version "1.9.4"
resolved "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz"
integrity sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==
@@ -10066,9 +9547,9 @@ lodash.truncate@^4.4.2:
integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==
lodash@^4.17.15, lodash@^4.17.21:
- version "4.17.21"
- resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
- integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+ version "4.18.1"
+ resolved "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz"
+ integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==
log-update@^6.1.0:
version "6.1.0"
@@ -10093,11 +9574,21 @@ loupe@^3.1.0, loupe@^3.1.4:
resolved "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz"
integrity sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==
-lru-cache@^10.2.0, lru-cache@^10.4.3:
+lru-cache@^10.2.0:
+ version "10.4.3"
+ resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz"
+ integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
+
+lru-cache@^10.4.3:
version "10.4.3"
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz"
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
+lru-cache@^11.0.0:
+ version "11.5.3"
+ resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz"
+ integrity sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==
+
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz"
@@ -10159,6 +9650,34 @@ map-limit@0.0.1:
dependencies:
once "~1.3.0"
+"mapbox-gl@>=0.32.1 <2.0.0":
+ version "1.13.3"
+ resolved "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.13.3.tgz"
+ integrity sha512-p8lJFEiqmEQlyv+DQxFAOG/XPWN0Wp7j/Psq93Zywz7qt9CcUKFYDBOoOEKzqe6gudHVJY8/Bhqw6VDpX2lSBg==
+ dependencies:
+ "@mapbox/geojson-rewind" "^0.5.2"
+ "@mapbox/geojson-types" "^1.0.2"
+ "@mapbox/jsonlint-lines-primitives" "^2.0.2"
+ "@mapbox/mapbox-gl-supported" "^1.5.0"
+ "@mapbox/point-geometry" "^0.1.0"
+ "@mapbox/tiny-sdf" "^1.1.1"
+ "@mapbox/unitbezier" "^0.0.0"
+ "@mapbox/vector-tile" "^1.3.1"
+ "@mapbox/whoots-js" "^3.1.0"
+ csscolorparser "~1.0.3"
+ earcut "^2.2.2"
+ geojson-vt "^3.2.1"
+ gl-matrix "^3.2.1"
+ grid-index "^1.1.0"
+ murmurhash-js "^1.0.0"
+ pbf "^3.2.1"
+ potpack "^1.0.1"
+ quickselect "^2.0.0"
+ rw "^1.3.3"
+ supercluster "^7.1.0"
+ tinyqueue "^2.0.3"
+ vt-pbf "^3.1.1"
+
maplibre-gl@^4.7.1:
version "4.7.1"
resolved "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.7.1.tgz"
@@ -10228,16 +9747,16 @@ mem@^8.0.0:
map-age-cleaner "^0.1.3"
mimic-fn "^3.1.0"
-"memoize-one@>=3.1.1 <6":
- version "5.2.1"
- resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz"
- integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
-
memoize-one@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz"
integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
+"memoize-one@>=3.1.1 <6":
+ version "5.2.1"
+ resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz"
+ integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
+
meow@^13.2.0:
version "13.2.0"
resolved "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz"
@@ -10253,7 +9772,7 @@ merge2@^1.3.0, merge2@^1.4.1:
resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
-micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8:
+micromatch@^4.0.4, micromatch@^4.0.8:
version "4.0.8"
resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
@@ -10269,16 +9788,16 @@ miller-rabin@^4.0.0:
bn.js "^4.0.0"
brorand "^1.0.1"
-mime-db@1.52.0:
- version "1.52.0"
- resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
- integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
-
mime-db@^1.54.0:
version "1.54.0"
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz"
integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
mime-types@^2.1.35:
version "2.1.35"
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
@@ -10317,16 +9836,16 @@ minimalistic-crypto-utils@^1.0.1:
integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==
minimatch@^10.2.2:
- version "10.2.5"
- resolved "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz"
- integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==
+ version "10.2.6"
+ resolved "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz"
+ integrity sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==
dependencies:
- brace-expansion "^5.0.5"
+ brace-expansion "^5.0.8"
minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
- version "3.1.2"
- resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
- integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ version "3.1.5"
+ resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz"
+ integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==
dependencies:
brace-expansion "^1.1.7"
@@ -10342,10 +9861,10 @@ minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8:
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
-"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2:
- version "7.1.2"
- resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz"
- integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
+"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2, minipass@^7.1.3:
+ version "7.1.3"
+ resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz"
+ integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==
ml-array-max@^2.0.0:
version "2.0.0"
@@ -10385,9 +9904,9 @@ moment-timezone@^0.5.33:
dependencies:
moment "^2.29.4"
-moment@^2.29.4, moment@^2.31.0:
+moment@^2.29.4, moment@^2.31.0, "moment@>= 2.x":
version "2.31.0"
- resolved "https://registry.yarnpkg.com/moment/-/moment-2.31.0.tgz#6e19184e8005a9dfc61c9351263d4d6ea2ace7aa"
+ resolved "https://registry.npmjs.org/moment/-/moment-2.31.0.tgz"
integrity sha512-0acOTfMiWOheYS4eoWb80yYMb/JLvVv9SHbs2PehaDzfUG0Bw855SKyk0IKTnPGa5+U2bmi3W68l1+sGLX/pvw==
mouse-event-offset@^3.0.2:
@@ -10400,17 +9919,12 @@ mrmime@^2.0.0:
resolved "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz"
integrity sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==
-ms@2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
- integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
-
ms@^2.1.1, ms@^2.1.3:
version "2.1.3"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-msw@^2.10.4:
+msw@^2.10.4, msw@^2.4.9:
version "2.11.0"
resolved "https://registry.npmjs.org/msw/-/msw-2.11.0.tgz"
integrity sha512-jEqa5J5B1OMD1jHu0gasCb5YriIDiWGdoS22Ie8CNNrl1iGRzCVfok3zuerdfb7GNFeIbYePXDA5c2bwRKMpBA==
@@ -10449,15 +9963,15 @@ nano-spawn@^2.0.0:
resolved "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz"
integrity sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==
-nanoid@^3.3.11:
- version "3.3.11"
- resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz"
- integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
+nanoid@^3.3.20:
+ version "3.3.20"
+ resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz"
+ integrity sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==
napi-postinstall@^0.3.4:
- version "0.3.4"
- resolved "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz"
- integrity sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==
+ version "0.3.7"
+ resolved "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.7.tgz"
+ integrity sha512-jyU4cU4kVAmy/3qjP+DcoQTNXfINHc+dFw54gGqWC8nu0mpV0kOnk7kff+J+Q18SbX0k3Np8Rm7yFjd3phatNQ==
native-promise-only@^0.8.1:
version "0.8.1"
@@ -10503,10 +10017,10 @@ node-int64@^0.4.0:
resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz"
integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
-node-releases@^2.0.55:
- version "2.0.56"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.56.tgz#fff6a4093fe05d994a2a5e415eee5fcef874e6dc"
- integrity sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==
+node-releases@^2.0.60:
+ version "2.0.61"
+ resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.61.tgz"
+ integrity sha512-5NUN6QCxNFrS9oEA9RPS+d0AP+ooGBzzovY2JhIpDRHacPvOhHjXjUsUMX9qp4nLHZ+5jVgXB2jj/P4arGOE/g==
normalize-path@^3.0.0:
version "3.0.0"
@@ -10754,16 +10268,15 @@ parenthesis@^3.1.5:
resolved "https://registry.npmjs.org/parenthesis/-/parenthesis-3.1.8.tgz"
integrity sha512-KF/U8tk54BgQewkJPvB4s/US3VQY68BRDpH638+7O/n58TpnwiwnOtGIOsT2/i+M78s61BBpeC83STB88d8sqw==
-parse-asn1@^5.0.0, parse-asn1@^5.1.7:
- version "5.1.7"
- resolved "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz"
- integrity sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==
+parse-asn1@^5.0.0, parse-asn1@^5.1.9:
+ version "5.1.10"
+ resolved "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.10.tgz"
+ integrity sha512-gBlTKLbYnAJN9WjmawVOMfaGvVoy4lX0LH18t7ccwtEG9RgAa4ETf2eu3d2J4e/5SoCjCms0A0UF4tTw/Eyn6Q==
dependencies:
asn1.js "^4.10.1"
browserify-aes "^1.2.0"
evp_bytestokey "^1.0.3"
- hash-base "~3.0"
- pbkdf2 "^3.1.2"
+ pbkdf2 "^3.1.7"
safe-buffer "^5.2.1"
parse-json@^5.0.0, parse-json@^5.2.0:
@@ -10853,6 +10366,14 @@ path-scurry@^1.11.1:
lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+path-scurry@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz"
+ integrity sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==
+ dependencies:
+ lru-cache "^11.0.0"
+ minipass "^7.1.2"
+
path-to-regexp@^1.7.0:
version "1.9.0"
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz"
@@ -10888,17 +10409,17 @@ pbf@^3.2.1, pbf@^3.3.0:
ieee754 "^1.1.12"
resolve-protobuf-schema "^2.1.0"
-pbkdf2@^3.1.2:
- version "3.1.3"
- resolved "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.3.tgz"
- integrity sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==
+pbkdf2@^3.1.2, pbkdf2@^3.1.7:
+ version "3.1.7"
+ resolved "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.7.tgz"
+ integrity sha512-nS4mvFgVwUrecPTRdpdseM2fwpdfnX0/HibA8DgBRQtihUAzHQYJFU2l+OlEtiXtpqkrMvCPDwyXMUnASDt6Qw==
dependencies:
- create-hash "~1.1.3"
+ create-hash "^1.2.0"
create-hmac "^1.1.7"
- ripemd160 "=2.0.1"
+ ripemd160 "^2.0.3"
safe-buffer "^5.2.1"
- sha.js "^2.4.11"
- to-buffer "^1.2.0"
+ sha.js "^2.4.12"
+ to-buffer "^1.2.2"
pdfast@^0.2.0:
version "0.2.0"
@@ -10925,25 +10446,25 @@ pick-by-alias@^1.2.0:
resolved "https://registry.npmjs.org/pick-by-alias/-/pick-by-alias-1.2.0.tgz"
integrity sha512-ESj2+eBxhGrcA1azgHs7lARG5+5iLakc/6nlfbpjcLl00HuuUOIuORhYXN4D1HfvMSKuVtFQjAlnwi1JHEeDIw==
-picocolors@1.1.1, picocolors@^1.0.0, picocolors@^1.1.1:
+picocolors@^1.0.0, picocolors@^1.1.1, picocolors@1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1:
- version "2.3.1"
- resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
- integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+ version "2.3.2"
+ resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz"
+ integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==
-picomatch@^4.0.2:
- version "4.0.3"
- resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz"
- integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
+"picomatch@^3 || ^4", picomatch@^4.0.2, picomatch@^4.0.3, picomatch@^4.0.4:
+ version "4.0.7"
+ resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz"
+ integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==
-picomatch@^4.0.3, picomatch@^4.0.4:
- version "4.0.4"
- resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz"
- integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
+picomatch@^4.0.2:
+ version "4.0.7"
+ resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz"
+ integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==
pidtree@^0.6.0:
version "0.6.0"
@@ -10962,10 +10483,10 @@ pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"
-plotly.js@^3.5.0:
- version "3.5.1"
- resolved "https://registry.npmjs.org/plotly.js/-/plotly.js-3.5.1.tgz"
- integrity sha512-rUzQ7Q46whi1aWT2JlvKE2dnryNORPrxyy66OGSMXgejK3XgD4ysD9SapMOI1RzzUJyX0KbfVDFcB9/DqOyH9Q==
+plotly.js@^3.5.0, plotly.js@>1.34.0:
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/plotly.js/-/plotly.js-3.7.0.tgz"
+ integrity sha512-r17/w3Ob/E+A1vqN382PV/6Dz+IAxAsxNCpWcnGfGFLqE0M6vPUa+ve7HPhivrl9LMicZg2Fy0U5GbpauHo32A==
dependencies:
"@plotly/d3" "3.8.2"
"@plotly/d3-sankey" "0.7.2"
@@ -11051,10 +10572,10 @@ postcss-safe-parser@^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==
-postcss-selector-parser@^7.1.0, postcss-selector-parser@^7.1.1:
- version "7.1.5"
- resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz"
- integrity sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==
+postcss-selector-parser@^7.0.0, postcss-selector-parser@^7.1.0, postcss-selector-parser@^7.1.1:
+ version "7.1.7"
+ resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.7.tgz"
+ integrity sha512-wWBro/7hJ40ye6TPiXkUQoXKCw23xSsjriUvqd7Uls9Sso3oLiON1flc18cCdnjgCRXw4fBVqUJJeOdDTh3P3g==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
@@ -11064,14 +10585,14 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.2.0:
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
-postcss@^8.5.3, postcss@^8.5.6:
- version "8.5.6"
- resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz"
- integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
+postcss@^8.4.31, postcss@^8.5.3, postcss@^8.5.6:
+ version "8.5.32"
+ resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz"
+ integrity sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==
dependencies:
- nanoid "^3.3.11"
+ nanoid "^3.3.20"
picocolors "^1.1.1"
- source-map-js "^1.2.1"
+ source-map-js "^1.2.2"
potpack@^1.0.1:
version "1.0.2"
@@ -11095,30 +10616,11 @@ prettier-linter-helpers@^1.0.0:
dependencies:
fast-diff "^1.1.2"
-prettier@^1.19.1:
+prettier@^1.19.1, prettier@>=1.13.0:
version "1.19.1"
resolved "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz"
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
-pretty-format@30.0.5, pretty-format@^30.0.0:
- version "30.0.5"
- resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz"
- integrity sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==
- dependencies:
- "@jest/schemas" "30.0.5"
- ansi-styles "^5.2.0"
- react-is "^18.3.1"
-
-pretty-format@30.4.1:
- version "30.4.1"
- resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz"
- integrity sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==
- dependencies:
- "@jest/schemas" "30.4.1"
- ansi-styles "^5.2.0"
- react-is-18 "npm:react-is@^18.3.1"
- react-is-19 "npm:react-is@^19.2.5"
-
pretty-format@^26.6.2:
version "26.6.2"
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz"
@@ -11147,14 +10649,33 @@ pretty-format@^29.7.0:
ansi-styles "^5.0.0"
react-is "^18.0.0"
+pretty-format@^30.0.0:
+ version "30.5.1"
+ resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz"
+ integrity sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==
+ dependencies:
+ "@jest/react-is-18" "npm:react-is@^18.3.1"
+ "@jest/react-is-19" "npm:react-is@^19.2.5"
+ "@jest/schemas" "30.5.0"
+ ansi-styles "^5.2.0"
+
+pretty-format@30.5.1:
+ version "30.5.1"
+ resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz"
+ integrity sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==
+ dependencies:
+ "@jest/react-is-18" "npm:react-is@^18.3.1"
+ "@jest/react-is-19" "npm:react-is@^19.2.5"
+ "@jest/schemas" "30.5.0"
+ ansi-styles "^5.2.0"
+
probe-image-size@^7.2.3:
- version "7.3.0"
- resolved "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.3.0.tgz"
- integrity sha512-7CaDeBwiAbh6ohXsvLbAZhO7wzsZAmaevfxe39qvCwRh8LyaZfDlBGGLU1CCTgrTLtCOdwBBhjOrIHaIIimHfQ==
+ version "7.5.0"
+ resolved "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.5.0.tgz"
+ integrity sha512-ietdx4XPgmnzHRZOq1/KtcnsBkL4bRsBe+Szj5Tuq1/KPqDG42OspG1IhJb7vz6Z9pSzhp0J6UPnw6hujJTWoQ==
dependencies:
lodash.merge "^4.6.2"
needle "^2.5.2"
- stream-parser "~0.3.1"
process-nextick-args@~2.0.0:
version "2.0.1"
@@ -11169,7 +10690,7 @@ prop-types-extra@^1.1.0:
react-is "^16.3.2"
warning "^4.0.0"
-prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
+prop-types@^15.0.0, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -11708,9 +11229,9 @@ react-day-picker@^8.10.1:
resolved "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz"
integrity sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==
-react-dom@18.3.1, react-dom@^18.3.1:
+react-dom@*, "react-dom@^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^15.5.x || ^16.x || ^17.x || ^18.x", "react-dom@^16 || ^17 || ^18", "react-dom@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom@^16.8.0 || ^17 || ^18", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.8.0 || 17.x || 18.x", "react-dom@^16.9.0 || ^17 || ^18", "react-dom@^17.0.0 || ^18.0.0 || ^19.0.0", react-dom@^18.0.0, "react-dom@^18.0.0 || ^19.0.0", "react-dom@^18.0.0 || ^19.1.0", "react-dom@^18.2.0 || ^19.0.0", react-dom@^18.3.1, "react-dom@^19.0.0 || ^18.0.0 || ^17.0.1 || ^16.7.0", "react-dom@>= 16.8.0", react-dom@>=15, react-dom@>=15.0.0, react-dom@>=16, react-dom@>=16.0.0, react-dom@>=16.11.0, react-dom@>=16.3.0, react-dom@>=16.6.0, react-dom@>=16.8, react-dom@>=16.8.0, react-dom@>=16.8.4, react-dom@>=16.9.0, react-dom@>=18.0.0, "react-dom@~15 || ~16 || ~17 || ~18":
version "18.3.1"
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
+ resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
dependencies:
loose-envify "^1.1.0"
@@ -11726,27 +11247,47 @@ react-icons@^4.12.0:
resolved "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz"
integrity sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==
-"react-is-18@npm:react-is@^18.3.1":
- version "18.3.1"
- resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz"
- integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
+react-is@^16.13.1:
+ version "16.13.1"
+ resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
+ integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+
+react-is@^16.3.2:
+ version "16.13.1"
+ resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
+ integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
-"react-is-19@npm:react-is@^19.2.5":
- version "19.2.6"
- resolved "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz"
- integrity sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==
+react-is@^16.6.0:
+ version "16.13.1"
+ resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
+ integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
-react-is@^16.13.1, react-is@^16.3.2, react-is@^16.6.0, react-is@^16.7.0:
+react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
-react-is@^17.0.1, react-is@^17.0.2:
+react-is@^17.0.1:
+ version "17.0.2"
+ resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz"
+ integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
+
+react-is@^17.0.2:
version "17.0.2"
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
-react-is@^18.0.0, react-is@^18.2.0, react-is@^18.3.1:
+react-is@^18.0.0:
+ version "18.3.1"
+ resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz"
+ integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
+
+react-is@^18.2.0:
+ version "18.3.1"
+ resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz"
+ integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
+
+react-is@^18.3.1:
version "18.3.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
@@ -11757,9 +11298,9 @@ react-is@^19.1.1:
integrity sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==
react-is@^19.2.3:
- version "19.3.0"
- resolved "https://registry.npmjs.org/react-is/-/react-is-19.3.0.tgz"
- integrity sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==
+ version "19.3.1"
+ resolved "https://registry.npmjs.org/react-is/-/react-is-19.3.1.tgz"
+ integrity sha512-RRieF6IyxQBkcK6pP8xOHprLTPW6QjRkbGdImtqNme+/hYVwCZkLQLdlZGOhUqXwkS9IEynaGvPJtv6XB/cz0Q==
react-leaflet-cluster@^3.1.0:
version "3.1.1"
@@ -11768,7 +11309,7 @@ react-leaflet-cluster@^3.1.0:
dependencies:
leaflet.markercluster "^1.5.3"
-react-leaflet@4.2.1, react-leaflet@^4.0.0:
+react-leaflet@^4.0.0, react-leaflet@4.2.1:
version "4.2.1"
resolved "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz"
integrity sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==
@@ -11849,7 +11390,7 @@ react-property@2.0.0:
resolved "https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz"
integrity sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw==
-react-redux@^7.2.0:
+react-redux@^7.2.0, "react-redux@^7.2.1 || ^8.1.3 || ^9.0.0":
version "7.2.9"
resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz"
integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==
@@ -11874,7 +11415,7 @@ react-refresh@^0.17.0:
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz"
integrity sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==
-react-router-dom@^5.2.0:
+react-router-dom@^5.2.0, react-router-dom@>=4:
version "5.3.4"
resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz"
integrity sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==
@@ -11894,7 +11435,7 @@ react-router-hash-link@^2.3.1:
dependencies:
prop-types "^15.7.2"
-react-router@5.3.4, react-router@^5.3.4:
+react-router@^5.3.4, react-router@5.3.4:
version "5.3.4"
resolved "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz"
integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==
@@ -12042,9 +11583,9 @@ react-window@^1.8.11:
"@babel/runtime" "^7.0.0"
memoize-one ">=3.1.1 <6"
-react@18.3.1, react@^18.3.1:
+react@*, "react@^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^15.5.x || ^16.x || ^17.x || ^18.x", "react@^16 || ^17 || ^18", "react@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0", "react@^16.3 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ~19", "react@^16.8.0 || 17.x || 18.x", "react@^16.8.3 || ^17 || ^18", "react@^16.8.3 || ^17.0.0-0 || ^18.0.0", "react@^16.9.0 || ^17 || ^18", "react@^16.9.0 || ^17.0.0 || ^18 || ^19", "react@^17.0.0 || ^18.0.0 || ^19.0.0", "react@^18.0 || ^19", react@^18.0.0, "react@^18.0.0 || ^19.0.0", "react@^18.0.0 || ^19.1.0", "react@^18.2.0 || ^19.0.0", react@^18.3.1, "react@^19.0.0 || ^18.0.0 || ^17.0.1 || ^16.7.0", "react@>= 16.8.0", react@>=0.14.0, react@>=15, react@>=15.0.0, react@>=16, react@>=16.0.0, react@>=16.11.0, react@>=16.3.0, react@>=16.3.2, react@>=16.6.0, react@>=16.8, react@>=16.8.0, react@>=16.8.4, react@>=16.9.0, react@>=18.0.0, react@>0.13.0, "react@~15 || ~16 || ~17 || ~18", "react@0.14 || 15 || 16 || 17 || 18", "react@0.14.x || ^15.0.0 || ^16.0.0 || ^17.0.0":
version "18.3.1"
- resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
+ resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
dependencies:
loose-envify "^1.1.0"
@@ -12074,16 +11615,6 @@ read-excel-file@^5.5.3:
fflate "^0.7.3"
unzipper "^0.12.2"
-"readable-stream@>=1.0.33-1 <1.1.0-0":
- version "1.0.34"
- resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz"
- integrity sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==
- dependencies:
- core-util-is "~1.0.0"
- inherits "~2.0.1"
- isarray "0.0.1"
- string_decoder "~0.10.x"
-
readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@^2.3.5, readable-stream@^2.3.8, readable-stream@~2.3.6:
version "2.3.8"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz"
@@ -12106,6 +11637,16 @@ readable-stream@^3.5.0:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
+"readable-stream@>=1.0.33-1 <1.1.0-0":
+ version "1.0.34"
+ resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz"
+ integrity sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "0.0.1"
+ string_decoder "~0.10.x"
+
readdirp@^4.0.1:
version "4.1.2"
resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz"
@@ -12185,14 +11726,14 @@ redux-thunk@^3.1.0:
resolved "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz"
integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==
-redux@^4.0.0, redux@^4.0.5:
+redux@*, redux@^4, redux@^4.0.0, redux@^4.0.5, redux@>3.0.0:
version "4.2.1"
resolved "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz"
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
dependencies:
"@babel/runtime" "^7.9.2"
-redux@^5.0.1:
+redux@^5.0.0, redux@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz"
integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==
@@ -12436,21 +11977,13 @@ rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
-ripemd160@=2.0.1:
- version "2.0.1"
- resolved "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz"
- integrity sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==
- dependencies:
- hash-base "^2.0.0"
- inherits "^2.0.1"
-
-ripemd160@^2.0.0, ripemd160@^2.0.1:
- version "2.0.2"
- resolved "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz"
- integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==
+ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz"
+ integrity sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==
dependencies:
- hash-base "^3.0.0"
- inherits "^2.0.1"
+ hash-base "^3.1.2"
+ inherits "^2.0.4"
robust-predicates@^3.0.2:
version "3.0.2"
@@ -12458,32 +11991,38 @@ robust-predicates@^3.0.2:
integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==
rollup@^4.34.9:
- version "4.49.0"
- resolved "https://registry.npmjs.org/rollup/-/rollup-4.49.0.tgz"
- integrity sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==
+ version "4.66.0"
+ resolved "https://registry.npmjs.org/rollup/-/rollup-4.66.0.tgz"
+ integrity sha512-Onq6/qO4ARhxIs+jYiHE0pYehadLyMECAiGEqQ+0/aWND3+2PmifJIIqBLYtuBODqHTlOUEQFtIVPyCy0Lck7A==
dependencies:
- "@types/estree" "1.0.8"
+ "@types/estree" "1.0.9"
optionalDependencies:
- "@rollup/rollup-android-arm-eabi" "4.49.0"
- "@rollup/rollup-android-arm64" "4.49.0"
- "@rollup/rollup-darwin-arm64" "4.49.0"
- "@rollup/rollup-darwin-x64" "4.49.0"
- "@rollup/rollup-freebsd-arm64" "4.49.0"
- "@rollup/rollup-freebsd-x64" "4.49.0"
- "@rollup/rollup-linux-arm-gnueabihf" "4.49.0"
- "@rollup/rollup-linux-arm-musleabihf" "4.49.0"
- "@rollup/rollup-linux-arm64-gnu" "4.49.0"
- "@rollup/rollup-linux-arm64-musl" "4.49.0"
- "@rollup/rollup-linux-loongarch64-gnu" "4.49.0"
- "@rollup/rollup-linux-ppc64-gnu" "4.49.0"
- "@rollup/rollup-linux-riscv64-gnu" "4.49.0"
- "@rollup/rollup-linux-riscv64-musl" "4.49.0"
- "@rollup/rollup-linux-s390x-gnu" "4.49.0"
- "@rollup/rollup-linux-x64-gnu" "4.49.0"
- "@rollup/rollup-linux-x64-musl" "4.49.0"
- "@rollup/rollup-win32-arm64-msvc" "4.49.0"
- "@rollup/rollup-win32-ia32-msvc" "4.49.0"
- "@rollup/rollup-win32-x64-msvc" "4.49.0"
+ "@napi-rs/lzma-linux-x64-gnu" "1.5.1"
+ "@rollup/rollup-android-arm-eabi" "4.66.0"
+ "@rollup/rollup-android-arm64" "4.66.0"
+ "@rollup/rollup-darwin-arm64" "4.66.0"
+ "@rollup/rollup-darwin-x64" "4.66.0"
+ "@rollup/rollup-freebsd-arm64" "4.66.0"
+ "@rollup/rollup-freebsd-x64" "4.66.0"
+ "@rollup/rollup-linux-arm-gnueabihf" "4.66.0"
+ "@rollup/rollup-linux-arm-musleabihf" "4.66.0"
+ "@rollup/rollup-linux-arm64-gnu" "4.66.0"
+ "@rollup/rollup-linux-arm64-musl" "4.66.0"
+ "@rollup/rollup-linux-loong64-gnu" "4.66.0"
+ "@rollup/rollup-linux-loong64-musl" "4.66.0"
+ "@rollup/rollup-linux-ppc64-gnu" "4.66.0"
+ "@rollup/rollup-linux-ppc64-musl" "4.66.0"
+ "@rollup/rollup-linux-riscv64-gnu" "4.66.0"
+ "@rollup/rollup-linux-riscv64-musl" "4.66.0"
+ "@rollup/rollup-linux-s390x-gnu" "4.66.0"
+ "@rollup/rollup-linux-x64-gnu" "4.66.0"
+ "@rollup/rollup-linux-x64-musl" "4.66.0"
+ "@rollup/rollup-openbsd-x64" "4.66.0"
+ "@rollup/rollup-openharmony-arm64" "4.66.0"
+ "@rollup/rollup-win32-arm64-msvc" "4.66.0"
+ "@rollup/rollup-win32-ia32-msvc" "4.66.0"
+ "@rollup/rollup-win32-x64-gnu" "4.66.0"
+ "@rollup/rollup-win32-x64-msvc" "4.66.0"
fsevents "~2.3.2"
rrweb-cssom@^0.8.0:
@@ -12498,7 +12037,7 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
-rw@1, rw@^1.3.3:
+rw@^1.3.3, rw@1:
version "1.3.3"
resolved "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz"
integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==
@@ -12519,7 +12058,12 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2,
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
-safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@~5.1.0:
+ version "5.1.2"
+ resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
@@ -12553,7 +12097,7 @@ sass-loader@^16.0.5:
dependencies:
neo-async "^2.6.2"
-sass@^1.86.3:
+sass@*, sass@^1.3.0, sass@^1.86.3:
version "1.91.0"
resolved "https://registry.npmjs.org/sass/-/sass-1.91.0.tgz"
integrity sha512-aFOZHGf+ur+bp1bCHZ+u8otKGh77ZtmFyXDo4tlYvT7PWql41Kwd8wdkPqhhT+h2879IVblcHFglIMofsFd1EA==
@@ -12615,7 +12159,12 @@ semver@^5.5.0:
resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz"
integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
-semver@^6.3.0, semver@^6.3.1:
+semver@^6.3.0:
+ version "6.3.1"
+ resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
+ integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
+
+semver@^6.3.1:
version "6.3.1"
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
@@ -12656,7 +12205,7 @@ set-proto@^1.0.0:
es-errors "^1.3.0"
es-object-atoms "^1.0.0"
-sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8:
+sha.js@^2.4.0, sha.js@^2.4.12, sha.js@^2.4.8:
version "2.4.12"
resolved "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz"
integrity sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==
@@ -12749,7 +12298,12 @@ siginfo@^2.0.0:
resolved "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz"
integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==
-signal-exit@^3.0.3, signal-exit@^3.0.7:
+signal-exit@^3.0.3:
+ version "3.0.7"
+ resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz"
+ integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
+
+signal-exit@^3.0.7:
version "3.0.7"
resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
@@ -12802,18 +12356,10 @@ slice-ansi@^7.1.0:
ansi-styles "^6.2.1"
is-fullwidth-code-point "^5.0.0"
-"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.0, source-map-js@^1.2.1:
- version "1.2.1"
- resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz"
- integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
-
-source-map-support@0.5.13:
- version "0.5.13"
- resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz"
- integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==
- dependencies:
- buffer-from "^1.0.0"
- source-map "^0.6.0"
+source-map-js@^1.2.0, source-map-js@^1.2.1, source-map-js@^1.2.2, "source-map-js@>=0.6.2 <2.0.0":
+ version "1.2.2"
+ resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz"
+ integrity sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==
source-map-support@~0.5.20:
version "0.5.21"
@@ -12828,7 +12374,12 @@ source-map@^0.5.7:
resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz"
integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
-source-map@^0.6.0, source-map@~0.6.1:
+source-map@^0.6.0:
+ version "0.6.1"
+ resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
+ integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
@@ -12900,13 +12451,6 @@ stream-browserify@^3.0.0:
inherits "~2.0.4"
readable-stream "^3.5.0"
-stream-parser@~0.3.1:
- version "0.3.1"
- resolved "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz"
- integrity sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==
- dependencies:
- debug "2"
-
stream-shift@^1.0.0:
version "1.0.3"
resolved "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz"
@@ -12917,6 +12461,18 @@ strict-event-emitter@^0.5.1:
resolved "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz"
integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==
+string_decoder@^1.1.1, string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
+ integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+ dependencies:
+ safe-buffer "~5.1.0"
+
+string_decoder@~0.10.x:
+ version "0.10.31"
+ resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+ integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==
+
string-argv@^0.3.2:
version "0.3.2"
resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz"
@@ -12951,7 +12507,7 @@ string-split-by@^1.0.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
-string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+string-width@^4.1.0, string-width@^4.2.0:
version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -12969,16 +12525,7 @@ string-width@^4.2.3:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
-string-width@^5.0.1:
- version "5.1.2"
- resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz"
- integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
- dependencies:
- eastasianwidth "^0.2.0"
- emoji-regex "^9.2.2"
- strip-ansi "^7.0.1"
-
-string-width@^5.1.2:
+string-width@^5.0.1, string-width@^5.1.2:
version "5.1.2"
resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz"
integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
@@ -13072,18 +12619,6 @@ string.prototype.trimstart@^1.0.8:
define-properties "^1.2.1"
es-object-atoms "^1.0.0"
-string_decoder@^1.1.1, string_decoder@~1.1.1:
- version "1.1.1"
- resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
- integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
- dependencies:
- safe-buffer "~5.1.0"
-
-string_decoder@~0.10.x:
- version "0.10.31"
- resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
- integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==
-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
@@ -13105,12 +12640,12 @@ strip-ansi@^7.0.1:
dependencies:
ansi-regex "^6.2.2"
-strip-ansi@^7.1.0, strip-ansi@^7.1.2:
+strip-ansi@^7.1.0:
version "7.1.2"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz"
integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==
dependencies:
- ansi-regex "^6.2.2"
+ ansi-regex "^6.0.1"
strip-ansi@^7.1.2:
version "7.2.0"
@@ -13218,7 +12753,7 @@ stylelint-scss@^7.0.0:
postcss-selector-parser "^7.1.1"
postcss-value-parser "^4.2.0"
-stylelint@^16.26.1:
+"stylelint@^14.5.1 || ^15.0.0 || ^16.0.0 || ^17.0.0", stylelint@^16.23.0, stylelint@^16.26.1, "stylelint@^16.8.2 || ^17.0.0":
version "16.26.1"
resolved "https://registry.npmjs.org/stylelint/-/stylelint-16.26.1.tgz"
integrity sha512-v20V59/crfc8sVTAtge0mdafI3AdnzQ2KsWe6v523L4OA1bJO02S7MO2oyXDCS6iWb9ckIPnqAFVItqSBQr7jw==
@@ -13263,12 +12798,17 @@ stylelint@^16.26.1:
table "^6.9.0"
write-file-atomic "^5.0.1"
+stylis@^4.3.4:
+ version "4.4.0"
+ resolved "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz"
+ integrity sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==
+
stylis@4.2.0:
version "4.2.0"
resolved "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz"
integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==
-stylis@4.3.6, stylis@^4.3.4:
+stylis@4.3.6:
version "4.3.6"
resolved "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz"
integrity sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==
@@ -13301,7 +12841,14 @@ supports-color@^7.0.0, supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
-supports-color@^8.0.0, supports-color@^8.1.1:
+supports-color@^8.0.0:
+ version "8.1.1"
+ resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz"
+ integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-color@^8.1.1:
version "8.1.1"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz"
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
@@ -13410,7 +12957,7 @@ terser-webpack-plugin@^5.5.0:
schema-utils "^4.3.0"
terser "^5.31.1"
-terser@^5.31.1:
+terser@^5.16.0, terser@^5.31.1:
version "5.44.0"
resolved "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz"
integrity sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==
@@ -13509,7 +13056,7 @@ tinyglobby@^0.2.13, tinyglobby@^0.2.14, tinyglobby@^0.2.15:
fdir "^6.5.0"
picomatch "^4.0.4"
-tinymce@^7.9.3:
+tinymce@^7.9.3, "tinymce@^8.0.0 || ^7.0.0 || ^6.0.0 || ^5.5.1", tinymce@>=4.0.0:
version "7.9.3"
resolved "https://registry.npmjs.org/tinymce/-/tinymce-7.9.3.tgz"
integrity sha512-Mtm54U5YJ6Pyo/GaAx+JSHXTGEuxrg2AowVWCD9zy1eBolp5Ub7S1rTtsyQdxhPegfhLuR3VLiTKGw1tacv09g==
@@ -13535,9 +13082,9 @@ tinyrainbow@^2.0.0:
integrity sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==
tinyspy@^4.0.3:
- version "4.0.4"
- resolved "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz"
- integrity sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.6.tgz"
+ integrity sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==
tldts-core@^6.1.86:
version "6.1.86"
@@ -13556,10 +13103,10 @@ tmpl@1.0.5:
resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz"
integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
-to-buffer@^1.2.0:
- version "1.2.1"
- resolved "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz"
- integrity sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==
+to-buffer@^1.2.0, to-buffer@^1.2.1, to-buffer@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz"
+ integrity sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==
dependencies:
isarray "^2.0.5"
safe-buffer "^5.2.1"
@@ -13764,7 +13311,7 @@ typedarray@^0.0.6:
resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
-typescript@^4.8.4:
+typescript@^4.8.4, "typescript@>= 4.8.x", typescript@>=4.2.0, typescript@>=4.8.4, "typescript@>=4.8.4 <6.1.0", typescript@>=4.9.5:
version "4.9.5"
resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
@@ -13795,9 +13342,9 @@ undici-types@~7.10.0:
integrity sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==
undici@^7.12.0:
- version "7.30.0"
- resolved "https://registry.yarnpkg.com/undici/-/undici-7.30.0.tgz#64a947266eb7e21dbeac8b99f8720feef308bbe0"
- integrity sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==
+ version "7.30.1"
+ resolved "https://registry.npmjs.org/undici/-/undici-7.30.1.tgz"
+ integrity sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==
unicode-canonical-property-names-ecmascript@^2.0.0:
version "2.0.1"
@@ -13853,7 +13400,7 @@ unquote@^1.1.0:
resolved "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz"
integrity sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==
-unrs-resolver@^1.7.11:
+unrs-resolver@^1.12.1:
version "1.12.2"
resolved "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz"
integrity sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==
@@ -13894,10 +13441,10 @@ unzipper@^0.12.2:
graceful-fs "^4.2.2"
node-int64 "^0.4.0"
-update-browserslist-db@^1.3.3:
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz#197e21fb2561fa89f8b94fad605a2b296a18993a"
- integrity sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==
+update-browserslist-db@^1.3.4:
+ version "1.3.4"
+ resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz"
+ integrity sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==
dependencies:
escalade "^3.2.0"
picocolors "^1.1.1"
@@ -14015,10 +13562,10 @@ vite-node@3.2.4:
pathe "^2.0.3"
vite "^5.0.0 || ^6.0.0 || ^7.0.0-0"
-"vite@^5.0.0 || ^6.0.0 || ^7.0.0-0", vite@^6.4.3:
- version "6.4.3"
- resolved "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz"
- integrity sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==
+"vite@^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "vite@^5.0.0 || ^6.0.0 || ^7.0.0-0", vite@^6.4.3:
+ version "6.4.5"
+ resolved "https://registry.npmjs.org/vite/-/vite-6.4.5.tgz"
+ integrity sha512-4enwjFfi4IwtEh0w+jPzpLVOFQiPHmmzPxcHXQOyEur47o6u0jE573DmGpjA+7irXoMLYRZD5ZaFUEneS3abPg==
dependencies:
esbuild "^0.25.0"
fdir "^6.4.4"
@@ -14029,19 +13576,19 @@ vite-node@3.2.4:
optionalDependencies:
fsevents "~2.3.3"
-vitest@^3.2.4:
- version "3.2.4"
- resolved "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz"
- integrity sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==
+vitest@*, vitest@^3.2.4, vitest@3.2.7:
+ version "3.2.7"
+ resolved "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz"
+ integrity sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==
dependencies:
"@types/chai" "^5.2.2"
- "@vitest/expect" "3.2.4"
- "@vitest/mocker" "3.2.4"
- "@vitest/pretty-format" "^3.2.4"
- "@vitest/runner" "3.2.4"
- "@vitest/snapshot" "3.2.4"
- "@vitest/spy" "3.2.4"
- "@vitest/utils" "3.2.4"
+ "@vitest/expect" "3.2.7"
+ "@vitest/mocker" "3.2.7"
+ "@vitest/pretty-format" "^3.2.7"
+ "@vitest/runner" "3.2.7"
+ "@vitest/snapshot" "3.2.7"
+ "@vitest/spy" "3.2.7"
+ "@vitest/utils" "3.2.7"
chai "^5.2.0"
debug "^4.4.1"
expect-type "^1.2.1"
@@ -14118,7 +13665,7 @@ webpack-sources@^3.5.0:
resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.0.tgz"
integrity sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==
-webpack@^5.104.1:
+webpack@^5.0.0, webpack@^5.1.0, webpack@^5.104.1:
version "5.107.2"
resolved "https://registry.npmjs.org/webpack/-/webpack-5.107.2.tgz"
integrity sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ==
@@ -14220,7 +13767,14 @@ which-typed-array@^1.1.16, which-typed-array@^1.1.19, which-typed-array@^1.1.2:
gopd "^1.2.0"
has-tostringtag "^1.0.2"
-which@^1.2.9, which@^1.3.1:
+which@^1.2.9:
+ version "1.3.1"
+ resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz"
+ integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
+ dependencies:
+ isexe "^2.0.0"
+
+which@^1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -14338,9 +13892,9 @@ write-file-atomic@^5.0.1:
signal-exit "^4.0.1"
ws@^8.18.0:
- version "8.18.3"
- resolved "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz"
- integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==
+ version "8.22.0"
+ resolved "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz"
+ integrity sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==
xlsx@^0.18.5:
version "0.18.5"
@@ -14372,16 +13926,16 @@ xmldoc@^2.0.1:
dependencies:
sax "^1.2.4"
-"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.1:
- version "4.0.2"
- resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz"
- integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
-
xtend@^2.1.2:
version "2.2.0"
resolved "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz"
integrity sha512-SLt5uylT+4aoXxXuwtQp5ZnMMzhDb1Xkg4pEqc00WUJCQifPfV9Ub1VrNhp9kXkrjZD2I2Hl8WnjP37jzZLPZw==
+xtend@^4.0.0, "xtend@>=4.0.0 <4.1.0-0", xtend@~4.0.1:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz"
+ integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
+
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
@@ -14393,14 +13947,14 @@ yallist@^3.0.2:
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yaml@^1.10.0:
- version "1.10.2"
- resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz"
- integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
+ version "1.10.3"
+ resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz"
+ integrity sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==
-yaml@^2.8.1:
- version "2.8.1"
- resolved "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz"
- integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==
+yaml@^2.4.2, yaml@^2.8.1:
+ version "2.9.1"
+ resolved "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz"
+ integrity sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==
yargs-parser@^21.1.1:
version "21.1.1"