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]} /> ({name: e.range, value: e.count, label: `${e.range} HK`}))}/>} @@ -80,13 +82,13 @@ export default function AbsensiPage() { ) : (
- 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]} /> ({name: e.range, value: e.count, label: `${e.range} HK`}))}/>} + content={ ({name: e.range, value: Number(e.count).toLocaleString('id-ID'), label: `${e.range} HK`}))}/>} verticalAlign="bottom" height={36} iconType="circle" @@ -120,14 +122,14 @@ export default function AbsensiPage() { ) : (
- 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]} /> ({name: e.range, value: e.count, label: `${e.range} HK`}))}/>} @@ -163,13 +165,13 @@ export default function AbsensiPage() { ) : (
- 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]} /> ({name: e.range, value: e.count, label: `${e.range} HK`}))}/>} + content={ ({name: e.range, value: Number(e.count).toLocaleString('id-ID'), label: `${e.range} HK`}))}/>} verticalAlign="middle" align="right" layout="vertical" @@ -205,14 +207,14 @@ export default function AbsensiPage() { ) : (
- 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]} />
- Data belum tersedia + {t('common.dataNotAvailable')}
) : 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}))}> - Semua - Staff - Non Staff - Pemanen - Perawatan + dispatch(setFilter({...filter, job_name: e.target.value}))}> + {t('filters.all')} + {t('filters.staff')} + {t('filters.nonStaff')} + {t('filters.harvesters')} + {t('filters.maintenance')}
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}`; }} /> ) : (
- Data belum tersedia + {t('common.dataNotAvailable')}
)}
-
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}`} /> ( - {value}: {Math.round(Number(entry.payload?.value ?? 0)).toLocaleString('id-ID')} ton + {value}: {Math.round(Number(entry.payload?.value ?? 0)).toLocaleString('id-ID')} {t('common.ton')} )} /> @@ -383,66 +384,66 @@ export default function ProductivityPage() {
-
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]} /> ) : (
- Data belum tersedia + {t('common.dataNotAvailable')}
)}
-
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')}
midsuit
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"