diff --git a/.env.development b/.env.development
index bbfde99..dcadef9 100644
--- a/.env.development
+++ b/.env.development
@@ -1,2 +1,2 @@
-# NEXT_PUBLIC_API_URL="http://localhost:8080"
-NEXT_PUBLIC_API_URL="https://erp.julongindonesia.com:8443/api"
\ No newline at end of file
+NEXT_PUBLIC_API_URL="http://localhost:8080"
+# NEXT_PUBLIC_API_URL="https://erp.julongindonesia.com:8443/api"
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index fda24c1..f0f48f6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,13 +13,16 @@
"@heroicons/react": "^2.1.5",
"@mui/material": "^6.1.0",
"@mui/x-charts": "^7.17.0",
+ "@mui/x-data-grid": "^8.13.1",
"@mui/x-date-pickers": "^7.17.0",
"@reduxjs/toolkit": "^2.2.7",
"date-fns": "^4.0.0",
+ "i18next": "^25.6.0",
"lucide-react": "^0.441.0",
"next": "14.2.11",
"react": "^18",
"react-dom": "^18",
+ "react-i18next": "^16.0.1",
"react-redux": "^9.1.2",
"react-tooltip": "^5.28.0",
"recharts": "^3.2.1",
@@ -137,13 +140,10 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.25.6",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz",
- "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==",
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
"license": "MIT",
- "dependencies": {
- "regenerator-runtime": "^0.14.0"
- },
"engines": {
"node": ">=6.9.0"
}
@@ -719,10 +719,13 @@
}
},
"node_modules/@mui/types": {
- "version": "7.2.16",
- "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.16.tgz",
- "integrity": "sha512-qI8TV3M7ShITEEc8Ih15A2vLzZGLhD+/UPNwck/hcls2gwg7dyRjNGXcQYHKLB5Q7PuTRfrTkAoPa2VV1s67Ag==",
+ "version": "7.4.7",
+ "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.7.tgz",
+ "integrity": "sha512-8vVje9rdEr1rY8oIkYgP+Su5Kwl6ik7O3jQ0wl78JGSmiZhRHV+vkjooGdKD8pbtZbutXFVTWQYshu2b3sG9zw==",
"license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.4"
+ },
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
@@ -820,6 +823,102 @@
"robust-predicates": "^3.0.2"
}
},
+ "node_modules/@mui/x-data-grid": {
+ "version": "8.14.0",
+ "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.14.0.tgz",
+ "integrity": "sha512-bzUpD83Wx4mawkgquDQUUbLLnpF+JP7Pe7YQx1ixS6W/AlUwXAVagPTOijwchHvlx0Ky11dJvOQAfrnWu6an/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.4",
+ "@mui/utils": "^7.3.3",
+ "@mui/x-internals": "8.14.0",
+ "@mui/x-virtualizer": "0.2.3",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.9.0",
+ "@emotion/styled": "^11.8.1",
+ "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
+ "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/x-data-grid/node_modules/@mui/utils": {
+ "version": "7.3.3",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.3.tgz",
+ "integrity": "sha512-kwNAUh7bLZ7mRz9JZ+6qfRnnxbE4Zuc+RzXnhSpRSxjTlSTj7b4JxRLXpG+MVtPVtqks5k/XC8No1Vs3x4Z2gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.4",
+ "@mui/types": "^7.4.7",
+ "@types/prop-types": "^15.7.15",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1",
+ "react-is": "^19.1.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/x-data-grid/node_modules/@mui/x-internals": {
+ "version": "8.14.0",
+ "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.14.0.tgz",
+ "integrity": "sha512-esYyl61nuuFXiN631TWuPh2tqdoyTdBI/4UXgwH3rytF8jiWvy6prPBPRHEH1nvW3fgw9FoBI48FlOO+yEI8xg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.4",
+ "@mui/utils": "^7.3.3",
+ "reselect": "^5.1.1",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@mui/x-data-grid/node_modules/react-is": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz",
+ "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==",
+ "license": "MIT"
+ },
"node_modules/@mui/x-date-pickers": {
"version": "7.17.0",
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.17.0.tgz",
@@ -906,12 +1005,212 @@
"react": "^17.0.0 || ^18.0.0"
}
},
+ "node_modules/@mui/x-virtualizer": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@mui/x-virtualizer/-/x-virtualizer-0.2.3.tgz",
+ "integrity": "sha512-CZ+VxFmeJaTduAOlSyo5cVek0PV5Y8gm4coyaHEpCvms207J9AoMUKqWIcdwsVGlTH1Y71j35xT/MwHKutZiNw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.4",
+ "@mui/utils": "^7.3.3",
+ "@mui/x-internals": "8.14.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@mui/x-virtualizer/node_modules/@mui/utils": {
+ "version": "7.3.3",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.3.tgz",
+ "integrity": "sha512-kwNAUh7bLZ7mRz9JZ+6qfRnnxbE4Zuc+RzXnhSpRSxjTlSTj7b4JxRLXpG+MVtPVtqks5k/XC8No1Vs3x4Z2gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.4",
+ "@mui/types": "^7.4.7",
+ "@types/prop-types": "^15.7.15",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1",
+ "react-is": "^19.1.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/x-virtualizer/node_modules/@mui/x-internals": {
+ "version": "8.14.0",
+ "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.14.0.tgz",
+ "integrity": "sha512-esYyl61nuuFXiN631TWuPh2tqdoyTdBI/4UXgwH3rytF8jiWvy6prPBPRHEH1nvW3fgw9FoBI48FlOO+yEI8xg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.4",
+ "@mui/utils": "^7.3.3",
+ "reselect": "^5.1.1",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@mui/x-virtualizer/node_modules/react-is": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz",
+ "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==",
+ "license": "MIT"
+ },
"node_modules/@next/env": {
"version": "14.2.11",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.11.tgz",
"integrity": "sha512-HYsQRSIXwiNqvzzYThrBwq6RhXo3E0n8j8nQnAs8i4fCEo2Zf/3eS0IiRA8XnRg9Ha0YnpkyJZIZg1qEwemrHw==",
"license": "MIT"
},
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.11.tgz",
+ "integrity": "sha512-eiY9u7wEJZWp/Pga07Qy3ZmNEfALmmSS1HtsJF3y1QEyaExu7boENz11fWqDmZ3uvcyAxCMhTrA1jfVxITQW8g==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.11.tgz",
+ "integrity": "sha512-lnB0zYCld4yE0IX3ANrVMmtAbziBb7MYekcmR6iE9bujmgERl6+FK+b0MBq0pl304lYe7zO4yxJus9H/Af8jbg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.11.tgz",
+ "integrity": "sha512-Ulo9TZVocYmUAtzvZ7FfldtwUoQY0+9z3BiXZCLSUwU2bp7GqHA7/bqrfsArDlUb2xeGwn3ZuBbKtNK8TR0A8w==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.11.tgz",
+ "integrity": "sha512-fH377DnKGyUnkWlmUpFF1T90m0dADBfK11dF8sOQkiELF9M+YwDRCGe8ZyDzvQcUd20Rr5U7vpZRrAxKwd3Rzg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.11.tgz",
+ "integrity": "sha512-a0TH4ZZp4NS0LgXP/488kgvWelNpwfgGTUCDXVhPGH6pInb7yIYNgM4kmNWOxBFt+TIuOH6Pi9NnGG4XWFUyXQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.11.tgz",
+ "integrity": "sha512-DYYZcO4Uir2gZxA4D2JcOAKVs8ZxbOFYPpXSVIgeoQbREbeEHxysVsg3nY4FrQy51e5opxt5mOHl/LzIyZBoKA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.11.tgz",
+ "integrity": "sha512-PwqHeKG3/kKfPpM6of1B9UJ+Er6ySUy59PeFu0Un0LBzJTRKKAg2V6J60Yqzp99m55mLa+YTbU6xj61ImTv9mg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-ia32-msvc": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.11.tgz",
+ "integrity": "sha512-0U7PWMnOYIvM74GY6rbH6w7v+vNPDVH1gUhlwHpfInJnNe5LkmUZqhp7FNWeNa5wbVgRcRi1F1cyxp4dmeLLvA==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/@next/swc-win32-x64-msvc": {
"version": "14.2.11",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.11.tgz",
@@ -1185,9 +1484,9 @@
"license": "MIT"
},
"node_modules/@types/prop-types": {
- "version": "15.7.13",
- "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
- "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
"license": "MIT"
},
"node_modules/@types/react": {
@@ -1985,6 +2284,46 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
+ "node_modules/html-parse-stringify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "license": "MIT",
+ "dependencies": {
+ "void-elements": "3.1.0"
+ }
+ },
+ "node_modules/i18next": {
+ "version": "25.6.0",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.6.0.tgz",
+ "integrity": "sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://locize.com"
+ },
+ {
+ "type": "individual",
+ "url": "https://locize.com/i18next.html"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.6"
+ },
+ "peerDependencies": {
+ "typescript": "^5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/immer": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
@@ -2735,6 +3074,32 @@
"react": "^18.3.1"
}
},
+ "node_modules/react-i18next": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.0.1.tgz",
+ "integrity": "sha512-0S//bpYEkCPjzuVmxDf9Z6+Y+ArNvpAUk7eDL4qNCZXjDh6Z9j6MZ+NThU7kMCOsmYmDCun3GYEwkiOjjZo9Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.6",
+ "html-parse-stringify": "^3.0.1"
+ },
+ "peerDependencies": {
+ "i18next": ">= 25.5.2",
+ "react": ">= 16.8.0",
+ "typescript": "^5"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
@@ -2868,12 +3233,6 @@
"redux": "^5.0.0"
}
},
- "node_modules/regenerator-runtime": {
- "version": "0.14.1",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
- "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
- "license": "MIT"
- },
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
@@ -3328,12 +3687,12 @@
"license": "MIT"
},
"node_modules/use-sync-external-store": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
- "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/util-deprecate": {
@@ -3365,6 +3724,15 @@
"d3-timer": "^3.0.1"
}
},
+ "node_modules/void-elements": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -3487,126 +3855,6 @@
"engines": {
"node": ">= 14"
}
- },
- "node_modules/@next/swc-darwin-arm64": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.11.tgz",
- "integrity": "sha512-eiY9u7wEJZWp/Pga07Qy3ZmNEfALmmSS1HtsJF3y1QEyaExu7boENz11fWqDmZ3uvcyAxCMhTrA1jfVxITQW8g==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-darwin-x64": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.11.tgz",
- "integrity": "sha512-lnB0zYCld4yE0IX3ANrVMmtAbziBb7MYekcmR6iE9bujmgERl6+FK+b0MBq0pl304lYe7zO4yxJus9H/Af8jbg==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-gnu": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.11.tgz",
- "integrity": "sha512-Ulo9TZVocYmUAtzvZ7FfldtwUoQY0+9z3BiXZCLSUwU2bp7GqHA7/bqrfsArDlUb2xeGwn3ZuBbKtNK8TR0A8w==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-musl": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.11.tgz",
- "integrity": "sha512-fH377DnKGyUnkWlmUpFF1T90m0dADBfK11dF8sOQkiELF9M+YwDRCGe8ZyDzvQcUd20Rr5U7vpZRrAxKwd3Rzg==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-gnu": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.11.tgz",
- "integrity": "sha512-a0TH4ZZp4NS0LgXP/488kgvWelNpwfgGTUCDXVhPGH6pInb7yIYNgM4kmNWOxBFt+TIuOH6Pi9NnGG4XWFUyXQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-musl": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.11.tgz",
- "integrity": "sha512-DYYZcO4Uir2gZxA4D2JcOAKVs8ZxbOFYPpXSVIgeoQbREbeEHxysVsg3nY4FrQy51e5opxt5mOHl/LzIyZBoKA==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-arm64-msvc": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.11.tgz",
- "integrity": "sha512-PwqHeKG3/kKfPpM6of1B9UJ+Er6ySUy59PeFu0Un0LBzJTRKKAg2V6J60Yqzp99m55mLa+YTbU6xj61ImTv9mg==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-ia32-msvc": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.11.tgz",
- "integrity": "sha512-0U7PWMnOYIvM74GY6rbH6w7v+vNPDVH1gUhlwHpfInJnNe5LkmUZqhp7FNWeNa5wbVgRcRi1F1cyxp4dmeLLvA==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
}
}
}
diff --git a/package.json b/package.json
index 399dddb..8d00ade 100644
--- a/package.json
+++ b/package.json
@@ -18,10 +18,12 @@
"@mui/x-date-pickers": "^7.17.0",
"@reduxjs/toolkit": "^2.2.7",
"date-fns": "^4.0.0",
+ "i18next": "^25.6.0",
"lucide-react": "^0.441.0",
"next": "14.2.11",
"react": "^18",
"react-dom": "^18",
+ "react-i18next": "^16.0.1",
"react-redux": "^9.1.2",
"react-tooltip": "^5.28.0",
"recharts": "^3.2.1",
diff --git a/src/app/(dashboard)/absensi/page.tsx b/src/app/(dashboard)/absensi/page.tsx
index ac8ca13..0a35986 100644
--- a/src/app/(dashboard)/absensi/page.tsx
+++ b/src/app/(dashboard)/absensi/page.tsx
@@ -6,8 +6,10 @@ import { ComposedChart, Bar, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend,
import { formatDate } from "date-fns";
import { PageLoader } from "@/components/Loader";
import {CustomLegendHorizontal, CustomLegendVertical} from "@/components/CustomLegendPie";
+import { useLocale } from "@/lib/hooks/useLocale";
export default function AbsensiPage() {
+ const { t } = useLocale();
const filter = useAppSelector(state => state.filter.filter);
const {data: attendanceSummary, isLoading: isLoadingAttendanceSummary, isFetching: isFetchingAttendanceSummary} = useGetOrganizationAttendanceQuery(filter);
const {data: attendanceRangeStaff, isLoading: isLoadingAttendanceRangeStaff, isFetching: isFetchingAttendanceRangeStaff} = useGetAttendanceRangeQuery({
@@ -38,7 +40,7 @@ export default function AbsensiPage() {
// Show loader while any data is loading
if (isLoading) {
- return ;
+ return ;
}
@@ -46,7 +48,7 @@ export default function AbsensiPage() {
return (
-
Kehadiran Staff
+
{t('attendance.kehadiranStaff')}
{attendanceRangeStaff ? (
@@ -68,7 +70,7 @@ export default function AbsensiPage() {
})}
[`${value} employees`, name]}
+ formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('common.employees')}`, name]}
/>
) : (
- Data belum tersedia
+ {t('common.dataNotAvailable')}
)}
-
Kehadiran Non Staff
+
{t('attendance.kehadiranNonStaff')}
{attendanceRangeNonStaff ? (
@@ -108,10 +110,10 @@ export default function AbsensiPage() {
})}
[`${value} employees`, name]}
+ formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('common.employees')}`, name]}
/>
) : (
- Data belum tersedia
+ {t('common.dataNotAvailable')}
)}
-
Kehadiran Pemanen
+
{t('attendance.kehadiranPemanen')}
{attendanceRangeHarvester ? (
@@ -149,7 +151,7 @@ export default function AbsensiPage() {
})}
[`${value} employees`, name]}
+ formatter={(value, name) => [`${value} ${t('common.employees')}`, name]}
/>
) : (
- Data belum tersedia
+ {t('common.dataNotAvailable')}
)}
-
Kehadiran Perawatan
+
{t('attendance.kehadiranPerawatan')}
{attendanceRangeMaintenance ? (
@@ -191,10 +193,10 @@ export default function AbsensiPage() {
})}
[`${value} employees`, name]}
+ formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('common.employees')}`, name]}
/>
) : (
- Data belum tersedia
+ {t('common.dataNotAvailable')}
)}
-
Data Kehadiran Karyawan Setiap Perusahaan
+
{t('attendance.dataKehadiranKaryawan')}
{attendanceSummary ? (
@@ -237,10 +239,10 @@ export default function AbsensiPage() {
{
const orgData = attendanceSummary.find(e => e.organization_code === props.payload.organization_code);
- if (name === 'Hadir') {
- return [`${orgData?.count || 0} orang`, 'Hadir'];
+ if (name === t('attendance.hadir')) {
+ return [`${Number(orgData?.count || 0).toLocaleString('id-ID')} ${t('common.hariKerja')}`, t('attendance.hadir')];
} else {
- return [`${orgData?.absent || 0} orang`, 'Tidak Hadir'];
+ return [`${Number(orgData?.absent || 0).toLocaleString('id-ID')} ${t('common.hariKerja')}`, t('attendance.tidakHadir')];
}
}}
labelFormatter={(label) => {
@@ -254,25 +256,25 @@ export default function AbsensiPage() {
dataKey="countPercent"
stackId="attendance"
fill="#2385DE"
- name="Hadir"
+ name={t('attendance.hadir')}
/>
) : (
- Data belum tersedia
+ {t('common.dataNotAvailable')}
)}
-
Data Absensi Karyawan Perbulan
+
{t('attendance.dataAbsensiPerbulan')}
{montlyAttendance && (
@@ -292,13 +294,13 @@ export default function AbsensiPage() {
{
@@ -308,16 +310,16 @@ export default function AbsensiPage() {
return value;
}}
formatter={(value, name) => {
- if (name === 'Persentase (%)') {
+ if (name === t('attendance.persentase')) {
return [`${value}%`, name];
}
return [value, name];
}}
/>
-
-
-
+
+
+
)}
diff --git a/src/app/(dashboard)/hrcost/page.tsx b/src/app/(dashboard)/hrcost/page.tsx
index 219e142..a5400e7 100644
--- a/src/app/(dashboard)/hrcost/page.tsx
+++ b/src/app/(dashboard)/hrcost/page.tsx
@@ -2,6 +2,7 @@
import React from "react";
import { useAppSelector } from "@/lib/hooks";
+import { useLocale } from "@/lib/hooks/useLocale";
import { useGetHrCostByJobQuery, useGetHrCostQuery, useGetHrCostByOrganizationQuery, useGetHrCostPerMonthQuery, useGetHrCostPerEmployeeQuery } from "@/services/api";
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend, BarChart, Bar, XAxis, YAxis, CartesianGrid, LineChart, Line } from "recharts";
import { PageLoader } from "@/components/Loader";
@@ -12,6 +13,7 @@ import { ArrowsPointingOutIcon, ArrowsPointingInIcon, MagnifyingGlassIcon } from
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8', '#82CA9D'];
export default function HrcostPage() {
+ const { t } = useLocale();
const filter = useAppSelector(state => state.filter.filter);
const { data: hrCostByJob, isLoading: isLoadingHrCostByJob, isFetching: isFetchingHrCostByJob } = useGetHrCostByJobQuery(filter);
const { data: hrCost, isLoading: isLoadingHrCost, isFetching: isFetchingHrCost } = useGetHrCostQuery(filter);
@@ -26,7 +28,7 @@ export default function HrcostPage() {
const isLoading = isLoadingHrCostByJob || isFetchingHrCostByJob || isLoadingHrCost || isFetchingHrCost || isLoadingHrCostByOrganization || isFetchingHrCostByOrganization || isLoadingHrCostPerMonth || isFetchingHrCostPerMonth || isLoadingHrCostPerEmployee || isFetchingHrCostPerEmployee;
if (isLoading) {
- return
;
+ return
;
}
if (isFullscreen) {
@@ -40,13 +42,13 @@ export default function HrcostPage() {
-
Rincian Gaji Pemanen
+
{t('hrcost.rincianGajiPemanen')}
-
Total: {hrCostPerEmployee?.length?.toLocaleString('id-ID') || 0} karyawan
+
{t('common.total')}: {hrCostPerEmployee?.length?.toLocaleString('id-ID') || 0} {t('common.karyawan')}
@@ -57,15 +59,15 @@ export default function HrcostPage() {
{/* Information Cards */}
-
Grand Total Gaji
+
{t('hrcost.grandTotalGaji')}
Rp. {grandTotal.toLocaleString('id-ID')}
-
Rata-Rata Gaji
+
{t('hrcost.rataRataGaji')}
Rp. {Math.round(avgSalary).toLocaleString('id-ID')}
-
Gaji Tertinggi
+
{t('hrcost.gajiTertinggi')}
Rp. {maxSalary.toLocaleString('id-ID')}
@@ -92,19 +94,19 @@ export default function HrcostPage() {
// Format data for HR Cost pie chart
const hrCostPieData = hrCost ? [
{
- name: "Gaji",
+ name: t('hrcost.gaji'),
value: hrCost.total_salary,
valueInMillions: hrCost.total_salary / 1000000000,
color: COLORS[0]
},
{
- name: "BPJS TK",
+ name: t('hrcost.bpjsTk'),
value: hrCost.total_bpjs_tk,
valueInMillions: hrCost.total_bpjs_tk / 1000000000,
color: COLORS[1]
},
{
- name: "BPJS KS",
+ name: t('hrcost.bpjsKs'),
value: hrCost.total_bpjs_ks,
valueInMillions: hrCost.total_bpjs_ks / 1000000000,
color: COLORS[2]
@@ -165,7 +167,7 @@ export default function HrcostPage() {
-
HR Cost
+
{t('hrcost.hrCost')}
-
Gaji Karyawan
+
{t('hrcost.gajiKaryawan')}
@@ -230,7 +232,7 @@ export default function HrcostPage() {
-
Ranking Top 10 Total Gaji Perusahaan
+
{t('hrcost.rankingTop10')}
[
`${(value / 1000000000).toFixed(2)} M IDR`,
- 'Total Salary'
+ t('hrcost.totalSalary')
]}
labelFormatter={(label: string) => {
const org = organizationBarData.find(item => item.organization_code === label);
@@ -274,13 +276,13 @@ export default function HrcostPage() {
-
Rincian Gaji Pemanen
+
{t('hrcost.rincianGajiPemanen')}
-
Total: {hrCostPerEmployee?.length?.toLocaleString('id-ID') || 0} karyawan
+
{t('common.total')}: {hrCostPerEmployee?.length?.toLocaleString('id-ID') || 0} {t('common.karyawan')}
@@ -292,14 +294,14 @@ export default function HrcostPage() {
{/* Information Cards */}
-
Grand Total Gaji
+
{t('hrcost.grandTotalGaji')}
Rp. {(() => {
const salaries = hrCostPerEmployee?.map(emp => emp.salary) || [];
return salaries.reduce((sum, salary) => sum + salary, 0).toLocaleString('id-ID');
})()}
-
Rata-Rata Gaji
+
{t('hrcost.rataRataGaji')}
Rp. {(() => {
const salaries = hrCostPerEmployee?.map(emp => emp.salary) || [];
const avg = salaries.length > 0 ? salaries.reduce((sum, salary) => sum + salary, 0) / salaries.length : 0;
@@ -307,7 +309,7 @@ export default function HrcostPage() {
})()}
-
Gaji Tertinggi
+
{t('hrcost.gajiTertinggi')}
Rp. {(() => {
const salaries = hrCostPerEmployee?.map(emp => emp.salary) || [];
const max = salaries.length > 0 ? Math.max(...salaries) : 0;
@@ -325,7 +327,7 @@ export default function HrcostPage() {
-
Total Gaji Setiap Bulan
+
{t('hrcost.totalGajiSetiapBulan')}
[
`${(value / 1000000000).toFixed(2)} M IDR`,
- 'Total Salary'
+ t('hrcost.totalSalary')
]}
labelFormatter={(label: string) => {
const month = monthlyLineData.find(item => item.month_name === label);
@@ -377,13 +379,14 @@ export default function HrcostPage() {
}
function EmployeeDataGrid({ data, isFullscreen = false }: { data: { id_karyawan: string; name: string; address: string; salary: number; }[], isFullscreen?: boolean }) {
+ const { t } = useLocale();
const columns: GridColDef[] = [
- { field: 'id_karyawan', headerName: 'ID Karyawan', flex: 1, minWidth: 150 },
- { field: 'name', headerName: 'Nama', flex: 1, minWidth: 180 },
- { field: 'address', headerName: 'Alamat', flex: 1, minWidth: 220 },
+ { field: 'id_karyawan', headerName: t('hrcost.idKaryawan'), flex: 1, minWidth: 150 },
+ { field: 'name', headerName: t('hrcost.nama'), flex: 1, minWidth: 180 },
+ { field: 'address', headerName: t('hrcost.alamat'), flex: 1, minWidth: 220 },
{
field: 'salary',
- headerName: 'Gaji (IDR)',
+ headerName: t('hrcost.gajiIdr'),
flex: 1,
minWidth: 160,
type: 'number',
diff --git a/src/app/(dashboard)/karyawan/page.tsx b/src/app/(dashboard)/karyawan/page.tsx
index 0f16cf6..dc71654 100644
--- a/src/app/(dashboard)/karyawan/page.tsx
+++ b/src/app/(dashboard)/karyawan/page.tsx
@@ -6,9 +6,10 @@ import { BarChart, Bar, LineChart, Line, PieChart, Pie, Cell, XAxis, YAxis, Cart
import { formatDate } from "date-fns";
import { useEffect, useState } from "react";
import { PageLoader } from "@/components/Loader";
+import { useLocale } from "@/lib/hooks/useLocale";
export default function KaryawanPage() {
-
+ const { t } = useLocale();
const filter = useAppSelector(state => state.filter.filter);
const {data: employeeSummary, isFetching : loadingSummary} = useGetEmployeeSummaryQuery(filter);
const {data: montlyEmployee, isFetching : loadingMonthly} = useGetMonthlyEmployeeQuery(filter);
@@ -95,13 +96,13 @@ export default function KaryawanPage() {
// Show loader while any data is loading
if (isLoading) {
- return ;
+ return ;
}
return (
-
Data Karyawan
+
{t('employee.dataKaryawan')}
{employeeSummary && employeeSummary.length > 0 ? (
@@ -113,7 +114,7 @@ export default function KaryawanPage() {
/>
[`${value} karyawan`, "Jumlah Karyawan"]}
+ formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('employee.karyawan')}`, t('employee.jumlahKaryawan')]}
labelFormatter={(label) => {
const org = employeeSummary.find(e => e.organization_code === label);
return org?.organization_name || label;
@@ -125,14 +126,14 @@ export default function KaryawanPage() {
) : !loadingSummary ? (
- Data belum tersedia
+ {t('common.dataNotAvailable')}
) : null}
-
Data Karyawan Perbulan
+
{t('employee.dataKaryawanPerbulan')}
{montlyEmployee && montlyEmployee.length > 0 ? (
@@ -145,7 +146,7 @@ export default function KaryawanPage() {
/>
[`${value} karyawan`, "Jumlah Karyawan"]}
+ formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('employee.karyawan')}`, t('employee.jumlahKaryawan')]}
labelFormatter={(label) => formatDate(new Date(label), "MMMM yyyy")}
/>
- Data belum tersedia
+ {t('common.dataNotAvailable')}
) : null}
-
Pergerakan Karyawan
+
{t('employee.pergerakanKaryawan')}
{movementSummary && movementSummary.length > 0 ? (
@@ -188,7 +189,7 @@ export default function KaryawanPage() {
return | ;
})}
- [`${value} karyawan`, name]} />
+ [`${Number(value).toLocaleString('id-ID')} ${t('employee.karyawan')}`, name]} />
) : null}
-
Ranking (Top 10) Total Pergerakan Karyawan Setiap Perusahaan
+
{t('employee.rankingPergerakanKaryawan')}
{topMovementCompanies && topMovementCompanies.length > 0 ? (
@@ -217,33 +218,33 @@ export default function KaryawanPage() {
dataKey="organization_code"
tick={{ fontSize: 12 }}
/>
-
+
[value, name]}
+ formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('employee.karyawan')}`, name]}
labelFormatter={(label) => {
const org = topMovementCompanies.find(e => e.organization_code === label);
return org?.organization_name || label;
}}
/>
-
-
-
-
-
+
+
+
+
+
) : !loadingMovement ? (
- Data belum tersedia
+ {t('common.dataNotAvailable')}
) : null}
-
Penjatuhan Sanksi
+
{t('employee.penjatuhanSanksi')}
{totalSanctions && totalSanctions.length > 0 ? (
@@ -256,7 +257,7 @@ export default function KaryawanPage() {
/>
[value, "Total"]}
+ formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('common.total')}`, name]}
labelFormatter={(label) => label.toUpperCase()}
/>
@@ -264,13 +265,13 @@ export default function KaryawanPage() {
) : (
- Data belum tersedia
+ {t('common.dataNotAvailable')}
)}
-
Ranking (Top 10) Total Penjatuhan Sanksi Setiap Perusahaan
+
{t('employee.rankingSanksi')}
{sanctionSummary && sanctionSummary.length > 0 ? (
@@ -282,7 +283,7 @@ export default function KaryawanPage() {
/>
[value, name]}
+ formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('common.total')}`, name]}
labelFormatter={(label) => {
const org = sanctionSummary?.find(e => e.organization_code === label);
return org?.organization_name || label;
@@ -297,7 +298,7 @@ export default function KaryawanPage() {
) : !loadingSanction ? (
- Data belum tersedia
+ {t('common.dataNotAvailable')}
) : null}
diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx
index 7258f7e..ea2dfee 100644
--- a/src/app/(dashboard)/layout.tsx
+++ b/src/app/(dashboard)/layout.tsx
@@ -15,6 +15,8 @@ import { setFilter } from "@/lib/slice/filter";
import { format } from "date-fns";
import { useGetFilterOptionsQuery } from "@/services/api";
import { NeedLoginRoute } from "../RouteGuard";
+import { useLocale } from "@/lib/hooks/useLocale";
+import { LanguageSwitcher } from "@/components/LanguageSwitcher";
export default function DashboardLayout({children}:{children: React.ReactNode}) {
@@ -24,6 +26,7 @@ export default function DashboardLayout({children}:{children: React.ReactNode})
const filter = useAppSelector((state) => state.filter.filter);
const {data: filterOptions } = useGetFilterOptionsQuery(filter);
const [region, setRegion] = React.useState("");
+ const { t } = useLocale();
// Helper function to validate date range
const isValidDateRange = (startDate: string, endDate: string) => {
@@ -67,42 +70,42 @@ export default function DashboardLayout({children}:{children: React.ReactNode})
-
Halaman
+
{t('navigation.pages')}
-
Data Karyawan
+
{t('navigation.dataKaryawan')}
-
Absensi
+
{t('navigation.absensi')}
-
Turn Over Rate
+
{t('navigation.turnOverRate')}
-
Produktifitas Karyawan
+
{t('navigation.produktifitasKaryawan')}
-
HR Cost
+
{t('navigation.hrCost')}
-
Log Out
+
{t('navigation.logout')}
Powered by
@@ -114,10 +117,10 @@ export default function DashboardLayout({children}:{children: React.ReactNode})
-
-
ID
-
-
CN
+
option.name}
- renderInput={(params) => }
+ renderInput={(params) => }
onChange={(e, value) => {
dispatch(setFilter({...filter, organization_code: value?.codes ?? ""}));
setRegion(value?.codes ?? "");
@@ -156,7 +159,7 @@ export default function DashboardLayout({children}:{children: React.ReactNode})
className="w-full"
options={filterOptions?.organizations ?? []}
getOptionLabel={(option) => option.name}
- renderInput={(params) => }
+ renderInput={(params) => }
onChange={(e, value) => {
dispatch(setFilter({...filter, organization_code: value?.code ?? ""}));
if (value?.code == undefined) dispatch(setFilter({...filter, organization_code: region}));
@@ -166,17 +169,17 @@ export default function DashboardLayout({children}:{children: React.ReactNode})
className="w-full"
options={filterOptions?.estates ?? []}
getOptionLabel={(option) => option.name}
- renderInput={(params) => }
+ renderInput={(params) => }
onChange={(e, value) => dispatch(setFilter({...filter, estate_name: value?.name ?? ""}))}
/>
- dispatch(setFilter({...filter, job_name: e.target.value}))}>
-
-
-
-
-
+ dispatch(setFilter({...filter, job_name: e.target.value}))}>
+
+
+
+
+
diff --git a/src/app/(dashboard)/productivity/page.tsx b/src/app/(dashboard)/productivity/page.tsx
index 9a3a704..252f1b2 100644
--- a/src/app/(dashboard)/productivity/page.tsx
+++ b/src/app/(dashboard)/productivity/page.tsx
@@ -1,6 +1,7 @@
"use client";
import { useAppSelector } from "@/lib/hooks";
+import { useLocale } from "@/lib/hooks/useLocale";
import { useGetFilterOptionsQuery, useGetProductivityByRegionQuery, useGetProductivityByAgeQuery, useGetProductivityByTenureQuery, useGetTonnageHarvestGroupEmployeeQuery, useGetTonnageHarvestByEmployeeOriginQuery, useGetTonnageHarvestByEmployeeSalaryQuery, useGetTargetTonnageQuery, useGetOrganizationAttendanceQuery, useGetCostQuery } from "@/services/api";
import { Filter } from "@/services/types";
import { useState } from "react";
@@ -8,6 +9,7 @@ import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContaine
import { PageLoader } from "@/components/Loader";
export default function ProductivityPage() {
+ const { t } = useLocale();
const filter = useAppSelector(state => state.filter.filter);
const { data: filterOptions, isLoading: isLoadingFilterOptions, isFetching: isFetchingFilterOptions } = useGetFilterOptionsQuery(filter);
const { data: productivityByRegion, isLoading: isLoadingProductivityByRegion, isFetching: isFetchingProductivityByRegion } = useGetProductivityByRegionQuery(filter);
@@ -32,7 +34,7 @@ export default function ProductivityPage() {
// Show loader while any data is loading
if (isLoading) {
- return
;
+ return
;
}
// Calculate total tonnage for percentage calculations (convert kg to tons)
@@ -109,7 +111,6 @@ export default function ProductivityPage() {
name: item.salary_range,
value: item.tonnage / 1000, // Convert kg to tons
count: item.count,
- percentage: item.percentage.toFixed(1)
})).sort((a, b) => b.value - a.value) || []; // Sort by tonnage descending
// Calculate current total tonnage from region data (already in tons)
@@ -138,14 +139,14 @@ export default function ProductivityPage() {
return (
-
Produktifitas
+
{t('productivity.produktifitas')}
-
Target Tonase
+
{t('productivity.targetTonase')}
{/* Target tonnage background (lighter) */}
- {targetTonnageValue.toLocaleString('id-ID', { maximumFractionDigits: 0 })} Ton
+ {targetTonnageValue.toLocaleString('id-ID', { maximumFractionDigits: 0 })} {t('common.ton')}
{/* Current tonnage overlay (darker) */}
{currentPercentage >= 30 && (
- {currentTotalTonnage.toLocaleString('id-ID', { maximumFractionDigits: 0 })} Ton
+ {currentTotalTonnage.toLocaleString('id-ID', { maximumFractionDigits: 0 })} {t('common.ton')}
)}
- Current: {currentTotalTonnage.toLocaleString('id-ID', { maximumFractionDigits: 0 })} Ton | Target: {targetTonnageValue.toLocaleString('id-ID', { maximumFractionDigits: 0 })} Ton ({currentPercentage.toFixed(1)}% achieved)
+ {t('productivity.current')}: {currentTotalTonnage.toLocaleString('id-ID', { maximumFractionDigits: 0 })} {t('common.ton')} | {t('productivity.target')}: {targetTonnageValue.toLocaleString('id-ID', { maximumFractionDigits: 0 })} {t('common.ton')} ({currentPercentage.toFixed(1)}% {t('productivity.achieved')})
-
Rasio Absensi
+
{t('productivity.rasioAbsensi')}
{/* Total workdays background (lighter) */}
- {totalWorkdays.toLocaleString('id-ID')} Hari Kerja
+ {totalWorkdays.toLocaleString('id-ID')} {t('productivity.hariKerja')}
{/* Attendance overlay (darker) */}
{attendancePercentage >= 30 && (
- {totalAttendance.toLocaleString('id-ID')} Masuk
+ {totalAttendance.toLocaleString('id-ID')} {t('productivity.masuk')}
)}
- Masuk: {totalAttendance.toLocaleString('id-ID')} Hari | Hari Kerja: {totalWorkdays.toLocaleString('id-ID')} Hari ({attendancePercentage.toFixed(1)}% kehadiran)
+ {t('productivity.masuk')}: {totalAttendance.toLocaleString('id-ID')} {t('productivity.hari')} | {t('productivity.hariKerja')}: {totalWorkdays.toLocaleString('id-ID')} {t('productivity.hari')} ({attendancePercentage.toFixed(1)}% {t('productivity.kehadiran')})
-
Cost Per Kilo
+
{t('productivity.costPerKilo')}
Rp {costPerKilo?.toLocaleString('id-ID', { maximumFractionDigits: 0 }) || '0'}
-
Analisa Produktifitas Berdasarkan Wilayah
+
{t('productivity.analisaProduktifitas')}
{productivityByRegion && productivityByRegion.length > 0 ? (
@@ -202,19 +203,19 @@ export default function ProductivityPage() {
dataKey="organization_code"
tick={{ fontSize: 12 }}
/>
-
-
+
+
{
- if (name === 'Tonnage') {
- return [`${Math.round(Number(value)).toLocaleString('id-ID')} ton`, 'Tonnage'];
+ if (name === t('productivity.tonnage')) {
+ return [`${Math.round(Number(value)).toLocaleString('id-ID')} ${t('common.ton')}`, t('productivity.tonnage')];
}
- if (name === 'Employee Count') {
- return [`${Number(value).toLocaleString('id-ID')} employees`, 'Employee Count'];
+ if (name === t('productivity.employeeCount')) {
+ return [`${Number(value).toLocaleString('id-ID')} ${t('common.employees')}`, t('productivity.employeeCount')];
}
- if (name === 'Ratio per Employee') {
- return [`${Number(value).toFixed(2)} ton/employee`, 'Ratio per Employee'];
+ if (name === t('productivity.ratioPerEmployee')) {
+ return [`${Number(value).toFixed(2)} ${t('productivity.tonPerEmployee')}`, t('productivity.ratioPerEmployee')];
}
return [value, name];
}}
@@ -224,21 +225,21 @@ export default function ProductivityPage() {
}}
/>
-
-
-
+
+
+
) : (
- Data belum tersedia
+ {t('common.dataNotAvailable')}
)}
-
Output Ton Per Tenaga Kerja
+
{t('productivity.outputTonPerTenagaKerja')}
{tonnageGroupData && tonnageGroupData.length > 0 ? (
@@ -258,11 +259,11 @@ export default function ProductivityPage() {
[
- `${Number(value).toLocaleString('id-ID')} karyawan`,
- 'Jumlah Karyawan'
+ `${Number(value).toLocaleString('id-ID')} ${t('common.karyawan')}`,
+ t('productivity.jumlahKaryawan')
]}
labelFormatter={(label, payload) => {
- return `Range Tonase: ${label}`;
+ return `${t('productivity.rangeTonase')}: ${label}`;
}}
/>
-
Output Ton Berdasarkan Gaji
+
{t('productivity.outputTonBerdasarkanGaji')}
@@ -303,19 +304,19 @@ export default function ProductivityPage() {
[
- `${value.toLocaleString('id-ID')} ton`,
- 'Output Tonnage'
+ `${value.toLocaleString('id-ID')} ${t('common.ton')}`,
+ t('productivity.outputTonnage')
]}
- labelFormatter={(label) => `Gaji: ${label}`}
+ labelFormatter={(label) => `${t('productivity.gaji')}: ${label}`}
content={({ active, payload, label }) => {
if (active && payload && payload.length) {
const data = payload[0].payload;
return (
-
{`Gaji: ${payload[0].name}`}
-
{`Output: ${data.value.toLocaleString('id-ID')} ton`}
-
{`Karyawan: ${data.count.toLocaleString('id-ID')} orang`}
-
{`Persentase: ${data.percentage}%`}
+
{`${t('productivity.gaji')}: ${payload[0].name}`}
+
{`${t('productivity.output')}: ${data.value.toLocaleString('id-ID')} ${t('common.ton')}`}
+
{`${t('common.karyawan')}: ${data.count.toLocaleString('id-ID')} ${t('common.orang')}`}
+
{`${t('productivity.persentase')}: ${data.percentage}%`}
);
}
@@ -330,7 +331,7 @@ export default function ProductivityPage() {
wrapperStyle={{ paddingLeft: '20px' }}
formatter={(value, entry) => (
- {value}: {entry.payload?.value?.toLocaleString('id-ID')} ton ({(entry.payload as any)?.percentage}%)
+ {value}: {entry.payload?.value?.toLocaleString('id-ID')} {t('common.ton')} ({(entry.payload as any)?.percentage}%)
)}
/>
@@ -340,7 +341,7 @@ export default function ProductivityPage() {
-
Output Ton Berdasarkan Domisili
+
{t('productivity.outputTonBerdasarkanDomisili')}
@@ -360,10 +361,10 @@ export default function ProductivityPage() {
[
- `${props.payload?.count?.toLocaleString('id-ID') ?? 0} karyawan`,
- `${Math.round(Number(props.payload?.value ?? 0)).toLocaleString('id-ID')} ton`
+ `${props.payload?.count?.toLocaleString('id-ID') ?? 0} ${t('common.karyawan')}`,
+ `${Math.round(Number(props.payload?.value ?? 0)).toLocaleString('id-ID')} ${t('common.ton')}`
]}
- labelFormatter={(label) => `Category: ${label}`}
+ labelFormatter={(label) => `${t('productivity.category')}: ${label}`}
/>
-
Rasio Usia Karyawan : Tonase
+
{t('productivity.rasioUsiaKaryawan')}
-
-
+
+
{
- if (name === 'Tonnage') {
- return [`${Math.round(Number(value)).toLocaleString('id-ID')} ton`, 'Tonnage'];
+ if (name === t('productivity.tonnage')) {
+ return [`${Math.round(Number(value)).toLocaleString('id-ID')} ${t('common.ton')}`, t('productivity.tonnage')];
}
- if (name === 'Employee Count') {
- return [`${Number(value).toLocaleString('id-ID')} employees`, 'Employee Count'];
+ if (name === t('productivity.employeeCount')) {
+ return [`${Number(value).toLocaleString('id-ID')} ${t('common.employees')}`, t('productivity.employeeCount')];
}
- if (name === 'Ratio per Employee') {
- return [`${Number(value).toFixed(2)} ton/employee`, 'Ratio per Employee'];
+ if (name === t('productivity.ratioPerEmployee')) {
+ return [`${Number(value).toFixed(2)} ${t('productivity.tonPerEmployee')}`, t('productivity.ratioPerEmployee')];
}
return [value, name];
}}
/>
-
-
-
+
+
+
-
Rasio Masa Kerja Karyawan : Tonase
+
{t('productivity.rasioMasaKerja')}
-
-
+
+
{
- if (name === 'Tonnage') {
- return [`${Math.round(Number(value)).toLocaleString('id-ID')} ton`, 'Tonnage'];
+ if (name === t('productivity.tonnage')) {
+ return [`${Math.round(Number(value)).toLocaleString('id-ID')} ${t('common.ton')}`, t('productivity.tonnage')];
}
- if (name === 'Employee Count') {
- return [`${Number(value).toLocaleString('id-ID')} employees`, 'Employee Count'];
+ if (name === t('productivity.employeeCount')) {
+ return [`${Number(value).toLocaleString('id-ID')} ${t('common.employees')}`, t('productivity.employeeCount')];
}
- if (name === 'Ratio per Employee') {
- return [`${Number(value).toFixed(2)} ton/employee`, 'Ratio per Employee'];
+ if (name === t('productivity.ratioPerEmployee')) {
+ return [`${Number(value).toFixed(2)} ${t('productivity.tonPerEmployee')}`, t('productivity.ratioPerEmployee')];
}
return [value, name];
}}
/>
-
-
-
+
+
+
diff --git a/src/app/(dashboard)/turnover/page.tsx b/src/app/(dashboard)/turnover/page.tsx
index 6f7c113..5c378ba 100644
--- a/src/app/(dashboard)/turnover/page.tsx
+++ b/src/app/(dashboard)/turnover/page.tsx
@@ -1,6 +1,7 @@
"use client"
import { useAppSelector } from "@/lib/hooks";
import { useDimensions } from "@/lib/hooks/dimension";
+import { useLocale } from "@/lib/hooks/useLocale";
import { useGetEmployeeMovementQuery, useGetMppRecruitmentQuery, useGetResignCategoryQuery, useGetResignReasonQuery, useGetResignSummaryQuery, useGetResignTypeQuery } from "@/services/api";
import { BarChart, Bar, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts";
import { Loader, LucideLoader2 } from "lucide-react";
@@ -10,7 +11,7 @@ import { Tooltip as ReactTooltip } from "react-tooltip";
import { PageLoader } from "@/components/Loader";
export default function TurnoverPage() {
-
+ const { t } = useLocale();
const filter = useAppSelector(state => state.filter.filter);
const resignRef = useRef
(null);
const recruitmentRef = useRef(null);
@@ -170,13 +171,13 @@ export default function TurnoverPage() {
// Show loader while any data is loading
if (isLoading) {
- return ;
+ return ;
}
return (
-
Karyawan Baru Seluruh Perusahaan
+
{t('turnover.karyawanBaruSeluruh')}
{isLoadingEmployeeMovement ? (
@@ -188,15 +189,15 @@ export default function TurnoverPage() {
-
Rasio Recruitment
+
{t('turnover.rasioRecruitment')}
{recruitmentRatio} %
-
Karyawan Baru
+
{t('turnover.karyawanBaru')}
{totalRecruitment}
-
Karyawan Aktif
+
{t('turnover.karyawanAktif')}
{totalActiveEmployees}
@@ -204,7 +205,7 @@ export default function TurnoverPage() {
)}
-
Man Power Planning per Perusahaan : Recruitment
+
{t('turnover.manPowerPlanning')}
{mppRecruitmentSummary && !isLoadingMppRecruitment ? (
@@ -224,10 +225,10 @@ export default function TurnoverPage() {
{
const orgData = mppRecruitmentSummary.find(e => e.organization_code === props.payload.organization_code);
- if (name === "Recruited") {
- return [`${orgData?.recruited || 0} employees`, "Recruited"];
+ if (name === t('turnover.recruited')) {
+ return [`${orgData?.recruited || 0} ${t('common.employees')}`, t('turnover.recruited')];
} else {
- return [`${orgData?.remaining || 0} employees`, "Remaining"];
+ return [`${orgData?.remaining || 0} ${t('common.employees')}`, t('turnover.remaining')];
}
}}
labelFormatter={(label) => {
@@ -236,41 +237,41 @@ export default function TurnoverPage() {
}}
/>
-
-
+
+
) : isLoadingMppRecruitment ? (
) : (
- Data belum tersedia
+ {t('common.dataNotAvailable')}
)}
-
Karyawan Resign Seluruh Perusahaan
+
{t('turnover.karyawanResignSeluruh')}
-
Rasio Turn Over
+
{t('turnover.rasioTurnOver')}
{turnOverRatio} %
-
Karyawan Resign
+
{t('turnover.karyawanResign')}
{resign}
-
Karyawan Aktif
+
{t('turnover.karyawanAktif')}
{active}
-
Resignment per Perusahaan : Jumlah Karyawan
+
{t('turnover.resignmentPerPerusahaan')}
{limitedResignSummary && !isLoadingResignSummary ? (
@@ -290,10 +291,10 @@ export default function TurnoverPage() {
{
const orgData = limitedResignSummary.find(e => e.organization_code === props.payload.organization_code);
- if (name === "Resign") {
- return [`${orgData?.count || 0} employees`, "Resign"];
+ if (name === t('turnover.resign')) {
+ return [`${orgData?.count || 0} ${t('common.employees')}`, t('turnover.resign')];
} else {
- return [`${orgData?.active || 0} employees`, "Active"];
+ return [`${orgData?.active || 0} ${t('common.employees')}`, t('turnover.active')];
}
}}
labelFormatter={(label) => {
@@ -302,24 +303,24 @@ export default function TurnoverPage() {
}}
/>
-
-
+
+
) : isLoadingResignSummary || isFetchingResignSummary ? (
) : (
- Data belum tersedia
+ {t('common.dataNotAvailable')}
)}
-
Jenis Pemutusan Hubungan Kerja
+
{t('turnover.jenisPemutusanHubungan')}
{resignType && resignType.map((resign, index) => (
-
+
{resign.type}
@@ -327,12 +328,12 @@ export default function TurnoverPage() {
))}
- {resignType && resignType.length === 0 &&
Data belum tersedia}
+ {resignType && resignType.length === 0 &&
{t('common.dataNotAvailable')}}
-
Kategory Resign
+
{t('turnover.kategoriResign')}
{resignCategory && resignCategory.length > 0 ? (
@@ -350,7 +351,7 @@ export default function TurnoverPage() {
|
))}
- [`${value} employees`, name]} />
+ [`${value} ${t('common.employees')}`, name]} />
-
Alasan Pemutusan Hubungan Kerja
+
{t('turnover.alasanPemutusanHubungan')}
{resignReason && resignReason.map((resign, index) => (
-
+
{resign.reason}
@@ -378,7 +379,7 @@ export default function TurnoverPage() {
))}
- {resignReason && resignReason.length === 0 &&
Data belum tersedia}
+ {resignReason && resignReason.length === 0 &&
{t('common.dataNotAvailable')}}
diff --git a/src/app/AppProvider.tsx b/src/app/AppProvider.tsx
index 517601f..9da252d 100644
--- a/src/app/AppProvider.tsx
+++ b/src/app/AppProvider.tsx
@@ -7,18 +7,22 @@ import { Provider } from "react-redux";
import { StyledEngineProvider } from "@mui/material";
import { PersistGate } from "redux-persist/integration/react";
import { GuardedRoute } from "./RouteGuard";
+import { I18nextProvider } from "react-i18next";
+import i18n from "../lib/i18n";
export default function AppProvider({children}:{children: React.ReactNode}) {
return (
-
-
-
-
-
- {children}
-
-
-
-
-
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
)
}
\ No newline at end of file
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
index 417eb11..4594dcc 100644
--- a/src/app/login/page.tsx
+++ b/src/app/login/page.tsx
@@ -8,6 +8,8 @@ import { useLoginMutation } from "@/services/api";
import { useAppDispatch } from "@/lib/hooks";
import { setRoles, setToken, setUser } from "@/lib/slice/auth";
import { useRouter } from "next/navigation";
+import { useLocale } from "@/lib/hooks/useLocale";
+import { LanguageSwitcher } from "@/components/LanguageSwitcher";
export default function LoginPage(){
const [login, {data: loginData, error: loginError}] = useLoginMutation();
@@ -15,6 +17,7 @@ export default function LoginPage(){
const [password, setPassword] = useState("");
const dispatch = useAppDispatch();
const router = useRouter();
+ const { t } = useLocale();
useEffect(() => {
@@ -29,18 +32,36 @@ export default function LoginPage(){
return (
+
+ {/* Language Switcher positioned at top right */}
+
+
+
+
-
HRM Dashboard
-
Masukan Username & Password
+
{t('auth.loginTitle')}
+
{t('auth.loginSubtitle')}
- setUsername(e.target.value)}/>
- setPassword(e.target.value)}/>
+ setUsername(e.target.value)}
+ />
+ setPassword(e.target.value)}
+ />
{loginError &&
{(loginError as any).data.message}
}
@@ -48,9 +69,9 @@ export default function LoginPage(){
onClick={async () => {
await login({username, password})
}}
- >Log In
+ >{t('auth.loginButton')}
-
Powered by
+
{t('auth.poweredBy')}
diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx
new file mode 100644
index 0000000..1bff5b5
--- /dev/null
+++ b/src/components/LanguageSwitcher.tsx
@@ -0,0 +1,38 @@
+"use client";
+import React from 'react';
+import { useLocale } from '@/lib/hooks/useLocale';
+import { SupportedLocale } from '@/lib/slice/locale';
+
+interface LanguageSwitcherProps {
+ className?: string;
+}
+
+export const LanguageSwitcher: React.FC
= ({ className = '' }) => {
+ const { currentLocale, changeLanguage } = useLocale();
+
+ const languages = [
+ { code: 'id' as SupportedLocale, name: 'Bahasa Indonesia', flag: '🇮🇩' },
+ { code: 'cn' as SupportedLocale, name: '中文', flag: '🇨🇳' },
+ ];
+
+ return (
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/lib/hooks/useLocale.ts b/src/lib/hooks/useLocale.ts
new file mode 100644
index 0000000..49d7b6c
--- /dev/null
+++ b/src/lib/hooks/useLocale.ts
@@ -0,0 +1,30 @@
+import { useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useAppDispatch, useAppSelector } from '../hooks';
+import { setLocale, SupportedLocale } from '../slice/locale';
+
+export const useLocale = () => {
+ const { i18n, t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const currentLocale = useAppSelector((state) => state.locale.currentLocale);
+
+ // Sync Redux state with i18next on mount
+ useEffect(() => {
+ if (currentLocale && i18n.language !== currentLocale) {
+ i18n.changeLanguage(currentLocale);
+ }
+ }, [currentLocale, i18n]);
+
+ // Function to change language
+ const changeLanguage = (locale: SupportedLocale) => {
+ dispatch(setLocale(locale));
+ i18n.changeLanguage(locale);
+ };
+
+ return {
+ currentLocale,
+ changeLanguage,
+ t,
+ i18n,
+ };
+};
\ No newline at end of file
diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts
new file mode 100644
index 0000000..5d5ef7c
--- /dev/null
+++ b/src/lib/i18n.ts
@@ -0,0 +1,31 @@
+import i18n from 'i18next';
+import { initReactI18next } from 'react-i18next';
+
+// Import translation files
+import idTranslations from '../locales/id.json';
+import cnTranslations from '../locales/cn.json';
+
+const resources = {
+ id: {
+ translation: idTranslations,
+ },
+ cn: {
+ translation: cnTranslations,
+ },
+};
+
+i18n
+ .use(initReactI18next)
+ .init({
+ resources,
+ lng: 'id', // Default language
+ fallbackLng: 'id',
+ interpolation: {
+ escapeValue: false, // React already does escaping
+ },
+ react: {
+ useSuspense: false, // Disable suspense to avoid issues with SSR
+ },
+ });
+
+export default i18n;
\ No newline at end of file
diff --git a/src/lib/slice/locale.ts b/src/lib/slice/locale.ts
new file mode 100644
index 0000000..bb03a80
--- /dev/null
+++ b/src/lib/slice/locale.ts
@@ -0,0 +1,24 @@
+import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+
+export type SupportedLocale = 'id' | 'cn';
+
+interface LocaleState {
+ currentLocale: SupportedLocale;
+}
+
+const initialState: LocaleState = {
+ currentLocale: 'id', // Default to Indonesian
+};
+
+export const localeSlice = createSlice({
+ name: "locale",
+ initialState,
+ reducers: {
+ setLocale: (state, action: PayloadAction) => {
+ state.currentLocale = action.payload;
+ },
+ },
+});
+
+export const { setLocale } = localeSlice.actions;
+export default localeSlice.reducer;
\ No newline at end of file
diff --git a/src/lib/store.ts b/src/lib/store.ts
index c59e66e..2a7b185 100644
--- a/src/lib/store.ts
+++ b/src/lib/store.ts
@@ -3,6 +3,7 @@ import { configureStore } from '@reduxjs/toolkit'
import filterReducer from '@/lib/slice/filter'
import { FLUSH, PAUSE, PERSIST, persistReducer, persistStore, PURGE, REGISTER, REHYDRATE } from "redux-persist";
import { authSlice } from './slice/auth';
+import localeReducer from './slice/locale';
import storage from './storage';
const persistAuth = persistReducer({
@@ -10,10 +11,16 @@ const persistAuth = persistReducer({
storage: storage,
}, authSlice.reducer)
+const persistLocale = persistReducer({
+ key: 'locale',
+ storage: storage,
+}, localeReducer)
+
export const store = configureStore({
reducer: {
[api.reducerPath]: api.reducer,
auth: persistAuth,
+ locale: persistLocale,
filter: filterReducer,
},
middleware: (getDefaultMiddleware) =>
diff --git a/src/locales/cn.json b/src/locales/cn.json
new file mode 100644
index 0000000..dbaf913
--- /dev/null
+++ b/src/locales/cn.json
@@ -0,0 +1,295 @@
+{
+ "common": {
+ "loading": "加载中...",
+ "error": "发生错误",
+ "success": "成功",
+ "cancel": "取消",
+ "save": "保存",
+ "edit": "编辑",
+ "delete": "删除",
+ "search": "搜索",
+ "filter": "筛选",
+ "export": "导出",
+ "import": "导入",
+ "refresh": "刷新",
+ "close": "关闭",
+ "open": "打开",
+ "yes": "是",
+ "no": "否",
+ "confirm": "确认",
+ "warning": "警告",
+ "info": "信息",
+ "total": "总计",
+ "average": "平均",
+ "percentage": "百分比",
+ "date": "日期",
+ "time": "时间",
+ "name": "姓名",
+ "description": "描述",
+ "status": "状态",
+ "active": "活跃",
+ "inactive": "非活跃",
+ "all": "全部",
+ "none": "无",
+ "select": "选择",
+ "clear": "清除",
+ "reset": "重置",
+ "apply": "应用",
+ "submit": "提交",
+ "back": "返回",
+ "next": "下一个",
+ "previous": "上一个",
+ "first": "第一个",
+ "last": "最后一个",
+ "page": "页面",
+ "of": "的",
+ "items": "项目",
+ "results": "结果",
+ "noData": "无数据",
+ "noResults": "无结果",
+ "selectAll": "全选",
+ "deselectAll": "取消全选",
+ "employees": "员工",
+ "employee": "员工",
+ "karyawan": "员工",
+ "orang": "人",
+ "ton": "吨",
+ "dataNotAvailable": "数据不可用",
+ "jumlah": "数量",
+ "hariKerja": "工作日"
+ },
+ "auth": {
+ "login": "登录",
+ "logout": "退出",
+ "username": "用户名",
+ "password": "密码",
+ "email": "邮箱",
+ "forgotPassword": "忘记密码?",
+ "rememberMe": "记住我",
+ "loginSuccess": "登录成功",
+ "loginError": "登录失败",
+ "logoutSuccess": "退出成功",
+ "loginButton": "登录",
+ "loginTitle": "人力资源管理仪表板",
+ "loginSubtitle": "输入用户名和密码",
+ "usernamePlaceholder": "用户名",
+ "passwordPlaceholder": "密码",
+ "poweredBy": "技术支持"
+ },
+ "dashboard": {
+ "title": "仪表板",
+ "overview": "概览",
+ "statistics": "统计",
+ "reports": "报告",
+ "settings": "设置"
+ },
+ "navigation": {
+ "pages": "页面",
+ "dataKaryawan": "员工数据",
+ "absensi": "考勤",
+ "turnOverRate": "离职率",
+ "produktifitasKaryawan": "员工生产力",
+ "hrCost": "人力成本",
+ "logout": "退出",
+ "poweredBy": "技术支持"
+ },
+ "filters": {
+ "startDate": "开始日期",
+ "endDate": "结束日期",
+ "region": "地区",
+ "company": "公司",
+ "location": "位置",
+ "position": "职位",
+ "all": "全部",
+ "staff": "员工",
+ "nonStaff": "非员工",
+ "harvesters": "收割工",
+ "maintenance": "维护"
+ },
+ "employee": {
+ "title": "员工数据",
+ "loadingData": "正在加载员工数据...",
+ "totalEmployees": "员工总数",
+ "activeEmployees": "在职员工",
+ "newHires": "新员工",
+ "resignations": "离职",
+ "monthlyTrend": "员工月度趋势",
+ "employeeMovement": "员工流动",
+ "sanctionSummary": "处罚摘要",
+ "recruitment": "招聘",
+ "resignation": "离职",
+ "promotion": "晋升",
+ "mutation": "调动",
+ "demotion": "降职",
+ "st": "ST",
+ "sp1": "SP1",
+ "sp2": "SP2",
+ "sp3": "SP3",
+ "topCompanies": "顶级公司",
+ "movementBreakdown": "流动明细",
+ "dataKaryawan": "员工数据",
+ "karyawan": "员工",
+ "jumlahKaryawan": "员工数量",
+ "dataKaryawanPerbulan": "月度员工数据",
+ "pergerakanKaryawan": "员工流动",
+ "rankingPergerakanKaryawan": "员工流动排名",
+ "jumlahPergerakan": "流动数量",
+ "penjatuhanSanksi": "处罚执行",
+ "rankingSanksi": "处罚排名"
+ },
+ "attendance": {
+ "title": "考勤",
+ "loading": "正在加载考勤数据...",
+ "staffAttendance": "员工考勤",
+ "nonStaffAttendance": "非员工考勤",
+ "harvesterAttendance": "收割工考勤",
+ "maintenanceAttendance": "维护人员考勤",
+ "monthlyTrend": "月度考勤趋势",
+ "attendanceRange": "考勤范围",
+ "workingDays": "工作日",
+ "employees": "员工",
+ "attendanceRate": "出勤率",
+ "absenceRate": "缺勤率",
+ "loadingData": "加载数据...",
+ "kehadiranStaff": "员工出勤",
+ "kehadiranNonStaff": "非员工出勤",
+ "kehadiranPemanen": "收割工出勤",
+ "kehadiranPerawatan": "维护人员出勤",
+ "dataKehadiranKaryawan": "员工出勤数据",
+ "hadir": "出勤",
+ "tidakHadir": "缺勤",
+ "dataAbsensiPerbulan": "月度考勤数据",
+ "persentase": "百分比",
+ "kehadiran": "出勤",
+ "mandays": "工作日"
+ },
+ "turnover": {
+ "title": "离职率",
+ "loading": "加载离职数据...",
+ "rate": "离职率",
+ "monthlyTrend": "月度离职趋势",
+ "byDepartment": "按部门",
+ "byRegion": "按地区",
+ "newHires": "新员工",
+ "separations": "离职",
+ "netChange": "净变化",
+ "loadingData": "加载数据...",
+ "karyawanBaruSeluruh": "全公司新员工",
+ "rasioRecruitment": "招聘比例",
+ "karyawanBaru": "新员工",
+ "karyawanAktif": "在职员工",
+ "manPowerPlanning": "人力规划",
+ "recruited": "已招聘",
+ "remaining": "剩余",
+ "karyawanResignSeluruh": "全公司离职员工",
+ "rasioTurnOver": "离职比例",
+ "karyawanResign": "离职员工",
+ "resignmentPerPerusahaan": "各公司离职情况",
+ "resign": "离职",
+ "active": "在职",
+ "jenisPemutusanHubungan": "劳动关系终止类型",
+ "kategoriResign": "离职类别",
+ "alasanPemutusanHubungan": "劳动关系终止原因"
+ },
+ "productivity": {
+ "title": "员工生产力",
+ "loading": "正在加载生产力数据...",
+ "overallProductivity": "整体生产力",
+ "byDepartment": "按部门",
+ "monthlyTrend": "月度生产力趋势",
+ "topPerformers": "顶级表现者",
+ "performanceMetrics": "绩效指标",
+ "outputPerEmployee": "每员工产出",
+ "efficiency": "效率",
+ "performance": "绩效",
+ "target": "目标",
+ "actual": "实际",
+ "variance": "差异",
+ "loadingData": "加载数据...",
+ "produktifitas": "生产力",
+ "targetTonase": "目标吨位",
+ "current": "当前",
+ "achieved": "已达成",
+ "rasioAbsensi": "出勤比例",
+ "hariKerja": "工作日",
+ "masuk": "出勤",
+ "hari": "天",
+ "kehadiran": "出勤率",
+ "costPerKilo": "每公斤成本",
+ "analisaProduktifitas": "生产力分析",
+ "tonnage": "吨位",
+ "employeeCount": "员工数量",
+ "ratioPerEmployee": "每员工比例",
+ "tonPerEmployee": "吨/员工",
+ "outputTonPerTenagaKerja": "每劳动力产出吨数",
+ "jumlahKaryawan": "员工数量",
+ "rangeTonase": "吨位范围",
+ "outputTonBerdasarkanGaji": "基于薪资的产出吨数",
+ "outputTonnage": "产出吨位",
+ "gaji": "薪资",
+ "output": "产出",
+ "persentase": "百分比",
+ "outputTonBerdasarkanDomisili": "基于居住地的产出吨数",
+ "category": "类别",
+ "rasioUsiaKaryawan": "员工年龄比例:吨位",
+ "rasioMasaKerja": "员工工龄比例:吨位"
+ },
+ "hrcost": {
+ "title": "人力资源成本",
+ "loading": "正在加载人力资源成本数据...",
+ "loadingData": "正在加载人力资源成本数据...",
+ "totalCost": "总成本",
+ "costBreakdown": "成本明细",
+ "monthlyCost": "月度成本",
+ "costPerEmployee": "每位员工成本",
+ "salaries": "薪资",
+ "benefits": "福利",
+ "training": "培训",
+ "recruitment": "招聘",
+ "rincianGajiPemanen": "收割员工资明细",
+ "keluarFullscreen": "退出全屏",
+ "grandTotalGaji": "薪资总计",
+ "rataRataGaji": "平均薪资",
+ "gajiTertinggi": "最高薪资",
+ "gaji": "薪资",
+ "bpjsTk": "劳工社保",
+ "bpjsKs": "健康社保",
+ "hrCost": "人力成本",
+ "gajiKaryawan": "员工薪资",
+ "rankingTop10": "公司薪资总额前10排名",
+ "totalSalary": "薪资总额",
+ "fullscreen": "全屏",
+ "totalGajiSetiapBulan": "每月薪资总额",
+ "idKaryawan": "员工ID",
+ "nama": "姓名",
+ "alamat": "地址",
+ "gajiIdr": "薪资 (印尼盾)"
+ },
+ "forms": {
+ "required": "必填项",
+ "invalid": "无效",
+ "tooShort": "太短",
+ "tooLong": "太长",
+ "invalidEmail": "邮箱无效",
+ "passwordMismatch": "密码不匹配",
+ "pleaseSelect": "请选择",
+ "pleaseEnter": "请输入",
+ "selectOption": "选择选项",
+ "enterValue": "输入值"
+ },
+ "messages": {
+ "confirmDelete": "您确定要删除此项目吗?",
+ "deleteSuccess": "项目删除成功",
+ "deleteError": "删除项目失败",
+ "saveSuccess": "数据保存成功",
+ "saveError": "保存数据失败",
+ "updateSuccess": "数据更新成功",
+ "updateError": "更新数据失败",
+ "noData": "无数据",
+ "noResults": "未找到结果",
+ "connectionError": "连接错误",
+ "serverError": "服务器错误",
+ "unauthorized": "未授权访问",
+ "forbidden": "访问被拒绝"
+ }
+}
\ No newline at end of file
diff --git a/src/locales/id.json b/src/locales/id.json
new file mode 100644
index 0000000..fb2e4a2
--- /dev/null
+++ b/src/locales/id.json
@@ -0,0 +1,311 @@
+{
+ "common": {
+ "loading": "Memuat...",
+ "error": "Terjadi kesalahan",
+ "success": "Berhasil",
+ "cancel": "Batal",
+ "save": "Simpan",
+ "edit": "Edit",
+ "delete": "Hapus",
+ "search": "Cari",
+ "filter": "Filter",
+ "export": "Ekspor",
+ "import": "Impor",
+ "refresh": "Refresh",
+ "close": "Tutup",
+ "open": "Buka",
+ "yes": "Ya",
+ "no": "Tidak",
+ "confirm": "Konfirmasi",
+ "warning": "Peringatan",
+ "info": "Informasi",
+ "total": "Total",
+ "average": "Rata-rata",
+ "percentage": "Persentase",
+ "date": "Tanggal",
+ "time": "Waktu",
+ "name": "Nama",
+ "description": "Deskripsi",
+ "status": "Status",
+ "active": "Aktif",
+ "inactive": "Tidak Aktif",
+ "all": "Semua",
+ "none": "Tidak Ada",
+ "select": "Pilih",
+ "clear": "Bersihkan",
+ "reset": "Reset",
+ "apply": "Terapkan",
+ "submit": "Kirim",
+ "back": "Kembali",
+ "next": "Selanjutnya",
+ "previous": "Sebelumnya",
+ "first": "Pertama",
+ "last": "Terakhir",
+ "page": "Halaman",
+ "of": "dari",
+ "items": "item",
+ "results": "hasil",
+ "noData": "Tidak ada data",
+ "noResults": "Tidak ada hasil",
+ "selectAll": "Pilih Semua",
+ "deselectAll": "Batalkan Pilihan Semua",
+ "employees": "karyawan",
+ "employee": "karyawan",
+ "karyawan": "karyawan",
+ "orang": "orang",
+ "ton": "ton",
+ "dataNotAvailable": "Data tidak tersedia",
+ "jumlah": "Jumlah",
+ "add": "Tambah",
+ "ok": "OK",
+ "home": "Beranda",
+ "about": "Tentang",
+ "contact": "Kontak",
+ "help": "Bantuan",
+ "support": "Dukungan",
+ "documentation": "Dokumentasi",
+ "faq": "FAQ",
+ "terms": "Syarat & Ketentuan",
+ "privacy": "Kebijakan Privasi",
+ "hariKerja": "Hari Kerja"
+ },
+ "auth": {
+ "login": "Masuk",
+ "logout": "Keluar",
+ "username": "Nama Pengguna",
+ "password": "Kata Sandi",
+ "email": "Email",
+ "forgotPassword": "Lupa Kata Sandi?",
+ "rememberMe": "Ingat Saya",
+ "loginSuccess": "Berhasil masuk",
+ "loginError": "Gagal masuk",
+ "logoutSuccess": "Berhasil keluar",
+ "loginButton": "Log In",
+ "loginTitle": "HRM Dashboard",
+ "loginSubtitle": "Masukan Username & Password",
+ "usernamePlaceholder": "Username",
+ "passwordPlaceholder": "Password",
+ "poweredBy": "Powered by"
+ },
+ "dashboard": {
+ "title": "Dashboard",
+ "overview": "Ringkasan",
+ "statistics": "Statistik",
+ "reports": "Laporan",
+ "analytics": "Analitik",
+ "settings": "Pengaturan",
+ "profile": "Profil",
+ "notifications": "Notifikasi",
+ "messages": "Pesan",
+ "tasks": "Tugas",
+ "calendar": "Kalender",
+ "documents": "Dokumen",
+ "users": "Pengguna",
+ "roles": "Peran",
+ "permissions": "Izin"
+ },
+ "navigation": {
+ "pages": "Halaman",
+ "dataKaryawan": "Data Karyawan",
+ "absensi": "Absensi",
+ "turnOverRate": "Turn Over Rate",
+ "produktifitasKaryawan": "Produktifitas Karyawan",
+ "hrCost": "HR Cost",
+ "logout": "Log Out",
+ "poweredBy": "Powered by"
+ },
+ "filters": {
+ "startDate": "Tanggal Mulai",
+ "endDate": "Tanggal Akhir",
+ "region": "Region",
+ "company": "Company",
+ "location": "Lokasi",
+ "position": "Posisi",
+ "all": "Semua",
+ "staff": "Staff",
+ "nonStaff": "Non Staff",
+ "harvesters": "Pemanen",
+ "maintenance": "Perawatan"
+ },
+ "employee": {
+ "title": "Data Karyawan",
+ "loadingData": "Memuat data karyawan...",
+ "totalEmployees": "Total Karyawan",
+ "activeEmployees": "Karyawan Aktif",
+ "newHires": "Karyawan Baru",
+ "resignations": "Pengunduran Diri",
+ "monthlyTrend": "Tren Bulanan Karyawan",
+ "employeeMovement": "Pergerakan Karyawan",
+ "sanctionSummary": "Ringkasan Sanksi",
+ "recruitment": "Rekrutmen",
+ "resignation": "Pengunduran Diri",
+ "promotion": "Promosi",
+ "mutation": "Mutasi",
+ "demotion": "Demosi",
+ "st": "ST",
+ "sp1": "SP1",
+ "sp2": "SP2",
+ "sp3": "SP3",
+ "topCompanies": "Perusahaan Teratas",
+ "movementBreakdown": "Rincian Pergerakan",
+ "dataKaryawan": "Data Karyawan",
+ "karyawan": "Karyawan",
+ "jumlahKaryawan": "Jumlah Karyawan",
+ "dataKaryawanPerbulan": "Data Karyawan Per Bulan",
+ "pergerakanKaryawan": "Pergerakan Karyawan",
+ "rankingPergerakanKaryawan": "Ranking Pergerakan Karyawan",
+ "jumlahPergerakan": "Jumlah Pergerakan",
+ "penjatuhanSanksi": "Penjatuhan Sanksi",
+ "rankingSanksi": "Ranking Sanksi"
+ },
+ "attendance": {
+ "title": "Absensi",
+ "loading": "Memuat data kehadiran...",
+ "staffAttendance": "Kehadiran Staff",
+ "nonStaffAttendance": "Kehadiran Non Staff",
+ "harvesterAttendance": "Kehadiran Pemanen",
+ "maintenanceAttendance": "Kehadiran Perawatan",
+ "monthlyTrend": "Tren Kehadiran Bulanan",
+ "attendanceRange": "Rentang Kehadiran",
+ "workingDays": "HK",
+ "employees": "karyawan",
+ "attendanceRate": "Tingkat Kehadiran",
+ "absenceRate": "Tingkat Ketidakhadiran",
+ "loadingData": "Memuat data...",
+ "kehadiranStaff": "Kehadiran Staff",
+ "kehadiranNonStaff": "Kehadiran Non Staff",
+ "kehadiranPemanen": "Kehadiran Pemanen",
+ "kehadiranPerawatan": "Kehadiran Perawatan",
+ "dataKehadiranKaryawan": "Data Kehadiran Karyawan",
+ "hadir": "Hadir",
+ "tidakHadir": "Tidak Hadir",
+ "dataAbsensiPerbulan": "Data Absensi Per Bulan",
+ "persentase": "Persentase",
+ "kehadiran": "Kehadiran",
+ "mandays": "Mandays"
+ },
+ "turnover": {
+ "title": "Turnover",
+ "newEmployees": "Karyawan Baru",
+ "resignedEmployees": "Karyawan Resign",
+ "turnoverRate": "Tingkat Turnover",
+ "recruitmentRatio": "Rasio Recruitment",
+ "resignationReason": "Alasan Resign",
+ "resignationType": "Jenis Resign",
+ "loadingData": "Memuat data...",
+ "karyawanBaruSeluruh": "Karyawan Baru Seluruh Perusahaan",
+ "rasioRecruitment": "Rasio Recruitment",
+ "karyawanBaru": "Karyawan Baru",
+ "karyawanAktif": "Karyawan Aktif",
+ "manPowerPlanning": "Man Power Planning",
+ "recruited": "Direkrut",
+ "remaining": "Tersisa",
+ "karyawanResignSeluruh": "Karyawan Resign Seluruh Perusahaan",
+ "rasioTurnOver": "Rasio Turn Over",
+ "karyawanResign": "Karyawan Resign",
+ "resignmentPerPerusahaan": "Resignation Per Perusahaan",
+ "resign": "Resign",
+ "active": "Aktif",
+ "jenisPemutusanHubungan": "Jenis Pemutusan Hubungan Kerja",
+ "kategoriResign": "Kategori Resign",
+ "alasanPemutusanHubungan": "Alasan Pemutusan Hubungan Kerja"
+ },
+ "productivity": {
+ "title": "Produktifitas Karyawan",
+ "loading": "Memuat data produktivitas...",
+ "overallProductivity": "Produktivitas Keseluruhan",
+ "byDepartment": "Berdasarkan Departemen",
+ "monthlyTrend": "Tren Produktivitas Bulanan",
+ "topPerformers": "Performa Terbaik",
+ "performanceMetrics": "Metrik Kinerja",
+ "outputPerEmployee": "Output per Karyawan",
+ "efficiency": "Efisiensi",
+ "performance": "Performa",
+ "target": "Target",
+ "actual": "Aktual",
+ "variance": "Varians",
+ "loadingData": "Memuat data...",
+ "produktifitas": "Produktifitas",
+ "targetTonase": "Target Tonase",
+ "current": "Saat Ini",
+ "achieved": "Tercapai",
+ "rasioAbsensi": "Rasio Absensi",
+ "hariKerja": "Hari Kerja",
+ "masuk": "Masuk",
+ "hari": "Hari",
+ "kehadiran": "Kehadiran",
+ "costPerKilo": "Cost Per Kilo",
+ "analisaProduktifitas": "Analisa Produktifitas",
+ "tonnage": "Tonase",
+ "employeeCount": "Jumlah Karyawan",
+ "ratioPerEmployee": "Rasio per Karyawan",
+ "tonPerEmployee": "ton/karyawan",
+ "outputTonPerTenagaKerja": "Output Ton per Tenaga Kerja",
+ "jumlahKaryawan": "Jumlah Karyawan",
+ "rangeTonase": "Range Tonase",
+ "outputTonBerdasarkanGaji": "Output Ton Berdasarkan Gaji",
+ "outputTonnage": "Output Tonase",
+ "gaji": "Gaji",
+ "output": "Output",
+ "persentase": "Persentase",
+ "outputTonBerdasarkanDomisili": "Output Ton Berdasarkan Domisili",
+ "category": "Kategori",
+ "rasioUsiaKaryawan": "Rasio Usia Karyawan : Tonase",
+ "rasioMasaKerja": "Rasio Masa Kerja Karyawan : Tonase"
+ },
+ "hrcost": {
+ "title": "Biaya HR",
+ "loading": "Memuat data biaya HR...",
+ "loadingData": "Memuat data biaya HR...",
+ "totalCost": "Total Biaya",
+ "costPerEmployee": "Biaya per Karyawan",
+ "breakdown": "Rincian",
+ "salary": "Gaji",
+ "benefits": "Tunjangan",
+ "insurance": "Asuransi",
+ "rincianGajiPemanen": "Rincian Gaji Pemanen",
+ "keluarFullscreen": "Keluar Fullscreen",
+ "grandTotalGaji": "Grand Total Gaji",
+ "rataRataGaji": "Rata-Rata Gaji",
+ "gajiTertinggi": "Gaji Tertinggi",
+ "gaji": "Gaji",
+ "bpjsTk": "BPJS TK",
+ "bpjsKs": "BPJS KS",
+ "hrCost": "HR Cost",
+ "gajiKaryawan": "Gaji Karyawan",
+ "rankingTop10": "Ranking Top 10 Total Gaji Perusahaan",
+ "totalSalary": "Total Salary",
+ "fullscreen": "Fullscreen",
+ "totalGajiSetiapBulan": "Total Gaji Setiap Bulan",
+ "idKaryawan": "ID Karyawan",
+ "nama": "Nama",
+ "alamat": "Alamat",
+ "gajiIdr": "Gaji (IDR)"
+ },
+ "forms": {
+ "required": "Wajib diisi",
+ "invalid": "Tidak valid",
+ "tooShort": "Terlalu pendek",
+ "tooLong": "Terlalu panjang",
+ "invalidEmail": "Email tidak valid",
+ "passwordMismatch": "Kata sandi tidak cocok",
+ "pleaseSelect": "Silakan pilih",
+ "pleaseEnter": "Silakan masukkan",
+ "selectOption": "Pilih opsi",
+ "enterValue": "Masukkan nilai"
+ },
+ "messages": {
+ "confirmDelete": "Apakah Anda yakin ingin menghapus item ini?",
+ "deleteSuccess": "Item berhasil dihapus",
+ "deleteError": "Gagal menghapus item",
+ "saveSuccess": "Data berhasil disimpan",
+ "saveError": "Gagal menyimpan data",
+ "updateSuccess": "Data berhasil diperbarui",
+ "updateError": "Gagal memperbarui data",
+ "noResults": "Tidak ada hasil ditemukan",
+ "connectionError": "Kesalahan koneksi",
+ "serverError": "Kesalahan server",
+ "unauthorized": "Tidak memiliki akses",
+ "forbidden": "Akses ditolak"
+ }
+}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 418c154..99c22cc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -60,16 +60,9 @@
dependencies:
"@babel/types" "^7.25.6"
-"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.9", "@babel/runtime@^7.25.6", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
- version "7.25.6"
- resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz"
- integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==
- dependencies:
- regenerator-runtime "^0.14.0"
-
-"@babel/runtime@^7.28.4":
+"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.9", "@babel/runtime@^7.25.6", "@babel/runtime@^7.27.6", "@babel/runtime@^7.28.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
version "7.28.4"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326"
+ resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz"
integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==
"@babel/template@^7.25.0":
@@ -336,14 +329,9 @@
csstype "^3.1.3"
prop-types "^15.8.1"
-"@mui/types@^7.2.15", "@mui/types@^7.2.16":
- version "7.2.16"
- resolved "https://registry.npmjs.org/@mui/types/-/types-7.2.16.tgz"
- integrity sha512-qI8TV3M7ShITEEc8Ih15A2vLzZGLhD+/UPNwck/hcls2gwg7dyRjNGXcQYHKLB5Q7PuTRfrTkAoPa2VV1s67Ag==
-
-"@mui/types@^7.4.7":
+"@mui/types@^7.2.15", "@mui/types@^7.2.16", "@mui/types@^7.4.7":
version "7.4.7"
- resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.4.7.tgz#7231f6c050586b5e732c7169f4a934e1856efd51"
+ resolved "https://registry.npmjs.org/@mui/types/-/types-7.4.7.tgz"
integrity sha512-8vVje9rdEr1rY8oIkYgP+Su5Kwl6ik7O3jQ0wl78JGSmiZhRHV+vkjooGdKD8pbtZbutXFVTWQYshu2b3sG9zw==
dependencies:
"@babel/runtime" "^7.28.4"
@@ -372,9 +360,9 @@
prop-types "^15.8.1"
react-is "^18.3.1"
-"@mui/utils@^7.3.2":
+"@mui/utils@^7.3.3":
version "7.3.3"
- resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-7.3.3.tgz#9fe030b94451466fba51428804937286c5103a95"
+ resolved "https://registry.npmjs.org/@mui/utils/-/utils-7.3.3.tgz"
integrity sha512-kwNAUh7bLZ7mRz9JZ+6qfRnnxbE4Zuc+RzXnhSpRSxjTlSTj7b4JxRLXpG+MVtPVtqks5k/XC8No1Vs3x4Z2gg==
dependencies:
"@babel/runtime" "^7.28.4"
@@ -420,17 +408,17 @@
prop-types "^15.8.1"
"@mui/x-data-grid@^8.13.1":
- version "8.13.1"
- resolved "https://registry.yarnpkg.com/@mui/x-data-grid/-/x-data-grid-8.13.1.tgz#d01d2ca185239e7af14927288206cd3366f8cce2"
- integrity sha512-64MlyukMoGEDLT3kqdm6tw+rocgMayChj+h+fdAwqD4+2NMQoD5wZElQE+xTNmU0/DPv710X4ENceBRt2hMuGw==
+ version "8.14.0"
+ resolved "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.14.0.tgz"
+ integrity sha512-bzUpD83Wx4mawkgquDQUUbLLnpF+JP7Pe7YQx1ixS6W/AlUwXAVagPTOijwchHvlx0Ky11dJvOQAfrnWu6an/Q==
dependencies:
"@babel/runtime" "^7.28.4"
- "@mui/utils" "^7.3.2"
- "@mui/x-internals" "8.13.1"
- "@mui/x-virtualizer" "0.2.2"
+ "@mui/utils" "^7.3.3"
+ "@mui/x-internals" "8.14.0"
+ "@mui/x-virtualizer" "0.2.3"
clsx "^2.1.1"
prop-types "^15.8.1"
- use-sync-external-store "^1.5.0"
+ use-sync-external-store "^1.6.0"
"@mui/x-date-pickers@^7.17.0":
version "7.17.0"
@@ -453,70 +441,30 @@
"@babel/runtime" "^7.25.6"
"@mui/utils" "^5.16.6"
-"@mui/x-internals@8.13.1":
- version "8.13.1"
- resolved "https://registry.yarnpkg.com/@mui/x-internals/-/x-internals-8.13.1.tgz#ddf8f442121044d75e9141a446fc3988095ffc49"
- integrity sha512-OKQyCJ9uxtMpjBZCOEQGOR5MhgL1f9HjI4qZHuaLxxtDATK5rcBbVjBF67hI8FzXeF1wrcZP2wsjc4AgGpAo9g==
+"@mui/x-internals@8.14.0":
+ version "8.14.0"
+ resolved "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.14.0.tgz"
+ integrity sha512-esYyl61nuuFXiN631TWuPh2tqdoyTdBI/4UXgwH3rytF8jiWvy6prPBPRHEH1nvW3fgw9FoBI48FlOO+yEI8xg==
dependencies:
"@babel/runtime" "^7.28.4"
- "@mui/utils" "^7.3.2"
+ "@mui/utils" "^7.3.3"
reselect "^5.1.1"
- use-sync-external-store "^1.5.0"
+ use-sync-external-store "^1.6.0"
-"@mui/x-virtualizer@0.2.2":
- version "0.2.2"
- resolved "https://registry.yarnpkg.com/@mui/x-virtualizer/-/x-virtualizer-0.2.2.tgz#0d1c0ee0072c5436e7c2571b5e158b91915620d3"
- integrity sha512-+ZcGYh/9ykoEofzcAWcJ3n6TBXzCc2ETvytho30wRkYv1ez+8yps0ezns/QvC4JqXBge/3y+e+QatIYjkTltdw==
+"@mui/x-virtualizer@0.2.3":
+ version "0.2.3"
+ resolved "https://registry.npmjs.org/@mui/x-virtualizer/-/x-virtualizer-0.2.3.tgz"
+ integrity sha512-CZ+VxFmeJaTduAOlSyo5cVek0PV5Y8gm4coyaHEpCvms207J9AoMUKqWIcdwsVGlTH1Y71j35xT/MwHKutZiNw==
dependencies:
"@babel/runtime" "^7.28.4"
- "@mui/utils" "^7.3.2"
- "@mui/x-internals" "8.13.1"
+ "@mui/utils" "^7.3.3"
+ "@mui/x-internals" "8.14.0"
"@next/env@14.2.11":
version "14.2.11"
resolved "https://registry.npmjs.org/@next/env/-/env-14.2.11.tgz"
integrity sha512-HYsQRSIXwiNqvzzYThrBwq6RhXo3E0n8j8nQnAs8i4fCEo2Zf/3eS0IiRA8XnRg9Ha0YnpkyJZIZg1qEwemrHw==
-"@next/swc-darwin-arm64@14.2.11":
- version "14.2.11"
- resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.11.tgz#0022b52ccc62e21c34a34311ee0251e31cd5ff49"
- integrity sha512-eiY9u7wEJZWp/Pga07Qy3ZmNEfALmmSS1HtsJF3y1QEyaExu7boENz11fWqDmZ3uvcyAxCMhTrA1jfVxITQW8g==
-
-"@next/swc-darwin-x64@14.2.11":
- version "14.2.11"
- resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.11.tgz#41151d22b5009a2a83bb57e6d98efb8a8995a3cd"
- integrity sha512-lnB0zYCld4yE0IX3ANrVMmtAbziBb7MYekcmR6iE9bujmgERl6+FK+b0MBq0pl304lYe7zO4yxJus9H/Af8jbg==
-
-"@next/swc-linux-arm64-gnu@14.2.11":
- version "14.2.11"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.11.tgz#8fdc732c39b79dc7519bb561c48a2417e0eb6b24"
- integrity sha512-Ulo9TZVocYmUAtzvZ7FfldtwUoQY0+9z3BiXZCLSUwU2bp7GqHA7/bqrfsArDlUb2xeGwn3ZuBbKtNK8TR0A8w==
-
-"@next/swc-linux-arm64-musl@14.2.11":
- version "14.2.11"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.11.tgz#04c3730ca4bc2f0c56c73db53dd03cf00126a243"
- integrity sha512-fH377DnKGyUnkWlmUpFF1T90m0dADBfK11dF8sOQkiELF9M+YwDRCGe8ZyDzvQcUd20Rr5U7vpZRrAxKwd3Rzg==
-
-"@next/swc-linux-x64-gnu@14.2.11":
- version "14.2.11"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.11.tgz#533823c0a96d31122671f670b58da65bdb71f330"
- integrity sha512-a0TH4ZZp4NS0LgXP/488kgvWelNpwfgGTUCDXVhPGH6pInb7yIYNgM4kmNWOxBFt+TIuOH6Pi9NnGG4XWFUyXQ==
-
-"@next/swc-linux-x64-musl@14.2.11":
- version "14.2.11"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.11.tgz#fa81b56095ef4a64bae7f5798e6cdeac9de2906f"
- integrity sha512-DYYZcO4Uir2gZxA4D2JcOAKVs8ZxbOFYPpXSVIgeoQbREbeEHxysVsg3nY4FrQy51e5opxt5mOHl/LzIyZBoKA==
-
-"@next/swc-win32-arm64-msvc@14.2.11":
- version "14.2.11"
- resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.11.tgz#3ad4a72282f9b5e72f600522156e5bab6efb63a8"
- integrity sha512-PwqHeKG3/kKfPpM6of1B9UJ+Er6ySUy59PeFu0Un0LBzJTRKKAg2V6J60Yqzp99m55mLa+YTbU6xj61ImTv9mg==
-
-"@next/swc-win32-ia32-msvc@14.2.11":
- version "14.2.11"
- resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.11.tgz#80a6dd48e2f8035c518c2162bf6058bd61921a1b"
- integrity sha512-0U7PWMnOYIvM74GY6rbH6w7v+vNPDVH1gUhlwHpfInJnNe5LkmUZqhp7FNWeNa5wbVgRcRi1F1cyxp4dmeLLvA==
-
"@next/swc-win32-x64-msvc@14.2.11":
version "14.2.11"
resolved "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.11.tgz"
@@ -530,7 +478,7 @@
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
-"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
+"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
version "2.0.5"
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -598,7 +546,7 @@
"@react-spring/shared" "~9.7.4"
"@react-spring/types" "~9.7.4"
-"@reduxjs/toolkit@1.x.x || 2.x.x", "@reduxjs/toolkit@^2.2.7":
+"@reduxjs/toolkit@^2.2.7", "@reduxjs/toolkit@1.x.x || 2.x.x":
version "2.2.7"
resolved "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.7.tgz"
integrity sha512-faI3cZbSdFb8yv9dhDTmGwclW0vk0z5o1cia+kf7gCbaCwHI5e+7tP57mJUv22pNcNbeA62GSrPpfrUfdXcQ6g==
@@ -689,14 +637,9 @@
resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz"
integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==
-"@types/prop-types@*", "@types/prop-types@^15.7.12":
- version "15.7.13"
- resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz"
- integrity sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==
-
-"@types/prop-types@^15.7.15":
+"@types/prop-types@*", "@types/prop-types@^15.7.12", "@types/prop-types@^15.7.15":
version "15.7.15"
- resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7"
+ resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz"
integrity sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==
"@types/react-dom@^18":
@@ -881,16 +824,16 @@ color-convert@^2.0.1:
dependencies:
color-name "~1.1.4"
-color-name@1.1.3:
- version "1.1.3"
- resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
- integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
-
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+color-name@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
+ integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
+
commander@^4.0.0:
version "4.1.1"
resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz"
@@ -931,14 +874,14 @@ csstype@^3.0.2, csstype@^3.1.3:
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
-"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.1.6:
+d3-array@^3.1.6, "d3-array@2 - 3", "d3-array@2.10.0 - 3":
version "3.2.4"
resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz"
integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
dependencies:
internmap "1 - 2"
-"d3-color@1 - 3", d3-color@^3.1.0:
+d3-color@^3.1.0, "d3-color@1 - 3":
version "3.1.0"
resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz"
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
@@ -960,7 +903,7 @@ d3-ease@^3.0.1:
resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz"
integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
-"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1:
+d3-interpolate@^3.0.1, "d3-interpolate@1.2.0 - 3":
version "3.0.1"
resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz"
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
@@ -997,7 +940,7 @@ d3-shape@^3.1.0, d3-shape@^3.2.0:
dependencies:
d3-time "1 - 3"
-"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0, d3-time@^3.1.0:
+d3-time@^3.0.0, d3-time@^3.1.0, "d3-time@1 - 3", "d3-time@2.1.1 - 3":
version "3.1.0"
resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz"
integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
@@ -1026,7 +969,7 @@ decimal.js-light@^2.5.1:
resolved "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz"
integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==
-delaunator@5, delaunator@^5.0.1:
+delaunator@^5.0.1, delaunator@5:
version "5.0.1"
resolved "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz"
integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==
@@ -1131,11 +1074,6 @@ foreground-child@^3.1.0:
cross-spawn "^7.0.0"
signal-exit "^4.0.1"
-fsevents@~2.3.2:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
- integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
-
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
@@ -1196,6 +1134,20 @@ hoist-non-react-statics@^3.3.1:
dependencies:
react-is "^16.7.0"
+html-parse-stringify@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz"
+ integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
+ dependencies:
+ void-elements "3.1.0"
+
+i18next@^25.6.0:
+ version "25.6.0"
+ resolved "https://registry.npmjs.org/i18next/-/i18next-25.6.0.tgz"
+ integrity sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw==
+ dependencies:
+ "@babel/runtime" "^7.27.6"
+
immer@^10.0.3, immer@^10.1.1:
version "10.1.1"
resolved "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz"
@@ -1512,15 +1464,6 @@ postcss-value-parser@^4.0.0:
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
-postcss@8.4.31:
- version "8.4.31"
- resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz"
- integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
- dependencies:
- nanoid "^3.3.6"
- picocolors "^1.0.0"
- source-map-js "^1.0.2"
-
postcss@^8, postcss@^8.4.23:
version "8.4.47"
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz"
@@ -1530,6 +1473,15 @@ postcss@^8, postcss@^8.4.23:
picocolors "^1.1.0"
source-map-js "^1.2.1"
+postcss@8.4.31:
+ version "8.4.31"
+ resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz"
+ integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
+ dependencies:
+ nanoid "^3.3.6"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
+
prop-types@^15.6.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
@@ -1552,7 +1504,20 @@ react-dom@^18:
loose-envify "^1.1.0"
scheduler "^0.23.2"
-react-is@^16.13.1, react-is@^16.7.0:
+react-i18next@^16.0.1:
+ version "16.0.1"
+ resolved "https://registry.npmjs.org/react-i18next/-/react-i18next-16.0.1.tgz"
+ integrity sha512-0S//bpYEkCPjzuVmxDf9Z6+Y+ArNvpAUk7eDL4qNCZXjDh6Z9j6MZ+NThU7kMCOsmYmDCun3GYEwkiOjjZo9Ug==
+ dependencies:
+ "@babel/runtime" "^7.27.6"
+ html-parse-stringify "^3.0.1"
+
+react-is@^16.13.1:
+ version "16.13.1"
+ resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
+ integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+
+react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -1564,10 +1529,10 @@ react-is@^18.3.1:
react-is@^19.1.1:
version "19.2.0"
- resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.2.0.tgz#ddc3b4a4e0f3336c3847f18b806506388d7b9973"
+ resolved "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz"
integrity sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==
-"react-redux@8.x.x || 9.x.x", react-redux@^9.1.2:
+react-redux@^9.1.2, "react-redux@8.x.x || 9.x.x":
version "9.1.2"
resolved "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz"
integrity sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==
@@ -1646,12 +1611,7 @@ redux@^5.0.1:
resolved "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz"
integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==
-regenerator-runtime@^0.14.0:
- version "0.14.1"
- resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz"
- integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
-
-reselect@5.1.1, reselect@^5.1.0, reselect@^5.1.1:
+reselect@^5.1.0, reselect@^5.1.1, reselect@5.1.1:
version "5.1.1"
resolved "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz"
integrity sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==
@@ -1890,14 +1850,9 @@ undici-types@~6.19.2:
resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz"
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
-use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.2:
- version "1.2.2"
- resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz"
- integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==
-
-use-sync-external-store@^1.5.0:
+use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.2, use-sync-external-store@^1.6.0:
version "1.6.0"
- resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d"
+ resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz"
integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==
util-deprecate@^1.0.2:
@@ -1925,6 +1880,11 @@ victory-vendor@^37.0.2:
d3-time "^3.0.0"
d3-timer "^3.0.1"
+void-elements@3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz"
+ integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
+
which@^2.0.1:
version "2.0.2"
resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"