diff --git a/package-lock.json b/package-lock.json
index dbb69cf..670555e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -32,9 +32,8 @@
"framer-motion": "^12.23.24",
"highcharts": "^12.4.0",
"highcharts-react-official": "^3.2.2",
- "html2canvas": "^1.4.1",
"i": "^0.3.7",
- "jspdf": "^3.0.3",
+ "jspdf": "^4.0.0",
"jspdf-autotable": "^5.0.2",
"lucide-react": "^0.515.0",
"npm": "^11.5.2",
@@ -104,6 +103,7 @@
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3",
@@ -420,6 +420,7 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -463,6 +464,7 @@
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -1334,6 +1336,7 @@
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.2.tgz",
"integrity": "sha512-qXvbnawQhqUVfH1LMgMaiytP+ZpGoYhnGl7yYq2x57GYzcFL/iPzSZ3L30tlbwEjSVKNYcbiKO8tANR1tadjUg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.28.3",
"@mui/core-downloads-tracker": "^7.3.2",
@@ -1444,6 +1447,7 @@
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.2.tgz",
"integrity": "sha512-9d8JEvZW+H6cVkaZ+FK56R53vkJe3HsTpcjMUtH8v1xK6Y1TjzHdZ7Jck02mGXJsE6MQGWVs3ogRHTQmS9Q/rA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.28.3",
"@mui/private-theming": "^7.3.2",
@@ -3061,6 +3065,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -3071,6 +3076,7 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true,
"license": "MIT",
+ "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -3437,6 +3443,7 @@
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"license": "MIT",
+ "optional": true,
"engines": {
"node": ">= 0.6.0"
}
@@ -3481,6 +3488,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001741",
@@ -3646,6 +3654,7 @@
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
"integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@kurkle/color": "^0.3.0"
},
@@ -3751,12 +3760,16 @@
"license": "MIT"
},
"node_modules/cookie": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
- "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"license": "MIT",
"engines": {
"node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/core-js": {
@@ -3827,6 +3840,7 @@
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"license": "MIT",
+ "optional": true,
"dependencies": {
"utrie": "^1.0.2"
}
@@ -4094,6 +4108,7 @@
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"license": "MIT",
+ "peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
@@ -4103,7 +4118,8 @@
"version": "1.11.18",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz",
"integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/debug": {
"version": "4.4.3",
@@ -4228,9 +4244,9 @@
}
},
"node_modules/diff": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
- "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz",
+ "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
@@ -4558,6 +4574,7 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz",
"integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -5270,7 +5287,8 @@
"version": "12.4.0",
"resolved": "https://registry.npmjs.org/highcharts/-/highcharts-12.4.0.tgz",
"integrity": "sha512-o6UxxfChSUrvrZUbWrAuqL1HO/+exhAUPcZY6nnqLsadZQlnP16d082sg7DnXKZCk1gtfkyfkp6g3qkIZ9miZg==",
- "license": "https://www.highcharts.com/license"
+ "license": "https://www.highcharts.com/license",
+ "peer": true
},
"node_modules/highcharts-react-official": {
"version": "3.2.2",
@@ -5302,6 +5320,7 @@
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"license": "MIT",
+ "optional": true,
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
@@ -5910,12 +5929,13 @@
}
},
"node_modules/jspdf": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.3.tgz",
- "integrity": "sha512-eURjAyz5iX1H8BOYAfzvdPfIKK53V7mCpBTe7Kb16PaM8JSXEcUQNBQaiWMI8wY5RvNOPj4GccMjTlfwRBd+oQ==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.0.0.tgz",
+ "integrity": "sha512-w12U97Z6edKd2tXDn3LzTLg7C7QLJlx0BPfM3ecjK2BckUl9/81vZ+r5gK4/3KQdhAcEZhENUxRhtgYBj75MqQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
- "@babel/runtime": "^7.26.9",
+ "@babel/runtime": "^7.28.4",
"fast-png": "^6.2.0",
"fflate": "^0.8.1"
},
@@ -5927,12 +5947,12 @@
}
},
"node_modules/jspdf-autotable": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-5.0.2.tgz",
- "integrity": "sha512-YNKeB7qmx3pxOLcNeoqAv3qTS7KuvVwkFe5AduCawpop3NOkBUtqDToxNc225MlNecxT4kP2Zy3z/y/yvGdXUQ==",
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-5.0.7.tgz",
+ "integrity": "sha512-2wr7H6liNDBYNwt25hMQwXkEWFOEopgKIvR1Eukuw6Zmprm/ZcnmLTQEjW7Xx3FCbD3v7pflLcnMAv/h1jFDQw==",
"license": "MIT",
"peerDependencies": {
- "jspdf": "^2 || ^3"
+ "jspdf": "^2 || ^3 || ^4"
}
},
"node_modules/jsx-ast-utils": {
@@ -6243,9 +6263,9 @@
}
},
"node_modules/lodash-es": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
- "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
+ "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
"license": "MIT"
},
"node_modules/lodash.merge": {
@@ -7410,9 +7430,9 @@
"license": "MIT"
},
"node_modules/npm": {
- "version": "11.6.4",
- "resolved": "https://registry.npmjs.org/npm/-/npm-11.6.4.tgz",
- "integrity": "sha512-ERjKtGoFpQrua/9bG0+h3xiv/4nVdGViCjUYA1AmlV24fFvfnSB7B7dIfZnySQ1FDLd0ZVrWPsLLp78dCtJdRQ==",
+ "version": "11.8.0",
+ "resolved": "https://registry.npmjs.org/npm/-/npm-11.8.0.tgz",
+ "integrity": "sha512-n19sJeW+RGKdkHo8SCc5xhSwkKhQUFfZaFzSc+EsYXLjSqIV0tl72aDYQVuzVvfrbysGwdaQsNLNy58J10EBSQ==",
"bundleDependencies": [
"@isaacs/string-locale-compare",
"@npmcli/arborist",
@@ -7491,8 +7511,8 @@
],
"dependencies": {
"@isaacs/string-locale-compare": "^1.1.0",
- "@npmcli/arborist": "^9.1.8",
- "@npmcli/config": "^10.4.4",
+ "@npmcli/arborist": "^9.1.10",
+ "@npmcli/config": "^10.5.0",
"@npmcli/fs": "^5.0.0",
"@npmcli/map-workspaces": "^5.0.3",
"@npmcli/metavuln-calculator": "^9.0.3",
@@ -7500,7 +7520,7 @@
"@npmcli/promise-spawn": "^9.0.1",
"@npmcli/redact": "^4.0.0",
"@npmcli/run-script": "^10.0.3",
- "@sigstore/tuf": "^4.0.0",
+ "@sigstore/tuf": "^4.0.1",
"abbrev": "^4.0.0",
"archy": "~1.0.0",
"cacache": "^20.0.3",
@@ -7517,11 +7537,11 @@
"is-cidr": "^6.0.1",
"json-parse-even-better-errors": "^5.0.0",
"libnpmaccess": "^10.0.3",
- "libnpmdiff": "^8.0.11",
- "libnpmexec": "^10.1.10",
- "libnpmfund": "^7.0.11",
+ "libnpmdiff": "^8.0.13",
+ "libnpmexec": "^10.1.12",
+ "libnpmfund": "^7.0.13",
"libnpmorg": "^8.0.1",
- "libnpmpack": "^9.0.11",
+ "libnpmpack": "^9.0.13",
"libnpmpublish": "^11.1.3",
"libnpmsearch": "^9.0.1",
"libnpmteam": "^8.0.2",
@@ -7550,11 +7570,11 @@
"spdx-expression-parse": "^4.0.0",
"ssri": "^13.0.0",
"supports-color": "^10.2.2",
- "tar": "^7.5.2",
+ "tar": "^7.5.4",
"text-table": "~0.2.0",
"tiny-relative-date": "^2.0.2",
"treeverse": "^3.0.0",
- "validate-npm-package-name": "^7.0.0",
+ "validate-npm-package-name": "^7.0.2",
"which": "^6.0.0"
},
"bin": {
@@ -7616,7 +7636,7 @@
}
},
"node_modules/npm/node_modules/@npmcli/arborist": {
- "version": "9.1.8",
+ "version": "9.1.10",
"inBundle": true,
"license": "ISC",
"dependencies": {
@@ -7633,7 +7653,7 @@
"@npmcli/run-script": "^10.0.0",
"bin-links": "^6.0.0",
"cacache": "^20.0.1",
- "common-ancestor-path": "^1.0.1",
+ "common-ancestor-path": "^2.0.0",
"hosted-git-info": "^9.0.0",
"json-stringify-nice": "^1.1.4",
"lru-cache": "^11.2.1",
@@ -7662,7 +7682,7 @@
}
},
"node_modules/npm/node_modules/@npmcli/config": {
- "version": "10.4.4",
+ "version": "10.5.0",
"inBundle": true,
"license": "ISC",
"dependencies": {
@@ -7843,7 +7863,7 @@
}
},
"node_modules/npm/node_modules/@sigstore/core": {
- "version": "3.0.0",
+ "version": "3.1.0",
"inBundle": true,
"license": "Apache-2.0",
"engines": {
@@ -7859,48 +7879,40 @@
}
},
"node_modules/npm/node_modules/@sigstore/sign": {
- "version": "4.0.1",
+ "version": "4.1.0",
"inBundle": true,
"license": "Apache-2.0",
"dependencies": {
"@sigstore/bundle": "^4.0.0",
- "@sigstore/core": "^3.0.0",
+ "@sigstore/core": "^3.1.0",
"@sigstore/protobuf-specs": "^0.5.0",
- "make-fetch-happen": "^15.0.2",
- "proc-log": "^5.0.0",
+ "make-fetch-happen": "^15.0.3",
+ "proc-log": "^6.1.0",
"promise-retry": "^2.0.1"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
- "node_modules/npm/node_modules/@sigstore/sign/node_modules/proc-log": {
- "version": "5.0.0",
- "inBundle": true,
- "license": "ISC",
- "engines": {
- "node": "^18.17.0 || >=20.5.0"
- }
- },
"node_modules/npm/node_modules/@sigstore/tuf": {
- "version": "4.0.0",
+ "version": "4.0.1",
"inBundle": true,
"license": "Apache-2.0",
"dependencies": {
"@sigstore/protobuf-specs": "^0.5.0",
- "tuf-js": "^4.0.0"
+ "tuf-js": "^4.1.0"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/npm/node_modules/@sigstore/verify": {
- "version": "3.0.0",
+ "version": "3.1.0",
"inBundle": true,
"license": "Apache-2.0",
"dependencies": {
"@sigstore/bundle": "^4.0.0",
- "@sigstore/core": "^3.0.0",
+ "@sigstore/core": "^3.1.0",
"@sigstore/protobuf-specs": "^0.5.0"
},
"engines": {
@@ -7916,31 +7928,17 @@
}
},
"node_modules/npm/node_modules/@tufjs/models": {
- "version": "4.0.0",
+ "version": "4.1.0",
"inBundle": true,
"license": "MIT",
"dependencies": {
"@tufjs/canonical-json": "2.0.0",
- "minimatch": "^9.0.5"
+ "minimatch": "^10.1.1"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
- "node_modules/npm/node_modules/@tufjs/models/node_modules/minimatch": {
- "version": "9.0.5",
- "inBundle": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/npm/node_modules/abbrev": {
"version": "4.0.0",
"inBundle": true,
@@ -7975,11 +7973,6 @@
"inBundle": true,
"license": "MIT"
},
- "node_modules/npm/node_modules/balanced-match": {
- "version": "1.0.2",
- "inBundle": true,
- "license": "MIT"
- },
"node_modules/npm/node_modules/bin-links": {
"version": "6.0.0",
"inBundle": true,
@@ -8006,14 +7999,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/npm/node_modules/brace-expansion": {
- "version": "2.0.2",
- "inBundle": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
"node_modules/npm/node_modules/cacache": {
"version": "20.0.3",
"inBundle": true,
@@ -8100,9 +8085,12 @@
}
},
"node_modules/npm/node_modules/common-ancestor-path": {
- "version": "1.0.1",
+ "version": "2.0.0",
"inBundle": true,
- "license": "ISC"
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">= 18"
+ }
},
"node_modules/npm/node_modules/cssesc": {
"version": "3.0.0",
@@ -8132,7 +8120,7 @@
}
},
"node_modules/npm/node_modules/diff": {
- "version": "8.0.2",
+ "version": "8.0.3",
"inBundle": true,
"license": "BSD-3-Clause",
"engines": {
@@ -8308,7 +8296,7 @@
}
},
"node_modules/npm/node_modules/ip-address": {
- "version": "10.0.1",
+ "version": "10.1.0",
"inBundle": true,
"license": "MIT",
"engines": {
@@ -8400,11 +8388,11 @@
}
},
"node_modules/npm/node_modules/libnpmdiff": {
- "version": "8.0.11",
+ "version": "8.0.13",
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.1.8",
+ "@npmcli/arborist": "^9.1.10",
"@npmcli/installed-package-contents": "^4.0.0",
"binary-extensions": "^3.0.0",
"diff": "^8.0.2",
@@ -8418,11 +8406,11 @@
}
},
"node_modules/npm/node_modules/libnpmexec": {
- "version": "10.1.10",
+ "version": "10.1.12",
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.1.8",
+ "@npmcli/arborist": "^9.1.10",
"@npmcli/package-json": "^7.0.0",
"@npmcli/run-script": "^10.0.0",
"ci-info": "^4.0.0",
@@ -8440,11 +8428,11 @@
}
},
"node_modules/npm/node_modules/libnpmfund": {
- "version": "7.0.11",
+ "version": "7.0.13",
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.1.8"
+ "@npmcli/arborist": "^9.1.10"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
@@ -8463,11 +8451,11 @@
}
},
"node_modules/npm/node_modules/libnpmpack": {
- "version": "9.0.11",
+ "version": "9.0.13",
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.1.8",
+ "@npmcli/arborist": "^9.1.10",
"@npmcli/run-script": "^10.0.0",
"npm-package-arg": "^13.0.0",
"pacote": "^21.0.2"
@@ -8533,9 +8521,9 @@
}
},
"node_modules/npm/node_modules/lru-cache": {
- "version": "11.2.2",
+ "version": "11.2.4",
"inBundle": true,
- "license": "ISC",
+ "license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
@@ -8916,7 +8904,7 @@
}
},
"node_modules/npm/node_modules/path-scurry": {
- "version": "2.0.0",
+ "version": "2.0.1",
"inBundle": true,
"license": "BlueOak-1.0.0",
"dependencies": {
@@ -8931,7 +8919,7 @@
}
},
"node_modules/npm/node_modules/postcss-selector-parser": {
- "version": "7.1.0",
+ "version": "7.1.1",
"inBundle": true,
"license": "MIT",
"dependencies": {
@@ -9060,16 +9048,16 @@
}
},
"node_modules/npm/node_modules/sigstore": {
- "version": "4.0.0",
+ "version": "4.1.0",
"inBundle": true,
"license": "Apache-2.0",
"dependencies": {
"@sigstore/bundle": "^4.0.0",
- "@sigstore/core": "^3.0.0",
+ "@sigstore/core": "^3.1.0",
"@sigstore/protobuf-specs": "^0.5.0",
- "@sigstore/sign": "^4.0.0",
- "@sigstore/tuf": "^4.0.0",
- "@sigstore/verify": "^3.0.0"
+ "@sigstore/sign": "^4.1.0",
+ "@sigstore/tuf": "^4.0.1",
+ "@sigstore/verify": "^3.1.0"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
@@ -9194,7 +9182,7 @@
}
},
"node_modules/npm/node_modules/tar": {
- "version": "7.5.2",
+ "version": "7.5.4",
"inBundle": true,
"license": "BlueOak-1.0.0",
"dependencies": {
@@ -9261,6 +9249,7 @@
"version": "4.0.3",
"inBundle": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -9277,13 +9266,13 @@
}
},
"node_modules/npm/node_modules/tuf-js": {
- "version": "4.0.0",
+ "version": "4.1.0",
"inBundle": true,
"license": "MIT",
"dependencies": {
- "@tufjs/models": "4.0.0",
- "debug": "^4.4.1",
- "make-fetch-happen": "^15.0.0"
+ "@tufjs/models": "4.1.0",
+ "debug": "^4.4.3",
+ "make-fetch-happen": "^15.0.1"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
@@ -9335,7 +9324,7 @@
}
},
"node_modules/npm/node_modules/validate-npm-package-name": {
- "version": "7.0.0",
+ "version": "7.0.2",
"inBundle": true,
"license": "ISC",
"engines": {
@@ -9678,9 +9667,9 @@
}
},
"node_modules/preact": {
- "version": "10.27.2",
- "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz",
- "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==",
+ "version": "10.28.2",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz",
+ "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -9753,6 +9742,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz",
"integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -9787,6 +9777,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz",
"integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -9836,7 +9827,8 @@
"version": "19.1.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz",
"integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/react-kapsule": {
"version": "2.5.7",
@@ -9895,6 +9887,7 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
@@ -9971,9 +9964,9 @@
}
},
"node_modules/react-router": {
- "version": "7.9.1",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.1.tgz",
- "integrity": "sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz",
+ "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
@@ -9993,12 +9986,12 @@
}
},
"node_modules/react-router-dom": {
- "version": "7.9.1",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.1.tgz",
- "integrity": "sha512-U9WBQssBE9B1vmRjo9qTM7YRzfZ3lUxESIZnsf4VjR/lXYz9MHjvOxHzr/aUm4efpktbVOrF09rL/y4VHa8RMw==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz",
+ "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==",
"license": "MIT",
"dependencies": {
- "react-router": "7.9.1"
+ "react-router": "7.13.0"
},
"engines": {
"node": ">=20.0.0"
@@ -10134,7 +10127,8 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/redux-thunk": {
"version": "3.1.0",
@@ -11056,9 +11050,9 @@
}
},
"node_modules/set-cookie-parser": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
- "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
"license": "MIT"
},
"node_modules/set-function-length": {
@@ -11428,6 +11422,7 @@
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz",
"integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==",
"license": "MIT",
+ "peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
@@ -11456,7 +11451,8 @@
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz",
"integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/tailwindcss-animate": {
"version": "1.0.7",
@@ -11481,10 +11477,10 @@
}
},
"node_modules/tar": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.4.tgz",
- "integrity": "sha512-O1z7ajPkjTgEgmTGz0v9X4eqeEXTDREPTO77pVC1Nbs86feBU1Zhdg+edzavPmYW1olxkwsqA2v4uOw6E8LeDg==",
- "license": "ISC",
+ "version": "7.5.7",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
+ "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
+ "license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
@@ -11510,6 +11506,7 @@
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"license": "MIT",
+ "optional": true,
"dependencies": {
"utrie": "^1.0.2"
}
@@ -11518,7 +11515,8 @@
"version": "0.180.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz",
"integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/three-forcegraph": {
"version": "1.43.0",
@@ -11625,6 +11623,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -12011,6 +12010,7 @@
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"license": "MIT",
+ "optional": true,
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
@@ -12116,6 +12116,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -12207,6 +12208,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
diff --git a/package.json b/package.json
index cd871a9..d21f1b3 100644
--- a/package.json
+++ b/package.json
@@ -35,9 +35,9 @@
"highcharts": "^12.4.0",
"highcharts-react-official": "^3.2.2",
"i": "^0.3.7",
- "lucide-react": "^0.515.0",
- "jspdf": "^3.0.3",
+ "jspdf": "^4.0.0",
"jspdf-autotable": "^5.0.2",
+ "lucide-react": "^0.515.0",
"npm": "^11.5.2",
"react": "^19.2.1",
"react-chartjs-2": "^5.3.0",
@@ -74,4 +74,4 @@
"tw-animate-css": "^1.4.0",
"vite": "^6.3.5"
}
-}
\ No newline at end of file
+}
diff --git a/src/components/version.jsx b/src/components/version.jsx
index 139b8de..eb45143 100644
--- a/src/components/version.jsx
+++ b/src/components/version.jsx
@@ -1,18 +1,11 @@
-import { useEffect, useState } from "react";
-
export default function Version() {
- const [version, setVersion] = useState("");
+ const version = window?.configs?.version || "";
- useEffect(() => {
- const version = window?.configs?.version
- ? window.configs.version
- : "rc-1-1";
- setVersion(version);
- }, []);
+ if (!version) return null;
return (
-
- {version}
-
+
+ {version}
+
);
}
diff --git a/src/hooks/useGetDatasetsByYears.js b/src/hooks/useGetDatasetsByYears.js
index 2645f17..5ff1896 100644
--- a/src/hooks/useGetDatasetsByYears.js
+++ b/src/hooks/useGetDatasetsByYears.js
@@ -34,8 +34,10 @@ export const useGetDatasetsByYears = (selectedYears = [], yearToDatasetId = {})
.filter(Boolean);
}, [selectedYears, queries]);
- // Check if any query is loading
+ // Check if any query is loading or has error
const isAnyLoading = queries.some((query) => query.isLoading);
+ const isError = queries.some((query) => query.isError);
+ const error = queries.find((query) => query.error)?.error;
// Get the current loading year (first loading query)
const loadingYear = useMemo(() => {
@@ -46,6 +48,8 @@ export const useGetDatasetsByYears = (selectedYears = [], yearToDatasetId = {})
return {
fetchedDatasets,
loadingYear,
- isAnyLoading
+ isAnyLoading,
+ isError,
+ error
};
};
diff --git a/src/index.css b/src/index.css
index c2296a6..edf6f92 100644
--- a/src/index.css
+++ b/src/index.css
@@ -17,22 +17,25 @@
--primary: oklch(0.15 0 0);
--primary-foreground: oklch(0.98 0 0);
- --secondary: oklch(0.65 0.2 240); /* soft blue */
+ --secondary: oklch(0.65 0.2 240);
+ /* soft blue */
--secondary-foreground: oklch(1 0 0);
--muted: oklch(0.92 0 0);
--muted-foreground: oklch(0.5 0 0);
- --accent: oklch(0.65 0.2 240); /* same as secondary for accent */
+ --accent: oklch(0.65 0.2 240);
+ /* same as secondary for accent */
--accent-foreground: oklch(1 0 0);
- --destructive: oklch(0.577 0.245 27.325); /* red for destructive stays */
+ --destructive: oklch(0.577 0.245 27.325);
+ /* red for destructive stays */
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(89.143% 0.0001 271.152);
--input: oklch(0.92 0 0);
--ring: oklch(0.65 0.2 240);
-
+
--active-green: oklch(72.3% 0.219 149.579);
--purple-light: oklch(71.4% 0.203 305.504);
@@ -116,7 +119,7 @@ body {
.react-datepicker {
display: flex !important;
- gap: 1rem;
+ gap: 1rem;
}
.react-datepicker__month-container {
@@ -124,10 +127,11 @@ body {
}
.scroll-wrapper {
- scrollbar-width: thin;
+ scrollbar-width: thin;
scrollbar-color: transparent transparent;
transition: scrollbar-color 0.3s;
}
+
.react-datepicker__month-container {
width: 100% !important;
}
@@ -136,6 +140,7 @@ body {
width: 100% !important;
}
+
.react-datepicker__day-names {
width: 100% !important;
display: flex;
@@ -170,13 +175,15 @@ body {
}
.custom-scrollbar::-webkit-scrollbar-thumb {
- background-color: rgba(100, 100, 100, 0.4); /* Thumb color */
+ background-color: rgba(100, 100, 100, 0.4);
+ /* Thumb color */
border-radius: 4px;
}
/* Scrollbar Styling for Firefox */
.custom-scrollbar {
- scrollbar-width: thin; /* Makes it thin */
+ scrollbar-width: thin;
+ /* Makes it thin */
scrollbar-color: rgba(100, 100, 100, 0.4) transparent;
}
@@ -184,6 +191,7 @@ body {
* {
@apply border-border outline-ring/50;
}
+
body {
@apply bg-background text-foreground;
}
@@ -195,33 +203,28 @@ body {
/* Hide scrollbar but allow scroll */
.no-scrollbar {
- -ms-overflow-style: none;
- scrollbar-width: none;
+ -ms-overflow-style: none;
+ scrollbar-width: none;
}
+
.no-scrollbar::-webkit-scrollbar {
- display: none;
+ display: none;
}
::-webkit-scrollbar {
- width: 7px;
- height: 5px;
+ width: 5px;
+ height: 3px;
}
::-webkit-scrollbar-track {
- background: #1e293b;
+ background: #d6dcdf;
}
::-webkit-scrollbar-thumb {
- background-color: #043077;
+ background-color: #0099ee;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
- background-color: #06359c; /* darker on hover */
-}
-
-
-
-
-
-
+ background-color: #036fa9;
+}
\ No newline at end of file
diff --git a/src/pages/DataPage/components/chart-visualization.jsx b/src/pages/DataPage/components/chart-visualization.jsx
index 333b21b..5140ecd 100644
--- a/src/pages/DataPage/components/chart-visualization.jsx
+++ b/src/pages/DataPage/components/chart-visualization.jsx
@@ -17,7 +17,7 @@ import { useThemeContext } from "../../../context/themeContext";
const COLORS = [
- "#00bcd4", "#8bc34a", "#ffc107", "#ff9800", "#e91e63", "#9c27b0",
+ "#00bcd4", "#8bc34a", "#ffc107", "#ff9800", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#2196f3", "#009688", "#4caf50", "#ff5722", "#795548", "#9e9e9e", "#607d8b"
];
export function ChartVisualization({ columns, rows, yearlyData }) {
@@ -29,6 +29,7 @@ export function ChartVisualization({ columns, rows, yearlyData }) {
const [detectedXTicks, setDetectedXTicks] = useState([]);
const [tickRevision, setTickRevision] = useState(0);
const [chartType, setChartType] = useState("bar");
+ const [tiltLabels, setTiltLabels] = useState(false);
const chartRef = useRef(null);
const chartContainerRef = useRef(null);
@@ -104,6 +105,26 @@ export function ChartVisualization({ columns, rows, yearlyData }) {
.replace(/\s+/g, "");
};
+ const splitTextIntoLines = (text, maxChars) => {
+ const str = String(text || "").trim();
+ const words = str.split(/\s+/);
+ const lines = [];
+ let currentLine = "";
+
+ words.forEach((word) => {
+ if (!currentLine) {
+ currentLine = word;
+ } else if ((currentLine + " " + word).length <= maxChars) {
+ currentLine += " " + word;
+ } else {
+ lines.push(currentLine);
+ currentLine = word;
+ }
+ });
+ if (currentLine) lines.push(currentLine);
+ return lines;
+ };
+
const isXString = useMemo(() => stringColumns.includes(xAxis), [xAxis, stringColumns]);
const yStringColumn = useMemo(() => selectedYColumns.find(col => stringColumns.includes(col)), [selectedYColumns, stringColumns]);
const isAnyYString = !!yStringColumn;
@@ -324,13 +345,15 @@ export function ChartVisualization({ columns, rows, yearlyData }) {
}, 250);
return () => clearTimeout(timer);
- }, [chartData, selectedYColumns, chartLayout, chartType, tickRevision]);
+ }, [chartData, selectedYColumns, chartLayout, chartType, tickRevision, tiltLabels]);
const isMultiYear = normalizedYearlyData.length > 1;
// Layout constants
const yAxisGutterWidth = chartLayout === 'horizontal' ? 150 : 100;
- const yAxisLabelSectionWidth = yAxisGutterWidth - 25; // Reserve space for title
+ const yAxisLabelSectionWidth = chartLayout === 'horizontal'
+ ? yAxisGutterWidth - 2 // Horizontal: minimal gap, title is in fixed overlay
+ : yAxisGutterWidth - 25; // Vertical: reserve space for title
const barsPerCategory = normalizedYearlyData.length * valueColumns.length;
@@ -348,6 +371,28 @@ export function ChartVisualization({ columns, rows, yearlyData }) {
? Math.max(chartData.length * widthPerCategory, 450)
: 450;
+ // Format large numbers with abbreviations
+ const abbreviateNumber = (value) => {
+ if (typeof value !== 'number') return value;
+
+ const absValue = Math.abs(value);
+ const sign = value < 0 ? '-' : '';
+
+ if (absValue >= 1e12) {
+ return sign + (absValue / 1e12).toFixed(1).replace(/\.0$/, '') + 'T';
+ }
+ if (absValue >= 1e9) {
+ return sign + (absValue / 1e9).toFixed(1).replace(/\.0$/, '') + 'B';
+ }
+ if (absValue >= 1e6) {
+ return sign + (absValue / 1e6).toFixed(1).replace(/\.0$/, '') + 'M';
+ }
+ if (absValue >= 1e3) {
+ return sign + (absValue / 1e3).toFixed(1).replace(/\.0$/, '') + 'K';
+ }
+ return value.toString();
+ };
+
const renderCustomTooltip = ({ active, payload, label }) => {
if (active && payload && payload.length) {
return (
@@ -494,6 +539,18 @@ export function ChartVisualization({ columns, rows, yearlyData }) {
Line Chart
+
+ {/* Tilt Labels Toggle */}
+ {chartLayout === 'vertical' && (
+
+ setTiltLabels(!tiltLabels)}
+ className="px-3 py-1.5 text-sm border border-border rounded-md bg-background text-primary hover:bg-background-dark transition-colors"
+ >
+ {tiltLabels ? 'Straighten X-axis Labels' : 'Tilt X-axis Labels'}
+
+
+ )}
{/* Chart */}
@@ -590,7 +647,7 @@ export function ChartVisualization({ columns, rows, yearlyData }) {
{detectedTicks.map((tick, i) => {
const text = (tick.label || "").trim();
- const words = text.split(/\s+/);
- const maxChars = 13;
- const lines = [];
- let currentLine = "";
-
- words.forEach((word) => {
- if (!currentLine) {
- currentLine = word;
- } else if ((currentLine + " " + word).length <= maxChars) {
- currentLine += " " + word;
- } else {
- lines.push(currentLine);
- currentLine = word;
+ const lines = splitTextIntoLines(text, 13);
+ // Calculate dynamic translateY for horizontal layout
+ let translateY = "-50%";
+ if (chartLayout === 'horizontal') {
+ const isTopLabel = i === 0;
+ const isBottomLabel = i === detectedTicks.length - 1;
+
+ if (isTopLabel && lines.length > 1) {
+ // Bar chart: less adjustment (stays higher)
+ // Line chart: more adjustment (shifts down more)
+ const adjustment = chartType === 'bar'
+ ? Math.max(60, 50 - (lines.length - 1) * 5)
+ : Math.max(40, 50 - (lines.length - 1) * 10);
+ translateY = `-${adjustment}%`;
+ } else if (isBottomLabel && lines.length > 1) {
+ translateY = `-${Math.min(70, 50 + (lines.length - 1) * 10)}%`;
}
- });
- if (currentLine) lines.push(currentLine);
+ }
return (
{lines.map((line, idx) => (
-
{line}
+
{line}
))}
);
@@ -704,50 +764,86 @@ export function ChartVisualization({ columns, rows, yearlyData }) {
nice={chartLayout === "horizontal"}
stroke="white"
interval={0}
- height={chartLayout === "horizontal" ? 30 : 86}
+ height={chartLayout === "horizontal" ? 30 : (tiltLabels ? 128 : 86)}
tickLine={false}
axisLine={{ stroke: "#374151" }}
+ tickFormatter={chartLayout === 'horizontal' ? abbreviateNumber : undefined}
tick={chartLayout === "horizontal" ? { fill: "transparent" } : ({ x, y, payload }) => {
- const maxCharsPerLine = 8;
- const text = payload.value;
- let line1 = text;
- let line2 = "";
- if (text.length > maxCharsPerLine) {
- const splitIndex = text.lastIndexOf(" ", maxCharsPerLine);
- if (splitIndex > 0) {
- line1 = text.slice(0, splitIndex);
- line2 = text.slice(splitIndex + 1);
- } else {
- line1 = text.slice(0, maxCharsPerLine);
- line2 = text.slice(maxCharsPerLine);
+ const text = String(payload.value || "");
+
+ if (tiltLabels) {
+ // Tilted labels - multi-line with rotation
+ const lines = splitTextIntoLines(text, 12);
+
+ // Calculate x-offset based on number of lines
+ const xOffset = lines.length > 1 ? -4 * (lines.length - 1) : 0;
+
+ // Dynamic font size based on number of lines
+ const fontSize = lines.length === 1 ? 8 : lines.length === 2 ? 8 : 7;
+
+ return (
+
+ {lines.map((line, idx) => (
+
+ {line}
+
+ ))}
+
+ );
+ } else {
+ // Multi-line labels without tilt
+ const maxCharsPerLine = 14;
+ const str = String(text || "");
+ let line1 = str;
+ let line2 = "";
+ if (str.length > maxCharsPerLine) {
+ const splitIndex = str.lastIndexOf(" ", maxCharsPerLine);
+ if (splitIndex > 0) {
+ line1 = str.slice(0, splitIndex);
+ line2 = str.slice(splitIndex + 1);
+ } else {
+ line1 = str.slice(0, maxCharsPerLine);
+ line2 = str.slice(maxCharsPerLine);
+ }
}
- }
- return (
-
-
- {line1}
-
- {line2 && (
+ const fontSize = line2 ? 10.5 : 11.5;
+
+ return (
+
- {line2}
+ {line1}
- )}
-
- );
+ {line2 && (
+
+ {line2}
+
+ )}
+
+ );
+ }
}}
/>
{xAxis === "" ? (
@@ -805,12 +902,14 @@ export function ChartVisualization({ columns, rows, yearlyData }) {
{
- const maxCharsPerLine = 13;
- const text = payload.value;
- let line1 = text;
- let line2 = "";
- if (text.length > maxCharsPerLine) {
- const splitIndex = text.lastIndexOf(" ", maxCharsPerLine);
- if (splitIndex > 0) {
- line1 = text.slice(0, splitIndex);
- line2 = text.slice(splitIndex + 1);
- } else {
- line1 = text.slice(0, maxCharsPerLine);
- line2 = text.slice(maxCharsPerLine);
+ const text = String(payload.value || "");
+
+ if (tiltLabels) {
+ // Tilted labels - multi-line with rotation
+ const lines = splitTextIntoLines(text, 12);
+
+ // Calculate x-offset based on number of lines
+ const xOffset = lines.length > 1 ? -2 * (lines.length - 1) : 0;
+
+ // Dynamic font size based on number of lines
+ const fontSize = lines.length === 1 ? 8 : lines.length === 2 ? 8 : 7;
+
+ return (
+
+ {lines.map((line, idx) => (
+
+ {line}
+
+ ))}
+
+ );
+ } else {
+ // Multi-line labels without tilt
+ const maxCharsPerLine = 14;
+ const str = String(text || "");
+ let line1 = str;
+ let line2 = "";
+ if (str.length > maxCharsPerLine) {
+ const splitIndex = str.lastIndexOf(" ", maxCharsPerLine);
+ if (splitIndex > 0) {
+ line1 = str.slice(0, splitIndex);
+ line2 = str.slice(splitIndex + 1);
+ } else {
+ line1 = str.slice(0, maxCharsPerLine);
+ line2 = str.slice(maxCharsPerLine);
+ }
}
- }
- return (
-
-
- {line1}
-
- {line2 && (
+
+
+ const fontSize = line2 ? 10.5 : 11.5;
+
+ return (
+
- {line2}
+ {line1}
- )}
-
- );
+ {line2 && (
+
+ {line2}
+
+ )}
+
+ );
+ }
}}
/>
@@ -993,4 +1134,4 @@ export function ChartVisualization({ columns, rows, yearlyData }) {
)}
>
);
-}
\ No newline at end of file
+}
diff --git a/src/pages/DataPage/components/dataset-view.jsx b/src/pages/DataPage/components/dataset-view.jsx
index 3a95c93..daf1e89 100644
--- a/src/pages/DataPage/components/dataset-view.jsx
+++ b/src/pages/DataPage/components/dataset-view.jsx
@@ -2,7 +2,7 @@ import { DataTable } from "./table-view";
import { useEffect, useState, useMemo } from "react";
import { ClipLoader } from "react-spinners";
import { ChartVisualization } from "./chart-visualization";
-import { Eye } from "lucide-react";
+import { AlertCircle } from "lucide-react";
import { Link, useLocation } from "react-router-dom";
import { useThemeContext } from "../../../context/themeContext";
import { useAvailableYearsForDataset } from "../../../hooks/useAvailableYearsForDataset";
@@ -17,7 +17,7 @@ export function DatasetView({ data, setExternalDateRange }) {
const [filteredYears, setFilteredYears] = useState([]);
// fecth available years
- const { data: availableYearsData, isLoading: yearsLoading } =
+ const { data: availableYearsData, isLoading: yearsLoading, isError: isYearsError, error: yearsError } =
useAvailableYearsForDataset(data?.datasetIds ?? []);
//map year to dataset id
@@ -34,7 +34,8 @@ export function DatasetView({ data, setExternalDateRange }) {
}, [selectedYears, yearToDatasetId]);
// Fetch organization data for all selected datasets in parallel
- const { data: organizationsData } = useRootOrganizations(selectedDatasetIds);
+ const { data: organizationsData, isError: isOrgErrorReal } = useRootOrganizations(selectedDatasetIds);
+ const isOrgError = true; // Forced for testing
// Map organizations to years and check if all names are the same
const organizationsByYear = useMemo(() => {
@@ -110,7 +111,7 @@ export function DatasetView({ data, setExternalDateRange }) {
}, [filteredYears]);
// fetch datasets per year
- const { fetchedDatasets, loadingYear, isAnyLoading } = useGetDatasetsByYears(
+ const { fetchedDatasets, loadingYear, isAnyLoading, isError: isContentError, error: contentError } = useGetDatasetsByYears(
selectedYears,
yearToDatasetId
);
@@ -174,7 +175,20 @@ export function DatasetView({ data, setExternalDateRange }) {
if (yearsLoading) {
return (
-
+
+
+ );
+ }
+
+ if (isYearsError) {
+ return (
+
+
+
+
+ {yearsError?.message || "Failed to load available years for this dataset."}
+
+
);
}
@@ -182,11 +196,11 @@ export function DatasetView({ data, setExternalDateRange }) {
const firstSelectedYear = selectedYears[0];
return (
-
+
{/* dataset info */}
{filteredYears.length > 0 && (
-
+
{data?.name ?? "Dataset"}
@@ -194,7 +208,7 @@ export function DatasetView({ data, setExternalDateRange }) {
{/* year selector */}
{multiYearMode ? (
-
+
{!isPlottable && fetchedDatasets.length > 0 && (
This dataset cannot be visualized. Showing table view only for one year.
@@ -254,6 +268,15 @@ export function DatasetView({ data, setExternalDateRange }) {
)}
+ {isContentError && (
+
+
+
+ {contentError?.message || "Failed to load dataset content."}
+
+
+ )}
+
{fetchedDatasets.length > 0 ? (
<>
@@ -271,67 +294,13 @@ export function DatasetView({ data, setExternalDateRange }) {
>
)}
- {displayOrganizations && (
-
-
- Published by:
- {displayOrganizations.type === 'single' ? (
- // Single organization display
- displayOrganizations.data.type === "department" ? (
-
- {displayOrganizations.data.name}
-
- ) : (
- {displayOrganizations.data.name}
- )
- ) : (
- // Multiple organizations display
-
- {displayOrganizations.data.map((org, idx) => (
-
- {org.year} - {" "}
- {org.type === "department" ? (
-
- {org.name}
-
- ) : (
- {org.name}
- )}
-
- ))}
-
- )}
-
-
- See sources
-
-
- )}
>
) : (
filteredYears.length === 0 &&
- allAvailableYears.length > 0 && (
+ allAvailableYears.length > 0 && !loadingYear && (
- No available data yet! But you have data for
+ No available data yet! You can find data for the following years:
{allAvailableYears.map((year) => (
@@ -345,13 +314,74 @@ export function DatasetView({ data, setExternalDateRange }) {
className="flex text-accent/90 gap-2 cursor-pointer mt-2"
onClick={handleAvailableDatasetView}
>
-
Show me
)
)}
+
+ {(displayOrganizations || isOrgError) && (
+
+
+
+ Published by:
+ {isOrgError && !displayOrganizations && (
+
+ No publisher found
+
+ )}
+ {displayOrganizations && displayOrganizations.type === 'single' && (
+ displayOrganizations.data.type === "department" ? (
+
+ {displayOrganizations.data.name}
+
+ ) : (
+ {displayOrganizations.data.name}
+ )
+ )}
+
+
+ {displayOrganizations && displayOrganizations.type === 'multiple' && (
+
+ {displayOrganizations.data.map((org) => (
+
+ {org.year}:
+ {org.type === "department" ? (
+
+ {org.name}
+
+ ) : (
+ {org.name}
+ )}
+
+ ))}
+
+ )}
+
+
+ See sources
+
+
+ )}
diff --git a/src/pages/DataPage/screens/DataPage.jsx b/src/pages/DataPage/screens/DataPage.jsx
index 59648fd..d333f25 100644
--- a/src/pages/DataPage/screens/DataPage.jsx
+++ b/src/pages/DataPage/screens/DataPage.jsx
@@ -1,7 +1,7 @@
-import { Folder, FileText } from "lucide-react";
+import { AlertCircle, Folder, Loader2, TableProperties } from "lucide-react";
import React, { useEffect, useState } from "react";
import { motion } from "framer-motion";
-import { useNavigate, useSearchParams } from "react-router-dom";
+import { useNavigate, useSearchParams, useLocation } from "react-router-dom";
import { Breadcrumb } from "../components/breadcrumb";
import formatText from "../../../utils/common_functions";
import { DatasetView } from "../components/dataset-view";
@@ -10,99 +10,133 @@ import { useDataCatalog } from "../../../hooks/useDataCatalog";
export default function DataPage({ setExternalDateRange }) {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
+ const location = useLocation();
const [breadcrumbTrail, setBreadcrumbTrail] = useState([]);
const [selectedDataset, setSelectedDataset] = useState(null);
+ const [loadingCardId, setLoadingCardId] = useState(null);
// Decode categoryIds from URL as an array
const categoryIdsParam = searchParams.get("categoryIds") || "";
let categoryIds = [];
if (categoryIdsParam) {
try {
- categoryIds = JSON.parse(decodeURIComponent(categoryIdsParam));
+ // searchParams.get already decodes once. No need for decodeURIComponent.
+ categoryIds = JSON.parse(categoryIdsParam);
if (!Array.isArray(categoryIds)) categoryIds = [categoryIds];
} catch {
categoryIds = [categoryIdsParam];
}
}
- const { data, isLoading } = useDataCatalog(categoryIds);
+ const { data, isLoading, isError, error } = useDataCatalog(categoryIds);
- // Initialize breadcrumb from URL or from first category
+ // Keep reference to previous data while loading
+ const [displayData, setDisplayData] = useState(null);
+
+ // 1. Initialize breadcrumb from URL immediately
useEffect(() => {
const breadcrumbParam = searchParams.get("breadcrumb");
if (breadcrumbParam) {
try {
- const decoded = JSON.parse(decodeURIComponent(breadcrumbParam));
- if (Array.isArray(decoded)) setBreadcrumbTrail(decoded);
+ // searchParams.get already decodes once. No need for decodeURIComponent.
+ const decoded = JSON.parse(breadcrumbParam);
+ if (Array.isArray(decoded)) {
+ setBreadcrumbTrail(decoded);
+ return;
+ }
} catch {
console.warn("Invalid breadcrumb in URL");
}
- } else if (data?.categories?.length && categoryIds.length) {
- const newBreadcrumb = [
+ }
+ }, [searchParams]);
+
+ // 2. Fallback initialization if no breadcrumb exists but data/categoryIds are present
+ useEffect(() => {
+ const breadcrumbParam = searchParams.get("breadcrumb");
+ if (!breadcrumbParam && data?.categories?.length && categoryIds.length) {
+ const initialParams = new URLSearchParams();
+ initialParams.set("categoryIds", JSON.stringify(categoryIds));
+ const initialBreadcrumb = [
{
- label: formatText({ name: data.categories[0].name }) || "Category",
- path: `/datasets?categoryIds=${encodeURIComponent(
- JSON.stringify(categoryIds)
- )}`,
+ label: "Data Catalog",
+ path: `/data?${initialParams.toString()}`,
},
];
- const encodedTrail = encodeURIComponent(JSON.stringify(newBreadcrumb));
- setBreadcrumbTrail(newBreadcrumb);
+ setBreadcrumbTrail(initialBreadcrumb);
+ }
+ }, [data, categoryIds, searchParams]);
- const params = new URLSearchParams(location.search);
- params.set("categoryIds", encodeURIComponent(JSON.stringify(categoryIds)));
- params.set("breadcrumb", encodedTrail);
- navigate(`${location.pathname}?${params.toString()}`);
+ // Update display data only when new data is fully loaded
+ useEffect(() => {
+ if (!isLoading && data) {
+ setDisplayData(data);
+ setLoadingCardId(null);
}
- }, [data, categoryIdsParam]);
+ }, [isLoading, data]);
const handleCategoryClick = (category) => {
+ // Prevent clicking while loading
+ if (isLoading) return;
+
const categoryIdsArray = category.categoryIds; // full array
const categoryName = category.name;
+ const cardId = category.categoryIds.join("-");
if (!breadcrumbTrail.find((b) => b.label === categoryName)) {
+ setLoadingCardId(cardId);
+
+ const breadcrumbParams = new URLSearchParams();
+ breadcrumbParams.set("categoryIds", JSON.stringify(categoryIdsArray));
+
const newBreadcrumb = [
...breadcrumbTrail,
{
label: formatText({ name: categoryName }) || "Category",
- path: `/datasets?categoryIds=${encodeURIComponent(
- JSON.stringify(categoryIdsArray)
- )}`,
+ path: `/data?${breadcrumbParams.toString()}`,
},
];
- const encodedTrail = encodeURIComponent(JSON.stringify(newBreadcrumb));
+ const jsonTrail = JSON.stringify(newBreadcrumb);
setBreadcrumbTrail(newBreadcrumb);
+ setLoadingCardId(cardId);
- const params = new URLSearchParams(location.search);
- params.set("categoryIds", encodeURIComponent(JSON.stringify(categoryIdsArray)));
- params.set("breadcrumb", encodedTrail);
+ const params = new URLSearchParams(window.location.search);
+ params.set("categoryIds", JSON.stringify(categoryIdsArray));
+ params.set("breadcrumb", jsonTrail);
navigate(`${location.pathname}?${params.toString()}`);
}
};
const handleDatasetClick = (dataset) => {
+ // Prevent clicking while loading
+ if (isLoading) return;
+
const datasetName = dataset.name || formatText({ name: dataset.name });
if (!breadcrumbTrail.find((b) => b.label === datasetName)) {
+ setLoadingCardId(datasetName);
+
+ const breadcrumbParams = new URLSearchParams();
+ breadcrumbParams.set("datasetName", datasetName);
+ breadcrumbParams.set("categoryIds", JSON.stringify(categoryIds));
+
const newBreadcrumb = [
...breadcrumbTrail,
{
label: datasetName,
- path: `/datasets?datasetName=${datasetName}&categoryIds=${encodeURIComponent(
- JSON.stringify(categoryIds)
- )}`,
+ path: `/data?${breadcrumbParams.toString()}`,
},
];
- const encodedTrail = encodeURIComponent(JSON.stringify(newBreadcrumb));
+ const jsonTrail = JSON.stringify(newBreadcrumb);
setBreadcrumbTrail(newBreadcrumb);
+ setLoadingCardId(datasetName);
- const params = new URLSearchParams(location.search);
+ const params = new URLSearchParams(window.location.search);
params.set("datasetName", datasetName);
- params.set("categoryIds", encodeURIComponent(JSON.stringify(categoryIds)));
- params.set("breadcrumb", encodedTrail);
+ params.set("categoryIds", JSON.stringify(categoryIds));
+ params.set("breadcrumb", jsonTrail);
navigate(`${location.pathname}?${params.toString()}`);
}
@@ -112,17 +146,21 @@ export default function DataPage({ setExternalDateRange }) {
const handleBreadcrumbClick = (index, item) => {
const url = new URL(item.path, window.location.origin);
const newTrail = breadcrumbTrail.slice(0, index + 1);
- const encodedTrail = encodeURIComponent(JSON.stringify(newTrail));
setBreadcrumbTrail(newTrail);
- const params = new URLSearchParams(location.search);
+ // Clear states when navigating back
+ setSelectedDataset(null);
+ setLoadingCardId(null);
+
+ const params = new URLSearchParams(window.location.search);
const pidParam = url.searchParams.get("categoryIds");
const datasetName = url.searchParams.get("datasetName");
if (pidParam) {
try {
- const decodedPid = JSON.parse(decodeURIComponent(pidParam));
- params.set("categoryIds", encodeURIComponent(JSON.stringify(decodedPid)));
+ // pidParam is already decoded by URLSearchParams.get
+ const decodedPid = JSON.parse(pidParam);
+ params.set("categoryIds", JSON.stringify(decodedPid));
} catch {
params.set("categoryIds", pidParam);
}
@@ -133,7 +171,7 @@ export default function DataPage({ setExternalDateRange }) {
if (datasetName) params.set("datasetName", datasetName);
else params.delete("datasetName");
- params.set("breadcrumb", encodedTrail);
+ params.set("breadcrumb", JSON.stringify(newTrail));
navigate(`${location.pathname}?${params.toString()}`);
};
@@ -146,29 +184,48 @@ export default function DataPage({ setExternalDateRange }) {
setSelectedDatasets={() => setSelectedDataset(null)}
/>
)}
+ {isError && (
+
+
+
+ {error?.message || "An unexpected error occurred while fetching the data."}
+
+
- {isLoading ? (
-
Loading...
- ) : selectedDataset ? (
+ )}
+
+ {isLoading && !displayData && (
+
+
+
+ Loading Data...
+
+
+ )}
+
+ {selectedDataset ? (
) : (
<>
- {data?.categories?.length > 0 && (
+ {!isError && displayData?.categories?.length > 0 && (
<>
Categories
- {data.categories.map((item) => (
+ {displayData.categories.map((item) => (
handleCategoryClick(item)}
- className="cursor-pointer bg-background shadow-2xs relative w-full h-[100px] border border-border rounded-2xl p-4 flex items-center justify-between hover:bg-border/10 transition"
+ className={`bg-background shadow-2xs relative w-full h-[100px] border border-border rounded-2xl p-4 flex items-center justify-between transition ${isLoading
+ ? "opacity-60 cursor-not-allowed"
+ : "cursor-pointer hover:bg-border/10"
+ }`}
>
@@ -176,29 +233,35 @@ export default function DataPage({ setExternalDateRange }) {
{formatText({ name: item.name })}
+ {loadingCardId === item.categoryIds.join("-") && (
+
+ )}
))}
>
)}
- {data?.datasets?.length > 0 && (
+ {displayData?.datasets?.length > 0 && (
-
+
Datasets
- {data.datasets.map((dataset) => (
+ {displayData.datasets.map((dataset) => (
handleDatasetClick(dataset)}
- className="cursor-pointer shadow-2xs w-full h-[90px] border border-border rounded-xl p-4 flex items-center bg-dataset-card bg-background transition"
+ className={`shadow-2xs w-full h-[90px] border border-border rounded-2xl p-4 flex items-center justify-between bg-dataset-card bg-background transition ${isLoading
+ ? "opacity-60 cursor-not-allowed"
+ : "cursor-pointer"
+ }`}
>
-
-
-
+
@@ -208,7 +271,7 @@ export default function DataPage({ setExternalDateRange }) {
)}
- {data?.categories?.length === 0 && data?.datasets?.length === 0 && (
+ {displayData?.categories?.length === 0 && displayData?.datasets?.length === 0 && (
No categories or datasets found for this level.
diff --git a/src/pages/HomePage/screens/HomePage.jsx b/src/pages/HomePage/screens/HomePage.jsx
index e8b4c2b..ce28c85 100644
--- a/src/pages/HomePage/screens/HomePage.jsx
+++ b/src/pages/HomePage/screens/HomePage.jsx
@@ -195,7 +195,7 @@ export default function HomePage() {
{isExpanded ? : }
-
+
{isExpanded && Share feedback }
diff --git a/src/pages/LandingPage/components/textLogo.jsx b/src/pages/LandingPage/components/textLogo.jsx
index 3f09923..963b3b3 100644
--- a/src/pages/LandingPage/components/textLogo.jsx
+++ b/src/pages/LandingPage/components/textLogo.jsx
@@ -1,6 +1,7 @@
import React from "react";
import { Link } from "react-router-dom";
import { useThemeContext } from "../../../context/themeContext";
+import Version from "../../../components/version";
export default function TextLogo({ isExpanded }) {
const { isDark } = useThemeContext();
@@ -10,7 +11,7 @@ export default function TextLogo({ isExpanded }) {
{isExpanded ? (
- OpenGINXplore
+ OpenGINXplore
) : isDark ? (
diff --git a/src/pages/OrganizationPage/components/DepartmentTab.jsx b/src/pages/OrganizationPage/components/DepartmentTab.jsx
index 41a6d91..c5f6840 100644
--- a/src/pages/OrganizationPage/components/DepartmentTab.jsx
+++ b/src/pages/OrganizationPage/components/DepartmentTab.jsx
@@ -295,15 +295,29 @@ const DepartmentTab = ({ selectedDate, ministryId }) => {
History
- {dep.hasData && (
-
- Data
-
- ) }
+ {dep.hasData && (() => {
+ const pathParams = new URLSearchParams();
+ pathParams.set("categoryIds", JSON.stringify([dep.id]));
+
+ const outerParams = new URLSearchParams();
+ outerParams.set("categoryIds", JSON.stringify([dep.id]));
+ outerParams.set("breadcrumb", JSON.stringify([
+ {
+ label: dep.name,
+ path: `/data?${pathParams.toString()}`,
+ },
+ ]));
+
+ return (
+
+ Data
+
+ );
+ })()}
);
diff --git a/src/services/xploredataServices.jsx b/src/services/xploredataServices.jsx
index 8b64448..88f2a01 100644
--- a/src/services/xploredataServices.jsx
+++ b/src/services/xploredataServices.jsx
@@ -3,35 +3,56 @@ import axios from "../lib/axios";
const GI_SERVICE_URL = "/v1/data";
export const getDataCatalog = async ({ categoryIds = [], signal }) => {
- const { data } = await axios.post(
- `${GI_SERVICE_URL}/data-catalog`,
- { categoryIds },
- { signal }
- );
- return data;
+ try {
+ const { data } = await axios.post(
+ `${GI_SERVICE_URL}/data-catalog`,
+ { categoryIds },
+ { signal }
+ );
+ return data;
+ } catch (error) {
+ if (error.name === 'CanceledError') throw error;
+ throw new Error("An unexpected error occurred while fetching the data.");
+ }
};
+
export const getAvailableYearsForDataset = async ({ datasetIds = [], signal }) => {
- const { data } = await axios.post(
- `${GI_SERVICE_URL}/datasets/years`,
- { datasetIds },
- { signal }
- );
- return data;
+ try {
+ const { data } = await axios.post(
+ `${GI_SERVICE_URL}/datasets/years`,
+ { datasetIds },
+ { signal }
+ );
+ return data;
+ } catch (error) {
+ if (error.name === 'CanceledError') throw error;
+ throw new Error("Failed to load available years for the selected dataset.");
+ }
};
export const getDatasetById = async ({ datasetId, signal }) => {
- const { data } = await axios.get(
- `${GI_SERVICE_URL}/datasets/${datasetId}/data`,
- { signal }
- );
- return data;
+ try {
+ const { data } = await axios.get(
+ `${GI_SERVICE_URL}/datasets/${datasetId}/data`,
+ { signal }
+ );
+ return data;
+ } catch (error) {
+ if (error.name === 'CanceledError') throw error;
+ throw new Error("Failed to load dataset content. Please try again later.");
+ }
};
export const getRootOrganization = async ({ datasetId, signal }) => {
- const { data } = await axios.get(
- `${GI_SERVICE_URL}/datasets/${datasetId}/root`,
- { signal }
- );
- return data;
+ try {
+ const { data } = await axios.get(
+ `${GI_SERVICE_URL}/datasets/${datasetId}/root`,
+ { signal }
+ );
+ return data;
+ } catch (error) {
+ if (error.name === 'CanceledError') throw error;
+ throw new Error("Failed to load publisher information.");
+ }
};