added iip calculations
This commit is contained in:
parent
20c1fc61d9
commit
50bb4dd59b
671
ipi-survey-platform/package-lock.json
generated
671
ipi-survey-platform/package-lock.json
generated
@ -8,6 +8,10 @@
|
||||
"name": "ipi-survey-platform",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@mui/icons-material": "^7.3.6",
|
||||
"@mui/material": "^7.3.6",
|
||||
"axios": "^1.12.2",
|
||||
"chart.js": "^4.5.1",
|
||||
"lucide-react": "^0.548.0",
|
||||
@ -53,7 +57,6 @@
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.27.1",
|
||||
@ -109,7 +112,6 @@
|
||||
"version": "7.28.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
|
||||
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.28.3",
|
||||
@ -143,7 +145,6 @@
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
||||
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@ -153,7 +154,6 @@
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
|
||||
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.27.1",
|
||||
@ -195,7 +195,6 @@
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@ -205,7 +204,6 @@
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
||||
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@ -239,7 +237,6 @@
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
|
||||
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.4"
|
||||
@ -283,11 +280,19 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
@ -302,7 +307,6 @@
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
|
||||
"integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
@ -321,7 +325,6 @@
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
|
||||
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
@ -331,6 +334,158 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin": {
|
||||
"version": "11.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
||||
"integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.16.7",
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/hash": "^0.9.2",
|
||||
"@emotion/memoize": "^0.9.0",
|
||||
"@emotion/serialize": "^1.3.3",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"convert-source-map": "^1.5.0",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"find-root": "^1.1.0",
|
||||
"source-map": "^0.5.7",
|
||||
"stylis": "4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin/node_modules/convert-source-map": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
||||
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/cache": {
|
||||
"version": "11.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
|
||||
"integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.9.0",
|
||||
"@emotion/sheet": "^1.4.0",
|
||||
"@emotion/utils": "^1.4.2",
|
||||
"@emotion/weak-memoize": "^0.4.0",
|
||||
"stylis": "4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/hash": {
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
|
||||
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/is-prop-valid": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz",
|
||||
"integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/memoize": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
|
||||
"integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/react": {
|
||||
"version": "11.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
||||
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/cache": "^11.14.0",
|
||||
"@emotion/serialize": "^1.3.3",
|
||||
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
|
||||
"@emotion/utils": "^1.4.2",
|
||||
"@emotion/weak-memoize": "^0.4.0",
|
||||
"hoist-non-react-statics": "^3.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/serialize": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
|
||||
"integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/hash": "^0.9.2",
|
||||
"@emotion/memoize": "^0.9.0",
|
||||
"@emotion/unitless": "^0.10.0",
|
||||
"@emotion/utils": "^1.4.2",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/sheet": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
|
||||
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/styled": {
|
||||
"version": "11.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
|
||||
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/is-prop-valid": "^1.3.0",
|
||||
"@emotion/serialize": "^1.3.3",
|
||||
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
|
||||
"@emotion/utils": "^1.4.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.0.0-rc.0",
|
||||
"react": ">=16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/unitless": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
|
||||
"integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/use-insertion-effect-with-fallbacks": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
|
||||
"integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/utils": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
|
||||
"integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/weak-memoize": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
|
||||
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz",
|
||||
@ -984,7 +1139,6 @@
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
@ -1006,7 +1160,6 @@
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
@ -1016,14 +1169,12 @@
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
@ -1036,6 +1187,261 @@
|
||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mui/core-downloads-tracker": {
|
||||
"version": "7.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.6.tgz",
|
||||
"integrity": "sha512-QaYtTHlr8kDFN5mE1wbvVARRKH7Fdw1ZuOjBJcFdVpfNfRYKF3QLT4rt+WaB6CKJvpqxRsmEo0kpYinhH5GeHg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/icons-material": {
|
||||
"version": "7.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.6.tgz",
|
||||
"integrity": "sha512-0FfkXEj22ysIq5pa41A2NbcAhJSvmcZQ/vcTIbjDsd6hlslG82k5BEBqqS0ZJprxwIL3B45qpJ+bPHwJPlF7uQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mui/material": "^7.3.6",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material": {
|
||||
"version": "7.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.6.tgz",
|
||||
"integrity": "sha512-R4DaYF3dgCQCUAkr4wW1w26GHXcf5rCmBRHVBuuvJvaGLmZdD8EjatP80Nz5JCw0KxORAzwftnHzXVnjR8HnFw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@mui/core-downloads-tracker": "^7.3.6",
|
||||
"@mui/system": "^7.3.6",
|
||||
"@mui/types": "^7.4.9",
|
||||
"@mui/utils": "^7.3.6",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@types/react-transition-group": "^4.4.12",
|
||||
"clsx": "^2.1.1",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^19.2.0",
|
||||
"react-transition-group": "^4.4.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@mui/material-pigment-css": "^7.3.6",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
},
|
||||
"@mui/material-pigment-css": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material/node_modules/react-is": {
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz",
|
||||
"integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mui/private-theming": {
|
||||
"version": "7.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.6.tgz",
|
||||
"integrity": "sha512-Ws9wZpqM+FlnbZXaY/7yvyvWQo1+02Tbx50mVdNmzWEi51C51y56KAbaDCYyulOOBL6BJxuaqG8rNNuj7ivVyw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@mui/utils": "^7.3.6",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/styled-engine": {
|
||||
"version": "7.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.6.tgz",
|
||||
"integrity": "sha512-+wiYbtvj+zyUkmDB+ysH6zRjuQIJ+CM56w0fEXV+VDNdvOuSywG+/8kpjddvvlfMLsaWdQe5oTuYGBcodmqGzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@emotion/cache": "^11.14.0",
|
||||
"@emotion/serialize": "^1.3.3",
|
||||
"@emotion/sheet": "^1.4.0",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/system": {
|
||||
"version": "7.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.6.tgz",
|
||||
"integrity": "sha512-8fehAazkHNP1imMrdD2m2hbA9sl7Ur6jfuNweh5o4l9YPty4iaZzRXqYvBCWQNwFaSHmMEj2KPbyXGp7Bt73Rg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@mui/private-theming": "^7.3.6",
|
||||
"@mui/styled-engine": "^7.3.6",
|
||||
"@mui/types": "^7.4.9",
|
||||
"@mui/utils": "^7.3.6",
|
||||
"clsx": "^2.1.1",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/types": {
|
||||
"version": "7.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.9.tgz",
|
||||
"integrity": "sha512-dNO8Z9T2cujkSIaCnWwprfeKmTWh97cnjkgmpFJ2sbfXLx8SMZijCYHOtP/y5nnUb/Rm2omxbDMmtUoSaUtKaw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/utils": {
|
||||
"version": "7.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.6.tgz",
|
||||
"integrity": "sha512-jn+Ba02O6PiFs7nKva8R2aJJ9kJC+3kQ2R0BbKNY3KQQ36Qng98GnPRFTlbwYTdMD6hLEBKaMLUktyg/rTfd2w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@mui/types": "^7.4.9",
|
||||
"@types/prop-types": "^15.7.15",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^19.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/utils/node_modules/react-is": {
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz",
|
||||
"integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.0.tgz",
|
||||
@ -1795,11 +2201,22 @@
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
||||
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
@ -1815,6 +2232,15 @@
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-transition-group": {
|
||||
"version": "4.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
||||
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/use-sync-external-store": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||
@ -1955,6 +2381,21 @@
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-macros": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
|
||||
"integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"cosmiconfig": "^7.0.0",
|
||||
"resolve": "^1.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@ -2157,6 +2598,31 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
||||
"integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/parse-json": "^4.0.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"parse-json": "^5.0.0",
|
||||
"path-type": "^4.0.0",
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/cosmiconfig/node_modules/yaml": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@ -2175,7 +2641,6 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
@ -2347,6 +2812,16 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@ -2382,6 +2857,15 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
|
||||
"integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
@ -2737,6 +3221,12 @@
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/find-root": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||
@ -3047,6 +3537,27 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
@ -3106,7 +3617,6 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jsesc": "bin/jsesc"
|
||||
@ -3121,6 +3631,12 @@
|
||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
@ -3407,6 +3923,12 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lines-and-columns": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
@ -3650,6 +4172,24 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-json": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"error-ex": "^1.3.1",
|
||||
"json-parse-even-better-errors": "^2.3.0",
|
||||
"lines-and-columns": "^1.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
@ -3668,11 +4208,25 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
@ -3915,6 +4469,22 @@
|
||||
"react-dom": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.6.0",
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.5.0.tgz",
|
||||
@ -3967,6 +4537,26 @@
|
||||
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.11",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.16.1",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
@ -4061,6 +4651,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@ -4083,6 +4682,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/stylis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
|
||||
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
@ -4095,6 +4700,18 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz",
|
||||
@ -4355,6 +4972,24 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
||||
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
||||
@ -10,6 +10,10 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@mui/icons-material": "^7.3.6",
|
||||
"@mui/material": "^7.3.6",
|
||||
"axios": "^1.12.2",
|
||||
"chart.js": "^4.5.1",
|
||||
"lucide-react": "^0.548.0",
|
||||
|
||||
@ -15,6 +15,8 @@ import ForgotPassword from '@/pages/ForgotPassword/ForgotPassword';
|
||||
import PublicContactForm from '@/pages/PublicContactForm';
|
||||
import EditCompanyProfile from '@/pages/Admin/configuration/EditCompanyProfile';
|
||||
import Navbar from './pages/LandingPage/component/Navbar';
|
||||
import ManufacturingIndex from '@/pages/ManufacturingIndex/ManufacturingIndex';
|
||||
|
||||
const RequireRole = ({ allowedRoles = [], children }) => {
|
||||
let role;
|
||||
|
||||
@ -241,6 +243,14 @@ function App() {
|
||||
} />
|
||||
{/* /> */}
|
||||
{/* </Route> */}
|
||||
<Route
|
||||
path="/manufacturing-index"
|
||||
element={
|
||||
<RequireRole allowedRoles={['Admin', 'EstablishmentUser']}>
|
||||
<ManufacturingIndex />
|
||||
</RequireRole>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin/users"
|
||||
element={
|
||||
|
||||
@ -1871,4 +1871,7 @@ const location = useLocation();
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductData;
|
||||
export default ProductData;
|
||||
|
||||
|
||||
|
||||
|
||||
@ -49,7 +49,10 @@ const Badge = ({ children, status }) => {
|
||||
const variant = STATUS_VARIANTS[status] || { background: '#F2F4F7', text: '#475467', border: '#EAECF0' };
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex h-6 min-w-[67px] items-center justify-center rounded px-2 text-xs font-medium border ${variant.background} ${variant.text} ${variant.border}`}
|
||||
// className={`inline-flex h-6 min-w-[67px] items-center justify-center rounded px-2 text-xs font-medium border ${variant.background} ${variant.text} ${variant.border}`}
|
||||
className="inline-flex h-6 min-w-[67px] items-center justify-center rounded px-2 text-xs font-medium"
|
||||
style={{ background: variant.background, color: variant.text, border: `1px solid ${variant.border}` }}
|
||||
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
|
||||
@ -1,340 +1,626 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
TextField,
|
||||
MenuItem,
|
||||
Button,
|
||||
import React, { useState, useMemo, useEffect } from 'react';
|
||||
import {
|
||||
IconButton,
|
||||
Chip,
|
||||
Divider,
|
||||
ToggleButtonGroup,
|
||||
ToggleButton,
|
||||
Avatar,
|
||||
Menu,
|
||||
MenuItem as MuiMenuItem,
|
||||
ListItemIcon,
|
||||
Typography
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent
|
||||
} from '@mui/material';
|
||||
import {
|
||||
ArrowRightAlt as ArrowRightAltIcon,
|
||||
FileDownload as FileDownloadIcon,
|
||||
import { SelectField as BaseSelectField } from '@/components/common/FormControls';
|
||||
import Table from '@/components/common/Table';
|
||||
import {
|
||||
FiberManualRecord as FiberManualRecordIcon,
|
||||
NotificationsNone as NotificationsIcon,
|
||||
HelpOutline as HelpIcon,
|
||||
Settings as SettingsIcon,
|
||||
Logout as LogoutIcon,
|
||||
Person as PersonIcon,
|
||||
KeyboardArrowDown as KeyboardArrowDownIcon
|
||||
KeyboardArrowRight as KeyboardArrowRightIcon
|
||||
} from '@mui/icons-material';
|
||||
import Card from '@/components/common/Card';
|
||||
import { getManufacturingIndex, getManufacturingMonthlyOverview } from '@/services/manufacturingIndex/ManufacturingIndex';
|
||||
|
||||
// Import popup components
|
||||
import ManufacturingTotal from './ManufacturingIndexPopup/ManufacturingTotal';
|
||||
import ISIC2Digit from './ManufacturingIndexPopup/ISIC2Digit';
|
||||
import ISIC3Digit from './ManufacturingIndexPopup/ISIC3Digit';
|
||||
import ISIC4Digit from './ManufacturingIndexPopup/ISIC4Digit';
|
||||
|
||||
// Custom components
|
||||
const SelectField = (props) => (
|
||||
<BaseSelectField
|
||||
{...props}
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'&.Mui-focused fieldset, &.Mui-focused:hover fieldset': {
|
||||
borderColor: '#8E5B2D',
|
||||
},
|
||||
'&:hover .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: props.value ? '#8E5B2D' : 'rgba(0, 0, 0, 0.23)',
|
||||
},
|
||||
},
|
||||
...props.sx,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const ManufacturingIndex = () => {
|
||||
// State management
|
||||
const [year, setYear] = useState('2025');
|
||||
const [selectedMonth, setSelectedMonth] = useState('Oct 2025');
|
||||
const [view, setView] = useState('manufacturing');
|
||||
const [searchMonth, setSearchMonth] = useState('');
|
||||
const [selectedMonth, setSelectedMonth] = useState(null);
|
||||
const [filteredMonths, setFilteredMonths] = useState([]);
|
||||
const [activeTab, setActiveTab] = useState('manufacturing');
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [monthlyOverviewData, setMonthlyOverviewData] = useState(null);
|
||||
const [isPopupOpen, setIsPopupOpen] = useState(false);
|
||||
const [apiLoading, setApiLoading] = useState(false);
|
||||
|
||||
const years = ['2025', '2024', '2023'];
|
||||
const months = [
|
||||
{ month: 'Oct 2025', index: '114.8', mom: '+0.9', yoy: '+3.2', generated: '12 Nov 2025, 09:15', status: 'Completed' },
|
||||
{ month: 'Sep 2025', index: '113.9', mom: '-0.2', yoy: '+2.8', generated: '11 Oct 2025, 09:15', status: 'Completed' },
|
||||
{ month: 'Aug 2025', index: '114.1', mom: '+0.5', yoy: '+3.0', generated: '11 Sep 2025, 09:15', status: 'Completed' },
|
||||
{ month: 'Jul 2025', index: '113.6', mom: '+0.3', yoy: '+2.9', generated: '11 Aug 2025, 09:15', status: 'Completed' },
|
||||
{ month: 'Jun 2025', index: '113.3', mom: '+0.7', yoy: '+2.8', generated: '11 Jul 2025, 09:15', status: 'Completed' },
|
||||
];
|
||||
const [pagination, setPagination] = useState({
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
totalItems: 0
|
||||
});
|
||||
|
||||
const handleViewChange = (event, newView) => {
|
||||
if (newView !== null) {
|
||||
setView(newView);
|
||||
// Month name to number mapping
|
||||
const monthMap = {
|
||||
'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4,
|
||||
'May': 5, 'Jun': 6, 'Jul': 7, 'Aug': 8,
|
||||
'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12
|
||||
};
|
||||
|
||||
// Fetch data from API
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
console.log('Fetching manufacturing index data...');
|
||||
|
||||
const response = await getManufacturingIndex();
|
||||
|
||||
console.log('RAW API RESPONSE:', response);
|
||||
console.log('Response type:', typeof response);
|
||||
console.log('Response keys:', Object.keys(response || {}));
|
||||
|
||||
// Handle different response structures
|
||||
let dataArray = [];
|
||||
|
||||
// Case 1: Response is already an array
|
||||
if (Array.isArray(response)) {
|
||||
console.log('Response is array directly');
|
||||
dataArray = response;
|
||||
}
|
||||
// Case 2: Response has data property which is array
|
||||
else if (response && response.data && Array.isArray(response.data)) {
|
||||
console.log('Response.data is array');
|
||||
dataArray = response.data;
|
||||
}
|
||||
// Case 3: Response.data is object with nested data
|
||||
else if (response && response.data && typeof response.data === 'object') {
|
||||
console.log('Response.data is object');
|
||||
// Try to extract array from common structures
|
||||
if (Array.isArray(response.data.manufacturingIndex) || Array.isArray(response.data.data)) {
|
||||
dataArray = response.data.manufacturingIndex || response.data.data || [];
|
||||
} else {
|
||||
// Convert object values to array
|
||||
dataArray = Object.values(response.data);
|
||||
}
|
||||
}
|
||||
// Case 4: Response has nested structure
|
||||
else if (response && response.data && response.data.data && Array.isArray(response.data.data)) {
|
||||
console.log('Response.data.data is array');
|
||||
dataArray = response.data.data;
|
||||
}
|
||||
else {
|
||||
console.error('Unexpected API response structure:', response);
|
||||
// Try to extract any array from response
|
||||
const findArray = (obj) => {
|
||||
if (Array.isArray(obj)) return obj;
|
||||
if (typeof obj === 'object' && obj !== null) {
|
||||
for (const key in obj) {
|
||||
if (Array.isArray(obj[key])) {
|
||||
return obj[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
dataArray = findArray(response);
|
||||
}
|
||||
|
||||
console.log('Final dataArray:', dataArray);
|
||||
|
||||
// If no data found
|
||||
if (!dataArray || dataArray.length === 0) {
|
||||
console.warn('No data array found, using empty array');
|
||||
dataArray = [];
|
||||
}
|
||||
|
||||
// Format the data
|
||||
const formattedData = dataArray.map((item, index) => {
|
||||
// Create a date object from reference_date or other date fields
|
||||
let dateObj = new Date();
|
||||
let year = 2025;
|
||||
let monthNumber = 1;
|
||||
|
||||
if (item.reference_date) {
|
||||
dateObj = new Date(item.reference_date);
|
||||
} else if (item.date) {
|
||||
dateObj = new Date(item.date);
|
||||
} else if (item.referenceDate) {
|
||||
dateObj = new Date(item.referenceDate);
|
||||
} else if (item.year && item.month) {
|
||||
// If year and month are separate fields
|
||||
year = item.year;
|
||||
monthNumber = item.month;
|
||||
dateObj = new Date(year, monthNumber - 1, 1);
|
||||
}
|
||||
|
||||
const monthName = dateObj.toLocaleString('en-US', { month: 'short' });
|
||||
year = item.year || dateObj.getFullYear() || 2025;
|
||||
monthNumber = item.month || (dateObj.getMonth() + 1) || 1;
|
||||
|
||||
return {
|
||||
id: item.id || `${year}-${monthNumber}` || `item-${index}`,
|
||||
month: `${monthName} ${year}`,
|
||||
monthName: monthName,
|
||||
year: year,
|
||||
monthNumber: monthNumber,
|
||||
index: item.manufacturing_index?.toString() || item.index?.toString() || item.manufacturingIndex?.toString() || 'N/A',
|
||||
mom: item.mom_change || item.mom || item.momChange ?
|
||||
(parseFloat(item.mom_change || item.mom || item.momChange) > 0 ?
|
||||
`+${item.mom_change || item.mom || item.momChange}` :
|
||||
(item.mom_change || item.mom || item.momChange).toString()) :
|
||||
'N/A',
|
||||
yoy: item.yoy_change || item.yoy || item.yoyChange ?
|
||||
(parseFloat(item.yoy_change || item.yoy || item.yoyChange) > 0 ?
|
||||
`+${item.yoy_change || item.yoy || item.yoyChange}` :
|
||||
(item.yoy_change || item.yoy || item.yoyChange).toString()) :
|
||||
'N/A',
|
||||
generated: item.generated_on || item.generatedOn || item.generated_at ?
|
||||
new Date(item.generated_on || item.generatedOn || item.generated_at).toLocaleString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZone: 'Asia/Dubai',
|
||||
timeZoneName: 'short'
|
||||
}) : 'N/A',
|
||||
status: item.status || 'Completed',
|
||||
rawData: item
|
||||
};
|
||||
});
|
||||
|
||||
console.log('Formatted Data:', formattedData);
|
||||
|
||||
setFilteredMonths(formattedData);
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
totalItems: formattedData.length
|
||||
}));
|
||||
|
||||
if (formattedData.length > 0) {
|
||||
setSelectedMonth(formattedData[0]);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching manufacturing index:', err);
|
||||
console.error('Error details:', {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
response: err.response
|
||||
});
|
||||
setError('Failed to load manufacturing data. Please try again later.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Header with logo only */}
|
||||
<header className="bg-white shadow-sm">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="h-16 flex items-center">
|
||||
<img
|
||||
className="h-10 w-auto"
|
||||
src="/assets/images/FCSCLogo.svg"
|
||||
alt="FCSC IPI"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
{/* Main content */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center mb-4 gap-4">
|
||||
<h1 className="text-2xl font-semibold text-gray-900">IIP Manufacturing Index - Monthly Overview</h1>
|
||||
{/* <Button
|
||||
variant="contained"
|
||||
className="bg-primary-600 hover:bg-primary-700 text-white"
|
||||
// ARROW CLICK செய்தால் இந்த function வேலை செய்யும்
|
||||
const handleMonthSelect = async (row) => {
|
||||
try {
|
||||
console.log('Selected row for API call:', row);
|
||||
|
||||
const yearNumber = row.year;
|
||||
const monthNumber = row.monthNumber;
|
||||
|
||||
console.log('API Parameters:', { year: yearNumber, month: monthNumber });
|
||||
|
||||
setSelectedMonth(row);
|
||||
setActiveTab('manufacturing');
|
||||
setIsPopupOpen(true);
|
||||
setApiLoading(true);
|
||||
|
||||
// API CALL
|
||||
const apiResponse = await getManufacturingMonthlyOverview(yearNumber, monthNumber);
|
||||
|
||||
console.log('Full API Response:', apiResponse);
|
||||
|
||||
// Extract data from the response structure
|
||||
// Note: Your API returns {status, message, manufacturing_total, isic_2digit, isic_3digit, isic_4digit}
|
||||
const responseData = apiResponse.data || apiResponse;
|
||||
|
||||
console.log('Response Data:', responseData);
|
||||
|
||||
// Transform data based on YOUR API structure
|
||||
const transformedData = {
|
||||
manufacturing: responseData?.manufacturing_total || {},
|
||||
isic2: Array.isArray(responseData?.isic_2digit) ? responseData.isic_2digit : [],
|
||||
isic3: Array.isArray(responseData?.isic_3digit) ? responseData.isic_3digit : [],
|
||||
isic4: Array.isArray(responseData?.isic_4digit) ? responseData.isic_4digit : []
|
||||
};
|
||||
|
||||
console.log('Transformed Data for Popups:', transformedData);
|
||||
|
||||
// 4 popups-க்கும் data set பண்ணு
|
||||
setMonthlyOverviewData(transformedData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in handleMonthSelect:', error);
|
||||
setError(`API Error: ${error.message || 'Failed to load detailed data'}`);
|
||||
|
||||
// Fallback: empty data set
|
||||
setMonthlyOverviewData({
|
||||
manufacturing: {},
|
||||
isic2: [],
|
||||
isic3: [],
|
||||
isic4: []
|
||||
});
|
||||
} finally {
|
||||
setApiLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClosePopup = () => {
|
||||
setIsPopupOpen(false);
|
||||
setMonthlyOverviewData(null);
|
||||
};
|
||||
|
||||
const handleYearChange = (e) => {
|
||||
const selectedYear = e.target.value;
|
||||
setYear(selectedYear);
|
||||
filterMonths(selectedYear, searchMonth);
|
||||
};
|
||||
|
||||
const handleSearchMonth = (e) => {
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
setSearchMonth(searchTerm);
|
||||
filterMonths(year, searchTerm);
|
||||
};
|
||||
|
||||
const filterMonths = (selectedYear, searchTerm) => {
|
||||
const filtered = filteredMonths.filter(month => {
|
||||
const matchesYear = month.month.endsWith(selectedYear);
|
||||
const matchesSearch = month.month.toLowerCase().includes(searchTerm);
|
||||
return matchesYear && (searchTerm === '' || matchesSearch);
|
||||
});
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
currentPage: 1,
|
||||
totalItems: filtered.length
|
||||
}));
|
||||
return filtered;
|
||||
};
|
||||
|
||||
const handleExportCSV = () => {
|
||||
const headers = ['Month', 'Index', 'MoM Change', 'YoY Change', 'Generated On', 'Status'];
|
||||
const csvContent = [
|
||||
headers.join(','),
|
||||
...filteredMonths.map(month => [
|
||||
`"${month.month}"`,
|
||||
month.index,
|
||||
month.mom,
|
||||
month.yoy,
|
||||
`"${month.generated}"`,
|
||||
`"${month.status}"`
|
||||
].join(','))
|
||||
].join('\n');
|
||||
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.setAttribute('download', `manufacturing_index_${year}.csv`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
const downloadIconSrc = '/assets/images/DownloadSimple.svg';
|
||||
const years = ['2025', '2024', '2023'];
|
||||
const selectedMonthData = selectedMonth || {};
|
||||
|
||||
// Show loading state
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#92722A] mx-auto mb-4"></div>
|
||||
<p className="text-gray-600">Loading manufacturing data...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show error state
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
|
||||
<div className="text-center p-6 max-w-md mx-auto bg-white rounded-lg shadow-md">
|
||||
<div className="text-red-500 mb-4">
|
||||
<svg className="w-12 h-12 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Error Loading Data</h3>
|
||||
<p className="text-gray-600 mb-4">{error}</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="px-4 py-2 bg-[#92722A] text-white rounded-md hover:bg-[#7a5e22] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#92722A]"
|
||||
>
|
||||
Edit with ChatGPT
|
||||
</Button> */}
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Card className="p-6 mb-6">
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
|
||||
<div>
|
||||
<div className="flex items-center mb-2">
|
||||
{/* <Chip
|
||||
label="IPI ADMIN"
|
||||
size="small"
|
||||
className="bg-primary-50 text-primary-700 mr-2 font-medium"
|
||||
icon={<FiberManualRecordIcon className="text-primary-500 text-xs" />}
|
||||
/> */}
|
||||
<h2 className="text-lg font-semibold text-gray-900">
|
||||
Manufacturing Index — Monthly overview
|
||||
</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100">
|
||||
{/* Popup Dialog */}
|
||||
<Dialog
|
||||
open={isPopupOpen}
|
||||
onClose={handleClosePopup}
|
||||
maxWidth="lg"
|
||||
fullWidth={true}
|
||||
className="relative"
|
||||
>
|
||||
<DialogTitle className="bg-[#F9F5E8] text-[#92722A] flex justify-between items-center pr-2">
|
||||
<span>Manufacturing Details - {selectedMonth?.month}</span>
|
||||
<IconButton
|
||||
onClick={handleClosePopup}
|
||||
size="small"
|
||||
className="absolute right-2 top-2 text-gray-500 hover:text-gray-700"
|
||||
sx={{
|
||||
'&:hover': {
|
||||
backgroundColor: 'transparent'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
<DialogContent className="p-0">
|
||||
<div className="p-6">
|
||||
{apiLoading ? (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="animate-spin rounded-full h-10 w-10 border-b-2 border-[#92722A]"></div>
|
||||
<span className="ml-3">Loading detailed data...</span>
|
||||
</div>
|
||||
<p className="text-gray-600 text-sm">
|
||||
Monthly manufacturing indices are generated automatically on the 11th of each month using the finalized Laspeyres item indices.
|
||||
Use this view to monitor trends and drill down into ISIC groups.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<div className="text-green-600 flex items-center justify-end text-sm">
|
||||
<FiberManualRecordIcon className="text-xs mr-1" />
|
||||
Next scheduled run: 11 Nov 2025, 02:00 GST
|
||||
) : (
|
||||
<div className="space-y-0 w-[1000px] max-w-[95vw] min-w-[800px]">
|
||||
{/* Tabs Header */}
|
||||
<div className="sticky top-0 z-10 bg-white pt-2">
|
||||
<div className="flex justify-between items-center border-b border-gray-200 pb-2">
|
||||
<div className="flex space-x-1">
|
||||
{['manufacturing', 'isic2', 'isic3', 'isic4'].map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
className={`px-4 py-2 text-sm font-medium ${
|
||||
activeTab === tab
|
||||
? 'text-[#92722A] border-b-2 border-[#92722A]'
|
||||
: 'text-medium-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
{tab === 'manufacturing' ? 'Manufacturing total' :
|
||||
tab === 'isic2' ? 'ISIC 2-digit' :
|
||||
tab === 'isic3' ? 'ISIC 3-digit' : 'ISIC 4-digit'}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="w-5"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
<div className="bg-white mt-2 rounded-lg shadow p-6 w-full">
|
||||
{activeTab === 'manufacturing' && (
|
||||
<ManufacturingTotal
|
||||
selectedMonthData={selectedMonthData}
|
||||
setActiveTab={setActiveTab}
|
||||
apiData={monthlyOverviewData?.manufacturing}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'isic2' && (
|
||||
<ISIC2Digit
|
||||
setActiveTab={setActiveTab}
|
||||
apiData={monthlyOverviewData?.isic2}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'isic3' && (
|
||||
<ISIC3Digit
|
||||
setActiveTab={setActiveTab}
|
||||
apiData={monthlyOverviewData?.isic3}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === 'isic4' && (
|
||||
<ISIC4Digit
|
||||
setActiveTab={setActiveTab}
|
||||
apiData={monthlyOverviewData?.isic4}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-500 text-xs mt-1">
|
||||
Formula: Manufacturing index = Σ (sector weightᵢ × group indexᵢ)
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Left Column */}
|
||||
<div className="lg:col-span-2">
|
||||
<Card className="p-6 mb-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">Manufacturing index — by reference month</h3>
|
||||
<p className="text-gray-600 mb-6">
|
||||
Filter by year and quickly jump to a month to see its ISIC breakdown.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-2 mb-6">
|
||||
<Chip
|
||||
label="Base year: 2022 = 100"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
className="text-gray-600 border-gray-300"
|
||||
/>
|
||||
<Chip
|
||||
label="Coverage: Manufacturing (ISIC C)"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
className="text-gray-600 border-gray-300"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-3 mb-6">
|
||||
<TextField
|
||||
select
|
||||
label="Year"
|
||||
value={year}
|
||||
onChange={(e) => setYear(e.target.value)}
|
||||
size="small"
|
||||
className="min-w-[120px]"
|
||||
InputLabelProps={{ className: 'text-gray-700' }}
|
||||
>
|
||||
{years.map((y) => (
|
||||
<MenuItem key={y} value={y}>
|
||||
{y}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
placeholder="Search month"
|
||||
size="small"
|
||||
className="flex-grow"
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<IconButton size="small" className="text-gray-500">
|
||||
<FileDownloadIcon />
|
||||
</IconButton>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-4">
|
||||
<h4 className="text-sm font-medium text-gray-900 mb-2 sm:mb-0">Showing 5 months for 2025</h4>
|
||||
<p className="text-sm text-gray-500">Click a row to open its ISIC breakdown on the right.</p>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">REFERENCE MONTH</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">MANUFACTURING INDEX</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">MOM CHANGE</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">YOY CHANGE</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">GENERATED ON</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">STATUS</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ACTIONS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{months.map((row) => (
|
||||
<tr
|
||||
key={row.month}
|
||||
onClick={() => setSelectedMonth(row.month)}
|
||||
className={`cursor-pointer ${selectedMonth === row.month ? 'bg-primary-50' : 'hover:bg-gray-50'}`}
|
||||
>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
<span className="text-sm font-medium text-gray-900">{row.month}</span>
|
||||
{selectedMonth === row.month && (
|
||||
<span className="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary-100 text-primary-800">
|
||||
Selected
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{row.index} (2022=100)</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||
row.mom.startsWith('+')
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-red-100 text-red-800'
|
||||
}`}>
|
||||
{row.mom}%
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||
row.yoy.startsWith('+')
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-red-100 text-red-800'
|
||||
}`}>
|
||||
{row.yoy}%
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{row.generated}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
||||
{row.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<IconButton size="small" className="text-gray-400 hover:text-gray-600">
|
||||
<ArrowRightAltIcon />
|
||||
</IconButton>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{/* Main UI */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<div className="mb-6">
|
||||
<Card className="p-6 bg-white rounded-lg shadow">
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-start gap-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-2">
|
||||
Manufacturing Index - Monthly overview
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 max-w-3xl">
|
||||
Monthly manufacturing indices are automatically generated on the 11th of each month using the finalized Laspeyres item indices. This view enables users to monitor trends and drill down into ISIC group–level details.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<div className="mt-5 flex items-center justify-end text-sm text-green-600 mb-1">
|
||||
<FiberManualRecordIcon className="text-xs mr-1" />
|
||||
Next scheduled run: 11 Nov 2025, 02:00 GST
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Right Column */}
|
||||
<div>
|
||||
<Card className="p-6">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900">Monthly ISIC breakdown</h3>
|
||||
<Chip
|
||||
label={selectedMonth}
|
||||
size="small"
|
||||
className="bg-gray-900 text-white font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-600 text-sm mb-6">
|
||||
Start from the manufacturing total, then drill down into ISIC 2-digit and 3 & 4-digit groups for the selected month.
|
||||
</p>
|
||||
|
||||
<ToggleButtonGroup
|
||||
color="primary"
|
||||
value={view}
|
||||
exclusive
|
||||
onChange={handleViewChange}
|
||||
aria-label="View"
|
||||
fullWidth
|
||||
className="mb-6"
|
||||
>
|
||||
<ToggleButton
|
||||
value="manufacturing"
|
||||
className={`flex-1 ${view === 'manufacturing' ? 'bg-primary-50 text-primary-700' : 'text-gray-600 hover:bg-gray-50'}`}
|
||||
>
|
||||
Manufacturing total
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
value="isic2"
|
||||
className={`flex-1 ${view === 'isic2' ? 'bg-primary-50 text-primary-700' : 'text-gray-600 hover:bg-gray-50'}`}
|
||||
>
|
||||
ISIC 2-digit
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
value="isic34"
|
||||
className={`flex-1 ${view === 'isic34' ? 'bg-primary-50 text-primary-700' : 'text-gray-600 hover:bg-gray-50'}`}
|
||||
>
|
||||
ISIC 3 & 4-digit
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
|
||||
<div className="bg-gray-800 p-4 rounded-lg mb-6">
|
||||
<h4 className="text-gray-400 text-xs font-medium uppercase tracking-wider mb-2">MANUFACTURING INDEX (HEADLINE)</h4>
|
||||
<h3 className="text-2xl font-bold text-white mb-2">114.8 (2022=100)</h3>
|
||||
<div className="flex gap-4 mb-3">
|
||||
<p className="text-gray-400">MoM: <span className="text-green-500 font-semibold">+0.9%</span></p>
|
||||
<p className="text-gray-400">YoY: <span className="text-green-500 font-semibold">+3.2%</span></p>
|
||||
{/* Main Table */}
|
||||
<div className="space-y-6">
|
||||
<Card className="p-6 bg-white rounded-lg shadow">
|
||||
<div className="space-y-4">
|
||||
<div className="border border-gray-200 rounded-lg overflow-hidden">
|
||||
{/* Filters */}
|
||||
<div className="bg-white px-6 py-4 border-b border-gray-200">
|
||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-4">
|
||||
<div className="mb-4 sm:mb-0">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-1">
|
||||
Manufacturing index — by reference month ({filteredMonths.length})
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600">
|
||||
Filter by year and quickly jump to a month to see its ISIC breakdown.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 w-full sm:w-auto">
|
||||
<div className="flex-1 sm:flex-none w-40">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<img src="/assets/images/material-symbols_search-rounded.svg" alt="Search" className="h-5 w-5" />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search month"
|
||||
className="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md h-[40px]"
|
||||
value={searchMonth}
|
||||
onChange={handleSearchMonth}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center w-32">
|
||||
<SelectField
|
||||
value={year}
|
||||
onChange={handleYearChange}
|
||||
className="w-full h-[40px]"
|
||||
size="small"
|
||||
options={years.map(y => ({ value: y, label: y }))}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="h-10 px-3 xl:px-4 rounded-[6px] bg-[#F7F7F7] border border-[#C3C6CB] text-sm inline-flex items-center gap-2 hover:bg-gray-50 whitespace-nowrap"
|
||||
onClick={handleExportCSV}
|
||||
>
|
||||
<img src={downloadIconSrc} alt="Download" className="h-5 w-5" />
|
||||
<span className="hidden xl:inline font-medium text-[#232528]">Export CSV</span>
|
||||
<span className="xl:hidden font-medium text-[#232528]">Export</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div className="relative">
|
||||
<div className="w-full">
|
||||
<Table
|
||||
headers={[
|
||||
'Reference Month',
|
||||
'Manufacturing Index',
|
||||
'MOM Change',
|
||||
'YOY Change',
|
||||
'Generated On',
|
||||
'Status',
|
||||
'Actions'
|
||||
]}
|
||||
rows={filteredMonths.map((row) => {
|
||||
const isSelected = selectedMonth?.month === row.month;
|
||||
return [
|
||||
<div key="month" className={`${isSelected ? 'font-medium' : ''}`}>
|
||||
{row.month}
|
||||
</div>,
|
||||
<span className={isSelected ? 'font-medium' : ''} key="index">
|
||||
{row.index} (2022=100)
|
||||
</span>,
|
||||
<span className={`${parseFloat(row.mom) >= 0 ? 'text-green-600' : 'text-red-600'} ${isSelected ? 'font-medium' : ''}`} key="mom">
|
||||
{row.mom}%
|
||||
</span>,
|
||||
<span className={`${parseFloat(row.yoy) >= 0 ? 'text-green-600' : 'text-red-600'} ${isSelected ? 'font-medium' : ''}`} key="yoy">
|
||||
{row.yoy}%
|
||||
</span>,
|
||||
<span className={isSelected ? 'font-medium' : ''} key="generated">
|
||||
{row.generated}
|
||||
</span>,
|
||||
<span className={isSelected ? 'font-medium' : ''} key="status">
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||
row.status === 'Completed'
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-yellow-100 text-yellow-800'
|
||||
}`}>
|
||||
{row.status}
|
||||
</span>
|
||||
</span>,
|
||||
<div
|
||||
key="arrow"
|
||||
className={`cursor-pointer flex items-center justify-end pr-4 ${
|
||||
isSelected ? 'text-gray-900' : 'text-gray-400'
|
||||
}`}
|
||||
onClick={() => handleMonthSelect(row)}
|
||||
>
|
||||
<KeyboardArrowRightIcon />
|
||||
</div>
|
||||
];
|
||||
})}
|
||||
columnWidths={['180px', '190px', '130px', '130px', '200px', '120px', '80px']}
|
||||
getRowClass={(index) => {
|
||||
const isSelected = selectedMonth?.month === filteredMonths[index]?.month;
|
||||
return isSelected ? 'bg-[#F5F0E1]' : 'hover:bg-gray-50';
|
||||
}}
|
||||
pagination={{
|
||||
currentPage: pagination.currentPage,
|
||||
onPageChange: (page) => setPagination(prev => ({ ...prev, currentPage: page })),
|
||||
pageSize: pagination.pageSize,
|
||||
totalItems: filteredMonths.length,
|
||||
pageSizeOptions: [10, 20, 50, 100],
|
||||
onPageSizeChange: (size) => {
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
pageSize: size,
|
||||
currentPage: 1
|
||||
}));
|
||||
}
|
||||
}}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-300 text-sm">
|
||||
The manufacturing index increased by 0.9% compared to the previous month and 3.2% compared to the same month last year.
|
||||
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
Note: Indices shown here are headline manufacturing totals. Use the drill-down actions to move from manufacturing total to ISIC 2-digit, then to ISIC 3 & 4-digit groups.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Divider className="border-[#424242] my-6" />
|
||||
|
||||
<div>
|
||||
<h4 className="text-gray-500 text-sm font-medium uppercase tracking-wider mb-4">KEY CONTRIBUTORS</h4>
|
||||
|
||||
<div className="mb-5">
|
||||
<div className="flex justify-between mb-1">
|
||||
<p className="text-sm text-gray-800">Food products (ISIC 10-12)</p>
|
||||
<p className="font-semibold text-gray-900">+0.24</p>
|
||||
</div>
|
||||
<div className="w-full h-1.5 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div className="h-full bg-green-500 w-3/5"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<div className="flex justify-between mb-1">
|
||||
<p className="text-sm text-gray-800">Chemicals (ISIC 20-21)</p>
|
||||
<p className="font-semibold text-gray-900">+0.18</p>
|
||||
</div>
|
||||
<div className="w-full h-1.5 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div className="h-full bg-green-500 w-[45%]"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-2">
|
||||
<div className="flex justify-between mb-1">
|
||||
<p className="text-sm text-gray-800">Basic metals (ISIC 24)</p>
|
||||
<p className="text-red-500 font-semibold">-0.12</p>
|
||||
</div>
|
||||
<div className="w-full h-1.5 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div className="h-full bg-red-500 w-[30%]"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManufacturingIndex;
|
||||
export default ManufacturingIndex;
|
||||
@ -0,0 +1,230 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import Table from '@/components/common/Table';
|
||||
|
||||
const ISIC2Digit = ({ setActiveTab, apiData }) => {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
|
||||
// Your API returns array of objects with: isic_2digit_code, isic_description, total_weight, weighted_index_sum, isic_2digit_index
|
||||
const isicData = Array.isArray(apiData) ? apiData : [];
|
||||
|
||||
// Filter ISIC 2-digit data based on search term
|
||||
const filteredData = useMemo(() => {
|
||||
if (!searchTerm.trim()) return isicData;
|
||||
|
||||
const term = searchTerm.toLowerCase().trim();
|
||||
return isicData.filter(item => {
|
||||
const code = item.isic_2digit_code || '';
|
||||
const name = item.isic_description || '';
|
||||
const index = item.isic_2digit_index || '';
|
||||
const weight = item.total_weight || '';
|
||||
|
||||
return (
|
||||
code.toString().toLowerCase().includes(term) ||
|
||||
name.toString().toLowerCase().includes(term) ||
|
||||
index.toString().toLowerCase().includes(term) ||
|
||||
weight.toString().toLowerCase().includes(term)
|
||||
);
|
||||
});
|
||||
}, [searchTerm, isicData]);
|
||||
|
||||
// Handle CSV export
|
||||
const handleExport = () => {
|
||||
if (filteredData.length === 0) return;
|
||||
|
||||
// Create CSV content
|
||||
const headers = ['ISIC Code', 'Sector', 'Index', 'Weight (%)'];
|
||||
const csvRows = [
|
||||
headers.join(','),
|
||||
...filteredData.map(item =>
|
||||
[
|
||||
`"${item.isic_2digit_code || ''}"`,
|
||||
`"${item.isic_description || ''}"`,
|
||||
item.isic_2digit_index || '',
|
||||
item.total_weight || ''
|
||||
].join(',')
|
||||
)
|
||||
];
|
||||
|
||||
// Create and trigger download
|
||||
const csvContent = csvRows.join('\n');
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', `isic_2digit_${new Date().toISOString().split('T')[0]}.csv`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
// Format number with 2 decimal places if needed
|
||||
const formatNumber = (value) => {
|
||||
if (value === null || value === undefined || value === '' || value === 'N/A') return 'N/A';
|
||||
|
||||
const num = parseFloat(value);
|
||||
if (isNaN(num)) return value;
|
||||
|
||||
// If it's a whole number, show as is, else show 2 decimal places
|
||||
return num % 1 === 0 ? num.toString() : num.toFixed(2);
|
||||
};
|
||||
|
||||
// Get total weight for display
|
||||
const totalWeight = useMemo(() => {
|
||||
return isicData.reduce((sum, item) => {
|
||||
const weight = parseFloat(item.total_weight || 0);
|
||||
return sum + (isNaN(weight) ? 0 : weight);
|
||||
}, 0);
|
||||
}, [isicData]);
|
||||
|
||||
return (
|
||||
<div className="-mt-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900">ISIC 2-digit breakdown</h3>
|
||||
<p className="text-sm mt-2 text-gray-600 mb-4">
|
||||
The manufacturing index is calculated as a weighted average of ISIC 2-digit groups,
|
||||
using their relative weights in the manufacturing sector.
|
||||
{totalWeight > 0 && ` Total weight: ${formatNumber(totalWeight)}%`}
|
||||
</p>
|
||||
|
||||
{/* Data Summary */}
|
||||
{isicData.length === 0 ? (
|
||||
<div className="bg-yellow-50 border border-yellow-200 rounded-md p-4 mb-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="h-5 w-5 text-yellow-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-yellow-800">No data available</h3>
|
||||
<div className="mt-2 text-sm text-yellow-700">
|
||||
<p>No ISIC 2-digit data found for this month.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Search and Filter Bar */}
|
||||
<div className="flex flex-col sm:flex-row justify-end items-center gap-4 mb-4">
|
||||
<div className="relative w-full max-w-md">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg className="h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by sector or code..."
|
||||
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:ring-1 focus:ring-[#92722A] focus:border-[#92722A] sm:text-sm"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleExport}
|
||||
disabled={filteredData.length === 0}
|
||||
className={`inline-flex items-center gap-2 h-9 px-4 rounded-md border text-sm font-medium ${
|
||||
filteredData.length > 0
|
||||
? 'border-[#92722A] text-[#92722A] hover:bg-[#F2ECCF]'
|
||||
: 'border-gray-200 text-gray-400 cursor-not-allowed'
|
||||
}`}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
<span>Export CSV</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="-mx-6">
|
||||
<Table
|
||||
headers={['ISIC Code', 'Sector', 'Index', 'Weight (%)']}
|
||||
rows={filteredData.map((item, index) => [
|
||||
<span className="font-medium" key={`code-${item.isic_2digit_code || index}`}>
|
||||
{item.isic_2digit_code || 'N/A'}
|
||||
</span>,
|
||||
<span key={`name-${item.isic_2digit_code || index}`}>
|
||||
{item.isic_description || 'N/A'}
|
||||
</span>,
|
||||
<span
|
||||
key={`index-${item.isic_2digit_code || index}`}
|
||||
className={parseFloat(item.isic_2digit_index) >= 0 ? 'text-green-600' : 'text-red-600'}
|
||||
>
|
||||
{formatNumber(item.isic_2digit_index)}
|
||||
</span>,
|
||||
<span key={`weight-${item.isic_2digit_code || index}`}>
|
||||
{formatNumber(item.total_weight)}%
|
||||
</span>
|
||||
])}
|
||||
className="w-full"
|
||||
columnWidths={['15%', '55%', '15%', '15%']}
|
||||
getCellClass={(rowIndex, colIndex) => {
|
||||
const baseClasses = 'px-6 py-4 whitespace-nowrap text-sm';
|
||||
return colIndex === 0
|
||||
? `${baseClasses} text-gray-900 font-medium`
|
||||
: baseClasses;
|
||||
}}
|
||||
getRowClass={(rowIndex) => {
|
||||
const baseClasses = 'border-b border-gray-200';
|
||||
return rowIndex % 2 === 0
|
||||
? `${baseClasses} bg-white`
|
||||
: `${baseClasses} bg-gray-50`;
|
||||
}}
|
||||
pagination={{
|
||||
currentPage: currentPage,
|
||||
onPageChange: setCurrentPage,
|
||||
pageSize: pageSize,
|
||||
totalItems: filteredData.length,
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
onPageSizeChange: setPageSize
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Data Stats */}
|
||||
{filteredData.length > 0 && (
|
||||
<div className="mt-4 text-sm text-gray-500 flex justify-between items-center">
|
||||
{/* <span>Showing {filteredData.length} of {isicData.length} sectors</span> */}
|
||||
{/* {totalWeight > 0 && (
|
||||
<span>Total weight: <span className="font-medium">{formatNumber(totalWeight)}%</span></span>
|
||||
)} */}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Back and Next Buttons */}
|
||||
<div className="mt-8 flex flex-col sm:flex-row justify-between gap-3">
|
||||
<button
|
||||
onClick={() => setActiveTab('manufacturing')}
|
||||
className="inline-flex items-center gap-2 h-9 px-4 rounded-md border border-[#92722A] text-[#92722A] bg-transparent hover:bg-[#92722A]/10"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
<span>Back</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('isic3')}
|
||||
disabled={isicData.length === 0}
|
||||
className={`inline-flex items-center gap-2 h-9 px-5 rounded-md ${
|
||||
isicData.length > 0
|
||||
? 'bg-[#92722A] text-white hover:bg-[#7b5c1f] cursor-pointer'
|
||||
: 'bg-gray-200 text-gray-500 cursor-not-allowed'
|
||||
}`}
|
||||
>
|
||||
<span>Next</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ISIC2Digit;
|
||||
@ -0,0 +1,209 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import Table from '@/components/common/Table';
|
||||
|
||||
const ISIC3Digit = ({ setActiveTab, apiData }) => {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
// API-லிருந்து வந்த data-ஐ பயன்படுத்தவும்
|
||||
const isicData = Array.isArray(apiData) ? apiData : [];
|
||||
|
||||
// Filter data based on search term
|
||||
const filteredData = useMemo(() => {
|
||||
if (!searchTerm.trim()) return isicData;
|
||||
|
||||
const term = searchTerm.toLowerCase().trim();
|
||||
return isicData.filter(item => {
|
||||
const code = item.isic_3digit_code || '';
|
||||
const name = item.isic_description || '';
|
||||
const index = item.isic_3digit_index || '';
|
||||
const weight = item.total_weight || '';
|
||||
|
||||
return (
|
||||
code.toString().toLowerCase().includes(term) ||
|
||||
name.toString().toLowerCase().includes(term) ||
|
||||
index.toString().toLowerCase().includes(term) ||
|
||||
weight.toString().toLowerCase().includes(term)
|
||||
);
|
||||
});
|
||||
}, [searchTerm, isicData]);
|
||||
|
||||
// Handle CSV export
|
||||
const handleExport = () => {
|
||||
if (filteredData.length === 0) return;
|
||||
|
||||
// Create CSV content
|
||||
const headers = ['ISIC Code', 'Sector', 'Index', 'Weight (%)'];
|
||||
const csvRows = [
|
||||
headers.join(','),
|
||||
...filteredData.map(item =>
|
||||
[
|
||||
`"${item.isic_3digit_code || ''}"`,
|
||||
`"${item.isic_description || ''}"`,
|
||||
item.isic_3digit_index || '',
|
||||
item.total_weight || ''
|
||||
].join(',')
|
||||
)
|
||||
];
|
||||
|
||||
// Create and trigger download
|
||||
const csvContent = csvRows.join('\n');
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', `isic_3digit_${new Date().toISOString().split('T')[0]}.csv`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
// Format number with 2 decimal places if needed
|
||||
const formatNumber = (value) => {
|
||||
if (value === null || value === undefined || value === '' || value === 'N/A') return 'N/A';
|
||||
|
||||
const num = parseFloat(value);
|
||||
if (isNaN(num)) return value;
|
||||
|
||||
return num % 1 === 0 ? num.toString() : num.toFixed(2);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="-mt-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900">ISIC 3-digit breakdown</h3>
|
||||
<p className="text-sm mt-2 text-gray-600 mb-4">
|
||||
Detailed breakdown of manufacturing index by ISIC 3-digit classification.
|
||||
</p>
|
||||
|
||||
{/* Data Summary */}
|
||||
{isicData.length === 0 ? (
|
||||
<div className="bg-yellow-50 border border-yellow-200 rounded-md p-4 mb-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="h-5 w-5 text-yellow-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-yellow-800">No data available</h3>
|
||||
<div className="mt-2 text-sm text-yellow-700">
|
||||
<p>No ISIC 3-digit data found for this month.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Search and Filter Bar */}
|
||||
<div className="flex flex-col sm:flex-row justify-end items-center gap-4 mb-4">
|
||||
<div className="relative w-full max-w-md">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg className="h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by sector or code..."
|
||||
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:ring-1 focus:ring-[#92722A] focus:border-[#92722A] sm:text-sm"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleExport}
|
||||
disabled={filteredData.length === 0}
|
||||
className={`inline-flex items-center gap-2 h-9 px-4 rounded-md border text-sm font-medium ${
|
||||
filteredData.length > 0
|
||||
? 'border-[#92722A] text-[#92722A] hover:bg-[#F2ECCF]'
|
||||
: 'border-gray-200 text-gray-400 cursor-not-allowed'
|
||||
}`}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
<span>Export CSV</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="-mx-6">
|
||||
<Table
|
||||
headers={['ISIC Code', 'Sector', 'Index', 'Weight (%)']}
|
||||
rows={filteredData.map((item, index) => [
|
||||
<span className="font-medium" key={`code-${item.isic_3digit_code || index}`}>
|
||||
{item.isic_3digit_code || 'N/A'}
|
||||
</span>,
|
||||
<span key={`name-${item.isic_3digit_code || index}`}>
|
||||
{item.isic_description || 'N/A'}
|
||||
</span>,
|
||||
<span
|
||||
key={`index-${item.isic_3digit_code || index}`}
|
||||
className={parseFloat(item.isic_3digit_index) >= 0 ? 'text-green-600' : 'text-red-600'}
|
||||
>
|
||||
{formatNumber(item.isic_3digit_index)}
|
||||
</span>,
|
||||
<span key={`weight-${item.isic_3digit_code || index}`}>
|
||||
{formatNumber(item.total_weight)}%
|
||||
</span>
|
||||
])}
|
||||
className="w-full"
|
||||
columnWidths={['20%', '50%', '15%', '15%']}
|
||||
getCellClass={(rowIndex, colIndex) => {
|
||||
const baseClasses = 'px-6 py-4 whitespace-nowrap text-sm text-gray-900';
|
||||
return colIndex === 0
|
||||
? `${baseClasses} font-medium`
|
||||
: baseClasses;
|
||||
}}
|
||||
pagination={{
|
||||
currentPage: currentPage,
|
||||
onPageChange: setCurrentPage,
|
||||
pageSize: pageSize,
|
||||
totalItems: filteredData.length,
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
onPageSizeChange: setPageSize
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Data Stats */}
|
||||
{filteredData.length > 0 && (
|
||||
<div className="mt-4 text-sm text-gray-500">
|
||||
{/* <span>Showing {filteredData.length} of {isicData.length} sectors</span> */}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Back and Next Buttons */}
|
||||
<div className="mt-8 flex flex-col sm:flex-row justify-between gap-3">
|
||||
<button
|
||||
onClick={() => setActiveTab('isic2')}
|
||||
className="inline-flex items-center gap-2 h-9 px-4 rounded-md border border-[#92722A] text-[#92722A] bg-transparent hover:bg-[#92722A]/10"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
<span>Back</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('isic4')}
|
||||
disabled={isicData.length === 0}
|
||||
className={`inline-flex items-center gap-2 h-9 px-5 rounded-md ${
|
||||
isicData.length > 0
|
||||
? 'bg-[#92722A] text-white hover:bg-[#7b5c1f] cursor-pointer'
|
||||
: 'bg-gray-200 text-gray-500 cursor-not-allowed'
|
||||
}`}
|
||||
>
|
||||
<span>Next</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ISIC3Digit;
|
||||
@ -0,0 +1,195 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import Table from '@/components/common/Table';
|
||||
|
||||
const ISIC4Digit = ({ setActiveTab, apiData }) => {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
// API-லிருந்து வந்த data-ஐ பயன்படுத்தவும்
|
||||
const isicData = Array.isArray(apiData) ? apiData : [];
|
||||
|
||||
// Filter data based on search term
|
||||
const filteredData = useMemo(() => {
|
||||
if (!searchTerm.trim()) return isicData;
|
||||
|
||||
const term = searchTerm.toLowerCase().trim();
|
||||
return isicData.filter(item => {
|
||||
const code = item.isic_4digit_code || '';
|
||||
const name = item.isic_description || '';
|
||||
const index = item.isic_4digit_index || '';
|
||||
const weight = item.total_weight || '';
|
||||
|
||||
return (
|
||||
code.toString().toLowerCase().includes(term) ||
|
||||
name.toString().toLowerCase().includes(term) ||
|
||||
index.toString().toLowerCase().includes(term) ||
|
||||
weight.toString().toLowerCase().includes(term)
|
||||
);
|
||||
});
|
||||
}, [searchTerm, isicData]);
|
||||
|
||||
// Handle CSV export
|
||||
const handleExport = () => {
|
||||
if (filteredData.length === 0) return;
|
||||
|
||||
// Create CSV content
|
||||
const headers = ['ISIC Code', 'Sector', 'Index', 'Weight (%)'];
|
||||
const csvRows = [
|
||||
headers.join(','),
|
||||
...filteredData.map(item =>
|
||||
[
|
||||
`"${item.isic_4digit_code || ''}"`,
|
||||
`"${item.isic_description || ''}"`,
|
||||
item.isic_4digit_index || '',
|
||||
item.total_weight || ''
|
||||
].join(',')
|
||||
)
|
||||
];
|
||||
|
||||
// Create and trigger download
|
||||
const csvContent = csvRows.join('\n');
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', `isic_4digit_${new Date().toISOString().split('T')[0]}.csv`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
// Format number with 2 decimal places if needed
|
||||
const formatNumber = (value) => {
|
||||
if (value === null || value === undefined || value === '' || value === 'N/A') return 'N/A';
|
||||
|
||||
const num = parseFloat(value);
|
||||
if (isNaN(num)) return value;
|
||||
|
||||
return num % 1 === 0 ? num.toString() : num.toFixed(2);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="-mt-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900">ISIC 4-digit breakdown</h3>
|
||||
<p className="text-sm mt-2 text-gray-600 mb-4">
|
||||
Detailed breakdown of manufacturing index by ISIC 4-digit classification.
|
||||
</p>
|
||||
|
||||
{/* Data Summary */}
|
||||
{isicData.length === 0 ? (
|
||||
<div className="bg-yellow-50 border border-yellow-200 rounded-md p-4 mb-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="h-5 w-5 text-yellow-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-yellow-800">No data available</h3>
|
||||
<div className="mt-2 text-sm text-yellow-700">
|
||||
<p>No ISIC 4-digit data found for this month.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Search and Filter Bar */}
|
||||
<div className="flex flex-col sm:flex-row justify-end items-center gap-4 mb-4">
|
||||
<div className="relative w-full max-w-md">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg className="h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by sector or code..."
|
||||
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:ring-1 focus:ring-[#92722A] focus:border-[#92722A] sm:text-sm"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleExport}
|
||||
disabled={filteredData.length === 0}
|
||||
className={`inline-flex items-center gap-2 h-9 px-4 rounded-md border text-sm font-medium ${
|
||||
filteredData.length > 0
|
||||
? 'border-[#92722A] text-[#92722A] hover:bg-[#F2ECCF]'
|
||||
: 'border-gray-200 text-gray-400 cursor-not-allowed'
|
||||
}`}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
<span>Export CSV</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="-mx-6">
|
||||
<Table
|
||||
headers={['ISIC Code', 'Sector', 'Index', 'Weight (%)']}
|
||||
rows={filteredData.map((item, index) => [
|
||||
<span className="font-medium" key={`code-${item.isic_4digit_code || index}`}>
|
||||
{item.isic_4digit_code || 'N/A'}
|
||||
</span>,
|
||||
<span key={`name-${item.isic_4digit_code || index}`}>
|
||||
{item.isic_description || 'N/A'}
|
||||
</span>,
|
||||
<span
|
||||
key={`index-${item.isic_4digit_code || index}`}
|
||||
className={parseFloat(item.isic_4digit_index) >= 0 ? 'text-green-600' : 'text-red-600'}
|
||||
>
|
||||
{formatNumber(item.isic_4digit_index)}
|
||||
</span>,
|
||||
<span key={`weight-${item.isic_4digit_code || index}`}>
|
||||
{formatNumber(item.total_weight)}%
|
||||
</span>
|
||||
])}
|
||||
className="w-full"
|
||||
columnWidths={['20%', '50%', '15%', '15%']}
|
||||
getCellClass={(rowIndex, colIndex) => {
|
||||
const baseClasses = 'px-6 py-4 whitespace-nowrap text-sm text-gray-900';
|
||||
return colIndex === 0
|
||||
? `${baseClasses} font-medium`
|
||||
: baseClasses;
|
||||
}}
|
||||
pagination={{
|
||||
currentPage: currentPage,
|
||||
onPageChange: setCurrentPage,
|
||||
pageSize: pageSize,
|
||||
totalItems: filteredData.length,
|
||||
pageSizeOptions: [10, 25, 50, 100],
|
||||
onPageSizeChange: setPageSize
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Data Stats */}
|
||||
{filteredData.length > 0 && (
|
||||
<div className="mt-4 text-sm text-gray-500">
|
||||
{/* <span>Showing {filteredData.length} of {isicData.length} sectors</span> */}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Only Back Button for ISIC 4-digit (no Next button) */}
|
||||
<div className="mt-8 flex flex-col sm:flex-row justify-start gap-3">
|
||||
<button
|
||||
onClick={() => setActiveTab('isic3')}
|
||||
className="inline-flex items-center gap-2 h-9 px-4 rounded-md border border-[#92722A] text-[#92722A] bg-transparent hover:bg-[#92722A]/10"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
<span>Back</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ISIC4Digit;
|
||||
@ -0,0 +1,115 @@
|
||||
import React from 'react';
|
||||
|
||||
const ManufacturingTotal = ({ selectedMonthData, setActiveTab, monthlyOverviewData }) => {
|
||||
// Use API data if available, otherwise use table data
|
||||
const apiData = monthlyOverviewData?.manufacturing;
|
||||
|
||||
// Determine which data source to use
|
||||
const dataSource = apiData || selectedMonthData;
|
||||
|
||||
if (!dataSource) {
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<p className="text-xs font-semibold uppercase tracking-wider text-medium-500">
|
||||
Manufacturing Index (Headline)
|
||||
</p>
|
||||
<p className="text-gray-500">No data available for this month.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Extract values from the selected data source
|
||||
const getValue = (key) => {
|
||||
if (apiData) {
|
||||
// API data has different structure
|
||||
switch(key) {
|
||||
case 'index': return apiData.index || apiData.manufacturing_index || 'N/A';
|
||||
case 'mom':
|
||||
const momVal = apiData.mom || apiData.mom_change || 0;
|
||||
return momVal > 0 ? `+${momVal}%` : `${momVal}%`;
|
||||
case 'yoy':
|
||||
const yoyVal = apiData.yoy || apiData.yoy_change || 0;
|
||||
return yoyVal > 0 ? `+${yoyVal}%` : `${yoyVal}%`;
|
||||
default: return 'N/A';
|
||||
}
|
||||
} else {
|
||||
// Table data
|
||||
return dataSource[key] || 'N/A';
|
||||
}
|
||||
};
|
||||
|
||||
const indexValue = getValue('index');
|
||||
const momValue = getValue('mom');
|
||||
const yoyValue = getValue('yoy');
|
||||
|
||||
// Parse numeric values for calculations
|
||||
const momNumeric = parseFloat(momValue);
|
||||
const yoyNumeric = parseFloat(yoyValue);
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<p className="text-xs font-semibold uppercase tracking-wider text-medium-500">
|
||||
Manufacturing Index (Headline)
|
||||
</p>
|
||||
|
||||
<div className="flex items-baseline gap-2">
|
||||
<h3 className="text-3xl font-semibold text-gray-900">
|
||||
{indexValue}
|
||||
</h3>
|
||||
<span className="text-sm text-gray-500">(2022 = 100)</span>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-6">
|
||||
<div className="text-sm text-gray-700">
|
||||
<span className="text-medium-500">MoM:</span>{' '}
|
||||
<span
|
||||
className={`font-medium ${
|
||||
momNumeric >= 0
|
||||
? 'text-green-600'
|
||||
: 'text-red-600'
|
||||
}`}
|
||||
>
|
||||
{momValue}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-gray-700">
|
||||
<span className="text-medium-500">YoY:</span>{' '}
|
||||
<span
|
||||
className={`font-medium ${
|
||||
yoyNumeric >= 0
|
||||
? 'text-green-600'
|
||||
: 'text-red-600'
|
||||
}`}
|
||||
>
|
||||
{yoyValue}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-700 leading-relaxed max-w-3xl">
|
||||
The manufacturing index{' '}
|
||||
{momNumeric >= 0 ? 'increased' : 'decreased'} by{' '}
|
||||
<strong>{Math.abs(momNumeric)}%</strong> compared to
|
||||
the previous month and{' '}
|
||||
{yoyNumeric >= 0 ? 'increased' : 'decreased'} by{' '}
|
||||
<strong>{Math.abs(yoyNumeric)}%</strong> compared to
|
||||
the same month last year.
|
||||
</p>
|
||||
|
||||
<div className="mt-8 pt-4 border-gray-200 flex justify-end">
|
||||
<button
|
||||
onClick={() => setActiveTab('isic2')}
|
||||
className="inline-flex items-center gap-2 h-9 px-5 rounded-md bg-[#92722A] text-white hover:bg-[#7b5c1f]"
|
||||
>
|
||||
<span>Next</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManufacturingTotal;
|
||||
@ -0,0 +1,40 @@
|
||||
import { getRequest } from '@/services/api/CommonService';
|
||||
|
||||
const MANUFACTURING_ENDPOINT = '/manufacturing';
|
||||
|
||||
export const getManufacturingIndex = async () => {
|
||||
try {
|
||||
const response = await getRequest(`${MANUFACTURING_ENDPOINT}/getManufacturingIndex`);
|
||||
console.log('Manufacturing Index API Response:', response);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Error fetching manufacturing index:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getManufacturingMonthlyOverview = async (year, month) => {
|
||||
try {
|
||||
console.log('Monthly Overview API Call - Year:', year, 'Month:', month);
|
||||
|
||||
const response = await getRequest(
|
||||
`${MANUFACTURING_ENDPOINT}/getManufacturingMonthlyOverview`,
|
||||
{
|
||||
params: {
|
||||
year: year.toString(),
|
||||
month: month.toString(),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
console.log('API Response:', response);
|
||||
|
||||
// Return the entire response
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching manufacturing monthly overview:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@ -14,7 +14,7 @@ export const getSubmissions = async (page = 1, limit = 100, config = {}) => {
|
||||
params: {
|
||||
page,
|
||||
limit,
|
||||
...(config.params || {}) // Preserve any existing params
|
||||
...(config.params || {})
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user