update report add standard hpp, realisasi hpp, dan status
This commit is contained in:
parent
3c13aa897c
commit
a3dd214a99
42
package-lock.json
generated
42
package-lock.json
generated
@ -308,6 +308,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
@ -331,6 +332,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
@ -489,6 +491,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
||||||
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.18.3",
|
"@babel/runtime": "^7.18.3",
|
||||||
"@emotion/babel-plugin": "^11.13.5",
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
@ -532,6 +535,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz",
|
||||||
"integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==",
|
"integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.18.3",
|
"@babel/runtime": "^7.18.3",
|
||||||
"@emotion/babel-plugin": "^11.13.5",
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
@ -1211,6 +1215,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz",
|
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz",
|
||||||
"integrity": "sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==",
|
"integrity": "sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"preact": "~10.12.1"
|
"preact": "~10.12.1"
|
||||||
}
|
}
|
||||||
@ -2043,6 +2048,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.2.1.tgz",
|
||||||
"integrity": "sha512-7VlKGsRKsy1bOSOPaSNgpkzaL+0C7iWAVKd2KYyAvhR9fTLJtiAMpq+KuzgEh1so2mtvQERN0tZVIceWMiIesw==",
|
"integrity": "sha512-7VlKGsRKsy1bOSOPaSNgpkzaL+0C7iWAVKd2KYyAvhR9fTLJtiAMpq+KuzgEh1so2mtvQERN0tZVIceWMiIesw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.26.0",
|
"@babel/runtime": "^7.26.0",
|
||||||
"@mui/core-downloads-tracker": "^6.2.1",
|
"@mui/core-downloads-tracker": "^6.2.1",
|
||||||
@ -2955,6 +2961,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.84.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.84.1.tgz",
|
||||||
"integrity": "sha512-zo7EUygcWJMQfFNWDSG7CBhy8irje/XY0RDVKKV4IQJAysb+ZJkkJPcnQi+KboyGUgT+SQebRFoTqLuTtfoDLw==",
|
"integrity": "sha512-zo7EUygcWJMQfFNWDSG7CBhy8irje/XY0RDVKKV4IQJAysb+ZJkkJPcnQi+KboyGUgT+SQebRFoTqLuTtfoDLw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/query-core": "5.83.1"
|
"@tanstack/query-core": "5.83.1"
|
||||||
},
|
},
|
||||||
@ -3021,6 +3028,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.26.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.26.1.tgz",
|
||||||
"integrity": "sha512-fymyd/XZvYiHjBoLt1gxs024xP/LY26d43R1vluYq7AHBL/7DE3ywzy+1GEsGyAv5Je2L0KBhNIR/izbq3Kaqg==",
|
"integrity": "sha512-fymyd/XZvYiHjBoLt1gxs024xP/LY26d43R1vluYq7AHBL/7DE3ywzy+1GEsGyAv5Je2L0KBhNIR/izbq3Kaqg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ueberdosis"
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
@ -3348,6 +3356,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.26.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.26.1.tgz",
|
||||||
"integrity": "sha512-t9Nc/UkrbCfnSHEUi1gvUQ2ZPzvfdYFT5TExoV2DTiUCkhG6+mecT5bTVFGW3QkPmbToL+nFhGn4ZRMDD0SP3Q==",
|
"integrity": "sha512-t9Nc/UkrbCfnSHEUi1gvUQ2ZPzvfdYFT5TExoV2DTiUCkhG6+mecT5bTVFGW3QkPmbToL+nFhGn4ZRMDD0SP3Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ueberdosis"
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
@ -3374,6 +3383,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.26.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.26.1.tgz",
|
||||||
"integrity": "sha512-8aF+mY/vSHbGFqyG663ds84b+vca5Lge3tHdTMTKazxCnhXR9dn2oQJMnZ78YZvdRbkPkMJJHti9h3K7u2UQvw==",
|
"integrity": "sha512-8aF+mY/vSHbGFqyG663ds84b+vca5Lge3tHdTMTKazxCnhXR9dn2oQJMnZ78YZvdRbkPkMJJHti9h3K7u2UQvw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"prosemirror-changeset": "^2.3.0",
|
"prosemirror-changeset": "^2.3.0",
|
||||||
"prosemirror-collab": "^1.3.1",
|
"prosemirror-collab": "^1.3.1",
|
||||||
@ -3672,6 +3682,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
|
||||||
"integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
|
"integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
@ -3683,6 +3694,7 @@
|
|||||||
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^18.0.0"
|
"@types/react": "^18.0.0"
|
||||||
}
|
}
|
||||||
@ -3780,6 +3792,7 @@
|
|||||||
"integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
|
"integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "7.18.0",
|
"@typescript-eslint/scope-manager": "7.18.0",
|
||||||
"@typescript-eslint/types": "7.18.0",
|
"@typescript-eslint/types": "7.18.0",
|
||||||
@ -3989,6 +4002,7 @@
|
|||||||
"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"
|
||||||
},
|
},
|
||||||
@ -4084,6 +4098,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.49.0.tgz",
|
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.49.0.tgz",
|
||||||
"integrity": "sha512-2T9HnbQFLCuYRPndQLmh+bEQFoz0meUbvASaGgiSKDuYhWcLBodJtIpKql2aOtMx4B/sHrWW0dm90HsW4+h2PQ==",
|
"integrity": "sha512-2T9HnbQFLCuYRPndQLmh+bEQFoz0meUbvASaGgiSKDuYhWcLBodJtIpKql2aOtMx4B/sHrWW0dm90HsW4+h2PQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@yr/monotone-cubic-spline": "^1.0.3",
|
"@yr/monotone-cubic-spline": "^1.0.3",
|
||||||
"svg.draggable.js": "^2.2.2",
|
"svg.draggable.js": "^2.2.2",
|
||||||
@ -4747,6 +4762,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001726",
|
"caniuse-lite": "^1.0.30001726",
|
||||||
"electron-to-chromium": "^1.5.173",
|
"electron-to-chromium": "^1.5.173",
|
||||||
@ -5007,6 +5023,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
|
||||||
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
|
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kurkle/color": "^0.3.0"
|
"@kurkle/color": "^0.3.0"
|
||||||
},
|
},
|
||||||
@ -6034,7 +6051,8 @@
|
|||||||
"version": "5.6.0",
|
"version": "5.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz",
|
||||||
"integrity": "sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==",
|
"integrity": "sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "9.2.2",
|
"version": "9.2.2",
|
||||||
@ -6362,6 +6380,7 @@
|
|||||||
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
|
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.6.1",
|
"@eslint-community/regexpp": "^4.6.1",
|
||||||
@ -8793,6 +8812,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.1.tgz",
|
||||||
"integrity": "sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==",
|
"integrity": "sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.26.7",
|
"@babel/runtime": "^7.26.7",
|
||||||
"atob": "^2.1.2",
|
"atob": "^2.1.2",
|
||||||
@ -9214,6 +9234,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.9.0.tgz",
|
||||||
"integrity": "sha512-QKAxLHcbdoqobXuhu2PP6HJDSy0/GhfZuO5O8BrmwfR0ihZbA5ihYD/u0wGqu2QTDWi/DbgCWJIlV2mXh2Sekg==",
|
"integrity": "sha512-QKAxLHcbdoqobXuhu2PP6HJDSy0/GhfZuO5O8BrmwfR0ihZbA5ihYD/u0wGqu2QTDWi/DbgCWJIlV2mXh2Sekg==",
|
||||||
"license": "SEE LICENSE IN LICENSE.txt",
|
"license": "SEE LICENSE IN LICENSE.txt",
|
||||||
|
"peer": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"src/style-spec",
|
"src/style-spec",
|
||||||
"test/build/typings"
|
"test/build/typings"
|
||||||
@ -9540,6 +9561,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/next/-/next-15.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-15.1.9.tgz",
|
||||||
"integrity": "sha512-OoQpDPV2i3o5Hnn46nz2x6fzdFxFO+JsU4ZES12z65/feMjPHKKHLDVQ2NuEvTaXTRisix/G5+6hyTkwK329kA==",
|
"integrity": "sha512-OoQpDPV2i3o5Hnn46nz2x6fzdFxFO+JsU4ZES12z65/feMjPHKKHLDVQ2NuEvTaXTRisix/G5+6hyTkwK329kA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "15.1.9",
|
"@next/env": "15.1.9",
|
||||||
"@swc/counter": "0.1.3",
|
"@swc/counter": "0.1.3",
|
||||||
@ -10173,6 +10195,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@ -10300,6 +10323,7 @@
|
|||||||
"integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
|
"integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssesc": "^3.0.0",
|
"cssesc": "^3.0.0",
|
||||||
"util-deprecate": "^1.0.2"
|
"util-deprecate": "^1.0.2"
|
||||||
@ -10512,6 +10536,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.2.tgz",
|
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.2.tgz",
|
||||||
"integrity": "sha512-BVypCAJ4SL6jOiTsDffP3Wp6wD69lRhI4zg/iT8JXjp3ccZFiq5WyguxvMKmdKFC3prhaig7wSr8dneDToHE1Q==",
|
"integrity": "sha512-BVypCAJ4SL6jOiTsDffP3Wp6wD69lRhI4zg/iT8JXjp3ccZFiq5WyguxvMKmdKFC3prhaig7wSr8dneDToHE1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"orderedmap": "^2.0.0"
|
"orderedmap": "^2.0.0"
|
||||||
}
|
}
|
||||||
@ -10541,6 +10566,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz",
|
||||||
"integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==",
|
"integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"prosemirror-model": "^1.0.0",
|
"prosemirror-model": "^1.0.0",
|
||||||
"prosemirror-transform": "^1.0.0",
|
"prosemirror-transform": "^1.0.0",
|
||||||
@ -10589,6 +10615,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.40.1.tgz",
|
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.40.1.tgz",
|
||||||
"integrity": "sha512-pbwUjt3G7TlsQQHDiYSupWBhJswpLVB09xXm1YiJPdkjkh9Pe7Y51XdLh5VWIZmROLY8UpUpG03lkdhm9lzIBA==",
|
"integrity": "sha512-pbwUjt3G7TlsQQHDiYSupWBhJswpLVB09xXm1YiJPdkjkh9Pe7Y51XdLh5VWIZmROLY8UpUpG03lkdhm9lzIBA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"prosemirror-model": "^1.20.0",
|
"prosemirror-model": "^1.20.0",
|
||||||
"prosemirror-state": "^1.0.0",
|
"prosemirror-state": "^1.0.0",
|
||||||
@ -10715,6 +10742,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
},
|
},
|
||||||
@ -10792,6 +10820,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"scheduler": "^0.23.2"
|
"scheduler": "^0.23.2"
|
||||||
@ -10828,6 +10857,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.1.tgz",
|
||||||
"integrity": "sha512-PUNzFwQeQ5oHiiTUO7GO/EJXGEtuun2Y1A59rLnZBBj+vNEOWt/3ERTiG1/zt7dVeJEM+4vDX/7XQ/qanuvPMg==",
|
"integrity": "sha512-PUNzFwQeQ5oHiiTUO7GO/EJXGEtuun2Y1A59rLnZBBj+vNEOWt/3ERTiG1/zt7dVeJEM+4vDX/7XQ/qanuvPMg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
},
|
},
|
||||||
@ -11184,7 +11214,8 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/redux-thunk": {
|
"node_modules/redux-thunk": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
@ -12610,7 +12641,8 @@
|
|||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.4.tgz",
|
||||||
"integrity": "sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==",
|
"integrity": "sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/stylis-plugin-rtl": {
|
"node_modules/stylis-plugin-rtl": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
@ -13438,7 +13470,8 @@
|
|||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"license": "0BSD"
|
"license": "0BSD",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/tsx": {
|
"node_modules/tsx": {
|
||||||
"version": "4.19.2",
|
"version": "4.19.2",
|
||||||
@ -13570,6 +13603,7 @@
|
|||||||
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
||||||
"devOptional": true,
|
"devOptional": 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"
|
||||||
|
|||||||
@ -15,6 +15,23 @@ import ReportGeneratorComponent from '@/views/dashboards/daily-report/report-gen
|
|||||||
import ReportHeader from '@/views/dashboards/daily-report/report-header'
|
import ReportHeader from '@/views/dashboards/daily-report/report-header'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
// Dummy HPP values — ganti dengan data real nantinya
|
||||||
|
const DUMMY_STD_HPP = 30 // 30%
|
||||||
|
const DUMMY_REAL_HPP = 28 // 28%
|
||||||
|
|
||||||
|
const getHppStatus = (stdHpp: number, realHpp: number): 'Sehat' | 'Tidak Sehat' =>
|
||||||
|
realHpp <= stdHpp ? 'Sehat' : 'Tidak Sehat'
|
||||||
|
|
||||||
|
const StatusBadge = ({ status }: { status: 'Sehat' | 'Tidak Sehat' }) => (
|
||||||
|
<span
|
||||||
|
className={`inline-block px-3 py-1 rounded-full text-base font-bold ${
|
||||||
|
status === 'Sehat' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{status}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
|
||||||
const DailyPOSReport = () => {
|
const DailyPOSReport = () => {
|
||||||
const reportRef = useRef<HTMLDivElement | null>(null)
|
const reportRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
@ -108,11 +125,7 @@ const DailyPOSReport = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getReportTitle = () => {
|
const getReportTitle = () => {
|
||||||
if (filterType === 'single') {
|
|
||||||
return 'Laporan Transaksi'
|
return 'Laporan Transaksi'
|
||||||
} else {
|
|
||||||
return `Laporan Transaksi`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleGeneratePDF = async () => {
|
const handleGeneratePDF = async () => {
|
||||||
@ -342,7 +355,7 @@ const DailyPOSReport = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Category Summary */}
|
{/* Category Summary — +3 kolom baru */}
|
||||||
<div className='px-8 pb-8'>
|
<div className='px-8 pb-8'>
|
||||||
<h3 className='text-3xl font-bold mb-8' style={{ color: '#36175e' }}>
|
<h3 className='text-3xl font-bold mb-8' style={{ color: '#36175e' }}>
|
||||||
Ringkasan Kategori
|
Ringkasan Kategori
|
||||||
@ -355,18 +368,31 @@ const DailyPOSReport = () => {
|
|||||||
<th className='text-left text-xl p-4 font-semibold'>Nama</th>
|
<th className='text-left text-xl p-4 font-semibold'>Nama</th>
|
||||||
<th className='text-center text-xl p-4 font-semibold'>Qty</th>
|
<th className='text-center text-xl p-4 font-semibold'>Qty</th>
|
||||||
<th className='text-right text-xl p-4 font-semibold'>Pendapatan</th>
|
<th className='text-right text-xl p-4 font-semibold'>Pendapatan</th>
|
||||||
|
<th className='text-center text-xl p-4 font-semibold'>% Std HPP</th>
|
||||||
|
<th className='text-center text-xl p-4 font-semibold'>% Real HPP</th>
|
||||||
|
<th className='text-center text-xl p-4 font-semibold'>Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{category?.data?.map((c, index) => (
|
{category?.data?.map((c, index) => {
|
||||||
|
const stdHpp = DUMMY_STD_HPP
|
||||||
|
const realHpp = DUMMY_REAL_HPP
|
||||||
|
const status = getHppStatus(stdHpp, realHpp)
|
||||||
|
return (
|
||||||
<tr key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
|
<tr key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
|
||||||
<td className='p-4 text-xl font-medium text-gray-800'>{c.category_name}</td>
|
<td className='p-4 text-xl font-medium text-gray-800'>{c.category_name}</td>
|
||||||
<td className='p-4 text-xl text-center text-gray-700'>{c.total_quantity}</td>
|
<td className='p-4 text-xl text-center text-gray-700'>{c.total_quantity}</td>
|
||||||
<td className='p-4 text-xl text-right font-semibold' style={{ color: '#36175e' }}>
|
<td className='p-4 text-xl text-right font-semibold' style={{ color: '#36175e' }}>
|
||||||
{formatCurrency(c.total_revenue)}
|
{formatCurrency(c.total_revenue)}
|
||||||
</td>
|
</td>
|
||||||
|
<td className='p-4 text-xl text-center text-gray-700'>{stdHpp}%</td>
|
||||||
|
<td className='p-4 text-xl text-center text-gray-700'>{realHpp}%</td>
|
||||||
|
<td className='p-4 text-center'>
|
||||||
|
<StatusBadge status={status} />
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)) || []}
|
)
|
||||||
|
}) || []}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr className='text-gray-800 border-t-2 border-gray-300'>
|
<tr className='text-gray-800 border-t-2 border-gray-300'>
|
||||||
@ -375,13 +401,16 @@ const DailyPOSReport = () => {
|
|||||||
<td className='p-4 text-xl text-right font-bold'>
|
<td className='p-4 text-xl text-right font-bold'>
|
||||||
{formatCurrency(categorySummary?.totalRevenue ?? 0)}
|
{formatCurrency(categorySummary?.totalRevenue ?? 0)}
|
||||||
</td>
|
</td>
|
||||||
|
<td className='p-4'></td>
|
||||||
|
<td className='p-4'></td>
|
||||||
|
<td className='p-4'></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Product Summary - Dipisah per kategori dengan tabel terpisah */}
|
{/* Product Summary — +3 kolom baru */}
|
||||||
<div className='px-8 pb-8'>
|
<div className='px-8 pb-8'>
|
||||||
<h3 className='text-3xl font-bold mb-8' style={{ color: '#36175e' }}>
|
<h3 className='text-3xl font-bold mb-8' style={{ color: '#36175e' }}>
|
||||||
Ringkasan Item Per Kategori
|
Ringkasan Item Per Kategori
|
||||||
@ -398,7 +427,6 @@ const DailyPOSReport = () => {
|
|||||||
})
|
})
|
||||||
.map((categoryName, catIndex) => {
|
.map((categoryName, catIndex) => {
|
||||||
const categoryProducts = groupedProducts[categoryName].sort((a, b) => {
|
const categoryProducts = groupedProducts[categoryName].sort((a, b) => {
|
||||||
// Sort by product_sku ASC
|
|
||||||
const skuA = a.product_sku || ''
|
const skuA = a.product_sku || ''
|
||||||
const skuB = b.product_sku || ''
|
const skuB = b.product_sku || ''
|
||||||
return skuA.localeCompare(skuB)
|
return skuA.localeCompare(skuB)
|
||||||
@ -421,19 +449,35 @@ const DailyPOSReport = () => {
|
|||||||
style={{ borderCollapse: 'collapse', tableLayout: 'fixed', width: '100%' }}
|
style={{ borderCollapse: 'collapse', tableLayout: 'fixed', width: '100%' }}
|
||||||
>
|
>
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col style={{ width: '50%' }} />
|
<col style={{ width: '28%' }} />
|
||||||
<col style={{ width: '20%' }} />
|
<col style={{ width: '10%' }} />
|
||||||
<col style={{ width: '30%' }} />
|
<col style={{ width: '22%' }} />
|
||||||
|
<col style={{ width: '13%' }} />
|
||||||
|
<col style={{ width: '13%' }} />
|
||||||
|
<col style={{ width: '14%' }} />
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='text-gray-800 border-b-2 border-gray-300 bg-gray-100'>
|
<tr className='text-gray-800 border-b-2 border-gray-300 bg-gray-100'>
|
||||||
<th className='text-left text-xl p-5 font-semibold border-r border-gray-300'>Produk</th>
|
<th className='text-left text-xl p-5 font-semibold border-r border-gray-300'>Produk</th>
|
||||||
<th className='text-center text-xl p-5 font-semibold border-r border-gray-300'>Qty</th>
|
<th className='text-center text-xl p-5 font-semibold border-r border-gray-300'>Qty</th>
|
||||||
<th className='text-right text-xl p-5 font-semibold'>Pendapatan</th>
|
<th className='text-right text-xl p-5 font-semibold border-r border-gray-300'>
|
||||||
|
Pendapatan
|
||||||
|
</th>
|
||||||
|
<th className='text-center text-xl p-5 font-semibold border-r border-gray-300'>
|
||||||
|
% Std HPP
|
||||||
|
</th>
|
||||||
|
<th className='text-center text-xl p-5 font-semibold border-r border-gray-300'>
|
||||||
|
% Real HPP
|
||||||
|
</th>
|
||||||
|
<th className='text-center text-xl p-5 font-semibold'>Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{categoryProducts.map((item, index) => (
|
{categoryProducts.map((item, index) => {
|
||||||
|
const stdHpp = DUMMY_STD_HPP
|
||||||
|
const realHpp = DUMMY_REAL_HPP
|
||||||
|
const status = getHppStatus(stdHpp, realHpp)
|
||||||
|
return (
|
||||||
<tr key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
|
<tr key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
|
||||||
<td className='p-5 text-xl font-medium text-gray-800 border-r border-gray-200'>
|
<td className='p-5 text-xl font-medium text-gray-800 border-r border-gray-200'>
|
||||||
{item.product_name}
|
{item.product_name}
|
||||||
@ -441,11 +485,21 @@ const DailyPOSReport = () => {
|
|||||||
<td className='p-5 text-xl text-center text-gray-700 border-r border-gray-200'>
|
<td className='p-5 text-xl text-center text-gray-700 border-r border-gray-200'>
|
||||||
{item.quantity_sold}
|
{item.quantity_sold}
|
||||||
</td>
|
</td>
|
||||||
<td className='p-5 text-xl text-right font-semibold text-gray-800'>
|
<td className='p-5 text-xl text-right font-semibold text-gray-800 border-r border-gray-200'>
|
||||||
{formatCurrency(item.revenue)}
|
{formatCurrency(item.revenue)}
|
||||||
</td>
|
</td>
|
||||||
|
<td className='p-5 text-xl text-center text-gray-700 border-r border-gray-200'>
|
||||||
|
{stdHpp}%
|
||||||
|
</td>
|
||||||
|
<td className='p-5 text-xl text-center text-gray-700 border-r border-gray-200'>
|
||||||
|
{realHpp}%
|
||||||
|
</td>
|
||||||
|
<td className='p-5 text-center'>
|
||||||
|
<StatusBadge status={status} />
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr className='bg-gray-200 border-t-2 border-gray-400'>
|
<tr className='bg-gray-200 border-t-2 border-gray-400'>
|
||||||
@ -455,9 +509,12 @@ const DailyPOSReport = () => {
|
|||||||
<td className='p-5 text-xl text-center font-bold text-gray-800 border-r border-gray-400'>
|
<td className='p-5 text-xl text-center font-bold text-gray-800 border-r border-gray-400'>
|
||||||
{categoryTotalQty}
|
{categoryTotalQty}
|
||||||
</td>
|
</td>
|
||||||
<td className='p-5 text-xl text-right font-bold text-gray-800'>
|
<td className='p-5 text-xl text-right font-bold text-gray-800 border-r border-gray-400'>
|
||||||
{formatCurrency(categoryTotalRevenue)}
|
{formatCurrency(categoryTotalRevenue)}
|
||||||
</td>
|
</td>
|
||||||
|
<td className='p-5 border-r border-gray-400'></td>
|
||||||
|
<td className='p-5 border-r border-gray-400'></td>
|
||||||
|
<td className='p-5'></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
@ -468,24 +525,35 @@ const DailyPOSReport = () => {
|
|||||||
|
|
||||||
{/* Grand Total */}
|
{/* Grand Total */}
|
||||||
<div className='bg-purple-50 rounded-lg border-2 border-purple-300 mt-6'>
|
<div className='bg-purple-50 rounded-lg border-2 border-purple-300 mt-6'>
|
||||||
<table className='w-full' style={{ borderCollapse: 'collapse' }}>
|
<table className='w-full' style={{ borderCollapse: 'collapse', tableLayout: 'fixed' }}>
|
||||||
|
<colgroup>
|
||||||
|
<col style={{ width: '28%' }} />
|
||||||
|
<col style={{ width: '10%' }} />
|
||||||
|
<col style={{ width: '22%' }} />
|
||||||
|
<col style={{ width: '13%' }} />
|
||||||
|
<col style={{ width: '13%' }} />
|
||||||
|
<col style={{ width: '14%' }} />
|
||||||
|
</colgroup>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr className='text-gray-800'>
|
<tr className='text-gray-800'>
|
||||||
<td
|
<td className='p-5 text-2xl font-bold border-r-2 border-purple-300' style={{ color: '#36175e' }}>
|
||||||
className='p-5 text-2xl font-bold border-r-2 border-purple-300'
|
|
||||||
style={{ width: '50%', color: '#36175e' }}
|
|
||||||
>
|
|
||||||
TOTAL KESELURUHAN
|
TOTAL KESELURUHAN
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
className='p-5 text-2xl text-center font-bold border-r-2 border-purple-300'
|
className='p-5 text-2xl text-center font-bold border-r-2 border-purple-300'
|
||||||
style={{ width: '20%', color: '#36175e' }}
|
style={{ color: '#36175e' }}
|
||||||
>
|
>
|
||||||
{productSummary.totalQuantitySold ?? 0}
|
{productSummary.totalQuantitySold ?? 0}
|
||||||
</td>
|
</td>
|
||||||
<td className='p-5 text-2xl text-right font-bold' style={{ width: '30%', color: '#36175e' }}>
|
<td
|
||||||
|
className='p-5 text-2xl text-right font-bold border-r-2 border-purple-300'
|
||||||
|
style={{ color: '#36175e' }}
|
||||||
|
>
|
||||||
{formatCurrency(productSummary.totalRevenue ?? 0)}
|
{formatCurrency(productSummary.totalRevenue ?? 0)}
|
||||||
</td>
|
</td>
|
||||||
|
<td className='p-5 border-r-2 border-purple-300'></td>
|
||||||
|
<td className='p-5 border-r-2 border-purple-300'></td>
|
||||||
|
<td className='p-5'></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -35,7 +35,7 @@ const getReportPeriodText = (params: ExcelGeneratorParams) => {
|
|||||||
return `${formatDateDDMMYYYY(params.dateRange.startDate)} - ${formatDateDDMMYYYY(params.dateRange.endDate)}`
|
return `${formatDateDDMMYYYY(params.dateRange.startDate)} - ${formatDateDDMMYYYY(params.dateRange.endDate)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== EXCEL STYLES (IMPROVED) ==========
|
// ========== EXCEL STYLES ==========
|
||||||
const headerStyle: Partial<ExcelJS.Style> = {
|
const headerStyle: Partial<ExcelJS.Style> = {
|
||||||
font: { bold: true, size: 12, color: { argb: 'FFFFFFFF' } },
|
font: { bold: true, size: 12, color: { argb: 'FFFFFFFF' } },
|
||||||
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF36175E' } } as ExcelJS.FillPattern,
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF36175E' } } as ExcelJS.FillPattern,
|
||||||
@ -83,7 +83,6 @@ const dataStyle: Partial<ExcelJS.Style> = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zebra striping untuk data rows
|
|
||||||
const dataStyleAlt: Partial<ExcelJS.Style> = {
|
const dataStyleAlt: Partial<ExcelJS.Style> = {
|
||||||
...dataStyle,
|
...dataStyle,
|
||||||
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFAFAFA' } } as ExcelJS.FillPattern
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFAFAFA' } } as ExcelJS.FillPattern
|
||||||
@ -92,6 +91,35 @@ const dataStyleAlt: Partial<ExcelJS.Style> = {
|
|||||||
const currencyFormat = '#,##0'
|
const currencyFormat = '#,##0'
|
||||||
const percentageFormat = '0.0"%"'
|
const percentageFormat = '0.0"%"'
|
||||||
|
|
||||||
|
// ========== HELPER: STATUS STYLE ==========
|
||||||
|
// Sehat → teks hijau gelap, background hijau muda
|
||||||
|
// Tidak Sehat → teks merah gelap, background merah muda
|
||||||
|
const getStatusStyle = (status: 'Sehat' | 'Tidak Sehat', isAlt: boolean): Partial<ExcelJS.Style> => {
|
||||||
|
const baseStyle = isAlt ? dataStyleAlt : dataStyle
|
||||||
|
if (status === 'Sehat') {
|
||||||
|
return {
|
||||||
|
...baseStyle,
|
||||||
|
font: { bold: true, size: 11, color: { argb: 'FF166534' } }, // green-800
|
||||||
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFDCFCE7' } } as ExcelJS.FillPattern, // green-100
|
||||||
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
||||||
|
border: dataStyle.border
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...baseStyle,
|
||||||
|
font: { bold: true, size: 11, color: { argb: 'FF991B1B' } }, // red-800
|
||||||
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFEE2E2' } } as ExcelJS.FillPattern, // red-100
|
||||||
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
||||||
|
border: dataStyle.border
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dummy HPP values — ganti dengan data real nantinya
|
||||||
|
const DUMMY_STD_HPP = 0.3 // 30%
|
||||||
|
const DUMMY_REAL_HPP = 0.28 // 28%
|
||||||
|
const getDummyStatus = (stdHpp: number, realHpp: number): 'Sehat' | 'Tidak Sehat' =>
|
||||||
|
realHpp <= stdHpp ? 'Sehat' : 'Tidak Sehat'
|
||||||
|
|
||||||
export const generateExcel = async (params: ExcelGeneratorParams) => {
|
export const generateExcel = async (params: ExcelGeneratorParams) => {
|
||||||
const {
|
const {
|
||||||
outlet,
|
outlet,
|
||||||
@ -109,7 +137,6 @@ export const generateExcel = async (params: ExcelGeneratorParams) => {
|
|||||||
const ExcelJS = await import('exceljs')
|
const ExcelJS = await import('exceljs')
|
||||||
const workbook = new ExcelJS.Workbook()
|
const workbook = new ExcelJS.Workbook()
|
||||||
|
|
||||||
// Metadata
|
|
||||||
workbook.creator = outlet?.name || 'POS System'
|
workbook.creator = outlet?.name || 'POS System'
|
||||||
workbook.created = new Date()
|
workbook.created = new Date()
|
||||||
|
|
||||||
@ -118,7 +145,6 @@ export const generateExcel = async (params: ExcelGeneratorParams) => {
|
|||||||
views: [{ showGridLines: false }]
|
views: [{ showGridLines: false }]
|
||||||
})
|
})
|
||||||
|
|
||||||
// Title
|
|
||||||
ws1.mergeCells('A1:B1')
|
ws1.mergeCells('A1:B1')
|
||||||
const titleCell = ws1.getCell('A1')
|
const titleCell = ws1.getCell('A1')
|
||||||
titleCell.value = 'LAPORAN TRANSAKSI'
|
titleCell.value = 'LAPORAN TRANSAKSI'
|
||||||
@ -128,7 +154,6 @@ export const generateExcel = async (params: ExcelGeneratorParams) => {
|
|||||||
}
|
}
|
||||||
ws1.getRow(1).height = 30
|
ws1.getRow(1).height = 30
|
||||||
|
|
||||||
// Outlet Info
|
|
||||||
ws1.getCell('A2').value = outlet?.name || ''
|
ws1.getCell('A2').value = outlet?.name || ''
|
||||||
ws1.getCell('A2').style = { ...subtitleStyle, font: { size: 12, color: { argb: 'FF4B5563' } } }
|
ws1.getCell('A2').style = { ...subtitleStyle, font: { size: 12, color: { argb: 'FF4B5563' } } }
|
||||||
ws1.getCell('A3').value = outlet?.address || ''
|
ws1.getCell('A3').value = outlet?.address || ''
|
||||||
@ -141,21 +166,18 @@ export const generateExcel = async (params: ExcelGeneratorParams) => {
|
|||||||
ws1.getCell('A5').style = labelStyle
|
ws1.getCell('A5').style = labelStyle
|
||||||
ws1.getCell('B5').style = { font: { size: 11 } }
|
ws1.getCell('B5').style = { font: { size: 11 } }
|
||||||
|
|
||||||
// Section: Ringkasan
|
|
||||||
ws1.getCell('A7').value = 'RINGKASAN'
|
ws1.getCell('A7').value = 'RINGKASAN'
|
||||||
ws1.getCell('A7').style = {
|
ws1.getCell('A7').style = {
|
||||||
font: { bold: true, size: 14, color: { argb: 'FF36175E' } },
|
font: { bold: true, size: 14, color: { argb: 'FF36175E' } },
|
||||||
alignment: { vertical: 'middle', horizontal: 'left' }
|
alignment: { vertical: 'middle', horizontal: 'left' }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header row
|
|
||||||
ws1.getCell('A8').value = 'Keterangan'
|
ws1.getCell('A8').value = 'Keterangan'
|
||||||
ws1.getCell('B8').value = 'Jumlah (IDR)'
|
ws1.getCell('B8').value = 'Jumlah (IDR)'
|
||||||
ws1.getCell('A8').style = headerStyle
|
ws1.getCell('A8').style = headerStyle
|
||||||
ws1.getCell('B8').style = headerStyle
|
ws1.getCell('B8').style = headerStyle
|
||||||
ws1.getRow(8).height = 25
|
ws1.getRow(8).height = 25
|
||||||
|
|
||||||
// Data rows
|
|
||||||
const summaryData = [
|
const summaryData = [
|
||||||
['Total Penjualan', profitLoss?.summary.total_revenue || 0],
|
['Total Penjualan', profitLoss?.summary.total_revenue || 0],
|
||||||
['Total Diskon', profitLoss?.summary.total_discount || 0],
|
['Total Diskon', profitLoss?.summary.total_discount || 0],
|
||||||
@ -168,33 +190,20 @@ export const generateExcel = async (params: ExcelGeneratorParams) => {
|
|||||||
const rowNum = idx + 9
|
const rowNum = idx + 9
|
||||||
ws1.getCell(`A${rowNum}`).value = row[0]
|
ws1.getCell(`A${rowNum}`).value = row[0]
|
||||||
ws1.getCell(`B${rowNum}`).value = row[1]
|
ws1.getCell(`B${rowNum}`).value = row[1]
|
||||||
|
|
||||||
const isAlt = idx % 2 === 1
|
const isAlt = idx % 2 === 1
|
||||||
const baseStyle = isAlt ? dataStyleAlt : dataStyle
|
const baseStyle = isAlt ? dataStyleAlt : dataStyle
|
||||||
|
|
||||||
ws1.getCell(`A${rowNum}`).style = { ...baseStyle, alignment: { horizontal: 'left' } }
|
ws1.getCell(`A${rowNum}`).style = { ...baseStyle, alignment: { horizontal: 'left' } }
|
||||||
ws1.getCell(`B${rowNum}`).style = {
|
ws1.getCell(`B${rowNum}`).style = { ...baseStyle, alignment: { horizontal: 'right' }, numFmt: currencyFormat }
|
||||||
...baseStyle,
|
|
||||||
alignment: { horizontal: 'right' },
|
|
||||||
numFmt: currencyFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
ws1.getRow(rowNum).height = 22
|
ws1.getRow(rowNum).height = 22
|
||||||
})
|
})
|
||||||
|
|
||||||
// Total row
|
|
||||||
const totalRow = 9 + summaryData.length
|
const totalRow = 9 + summaryData.length
|
||||||
ws1.getCell(`A${totalRow}`).value = 'Total'
|
ws1.getCell(`A${totalRow}`).value = 'Total'
|
||||||
ws1.getCell(`B${totalRow}`).value = profitLoss?.summary.total_revenue || 0
|
ws1.getCell(`B${totalRow}`).value = profitLoss?.summary.total_revenue || 0
|
||||||
ws1.getCell(`A${totalRow}`).style = { ...totalRowStyle, alignment: { horizontal: 'left' } }
|
ws1.getCell(`A${totalRow}`).style = { ...totalRowStyle, alignment: { horizontal: 'left' } }
|
||||||
ws1.getCell(`B${totalRow}`).style = {
|
ws1.getCell(`B${totalRow}`).style = { ...totalRowStyle, alignment: { horizontal: 'right' }, numFmt: currencyFormat }
|
||||||
...totalRowStyle,
|
|
||||||
alignment: { horizontal: 'right' },
|
|
||||||
numFmt: currencyFormat
|
|
||||||
}
|
|
||||||
ws1.getRow(totalRow).height = 25
|
ws1.getRow(totalRow).height = 25
|
||||||
|
|
||||||
// Invoice section (pisah dari tabel ringkasan)
|
|
||||||
const invoiceRow = totalRow + 2
|
const invoiceRow = totalRow + 2
|
||||||
ws1.getCell(`A${invoiceRow}`).value = 'INVOICE'
|
ws1.getCell(`A${invoiceRow}`).value = 'INVOICE'
|
||||||
ws1.getCell(`A${invoiceRow}`).style = {
|
ws1.getCell(`A${invoiceRow}`).style = {
|
||||||
@ -207,7 +216,6 @@ export const generateExcel = async (params: ExcelGeneratorParams) => {
|
|||||||
ws1.getCell(`A${invoiceRow + 1}`).style = { ...dataStyle, font: { bold: true } }
|
ws1.getCell(`A${invoiceRow + 1}`).style = { ...dataStyle, font: { bold: true } }
|
||||||
ws1.getCell(`B${invoiceRow + 1}`).style = { ...dataStyle, alignment: { horizontal: 'right' }, font: { bold: true } }
|
ws1.getCell(`B${invoiceRow + 1}`).style = { ...dataStyle, alignment: { horizontal: 'right' }, font: { bold: true } }
|
||||||
|
|
||||||
// Column widths
|
|
||||||
ws1.getColumn(1).width = 28
|
ws1.getColumn(1).width = 28
|
||||||
ws1.getColumn(2).width = 22
|
ws1.getColumn(2).width = 22
|
||||||
|
|
||||||
@ -216,14 +224,12 @@ export const generateExcel = async (params: ExcelGeneratorParams) => {
|
|||||||
views: [{ showGridLines: false }]
|
views: [{ showGridLines: false }]
|
||||||
})
|
})
|
||||||
|
|
||||||
// Title
|
|
||||||
ws2.mergeCells('A1:D1')
|
ws2.mergeCells('A1:D1')
|
||||||
const paymentTitle = ws2.getCell('A1')
|
const paymentTitle = ws2.getCell('A1')
|
||||||
paymentTitle.value = 'RINGKASAN METODE PEMBAYARAN'
|
paymentTitle.value = 'RINGKASAN METODE PEMBAYARAN'
|
||||||
paymentTitle.style = titleStyle
|
paymentTitle.style = titleStyle
|
||||||
ws2.getRow(1).height = 30
|
ws2.getRow(1).height = 30
|
||||||
|
|
||||||
// Headers
|
|
||||||
const paymentHeaders = ['Metode', 'Jumlah Order', 'Total Amount (IDR)', 'Persentase']
|
const paymentHeaders = ['Metode', 'Jumlah Order', 'Total Amount (IDR)', 'Persentase']
|
||||||
paymentHeaders.forEach((header, idx) => {
|
paymentHeaders.forEach((header, idx) => {
|
||||||
const cell = ws2.getCell(3, idx + 1)
|
const cell = ws2.getCell(3, idx + 1)
|
||||||
@ -232,7 +238,6 @@ export const generateExcel = async (params: ExcelGeneratorParams) => {
|
|||||||
})
|
})
|
||||||
ws2.getRow(3).height = 25
|
ws2.getRow(3).height = 25
|
||||||
|
|
||||||
// Data
|
|
||||||
let paymentRow = 4
|
let paymentRow = 4
|
||||||
paymentAnalytics?.data?.forEach((payment: any, idx: number) => {
|
paymentAnalytics?.data?.forEach((payment: any, idx: number) => {
|
||||||
const isAlt = idx % 2 === 1
|
const isAlt = idx % 2 === 1
|
||||||
@ -245,57 +250,41 @@ export const generateExcel = async (params: ExcelGeneratorParams) => {
|
|||||||
|
|
||||||
ws2.getCell(paymentRow, 1).style = { ...baseStyle, alignment: { horizontal: 'left' } }
|
ws2.getCell(paymentRow, 1).style = { ...baseStyle, alignment: { horizontal: 'left' } }
|
||||||
ws2.getCell(paymentRow, 2).style = { ...baseStyle, alignment: { horizontal: 'center' } }
|
ws2.getCell(paymentRow, 2).style = { ...baseStyle, alignment: { horizontal: 'center' } }
|
||||||
ws2.getCell(paymentRow, 3).style = {
|
ws2.getCell(paymentRow, 3).style = { ...baseStyle, alignment: { horizontal: 'right' }, numFmt: currencyFormat }
|
||||||
...baseStyle,
|
ws2.getCell(paymentRow, 4).style = { ...baseStyle, alignment: { horizontal: 'center' }, numFmt: percentageFormat }
|
||||||
alignment: { horizontal: 'right' },
|
|
||||||
numFmt: currencyFormat
|
|
||||||
}
|
|
||||||
ws2.getCell(paymentRow, 4).style = {
|
|
||||||
...baseStyle,
|
|
||||||
alignment: { horizontal: 'center' },
|
|
||||||
numFmt: percentageFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
ws2.getRow(paymentRow).height = 22
|
ws2.getRow(paymentRow).height = 22
|
||||||
paymentRow++
|
paymentRow++
|
||||||
})
|
})
|
||||||
|
|
||||||
// Total row
|
|
||||||
ws2.getCell(paymentRow, 1).value = 'TOTAL'
|
ws2.getCell(paymentRow, 1).value = 'TOTAL'
|
||||||
ws2.getCell(paymentRow, 2).value = paymentAnalytics?.summary.total_orders || 0
|
ws2.getCell(paymentRow, 2).value = paymentAnalytics?.summary.total_orders || 0
|
||||||
ws2.getCell(paymentRow, 3).value = paymentAnalytics?.summary.total_amount || 0
|
ws2.getCell(paymentRow, 3).value = paymentAnalytics?.summary.total_amount || 0
|
||||||
ws2.getCell(paymentRow, 4).value = ''
|
ws2.getCell(paymentRow, 4).value = ''
|
||||||
|
|
||||||
ws2.getCell(paymentRow, 1).style = { ...totalRowStyle, alignment: { horizontal: 'left' } }
|
ws2.getCell(paymentRow, 1).style = { ...totalRowStyle, alignment: { horizontal: 'left' } }
|
||||||
ws2.getCell(paymentRow, 2).style = { ...totalRowStyle, alignment: { horizontal: 'center' } }
|
ws2.getCell(paymentRow, 2).style = { ...totalRowStyle, alignment: { horizontal: 'center' } }
|
||||||
ws2.getCell(paymentRow, 3).style = {
|
ws2.getCell(paymentRow, 3).style = { ...totalRowStyle, alignment: { horizontal: 'right' }, numFmt: currencyFormat }
|
||||||
...totalRowStyle,
|
|
||||||
alignment: { horizontal: 'right' },
|
|
||||||
numFmt: currencyFormat
|
|
||||||
}
|
|
||||||
ws2.getCell(paymentRow, 4).style = totalRowStyle
|
ws2.getCell(paymentRow, 4).style = totalRowStyle
|
||||||
ws2.getRow(paymentRow).height = 25
|
ws2.getRow(paymentRow).height = 25
|
||||||
|
|
||||||
// Column widths
|
|
||||||
ws2.getColumn(1).width = 28
|
ws2.getColumn(1).width = 28
|
||||||
ws2.getColumn(2).width = 16
|
ws2.getColumn(2).width = 16
|
||||||
ws2.getColumn(3).width = 22
|
ws2.getColumn(3).width = 22
|
||||||
ws2.getColumn(4).width = 16
|
ws2.getColumn(4).width = 16
|
||||||
|
|
||||||
// ============ SHEET 3: KATEGORI ============
|
// ============ SHEET 3: KATEGORI ============
|
||||||
|
// + 3 kolom baru: % Standard HPP | % Realisasi HPP | Status
|
||||||
const ws3 = workbook.addWorksheet('Kategori', {
|
const ws3 = workbook.addWorksheet('Kategori', {
|
||||||
views: [{ showGridLines: false }]
|
views: [{ showGridLines: false }]
|
||||||
})
|
})
|
||||||
|
|
||||||
// Title
|
ws3.mergeCells('A1:F1')
|
||||||
ws3.mergeCells('A1:C1')
|
|
||||||
const categoryTitle = ws3.getCell('A1')
|
const categoryTitle = ws3.getCell('A1')
|
||||||
categoryTitle.value = 'RINGKASAN KATEGORI'
|
categoryTitle.value = 'RINGKASAN KATEGORI'
|
||||||
categoryTitle.style = titleStyle
|
categoryTitle.style = titleStyle
|
||||||
ws3.getRow(1).height = 30
|
ws3.getRow(1).height = 30
|
||||||
|
|
||||||
// Headers
|
const categoryHeaders = ['Nama', 'Qty', 'Pendapatan (IDR)', '% Standard HPP', '% Realisasi HPP', 'Status']
|
||||||
const categoryHeaders = ['Nama', 'Qty', 'Pendapatan (IDR)']
|
|
||||||
categoryHeaders.forEach((header, idx) => {
|
categoryHeaders.forEach((header, idx) => {
|
||||||
const cell = ws3.getCell(3, idx + 1)
|
const cell = ws3.getCell(3, idx + 1)
|
||||||
cell.value = header
|
cell.value = header
|
||||||
@ -303,67 +292,72 @@ export const generateExcel = async (params: ExcelGeneratorParams) => {
|
|||||||
})
|
})
|
||||||
ws3.getRow(3).height = 25
|
ws3.getRow(3).height = 25
|
||||||
|
|
||||||
// Data
|
|
||||||
let categoryRow = 4
|
let categoryRow = 4
|
||||||
category?.data?.forEach((cat: any, idx: number) => {
|
category?.data?.forEach((cat: any, idx: number) => {
|
||||||
const isAlt = idx % 2 === 1
|
const isAlt = idx % 2 === 1
|
||||||
const baseStyle = isAlt ? dataStyleAlt : dataStyle
|
const baseStyle = isAlt ? dataStyleAlt : dataStyle
|
||||||
|
const stdHpp = DUMMY_STD_HPP
|
||||||
|
const realHpp = DUMMY_REAL_HPP
|
||||||
|
const status = getDummyStatus(stdHpp, realHpp)
|
||||||
|
|
||||||
ws3.getCell(categoryRow, 1).value = cat.category_name
|
ws3.getCell(categoryRow, 1).value = cat.category_name
|
||||||
ws3.getCell(categoryRow, 2).value = cat.total_quantity
|
ws3.getCell(categoryRow, 2).value = cat.total_quantity
|
||||||
ws3.getCell(categoryRow, 3).value = cat.total_revenue
|
ws3.getCell(categoryRow, 3).value = cat.total_revenue
|
||||||
|
ws3.getCell(categoryRow, 4).value = stdHpp // akan diformat sebagai persentase
|
||||||
|
ws3.getCell(categoryRow, 5).value = realHpp
|
||||||
|
ws3.getCell(categoryRow, 6).value = status
|
||||||
|
|
||||||
ws3.getCell(categoryRow, 1).style = { ...baseStyle, alignment: { horizontal: 'left' } }
|
ws3.getCell(categoryRow, 1).style = { ...baseStyle, alignment: { horizontal: 'left' } }
|
||||||
ws3.getCell(categoryRow, 2).style = { ...baseStyle, alignment: { horizontal: 'center' } }
|
ws3.getCell(categoryRow, 2).style = { ...baseStyle, alignment: { horizontal: 'center' } }
|
||||||
ws3.getCell(categoryRow, 3).style = {
|
ws3.getCell(categoryRow, 3).style = { ...baseStyle, alignment: { horizontal: 'right' }, numFmt: currencyFormat }
|
||||||
...baseStyle,
|
ws3.getCell(categoryRow, 4).style = { ...baseStyle, alignment: { horizontal: 'center' }, numFmt: '0.0"%"' }
|
||||||
alignment: { horizontal: 'right' },
|
ws3.getCell(categoryRow, 5).style = { ...baseStyle, alignment: { horizontal: 'center' }, numFmt: '0.0"%"' }
|
||||||
numFmt: currencyFormat
|
ws3.getCell(categoryRow, 6).style = getStatusStyle(status, isAlt)
|
||||||
}
|
|
||||||
|
|
||||||
ws3.getRow(categoryRow).height = 22
|
ws3.getRow(categoryRow).height = 22
|
||||||
categoryRow++
|
categoryRow++
|
||||||
})
|
})
|
||||||
|
|
||||||
// Total row
|
// Total row (3 kolom baru dikosongkan)
|
||||||
ws3.getCell(categoryRow, 1).value = 'TOTAL'
|
ws3.getCell(categoryRow, 1).value = 'TOTAL'
|
||||||
ws3.getCell(categoryRow, 2).value = categorySummary.totalQuantity
|
ws3.getCell(categoryRow, 2).value = categorySummary.totalQuantity
|
||||||
ws3.getCell(categoryRow, 3).value = categorySummary.totalRevenue
|
ws3.getCell(categoryRow, 3).value = categorySummary.totalRevenue
|
||||||
|
ws3.getCell(categoryRow, 4).value = ''
|
||||||
|
ws3.getCell(categoryRow, 5).value = ''
|
||||||
|
ws3.getCell(categoryRow, 6).value = ''
|
||||||
|
|
||||||
ws3.getCell(categoryRow, 1).style = { ...totalRowStyle, alignment: { horizontal: 'left' } }
|
ws3.getCell(categoryRow, 1).style = { ...totalRowStyle, alignment: { horizontal: 'left' } }
|
||||||
ws3.getCell(categoryRow, 2).style = { ...totalRowStyle, alignment: { horizontal: 'center' } }
|
ws3.getCell(categoryRow, 2).style = { ...totalRowStyle, alignment: { horizontal: 'center' } }
|
||||||
ws3.getCell(categoryRow, 3).style = {
|
ws3.getCell(categoryRow, 3).style = { ...totalRowStyle, alignment: { horizontal: 'right' }, numFmt: currencyFormat }
|
||||||
...totalRowStyle,
|
ws3.getCell(categoryRow, 4).style = totalRowStyle
|
||||||
alignment: { horizontal: 'right' },
|
ws3.getCell(categoryRow, 5).style = totalRowStyle
|
||||||
numFmt: currencyFormat
|
ws3.getCell(categoryRow, 6).style = totalRowStyle
|
||||||
}
|
|
||||||
ws3.getRow(categoryRow).height = 25
|
ws3.getRow(categoryRow).height = 25
|
||||||
|
|
||||||
// Column widths
|
|
||||||
ws3.getColumn(1).width = 35
|
ws3.getColumn(1).width = 35
|
||||||
ws3.getColumn(2).width = 12
|
ws3.getColumn(2).width = 12
|
||||||
ws3.getColumn(3).width = 22
|
ws3.getColumn(3).width = 22
|
||||||
|
ws3.getColumn(4).width = 18 // % Standard HPP
|
||||||
|
ws3.getColumn(5).width = 18 // % Realisasi HPP
|
||||||
|
ws3.getColumn(6).width = 16 // Status
|
||||||
|
|
||||||
// ============ SHEET 4: DETAIL PRODUK ============
|
// ============ SHEET 4: DETAIL PRODUK ============
|
||||||
|
// + 3 kolom baru: % Standard HPP | % Realisasi HPP | Status
|
||||||
const ws4 = workbook.addWorksheet('Detail Produk', {
|
const ws4 = workbook.addWorksheet('Detail Produk', {
|
||||||
views: [{ showGridLines: false }]
|
views: [{ showGridLines: false }]
|
||||||
})
|
})
|
||||||
|
|
||||||
// Title
|
ws4.mergeCells('A1:F1')
|
||||||
ws4.mergeCells('A1:C1')
|
|
||||||
const productTitle = ws4.getCell('A1')
|
const productTitle = ws4.getCell('A1')
|
||||||
productTitle.value = 'RINGKASAN ITEM PER KATEGORI'
|
productTitle.value = 'RINGKASAN ITEM PER KATEGORI'
|
||||||
productTitle.style = titleStyle
|
productTitle.style = titleStyle
|
||||||
ws4.getRow(1).height = 30
|
ws4.getRow(1).height = 30
|
||||||
|
|
||||||
// Group products by category
|
|
||||||
const groupedProducts =
|
const groupedProducts =
|
||||||
products?.data?.reduce(
|
products?.data?.reduce(
|
||||||
(acc: any, item: any) => {
|
(acc: any, item: any) => {
|
||||||
const categoryName = item.category_name || 'Tidak Berkategori'
|
const categoryName = item.category_name || 'Tidak Berkategori'
|
||||||
if (!acc[categoryName]) {
|
if (!acc[categoryName]) acc[categoryName] = []
|
||||||
acc[categoryName] = []
|
|
||||||
}
|
|
||||||
acc[categoryName].push(item)
|
acc[categoryName].push(item)
|
||||||
return acc
|
return acc
|
||||||
},
|
},
|
||||||
@ -372,18 +366,15 @@ export const generateExcel = async (params: ExcelGeneratorParams) => {
|
|||||||
|
|
||||||
let currentRow = 3
|
let currentRow = 3
|
||||||
|
|
||||||
// Loop through each category
|
|
||||||
Object.keys(groupedProducts)
|
Object.keys(groupedProducts)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
const productsA = groupedProducts[a]
|
const orderA = groupedProducts[a][0]?.category_order ?? 999
|
||||||
const productsB = groupedProducts[b]
|
const orderB = groupedProducts[b][0]?.category_order ?? 999
|
||||||
const orderA = productsA[0]?.category_order ?? 999
|
|
||||||
const orderB = productsB[0]?.category_order ?? 999
|
|
||||||
return orderA - orderB
|
return orderA - orderB
|
||||||
})
|
})
|
||||||
.forEach((categoryName, index) => {
|
.forEach((categoryName, index) => {
|
||||||
// Category header
|
// Category header — span 6 kolom
|
||||||
ws4.mergeCells(`A${currentRow}:C${currentRow}`)
|
ws4.mergeCells(`A${currentRow}:F${currentRow}`)
|
||||||
const catHeader = ws4.getCell(`A${currentRow}`)
|
const catHeader = ws4.getCell(`A${currentRow}`)
|
||||||
catHeader.value = `${index + 1}. ${categoryName.toUpperCase()}`
|
catHeader.value = `${index + 1}. ${categoryName.toUpperCase()}`
|
||||||
catHeader.style = {
|
catHeader.style = {
|
||||||
@ -400,8 +391,8 @@ export const generateExcel = async (params: ExcelGeneratorParams) => {
|
|||||||
ws4.getRow(currentRow).height = 28
|
ws4.getRow(currentRow).height = 28
|
||||||
currentRow++
|
currentRow++
|
||||||
|
|
||||||
// Column headers
|
// Column headers — 6 kolom
|
||||||
const prodHeaders = ['Produk', 'Qty', 'Pendapatan (IDR)']
|
const prodHeaders = ['Produk', 'Qty', 'Pendapatan (IDR)', '% Standard HPP', '% Realisasi HPP', 'Status']
|
||||||
prodHeaders.forEach((header, idx) => {
|
prodHeaders.forEach((header, idx) => {
|
||||||
const cell = ws4.getCell(currentRow, idx + 1)
|
const cell = ws4.getCell(currentRow, idx + 1)
|
||||||
cell.value = header
|
cell.value = header
|
||||||
@ -420,93 +411,104 @@ export const generateExcel = async (params: ExcelGeneratorParams) => {
|
|||||||
ws4.getRow(currentRow).height = 24
|
ws4.getRow(currentRow).height = 24
|
||||||
currentRow++
|
currentRow++
|
||||||
|
|
||||||
// Sort products
|
const categoryProducts = groupedProducts[categoryName].sort((a: any, b: any) =>
|
||||||
const categoryProducts = groupedProducts[categoryName].sort((a: any, b: any) => {
|
(a.product_sku || '').localeCompare(b.product_sku || '')
|
||||||
const skuA = a.product_sku || ''
|
)
|
||||||
const skuB = b.product_sku || ''
|
|
||||||
return skuA.localeCompare(skuB)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add products with zebra striping
|
|
||||||
categoryProducts.forEach((product: any, idx: number) => {
|
categoryProducts.forEach((product: any, idx: number) => {
|
||||||
const isAlt = idx % 2 === 1
|
const isAlt = idx % 2 === 1
|
||||||
const baseStyle = isAlt ? dataStyleAlt : dataStyle
|
const baseStyle = isAlt ? dataStyleAlt : dataStyle
|
||||||
|
const stdHpp = DUMMY_STD_HPP
|
||||||
|
const realHpp = DUMMY_REAL_HPP
|
||||||
|
const status = getDummyStatus(stdHpp, realHpp)
|
||||||
|
|
||||||
ws4.getCell(currentRow, 1).value = product.product_name
|
ws4.getCell(currentRow, 1).value = product.product_name
|
||||||
ws4.getCell(currentRow, 2).value = product.quantity_sold
|
ws4.getCell(currentRow, 2).value = product.quantity_sold
|
||||||
ws4.getCell(currentRow, 3).value = product.revenue
|
ws4.getCell(currentRow, 3).value = product.revenue
|
||||||
|
ws4.getCell(currentRow, 4).value = stdHpp
|
||||||
|
ws4.getCell(currentRow, 5).value = realHpp
|
||||||
|
ws4.getCell(currentRow, 6).value = status
|
||||||
|
|
||||||
ws4.getCell(currentRow, 1).style = { ...baseStyle, alignment: { horizontal: 'left' } }
|
ws4.getCell(currentRow, 1).style = { ...baseStyle, alignment: { horizontal: 'left' } }
|
||||||
ws4.getCell(currentRow, 2).style = { ...baseStyle, alignment: { horizontal: 'center' } }
|
ws4.getCell(currentRow, 2).style = { ...baseStyle, alignment: { horizontal: 'center' } }
|
||||||
ws4.getCell(currentRow, 3).style = {
|
ws4.getCell(currentRow, 3).style = { ...baseStyle, alignment: { horizontal: 'right' }, numFmt: currencyFormat }
|
||||||
...baseStyle,
|
ws4.getCell(currentRow, 4).style = { ...baseStyle, alignment: { horizontal: 'center' }, numFmt: '0.0"%"' }
|
||||||
alignment: { horizontal: 'right' },
|
ws4.getCell(currentRow, 5).style = { ...baseStyle, alignment: { horizontal: 'center' }, numFmt: '0.0"%"' }
|
||||||
numFmt: currencyFormat
|
ws4.getCell(currentRow, 6).style = getStatusStyle(status, isAlt)
|
||||||
}
|
|
||||||
|
|
||||||
ws4.getRow(currentRow).height = 20
|
ws4.getRow(currentRow).height = 20
|
||||||
currentRow++
|
currentRow++
|
||||||
})
|
})
|
||||||
|
|
||||||
// Subtotal
|
// Subtotal — 3 kolom baru dikosongkan
|
||||||
const subQty = categoryProducts.reduce((sum: number, p: any) => sum + p.quantity_sold, 0)
|
const subQty = categoryProducts.reduce((sum: number, p: any) => sum + p.quantity_sold, 0)
|
||||||
const subRevenue = categoryProducts.reduce((sum: number, p: any) => sum + p.revenue, 0)
|
const subRevenue = categoryProducts.reduce((sum: number, p: any) => sum + p.revenue, 0)
|
||||||
|
|
||||||
|
const subtotalFill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF3F4F6' } } as ExcelJS.FillPattern
|
||||||
ws4.getCell(currentRow, 1).value = `Subtotal ${categoryName}`
|
ws4.getCell(currentRow, 1).value = `Subtotal ${categoryName}`
|
||||||
ws4.getCell(currentRow, 2).value = subQty
|
ws4.getCell(currentRow, 2).value = subQty
|
||||||
ws4.getCell(currentRow, 3).value = subRevenue
|
ws4.getCell(currentRow, 3).value = subRevenue
|
||||||
|
ws4.getCell(currentRow, 4).value = ''
|
||||||
|
ws4.getCell(currentRow, 5).value = ''
|
||||||
|
ws4.getCell(currentRow, 6).value = ''
|
||||||
|
|
||||||
ws4.getCell(currentRow, 1).style = {
|
ws4.getCell(currentRow, 1).style = { ...totalRowStyle, alignment: { horizontal: 'left' }, fill: subtotalFill }
|
||||||
...totalRowStyle,
|
ws4.getCell(currentRow, 2).style = { ...totalRowStyle, alignment: { horizontal: 'center' }, fill: subtotalFill }
|
||||||
alignment: { horizontal: 'left' },
|
|
||||||
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF3F4F6' } } as ExcelJS.FillPattern
|
|
||||||
}
|
|
||||||
ws4.getCell(currentRow, 2).style = {
|
|
||||||
...totalRowStyle,
|
|
||||||
alignment: { horizontal: 'center' },
|
|
||||||
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF3F4F6' } } as ExcelJS.FillPattern
|
|
||||||
}
|
|
||||||
ws4.getCell(currentRow, 3).style = {
|
ws4.getCell(currentRow, 3).style = {
|
||||||
...totalRowStyle,
|
...totalRowStyle,
|
||||||
alignment: { horizontal: 'right' },
|
alignment: { horizontal: 'right' },
|
||||||
numFmt: currencyFormat,
|
numFmt: currencyFormat,
|
||||||
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF3F4F6' } } as ExcelJS.FillPattern
|
fill: subtotalFill
|
||||||
}
|
}
|
||||||
|
ws4.getCell(currentRow, 4).style = { ...totalRowStyle, fill: subtotalFill }
|
||||||
|
ws4.getCell(currentRow, 5).style = { ...totalRowStyle, fill: subtotalFill }
|
||||||
|
ws4.getCell(currentRow, 6).style = { ...totalRowStyle, fill: subtotalFill }
|
||||||
|
|
||||||
ws4.getRow(currentRow).height = 24
|
ws4.getRow(currentRow).height = 24
|
||||||
currentRow += 3 // Spacing lebih lega
|
currentRow += 3
|
||||||
})
|
})
|
||||||
|
|
||||||
// Grand Total
|
// Grand Total
|
||||||
|
const grandFill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF36175E' } } as ExcelJS.FillPattern
|
||||||
|
const grandFont = { bold: true, size: 13, color: { argb: 'FFFFFFFF' } }
|
||||||
|
|
||||||
ws4.getCell(currentRow, 1).value = 'TOTAL KESELURUHAN'
|
ws4.getCell(currentRow, 1).value = 'TOTAL KESELURUHAN'
|
||||||
ws4.getCell(currentRow, 2).value = productSummary.totalQuantitySold
|
ws4.getCell(currentRow, 2).value = productSummary.totalQuantitySold
|
||||||
ws4.getCell(currentRow, 3).value = productSummary.totalRevenue
|
ws4.getCell(currentRow, 3).value = productSummary.totalRevenue
|
||||||
|
ws4.getCell(currentRow, 4).value = ''
|
||||||
|
ws4.getCell(currentRow, 5).value = ''
|
||||||
|
ws4.getCell(currentRow, 6).value = ''
|
||||||
|
|
||||||
ws4.getCell(currentRow, 1).style = {
|
ws4.getCell(currentRow, 1).style = {
|
||||||
...totalRowStyle,
|
...totalRowStyle,
|
||||||
alignment: { horizontal: 'left' },
|
alignment: { horizontal: 'left' },
|
||||||
font: { bold: true, size: 13, color: { argb: 'FFFFFFFF' } },
|
font: grandFont,
|
||||||
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF36175E' } } as ExcelJS.FillPattern
|
fill: grandFill
|
||||||
}
|
}
|
||||||
ws4.getCell(currentRow, 2).style = {
|
ws4.getCell(currentRow, 2).style = {
|
||||||
...totalRowStyle,
|
...totalRowStyle,
|
||||||
alignment: { horizontal: 'center' },
|
alignment: { horizontal: 'center' },
|
||||||
font: { bold: true, size: 13, color: { argb: 'FFFFFFFF' } },
|
font: grandFont,
|
||||||
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF36175E' } } as ExcelJS.FillPattern
|
fill: grandFill
|
||||||
}
|
}
|
||||||
ws4.getCell(currentRow, 3).style = {
|
ws4.getCell(currentRow, 3).style = {
|
||||||
...totalRowStyle,
|
...totalRowStyle,
|
||||||
alignment: { horizontal: 'right' },
|
alignment: { horizontal: 'right' },
|
||||||
numFmt: currencyFormat,
|
numFmt: currencyFormat,
|
||||||
font: { bold: true, size: 13, color: { argb: 'FFFFFFFF' } },
|
font: grandFont,
|
||||||
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF36175E' } } as ExcelJS.FillPattern
|
fill: grandFill
|
||||||
}
|
}
|
||||||
|
ws4.getCell(currentRow, 4).style = { ...totalRowStyle, font: grandFont, fill: grandFill }
|
||||||
|
ws4.getCell(currentRow, 5).style = { ...totalRowStyle, font: grandFont, fill: grandFill }
|
||||||
|
ws4.getCell(currentRow, 6).style = { ...totalRowStyle, font: grandFont, fill: grandFill }
|
||||||
ws4.getRow(currentRow).height = 28
|
ws4.getRow(currentRow).height = 28
|
||||||
|
|
||||||
// Column widths
|
|
||||||
ws4.getColumn(1).width = 45
|
ws4.getColumn(1).width = 45
|
||||||
ws4.getColumn(2).width = 12
|
ws4.getColumn(2).width = 12
|
||||||
ws4.getColumn(3).width = 22
|
ws4.getColumn(3).width = 22
|
||||||
|
ws4.getColumn(4).width = 18 // % Standard HPP
|
||||||
|
ws4.getColumn(5).width = 18 // % Realisasi HPP
|
||||||
|
ws4.getColumn(6).width = 16 // Status
|
||||||
|
|
||||||
// ============ GENERATE & DOWNLOAD FILE ============
|
// ============ GENERATE & DOWNLOAD FILE ============
|
||||||
const fileName =
|
const fileName =
|
||||||
|
|||||||
@ -47,6 +47,11 @@ const formatDateForInput = (date: Date) => {
|
|||||||
return date.toISOString().split('T')[0]
|
return date.toISOString().split('T')[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: tentukan warna status
|
||||||
|
const getStatusColor = (status: string): [number, number, number] => {
|
||||||
|
return status === 'Sehat' ? [22, 163, 74] : [220, 38, 38] // green-600 / red-600
|
||||||
|
}
|
||||||
|
|
||||||
export const generatePDF = async (params: PDFGeneratorParams) => {
|
export const generatePDF = async (params: PDFGeneratorParams) => {
|
||||||
const {
|
const {
|
||||||
reportRef,
|
reportRef,
|
||||||
@ -227,23 +232,47 @@ export const generatePDF = async (params: PDFGeneratorParams) => {
|
|||||||
currentY = (pdf as any).lastAutoTable.finalY + 20
|
currentY = (pdf as any).lastAutoTable.finalY + 20
|
||||||
|
|
||||||
// ========== CATEGORY SECTION ==========
|
// ========== CATEGORY SECTION ==========
|
||||||
|
// Kolom baru: % Standard HPP, % Realisasi HPP, Status — diisi dummy visual untuk saat ini
|
||||||
pdf.setFontSize(PDF_FONT_SIZES.heading)
|
pdf.setFontSize(PDF_FONT_SIZES.heading)
|
||||||
pdf.text('Ringkasan Kategori', 14, currentY)
|
pdf.text('Ringkasan Kategori', 14, currentY)
|
||||||
currentY += 15
|
currentY += 15
|
||||||
|
|
||||||
|
const DUMMY_STD_HPP = 30 // % (nilai dummy — ganti dengan data real nantinya)
|
||||||
|
|
||||||
const categoryBody =
|
const categoryBody =
|
||||||
category?.data?.map((c: any) => [c.category_name, String(c.total_quantity), formatCurrency(c.total_revenue)]) || []
|
category?.data?.map((c: any) => {
|
||||||
|
const stdHpp = DUMMY_STD_HPP
|
||||||
|
const realisasiHpp = 28 // dummy — ganti dengan kalkulasi real
|
||||||
|
const status = realisasiHpp <= stdHpp ? 'Sehat' : 'Tidak Sehat'
|
||||||
|
return [
|
||||||
|
c.category_name,
|
||||||
|
String(c.total_quantity),
|
||||||
|
formatCurrency(c.total_revenue),
|
||||||
|
`${stdHpp}%`,
|
||||||
|
`${realisasiHpp}%`,
|
||||||
|
status
|
||||||
|
]
|
||||||
|
}) || []
|
||||||
|
|
||||||
autoTable(pdf, {
|
autoTable(pdf, {
|
||||||
startY: currentY,
|
startY: currentY,
|
||||||
head: [['Nama', 'Qty', 'Pendapatan']],
|
head: [['Nama', 'Qty', 'Pendapatan', '% Std HPP', '% Real HPP', 'Status']],
|
||||||
body: categoryBody,
|
body: categoryBody,
|
||||||
foot: [['TOTAL', String(categorySummary?.totalQuantity ?? 0), formatCurrency(categorySummary?.totalRevenue ?? 0)]],
|
foot: [
|
||||||
|
[
|
||||||
|
'TOTAL',
|
||||||
|
String(categorySummary?.totalQuantity ?? 0),
|
||||||
|
formatCurrency(categorySummary?.totalRevenue ?? 0),
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
''
|
||||||
|
]
|
||||||
|
],
|
||||||
theme: 'grid',
|
theme: 'grid',
|
||||||
showFoot: 'lastPage',
|
showFoot: 'lastPage',
|
||||||
tableWidth: 'auto',
|
tableWidth: 'auto',
|
||||||
styles: {
|
styles: {
|
||||||
fontSize: PDF_FONT_SIZES.tableContent,
|
fontSize: 9,
|
||||||
cellPadding: PDF_SPACING.cellPadding,
|
cellPadding: PDF_SPACING.cellPadding,
|
||||||
lineColor: [0, 0, 0],
|
lineColor: [0, 0, 0],
|
||||||
lineWidth: 0.1
|
lineWidth: 0.1
|
||||||
@ -252,7 +281,7 @@ export const generatePDF = async (params: PDFGeneratorParams) => {
|
|||||||
fillColor: [54, 23, 94],
|
fillColor: [54, 23, 94],
|
||||||
textColor: 255,
|
textColor: 255,
|
||||||
fontStyle: 'bold',
|
fontStyle: 'bold',
|
||||||
fontSize: PDF_FONT_SIZES.tableHeader,
|
fontSize: 9,
|
||||||
lineColor: [0, 0, 0],
|
lineColor: [0, 0, 0],
|
||||||
lineWidth: 0.1
|
lineWidth: 0.1
|
||||||
},
|
},
|
||||||
@ -260,24 +289,37 @@ export const generatePDF = async (params: PDFGeneratorParams) => {
|
|||||||
fillColor: [220, 220, 220],
|
fillColor: [220, 220, 220],
|
||||||
textColor: [60, 60, 60],
|
textColor: [60, 60, 60],
|
||||||
fontStyle: 'bold',
|
fontStyle: 'bold',
|
||||||
fontSize: PDF_FONT_SIZES.tableFooter,
|
fontSize: 9,
|
||||||
lineColor: [0, 0, 0],
|
lineColor: [0, 0, 0],
|
||||||
lineWidth: 0.1,
|
lineWidth: 0.1
|
||||||
halign: 'center'
|
|
||||||
},
|
},
|
||||||
columnStyles: {
|
columnStyles: {
|
||||||
1: { halign: 'center' },
|
1: { halign: 'center' },
|
||||||
2: { halign: 'right' }
|
2: { halign: 'right' },
|
||||||
|
3: { halign: 'center' },
|
||||||
|
4: { halign: 'center' },
|
||||||
|
5: { halign: 'center' }
|
||||||
},
|
},
|
||||||
didParseCell: (data: any) => {
|
didParseCell: (data: any) => {
|
||||||
|
// Footer alignment
|
||||||
if (data.section === 'foot') {
|
if (data.section === 'foot') {
|
||||||
if (data.column.index === 0) {
|
if (data.column.index === 0) data.cell.styles.halign = 'left'
|
||||||
data.cell.styles.halign = 'left'
|
else if (data.column.index === 1) data.cell.styles.halign = 'center'
|
||||||
} else if (data.column.index === 1) {
|
else if (data.column.index === 2) data.cell.styles.halign = 'right'
|
||||||
data.cell.styles.halign = 'center'
|
|
||||||
} else if (data.column.index === 2) {
|
|
||||||
data.cell.styles.halign = 'right'
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
didDrawCell: (data: any) => {
|
||||||
|
// Warna teks Status (kolom index 5)
|
||||||
|
if (data.section === 'body' && data.column.index === 5) {
|
||||||
|
const status = data.cell.text[0]
|
||||||
|
const color = getStatusColor(status)
|
||||||
|
pdf.setTextColor(...color)
|
||||||
|
pdf.setFontSize(9)
|
||||||
|
pdf.setFont('helvetica', 'bold')
|
||||||
|
pdf.text(status, data.cell.x + data.cell.width / 2, data.cell.y + data.cell.height / 2 + 1, { align: 'center' })
|
||||||
|
// Reset warna
|
||||||
|
pdf.setTextColor(0, 0, 0)
|
||||||
|
pdf.setFont('helvetica', 'normal')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
margin: { left: 14, right: 14 }
|
margin: { left: 14, right: 14 }
|
||||||
@ -324,11 +366,20 @@ export const generatePDF = async (params: PDFGeneratorParams) => {
|
|||||||
const categoryTotalQty = categoryProducts.reduce((sum: number, item: any) => sum + (item.quantity_sold || 0), 0)
|
const categoryTotalQty = categoryProducts.reduce((sum: number, item: any) => sum + (item.quantity_sold || 0), 0)
|
||||||
const categoryTotalRevenue = categoryProducts.reduce((sum: number, item: any) => sum + (item.revenue || 0), 0)
|
const categoryTotalRevenue = categoryProducts.reduce((sum: number, item: any) => sum + (item.revenue || 0), 0)
|
||||||
|
|
||||||
const productBody = categoryProducts.map((item: any) => [
|
// Kolom baru: % Standard HPP, % Realisasi HPP, Status — dummy visual
|
||||||
|
const productBody = categoryProducts.map((item: any) => {
|
||||||
|
const stdHpp = 30 // dummy — ganti dengan data real
|
||||||
|
const realisasiHpp = 28 // dummy — ganti dengan kalkulasi real
|
||||||
|
const status = realisasiHpp <= stdHpp ? 'Sehat' : 'Tidak Sehat'
|
||||||
|
return [
|
||||||
item.product_name,
|
item.product_name,
|
||||||
String(item.quantity_sold),
|
String(item.quantity_sold),
|
||||||
formatCurrency(item.revenue)
|
formatCurrency(item.revenue),
|
||||||
])
|
`${stdHpp}%`,
|
||||||
|
`${realisasiHpp}%`,
|
||||||
|
status
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
const estimatedHeight = (productBody.length + 3) * 12
|
const estimatedHeight = (productBody.length + 3) * 12
|
||||||
if (currentY + estimatedHeight > 270) {
|
if (currentY + estimatedHeight > 270) {
|
||||||
@ -344,13 +395,15 @@ export const generatePDF = async (params: PDFGeneratorParams) => {
|
|||||||
|
|
||||||
autoTable(pdf, {
|
autoTable(pdf, {
|
||||||
startY: currentY,
|
startY: currentY,
|
||||||
head: [['Produk', 'Qty', 'Pendapatan']],
|
head: [['Produk', 'Qty', 'Pendapatan', '% Std HPP', '% Real HPP', 'Status']],
|
||||||
body: productBody,
|
body: productBody,
|
||||||
foot: [[`Subtotal ${categoryName}`, String(categoryTotalQty), formatCurrency(categoryTotalRevenue)]],
|
foot: [
|
||||||
|
[`Subtotal ${categoryName}`, String(categoryTotalQty), formatCurrency(categoryTotalRevenue), '', '', '']
|
||||||
|
],
|
||||||
showFoot: 'lastPage',
|
showFoot: 'lastPage',
|
||||||
theme: 'grid',
|
theme: 'grid',
|
||||||
styles: {
|
styles: {
|
||||||
fontSize: PDF_FONT_SIZES.tableContent,
|
fontSize: 9,
|
||||||
cellPadding: PDF_SPACING.cellPadding,
|
cellPadding: PDF_SPACING.cellPadding,
|
||||||
lineColor: [0, 0, 0],
|
lineColor: [0, 0, 0],
|
||||||
lineWidth: 0.1
|
lineWidth: 0.1
|
||||||
@ -359,7 +412,7 @@ export const generatePDF = async (params: PDFGeneratorParams) => {
|
|||||||
fillColor: [54, 23, 94],
|
fillColor: [54, 23, 94],
|
||||||
textColor: 255,
|
textColor: 255,
|
||||||
fontStyle: 'bold',
|
fontStyle: 'bold',
|
||||||
fontSize: PDF_FONT_SIZES.tableHeader,
|
fontSize: 9,
|
||||||
lineColor: [0, 0, 0],
|
lineColor: [0, 0, 0],
|
||||||
lineWidth: 0.1
|
lineWidth: 0.1
|
||||||
},
|
},
|
||||||
@ -367,25 +420,38 @@ export const generatePDF = async (params: PDFGeneratorParams) => {
|
|||||||
fillColor: [200, 200, 200],
|
fillColor: [200, 200, 200],
|
||||||
textColor: [60, 60, 60],
|
textColor: [60, 60, 60],
|
||||||
fontStyle: 'bold',
|
fontStyle: 'bold',
|
||||||
fontSize: PDF_FONT_SIZES.tableFooter,
|
fontSize: 9,
|
||||||
lineColor: [0, 0, 0],
|
lineColor: [0, 0, 0],
|
||||||
lineWidth: 0.1,
|
lineWidth: 0.1
|
||||||
halign: 'center'
|
|
||||||
},
|
},
|
||||||
columnStyles: {
|
columnStyles: {
|
||||||
0: { cellWidth: 90 },
|
0: { cellWidth: 55 },
|
||||||
1: { halign: 'center', cellWidth: 40 },
|
1: { halign: 'center', cellWidth: 20 },
|
||||||
2: { halign: 'right', cellWidth: 52 }
|
2: { halign: 'right', cellWidth: 35 },
|
||||||
|
3: { halign: 'center', cellWidth: 22 },
|
||||||
|
4: { halign: 'center', cellWidth: 22 },
|
||||||
|
5: { halign: 'center', cellWidth: 22 }
|
||||||
},
|
},
|
||||||
didParseCell: (data: any) => {
|
didParseCell: (data: any) => {
|
||||||
if (data.section === 'foot') {
|
if (data.section === 'foot') {
|
||||||
if (data.column.index === 0) {
|
if (data.column.index === 0) data.cell.styles.halign = 'left'
|
||||||
data.cell.styles.halign = 'left'
|
else if (data.column.index === 1) data.cell.styles.halign = 'center'
|
||||||
} else if (data.column.index === 1) {
|
else if (data.column.index === 2) data.cell.styles.halign = 'right'
|
||||||
data.cell.styles.halign = 'center'
|
|
||||||
} else if (data.column.index === 2) {
|
|
||||||
data.cell.styles.halign = 'right'
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
didDrawCell: (data: any) => {
|
||||||
|
// Warna teks Status (kolom index 5)
|
||||||
|
if (data.section === 'body' && data.column.index === 5) {
|
||||||
|
const status = data.cell.text[0]
|
||||||
|
const color = getStatusColor(status)
|
||||||
|
pdf.setTextColor(...color)
|
||||||
|
pdf.setFontSize(9)
|
||||||
|
pdf.setFont('helvetica', 'bold')
|
||||||
|
pdf.text(status, data.cell.x + data.cell.width / 2, data.cell.y + data.cell.height / 2 + 1, {
|
||||||
|
align: 'center'
|
||||||
|
})
|
||||||
|
pdf.setTextColor(0, 0, 0)
|
||||||
|
pdf.setFont('helvetica', 'normal')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
margin: { left: 14, right: 14 }
|
margin: { left: 14, right: 14 }
|
||||||
@ -404,7 +470,14 @@ export const generatePDF = async (params: PDFGeneratorParams) => {
|
|||||||
startY: currentY,
|
startY: currentY,
|
||||||
head: [],
|
head: [],
|
||||||
body: [
|
body: [
|
||||||
['TOTAL KESELURUHAN', String(productSummary.totalQuantitySold), formatCurrency(productSummary.totalRevenue)]
|
[
|
||||||
|
'TOTAL KESELURUHAN',
|
||||||
|
String(productSummary.totalQuantitySold),
|
||||||
|
formatCurrency(productSummary.totalRevenue),
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
''
|
||||||
|
]
|
||||||
],
|
],
|
||||||
theme: 'grid',
|
theme: 'grid',
|
||||||
styles: {
|
styles: {
|
||||||
@ -416,9 +489,12 @@ export const generatePDF = async (params: PDFGeneratorParams) => {
|
|||||||
lineWidth: 0.2
|
lineWidth: 0.2
|
||||||
},
|
},
|
||||||
columnStyles: {
|
columnStyles: {
|
||||||
0: { cellWidth: 90 },
|
0: { cellWidth: 55 },
|
||||||
1: { halign: 'center', cellWidth: 40 },
|
1: { halign: 'center', cellWidth: 20 },
|
||||||
2: { halign: 'right', cellWidth: 52 }
|
2: { halign: 'right', cellWidth: 35 },
|
||||||
|
3: { cellWidth: 22 },
|
||||||
|
4: { cellWidth: 22 },
|
||||||
|
5: { cellWidth: 22 }
|
||||||
},
|
},
|
||||||
margin: { left: 14, right: 14 }
|
margin: { left: 14, right: 14 }
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user