added iip calculations

This commit is contained in:
Malini 2025-12-30 13:21:53 +05:30
parent 20c1fc61d9
commit 50bb4dd59b
12 changed files with 2057 additions and 327 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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={

View File

@ -1871,4 +1871,7 @@ const location = useLocation();
);
};
export default ProductData;
export default ProductData;

View File

@ -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>

View File

@ -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 grouplevel 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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
};

View File

@ -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;