diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ee8a79a5..e02ea8ee 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,6 +2,17 @@ stages:
- build
- deploy
+# ==========================================================
+# ✅ Global defaults
+# ==========================================================
+default:
+ tags:
+ - self-hosted
+ interruptible: true
+
+# ==========================================================
+# 🏗️ Build Template
+# ==========================================================
.build_template: &build_template
stage: build
image: node:20-alpine
@@ -39,6 +50,9 @@ stages:
- out/
expire_in: 1 week
+# ==========================================================
+# 🚀 Deploy Template
+# ==========================================================
.deploy_template: &deploy_template
stage: deploy
image:
@@ -82,11 +96,11 @@ stages:
if [ "$STATUS" = "success" ]; then
COLOR=3066993
TITLE="✅ Deployment ${ENVIRONMENT_NAME} Succeeded"
- DESC="Deployment job on branch \`${CI_COMMIT_REF_NAME}\` completed successfully."
+ DESC="Deployment job on branch \${CI_COMMIT_REF_NAME}\ completed successfully."
else
COLOR=15158332
TITLE="❌ Deployment ${ENVIRONMENT_NAME} Failed"
- DESC="Deployment job on branch \`${CI_COMMIT_REF_NAME}\` encountered issues."
+ DESC="Deployment job on branch \${CI_COMMIT_REF_NAME}\ encountered issues."
fi
jq -n \
@@ -114,7 +128,9 @@ stages:
curl -sS -H "Content-Type: application/json" -d @payload.json "$DISCORD_WEBHOOK_URL"
-# ====== DEVELOPMENT (Branch development) ======
+# ==========================================================
+# ==== DEVELOPMENT (Branch development) ======
+# ==========================================================
build:dev:
<<: *build_template
rules:
@@ -140,7 +156,9 @@ deploy:dev:
name: development
url: https://dev-lti-erp.mbugroup.id
+# ==========================================================
# ====== STAGING (Branch staging) ======
+# ==========================================================
build:staging:
<<: *build_template
rules:
@@ -165,27 +183,3 @@ deploy:staging:
environment:
name: staging
url: https://stg-lti-erp.mbugroup.id
-
-
-# ====== PRODUCTION ======
-# build:production:
-# <<: *build_template
-# rules:
-# # pilih salah satu: pakai branch master ATAU pakai tags rilis
-# - if: '$CI_COMMIT_BRANCH == "master"'
-# # - if: '$CI_COMMIT_TAG' # kalau mau rilis via tag, uncomment ini dan hapus baris di atas
-# environment:
-# name: production
-
-# deploy:production:
-# <<: *deploy_template
-# needs: ["build:production"]
-# rules:
-# - if: '$CI_COMMIT_BRANCH == "master"'
-# # - if: '$CI_COMMIT_TAG' # selaras dengan rule di build:production
-# variables:
-# S3_BUCKET: "lti-erp.mbugroup.id"
-# CLOUDFRONT_DISTRIBUTION_ID: "ddfd"
-# environment:
-# name: production
-
diff --git a/.husky/pre-commit b/.husky/pre-commit
index 3782914b..ff51d55a 100644
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,3 +1,3 @@
npm run format
npm run lint
-npm run build
\ No newline at end of file
+npx tsc --noEmit
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index f0212474..d7ffd3eb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,8 +14,10 @@
"axios": "^1.12.2",
"clsx": "^2.1.1",
"formik": "^2.4.6",
+ "jspdf": "^3.0.4",
+ "jspdf-autotable": "^5.0.2",
"moment": "^2.30.1",
- "next": "15.5.7",
+ "next": "15.5.9",
"react": "19.1.0",
"react-day-picker": "^9.11.1",
"react-dom": "19.1.0",
@@ -23,9 +25,11 @@
"react-hot-toast": "^2.6.0",
"react-number-format": "^5.4.4",
"react-select": "^5.10.2",
+ "recharts": "^3.6.0",
"swr": "^2.3.6",
"tailwind-merge": "^3.3.1",
"use-debounce": "^10.0.6",
+ "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
"yup": "^1.7.0",
"zustand": "^5.0.8"
},
@@ -1082,9 +1086,9 @@
}
},
"node_modules/@next/env": {
- "version": "15.5.7",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz",
- "integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==",
+ "version": "15.5.9",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.9.tgz",
+ "integrity": "sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==",
"license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
@@ -1447,6 +1451,42 @@
"@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": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1461,6 +1501,18 @@
"dev": true,
"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": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -1801,6 +1853,69 @@
"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": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -1844,12 +1959,25 @@
"undici-types": "~6.21.0"
}
},
+ "node_modules/@types/pako": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",
+ "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
+ "license": "MIT"
+ },
"node_modules/@types/parse-json": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
"license": "MIT"
},
+ "node_modules/@types/raf": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
+ "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@types/react": {
"version": "19.2.2",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
@@ -1878,6 +2006,19 @@
"@types/react": "*"
}
},
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "license": "MIT",
+ "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": {
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz",
@@ -2775,6 +2916,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/base64-arraybuffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+ "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -2924,6 +3075,26 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/canvg": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
+ "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@types/raf": "^3.4.0",
+ "core-js": "^3.8.3",
+ "raf": "^3.4.1",
+ "regenerator-runtime": "^0.13.7",
+ "rgbcolor": "^1.0.1",
+ "stackblur-canvas": "^2.0.0",
+ "svg-pathdata": "^6.0.3"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -3019,6 +3190,18 @@
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
"license": "MIT"
},
+ "node_modules/core-js": {
+ "version": "3.47.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz",
+ "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -3056,12 +3239,143 @@
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
"license": "MIT"
},
+ "node_modules/css-line-break": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+ "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
+ "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": {
"version": "5.5.8",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.5.8.tgz",
@@ -3166,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": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -3275,6 +3595,16 @@
"csstype": "^3.0.2"
}
},
+ "node_modules/dompurify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz",
+ "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "optional": true,
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -3498,6 +3828,16 @@
"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": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -3935,6 +4275,12 @@
"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": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@@ -3994,6 +4340,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-png": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz",
+ "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/pako": "^2.0.3",
+ "iobuffer": "^5.3.2",
+ "pako": "^2.1.0"
+ }
+ },
+ "node_modules/fast-png/node_modules/pako": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
+ "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
+ "license": "(MIT AND Zlib)"
+ },
"node_modules/fastq": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
@@ -4004,6 +4367,12 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "license": "MIT"
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -4491,6 +4860,20 @@
"integrity": "sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==",
"license": "ISC"
},
+ "node_modules/html2canvas": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+ "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "css-line-break": "^2.1.0",
+ "text-segmentation": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/husky": {
"version": "9.1.7",
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
@@ -4523,6 +4906,16 @@
"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": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -4570,6 +4963,21 @@
"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": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
+ "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==",
+ "license": "MIT"
+ },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -5105,6 +5513,32 @@
"json5": "lib/cli.js"
}
},
+ "node_modules/jspdf": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.4.tgz",
+ "integrity": "sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.4",
+ "fast-png": "^6.2.0",
+ "fflate": "^0.8.1"
+ },
+ "optionalDependencies": {
+ "canvg": "^3.0.11",
+ "core-js": "^3.6.0",
+ "dompurify": "^3.2.4",
+ "html2canvas": "^1.0.0-rc.5"
+ }
+ },
+ "node_modules/jspdf-autotable": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-5.0.2.tgz",
+ "integrity": "sha512-YNKeB7qmx3pxOLcNeoqAv3qTS7KuvVwkFe5AduCawpop3NOkBUtqDToxNc225MlNecxT4kP2Zy3z/y/yvGdXUQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "jspdf": "^2 || ^3"
+ }
+ },
"node_modules/jsx-ast-utils": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -5654,12 +6088,12 @@
"license": "MIT"
},
"node_modules/next": {
- "version": "15.5.7",
- "resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz",
- "integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==",
+ "version": "15.5.9",
+ "resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz",
+ "integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==",
"license": "MIT",
"dependencies": {
- "@next/env": "15.5.7",
+ "@next/env": "15.5.9",
"@swc/helpers": "0.5.15",
"caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31",
@@ -6009,6 +6443,13 @@
"node": ">=8"
}
},
+ "node_modules/performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -6162,6 +6603,16 @@
],
"license": "MIT"
},
+ "node_modules/raf": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
+ "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "performance-now": "^2.1.0"
+ }
+ },
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
@@ -6260,6 +6711,29 @@
"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": {
"version": "5.10.2",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz",
@@ -6297,6 +6771,51 @@
"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": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -6320,6 +6839,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/regenerator-runtime": {
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/regexp.prototype.flags": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
@@ -6356,6 +6882,12 @@
"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": {
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
@@ -6412,6 +6944,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/rgbcolor": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
+ "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+ "license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.8.15"
+ }
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -6761,6 +7303,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/stackblur-canvas": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
+ "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.1.14"
+ }
+ },
"node_modules/stop-iteration-iterator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
@@ -6980,6 +7532,16 @@
"integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==",
"license": "ISC"
},
+ "node_modules/svg-pathdata": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+ "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/swr": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz",
@@ -7024,6 +7586,16 @@
"url": "https://opencollective.com/webpack"
}
},
+ "node_modules/text-segmentation": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+ "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
"node_modules/tiny-case": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
@@ -7036,6 +7608,12 @@
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
"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": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
@@ -7396,6 +7974,38 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
+ "node_modules/utrie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+ "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "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": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz",
@@ -7525,6 +8135,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/xlsx": {
+ "version": "0.20.3",
+ "resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
+ "integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==",
+ "license": "Apache-2.0",
+ "bin": {
+ "xlsx": "bin/xlsx.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
diff --git a/package.json b/package.json
index 52fc6ce2..319f0e3d 100644
--- a/package.json
+++ b/package.json
@@ -17,8 +17,10 @@
"axios": "^1.12.2",
"clsx": "^2.1.1",
"formik": "^2.4.6",
+ "jspdf": "^3.0.4",
+ "jspdf-autotable": "^5.0.2",
"moment": "^2.30.1",
- "next": "15.5.7",
+ "next": "15.5.9",
"react": "19.1.0",
"react-day-picker": "^9.11.1",
"react-dom": "19.1.0",
@@ -26,9 +28,11 @@
"react-hot-toast": "^2.6.0",
"react-number-format": "^5.4.4",
"react-select": "^5.10.2",
+ "recharts": "^3.6.0",
"swr": "^2.3.6",
"tailwind-merge": "^3.3.1",
"use-debounce": "^10.0.6",
+ "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
"yup": "^1.7.0",
"zustand": "^5.0.8"
},
diff --git a/src/app/closing/detail/page.tsx b/src/app/closing/detail/page.tsx
index 1b4ebc45..62f3fa20 100644
--- a/src/app/closing/detail/page.tsx
+++ b/src/app/closing/detail/page.tsx
@@ -24,6 +24,11 @@ const ClosingDetailPage = () => {
() => ClosingApi.getPenjualan(Number(closingId))
);
+ const { data: hppEkspedisiData, isLoading: isLoadingHppEkspedisi } = useSWR(
+ closingId ? `hpp-ekspedisi-${closingId}` : null,
+ () => ClosingApi.getHppEkspedisi(Number(closingId))
+ );
+
if (!closingId) {
router.back();
@@ -39,7 +44,7 @@ const ClosingDetailPage = () => {
return;
}
- const isLoading = isLoadingClosing || isLoadingSales;
+ const isLoading = isLoadingClosing || isLoadingSales || isLoadingHppEkspedisi;
return (
@@ -50,6 +55,11 @@ const ClosingDetailPage = () => {
id={Number(closingId)}
initialValue={closing.data}
salesData={isResponseSuccess(salesData) ? salesData.data : undefined}
+ hppExpeditionData={
+ isResponseSuccess(hppEkspedisiData)
+ ? hppEkspedisiData.data
+ : undefined
+ }
/>
)}
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
index 4f2c344e..426cf6b9 100644
--- a/src/app/dashboard/page.tsx
+++ b/src/app/dashboard/page.tsx
@@ -1,9 +1,7 @@
+import DashboardProduction from '@/components/pages/dashboard/DashboardProduction';
+
const Dashboard = () => {
- return (
-
- );
+ return ;
};
export default Dashboard;
diff --git a/src/app/finance/add/adjust/page.tsx b/src/app/finance/add/adjust/page.tsx
new file mode 100644
index 00000000..3536892d
--- /dev/null
+++ b/src/app/finance/add/adjust/page.tsx
@@ -0,0 +1,5 @@
+const FinanceAdjust = () => {
+ return Finance Adjust
;
+};
+
+export default FinanceAdjust;
diff --git a/src/app/finance/add/initial-balance/page.tsx b/src/app/finance/add/initial-balance/page.tsx
new file mode 100644
index 00000000..fb3114ad
--- /dev/null
+++ b/src/app/finance/add/initial-balance/page.tsx
@@ -0,0 +1,7 @@
+import FormFinanceAddInitialBalance from '@/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance';
+
+const FinanceAddInitialBalancePage = () => {
+ return ;
+};
+
+export default FinanceAddInitialBalancePage;
diff --git a/src/app/finance/add/injection/page.tsx b/src/app/finance/add/injection/page.tsx
new file mode 100644
index 00000000..502df04b
--- /dev/null
+++ b/src/app/finance/add/injection/page.tsx
@@ -0,0 +1,7 @@
+import FormFinanceInjection from '@/components/pages/finance/add/injection/FormFinanceInjection';
+
+const FinanceAddInjectionPage = () => {
+ return ;
+};
+
+export default FinanceAddInjectionPage;
diff --git a/src/app/finance/add/page.tsx b/src/app/finance/add/page.tsx
new file mode 100644
index 00000000..162cd7ec
--- /dev/null
+++ b/src/app/finance/add/page.tsx
@@ -0,0 +1,7 @@
+import FormFinanceAdd from '@/components/pages/finance/add/FormFinanceAdd';
+
+const FinanceAddPage = () => {
+ return ;
+};
+
+export default FinanceAddPage;
diff --git a/src/app/finance/detail/edit/initial-balance/page.tsx b/src/app/finance/detail/edit/initial-balance/page.tsx
new file mode 100644
index 00000000..fddb46d9
--- /dev/null
+++ b/src/app/finance/detail/edit/initial-balance/page.tsx
@@ -0,0 +1,51 @@
+'use client';
+
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
+import { FinanceApi } from '@/services/api/finance';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import FormFinanceAddInitialBalance from '@/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance';
+
+const EditFinanceInitialBalancePage = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const financeId = searchParams.get('financeId');
+
+ const { data: finance, isLoading: isLoadingFinance } = useSWR(
+ financeId,
+ (id: number) => FinanceApi.getSingle(id)
+ );
+
+ if (!financeId) {
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ if (!isLoadingFinance && (!finance || isResponseError(finance))) {
+ router.replace('/404');
+ return;
+ }
+
+ return (
+
+ {isLoadingFinance && (
+
+ )}
+
+ {!isLoadingFinance && (
+
+ )}
+
+ );
+};
+
+export default EditFinanceInitialBalancePage;
diff --git a/src/app/finance/detail/edit/injection/page.tsx b/src/app/finance/detail/edit/injection/page.tsx
new file mode 100644
index 00000000..a538ffd1
--- /dev/null
+++ b/src/app/finance/detail/edit/injection/page.tsx
@@ -0,0 +1,51 @@
+'use client';
+
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
+import { FinanceApi } from '@/services/api/finance';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import FormFinanceInjection from '@/components/pages/finance/add/injection/FormFinanceInjection';
+
+const EditFinanceInjectionPage = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const financeId = searchParams.get('financeId');
+
+ const { data: finance, isLoading: isLoadingFinance } = useSWR(
+ financeId,
+ (id: number) => FinanceApi.getSingle(id)
+ );
+
+ if (!financeId) {
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ if (!isLoadingFinance && (!finance || isResponseError(finance))) {
+ router.replace('/404');
+ return;
+ }
+
+ return (
+
+ {isLoadingFinance && (
+
+ )}
+
+ {!isLoadingFinance && (
+
+ )}
+
+ );
+};
+
+export default EditFinanceInjectionPage;
diff --git a/src/app/finance/detail/edit/page.tsx b/src/app/finance/detail/edit/page.tsx
new file mode 100644
index 00000000..93a0daea
--- /dev/null
+++ b/src/app/finance/detail/edit/page.tsx
@@ -0,0 +1,52 @@
+'use client';
+
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
+import { FinanceApi } from '@/services/api/finance';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import FormFinanceAdd from '@/components/pages/finance/add/FormFinanceAdd';
+import FormFinanceAddInitialBalance from '@/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance';
+
+const EditFinanceTransactionPage = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const financeId = searchParams.get('financeId');
+
+ const { data: finance, isLoading: isLoadingFinance } = useSWR(
+ financeId,
+ (id: number) => FinanceApi.getSingle(id)
+ );
+
+ if (!financeId) {
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ if (!isLoadingFinance && (!finance || isResponseError(finance))) {
+ router.replace('/404');
+ return;
+ }
+
+ return (
+
+ {isLoadingFinance && (
+
+ )}
+
+ {!isLoadingFinance && (
+
+ )}
+
+ );
+};
+
+export default EditFinanceTransactionPage;
diff --git a/src/app/production/project-flock/chickin/add/layout.tsx b/src/app/finance/detail/layout.tsx
similarity index 100%
rename from src/app/production/project-flock/chickin/add/layout.tsx
rename to src/app/finance/detail/layout.tsx
diff --git a/src/app/finance/detail/page.tsx b/src/app/finance/detail/page.tsx
new file mode 100644
index 00000000..1d20e9f5
--- /dev/null
+++ b/src/app/finance/detail/page.tsx
@@ -0,0 +1,41 @@
+'use client';
+
+import FinanceDetail from '@/components/pages/finance/FinanceDetail';
+import useSWR from 'swr';
+import { useRouter, useSearchParams } from 'next/navigation';
+import { FinanceApi } from '@/services/api/finance';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+
+const FinanceDetailPage = () => {
+ const router = useRouter();
+ const financeId = useSearchParams().get('financeId');
+
+ const { data: finance } = useSWR(financeId, () =>
+ FinanceApi.getSingle(Number(financeId))
+ );
+
+ if (!financeId) {
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ console.log(finance);
+
+ // if (!finance || isResponseError(finance)) {
+ // router.replace('/404');
+ // return;
+ // }
+
+ return (
+ <>
+ {isResponseSuccess(finance) && }
+ >
+ );
+};
+
+export default FinanceDetailPage;
diff --git a/src/app/finance/page.tsx b/src/app/finance/page.tsx
new file mode 100644
index 00000000..ec78820c
--- /dev/null
+++ b/src/app/finance/page.tsx
@@ -0,0 +1,14 @@
+'use client';
+
+import FinanceTable from '@/components/pages/finance/FinanceTable';
+
+const Finance = () => {
+ return (
+
+ );
+};
+
+export default Finance;
diff --git a/src/app/master-data/production-standard/add/page.tsx b/src/app/master-data/production-standard/add/page.tsx
new file mode 100644
index 00000000..f25338d6
--- /dev/null
+++ b/src/app/master-data/production-standard/add/page.tsx
@@ -0,0 +1,13 @@
+'use client';
+
+import ProductionStandardForm from '@/components/pages/master-data/production-standard/form/ProductionStandardForm';
+
+const AddProductionStandardPage = () => {
+ return (
+ <>
+
+ >
+ );
+};
+
+export default AddProductionStandardPage;
diff --git a/src/app/master-data/production-standard/detail/edit/page.tsx b/src/app/master-data/production-standard/detail/edit/page.tsx
new file mode 100644
index 00000000..d048b411
--- /dev/null
+++ b/src/app/master-data/production-standard/detail/edit/page.tsx
@@ -0,0 +1,56 @@
+'use client';
+
+import ProductionStandardForm from '@/components/pages/master-data/production-standard/form/ProductionStandardForm';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import { ProductionStandardApi } from '@/services/api/master-data';
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
+
+const EditProductionStandardPage = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ // Get Query Params
+ const productionStandardId = searchParams.get('productionStandardId');
+
+ // Fetch Data
+ const { data: productionStandard, isLoading: isLoadingProductionStandard } =
+ useSWR(productionStandardId, (id: number) =>
+ ProductionStandardApi.getSingle(id)
+ );
+
+ if (!productionStandardId) {
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ if (
+ !isLoadingProductionStandard &&
+ (!productionStandard || isResponseError(productionStandard))
+ ) {
+ router.replace('/404');
+ return;
+ }
+
+ return (
+ <>
+ {isLoadingProductionStandard && (
+
+ )}
+ {!isLoadingProductionStandard &&
+ isResponseSuccess(productionStandard) && (
+
+ )}
+ >
+ );
+};
+
+export default EditProductionStandardPage;
diff --git a/src/app/master-data/production-standard/detail/layout.tsx b/src/app/master-data/production-standard/detail/layout.tsx
new file mode 100644
index 00000000..7220dfa1
--- /dev/null
+++ b/src/app/master-data/production-standard/detail/layout.tsx
@@ -0,0 +1,11 @@
+import SuspenseHelper from '@/components/helper/SuspenseHelper';
+
+const Layout = ({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) => {
+ return {children};
+};
+
+export default Layout;
diff --git a/src/app/master-data/production-standard/detail/page.tsx b/src/app/master-data/production-standard/detail/page.tsx
new file mode 100644
index 00000000..99806dcd
--- /dev/null
+++ b/src/app/master-data/production-standard/detail/page.tsx
@@ -0,0 +1,56 @@
+'use client';
+
+import ProductionStandardForm from '@/components/pages/master-data/production-standard/form/ProductionStandardForm';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import { ProductionStandardApi } from '@/services/api/master-data';
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
+
+const DetailProductionStandardPage = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ // Get Query Params
+ const productionStandardId = searchParams.get('productionStandardId');
+
+ // Fetch Data
+ const { data: productionStandard, isLoading: isLoadingProductionStandard } =
+ useSWR(productionStandardId, (id: number) =>
+ ProductionStandardApi.getSingle(id)
+ );
+
+ if (!productionStandardId) {
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ if (
+ !isLoadingProductionStandard &&
+ (!productionStandard || isResponseError(productionStandard))
+ ) {
+ router.replace('/404');
+ return;
+ }
+
+ return (
+ <>
+ {isLoadingProductionStandard && (
+
+ )}
+ {!isLoadingProductionStandard &&
+ isResponseSuccess(productionStandard) && (
+
+ )}
+ >
+ );
+};
+
+export default DetailProductionStandardPage;
diff --git a/src/app/master-data/production-standard/page.tsx b/src/app/master-data/production-standard/page.tsx
new file mode 100644
index 00000000..ed1107cd
--- /dev/null
+++ b/src/app/master-data/production-standard/page.tsx
@@ -0,0 +1,11 @@
+import ProductionStandardTable from '@/components/pages/master-data/production-standard/ProductionStandardTable';
+
+const ProductionStandardPage = () => {
+ return (
+
+ );
+};
+
+export default ProductionStandardPage;
diff --git a/src/app/production/project-flock/chickin/add/page.tsx b/src/app/production/project-flock/chickin/add/page.tsx
deleted file mode 100644
index 831979cb..00000000
--- a/src/app/production/project-flock/chickin/add/page.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-'use client';
-
-import { FormHeader } from '@/components/helper/form/FormHeader';
-import ProjectFlockChickinDetail from '@/components/pages/production/project-flock/chickin/ProjectFlockChickinDetail';
-import { useSearchParams } from 'next/navigation';
-
-const AddChickin = () => {
- const searchParams = useSearchParams();
- const projectFlockId = searchParams.get('projectFlockId');
-
- return (
- <>
-
- >
- );
-};
-
-export default AddChickin;
diff --git a/src/app/production/project-flock/chickin/page.tsx b/src/app/production/project-flock/chickin/page.tsx
deleted file mode 100644
index d40c39a3..00000000
--- a/src/app/production/project-flock/chickin/page.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import ChickinTable from '@/components/pages/production/chickin/ChickinTable';
-
-const Chickin = () => {
- return (
-
- );
-};
-export default Chickin;
diff --git a/src/app/production/uniformity/add/page.tsx b/src/app/production/uniformity/add/page.tsx
new file mode 100644
index 00000000..136aab5d
--- /dev/null
+++ b/src/app/production/uniformity/add/page.tsx
@@ -0,0 +1,7 @@
+import UniformityForm from '@/components/pages/production/uniformity/form/UniformityForm';
+
+const AddUniformity = () => {
+ return ;
+};
+
+export default AddUniformity;
diff --git a/src/app/production/uniformity/detail/page.tsx b/src/app/production/uniformity/detail/page.tsx
new file mode 100644
index 00000000..bf1458ef
--- /dev/null
+++ b/src/app/production/uniformity/detail/page.tsx
@@ -0,0 +1,49 @@
+'use client';
+
+import UniformityDetail from '@/components/pages/production/uniformity/detail/UniformityDetail';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import { UniformityApi } from '@/services/api/uniformity';
+import { useRouter, useSearchParams } from 'next/navigation';
+import useSWR from 'swr';
+
+const UniformityDetailPage = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ const uniformityId = searchParams.get('uniformityId');
+
+ const { data: uniformity, isLoading: isLoadingUniformity } = useSWR(
+ uniformityId,
+ (id: string) => UniformityApi.getUniformityDetail(parseInt(id))
+ );
+
+ if (!uniformityId) {
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ if (!isLoadingUniformity && (!uniformity || isResponseError(uniformity))) {
+ router.replace('/404');
+ return;
+ }
+
+ return (
+
+ {isLoadingUniformity && (
+
+
+
+ )}
+ {isResponseSuccess(uniformity) && (
+
+ )}
+
+ );
+};
+
+export default UniformityDetailPage;
diff --git a/src/app/production/uniformity/layout.tsx b/src/app/production/uniformity/layout.tsx
new file mode 100644
index 00000000..511aa0a1
--- /dev/null
+++ b/src/app/production/uniformity/layout.tsx
@@ -0,0 +1,10 @@
+import { ReactNode } from 'react';
+import UniformityPageWrapper from '@/components/pages/production/uniformity/UniformityPageWrapper';
+
+export default function UniformityLayout({
+ children,
+}: {
+ children: ReactNode;
+}) {
+ return {children};
+}
diff --git a/src/app/production/uniformity/page.tsx b/src/app/production/uniformity/page.tsx
new file mode 100644
index 00000000..841a7507
--- /dev/null
+++ b/src/app/production/uniformity/page.tsx
@@ -0,0 +1,7 @@
+import UniformityTable from '@/components/pages/production/uniformity/UniformityTable';
+
+const Uniformity = () => {
+ return ;
+};
+
+export default Uniformity;
diff --git a/src/app/report/expense/detail/layout.tsx b/src/app/report/expense/detail/layout.tsx
new file mode 100644
index 00000000..7220dfa1
--- /dev/null
+++ b/src/app/report/expense/detail/layout.tsx
@@ -0,0 +1,11 @@
+import SuspenseHelper from '@/components/helper/SuspenseHelper';
+
+const Layout = ({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) => {
+ return {children};
+};
+
+export default Layout;
diff --git a/src/app/report/expense/detail/page.tsx b/src/app/report/expense/detail/page.tsx
new file mode 100644
index 00000000..f7ae906e
--- /dev/null
+++ b/src/app/report/expense/detail/page.tsx
@@ -0,0 +1,5 @@
+const ReportExpenseDetail = () => {
+ return ReportExpenseDetail
;
+};
+
+export default ReportExpenseDetail;
diff --git a/src/app/report/expense/page.tsx b/src/app/report/expense/page.tsx
new file mode 100644
index 00000000..99d2862e
--- /dev/null
+++ b/src/app/report/expense/page.tsx
@@ -0,0 +1,13 @@
+'use client';
+
+import ReportExpenseTable from '@/components/pages/report/expense/ReportExpenseTable';
+
+const ReportExpense = () => {
+ return (
+
+
+
+ );
+};
+
+export default ReportExpense;
diff --git a/src/app/report/logistic-stock/layout.tsx b/src/app/report/logistic-stock/layout.tsx
new file mode 100644
index 00000000..7220dfa1
--- /dev/null
+++ b/src/app/report/logistic-stock/layout.tsx
@@ -0,0 +1,11 @@
+import SuspenseHelper from '@/components/helper/SuspenseHelper';
+
+const Layout = ({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) => {
+ return {children};
+};
+
+export default Layout;
diff --git a/src/app/report/logistic-stock/page.tsx b/src/app/report/logistic-stock/page.tsx
new file mode 100644
index 00000000..77ba31ed
--- /dev/null
+++ b/src/app/report/logistic-stock/page.tsx
@@ -0,0 +1,7 @@
+import LogisticStockTabs from '@/components/pages/report/logistic-stock/LogisticStockTabs';
+
+const LogisticStock = () => {
+ return ;
+};
+
+export default LogisticStock;
diff --git a/src/app/report/marketing/layout.tsx b/src/app/report/marketing/layout.tsx
new file mode 100644
index 00000000..7220dfa1
--- /dev/null
+++ b/src/app/report/marketing/layout.tsx
@@ -0,0 +1,11 @@
+import SuspenseHelper from '@/components/helper/SuspenseHelper';
+
+const Layout = ({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) => {
+ return {children};
+};
+
+export default Layout;
diff --git a/src/app/report/marketing/page.tsx b/src/app/report/marketing/page.tsx
new file mode 100644
index 00000000..52a3d4dd
--- /dev/null
+++ b/src/app/report/marketing/page.tsx
@@ -0,0 +1,11 @@
+import MarketingReportContent from '@/components/pages/report/MarketingReportContent';
+
+const MarketingReportPage = () => {
+ return (
+
+ );
+};
+
+export default MarketingReportPage;
diff --git a/src/app/report/production-result/page.tsx b/src/app/report/production-result/page.tsx
new file mode 100644
index 00000000..691ea734
--- /dev/null
+++ b/src/app/report/production-result/page.tsx
@@ -0,0 +1,11 @@
+import ProductionResultContent from '@/components/pages/report/production-result/ProductionResultContent';
+
+const ProductionResultReportPage = () => {
+ return (
+
+ );
+};
+
+export default ProductionResultReportPage;
diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx
index 5dc5022d..821aae42 100644
--- a/src/components/Badge.tsx
+++ b/src/components/Badge.tsx
@@ -3,29 +3,25 @@
import { HTMLAttributes, ReactNode } from 'react';
import { cn } from '@/lib/helper';
+import type { Color, Variant, Size } from '@/types/theme';
export interface BadgeProps
extends Omit, 'className'> {
children?: ReactNode;
className?: {
badge?: string;
+ status?: string;
};
- variant?: 'default' | 'outline' | 'ghost' | 'soft' | 'dash';
- color?:
- | 'neutral'
- | 'primary'
- | 'secondary'
- | 'accent'
- | 'info'
- | 'success'
- | 'warning'
- | 'error';
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
+ statusIndicator?: boolean;
+ variant?: Variant;
+ color?: Color;
+ size?: Size;
}
const Badge = ({
children,
className,
+ statusIndicator = false,
variant = 'default',
color,
size = 'md',
@@ -34,7 +30,7 @@ const Badge = ({
const getBadgeClasses = () => {
const baseClasses = 'badge';
- const variantClasses = {
+ const variantClasses: Record = {
default: '',
outline: 'badge-outline',
ghost: 'badge-ghost',
@@ -42,7 +38,7 @@ const Badge = ({
dash: 'badge-dash',
};
- const colorClasses = {
+ const colorClasses: Record = {
neutral: 'badge-neutral',
primary: 'badge-primary',
secondary: 'badge-secondary',
@@ -51,9 +47,10 @@ const Badge = ({
success: 'badge-success',
warning: 'badge-warning',
error: 'badge-error',
+ none: '',
};
- const sizeClasses = {
+ const sizeClasses: Record = {
xs: 'badge-xs',
sm: 'badge-sm',
md: 'badge-md',
@@ -70,8 +67,31 @@ const Badge = ({
);
};
+ const getStatusClasses = () => {
+ if (!statusIndicator) return '';
+
+ const statusIndicatorClasses: Record = {
+ neutral: 'bg-neutral',
+ primary: 'bg-primary',
+ secondary: 'bg-secondary',
+ accent: 'bg-accent',
+ info: 'bg-info',
+ success: 'bg-success',
+ warning: 'bg-warning',
+ error: 'bg-error',
+ none: '',
+ };
+
+ return cn(
+ 'w-2.5 h-2.5 rounded-full',
+ color && statusIndicatorClasses[color],
+ className?.status
+ );
+ };
+
return (
+ {statusIndicator && }
{children}
);
diff --git a/src/components/Drawer.tsx b/src/components/Drawer.tsx
index 17b8a56f..7b5e2374 100644
--- a/src/components/Drawer.tsx
+++ b/src/components/Drawer.tsx
@@ -15,6 +15,8 @@ interface DrawerProps {
className?: DrawerClassName;
onBackdropClick?: () => void;
closeOnBackdropClick?: boolean;
+ expandedContent?: ReactNode;
+ expandedWidth?: string;
}
type DrawerClassName = {
@@ -36,6 +38,8 @@ const Drawer = ({
className,
onBackdropClick,
closeOnBackdropClick = true,
+ expandedContent,
+ expandedWidth = 'w-[400px]',
}: DrawerProps) => {
const getDrawerClassNames = (): DrawerClassName => {
const baseClassNames = {
@@ -46,12 +50,21 @@ const Drawer = ({
drawerSidebarContent: 'min-h-full bg-base-100',
};
+ const getSidebarWidth = () => {
+ if (variant === 'sidebar') {
+ return expandedContent
+ ? 'w-full lg:min-w-[600px] lg:max-w-[600px]'
+ : 'w-full max-w-[300px] lg:w-[300px]';
+ }
+ return 'w-full sm:min-w-120 sm:w-fit';
+ };
+
if (variant === 'sidebar') {
return {
...baseClassNames,
drawerSidebarContent: cn(
baseClassNames.drawerSidebarContent,
- 'w-full max-w-[300px] lg:w-[300px]'
+ getSidebarWidth()
),
};
} else if (variant === 'right') {
@@ -60,11 +73,11 @@ const Drawer = ({
drawer: cn(baseClassNames.drawer, 'drawer-end'),
drawerSide: cn(
baseClassNames.drawerSide,
- 'border-l border-solid border-gray-200 drawer-side w-screen top-0 right-0 fixed z-21'
+ 'border-l border-solid border-gray-200 sm:drawer-side w-screen top-0 right-0 fixed z-21'
),
drawerSidebarContent: cn(
baseClassNames.drawerSidebarContent,
- 'w-full min-w-120 sm:w-fit'
+ getSidebarWidth()
),
};
} else if (variant === 'left') {
@@ -76,7 +89,7 @@ const Drawer = ({
),
drawerSidebarContent: cn(
baseClassNames.drawerSidebarContent,
- 'w-full min-w-120 sm:w-fit'
+ getSidebarWidth()
),
};
}
@@ -138,14 +151,37 @@ const Drawer = ({
onClick={closeDrawer}
/>
- {/* Sidebar Content */}
+ {/* Sidebar Content - Full height container */}
- {sidebarContent}
+ {/* Primary Sidebar Content */}
+
+ {sidebarContent}
+
+
+ {/* Expanded Drawer (Right side, side-by-side) */}
+ {expandedContent && (
+
+
+ {expandedContent}
+
+
+ )}
diff --git a/src/components/Dropdown.tsx b/src/components/Dropdown.tsx
new file mode 100644
index 00000000..5bfa7a7d
--- /dev/null
+++ b/src/components/Dropdown.tsx
@@ -0,0 +1,114 @@
+import React, { ReactNode, useState, useRef } from 'react';
+
+import { cn } from '@/lib/helper';
+
+export interface DropdownProps {
+ trigger: ReactNode;
+ children: ReactNode;
+ className?: {
+ wrapper?: string;
+ trigger?: string;
+ content?: string;
+ };
+ align?: 'start' | 'center' | 'end';
+ direction?: 'top' | 'bottom' | 'left' | 'right';
+ hover?: boolean;
+ defaultOpen?: boolean;
+ open?: boolean;
+ close?: boolean;
+ controlled?: boolean;
+}
+
+const Dropdown = ({
+ trigger,
+ children,
+ className,
+ align,
+ direction,
+ hover,
+ defaultOpen = false,
+ open,
+ close,
+ controlled = false,
+}: DropdownProps) => {
+ const [isOpen, setIsOpen] = useState(defaultOpen);
+ const dropdownRef = useRef(null);
+
+ const toggleDropdown = () => {
+ if (!controlled) {
+ const newState = !isOpen;
+ setIsOpen(newState);
+ }
+ };
+
+ const getWrapperClasses = () => {
+ const openState = controlled ? open : isOpen;
+
+ return cn(
+ 'dropdown',
+ {
+ 'dropdown-start': align === 'start',
+ 'dropdown-center': align === 'center',
+ 'dropdown-end': align === 'end',
+ 'dropdown-top': direction === 'top',
+ 'dropdown-bottom': direction === 'bottom',
+ 'dropdown-left': direction === 'left',
+ 'dropdown-right': direction === 'right',
+ 'dropdown-hover': hover,
+ 'dropdown-open': openState && !close,
+ 'dropdown-close': close,
+ },
+ className?.wrapper
+ );
+ };
+
+ const getTriggerClasses = () => {
+ return cn(className?.trigger);
+ };
+
+ const getContentClasses = () => {
+ return cn(
+ 'dropdown-content z-[9999] shadow-sm bg-base-100 rounded-box',
+ className?.content
+ );
+ };
+
+ if (controlled) {
+ return (
+
+ {trigger}
+ {open && !close && (
+
+ {children}
+
+ )}
+
+ );
+ }
+
+ return (
+
+
{
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ toggleDropdown();
+ }
+ }}
+ >
+ {trigger}
+
+ {!close && (
+
+ {children}
+
+ )}
+
+ );
+};
+
+export default Dropdown;
diff --git a/src/components/FloatingActionsButton.tsx b/src/components/FloatingActionsButton.tsx
index c9ca3454..974ca280 100644
--- a/src/components/FloatingActionsButton.tsx
+++ b/src/components/FloatingActionsButton.tsx
@@ -5,6 +5,8 @@ import Tooltip from '@/components/Tooltip';
import { cn } from '@/lib/helper';
import { Icon } from '@iconify/react';
+import { useAuth } from '@/services/hooks/useAuth';
+
type FloatingActionsButtonProps = {
actions: {
action: 'DETAIL' | 'EDIT' | 'DELETE';
@@ -13,6 +15,7 @@ type FloatingActionsButtonProps = {
onClick?: () => void;
hidden?: boolean;
disabled?: boolean;
+ permissions?: string | string[];
}[];
approvals: {
action: 'APPROVED' | 'REJECTED';
@@ -20,6 +23,7 @@ type FloatingActionsButtonProps = {
label?: string;
onClick?: () => void;
disabled?: boolean;
+ permissions?: string | string[];
}[];
selectedRowIds: number[];
onClose: () => void;
@@ -31,9 +35,12 @@ const FloatingActionsButton = ({
selectedRowIds,
onClose,
}: FloatingActionsButtonProps) => {
+ const { permissionCheck } = useAuth();
// Jika tidak ada baris yang dipilih, jangan tampilkan FAB
const positionStyles =
- selectedRowIds.length > 0 ? 'bottom-[10%]' : 'bottom-[-100%]';
+ selectedRowIds.length > 0
+ ? 'bottom-[10%] opacity-100'
+ : 'bottom-[-10%] opacity-0';
// Helper untuk menentukan gaya warna tombol approval
const getApprovalColor = (action: 'APPROVED' | 'REJECTED') => {
@@ -69,7 +76,18 @@ const FloatingActionsButton = ({
{/* Render Aksi dari props.actions */}
{actions
- .filter((action) => !action.hidden)
+ .filter((action) => {
+ if (action.hidden) return false;
+ if (action.permissions) {
+ if (typeof action.permissions === 'string') {
+ return permissionCheck(action.permissions);
+ }
+ return action.permissions.some((permission) =>
+ permissionCheck(permission)
+ );
+ }
+ return true;
+ })
.map((action, index) => {
return (
diff --git a/src/components/MainDrawer.tsx b/src/components/MainDrawer.tsx
index 3a09c0b1..fc8cbb18 100644
--- a/src/components/MainDrawer.tsx
+++ b/src/components/MainDrawer.tsx
@@ -9,10 +9,13 @@ import Drawer from '@/components/Drawer';
import Navbar from '@/components/Navbar';
import Button from '@/components/Button';
import SidebarMenu from '@/components/molecules/SidebarMenu';
+import PermissionNotFound from '@/components/helper/PermissionNotFound';
import { useUiStore } from '@/stores/ui/ui.store';
import { MAIN_DRAWER_LINKS } from '@/config/constant';
import { isPathActive } from '@/lib/helper';
+import { ROUTE_PERMISSIONS } from '@/config/route-permission';
+import { useAuth } from '@/services/hooks/useAuth';
const MainDrawerContent = () => {
const pathname = usePathname();
@@ -62,6 +65,11 @@ const MainDrawer = ({
}>) => {
const { mainDrawerOpen, setMainDrawerOpen } = useUiStore();
const pathname = usePathname();
+ const { permissionCheck } = useAuth();
+
+ const isPermitted = ROUTE_PERMISSIONS[pathname]?.some((permission) =>
+ permissionCheck(permission)
+ );
const getPageTitle = useCallback(() => {
let title = '';
@@ -101,6 +109,10 @@ const MainDrawer = ({
setMainDrawerOpen(!mainDrawerOpen);
};
+ if (!isPermitted) {
+ return ;
+ }
+
return (
{
@@ -62,9 +63,11 @@ const Navbar = ({ title, toggleSidebar }: NavbarProps) => {
}
- contentClassName='w-52 mt-3'
+ className={{
+ content: 'w-52 mt-3',
+ }}
>
-