mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-24 15:25:46 +00:00
Merge branch 'dev/randy' into 'development'
[FIX/FE][US#337-390] Fix issue in finance and adding dummy dashboard See merge request mbugroup/lti-web-client!121
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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
|
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import { cn, formatCurrency, formatNumber } from '@/lib/helper';
|
import { formatCurrency, formatNumber } from '@/lib/helper';
|
||||||
import {
|
import {
|
||||||
RowSapronakCalculation,
|
RowSapronakCalculation,
|
||||||
TotalSapronakCalculation,
|
TotalSapronakCalculation,
|
||||||
@@ -54,7 +54,7 @@ const ClosingSapronakCalculationTable = ({
|
|||||||
footer: total
|
footer: total
|
||||||
? () => (
|
? () => (
|
||||||
<div className='font-semibold text-gray-900'>
|
<div className='font-semibold text-gray-900'>
|
||||||
{formatNumber(total.qty_masuk)}
|
{formatNumber(total?.qty_masuk)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: '',
|
: '',
|
||||||
@@ -66,7 +66,7 @@ const ClosingSapronakCalculationTable = ({
|
|||||||
footer: total
|
footer: total
|
||||||
? () => (
|
? () => (
|
||||||
<div className='font-semibold text-gray-900'>
|
<div className='font-semibold text-gray-900'>
|
||||||
{formatNumber(total.qty_keluar)}
|
{formatNumber(total?.qty_keluar)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: '',
|
: '',
|
||||||
@@ -78,7 +78,7 @@ const ClosingSapronakCalculationTable = ({
|
|||||||
footer: total
|
footer: total
|
||||||
? () => (
|
? () => (
|
||||||
<div className='font-semibold text-gray-900'>
|
<div className='font-semibold text-gray-900'>
|
||||||
{formatNumber(total.qty_pakai)}
|
{formatNumber(total?.qty_pakai)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: '',
|
: '',
|
||||||
@@ -102,7 +102,7 @@ const ClosingSapronakCalculationTable = ({
|
|||||||
footer: total
|
footer: total
|
||||||
? () => (
|
? () => (
|
||||||
<div className='font-semibold text-gray-900'>
|
<div className='font-semibold text-gray-900'>
|
||||||
{formatCurrency(total.harga_beli_per_qty)}
|
{formatCurrency(total?.harga_beli_per_qty)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: '',
|
: '',
|
||||||
@@ -114,7 +114,7 @@ const ClosingSapronakCalculationTable = ({
|
|||||||
footer: total
|
footer: total
|
||||||
? () => (
|
? () => (
|
||||||
<div className='font-semibold text-gray-900'>
|
<div className='font-semibold text-gray-900'>
|
||||||
{formatCurrency(total.total_harga)}
|
{formatCurrency(total?.total_harga)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: '',
|
: '',
|
||||||
@@ -131,7 +131,7 @@ const ClosingSapronakCalculationTable = ({
|
|||||||
const docBroilerColumns = useMemo(
|
const docBroilerColumns = useMemo(
|
||||||
() =>
|
() =>
|
||||||
isResponseSuccess(sapronakCalculation)
|
isResponseSuccess(sapronakCalculation)
|
||||||
? createColumns(sapronakCalculation.data?.doc_broiler.total)
|
? createColumns(sapronakCalculation.data?.doc_broiler?.total)
|
||||||
: createColumns(),
|
: createColumns(),
|
||||||
[sapronakCalculation]
|
[sapronakCalculation]
|
||||||
);
|
);
|
||||||
@@ -139,7 +139,7 @@ const ClosingSapronakCalculationTable = ({
|
|||||||
const ovkColumns = useMemo(
|
const ovkColumns = useMemo(
|
||||||
() =>
|
() =>
|
||||||
isResponseSuccess(sapronakCalculation)
|
isResponseSuccess(sapronakCalculation)
|
||||||
? createColumns(sapronakCalculation.data?.ovk.total)
|
? createColumns(sapronakCalculation.data?.ovk?.total)
|
||||||
: createColumns(),
|
: createColumns(),
|
||||||
[sapronakCalculation]
|
[sapronakCalculation]
|
||||||
);
|
);
|
||||||
@@ -147,7 +147,7 @@ const ClosingSapronakCalculationTable = ({
|
|||||||
const pakanColumns = useMemo(
|
const pakanColumns = useMemo(
|
||||||
() =>
|
() =>
|
||||||
isResponseSuccess(sapronakCalculation)
|
isResponseSuccess(sapronakCalculation)
|
||||||
? createColumns(sapronakCalculation.data?.pakan.total)
|
? createColumns(sapronakCalculation.data?.pakan?.total)
|
||||||
: createColumns(),
|
: createColumns(),
|
||||||
[sapronakCalculation]
|
[sapronakCalculation]
|
||||||
);
|
);
|
||||||
@@ -166,7 +166,7 @@ const ClosingSapronakCalculationTable = ({
|
|||||||
<Table<RowSapronakCalculation>
|
<Table<RowSapronakCalculation>
|
||||||
data={
|
data={
|
||||||
isResponseSuccess(sapronakCalculation)
|
isResponseSuccess(sapronakCalculation)
|
||||||
? (sapronakCalculation.data?.doc_broiler.rows ?? [])
|
? (sapronakCalculation.data?.doc_broiler?.rows ?? [])
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
columns={docBroilerColumns}
|
columns={docBroilerColumns}
|
||||||
@@ -189,7 +189,7 @@ const ClosingSapronakCalculationTable = ({
|
|||||||
<Table<RowSapronakCalculation>
|
<Table<RowSapronakCalculation>
|
||||||
data={
|
data={
|
||||||
isResponseSuccess(sapronakCalculation)
|
isResponseSuccess(sapronakCalculation)
|
||||||
? (sapronakCalculation.data?.ovk.rows ?? [])
|
? (sapronakCalculation.data?.ovk?.rows ?? [])
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
columns={ovkColumns}
|
columns={ovkColumns}
|
||||||
@@ -212,7 +212,7 @@ const ClosingSapronakCalculationTable = ({
|
|||||||
<Table<RowSapronakCalculation>
|
<Table<RowSapronakCalculation>
|
||||||
data={
|
data={
|
||||||
isResponseSuccess(sapronakCalculation)
|
isResponseSuccess(sapronakCalculation)
|
||||||
? (sapronakCalculation.data?.pakan.rows ?? [])
|
? (sapronakCalculation.data?.pakan?.rows ?? [])
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
columns={pakanColumns}
|
columns={pakanColumns}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -39,6 +39,12 @@ interface FormFinanceAddProps {
|
|||||||
initialValues?: Finance;
|
initialValues?: Finance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PartyCommonProps {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
account_number: string;
|
||||||
|
}
|
||||||
|
|
||||||
const FormFinanceAdd = ({
|
const FormFinanceAdd = ({
|
||||||
type = 'add',
|
type = 'add',
|
||||||
initialValues,
|
initialValues,
|
||||||
@@ -52,10 +58,12 @@ const FormFinanceAdd = ({
|
|||||||
FINANCE_PARTY_TYPE_OPTIONS.find(
|
FINANCE_PARTY_TYPE_OPTIONS.find(
|
||||||
(option) => option.value === initialValues?.party.type
|
(option) => option.value === initialValues?.party.type
|
||||||
) || null,
|
) || null,
|
||||||
party_id_option: {
|
party_id_option: initialValues?.party
|
||||||
label: initialValues?.party.name || '',
|
? {
|
||||||
value: initialValues?.party.id || 0,
|
label: initialValues?.party.name || '',
|
||||||
},
|
value: initialValues?.party.id || 0,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
payment_date: initialValues?.payment_date || '',
|
payment_date: initialValues?.payment_date || '',
|
||||||
payment_method_option:
|
payment_method_option:
|
||||||
FINANCE_PAYMENT_METHOD_OPTIONS.find(
|
FINANCE_PAYMENT_METHOD_OPTIONS.find(
|
||||||
@@ -97,16 +105,19 @@ const FormFinanceAdd = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ===== Options =====
|
// ===== Options =====
|
||||||
const { options: partyOptions, isLoadingOptions: isLoadingPartyOptions } =
|
const {
|
||||||
useSelect(
|
options: partyOptions,
|
||||||
formik.values.party_type_option?.value === 'CUSTOMER'
|
isLoadingOptions: isLoadingPartyOptions,
|
||||||
? CustomerApi.basePath
|
rawData: partyRawData,
|
||||||
: SupplierApi.basePath,
|
} = useSelect<PartyCommonProps>(
|
||||||
'id',
|
formik.values.party_type_option?.value === 'CUSTOMER'
|
||||||
'name',
|
? CustomerApi.basePath
|
||||||
'',
|
: SupplierApi.basePath,
|
||||||
{ limit: 'limit' }
|
'id',
|
||||||
);
|
'name',
|
||||||
|
'',
|
||||||
|
{ limit: 'limit' }
|
||||||
|
);
|
||||||
const {
|
const {
|
||||||
options: bankOptions,
|
options: bankOptions,
|
||||||
rawData: bankRawData,
|
rawData: bankRawData,
|
||||||
@@ -204,6 +215,14 @@ const FormFinanceAdd = ({
|
|||||||
value={formik.values.party_id_option}
|
value={formik.values.party_id_option}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
formik.setFieldValue('party_id_option', value);
|
formik.setFieldValue('party_id_option', value);
|
||||||
|
if (isResponseSuccess(partyRawData) && value) {
|
||||||
|
formik.setFieldValue(
|
||||||
|
'party_account_number',
|
||||||
|
partyRawData.data?.find(
|
||||||
|
(item) => item.id === (value as OptionType)?.value
|
||||||
|
)?.account_number || ''
|
||||||
|
);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
isLoading={isLoadingPartyOptions}
|
isLoading={isLoadingPartyOptions}
|
||||||
isError={Boolean(
|
isError={Boolean(
|
||||||
@@ -312,6 +331,7 @@ const FormFinanceAdd = ({
|
|||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
required
|
required
|
||||||
|
readOnly
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label='Nomor Referensi'
|
label='Nomor Referensi'
|
||||||
|
|||||||
-13
@@ -1,19 +1,6 @@
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { OptionType } from '@/components/input/SelectInput';
|
import { OptionType } from '@/components/input/SelectInput';
|
||||||
|
|
||||||
/**
|
|
||||||
* API Payload format for Initial Balance:
|
|
||||||
* {
|
|
||||||
"party_type": "CUSTOMER",
|
|
||||||
"party_id": 1,
|
|
||||||
"bank_id": 1,
|
|
||||||
"reference_number": "IB.MBU.001",
|
|
||||||
"initial_balance_type": "DEBIT",
|
|
||||||
"nominal": 5000000,
|
|
||||||
"note": "Saldo awal piutang customer"
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Type for form values (includes option objects for SelectInput)
|
// Type for form values (includes option objects for SelectInput)
|
||||||
export type InitialBalanceFormValues = {
|
export type InitialBalanceFormValues = {
|
||||||
party_type_option: OptionType | null;
|
party_type_option: OptionType | null;
|
||||||
|
|||||||
@@ -431,7 +431,7 @@ const MarketingDetail = ({
|
|||||||
<Button
|
<Button
|
||||||
color='warning'
|
color='warning'
|
||||||
type='button'
|
type='button'
|
||||||
href={`/marketing/detail/${initialValues?.latest_approval.step_number == 3 ? 'delivery-orders' : 'sales-orders'}/edit?marketingId=${initialValues?.id}`}
|
href={`/marketing/detail/${initialValues?.latest_approval?.step_number == 3 ? 'delivery-orders' : 'sales-orders'}/edit?marketingId=${initialValues?.id}`}
|
||||||
>
|
>
|
||||||
<Icon icon='mdi:pencil' width={24} height={24} />
|
<Icon icon='mdi:pencil' width={24} height={24} />
|
||||||
Edit
|
Edit
|
||||||
|
|||||||
@@ -174,19 +174,6 @@ const DeliveryOrderProductForm = ({
|
|||||||
}}
|
}}
|
||||||
onReset={handleResetForm}
|
onReset={handleResetForm}
|
||||||
>
|
>
|
||||||
{/* <small className='block text-blue-500'>
|
|
||||||
{JSON.stringify(exisitingValues)}
|
|
||||||
</small>
|
|
||||||
<small className='block text-emerald-500'>
|
|
||||||
{JSON.stringify(formik.values)}
|
|
||||||
</small> */}
|
|
||||||
{/* <small className='block text-red-500'>
|
|
||||||
{JSON.stringify(formik.errors)}
|
|
||||||
</small>
|
|
||||||
<div className='hidden'>
|
|
||||||
{JSON.stringify(formik.values.marketing_product)}
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
{formikErrorMessage && (
|
{formikErrorMessage && (
|
||||||
<div onClick={() => setFormErrorMessage('')} className='my-3 w-full'>
|
<div onClick={() => setFormErrorMessage('')} className='my-3 w-full'>
|
||||||
<Alert color='error'>{formikErrorMessage}</Alert>
|
<Alert color='error'>{formikErrorMessage}</Alert>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import SelectInput, {
|
|||||||
useSelect,
|
useSelect,
|
||||||
} from '@/components/input/SelectInput';
|
} from '@/components/input/SelectInput';
|
||||||
import { Kandang } from '@/types/api/master-data/kandang';
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
import { KandangApi } from '@/services/api/master-data';
|
import { KandangApi, WarehouseApi } from '@/services/api/master-data';
|
||||||
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
||||||
import { ProductWarehouseApi } from '@/services/api/inventory';
|
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||||
import NumberInput from '@/components/input/NumberInput';
|
import NumberInput from '@/components/input/NumberInput';
|
||||||
@@ -61,7 +61,7 @@ const SalesOrderProductForm = ({
|
|||||||
const {
|
const {
|
||||||
options: kandangSourceOptions,
|
options: kandangSourceOptions,
|
||||||
isLoadingOptions: isLoadingKandangSourceOptions,
|
isLoadingOptions: isLoadingKandangSourceOptions,
|
||||||
} = useSelect<Kandang>(KandangApi.basePath, 'id', 'name');
|
} = useSelect<Kandang>(WarehouseApi.basePath, 'id', 'name');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
options: warehouseSourceOptions,
|
options: warehouseSourceOptions,
|
||||||
|
|||||||
@@ -79,14 +79,14 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
|
|||||||
uomId: initialValues?.uom_id ?? 0,
|
uomId: initialValues?.uom_id ?? 0,
|
||||||
uom: initialValues?.uom
|
uom: initialValues?.uom
|
||||||
? {
|
? {
|
||||||
value: initialValues?.uom.id,
|
value: initialValues?.uom?.id,
|
||||||
label: initialValues?.uom.name,
|
label: initialValues?.uom?.name,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
supplierIds:
|
supplierIds:
|
||||||
initialValues?.suppliers.map((supplier) => supplier.id) ?? [],
|
initialValues?.suppliers?.map((supplier) => supplier.id) ?? [],
|
||||||
suppliers:
|
suppliers:
|
||||||
initialValues?.suppliers.map((supplier) => ({
|
initialValues?.suppliers?.map((supplier) => ({
|
||||||
value: supplier.id,
|
value: supplier.id,
|
||||||
label: supplier.name,
|
label: supplier.name,
|
||||||
})) ?? [],
|
})) ?? [],
|
||||||
|
|||||||
+5
-1
@@ -18,6 +18,7 @@ const LayingRepeaterFormSchema = Yup.object({
|
|||||||
),
|
),
|
||||||
target_egg_weight: Yup.number().required('Berat telur wajib diisi!'),
|
target_egg_weight: Yup.number().required('Berat telur wajib diisi!'),
|
||||||
target_egg_mass: Yup.number().required('Massa telur wajib diisi!'),
|
target_egg_mass: Yup.number().required('Massa telur wajib diisi!'),
|
||||||
|
standard_fcr: Yup.number().required('FCR wajib diisi!'),
|
||||||
}).required(),
|
}).required(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -35,6 +36,7 @@ const GrowingRepeaterFormSchema = Yup.object({
|
|||||||
target_hen_house_production: Yup.number().optional(),
|
target_hen_house_production: Yup.number().optional(),
|
||||||
target_egg_weight: Yup.number().optional(),
|
target_egg_weight: Yup.number().optional(),
|
||||||
target_egg_mass: Yup.number().optional(),
|
target_egg_mass: Yup.number().optional(),
|
||||||
|
standard_fcr: Yup.number().optional(),
|
||||||
}).optional(),
|
}).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -68,7 +70,9 @@ export const createProductionStandardRepeaterFormSchema = (
|
|||||||
export const createProductionStandardFormSchema = (category: string) => {
|
export const createProductionStandardFormSchema = (category: string) => {
|
||||||
return Yup.object({
|
return Yup.object({
|
||||||
name: Yup.string().required('Nama wajib diisi!'),
|
name: Yup.string().required('Nama wajib diisi!'),
|
||||||
project_category: Yup.string().required('Kategori proyek wajib diisi!'),
|
project_category: Yup.string()
|
||||||
|
.min(1, 'Kategori proyek wajib diisi!')
|
||||||
|
.required('Kategori proyek wajib diisi!'),
|
||||||
details: Yup.array().of(
|
details: Yup.array().of(
|
||||||
createProductionStandardRepeaterFormSchema(category)
|
createProductionStandardRepeaterFormSchema(category)
|
||||||
),
|
),
|
||||||
|
|||||||
+120
-38
@@ -29,6 +29,8 @@ import toast from 'react-hot-toast';
|
|||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import RequirePermission from '@/components/helper/RequirePermission';
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
import Tooltip from '@/components/Tooltip';
|
||||||
|
import Alert from '@/components/Alert';
|
||||||
|
|
||||||
type TableRowsType = {
|
type TableRowsType = {
|
||||||
customRow: boolean;
|
customRow: boolean;
|
||||||
@@ -41,6 +43,7 @@ type ProductionDetailsErrors = {
|
|||||||
target_hen_house_production?: string;
|
target_hen_house_production?: string;
|
||||||
target_egg_weight?: string;
|
target_egg_weight?: string;
|
||||||
target_egg_mass?: string;
|
target_egg_mass?: string;
|
||||||
|
standard_fcr?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ProductionDetailsTouched = {
|
type ProductionDetailsTouched = {
|
||||||
@@ -48,6 +51,7 @@ type ProductionDetailsTouched = {
|
|||||||
target_hen_house_production?: boolean;
|
target_hen_house_production?: boolean;
|
||||||
target_egg_weight?: boolean;
|
target_egg_weight?: boolean;
|
||||||
target_egg_mass?: boolean;
|
target_egg_mass?: boolean;
|
||||||
|
standard_fcr?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProductionDetailsError = (
|
const getProductionDetailsError = (
|
||||||
@@ -91,6 +95,9 @@ const convertPayloadToNumberTypes = (payload: ProductionStandardFormValues) => {
|
|||||||
target_egg_mass: Number(
|
target_egg_mass: Number(
|
||||||
detail.production_standard_details.target_egg_mass
|
detail.production_standard_details.target_egg_mass
|
||||||
),
|
),
|
||||||
|
standard_fcr: Number(
|
||||||
|
detail.production_standard_details.standard_fcr
|
||||||
|
),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
production_standard_uniformity_details: {
|
production_standard_uniformity_details: {
|
||||||
@@ -131,6 +138,9 @@ const convertStandardValueToFormValues = (
|
|||||||
target_egg_mass: Number(
|
target_egg_mass: Number(
|
||||||
detail.egg_production_standard_detail.target_egg_mass
|
detail.egg_production_standard_detail.target_egg_mass
|
||||||
),
|
),
|
||||||
|
standard_fcr: Number(
|
||||||
|
detail.egg_production_standard_detail.standard_fcr
|
||||||
|
),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
production_standard_uniformity_details: {
|
production_standard_uniformity_details: {
|
||||||
@@ -175,13 +185,15 @@ const ProductionStandardForm = ({
|
|||||||
} = useFormStore();
|
} = useFormStore();
|
||||||
|
|
||||||
// ===== Formik =====
|
// ===== Formik =====
|
||||||
|
// Initial values - only recalculate when initialValue changes (for edit/detail mode)
|
||||||
|
// For add mode, we load from cache via useEffect instead to avoid race conditions
|
||||||
const formikInitialValues = useMemo(() => {
|
const formikInitialValues = useMemo(() => {
|
||||||
// For add mode, merge cached data with initial values
|
if (formType === 'add') {
|
||||||
if (formType === 'add' && formData) {
|
// Don't use formData here - will be loaded via useEffect
|
||||||
return {
|
return {
|
||||||
name: formData.name || '',
|
name: '',
|
||||||
project_category: formData.project_category || '',
|
project_category: '',
|
||||||
details: formData.details || [],
|
details: [],
|
||||||
} as ProductionStandardFormValues;
|
} as ProductionStandardFormValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,10 +202,11 @@ const ProductionStandardForm = ({
|
|||||||
project_category: initialValue?.project_category || '',
|
project_category: initialValue?.project_category || '',
|
||||||
details: convertStandardValueToFormValues(initialValue?.details || []),
|
details: convertStandardValueToFormValues(initialValue?.details || []),
|
||||||
} as ProductionStandardFormValues;
|
} as ProductionStandardFormValues;
|
||||||
}, [initialValue, formData, formType]);
|
}, [initialValue, formType]);
|
||||||
const formik = useFormik<ProductionStandardFormValues>({
|
const formik = useFormik<ProductionStandardFormValues>({
|
||||||
initialValues: formikInitialValues as ProductionStandardFormValues,
|
initialValues: formikInitialValues as ProductionStandardFormValues,
|
||||||
enableReinitialize: true,
|
// Only enable reinitialize for edit/detail mode, not add mode
|
||||||
|
enableReinitialize: formType !== 'add',
|
||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
switch (formType) {
|
switch (formType) {
|
||||||
case 'add':
|
case 'add':
|
||||||
@@ -222,6 +235,7 @@ const ProductionStandardForm = ({
|
|||||||
target_hen_house_production: '' as unknown as number,
|
target_hen_house_production: '' as unknown as number,
|
||||||
target_egg_weight: '' as unknown as number,
|
target_egg_weight: '' as unknown as number,
|
||||||
target_egg_mass: '' as unknown as number,
|
target_egg_mass: '' as unknown as number,
|
||||||
|
standard_fcr: '' as unknown as number,
|
||||||
},
|
},
|
||||||
production_standard_uniformity_details: {
|
production_standard_uniformity_details: {
|
||||||
target_mean_bw: '' as unknown as number,
|
target_mean_bw: '' as unknown as number,
|
||||||
@@ -255,36 +269,38 @@ const ProductionStandardForm = ({
|
|||||||
const { setValues: repeaterFormikSetValues } = repeaterFormik;
|
const { setValues: repeaterFormikSetValues } = repeaterFormik;
|
||||||
|
|
||||||
// ===== Effect =====
|
// ===== Effect =====
|
||||||
// Load initial values only when component mounts or when initialValue changes (for edit mode)
|
// Load cached data only once on mount for add mode
|
||||||
// This allows:
|
const [isInitialized, setIsInitialized] = useState(false);
|
||||||
// 1. Add mode: Load cached data from formData store
|
|
||||||
// 2. Edit mode: Load existing data from initialValue
|
|
||||||
// We use initialValue?.id as dependency to avoid infinite loops
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formType === 'add' && formData) {
|
if (formType === 'add' && formData && !isInitialized) {
|
||||||
// For add mode, load from cache
|
// For add mode, load from cache only on initial mount
|
||||||
formikSetValues({
|
formikSetValues({
|
||||||
name: formData.name || '',
|
name: formData.name || '',
|
||||||
project_category: formData.project_category || '',
|
project_category: formData.project_category || '',
|
||||||
details: formData.details || [],
|
details: formData.details || [],
|
||||||
} as ProductionStandardFormValues);
|
} as ProductionStandardFormValues);
|
||||||
} else if (formType === 'detail' && initialValue) {
|
setIsInitialized(true);
|
||||||
// For detail mode, load from initialValue and convert the details
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []); // Only run once on mount
|
||||||
|
|
||||||
|
// For edit/detail mode, update when initialValue changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (formType === 'detail' && initialValue) {
|
||||||
formikSetValues({
|
formikSetValues({
|
||||||
name: initialValue.name || '',
|
name: initialValue.name || '',
|
||||||
project_category: initialValue.project_category || '',
|
project_category: initialValue.project_category || '',
|
||||||
details: convertStandardValueToFormValues(initialValue.details || []),
|
details: convertStandardValueToFormValues(initialValue.details || []),
|
||||||
} as ProductionStandardFormValues);
|
} as ProductionStandardFormValues);
|
||||||
} else if (formType === 'edit' && initialValue) {
|
} else if (formType === 'edit' && initialValue) {
|
||||||
// For edit mode, load from initialValue and convert the details
|
|
||||||
formikSetValues({
|
formikSetValues({
|
||||||
name: initialValue.name || '',
|
name: initialValue.name || '',
|
||||||
project_category: initialValue.project_category || '',
|
project_category: initialValue.project_category || '',
|
||||||
details: convertStandardValueToFormValues(initialValue.details || []),
|
details: convertStandardValueToFormValues(initialValue.details || []),
|
||||||
} as ProductionStandardFormValues);
|
} as ProductionStandardFormValues);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [initialValue?.id, formType]);
|
||||||
}, [formData, initialValue?.id]); // Trigger when formData or initialValue.id changes
|
|
||||||
|
|
||||||
// ===== Data Table =====
|
// ===== Data Table =====
|
||||||
const tableRows = useMemo(() => {
|
const tableRows = useMemo(() => {
|
||||||
@@ -323,11 +339,6 @@ const ProductionStandardForm = ({
|
|||||||
}, [formik.values.details]);
|
}, [formik.values.details]);
|
||||||
const columns = useMemo<ColumnDef<TableRowsType>[]>(() => {
|
const columns = useMemo<ColumnDef<TableRowsType>[]>(() => {
|
||||||
const baseColumns: ColumnDef<TableRowsType>[] = [
|
const baseColumns: ColumnDef<TableRowsType>[] = [
|
||||||
{
|
|
||||||
header: 'No',
|
|
||||||
accessorFn: (row, index) => index + 1,
|
|
||||||
enableSorting: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
header: 'Minggu',
|
header: 'Minggu',
|
||||||
accessorKey: 'week',
|
accessorKey: 'week',
|
||||||
@@ -363,6 +374,12 @@ const ProductionStandardForm = ({
|
|||||||
row.production_standard_details?.target_egg_mass,
|
row.production_standard_details?.target_egg_mass,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
header: 'FCR',
|
||||||
|
accessorFn: (row) =>
|
||||||
|
row.production_standard_details?.standard_fcr,
|
||||||
|
enableSorting: false,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
@@ -407,6 +424,7 @@ const ProductionStandardForm = ({
|
|||||||
variant='outline'
|
variant='outline'
|
||||||
color='warning'
|
color='warning'
|
||||||
className='p-2'
|
className='p-2'
|
||||||
|
type='button'
|
||||||
onClick={() => handleEditClick(row.row.original.week)}
|
onClick={() => handleEditClick(row.row.original.week)}
|
||||||
>
|
>
|
||||||
<Icon icon='mdi:pencil' />
|
<Icon icon='mdi:pencil' />
|
||||||
@@ -415,6 +433,7 @@ const ProductionStandardForm = ({
|
|||||||
variant='outline'
|
variant='outline'
|
||||||
color='error'
|
color='error'
|
||||||
className='p-2'
|
className='p-2'
|
||||||
|
type='button'
|
||||||
onClick={() => handleRemoveRow(row.row.original.week)}
|
onClick={() => handleRemoveRow(row.row.original.week)}
|
||||||
>
|
>
|
||||||
<Icon icon='mdi:delete' />
|
<Icon icon='mdi:delete' />
|
||||||
@@ -430,7 +449,7 @@ const ProductionStandardForm = ({
|
|||||||
...uniformityColumns,
|
...uniformityColumns,
|
||||||
...(formType !== 'detail' ? [actionColumn] : []),
|
...(formType !== 'detail' ? [actionColumn] : []),
|
||||||
];
|
];
|
||||||
}, [formik.values.project_category, formType]);
|
}, [formik.values, formType]);
|
||||||
|
|
||||||
// ===== Handler =====
|
// ===== Handler =====
|
||||||
const handleAddRow = async (
|
const handleAddRow = async (
|
||||||
@@ -488,9 +507,11 @@ const ProductionStandardForm = ({
|
|||||||
setIsAddingRow(false);
|
setIsAddingRow(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveRow = (week: number) => {
|
const handleRemoveRow = async (week: number) => {
|
||||||
const newValues = (formik.values.details || []).filter(
|
// Access formik.values directly to get the latest values
|
||||||
(detail) => detail.week !== week
|
const currentDetails = formik.values.details || [];
|
||||||
|
const newValues = currentDetails.filter(
|
||||||
|
(detail) => Number(detail.week) !== Number(week)
|
||||||
);
|
);
|
||||||
|
|
||||||
const updatedFormValues = {
|
const updatedFormValues = {
|
||||||
@@ -671,6 +692,7 @@ const ProductionStandardForm = ({
|
|||||||
target_hen_house_production: 0,
|
target_hen_house_production: 0,
|
||||||
target_egg_weight: 0,
|
target_egg_weight: 0,
|
||||||
target_egg_mass: 0,
|
target_egg_mass: 0,
|
||||||
|
standard_fcr: 0,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -745,6 +767,7 @@ const ProductionStandardForm = ({
|
|||||||
}
|
}
|
||||||
required
|
required
|
||||||
isDisabled={formType === 'detail'}
|
isDisabled={formType === 'detail'}
|
||||||
|
isClearable
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Table<TableRowsType>
|
<Table<TableRowsType>
|
||||||
@@ -803,7 +826,7 @@ const ProductionStandardForm = ({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'grid gap-4 items-start',
|
'grid gap-4 items-start',
|
||||||
formik.values.project_category === 'LAYING'
|
formik.values.project_category === 'LAYING'
|
||||||
? 'grid-cols-9'
|
? 'grid-cols-10'
|
||||||
: 'grid-cols-5'
|
: 'grid-cols-5'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -962,6 +985,41 @@ const ProductionStandardForm = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<NumberInput
|
||||||
|
name='production_standard_details.standard_fcr'
|
||||||
|
label='FCR'
|
||||||
|
placeholder='1'
|
||||||
|
value={
|
||||||
|
repeaterFormik.values
|
||||||
|
.production_standard_details?.standard_fcr
|
||||||
|
}
|
||||||
|
onChange={repeaterFormik.handleChange}
|
||||||
|
onBlur={repeaterFormik.handleBlur}
|
||||||
|
endAdornment={
|
||||||
|
<div className='w-full h-full flex items-center justify-center'>
|
||||||
|
gr
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
errorMessage={getProductionDetailsError(
|
||||||
|
repeaterFormik.errors
|
||||||
|
.production_standard_details,
|
||||||
|
'standard_fcr'
|
||||||
|
)}
|
||||||
|
isError={
|
||||||
|
Boolean(
|
||||||
|
getProductionDetailsError(
|
||||||
|
repeaterFormik.errors
|
||||||
|
.production_standard_details,
|
||||||
|
'standard_fcr'
|
||||||
|
)
|
||||||
|
) &&
|
||||||
|
getProductionDetailsTouched(
|
||||||
|
repeaterFormik.touched
|
||||||
|
.production_standard_details,
|
||||||
|
'standard_fcr'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<NumberInput
|
<NumberInput
|
||||||
@@ -1105,16 +1163,27 @@ const ProductionStandardForm = ({
|
|||||||
<Icon icon='mdi:close' /> Batal
|
<Icon icon='mdi:close' /> Batal
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Tooltip
|
||||||
type='submit'
|
content={
|
||||||
color={editMode ? 'warning' : 'success'}
|
formik.values.project_category === ''
|
||||||
className='min-w-24'
|
? 'Isi kategori proyek terlebih dahulu'
|
||||||
disabled={isAddingRow}
|
: ''
|
||||||
isLoading={isAddingRow}
|
}
|
||||||
>
|
>
|
||||||
<Icon icon={editMode ? 'mdi:pencil' : 'mdi:plus'} />{' '}
|
<Button
|
||||||
{editMode ? 'Edit Data' : 'Tambah Data'}
|
type='submit'
|
||||||
</Button>
|
color={editMode ? 'warning' : 'success'}
|
||||||
|
className='min-w-24'
|
||||||
|
disabled={
|
||||||
|
isAddingRow ||
|
||||||
|
formik.values.project_category === ''
|
||||||
|
}
|
||||||
|
isLoading={isAddingRow}
|
||||||
|
>
|
||||||
|
<Icon icon={editMode ? 'mdi:pencil' : 'mdi:plus'} />{' '}
|
||||||
|
{editMode ? 'Edit Data' : 'Tambah Data'}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
{/* Should not be absolute */}
|
{/* Should not be absolute */}
|
||||||
<Button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
@@ -1224,6 +1293,19 @@ const ProductionStandardForm = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
|
{productionStandardFormErrorMessage && (
|
||||||
|
<Alert color='error' className='w-full'>
|
||||||
|
<div className='flex items-center gap-2 stretch'>
|
||||||
|
<Icon icon='mdi:alert' />
|
||||||
|
<span>{productionStandardFormErrorMessage}</span>
|
||||||
|
</div>
|
||||||
|
<Icon
|
||||||
|
icon='mdi:close'
|
||||||
|
onClick={() => setProductionStandardFormErrorMessage('')}
|
||||||
|
className='ms-auto'
|
||||||
|
/>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const SupplierFormSchema = Yup.object({
|
|||||||
value: Yup.string().required(),
|
value: Yup.string().required(),
|
||||||
label: Yup.string().required(),
|
label: Yup.string().required(),
|
||||||
}).required('Tipe wajib diisi!'),
|
}).required('Tipe wajib diisi!'),
|
||||||
hatchery: Yup.string().required('Hatchery wajib diisi!'),
|
hatchery: Yup.string().optional(),
|
||||||
phone: Yup.string()
|
phone: Yup.string()
|
||||||
.matches(/^[0-9]+$/, 'Nomor telepon hanya boleh berisi angka!')
|
.matches(/^[0-9]+$/, 'Nomor telepon hanya boleh berisi angka!')
|
||||||
.min(10, 'Nomor telepon minimal 10 digit!')
|
.min(10, 'Nomor telepon minimal 10 digit!')
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ const SupplierForm = ({
|
|||||||
pic: values.pic,
|
pic: values.pic,
|
||||||
type: values.type.value,
|
type: values.type.value,
|
||||||
category: values.category.value,
|
category: values.category.value,
|
||||||
hatchery: values.hatchery,
|
hatchery: values.hatchery ?? '',
|
||||||
phone: values.phone,
|
phone: values.phone,
|
||||||
email: values.email,
|
email: values.email,
|
||||||
address: values.address,
|
address: values.address,
|
||||||
@@ -171,12 +171,12 @@ const SupplierForm = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
formikSetValues(formikInitialValues);
|
formikSetValues(formikInitialValues);
|
||||||
if (formType != 'add') {
|
if (formType != 'add') {
|
||||||
const hatcheryArrays = formikInitialValues.hatchery.split(',');
|
const hatcheryArrays = formikInitialValues.hatchery?.split(',');
|
||||||
const hatcheryCreatedOptions = hatcheryArrays.map((item) => ({
|
const hatcheryCreatedOptions = hatcheryArrays?.map((item) => ({
|
||||||
value: item,
|
value: item,
|
||||||
label: item,
|
label: item,
|
||||||
}));
|
}));
|
||||||
setHatcheryOptionValues(hatcheryCreatedOptions);
|
setHatcheryOptionValues(hatcheryCreatedOptions ?? []);
|
||||||
}
|
}
|
||||||
}, [formikSetValues, formikInitialValues, setHatcheryOptionValues]);
|
}, [formikSetValues, formikInitialValues, setHatcheryOptionValues]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -302,7 +302,6 @@ const SupplierForm = ({
|
|||||||
<SelectInput
|
<SelectInput
|
||||||
isMulti
|
isMulti
|
||||||
createables
|
createables
|
||||||
required
|
|
||||||
placeholder='Pilih Hatchery'
|
placeholder='Pilih Hatchery'
|
||||||
label='Hatchery'
|
label='Hatchery'
|
||||||
value={hatcheryOptionsValues}
|
value={hatcheryOptionsValues}
|
||||||
|
|||||||
@@ -618,7 +618,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
ref={deleteModal.ref}
|
ref={deleteModal.ref}
|
||||||
type='error'
|
type='error'
|
||||||
text={`Apakah anda yakin ingin menghapus data Project Flock ini (${selectedProjectFlock?.flock_name})?`}
|
text={`Apakah anda yakin ingin menghapus data Project Flock ini (${selectedRowIds?.length} data)?`}
|
||||||
secondaryButton={{
|
secondaryButton={{
|
||||||
text: 'Tidak',
|
text: 'Tidak',
|
||||||
}}
|
}}
|
||||||
@@ -633,7 +633,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
|||||||
<ConfirmationModalWithNotes
|
<ConfirmationModalWithNotes
|
||||||
ref={confirmModal.ref}
|
ref={confirmModal.ref}
|
||||||
type={approvalAction == 'APPROVED' ? 'success' : 'error'}
|
type={approvalAction == 'APPROVED' ? 'success' : 'error'}
|
||||||
text={`Apakah anda yakin ingin ${approvalAction == 'APPROVED' ? 'approve' : 'reject'} data Project Flock ini (${selectedRowIds.length} data)?`}
|
text={`Apakah anda yakin ingin ${approvalAction == 'APPROVED' ? 'approve' : 'reject'} data Project Flock ini (${selectedRowIds?.length} data)?`}
|
||||||
secondaryButton={{
|
secondaryButton={{
|
||||||
text: 'Tidak',
|
text: 'Tidak',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ type ProjectFlockFormSchemaType = {
|
|||||||
label: string;
|
label: string;
|
||||||
} | null;
|
} | null;
|
||||||
fcr_id: number;
|
fcr_id: number;
|
||||||
|
production_standard: {
|
||||||
|
value: number | string;
|
||||||
|
label: string;
|
||||||
|
} | null;
|
||||||
|
production_standard_id: number;
|
||||||
location: {
|
location: {
|
||||||
value: number | string;
|
value: number | string;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -100,6 +105,15 @@ export const ProjectFlockFormSchema: Yup.ObjectSchema<ProjectFlockFormSchemaType
|
|||||||
.min(1, 'FCR wajib diisi!')
|
.min(1, 'FCR wajib diisi!')
|
||||||
.required('FCR wajib diisi!'),
|
.required('FCR wajib diisi!'),
|
||||||
|
|
||||||
|
// Production Standard
|
||||||
|
production_standard: Yup.object({
|
||||||
|
value: Yup.number().required('ID Standar Produksi wajib diisi!'),
|
||||||
|
label: Yup.string().required('Nama Standar Produksi wajib diisi!'),
|
||||||
|
}).nullable(),
|
||||||
|
production_standard_id: Yup.number()
|
||||||
|
.min(1, 'Standar Produksi wajib diisi!')
|
||||||
|
.required('Standar Produksi wajib diisi!'),
|
||||||
|
|
||||||
// Location
|
// Location
|
||||||
location: Yup.object({
|
location: Yup.object({
|
||||||
value: Yup.number().required('ID Lokasi wajib diisi!'),
|
value: Yup.number().required('ID Lokasi wajib diisi!'),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
KandangApi,
|
KandangApi,
|
||||||
LocationApi,
|
LocationApi,
|
||||||
NonstockApi,
|
NonstockApi,
|
||||||
|
ProductionStandardApi,
|
||||||
} from '@/services/api/master-data';
|
} from '@/services/api/master-data';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { FormikErrors, useFormik } from 'formik';
|
import { FormikErrors, useFormik } from 'formik';
|
||||||
@@ -136,6 +137,11 @@ const ProjectFlockForm = ({
|
|||||||
'name'
|
'name'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
options: optionsProductionStandards,
|
||||||
|
isLoadingOptions: isLoadingProductionStandards,
|
||||||
|
} = useSelect(ProductionStandardApi.basePath, 'id', 'name');
|
||||||
|
|
||||||
const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({
|
const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({
|
||||||
search: '',
|
search: '',
|
||||||
location_id: selectedLocation == '' ? '0' : selectedLocation,
|
location_id: selectedLocation == '' ? '0' : selectedLocation,
|
||||||
@@ -341,6 +347,12 @@ const ProjectFlockForm = ({
|
|||||||
label: initialValues.fcr.name,
|
label: initialValues.fcr.name,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
|
production_standard: initialValues?.production_standard
|
||||||
|
? {
|
||||||
|
value: initialValues.production_standard?.id,
|
||||||
|
label: initialValues.production_standard.name,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
location: initialValues?.location
|
location: initialValues?.location
|
||||||
? {
|
? {
|
||||||
value: initialValues.location?.id,
|
value: initialValues.location?.id,
|
||||||
@@ -356,6 +368,7 @@ const ProjectFlockForm = ({
|
|||||||
'GROWING' | 'LAYING' | undefined
|
'GROWING' | 'LAYING' | undefined
|
||||||
>,
|
>,
|
||||||
fcr_id: initialValues?.fcr?.id ?? 0,
|
fcr_id: initialValues?.fcr?.id ?? 0,
|
||||||
|
production_standard_id: initialValues?.production_standard?.id ?? 0,
|
||||||
location_id: initialValues?.location?.id ?? 0,
|
location_id: initialValues?.location?.id ?? 0,
|
||||||
kandang_ids: initialValues?.kandangs?.map(
|
kandang_ids: initialValues?.kandangs?.map(
|
||||||
(k: Kandang) => k.id
|
(k: Kandang) => k.id
|
||||||
@@ -400,6 +413,7 @@ const ProjectFlockForm = ({
|
|||||||
area_id: values.area_id as number,
|
area_id: values.area_id as number,
|
||||||
category: values.category as string,
|
category: values.category as string,
|
||||||
fcr_id: values.fcr_id as number,
|
fcr_id: values.fcr_id as number,
|
||||||
|
production_standard_id: values.production_standard_id as number,
|
||||||
location_id: values.location_id as number,
|
location_id: values.location_id as number,
|
||||||
kandang_ids: values.kandang_ids as number[],
|
kandang_ids: values.kandang_ids as number[],
|
||||||
project_budgets: values.project_budgets.flatMap((budget) => {
|
project_budgets: values.project_budgets.flatMap((budget) => {
|
||||||
@@ -858,6 +872,23 @@ const ProjectFlockForm = ({
|
|||||||
isClearable
|
isClearable
|
||||||
isDisabled={formType != 'add'}
|
isDisabled={formType != 'add'}
|
||||||
/>
|
/>
|
||||||
|
<SelectInput
|
||||||
|
required
|
||||||
|
label='Standar Produksi'
|
||||||
|
value={formik.values.production_standard as OptionType}
|
||||||
|
onChange={(val) => {
|
||||||
|
optionChangeHandler(val, 'production_standard');
|
||||||
|
}}
|
||||||
|
options={optionsProductionStandards}
|
||||||
|
isLoading={isLoadingProductionStandards}
|
||||||
|
isError={
|
||||||
|
formik.touched.production_standard &&
|
||||||
|
Boolean(formik.errors.production_standard)
|
||||||
|
}
|
||||||
|
errorMessage={formik.errors.production_standard as string}
|
||||||
|
isClearable
|
||||||
|
isDisabled={formType != 'add'}
|
||||||
|
/>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
required
|
||||||
label='Kategori'
|
label='Kategori'
|
||||||
|
|||||||
@@ -264,17 +264,20 @@ export const FLOCK_CATEGORY_OPTIONS = [
|
|||||||
value: 'LAYING',
|
value: 'LAYING',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PRODUCT_FLAG_OPTIONS = [
|
export const PRODUCT_FLAG_OPTIONS = [
|
||||||
{ label: 'DOC', value: 'DOC' },
|
{ label: 'DOC', value: 'DOC' },
|
||||||
|
{ label: 'EKSPEDISI', value: 'EKSPEDISI' },
|
||||||
|
{ label: 'FINISHER', value: 'FINISHER' },
|
||||||
|
{ label: 'ACTIVE', value: 'IS_ACTIVE' },
|
||||||
|
{ label: 'KIMIA', value: 'KIMIA' },
|
||||||
|
{ label: 'LAYER', value: 'LAYER' },
|
||||||
|
{ label: 'OBAT', value: 'OBAT' },
|
||||||
|
{ label: 'OVK', value: 'OVK' },
|
||||||
{ label: 'PAKAN', value: 'PAKAN' },
|
{ label: 'PAKAN', value: 'PAKAN' },
|
||||||
{ label: 'PRE-STARTER', value: 'PRE-STARTER' },
|
{ label: 'PRE-STARTER', value: 'PRE-STARTER' },
|
||||||
|
{ label: 'PULLET', value: 'PULLET' },
|
||||||
{ label: 'STARTER', value: 'STARTER' },
|
{ label: 'STARTER', value: 'STARTER' },
|
||||||
{ label: 'FINISHER', value: 'FINISHER' },
|
|
||||||
{ label: 'OVK', value: 'OVK' },
|
|
||||||
{ label: 'OBAT', value: 'OBAT' },
|
|
||||||
{ label: 'VITAMIN', value: 'VITAMIN' },
|
{ label: 'VITAMIN', value: 'VITAMIN' },
|
||||||
{ label: 'KIMIA', value: 'KIMIA' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const SUPPLIER_FLAG_OPTIONS = [
|
export const SUPPLIER_FLAG_OPTIONS = [
|
||||||
@@ -305,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', 'BIAYA'];
|
export const FINANCE_TRANSACTION_STATUS = ['PENJUALAN', 'PEMBELIAN', 'BIAYA'];
|
||||||
|
|
||||||
export const FINANCE_INITIAL_BALANCE_STATUS = ['SALDO_AWAL'];
|
export const FINANCE_INITIAL_BALANCE_STATUS = ['SALDO_AWAL'];
|
||||||
|
|
||||||
|
|||||||
@@ -69,16 +69,6 @@ export const ROUTE_PERMISSIONS: Record<string, string[]> = {
|
|||||||
'/expense/realization/edit/': ['lti.expense.update.realization'],
|
'/expense/realization/edit/': ['lti.expense.update.realization'],
|
||||||
|
|
||||||
// Finance
|
// Finance
|
||||||
// // ===== FINANCE =====
|
|
||||||
// "lti.finance.transaction.list",
|
|
||||||
// "lti.finance.transaction.detail",
|
|
||||||
// "lti.finance.transaction.delete",
|
|
||||||
// "lti.finance.payments.create",
|
|
||||||
// "lti.finance.payments.update",
|
|
||||||
// "lti.finance.initial_balances.create",
|
|
||||||
// "lti.finance.initial_balances.update",
|
|
||||||
// "lti.finance.injections.create",
|
|
||||||
// "lti.finance.injections.update",
|
|
||||||
'/finance/': ['lti.finance.transaction.list'],
|
'/finance/': ['lti.finance.transaction.list'],
|
||||||
'/finance/detail/': ['lti.finance.transaction.detail'],
|
'/finance/detail/': ['lti.finance.transaction.detail'],
|
||||||
'/finance/add/': ['lti.finance.payments.create'],
|
'/finance/add/': ['lti.finance.payments.create'],
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ export interface ProductionStandardDetails {
|
|||||||
target_hen_house_production: number;
|
target_hen_house_production: number;
|
||||||
target_egg_weight: number;
|
target_egg_weight: number;
|
||||||
target_egg_mass: number;
|
target_egg_mass: number;
|
||||||
|
standard_fcr: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StandardGrowthDetails {
|
export interface StandardGrowthDetails {
|
||||||
@@ -46,6 +47,7 @@ export interface CreateProductionStandardPayload {
|
|||||||
target_hen_house_production: number;
|
target_hen_house_production: number;
|
||||||
target_egg_weight: number;
|
target_egg_weight: number;
|
||||||
target_egg_mass: number;
|
target_egg_mass: number;
|
||||||
|
standard_fcr: number;
|
||||||
};
|
};
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
@@ -66,6 +68,7 @@ export interface UpdateProductionStandardPayload {
|
|||||||
target_hen_house_production: number;
|
target_hen_house_production: number;
|
||||||
target_egg_weight: number;
|
target_egg_weight: number;
|
||||||
target_egg_mass: number;
|
target_egg_mass: number;
|
||||||
|
standard_fcr: number;
|
||||||
};
|
};
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -16,6 +16,8 @@ export type BaseProjectFlock = {
|
|||||||
category: string;
|
category: string;
|
||||||
fcr: Fcr;
|
fcr: Fcr;
|
||||||
fcr_id: number;
|
fcr_id: number;
|
||||||
|
production_standard: ProductionStandard;
|
||||||
|
production_standard_id: number;
|
||||||
location: Location;
|
location: Location;
|
||||||
location_id: number;
|
location_id: number;
|
||||||
period: number;
|
period: number;
|
||||||
@@ -48,6 +50,7 @@ export type CreateProjectFlockPayload = {
|
|||||||
area_id: number;
|
area_id: number;
|
||||||
category: string;
|
category: string;
|
||||||
fcr_id: number;
|
fcr_id: number;
|
||||||
|
production_standard_id: number;
|
||||||
location_id: number;
|
location_id: number;
|
||||||
kandang_ids: number[];
|
kandang_ids: number[];
|
||||||
project_budgets?: ProjectFlockBudget[];
|
project_budgets?: ProjectFlockBudget[];
|
||||||
|
|||||||
Reference in New Issue
Block a user