mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
feat(FE-390): slicing UI and API integration for production dashboard
This commit is contained in:
Generated
+383
-12
@@ -25,6 +25,7 @@
|
|||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
"react-number-format": "^5.4.4",
|
"react-number-format": "^5.4.4",
|
||||||
"react-select": "^5.10.2",
|
"react-select": "^5.10.2",
|
||||||
|
"recharts": "^3.6.0",
|
||||||
"swr": "^2.3.6",
|
"swr": "^2.3.6",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"use-debounce": "^10.0.6",
|
"use-debounce": "^10.0.6",
|
||||||
@@ -1450,6 +1451,42 @@
|
|||||||
"@react-pdf/stylesheet": "^6.1.1"
|
"@react-pdf/stylesheet": "^6.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@reduxjs/toolkit": {
|
||||||
|
"version": "2.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz",
|
||||||
|
"integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@standard-schema/spec": "^1.0.0",
|
||||||
|
"@standard-schema/utils": "^0.3.0",
|
||||||
|
"immer": "^11.0.0",
|
||||||
|
"redux": "^5.0.1",
|
||||||
|
"redux-thunk": "^3.1.0",
|
||||||
|
"reselect": "^5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
|
||||||
|
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@reduxjs/toolkit/node_modules/immer": {
|
||||||
|
"version": "11.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz",
|
||||||
|
"integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rtsao/scc": {
|
"node_modules/@rtsao/scc": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||||
@@ -1464,6 +1501,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@standard-schema/spec": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@standard-schema/utils": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.15",
|
"version": "0.5.15",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||||
@@ -1804,6 +1853,69 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-array": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-color": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-ease": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-interpolate": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-color": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-path": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-scale": {
|
||||||
|
"version": "4.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
|
||||||
|
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-time": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-shape": {
|
||||||
|
"version": "3.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
|
||||||
|
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-path": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-time": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-timer": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
@@ -1871,7 +1983,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
||||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@@ -1902,6 +2013,12 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"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",
|
||||||
|
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.46.2",
|
"version": "8.46.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz",
|
||||||
@@ -1948,7 +2065,6 @@
|
|||||||
"integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==",
|
"integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.46.2",
|
"@typescript-eslint/scope-manager": "8.46.2",
|
||||||
"@typescript-eslint/types": "8.46.2",
|
"@typescript-eslint/types": "8.46.2",
|
||||||
@@ -2472,7 +2588,6 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -3138,8 +3253,128 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
},
|
||||||
|
"node_modules/d3-array": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"internmap": "1 - 2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-color": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-ease": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-format": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-interpolate": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-color": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-path": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-scale": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "2.10.0 - 3",
|
||||||
|
"d3-format": "1 - 3",
|
||||||
|
"d3-interpolate": "1.2.0 - 3",
|
||||||
|
"d3-time": "2.1.1 - 3",
|
||||||
|
"d3-time-format": "2 - 4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-shape": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-path": "^3.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-time": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "2 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-time-format": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-time": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-timer": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/daisyui": {
|
"node_modules/daisyui": {
|
||||||
"version": "5.5.8",
|
"version": "5.5.8",
|
||||||
@@ -3245,6 +3480,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decimal.js-light": {
|
||||||
|
"version": "2.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||||
|
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/deep-is": {
|
"node_modules/deep-is": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||||
@@ -3587,6 +3828,16 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/es-toolkit": {
|
||||||
|
"version": "1.43.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.43.0.tgz",
|
||||||
|
"integrity": "sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"docs",
|
||||||
|
"benchmarks"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/escape-string-regexp": {
|
"node_modules/escape-string-regexp": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
@@ -3605,7 +3856,6 @@
|
|||||||
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
|
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -3779,7 +4029,6 @@
|
|||||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rtsao/scc": "^1.1.0",
|
"@rtsao/scc": "^1.1.0",
|
||||||
"array-includes": "^3.1.9",
|
"array-includes": "^3.1.9",
|
||||||
@@ -4026,6 +4275,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eventemitter3": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/events": {
|
"node_modules/events": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||||
@@ -4651,6 +4906,16 @@
|
|||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/immer": {
|
||||||
|
"version": "10.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
|
||||||
|
"integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||||
@@ -4698,6 +4963,15 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/internmap": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/iobuffer": {
|
"node_modules/iobuffer": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
|
||||||
@@ -5244,7 +5518,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.4.tgz",
|
||||||
"integrity": "sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==",
|
"integrity": "sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.28.4",
|
"@babel/runtime": "^7.28.4",
|
||||||
"fast-png": "^6.2.0",
|
"fast-png": "^6.2.0",
|
||||||
@@ -6345,7 +6618,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -6376,7 +6648,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.26.0"
|
"scheduler": "^0.26.0"
|
||||||
},
|
},
|
||||||
@@ -6440,6 +6711,29 @@
|
|||||||
"react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
"react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-redux": {
|
||||||
|
"version": "9.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||||
|
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/use-sync-external-store": "^0.0.6",
|
||||||
|
"use-sync-external-store": "^1.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^18.2.25 || ^19",
|
||||||
|
"react": "^18.0 || ^19",
|
||||||
|
"redux": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-select": {
|
"node_modules/react-select": {
|
||||||
"version": "5.10.2",
|
"version": "5.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz",
|
||||||
@@ -6477,6 +6771,51 @@
|
|||||||
"react-dom": ">=16.6.0"
|
"react-dom": ">=16.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/recharts": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"www"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@reduxjs/toolkit": "1.x.x || 2.x.x",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"decimal.js-light": "^2.5.1",
|
||||||
|
"es-toolkit": "^1.39.3",
|
||||||
|
"eventemitter3": "^5.0.1",
|
||||||
|
"immer": "^10.1.1",
|
||||||
|
"react-redux": "8.x.x || 9.x.x",
|
||||||
|
"reselect": "5.1.1",
|
||||||
|
"tiny-invariant": "^1.3.3",
|
||||||
|
"use-sync-external-store": "^1.2.2",
|
||||||
|
"victory-vendor": "^37.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/redux": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/redux-thunk": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"redux": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reflect.getprototypeof": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
||||||
@@ -6543,6 +6882,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/reselect": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.11",
|
"version": "1.22.11",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||||
@@ -7263,6 +7608,12 @@
|
|||||||
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
|
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/tiny-invariant": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/tiny-warning": {
|
"node_modules/tiny-warning": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||||
@@ -7310,7 +7661,6 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -7478,7 +7828,6 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -7635,6 +7984,28 @@
|
|||||||
"base64-arraybuffer": "^1.0.2"
|
"base64-arraybuffer": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/victory-vendor": {
|
||||||
|
"version": "37.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
|
||||||
|
"integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
|
||||||
|
"license": "MIT AND ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-array": "^3.0.3",
|
||||||
|
"@types/d3-ease": "^3.0.0",
|
||||||
|
"@types/d3-interpolate": "^3.0.1",
|
||||||
|
"@types/d3-scale": "^4.0.2",
|
||||||
|
"@types/d3-shape": "^3.1.0",
|
||||||
|
"@types/d3-time": "^3.0.0",
|
||||||
|
"@types/d3-timer": "^3.0.0",
|
||||||
|
"d3-array": "^3.1.6",
|
||||||
|
"d3-ease": "^3.0.1",
|
||||||
|
"d3-interpolate": "^3.0.1",
|
||||||
|
"d3-scale": "^4.0.2",
|
||||||
|
"d3-shape": "^3.1.0",
|
||||||
|
"d3-time": "^3.0.0",
|
||||||
|
"d3-timer": "^3.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite-compatible-readable-stream": {
|
"node_modules/vite-compatible-readable-stream": {
|
||||||
"version": "3.6.1",
|
"version": "3.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz",
|
||||||
|
|||||||
+2
-1
@@ -28,6 +28,7 @@
|
|||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
"react-number-format": "^5.4.4",
|
"react-number-format": "^5.4.4",
|
||||||
"react-select": "^5.10.2",
|
"react-select": "^5.10.2",
|
||||||
|
"recharts": "^3.6.0",
|
||||||
"swr": "^2.3.6",
|
"swr": "^2.3.6",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"use-debounce": "^10.0.6",
|
"use-debounce": "^10.0.6",
|
||||||
@@ -50,4 +51,4 @@
|
|||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
|
import DashboardProduction from '@/components/pages/dashboard/DashboardProduction';
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
return (
|
return <DashboardProduction />;
|
||||||
<section className='w-full p-4'>
|
|
||||||
<h1 className='text-3xl font-bold text-primary'>Dashboard</h1>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Dashboard;
|
export default Dashboard;
|
||||||
|
|||||||
@@ -0,0 +1,399 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import Card from '@/components/Card';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
import ProductionLineChart from '@/components/pages/dashboard/chart/ProductionLineChart';
|
||||||
|
import StandardLineChart from '@/components/pages/dashboard/chart/StandardLineChart';
|
||||||
|
import EggWeightBarChart from '@/components/pages/dashboard/chart/EggWeightBarChart';
|
||||||
|
import FCRBarChart from '@/components/pages/dashboard/chart/FCRBarChart';
|
||||||
|
import ProductionStat from '@/components/pages/dashboard/chart/ProductionStat';
|
||||||
|
import Modal, { useModal } from '@/components/Modal';
|
||||||
|
import DateInput from '@/components/input/DateInput';
|
||||||
|
import SelectInput, {
|
||||||
|
OptionType,
|
||||||
|
useSelect,
|
||||||
|
} from '@/components/input/SelectInput';
|
||||||
|
import { RadioGroup } from '@/components/input/RadioInput';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import { DashboardApi } from '@/services/api/dashboard';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import dashboardProductionFilterSchema from '@/components/pages/dashboard/filter/DashboardProductionFilter.schema';
|
||||||
|
import { ProjectFlockApi } from '@/services/api/production';
|
||||||
|
import { ProductionStandardApi } from '@/services/api/master-data';
|
||||||
|
|
||||||
|
const DashboardProduction = () => {
|
||||||
|
const filterModal = useModal();
|
||||||
|
const [selectedPeriod, setSelectedPeriod] = useState('daily');
|
||||||
|
const [selectedStandards, setSelectedStandards] = useState<string[]>([
|
||||||
|
'hen_day',
|
||||||
|
'hen_house',
|
||||||
|
]);
|
||||||
|
const [endpointUrl, setEndpointUrl] = useState('/dashboard');
|
||||||
|
|
||||||
|
// ===== FETCH DATA =====
|
||||||
|
const {
|
||||||
|
data: dashboardProductionResponse,
|
||||||
|
isLoading: isLoadingDashboardProductionData,
|
||||||
|
error: dashboardProductionError,
|
||||||
|
} = useSWR(endpointUrl, () =>
|
||||||
|
DashboardApi.getDashboardProductionFetcher(endpointUrl)
|
||||||
|
);
|
||||||
|
|
||||||
|
const dashboardProductionData =
|
||||||
|
dashboardProductionResponse?.status === 'success'
|
||||||
|
? dashboardProductionResponse.data
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
// ===== SELECT =====
|
||||||
|
const { options: flockOptions, isLoadingOptions: isLoadingFlockOptions } =
|
||||||
|
useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', '', {
|
||||||
|
limit: 'limit',
|
||||||
|
category: 'LAYING',
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
options: standardProductionOptions,
|
||||||
|
isLoadingOptions: isLoadingStandardProductionOptions,
|
||||||
|
} = useSelect(ProductionStandardApi.basePath, 'id', 'name', '', {
|
||||||
|
limit: 'limit',
|
||||||
|
});
|
||||||
|
|
||||||
|
// ===== FORMIK =====
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
startDate: '',
|
||||||
|
endDate: '',
|
||||||
|
flock: [] as OptionType[],
|
||||||
|
standard_production_id: [] as OptionType[],
|
||||||
|
standard_productions: [] as OptionType[],
|
||||||
|
period: selectedPeriod,
|
||||||
|
},
|
||||||
|
validationSchema: dashboardProductionFilterSchema,
|
||||||
|
onSubmit: (values) => {
|
||||||
|
console.log(values);
|
||||||
|
// Build URL with query parameters
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
if (values.startDate) params.set('startDate', values.startDate);
|
||||||
|
if (values.endDate) params.set('endDate', values.endDate);
|
||||||
|
|
||||||
|
if (values.flock && values.flock.length > 0) {
|
||||||
|
const flockIds = values.flock
|
||||||
|
.map((f: OptionType) => f.value || f)
|
||||||
|
.join(',');
|
||||||
|
params.set('flock', flockIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
values.standard_production_id &&
|
||||||
|
values.standard_production_id.length > 0
|
||||||
|
) {
|
||||||
|
const standardIds = values.standard_production_id
|
||||||
|
.map((s: OptionType) => s.value || s)
|
||||||
|
.join(',');
|
||||||
|
params.set('standard_production_id', standardIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedStandards.length > 0) {
|
||||||
|
params.set('standards', selectedStandards.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
params.set('period', selectedPeriod);
|
||||||
|
|
||||||
|
const newUrl = `/dashboard?${params.toString()}`;
|
||||||
|
setEndpointUrl(newUrl);
|
||||||
|
|
||||||
|
// Close modal after applying filter
|
||||||
|
filterModal.closeModal();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleResetFilter = () => {
|
||||||
|
formik.resetForm();
|
||||||
|
setSelectedPeriod('daily');
|
||||||
|
setSelectedStandards(['hen_day', 'hen_house']);
|
||||||
|
setEndpointUrl('/dashboard');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoadingDashboardProductionData) {
|
||||||
|
return (
|
||||||
|
<div className='w-full min-h-screen flex items-center justify-center'>
|
||||||
|
<span className='loading loading-spinner loading-xl'></span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className='w-full p-4 space-y-6'>
|
||||||
|
<div className='flex flex-col sm:flex-row items-center justify-between gap-4'>
|
||||||
|
<h1 className='text-3xl font-bold text-primary'>Dashboard</h1>
|
||||||
|
<div className='flex flex-row justify-end gap-2'>
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
className='min-w-28 rounded-lg'
|
||||||
|
onClick={() => filterModal.openModal()}
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:funnel' width={20} height={20} />
|
||||||
|
Filter
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='neutral'
|
||||||
|
className='min-w-28 rounded-lg'
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:arrow-down-tray' width={20} height={20} />
|
||||||
|
Export
|
||||||
|
<Icon icon='heroicons:chevron-down' width={20} height={20} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Dashboard Statistics */}
|
||||||
|
<ProductionStat data={dashboardProductionData?.statistics_data} />
|
||||||
|
|
||||||
|
{/* Charts Grid */}
|
||||||
|
<div className='grid grid-cols-1 gap-4'>
|
||||||
|
{/* Production Line Chart */}
|
||||||
|
<Card
|
||||||
|
variant='bordered'
|
||||||
|
className={{ wrapper: 'w-full', body: 'p-6' }}
|
||||||
|
>
|
||||||
|
<ProductionLineChart
|
||||||
|
period={
|
||||||
|
selectedPeriod as 'daily' | 'weekly' | 'monthly' | 'yearly'
|
||||||
|
}
|
||||||
|
data={dashboardProductionData?.production_charts}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Standard Line Chart */}
|
||||||
|
<Card
|
||||||
|
variant='bordered'
|
||||||
|
className={{ wrapper: 'w-full', body: 'p-6' }}
|
||||||
|
>
|
||||||
|
<StandardLineChart
|
||||||
|
selectedStandards={selectedStandards}
|
||||||
|
data={dashboardProductionData?.standard_productions}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Bar Charts Grid - 2 columns */}
|
||||||
|
<div className='grid grid-cols-1 lg:grid-cols-2 gap-4'>
|
||||||
|
{/* FCR Bar Chart */}
|
||||||
|
<Card
|
||||||
|
variant='bordered'
|
||||||
|
className={{ wrapper: 'w-full', body: 'p-6' }}
|
||||||
|
>
|
||||||
|
<FCRBarChart data={dashboardProductionData?.fcr_data} />
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Egg Weight Bar Chart */}
|
||||||
|
<Card
|
||||||
|
variant='bordered'
|
||||||
|
className={{ wrapper: 'w-full', body: 'p-6' }}
|
||||||
|
>
|
||||||
|
<EggWeightBarChart data={dashboardProductionData?.egg_weights} />
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<Modal
|
||||||
|
ref={filterModal.ref}
|
||||||
|
className={{
|
||||||
|
modal: 'p-0',
|
||||||
|
modalBox: 'p-0 rounded-xl',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='space-y-6'>
|
||||||
|
{/* Modal Header */}
|
||||||
|
<div className='flex items-center justify-between gap-2 py-3 border-b border-gray-300'>
|
||||||
|
<div className='flex items-center gap-2 ms-4'>
|
||||||
|
<Icon icon='heroicons:funnel' width={20} height={20} />
|
||||||
|
<h3 className='font-semibold'>Filter Data</h3>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant='link'
|
||||||
|
onClick={() => filterModal.closeModal()}
|
||||||
|
className='text-gray-500 hover:text-gray-700 me-4 '
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form className='space-y-4' onSubmit={formik.handleSubmit}>
|
||||||
|
{/* Rentang Waktu */}
|
||||||
|
<div className='px-4'>
|
||||||
|
<label className='flex items-center gap-2 mb-3'>
|
||||||
|
<Icon icon='heroicons:calendar' width={20} height={20} />
|
||||||
|
Rentang Waktu
|
||||||
|
</label>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<DateInput
|
||||||
|
name='startDate'
|
||||||
|
placeholder='Tanggal Mulai'
|
||||||
|
value={formik.values.startDate}
|
||||||
|
errorMessage={formik.errors.startDate}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
className={{
|
||||||
|
inputWrapper: 'rounded-lg',
|
||||||
|
}}
|
||||||
|
isError={
|
||||||
|
Boolean(formik.errors.startDate) &&
|
||||||
|
Boolean(formik.touched.startDate)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className='hidden md:block text-center'>—</span>
|
||||||
|
<DateInput
|
||||||
|
name='endDate'
|
||||||
|
placeholder='Tanggal Akhir'
|
||||||
|
value={formik.values.endDate}
|
||||||
|
errorMessage={formik.errors.endDate}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
className={{
|
||||||
|
inputWrapper: 'rounded-lg',
|
||||||
|
}}
|
||||||
|
isError={
|
||||||
|
Boolean(formik.errors.endDate) &&
|
||||||
|
Boolean(formik.touched.endDate)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Flock */}
|
||||||
|
<div className='px-4'>
|
||||||
|
<SelectInput
|
||||||
|
label='Flock'
|
||||||
|
value={formik.values.flock}
|
||||||
|
onChange={(selected) => formik.setFieldValue('flock', selected)}
|
||||||
|
errorMessage={formik.errors.flock as string}
|
||||||
|
options={flockOptions}
|
||||||
|
isLoading={isLoadingFlockOptions}
|
||||||
|
isMulti
|
||||||
|
isError={
|
||||||
|
Boolean(formik.errors.flock) && Boolean(formik.touched.flock)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Production */}
|
||||||
|
<div className='px-4'>
|
||||||
|
<SelectInput
|
||||||
|
label='Standard Produksi'
|
||||||
|
value={formik.values.standard_production_id}
|
||||||
|
onChange={(selected) =>
|
||||||
|
formik.setFieldValue('standard_production_id', selected)
|
||||||
|
}
|
||||||
|
errorMessage={formik.errors.standard_production_id as string}
|
||||||
|
options={standardProductionOptions}
|
||||||
|
isLoading={isLoadingStandardProductionOptions}
|
||||||
|
isMulti
|
||||||
|
isError={
|
||||||
|
Boolean(formik.errors.standard_production_id) &&
|
||||||
|
Boolean(formik.touched.standard_production_id)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Standard */}
|
||||||
|
<div className='px-4'>
|
||||||
|
<SelectInput
|
||||||
|
label='Standard'
|
||||||
|
value={selectedStandards.map((s) => ({
|
||||||
|
value: s,
|
||||||
|
label:
|
||||||
|
s === 'hen_day'
|
||||||
|
? 'Hen Day'
|
||||||
|
: s === 'hen_house'
|
||||||
|
? 'Hen House'
|
||||||
|
: s === 'uniformity'
|
||||||
|
? 'Uniformity'
|
||||||
|
: s === 'egg_weight'
|
||||||
|
? 'Egg Weight'
|
||||||
|
: 'Egg Mass',
|
||||||
|
}))}
|
||||||
|
options={[
|
||||||
|
{ value: 'hen_day', label: 'Hen Day' },
|
||||||
|
{ value: 'hen_house', label: 'Hen House' },
|
||||||
|
{ value: 'uniformity', label: 'Uniformity' },
|
||||||
|
{ value: 'egg_weight', label: 'Egg Weight' },
|
||||||
|
{ value: 'egg_mass', label: 'Egg Mass' },
|
||||||
|
]}
|
||||||
|
isMulti
|
||||||
|
onChange={(selected: OptionType | OptionType[] | null) => {
|
||||||
|
const values = Array.isArray(selected)
|
||||||
|
? selected.map((item) => String(item.value))
|
||||||
|
: [];
|
||||||
|
setSelectedStandards(
|
||||||
|
values.length > 0 ? values : ['hen_day']
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
isError={
|
||||||
|
Boolean(formik.errors.standard_productions) &&
|
||||||
|
Boolean(formik.touched.standard_productions)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Periode Perbandingan */}
|
||||||
|
<div className='px-4'>
|
||||||
|
<label className='block mb-3'>Periode Perbandingan</label>
|
||||||
|
<div className='grid grid-cols-4 gap-2'>
|
||||||
|
<Button
|
||||||
|
variant={selectedPeriod === 'daily' ? 'active' : 'soft'}
|
||||||
|
type='button'
|
||||||
|
className='rounded-lg'
|
||||||
|
onClick={() => setSelectedPeriod('daily')}
|
||||||
|
>
|
||||||
|
Harian
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={selectedPeriod === 'weekly' ? 'active' : 'soft'}
|
||||||
|
type='button'
|
||||||
|
className='rounded-lg'
|
||||||
|
onClick={() => setSelectedPeriod('weekly')}
|
||||||
|
>
|
||||||
|
Mingguan
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={selectedPeriod === 'monthly' ? 'active' : 'soft'}
|
||||||
|
type='button'
|
||||||
|
className='rounded-lg'
|
||||||
|
onClick={() => setSelectedPeriod('monthly')}
|
||||||
|
>
|
||||||
|
Bulanan
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={selectedPeriod === 'yearly' ? 'active' : 'soft'}
|
||||||
|
type='button'
|
||||||
|
className='rounded-lg'
|
||||||
|
onClick={() => setSelectedPeriod('yearly')}
|
||||||
|
>
|
||||||
|
Tahunan
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<div className='flex justify-between gap-4 py-4 mt-8 border-t border-gray-300 bg-gray-100'>
|
||||||
|
<Button
|
||||||
|
type='reset'
|
||||||
|
variant='soft'
|
||||||
|
className='ms-4 min-w-36 rounded-lg'
|
||||||
|
onClick={handleResetFilter}
|
||||||
|
>
|
||||||
|
Reset Filter
|
||||||
|
</Button>
|
||||||
|
<Button type='submit' className='me-4 min-w-36 rounded-lg'>
|
||||||
|
Terapkan Filter
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardProduction;
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BarChart,
|
||||||
|
Bar,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
CartesianGrid,
|
||||||
|
Tooltip,
|
||||||
|
ResponsiveContainer,
|
||||||
|
Cell,
|
||||||
|
} from 'recharts';
|
||||||
|
import { DashboardProductionEggWeights } from '@/types/api/dashboard/dashboard-production';
|
||||||
|
|
||||||
|
interface EggWeightBarChartProps {
|
||||||
|
data?: DashboardProductionEggWeights[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const EggWeightBarChart = ({ data }: EggWeightBarChartProps) => {
|
||||||
|
// Show loading state if no data
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className='w-full h-full'>
|
||||||
|
<h3 className='text-lg font-semibold mb-4'>
|
||||||
|
Rata-rata Berat Telur (EW)
|
||||||
|
</h3>
|
||||||
|
<div className='flex items-center justify-center h-[350px]'>
|
||||||
|
<p className='text-gray-500'>Memuat data...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full h-full'>
|
||||||
|
<h3 className='text-lg font-semibold mb-4'>Rata-rata Berat Telur (EW)</h3>
|
||||||
|
<ResponsiveContainer width='100%' height={350}>
|
||||||
|
<BarChart
|
||||||
|
data={data}
|
||||||
|
margin={{
|
||||||
|
top: 5,
|
||||||
|
right: 30,
|
||||||
|
left: 0,
|
||||||
|
bottom: 5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CartesianGrid strokeDasharray='3 3' stroke='#e5e7eb' />
|
||||||
|
<XAxis
|
||||||
|
dataKey='flock.name'
|
||||||
|
tick={{ fontSize: 12 }}
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={{ stroke: '#e5e7eb' }}
|
||||||
|
/>
|
||||||
|
<YAxis
|
||||||
|
tick={{ fontSize: 12 }}
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={{ stroke: '#e5e7eb' }}
|
||||||
|
domain={[0, 'auto']}
|
||||||
|
label={{
|
||||||
|
value: 'Berat (gram)',
|
||||||
|
angle: -90,
|
||||||
|
position: 'insideLeft',
|
||||||
|
style: { fontSize: 12 },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
contentStyle={{
|
||||||
|
backgroundColor: 'white',
|
||||||
|
border: '1px solid #e5e7eb',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '8px 12px',
|
||||||
|
}}
|
||||||
|
formatter={(value: number | undefined) =>
|
||||||
|
value !== undefined ? [`${value} gram`, ''] : ['', '']
|
||||||
|
}
|
||||||
|
cursor={{ fill: 'rgba(59, 130, 246, 0.1)' }}
|
||||||
|
/>
|
||||||
|
<Bar dataKey='weight' radius={[8, 8, 0, 0]}>
|
||||||
|
{data.map((entry, index) => (
|
||||||
|
<Cell key={`cell-${index}`} fill='#3b82f6' />
|
||||||
|
))}
|
||||||
|
</Bar>
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EggWeightBarChart;
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BarChart,
|
||||||
|
Bar,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
CartesianGrid,
|
||||||
|
Tooltip,
|
||||||
|
ResponsiveContainer,
|
||||||
|
Cell,
|
||||||
|
} from 'recharts';
|
||||||
|
import { DashboardProductionFcrData } from '@/types/api/dashboard/dashboard-production';
|
||||||
|
|
||||||
|
interface FCRBarChartProps {
|
||||||
|
data?: DashboardProductionFcrData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternating colors: green and red
|
||||||
|
const colors = ['#10b981', '#ef4444'];
|
||||||
|
|
||||||
|
const FCRBarChart = ({ data }: FCRBarChartProps) => {
|
||||||
|
// Show loading state if no data
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className='w-full h-full'>
|
||||||
|
<h3 className='text-lg font-semibold mb-4'>
|
||||||
|
Feed Conversion Ratio (FCR)
|
||||||
|
</h3>
|
||||||
|
<div className='flex items-center justify-center h-[350px]'>
|
||||||
|
<p className='text-gray-500'>Memuat data...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full h-full'>
|
||||||
|
<h3 className='text-lg font-semibold mb-4'>
|
||||||
|
Feed Conversion Ratio (FCR)
|
||||||
|
</h3>
|
||||||
|
<ResponsiveContainer width='100%' height={350}>
|
||||||
|
<BarChart
|
||||||
|
data={data}
|
||||||
|
margin={{
|
||||||
|
top: 5,
|
||||||
|
right: 30,
|
||||||
|
left: 0,
|
||||||
|
bottom: 5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CartesianGrid strokeDasharray='3 3' stroke='#e5e7eb' />
|
||||||
|
<XAxis
|
||||||
|
dataKey='flock.name'
|
||||||
|
tick={{ fontSize: 12 }}
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={{ stroke: '#e5e7eb' }}
|
||||||
|
/>
|
||||||
|
<YAxis
|
||||||
|
tick={{ fontSize: 12 }}
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={{ stroke: '#e5e7eb' }}
|
||||||
|
domain={[0, 'auto']}
|
||||||
|
label={{
|
||||||
|
value: 'FCR',
|
||||||
|
angle: -90,
|
||||||
|
position: 'insideLeft',
|
||||||
|
style: { fontSize: 12 },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
contentStyle={{
|
||||||
|
backgroundColor: 'white',
|
||||||
|
border: '1px solid #e5e7eb',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '8px 12px',
|
||||||
|
}}
|
||||||
|
formatter={(value: number | undefined) =>
|
||||||
|
value !== undefined ? [value.toFixed(2), 'FCR'] : ['', '']
|
||||||
|
}
|
||||||
|
cursor={{ fill: 'rgba(16, 185, 129, 0.1)' }}
|
||||||
|
/>
|
||||||
|
<Bar dataKey='fcr' radius={[8, 8, 0, 0]}>
|
||||||
|
{data.map((entry, index) => (
|
||||||
|
<Cell
|
||||||
|
key={`cell-${index}`}
|
||||||
|
fill={colors[index % colors.length]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Bar>
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FCRBarChart;
|
||||||
@@ -0,0 +1,357 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
LineChart,
|
||||||
|
Line,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
CartesianGrid,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
ResponsiveContainer,
|
||||||
|
} from 'recharts';
|
||||||
|
|
||||||
|
// Sample data in API format
|
||||||
|
const sampleApiData: ProductionChartItem[] = [
|
||||||
|
{
|
||||||
|
date: '2025-12-01T00:00:00Z',
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-002', data: 88 },
|
||||||
|
{ id: 2, name: 'Flock A-001', data: 92 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 90 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 85 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2025-12-03T00:00:00Z',
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-002', data: 85 },
|
||||||
|
{ id: 2, name: 'Flock A-001', data: 95 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 93 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 87 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2025-12-05T00:00:00Z',
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-002', data: 82 },
|
||||||
|
{ id: 2, name: 'Flock A-001', data: 98 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 91 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 84 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2025-12-07T00:00:00Z',
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-002', data: 80 },
|
||||||
|
{ id: 2, name: 'Flock A-001', data: 89 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 88 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 82 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2025-12-08T00:00:00Z',
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-002', data: 83 },
|
||||||
|
{ id: 2, name: 'Flock A-001', data: 92 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 95 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 85 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2025-12-11T00:00:00Z',
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-002', data: 81 },
|
||||||
|
{ id: 2, name: 'Flock A-001', data: 88 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 92 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 83 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2025-12-13T00:00:00Z',
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-002', data: 84 },
|
||||||
|
{ id: 2, name: 'Flock A-001', data: 90 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 89 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 86 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2025-12-15T00:00:00Z',
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-002', data: 82 },
|
||||||
|
{ id: 2, name: 'Flock A-001', data: 94 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 96 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 84 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2025-12-17T00:00:00Z',
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-002', data: 80 },
|
||||||
|
{ id: 2, name: 'Flock A-001', data: 91 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 93 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 82 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2025-12-19T00:00:00Z',
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-002', data: 79 },
|
||||||
|
{ id: 2, name: 'Flock A-001', data: 88 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 90 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 81 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2025-12-21T00:00:00Z',
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-002', data: 81 },
|
||||||
|
{ id: 2, name: 'Flock A-001', data: 97 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 92 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 83 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2025-12-23T00:00:00Z',
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-002', data: 83 },
|
||||||
|
{ id: 2, name: 'Flock A-001', data: 95 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 98 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 85 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2025-12-25T00:00:00Z',
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-002', data: 80 },
|
||||||
|
{ id: 2, name: 'Flock A-001', data: 89 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 94 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 82 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2025-12-27T00:00:00Z',
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-002', data: 82 },
|
||||||
|
{ id: 2, name: 'Flock A-001', data: 93 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 96 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 84 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2025-12-28T00:00:00Z',
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-002', data: 85 },
|
||||||
|
{ id: 2, name: 'Flock A-001', data: 96 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 95 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 87 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Helper function to format date based on period
|
||||||
|
const formatDateByPeriod = (
|
||||||
|
dateString: string,
|
||||||
|
period: 'daily' | 'weekly' | 'monthly' | 'yearly'
|
||||||
|
): string => {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
const monthNames = [
|
||||||
|
'Jan',
|
||||||
|
'Feb',
|
||||||
|
'Mar',
|
||||||
|
'Apr',
|
||||||
|
'Mei',
|
||||||
|
'Jun',
|
||||||
|
'Jul',
|
||||||
|
'Agu',
|
||||||
|
'Sep',
|
||||||
|
'Okt',
|
||||||
|
'Nov',
|
||||||
|
'Des',
|
||||||
|
];
|
||||||
|
|
||||||
|
switch (period) {
|
||||||
|
case 'daily':
|
||||||
|
// Format: "1 Des"
|
||||||
|
return `${date.getDate()} ${monthNames[date.getMonth()]}`;
|
||||||
|
|
||||||
|
case 'weekly':
|
||||||
|
// Format: "Week 1 Des"
|
||||||
|
const weekNumber = Math.ceil(date.getDate() / 7);
|
||||||
|
return `Week ${weekNumber} ${monthNames[date.getMonth()]}`;
|
||||||
|
|
||||||
|
case 'monthly':
|
||||||
|
// Format: "Des"
|
||||||
|
return monthNames[date.getMonth()];
|
||||||
|
|
||||||
|
case 'yearly':
|
||||||
|
// Format: "2025"
|
||||||
|
return date.getFullYear().toString();
|
||||||
|
|
||||||
|
default:
|
||||||
|
return dateString;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Type definitions for API data
|
||||||
|
interface FlockData {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
data: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProductionChartItem {
|
||||||
|
date: string;
|
||||||
|
flocks: FlockData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProductionChartsData {
|
||||||
|
production_charts: ProductionChartItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform API data to Recharts format
|
||||||
|
const transformProductionData = (apiData: ProductionChartItem[]) => {
|
||||||
|
return apiData.map((item) => {
|
||||||
|
const transformed: Record<string, string | number> = {
|
||||||
|
date: item.date.split('T')[0], // Extract YYYY-MM-DD from ISO string
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add each flock's data as a property
|
||||||
|
item.flocks.forEach((flock) => {
|
||||||
|
transformed[flock.name] = flock.data;
|
||||||
|
});
|
||||||
|
|
||||||
|
return transformed;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ProductionLineChartProps {
|
||||||
|
period?: 'daily' | 'weekly' | 'monthly' | 'yearly';
|
||||||
|
data?: ProductionChartItem[]; // Optional API data
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductionLineChart = ({
|
||||||
|
period = 'daily',
|
||||||
|
data: apiData,
|
||||||
|
}: ProductionLineChartProps) => {
|
||||||
|
// State to track which lines are hidden
|
||||||
|
const [hiddenLines, setHiddenLines] = useState<string[]>([]);
|
||||||
|
|
||||||
|
// Use API data if provided, otherwise use sample data
|
||||||
|
const chartData = apiData
|
||||||
|
? transformProductionData(apiData)
|
||||||
|
: transformProductionData(sampleApiData);
|
||||||
|
|
||||||
|
// Handle legend click to show/hide lines
|
||||||
|
const handleLegendClick = (dataKey: string) => {
|
||||||
|
setHiddenLines((prev) =>
|
||||||
|
prev.includes(dataKey)
|
||||||
|
? prev.filter((key) => key !== dataKey)
|
||||||
|
: [...prev, dataKey]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full h-full'>
|
||||||
|
<h3 className='text-lg font-semibold mb-4'>
|
||||||
|
Performa Produksi per Flock
|
||||||
|
</h3>
|
||||||
|
<ResponsiveContainer width='100%' height={400}>
|
||||||
|
<LineChart
|
||||||
|
data={chartData}
|
||||||
|
margin={{
|
||||||
|
top: 5,
|
||||||
|
right: 30,
|
||||||
|
left: 0,
|
||||||
|
bottom: 5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CartesianGrid strokeDasharray='3 3' stroke='#e5e7eb' />
|
||||||
|
<XAxis
|
||||||
|
dataKey='date'
|
||||||
|
tick={{ fontSize: 12 }}
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={{ stroke: '#e5e7eb' }}
|
||||||
|
tickFormatter={(value) => formatDateByPeriod(value, period)}
|
||||||
|
/>
|
||||||
|
<YAxis
|
||||||
|
tick={{ fontSize: 12 }}
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={{ stroke: '#e5e7eb' }}
|
||||||
|
domain={[0, 100]}
|
||||||
|
label={{
|
||||||
|
value: 'Persentase (%)',
|
||||||
|
angle: -90,
|
||||||
|
position: 'insideLeft',
|
||||||
|
style: { fontSize: 12 },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
contentStyle={{
|
||||||
|
backgroundColor: 'white',
|
||||||
|
border: '1px solid #e5e7eb',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '8px 12px',
|
||||||
|
}}
|
||||||
|
labelFormatter={(value) =>
|
||||||
|
formatDateByPeriod(value as string, period)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Legend
|
||||||
|
wrapperStyle={{
|
||||||
|
paddingTop: '20px',
|
||||||
|
}}
|
||||||
|
iconType='circle'
|
||||||
|
onClick={(e) => {
|
||||||
|
if (e.dataKey) handleLegendClick(e.dataKey as string);
|
||||||
|
}}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
type='monotone'
|
||||||
|
dataKey='Flock A-002'
|
||||||
|
stroke='#3b82f6'
|
||||||
|
strokeWidth={2}
|
||||||
|
dot={{ r: 4, fill: '#3b82f6' }}
|
||||||
|
activeDot={{ r: 6 }}
|
||||||
|
hide={hiddenLines.includes('Flock A-002')}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
type='monotone'
|
||||||
|
dataKey='Flock A-001'
|
||||||
|
stroke='#10b981'
|
||||||
|
strokeWidth={2}
|
||||||
|
dot={{ r: 4, fill: '#10b981' }}
|
||||||
|
activeDot={{ r: 6 }}
|
||||||
|
hide={hiddenLines.includes('Flock A-001')}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
type='monotone'
|
||||||
|
dataKey='Flock B-001'
|
||||||
|
stroke='#f59e0b'
|
||||||
|
strokeWidth={2}
|
||||||
|
dot={{ r: 4, fill: '#f59e0b' }}
|
||||||
|
activeDot={{ r: 6 }}
|
||||||
|
hide={hiddenLines.includes('Flock B-001')}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
type='monotone'
|
||||||
|
dataKey='Flock B-002'
|
||||||
|
stroke='#ef4444'
|
||||||
|
strokeWidth={2}
|
||||||
|
dot={{ r: 4, fill: '#ef4444' }}
|
||||||
|
activeDot={{ r: 6 }}
|
||||||
|
hide={hiddenLines.includes('Flock B-002')}
|
||||||
|
/>
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductionLineChart;
|
||||||
|
|
||||||
|
// Export types for external use
|
||||||
|
export type { FlockData, ProductionChartItem, ProductionChartsData };
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import Card from '@/components/Card';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
import { DashboardProductionStatisticsData } from '@/types/api/dashboard/dashboard-production';
|
||||||
|
import { formatCurrency } from '@/lib/helper';
|
||||||
|
|
||||||
|
interface ProductionStatProps {
|
||||||
|
data?: DashboardProductionStatisticsData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductionStat = ({ data }: ProductionStatProps) => {
|
||||||
|
// Helper function to get icon based on title
|
||||||
|
const getIcon = (title: string) => {
|
||||||
|
if (title.toLowerCase().includes('keuangan'))
|
||||||
|
return 'heroicons:currency-dollar';
|
||||||
|
if (title.toLowerCase().includes('penjualan'))
|
||||||
|
return 'heroicons:arrow-trending-up';
|
||||||
|
if (title.toLowerCase().includes('pembelian'))
|
||||||
|
return 'heroicons:shopping-cart';
|
||||||
|
if (title.toLowerCase().includes('overhead')) return 'heroicons:calculator';
|
||||||
|
return 'heroicons:chart-bar';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to get icon background color
|
||||||
|
const getIconBgColor = (title: string) => {
|
||||||
|
if (title.toLowerCase().includes('keuangan')) return 'bg-blue-500';
|
||||||
|
if (title.toLowerCase().includes('penjualan')) return 'bg-green-500';
|
||||||
|
if (title.toLowerCase().includes('pembelian')) return 'bg-orange-500';
|
||||||
|
if (title.toLowerCase().includes('overhead')) return 'bg-purple-500';
|
||||||
|
return 'bg-gray-500';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Show loading state if no data
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
return (
|
||||||
|
<section className='grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4'>
|
||||||
|
{[1, 2, 3, 4].map((i) => (
|
||||||
|
<Card
|
||||||
|
key={i}
|
||||||
|
variant='bordered'
|
||||||
|
className={{ wrapper: 'w-full', body: 'p-4' }}
|
||||||
|
>
|
||||||
|
<div className='animate-pulse'>
|
||||||
|
<div className='h-4 bg-gray-200 rounded w-1/2 mb-2'></div>
|
||||||
|
<div className='h-6 bg-gray-200 rounded w-3/4 mb-1'></div>
|
||||||
|
<div className='h-4 bg-gray-200 rounded w-1/3'></div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className='grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4'>
|
||||||
|
{data.map((stat, index) => (
|
||||||
|
<Card
|
||||||
|
key={index}
|
||||||
|
variant='bordered'
|
||||||
|
className={{ wrapper: 'w-full', body: 'p-4' }}
|
||||||
|
>
|
||||||
|
<div className='flex items-start justify-between'>
|
||||||
|
<div className='flex-1'>
|
||||||
|
<p className='text-sm text-gray-600 mb-2'>{stat.title}</p>
|
||||||
|
<p className='text-xl font-bold text-gray-900 mb-1'>
|
||||||
|
{formatCurrency(stat.value)}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className={`text-sm flex items-center gap-1 ${
|
||||||
|
stat.changeType === 'increase'
|
||||||
|
? 'text-green-600'
|
||||||
|
: 'text-red-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon={
|
||||||
|
stat.changeType === 'increase'
|
||||||
|
? 'heroicons:arrow-trending-up'
|
||||||
|
: 'heroicons:arrow-trending-down'
|
||||||
|
}
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
/>
|
||||||
|
{stat.change > 0 ? '+' : ''}
|
||||||
|
{stat.change}% vs{' '}
|
||||||
|
{stat.period === 'monthly' ? 'bulan lalu' : 'periode lalu'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className='flex-shrink-0'>
|
||||||
|
<div
|
||||||
|
className={`w-12 h-12 rounded-lg ${getIconBgColor(stat.title)} flex items-center justify-center`}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon={getIcon(stat.title)}
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className='text-white'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductionStat;
|
||||||
@@ -0,0 +1,691 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
LineChart,
|
||||||
|
Line,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
CartesianGrid,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
ResponsiveContainer,
|
||||||
|
} from 'recharts';
|
||||||
|
|
||||||
|
// Type definitions for API data
|
||||||
|
interface FlockData {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
data: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StandardData {
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StandardChartItem {
|
||||||
|
week: number;
|
||||||
|
standards: StandardData[];
|
||||||
|
flocks: FlockData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sample data in API format
|
||||||
|
const sampleApiData: StandardChartItem[] = [
|
||||||
|
{
|
||||||
|
week: 18,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 40 },
|
||||||
|
{ name: 'hen_house', value: 38 },
|
||||||
|
{ name: 'uniformity', value: 85 },
|
||||||
|
{ name: 'egg_weight', value: 52 },
|
||||||
|
{ name: 'egg_mass', value: 20 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 38 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 37 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 39 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 36 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 20,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 45 },
|
||||||
|
{ name: 'hen_house', value: 43 },
|
||||||
|
{ name: 'uniformity', value: 86 },
|
||||||
|
{ name: 'egg_weight', value: 54 },
|
||||||
|
{ name: 'egg_mass', value: 24 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 43 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 42 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 44 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 41 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 22,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 48 },
|
||||||
|
{ name: 'hen_house', value: 46 },
|
||||||
|
{ name: 'uniformity', value: 87 },
|
||||||
|
{ name: 'egg_weight', value: 55 },
|
||||||
|
{ name: 'egg_mass', value: 26 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 47 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 46 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 48 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 45 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 24,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 50 },
|
||||||
|
{ name: 'hen_house', value: 48 },
|
||||||
|
{ name: 'uniformity', value: 88 },
|
||||||
|
{ name: 'egg_weight', value: 56 },
|
||||||
|
{ name: 'egg_mass', value: 28 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 49 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 48 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 50 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 47 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 26,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 52 },
|
||||||
|
{ name: 'hen_house', value: 50 },
|
||||||
|
{ name: 'uniformity', value: 89 },
|
||||||
|
{ name: 'egg_weight', value: 57 },
|
||||||
|
{ name: 'egg_mass', value: 30 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 50 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 49 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 51 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 48 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 28,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 55 },
|
||||||
|
{ name: 'hen_house', value: 53 },
|
||||||
|
{ name: 'uniformity', value: 90 },
|
||||||
|
{ name: 'egg_weight', value: 58 },
|
||||||
|
{ name: 'egg_mass', value: 32 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 53 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 52 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 54 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 51 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 30,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 58 },
|
||||||
|
{ name: 'hen_house', value: 56 },
|
||||||
|
{ name: 'uniformity', value: 91 },
|
||||||
|
{ name: 'egg_weight', value: 59 },
|
||||||
|
{ name: 'egg_mass', value: 34 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 55 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 54 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 56 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 53 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 32,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 60 },
|
||||||
|
{ name: 'hen_house', value: 58 },
|
||||||
|
{ name: 'uniformity', value: 92 },
|
||||||
|
{ name: 'egg_weight', value: 60 },
|
||||||
|
{ name: 'egg_mass', value: 36 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 58 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 57 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 59 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 56 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 34,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 62 },
|
||||||
|
{ name: 'hen_house', value: 60 },
|
||||||
|
{ name: 'uniformity', value: 92 },
|
||||||
|
{ name: 'egg_weight', value: 61 },
|
||||||
|
{ name: 'egg_mass', value: 38 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 60 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 59 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 61 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 58 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 36,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 64 },
|
||||||
|
{ name: 'hen_house', value: 62 },
|
||||||
|
{ name: 'uniformity', value: 93 },
|
||||||
|
{ name: 'egg_weight', value: 62 },
|
||||||
|
{ name: 'egg_mass', value: 40 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 62 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 61 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 63 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 60 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 38,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 66 },
|
||||||
|
{ name: 'hen_house', value: 64 },
|
||||||
|
{ name: 'uniformity', value: 93 },
|
||||||
|
{ name: 'egg_weight', value: 63 },
|
||||||
|
{ name: 'egg_mass', value: 42 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 64 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 63 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 65 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 62 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 40,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 68 },
|
||||||
|
{ name: 'hen_house', value: 66 },
|
||||||
|
{ name: 'uniformity', value: 94 },
|
||||||
|
{ name: 'egg_weight', value: 64 },
|
||||||
|
{ name: 'egg_mass', value: 44 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 66 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 65 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 67 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 64 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 42,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 70 },
|
||||||
|
{ name: 'hen_house', value: 68 },
|
||||||
|
{ name: 'uniformity', value: 94 },
|
||||||
|
{ name: 'egg_weight', value: 65 },
|
||||||
|
{ name: 'egg_mass', value: 46 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 68 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 67 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 69 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 66 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 44,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 72 },
|
||||||
|
{ name: 'hen_house', value: 70 },
|
||||||
|
{ name: 'uniformity', value: 95 },
|
||||||
|
{ name: 'egg_weight', value: 66 },
|
||||||
|
{ name: 'egg_mass', value: 48 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 70 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 69 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 71 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 68 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 46,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 74 },
|
||||||
|
{ name: 'hen_house', value: 72 },
|
||||||
|
{ name: 'uniformity', value: 95 },
|
||||||
|
{ name: 'egg_weight', value: 67 },
|
||||||
|
{ name: 'egg_mass', value: 50 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 72 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 71 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 73 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 70 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 48,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 76 },
|
||||||
|
{ name: 'hen_house', value: 74 },
|
||||||
|
{ name: 'uniformity', value: 95 },
|
||||||
|
{ name: 'egg_weight', value: 68 },
|
||||||
|
{ name: 'egg_mass', value: 52 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 74 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 73 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 75 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 72 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 50,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 78 },
|
||||||
|
{ name: 'hen_house', value: 76 },
|
||||||
|
{ name: 'uniformity', value: 96 },
|
||||||
|
{ name: 'egg_weight', value: 69 },
|
||||||
|
{ name: 'egg_mass', value: 54 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 76 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 75 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 77 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 74 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 52,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 80 },
|
||||||
|
{ name: 'hen_house', value: 78 },
|
||||||
|
{ name: 'uniformity', value: 96 },
|
||||||
|
{ name: 'egg_weight', value: 70 },
|
||||||
|
{ name: 'egg_mass', value: 56 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 78 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 77 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 79 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 76 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 54,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 82 },
|
||||||
|
{ name: 'hen_house', value: 80 },
|
||||||
|
{ name: 'uniformity', value: 96 },
|
||||||
|
{ name: 'egg_weight', value: 71 },
|
||||||
|
{ name: 'egg_mass', value: 58 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 80 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 79 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 81 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 78 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 56,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 84 },
|
||||||
|
{ name: 'hen_house', value: 82 },
|
||||||
|
{ name: 'uniformity', value: 97 },
|
||||||
|
{ name: 'egg_weight', value: 72 },
|
||||||
|
{ name: 'egg_mass', value: 60 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 82 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 81 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 83 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 80 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 58,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 86 },
|
||||||
|
{ name: 'hen_house', value: 84 },
|
||||||
|
{ name: 'uniformity', value: 97 },
|
||||||
|
{ name: 'egg_weight', value: 73 },
|
||||||
|
{ name: 'egg_mass', value: 62 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 84 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 83 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 85 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 82 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 60,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 88 },
|
||||||
|
{ name: 'hen_house', value: 86 },
|
||||||
|
{ name: 'uniformity', value: 97 },
|
||||||
|
{ name: 'egg_weight', value: 74 },
|
||||||
|
{ name: 'egg_mass', value: 64 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 86 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 85 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 87 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 84 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 62,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 90 },
|
||||||
|
{ name: 'hen_house', value: 88 },
|
||||||
|
{ name: 'uniformity', value: 98 },
|
||||||
|
{ name: 'egg_weight', value: 75 },
|
||||||
|
{ name: 'egg_mass', value: 66 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 88 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 87 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 89 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 86 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 64,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 92 },
|
||||||
|
{ name: 'hen_house', value: 90 },
|
||||||
|
{ name: 'uniformity', value: 98 },
|
||||||
|
{ name: 'egg_weight', value: 76 },
|
||||||
|
{ name: 'egg_mass', value: 68 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 90 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 89 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 91 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 88 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 66,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 94 },
|
||||||
|
{ name: 'hen_house', value: 92 },
|
||||||
|
{ name: 'uniformity', value: 98 },
|
||||||
|
{ name: 'egg_weight', value: 77 },
|
||||||
|
{ name: 'egg_mass', value: 70 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 92 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 91 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 93 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 90 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 68,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 95 },
|
||||||
|
{ name: 'hen_house', value: 93 },
|
||||||
|
{ name: 'uniformity', value: 98 },
|
||||||
|
{ name: 'egg_weight', value: 78 },
|
||||||
|
{ name: 'egg_mass', value: 72 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 93 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 92 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 94 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 91 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 70,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 96 },
|
||||||
|
{ name: 'hen_house', value: 94 },
|
||||||
|
{ name: 'uniformity', value: 99 },
|
||||||
|
{ name: 'egg_weight', value: 79 },
|
||||||
|
{ name: 'egg_mass', value: 74 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 94 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 93 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 95 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 92 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
week: 72,
|
||||||
|
standards: [
|
||||||
|
{ name: 'hen_day', value: 97 },
|
||||||
|
{ name: 'hen_house', value: 95 },
|
||||||
|
{ name: 'uniformity', value: 99 },
|
||||||
|
{ name: 'egg_weight', value: 80 },
|
||||||
|
{ name: 'egg_mass', value: 76 },
|
||||||
|
],
|
||||||
|
flocks: [
|
||||||
|
{ id: 1, name: 'Flock A-001', data: 95 },
|
||||||
|
{ id: 2, name: 'Flock A-002', data: 94 },
|
||||||
|
{ id: 3, name: 'Flock B-001', data: 96 },
|
||||||
|
{ id: 4, name: 'Flock B-002', data: 93 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Transform API data to Recharts format
|
||||||
|
const transformStandardData = (
|
||||||
|
apiData: StandardChartItem[],
|
||||||
|
selectedStandards: string[] = [
|
||||||
|
'hen_day',
|
||||||
|
'hen_house',
|
||||||
|
'uniformity',
|
||||||
|
'egg_weight',
|
||||||
|
'egg_mass',
|
||||||
|
]
|
||||||
|
) => {
|
||||||
|
return apiData.map((item) => {
|
||||||
|
const transformed: Record<string, number> = {
|
||||||
|
week: item.week,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add selected standards as properties
|
||||||
|
selectedStandards.forEach((standardName) => {
|
||||||
|
const standardData = item.standards.find((s) => s.name === standardName);
|
||||||
|
if (standardData) {
|
||||||
|
transformed[standardName] = standardData.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add each flock's data as a property
|
||||||
|
item.flocks.forEach((flock) => {
|
||||||
|
transformed[flock.name] = flock.data;
|
||||||
|
});
|
||||||
|
|
||||||
|
return transformed;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface StandardLineChartProps {
|
||||||
|
data?: StandardChartItem[];
|
||||||
|
selectedStandards?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const StandardLineChart = ({
|
||||||
|
data: apiData,
|
||||||
|
selectedStandards = [
|
||||||
|
'hen_day',
|
||||||
|
'hen_house',
|
||||||
|
'uniformity',
|
||||||
|
'egg_weight',
|
||||||
|
'egg_mass',
|
||||||
|
],
|
||||||
|
}: StandardLineChartProps) => {
|
||||||
|
// State to track which lines are hidden
|
||||||
|
const [hiddenLines, setHiddenLines] = useState<string[]>([]);
|
||||||
|
|
||||||
|
// Use API data if provided, otherwise use sample data
|
||||||
|
const chartData = apiData
|
||||||
|
? transformStandardData(apiData, selectedStandards)
|
||||||
|
: transformStandardData(sampleApiData, selectedStandards);
|
||||||
|
|
||||||
|
// Handle legend click to show/hide lines
|
||||||
|
const handleLegendClick = (dataKey: string) => {
|
||||||
|
setHiddenLines((prev) =>
|
||||||
|
prev.includes(dataKey)
|
||||||
|
? prev.filter((key) => key !== dataKey)
|
||||||
|
: [...prev, dataKey]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Standard line colors mapping
|
||||||
|
const standardColors: Record<string, string> = {
|
||||||
|
hen_day: '#94a3b8',
|
||||||
|
hen_house: '#64748b',
|
||||||
|
uniformity: '#475569',
|
||||||
|
egg_weight: '#334155',
|
||||||
|
egg_mass: '#1e293b',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Standard names mapping for display
|
||||||
|
const standardLabels: Record<string, string> = {
|
||||||
|
hen_day: 'Hen Day',
|
||||||
|
hen_house: 'Hen House',
|
||||||
|
uniformity: 'Uniformity',
|
||||||
|
egg_weight: 'Egg Weight',
|
||||||
|
egg_mass: 'Egg Mass',
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full h-full'>
|
||||||
|
<h3 className='text-lg font-semibold mb-4'>
|
||||||
|
Perbandingan Henday per Umur
|
||||||
|
</h3>
|
||||||
|
<ResponsiveContainer width='100%' height={400}>
|
||||||
|
<LineChart
|
||||||
|
data={chartData}
|
||||||
|
margin={{
|
||||||
|
top: 5,
|
||||||
|
right: 30,
|
||||||
|
left: 0,
|
||||||
|
bottom: 5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CartesianGrid strokeDasharray='3 3' stroke='#e5e7eb' />
|
||||||
|
<XAxis
|
||||||
|
dataKey='week'
|
||||||
|
tick={{ fontSize: 12 }}
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={{ stroke: '#e5e7eb' }}
|
||||||
|
label={{
|
||||||
|
value: 'Umur (minggu)',
|
||||||
|
position: 'insideBottom',
|
||||||
|
offset: -5,
|
||||||
|
style: { fontSize: 12 },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<YAxis
|
||||||
|
tick={{ fontSize: 12 }}
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={{ stroke: '#e5e7eb' }}
|
||||||
|
domain={[0, 100]}
|
||||||
|
label={{
|
||||||
|
value: 'Henday (%)',
|
||||||
|
angle: -90,
|
||||||
|
position: 'insideLeft',
|
||||||
|
style: { fontSize: 12 },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
contentStyle={{
|
||||||
|
backgroundColor: 'white',
|
||||||
|
border: '1px solid #e5e7eb',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '8px 12px',
|
||||||
|
}}
|
||||||
|
formatter={(value: number | undefined) =>
|
||||||
|
value !== undefined ? [`${value}%`, ''] : ['', '']
|
||||||
|
}
|
||||||
|
labelFormatter={(label) => `Minggu ${label}`}
|
||||||
|
/>
|
||||||
|
<Legend
|
||||||
|
wrapperStyle={{
|
||||||
|
paddingTop: '20px',
|
||||||
|
}}
|
||||||
|
iconType='circle'
|
||||||
|
onClick={(e) => {
|
||||||
|
if (e.dataKey) handleLegendClick(e.dataKey as string);
|
||||||
|
}}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
/>
|
||||||
|
{/* Dynamic Standard Lines */}
|
||||||
|
{selectedStandards.map((standardName) => (
|
||||||
|
<Line
|
||||||
|
key={standardName}
|
||||||
|
type='monotone'
|
||||||
|
dataKey={standardName}
|
||||||
|
name={standardLabels[standardName] || standardName}
|
||||||
|
stroke={standardColors[standardName] || '#94a3b8'}
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeDasharray='5 5'
|
||||||
|
dot={{ r: 3, fill: standardColors[standardName] || '#94a3b8' }}
|
||||||
|
activeDot={{ r: 5 }}
|
||||||
|
hide={hiddenLines.includes(standardName)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{/* Flock Lines */}
|
||||||
|
<Line
|
||||||
|
type='monotone'
|
||||||
|
dataKey='Flock A-002'
|
||||||
|
stroke='#3b82f6'
|
||||||
|
strokeWidth={2}
|
||||||
|
dot={{ r: 4, fill: '#3b82f6' }}
|
||||||
|
activeDot={{ r: 6 }}
|
||||||
|
hide={hiddenLines.includes('Flock A-002')}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
type='monotone'
|
||||||
|
dataKey='Flock A-001'
|
||||||
|
stroke='#10b981'
|
||||||
|
strokeWidth={2}
|
||||||
|
dot={{ r: 4, fill: '#10b981' }}
|
||||||
|
activeDot={{ r: 6 }}
|
||||||
|
hide={hiddenLines.includes('Flock A-001')}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
type='monotone'
|
||||||
|
dataKey='Flock B-001'
|
||||||
|
stroke='#f59e0b'
|
||||||
|
strokeWidth={2}
|
||||||
|
dot={{ r: 4, fill: '#f59e0b' }}
|
||||||
|
activeDot={{ r: 6 }}
|
||||||
|
hide={hiddenLines.includes('Flock B-001')}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
type='monotone'
|
||||||
|
dataKey='Flock B-002'
|
||||||
|
stroke='#ef4444'
|
||||||
|
strokeWidth={2}
|
||||||
|
dot={{ r: 4, fill: '#ef4444' }}
|
||||||
|
activeDot={{ r: 6 }}
|
||||||
|
hide={hiddenLines.includes('Flock B-002')}
|
||||||
|
/>
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StandardLineChart;
|
||||||
|
|
||||||
|
// Export types for external use
|
||||||
|
export type { FlockData, StandardData, StandardChartItem };
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import * as yup from 'yup';
|
||||||
|
|
||||||
|
const dashboardProductionFilterSchema = yup.object({
|
||||||
|
startDate: yup.string().optional(),
|
||||||
|
endDate: yup.string().optional(),
|
||||||
|
flock: yup.array().optional(),
|
||||||
|
standard_production_id: yup.array().optional(),
|
||||||
|
standard_productions: yup.array().optional(),
|
||||||
|
period: yup.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type DashboardProductionFilterValues = yup.InferType<
|
||||||
|
typeof dashboardProductionFilterSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export default dashboardProductionFilterSchema;
|
||||||
@@ -308,7 +308,7 @@ export const FINANCE_INITIAL_BALANCE_TYPE_OPTIONS = [
|
|||||||
{ label: 'Saldo Awal Negatif', value: 'NEGATIVE' },
|
{ label: 'Saldo Awal Negatif', value: 'NEGATIVE' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const FINANCE_TRANSACTION_STATUS = ['PENJUALAN', 'PEMBELIAN'];
|
export const FINANCE_TRANSACTION_STATUS = ['PENJUALAN', 'PEMBELIAN', 'BIAYA'];
|
||||||
|
|
||||||
export const FINANCE_INITIAL_BALANCE_STATUS = ['SALDO_AWAL'];
|
export const FINANCE_INITIAL_BALANCE_STATUS = ['SALDO_AWAL'];
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Dummy data for DashboardProduction
|
||||||
|
* Generated from: dashboard.production.dummy.json
|
||||||
|
*
|
||||||
|
* This file is auto-generated. Do not edit manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DashboardProductionStatisticsData, DashboardProductionProductionChartsFlocks, DashboardProductionProductionCharts, DashboardProductionStandardProductionsStandards, DashboardProductionStandardProductions, DashboardProductionFcrDataFlock, DashboardProductionEggWeights, DashboardProductionFcrData, DashboardProduction } from '../../types/api/dashboard/dashboard-production';
|
||||||
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
|
import dummyData from './dashboard.production.dummy.json';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get dummy DashboardProduction data
|
||||||
|
* @returns Promise with BaseApiResponse containing DashboardProduction
|
||||||
|
*/
|
||||||
|
export async function getDummySingle(): Promise<BaseApiResponse<DashboardProduction>> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({
|
||||||
|
code: 200,
|
||||||
|
status: 'success',
|
||||||
|
message: 'Data retrieved successfully',
|
||||||
|
data: dummyData as unknown as DashboardProduction,
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { BaseApiService } from '@/services/api/base';
|
||||||
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
|
import { DashboardProduction } from '@/types/api/dashboard/dashboard-production';
|
||||||
|
import { getDummySingle } from '@/dummy/dashboard/dashboard.production.dummy';
|
||||||
|
|
||||||
|
class DashboardService extends BaseApiService<
|
||||||
|
DashboardProduction,
|
||||||
|
unknown,
|
||||||
|
unknown
|
||||||
|
> {
|
||||||
|
constructor(basePath: string) {
|
||||||
|
super(basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch dashboard production data
|
||||||
|
* @param endpoint - The endpoint URL with query parameters
|
||||||
|
* @returns Promise with BaseApiResponse containing DashboardProduction
|
||||||
|
*
|
||||||
|
* Note: Currently using dummy data. When real API is ready,
|
||||||
|
* uncomment the line below and remove getDummySingle() call:
|
||||||
|
* return await this.customRequest<BaseApiResponse<DashboardProduction>>(endpoint);
|
||||||
|
*/
|
||||||
|
async getDashboardProductionFetcher(
|
||||||
|
endpoint: string
|
||||||
|
): Promise<BaseApiResponse<DashboardProduction>> {
|
||||||
|
// For now, we're using dummy data regardless of the endpoint
|
||||||
|
// The endpoint parameter is kept for future API integration
|
||||||
|
console.log('Fetching dashboard data with endpoint:', endpoint);
|
||||||
|
return await getDummySingle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DashboardApi = new DashboardService('/dashboard');
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
export interface DashboardProduction {
|
||||||
|
statistics_data: DashboardProductionStatisticsData[];
|
||||||
|
production_charts: DashboardProductionProductionCharts[];
|
||||||
|
standard_productions: DashboardProductionStandardProductions[];
|
||||||
|
egg_weights: DashboardProductionEggWeights[];
|
||||||
|
fcr_data: DashboardProductionFcrData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardProductionFcrData {
|
||||||
|
flock: DashboardProductionFcrDataFlock;
|
||||||
|
fcr: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardProductionEggWeights {
|
||||||
|
flock: DashboardProductionFcrDataFlock;
|
||||||
|
weight: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardProductionStandardProductions {
|
||||||
|
week: number;
|
||||||
|
standards: DashboardProductionStandardProductionsStandards[];
|
||||||
|
flocks: DashboardProductionProductionChartsFlocks[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardProductionProductionCharts {
|
||||||
|
date: string;
|
||||||
|
flocks: DashboardProductionProductionChartsFlocks[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardProductionStatisticsData {
|
||||||
|
title: string;
|
||||||
|
value: number;
|
||||||
|
change: number;
|
||||||
|
period: string;
|
||||||
|
changeType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardProductionFcrDataFlock {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardProductionStandardProductionsStandards {
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardProductionProductionChartsFlocks {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
data: number;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user