translate
This commit is contained in:
parent
3878dc455f
commit
7ca52e1092
|
|
@ -1,2 +1,2 @@
|
||||||
# NEXT_PUBLIC_API_URL="http://localhost:8080"
|
NEXT_PUBLIC_API_URL="http://localhost:8080"
|
||||||
NEXT_PUBLIC_API_URL="https://erp.julongindonesia.com:8443/api"
|
# NEXT_PUBLIC_API_URL="https://erp.julongindonesia.com:8443/api"
|
||||||
|
|
@ -13,13 +13,16 @@
|
||||||
"@heroicons/react": "^2.1.5",
|
"@heroicons/react": "^2.1.5",
|
||||||
"@mui/material": "^6.1.0",
|
"@mui/material": "^6.1.0",
|
||||||
"@mui/x-charts": "^7.17.0",
|
"@mui/x-charts": "^7.17.0",
|
||||||
|
"@mui/x-data-grid": "^8.13.1",
|
||||||
"@mui/x-date-pickers": "^7.17.0",
|
"@mui/x-date-pickers": "^7.17.0",
|
||||||
"@reduxjs/toolkit": "^2.2.7",
|
"@reduxjs/toolkit": "^2.2.7",
|
||||||
"date-fns": "^4.0.0",
|
"date-fns": "^4.0.0",
|
||||||
|
"i18next": "^25.6.0",
|
||||||
"lucide-react": "^0.441.0",
|
"lucide-react": "^0.441.0",
|
||||||
"next": "14.2.11",
|
"next": "14.2.11",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-i18next": "^16.0.1",
|
||||||
"react-redux": "^9.1.2",
|
"react-redux": "^9.1.2",
|
||||||
"react-tooltip": "^5.28.0",
|
"react-tooltip": "^5.28.0",
|
||||||
"recharts": "^3.2.1",
|
"recharts": "^3.2.1",
|
||||||
|
|
@ -137,13 +140,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.25.6",
|
"version": "7.28.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||||
"integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==",
|
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"regenerator-runtime": "^0.14.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
|
|
@ -719,10 +719,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/types": {
|
"node_modules/@mui/types": {
|
||||||
"version": "7.2.16",
|
"version": "7.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.16.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.7.tgz",
|
||||||
"integrity": "sha512-qI8TV3M7ShITEEc8Ih15A2vLzZGLhD+/UPNwck/hcls2gwg7dyRjNGXcQYHKLB5Q7PuTRfrTkAoPa2VV1s67Ag==",
|
"integrity": "sha512-8vVje9rdEr1rY8oIkYgP+Su5Kwl6ik7O3jQ0wl78JGSmiZhRHV+vkjooGdKD8pbtZbutXFVTWQYshu2b3sG9zw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.28.4"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -820,6 +823,102 @@
|
||||||
"robust-predicates": "^3.0.2"
|
"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": {
|
"node_modules/@mui/x-date-pickers": {
|
||||||
"version": "7.17.0",
|
"version": "7.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.17.0.tgz",
|
"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"
|
"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": {
|
"node_modules/@next/env": {
|
||||||
"version": "14.2.11",
|
"version": "14.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.11.tgz",
|
||||||
"integrity": "sha512-HYsQRSIXwiNqvzzYThrBwq6RhXo3E0n8j8nQnAs8i4fCEo2Zf/3eS0IiRA8XnRg9Ha0YnpkyJZIZg1qEwemrHw==",
|
"integrity": "sha512-HYsQRSIXwiNqvzzYThrBwq6RhXo3E0n8j8nQnAs8i4fCEo2Zf/3eS0IiRA8XnRg9Ha0YnpkyJZIZg1qEwemrHw==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
"version": "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",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.11.tgz",
|
||||||
|
|
@ -1185,9 +1484,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.13",
|
"version": "15.7.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||||
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
|
|
@ -1985,6 +2284,46 @@
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/immer": {
|
||||||
"version": "10.1.1",
|
"version": "10.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||||
|
|
@ -2735,6 +3074,32 @@
|
||||||
"react": "^18.3.1"
|
"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": {
|
"node_modules/react-is": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
|
|
@ -2868,12 +3233,6 @@
|
||||||
"redux": "^5.0.0"
|
"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": {
|
"node_modules/reselect": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||||
|
|
@ -3328,12 +3687,12 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/use-sync-external-store": {
|
"node_modules/use-sync-external-store": {
|
||||||
"version": "1.2.2",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||||
"integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
|
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"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": {
|
"node_modules/util-deprecate": {
|
||||||
|
|
@ -3365,6 +3724,15 @@
|
||||||
"d3-timer": "^3.0.1"
|
"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": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|
@ -3487,126 +3855,6 @@
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14"
|
"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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,12 @@
|
||||||
"@mui/x-date-pickers": "^7.17.0",
|
"@mui/x-date-pickers": "^7.17.0",
|
||||||
"@reduxjs/toolkit": "^2.2.7",
|
"@reduxjs/toolkit": "^2.2.7",
|
||||||
"date-fns": "^4.0.0",
|
"date-fns": "^4.0.0",
|
||||||
|
"i18next": "^25.6.0",
|
||||||
"lucide-react": "^0.441.0",
|
"lucide-react": "^0.441.0",
|
||||||
"next": "14.2.11",
|
"next": "14.2.11",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-i18next": "^16.0.1",
|
||||||
"react-redux": "^9.1.2",
|
"react-redux": "^9.1.2",
|
||||||
"react-tooltip": "^5.28.0",
|
"react-tooltip": "^5.28.0",
|
||||||
"recharts": "^3.2.1",
|
"recharts": "^3.2.1",
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,10 @@ import { ComposedChart, Bar, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend,
|
||||||
import { formatDate } from "date-fns";
|
import { formatDate } from "date-fns";
|
||||||
import { PageLoader } from "@/components/Loader";
|
import { PageLoader } from "@/components/Loader";
|
||||||
import {CustomLegendHorizontal, CustomLegendVertical} from "@/components/CustomLegendPie";
|
import {CustomLegendHorizontal, CustomLegendVertical} from "@/components/CustomLegendPie";
|
||||||
|
import { useLocale } from "@/lib/hooks/useLocale";
|
||||||
|
|
||||||
export default function AbsensiPage() {
|
export default function AbsensiPage() {
|
||||||
|
const { t } = useLocale();
|
||||||
const filter = useAppSelector(state => state.filter.filter);
|
const filter = useAppSelector(state => state.filter.filter);
|
||||||
const {data: attendanceSummary, isLoading: isLoadingAttendanceSummary, isFetching: isFetchingAttendanceSummary} = useGetOrganizationAttendanceQuery(filter);
|
const {data: attendanceSummary, isLoading: isLoadingAttendanceSummary, isFetching: isFetchingAttendanceSummary} = useGetOrganizationAttendanceQuery(filter);
|
||||||
const {data: attendanceRangeStaff, isLoading: isLoadingAttendanceRangeStaff, isFetching: isFetchingAttendanceRangeStaff} = useGetAttendanceRangeQuery({
|
const {data: attendanceRangeStaff, isLoading: isLoadingAttendanceRangeStaff, isFetching: isFetchingAttendanceRangeStaff} = useGetAttendanceRangeQuery({
|
||||||
|
|
@ -38,7 +40,7 @@ export default function AbsensiPage() {
|
||||||
|
|
||||||
// Show loader while any data is loading
|
// Show loader while any data is loading
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <PageLoader text="Memuat data kehadiran..." />;
|
return <PageLoader text={t('attendance.loadingData')} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -46,7 +48,7 @@ export default function AbsensiPage() {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-12 gap-4">
|
<div className="grid grid-cols-12 gap-4">
|
||||||
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[640px] flex flex-col">
|
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[640px] flex flex-col">
|
||||||
<div className="text-xl font-bold">Kehadiran Staff</div>
|
<div className="text-xl font-bold">{t('attendance.kehadiranStaff')}</div>
|
||||||
<div className="flex-1 flex">
|
<div className="flex-1 flex">
|
||||||
{attendanceRangeStaff ? (
|
{attendanceRangeStaff ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -68,7 +70,7 @@ export default function AbsensiPage() {
|
||||||
})}
|
})}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => [`${value} employees`, name]}
|
formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('common.employees')}`, name]}
|
||||||
/>
|
/>
|
||||||
<Legend
|
<Legend
|
||||||
content={<CustomLegendHorizontal payload={attendanceRangeStaff.map(e => ({name: e.range, value: e.count, label: `${e.range} HK`}))}/>}
|
content={<CustomLegendHorizontal payload={attendanceRangeStaff.map(e => ({name: e.range, value: e.count, label: `${e.range} HK`}))}/>}
|
||||||
|
|
@ -80,13 +82,13 @@ export default function AbsensiPage() {
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex-1 flex items-center justify-center text-gray-500">
|
<div className="flex-1 flex items-center justify-center text-gray-500">
|
||||||
Data belum tersedia
|
{t('common.dataNotAvailable')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-4 bg-white rounded-lg py-4 px-4 max-h-[640px] flex flex-col">
|
<div className="col-span-4 bg-white rounded-lg py-4 px-4 max-h-[640px] flex flex-col">
|
||||||
<div className="text-xl font-bold">Kehadiran Non Staff</div>
|
<div className="text-xl font-bold">{t('attendance.kehadiranNonStaff')}</div>
|
||||||
<div className="flex-1 flex">
|
<div className="flex-1 flex">
|
||||||
{attendanceRangeNonStaff ? (
|
{attendanceRangeNonStaff ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -108,10 +110,10 @@ export default function AbsensiPage() {
|
||||||
})}
|
})}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => [`${value} employees`, name]}
|
formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('common.employees')}`, name]}
|
||||||
/>
|
/>
|
||||||
<Legend
|
<Legend
|
||||||
content={<CustomLegendHorizontal payload={attendanceRangeNonStaff.map(e => ({name: e.range, value: e.count, label: `${e.range} HK`}))}/>}
|
content={<CustomLegendHorizontal payload={attendanceRangeNonStaff.map(e => ({name: e.range, value: Number(e.count).toLocaleString('id-ID'), label: `${e.range} HK`}))}/>}
|
||||||
verticalAlign="bottom"
|
verticalAlign="bottom"
|
||||||
height={36}
|
height={36}
|
||||||
iconType="circle"
|
iconType="circle"
|
||||||
|
|
@ -120,14 +122,14 @@ export default function AbsensiPage() {
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex-1 flex items-center justify-center text-gray-500">
|
<div className="flex-1 flex items-center justify-center text-gray-500">
|
||||||
Data belum tersedia
|
{t('common.dataNotAvailable')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-4 max-h-[640px] flex flex-col gap-4">
|
<div className="col-span-4 max-h-[640px] flex flex-col gap-4">
|
||||||
<div className="bg-white rounded-lg py-4 px-4 flex flex-col flex-1 min-h-[300px]">
|
<div className="bg-white rounded-lg py-4 px-4 flex flex-col flex-1 min-h-[300px]">
|
||||||
<div className="text-xl font-bold">Kehadiran Pemanen</div>
|
<div className="text-xl font-bold">{t('attendance.kehadiranPemanen')}</div>
|
||||||
<div className="flex-1 flex min-h-[250px]">
|
<div className="flex-1 flex min-h-[250px]">
|
||||||
{attendanceRangeHarvester ? (
|
{attendanceRangeHarvester ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -149,7 +151,7 @@ export default function AbsensiPage() {
|
||||||
})}
|
})}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => [`${value} employees`, name]}
|
formatter={(value, name) => [`${value} ${t('common.employees')}`, name]}
|
||||||
/>
|
/>
|
||||||
<Legend
|
<Legend
|
||||||
content={<CustomLegendVertical payload={attendanceRangeHarvester.map(e => ({name: e.range, value: e.count, label: `${e.range} HK`}))}/>}
|
content={<CustomLegendVertical payload={attendanceRangeHarvester.map(e => ({name: e.range, value: e.count, label: `${e.range} HK`}))}/>}
|
||||||
|
|
@ -163,13 +165,13 @@ export default function AbsensiPage() {
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex-1 flex items-center justify-center text-gray-500">
|
<div className="flex-1 flex items-center justify-center text-gray-500">
|
||||||
Data belum tersedia
|
{t('common.dataNotAvailable')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white rounded-lg py-4 px-4 flex flex-col flex-1 min-h-[300px]">
|
<div className="bg-white rounded-lg py-4 px-4 flex flex-col flex-1 min-h-[300px]">
|
||||||
<div className="text-xl font-bold">Kehadiran Perawatan</div>
|
<div className="text-xl font-bold">{t('attendance.kehadiranPerawatan')}</div>
|
||||||
<div className="flex-1 flex min-h-[250px]">
|
<div className="flex-1 flex min-h-[250px]">
|
||||||
{attendanceRangeMaintenance ? (
|
{attendanceRangeMaintenance ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -191,10 +193,10 @@ export default function AbsensiPage() {
|
||||||
})}
|
})}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => [`${value} employees`, name]}
|
formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('common.employees')}`, name]}
|
||||||
/>
|
/>
|
||||||
<Legend
|
<Legend
|
||||||
content={<CustomLegendVertical payload={attendanceRangeMaintenance.map(e => ({name: e.range, value: e.count, label: `${e.range} HK`}))}/>}
|
content={<CustomLegendVertical payload={attendanceRangeMaintenance.map(e => ({name: e.range, value: Number(e.count).toLocaleString('id-ID'), label: `${e.range} HK`}))}/>}
|
||||||
verticalAlign="middle"
|
verticalAlign="middle"
|
||||||
align="right"
|
align="right"
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
|
|
@ -205,14 +207,14 @@ export default function AbsensiPage() {
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex-1 flex items-center justify-center text-gray-500">
|
<div className="flex-1 flex items-center justify-center text-gray-500">
|
||||||
Data belum tersedia
|
{t('common.dataNotAvailable')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-8 bg-white py-4 pl-4 rounded-lg max-h-[420px] flex flex-col">
|
<div className="col-span-8 bg-white py-4 pl-4 rounded-lg max-h-[420px] flex flex-col">
|
||||||
<div className="text-xl font-bold">Data Kehadiran Karyawan Setiap Perusahaan</div>
|
<div className="text-xl font-bold">{t('attendance.dataKehadiranKaryawan')}</div>
|
||||||
<div className="flex-1 min-h-[300px]">
|
<div className="flex-1 min-h-[300px]">
|
||||||
{attendanceSummary ? (
|
{attendanceSummary ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -237,10 +239,10 @@ export default function AbsensiPage() {
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name, props) => {
|
formatter={(value, name, props) => {
|
||||||
const orgData = attendanceSummary.find(e => e.organization_code === props.payload.organization_code);
|
const orgData = attendanceSummary.find(e => e.organization_code === props.payload.organization_code);
|
||||||
if (name === 'Hadir') {
|
if (name === t('attendance.hadir')) {
|
||||||
return [`${orgData?.count || 0} orang`, 'Hadir'];
|
return [`${Number(orgData?.count || 0).toLocaleString('id-ID')} ${t('common.hariKerja')}`, t('attendance.hadir')];
|
||||||
} else {
|
} else {
|
||||||
return [`${orgData?.absent || 0} orang`, 'Tidak Hadir'];
|
return [`${Number(orgData?.absent || 0).toLocaleString('id-ID')} ${t('common.hariKerja')}`, t('attendance.tidakHadir')];
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
labelFormatter={(label) => {
|
labelFormatter={(label) => {
|
||||||
|
|
@ -254,25 +256,25 @@ export default function AbsensiPage() {
|
||||||
dataKey="countPercent"
|
dataKey="countPercent"
|
||||||
stackId="attendance"
|
stackId="attendance"
|
||||||
fill="#2385DE"
|
fill="#2385DE"
|
||||||
name="Hadir"
|
name={t('attendance.hadir')}
|
||||||
/>
|
/>
|
||||||
<Bar
|
<Bar
|
||||||
dataKey="absentPercent"
|
dataKey="absentPercent"
|
||||||
stackId="attendance"
|
stackId="attendance"
|
||||||
fill="#F7B500"
|
fill="#F7B500"
|
||||||
name="Tidak Hadir"
|
name={t('attendance.tidakHadir')}
|
||||||
/>
|
/>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex-1 flex items-center justify-center text-gray-500">
|
<div className="flex-1 flex items-center justify-center text-gray-500">
|
||||||
Data belum tersedia
|
{t('common.dataNotAvailable')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-4 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
<div className="col-span-4 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
||||||
<div className="text-xl font-bold">Data Absensi Karyawan Perbulan</div>
|
<div className="text-xl font-bold">{t('attendance.dataAbsensiPerbulan')}</div>
|
||||||
<div className="flex-1 min-h-[300px]">
|
<div className="flex-1 min-h-[300px]">
|
||||||
{montlyAttendance && (
|
{montlyAttendance && (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -292,13 +294,13 @@ export default function AbsensiPage() {
|
||||||
<YAxis
|
<YAxis
|
||||||
yAxisId="left"
|
yAxisId="left"
|
||||||
tick={{ fontSize: 12 }}
|
tick={{ fontSize: 12 }}
|
||||||
label={{ value: 'Jumlah', angle: -90, position: 'insideLeft' }}
|
label={{ value: t('common.jumlah'), angle: -90, position: 'insideLeft' }}
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
yAxisId="right"
|
yAxisId="right"
|
||||||
orientation="right"
|
orientation="right"
|
||||||
tick={{ fontSize: 12 }}
|
tick={{ fontSize: 12 }}
|
||||||
label={{ value: 'Persentase (%)', angle: 90, position: 'insideRight' }}
|
label={{ value: t('attendance.persentase'), angle: 90, position: 'insideRight' }}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
labelFormatter={(value, payload) => {
|
labelFormatter={(value, payload) => {
|
||||||
|
|
@ -308,16 +310,16 @@ export default function AbsensiPage() {
|
||||||
return value;
|
return value;
|
||||||
}}
|
}}
|
||||||
formatter={(value, name) => {
|
formatter={(value, name) => {
|
||||||
if (name === 'Persentase (%)') {
|
if (name === t('attendance.persentase')) {
|
||||||
return [`${value}%`, name];
|
return [`${value}%`, name];
|
||||||
}
|
}
|
||||||
return [value, name];
|
return [value, name];
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Legend />
|
<Legend />
|
||||||
<Bar yAxisId="left" dataKey="count" fill="#F7CAA9" name="Kehadiran" />
|
<Bar yAxisId="left" dataKey="count" fill="#F7CAA9" name={t('attendance.kehadiran')} />
|
||||||
<Bar yAxisId="left" dataKey="workdays" fill="#2385DE" name="Mandays" />
|
<Bar yAxisId="left" dataKey="workdays" fill="#2385DE" name={t('attendance.mandays')} />
|
||||||
<Line yAxisId="right" type="monotone" dataKey="percentage" stroke="#10B981" strokeWidth={3} name="Persentase (%)" />
|
<Line yAxisId="right" type="monotone" dataKey="percentage" stroke="#10B981" strokeWidth={3} name={t('attendance.persentase')} />
|
||||||
</ComposedChart>
|
</ComposedChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useAppSelector } from "@/lib/hooks";
|
import { useAppSelector } from "@/lib/hooks";
|
||||||
|
import { useLocale } from "@/lib/hooks/useLocale";
|
||||||
import { useGetHrCostByJobQuery, useGetHrCostQuery, useGetHrCostByOrganizationQuery, useGetHrCostPerMonthQuery, useGetHrCostPerEmployeeQuery } from "@/services/api";
|
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 { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend, BarChart, Bar, XAxis, YAxis, CartesianGrid, LineChart, Line } from "recharts";
|
||||||
import { PageLoader } from "@/components/Loader";
|
import { PageLoader } from "@/components/Loader";
|
||||||
|
|
@ -12,6 +13,7 @@ import { ArrowsPointingOutIcon, ArrowsPointingInIcon, MagnifyingGlassIcon } from
|
||||||
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8', '#82CA9D'];
|
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8', '#82CA9D'];
|
||||||
|
|
||||||
export default function HrcostPage() {
|
export default function HrcostPage() {
|
||||||
|
const { t } = useLocale();
|
||||||
const filter = useAppSelector(state => state.filter.filter);
|
const filter = useAppSelector(state => state.filter.filter);
|
||||||
const { data: hrCostByJob, isLoading: isLoadingHrCostByJob, isFetching: isFetchingHrCostByJob } = useGetHrCostByJobQuery(filter);
|
const { data: hrCostByJob, isLoading: isLoadingHrCostByJob, isFetching: isFetchingHrCostByJob } = useGetHrCostByJobQuery(filter);
|
||||||
const { data: hrCost, isLoading: isLoadingHrCost, isFetching: isFetchingHrCost } = useGetHrCostQuery(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;
|
const isLoading = isLoadingHrCostByJob || isFetchingHrCostByJob || isLoadingHrCost || isFetchingHrCost || isLoadingHrCostByOrganization || isFetchingHrCostByOrganization || isLoadingHrCostPerMonth || isFetchingHrCostPerMonth || isLoadingHrCostPerEmployee || isFetchingHrCostPerEmployee;
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <PageLoader />;
|
return <PageLoader text={t('hrcost.loadingData')} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFullscreen) {
|
if (isFullscreen) {
|
||||||
|
|
@ -40,13 +42,13 @@ export default function HrcostPage() {
|
||||||
<div className="col-span-12">
|
<div className="col-span-12">
|
||||||
<div className="bg-white p-4 rounded-md shadow-md">
|
<div className="bg-white p-4 rounded-md shadow-md">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<div className="text-xl font-bold">Rincian Gaji Pemanen</div>
|
<div className="text-xl font-bold">{t('hrcost.rincianGajiPemanen')}</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="text-sm text-gray-500">Total: {hrCostPerEmployee?.length?.toLocaleString('id-ID') || 0} karyawan</div>
|
<div className="text-sm text-gray-500">{t('common.total')}: {hrCostPerEmployee?.length?.toLocaleString('id-ID') || 0} {t('common.karyawan')}</div>
|
||||||
<button
|
<button
|
||||||
className="p-2 border rounded hover:bg-gray-50"
|
className="p-2 border rounded hover:bg-gray-50"
|
||||||
onClick={() => setIsFullscreen(false)}
|
onClick={() => setIsFullscreen(false)}
|
||||||
title="Keluar Fullscreen"
|
title={t('hrcost.keluarFullscreen')}
|
||||||
>
|
>
|
||||||
<ArrowsPointingInIcon className="w-4 h-4" />
|
<ArrowsPointingInIcon className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -57,15 +59,15 @@ export default function HrcostPage() {
|
||||||
{/* Information Cards */}
|
{/* Information Cards */}
|
||||||
<div className="col-span-8 grid grid-cols-3 gap-4">
|
<div className="col-span-8 grid grid-cols-3 gap-4">
|
||||||
<div className="bg-blue-50 p-3 rounded-lg border">
|
<div className="bg-blue-50 p-3 rounded-lg border">
|
||||||
<div className="text-sm text-blue-600 font-medium">Grand Total Gaji</div>
|
<div className="text-sm text-blue-600 font-medium">{t('hrcost.grandTotalGaji')}</div>
|
||||||
<div className="text-lg font-bold text-blue-800">Rp. {grandTotal.toLocaleString('id-ID')}</div>
|
<div className="text-lg font-bold text-blue-800">Rp. {grandTotal.toLocaleString('id-ID')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-green-50 p-3 rounded-lg border">
|
<div className="bg-green-50 p-3 rounded-lg border">
|
||||||
<div className="text-sm text-green-600 font-medium">Rata-Rata Gaji</div>
|
<div className="text-sm text-green-600 font-medium">{t('hrcost.rataRataGaji')}</div>
|
||||||
<div className="text-lg font-bold text-green-800">Rp. {Math.round(avgSalary).toLocaleString('id-ID')}</div>
|
<div className="text-lg font-bold text-green-800">Rp. {Math.round(avgSalary).toLocaleString('id-ID')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-purple-50 p-3 rounded-lg border">
|
<div className="bg-purple-50 p-3 rounded-lg border">
|
||||||
<div className="text-sm text-purple-600 font-medium">Gaji Tertinggi</div>
|
<div className="text-sm text-purple-600 font-medium">{t('hrcost.gajiTertinggi')}</div>
|
||||||
<div className="text-lg font-bold text-purple-800">Rp. {maxSalary.toLocaleString('id-ID')}</div>
|
<div className="text-lg font-bold text-purple-800">Rp. {maxSalary.toLocaleString('id-ID')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -92,19 +94,19 @@ export default function HrcostPage() {
|
||||||
// Format data for HR Cost pie chart
|
// Format data for HR Cost pie chart
|
||||||
const hrCostPieData = hrCost ? [
|
const hrCostPieData = hrCost ? [
|
||||||
{
|
{
|
||||||
name: "Gaji",
|
name: t('hrcost.gaji'),
|
||||||
value: hrCost.total_salary,
|
value: hrCost.total_salary,
|
||||||
valueInMillions: hrCost.total_salary / 1000000000,
|
valueInMillions: hrCost.total_salary / 1000000000,
|
||||||
color: COLORS[0]
|
color: COLORS[0]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "BPJS TK",
|
name: t('hrcost.bpjsTk'),
|
||||||
value: hrCost.total_bpjs_tk,
|
value: hrCost.total_bpjs_tk,
|
||||||
valueInMillions: hrCost.total_bpjs_tk / 1000000000,
|
valueInMillions: hrCost.total_bpjs_tk / 1000000000,
|
||||||
color: COLORS[1]
|
color: COLORS[1]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "BPJS KS",
|
name: t('hrcost.bpjsKs'),
|
||||||
value: hrCost.total_bpjs_ks,
|
value: hrCost.total_bpjs_ks,
|
||||||
valueInMillions: hrCost.total_bpjs_ks / 1000000000,
|
valueInMillions: hrCost.total_bpjs_ks / 1000000000,
|
||||||
color: COLORS[2]
|
color: COLORS[2]
|
||||||
|
|
@ -165,7 +167,7 @@ export default function HrcostPage() {
|
||||||
<div className="grid grid-cols-12 gap-4">
|
<div className="grid grid-cols-12 gap-4">
|
||||||
<div className="col-span-8 flex flex-col" >
|
<div className="col-span-8 flex flex-col" >
|
||||||
<div className="bg-white p-4 rounded-md shadow-md flex-1">
|
<div className="bg-white p-4 rounded-md shadow-md flex-1">
|
||||||
<div className="text-xl font-bold mb-4">HR Cost</div>
|
<div className="text-xl font-bold mb-4">{t('hrcost.hrCost')}</div>
|
||||||
<ResponsiveContainer width="100%" height="90%">
|
<ResponsiveContainer width="100%" height="90%">
|
||||||
<PieChart>
|
<PieChart>
|
||||||
<Pie
|
<Pie
|
||||||
|
|
@ -198,7 +200,7 @@ export default function HrcostPage() {
|
||||||
<div className="col-span-4">
|
<div className="col-span-4">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="bg-white p-4 rounded-md shadow-md">
|
<div className="bg-white p-4 rounded-md shadow-md">
|
||||||
<div className="text-xl font-bold mb-4">Gaji Karyawan</div>
|
<div className="text-xl font-bold mb-4">{t('hrcost.gajiKaryawan')}</div>
|
||||||
<div className="h-48">
|
<div className="h-48">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<PieChart>
|
<PieChart>
|
||||||
|
|
@ -230,7 +232,7 @@ export default function HrcostPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white p-4 rounded-md shadow-md">
|
<div className="bg-white p-4 rounded-md shadow-md">
|
||||||
<div className="text-xl font-bold mb-4">Ranking Top 10 Total Gaji Perusahaan</div>
|
<div className="text-xl font-bold mb-4">{t('hrcost.rankingTop10')}</div>
|
||||||
<div className="h-48">
|
<div className="h-48">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<BarChart
|
<BarChart
|
||||||
|
|
@ -257,7 +259,7 @@ export default function HrcostPage() {
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value: number, name: string) => [
|
formatter={(value: number, name: string) => [
|
||||||
`${(value / 1000000000).toFixed(2)} M IDR`,
|
`${(value / 1000000000).toFixed(2)} M IDR`,
|
||||||
'Total Salary'
|
t('hrcost.totalSalary')
|
||||||
]}
|
]}
|
||||||
labelFormatter={(label: string) => {
|
labelFormatter={(label: string) => {
|
||||||
const org = organizationBarData.find(item => item.organization_code === label);
|
const org = organizationBarData.find(item => item.organization_code === label);
|
||||||
|
|
@ -274,13 +276,13 @@ export default function HrcostPage() {
|
||||||
<div className="col-span-8">
|
<div className="col-span-8">
|
||||||
<div className="bg-white p-4 rounded-md shadow-md flex-1 flex flex-col">
|
<div className="bg-white p-4 rounded-md shadow-md flex-1 flex flex-col">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<div className="text-xl font-bold">Rincian Gaji Pemanen</div>
|
<div className="text-xl font-bold">{t('hrcost.rincianGajiPemanen')}</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="text-sm text-gray-500">Total: {hrCostPerEmployee?.length?.toLocaleString('id-ID') || 0} karyawan</div>
|
<div className="text-sm text-gray-500">{t('common.total')}: {hrCostPerEmployee?.length?.toLocaleString('id-ID') || 0} {t('common.karyawan')}</div>
|
||||||
<button
|
<button
|
||||||
className="p-2 border rounded hover:bg-gray-50"
|
className="p-2 border rounded hover:bg-gray-50"
|
||||||
onClick={() => setIsFullscreen(true)}
|
onClick={() => setIsFullscreen(true)}
|
||||||
title="Fullscreen"
|
title={t('hrcost.fullscreen')}
|
||||||
>
|
>
|
||||||
<ArrowsPointingOutIcon className="w-4 h-4" />
|
<ArrowsPointingOutIcon className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -292,14 +294,14 @@ export default function HrcostPage() {
|
||||||
{/* Information Cards */}
|
{/* Information Cards */}
|
||||||
<div className="col-span-8 grid grid-cols-3 gap-4">
|
<div className="col-span-8 grid grid-cols-3 gap-4">
|
||||||
<div className="bg-blue-50 p-3 rounded-lg border">
|
<div className="bg-blue-50 p-3 rounded-lg border">
|
||||||
<div className="text-sm text-blue-600 font-medium">Grand Total Gaji</div>
|
<div className="text-sm text-blue-600 font-medium">{t('hrcost.grandTotalGaji')}</div>
|
||||||
<div className="text-lg font-bold text-blue-800">Rp. {(() => {
|
<div className="text-lg font-bold text-blue-800">Rp. {(() => {
|
||||||
const salaries = hrCostPerEmployee?.map(emp => emp.salary) || [];
|
const salaries = hrCostPerEmployee?.map(emp => emp.salary) || [];
|
||||||
return salaries.reduce((sum, salary) => sum + salary, 0).toLocaleString('id-ID');
|
return salaries.reduce((sum, salary) => sum + salary, 0).toLocaleString('id-ID');
|
||||||
})()}</div>
|
})()}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-green-50 p-3 rounded-lg border">
|
<div className="bg-green-50 p-3 rounded-lg border">
|
||||||
<div className="text-sm text-green-600 font-medium">Rata-Rata Gaji</div>
|
<div className="text-sm text-green-600 font-medium">{t('hrcost.rataRataGaji')}</div>
|
||||||
<div className="text-lg font-bold text-green-800">Rp. {(() => {
|
<div className="text-lg font-bold text-green-800">Rp. {(() => {
|
||||||
const salaries = hrCostPerEmployee?.map(emp => emp.salary) || [];
|
const salaries = hrCostPerEmployee?.map(emp => emp.salary) || [];
|
||||||
const avg = salaries.length > 0 ? salaries.reduce((sum, salary) => sum + salary, 0) / salaries.length : 0;
|
const avg = salaries.length > 0 ? salaries.reduce((sum, salary) => sum + salary, 0) / salaries.length : 0;
|
||||||
|
|
@ -307,7 +309,7 @@ export default function HrcostPage() {
|
||||||
})()}</div>
|
})()}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-purple-50 p-3 rounded-lg border">
|
<div className="bg-purple-50 p-3 rounded-lg border">
|
||||||
<div className="text-sm text-purple-600 font-medium">Gaji Tertinggi</div>
|
<div className="text-sm text-purple-600 font-medium">{t('hrcost.gajiTertinggi')}</div>
|
||||||
<div className="text-lg font-bold text-purple-800">Rp. {(() => {
|
<div className="text-lg font-bold text-purple-800">Rp. {(() => {
|
||||||
const salaries = hrCostPerEmployee?.map(emp => emp.salary) || [];
|
const salaries = hrCostPerEmployee?.map(emp => emp.salary) || [];
|
||||||
const max = salaries.length > 0 ? Math.max(...salaries) : 0;
|
const max = salaries.length > 0 ? Math.max(...salaries) : 0;
|
||||||
|
|
@ -325,7 +327,7 @@ export default function HrcostPage() {
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-4">
|
<div className="col-span-4">
|
||||||
<div className="bg-white p-4 rounded-md shadow-md flex-1">
|
<div className="bg-white p-4 rounded-md shadow-md flex-1">
|
||||||
<div className="text-xl font-bold mb-4">Total Gaji Setiap Bulan</div>
|
<div className="text-xl font-bold mb-4">{t('hrcost.totalGajiSetiapBulan')}</div>
|
||||||
<div className="h-96">
|
<div className="h-96">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<LineChart
|
<LineChart
|
||||||
|
|
@ -352,7 +354,7 @@ export default function HrcostPage() {
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value: number, name: string) => [
|
formatter={(value: number, name: string) => [
|
||||||
`${(value / 1000000000).toFixed(2)} M IDR`,
|
`${(value / 1000000000).toFixed(2)} M IDR`,
|
||||||
'Total Salary'
|
t('hrcost.totalSalary')
|
||||||
]}
|
]}
|
||||||
labelFormatter={(label: string) => {
|
labelFormatter={(label: string) => {
|
||||||
const month = monthlyLineData.find(item => item.month_name === label);
|
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 }) {
|
function EmployeeDataGrid({ data, isFullscreen = false }: { data: { id_karyawan: string; name: string; address: string; salary: number; }[], isFullscreen?: boolean }) {
|
||||||
|
const { t } = useLocale();
|
||||||
const columns: GridColDef[] = [
|
const columns: GridColDef[] = [
|
||||||
{ field: 'id_karyawan', headerName: 'ID Karyawan', flex: 1, minWidth: 150 },
|
{ field: 'id_karyawan', headerName: t('hrcost.idKaryawan'), flex: 1, minWidth: 150 },
|
||||||
{ field: 'name', headerName: 'Nama', flex: 1, minWidth: 180 },
|
{ field: 'name', headerName: t('hrcost.nama'), flex: 1, minWidth: 180 },
|
||||||
{ field: 'address', headerName: 'Alamat', flex: 1, minWidth: 220 },
|
{ field: 'address', headerName: t('hrcost.alamat'), flex: 1, minWidth: 220 },
|
||||||
{
|
{
|
||||||
field: 'salary',
|
field: 'salary',
|
||||||
headerName: 'Gaji (IDR)',
|
headerName: t('hrcost.gajiIdr'),
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@ import { BarChart, Bar, LineChart, Line, PieChart, Pie, Cell, XAxis, YAxis, Cart
|
||||||
import { formatDate } from "date-fns";
|
import { formatDate } from "date-fns";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { PageLoader } from "@/components/Loader";
|
import { PageLoader } from "@/components/Loader";
|
||||||
|
import { useLocale } from "@/lib/hooks/useLocale";
|
||||||
|
|
||||||
export default function KaryawanPage() {
|
export default function KaryawanPage() {
|
||||||
|
const { t } = useLocale();
|
||||||
const filter = useAppSelector(state => state.filter.filter);
|
const filter = useAppSelector(state => state.filter.filter);
|
||||||
const {data: employeeSummary, isFetching : loadingSummary} = useGetEmployeeSummaryQuery(filter);
|
const {data: employeeSummary, isFetching : loadingSummary} = useGetEmployeeSummaryQuery(filter);
|
||||||
const {data: montlyEmployee, isFetching : loadingMonthly} = useGetMonthlyEmployeeQuery(filter);
|
const {data: montlyEmployee, isFetching : loadingMonthly} = useGetMonthlyEmployeeQuery(filter);
|
||||||
|
|
@ -95,13 +96,13 @@ export default function KaryawanPage() {
|
||||||
|
|
||||||
// Show loader while any data is loading
|
// Show loader while any data is loading
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <PageLoader text="Memuat data karyawan..." />;
|
return <PageLoader text={t('employee.loadingData')} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-12 gap-4">
|
<div className="grid grid-cols-12 gap-4">
|
||||||
<div className="col-span-8 bg-white py-4 pl-4 rounded-lg max-h-[420px] flex flex-col">
|
<div className="col-span-8 bg-white py-4 pl-4 rounded-lg max-h-[420px] flex flex-col">
|
||||||
<div className="text-xl font-bold">Data Karyawan</div>
|
<div className="text-xl font-bold">{t('employee.dataKaryawan')}</div>
|
||||||
<div className="flex-1 min-h-[300px] relative">
|
<div className="flex-1 min-h-[300px] relative">
|
||||||
{employeeSummary && employeeSummary.length > 0 ? (
|
{employeeSummary && employeeSummary.length > 0 ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -113,7 +114,7 @@ export default function KaryawanPage() {
|
||||||
/>
|
/>
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => [`${value} karyawan`, "Jumlah Karyawan"]}
|
formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('employee.karyawan')}`, t('employee.jumlahKaryawan')]}
|
||||||
labelFormatter={(label) => {
|
labelFormatter={(label) => {
|
||||||
const org = employeeSummary.find(e => e.organization_code === label);
|
const org = employeeSummary.find(e => e.organization_code === label);
|
||||||
return org?.organization_name || label;
|
return org?.organization_name || label;
|
||||||
|
|
@ -125,14 +126,14 @@ export default function KaryawanPage() {
|
||||||
) : !loadingSummary ? (
|
) : !loadingSummary ? (
|
||||||
<div className="absolute inset-0 flex justify-center items-center">
|
<div className="absolute inset-0 flex justify-center items-center">
|
||||||
<div className="text-gray-400 flex flex-col items-center">
|
<div className="text-gray-400 flex flex-col items-center">
|
||||||
<span className="text-gray-600">Data belum tersedia</span>
|
<span className="text-gray-600">{t('common.dataNotAvailable')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-4 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
<div className="col-span-4 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
||||||
<div className="text-xl font-bold">Data Karyawan Perbulan</div>
|
<div className="text-xl font-bold">{t('employee.dataKaryawanPerbulan')}</div>
|
||||||
<div className="flex-1 min-h-[300px] relative">
|
<div className="flex-1 min-h-[300px] relative">
|
||||||
{montlyEmployee && montlyEmployee.length > 0 ? (
|
{montlyEmployee && montlyEmployee.length > 0 ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -145,7 +146,7 @@ export default function KaryawanPage() {
|
||||||
/>
|
/>
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => [`${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")}
|
labelFormatter={(label) => formatDate(new Date(label), "MMMM yyyy")}
|
||||||
/>
|
/>
|
||||||
<Area
|
<Area
|
||||||
|
|
@ -160,14 +161,14 @@ export default function KaryawanPage() {
|
||||||
) : !loadingMonthly ? (
|
) : !loadingMonthly ? (
|
||||||
<div className="absolute inset-0 flex justify-center items-center">
|
<div className="absolute inset-0 flex justify-center items-center">
|
||||||
<div className="text-gray-400 flex flex-col items-center">
|
<div className="text-gray-400 flex flex-col items-center">
|
||||||
<span className="text-gray-600">Data belum tersedia</span>
|
<span className="text-gray-600">{t('common.dataNotAvailable')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-4 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
<div className="col-span-4 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
||||||
<div className="text-xl font-bold">Pergerakan Karyawan</div>
|
<div className="text-xl font-bold">{t('employee.pergerakanKaryawan')}</div>
|
||||||
<div className="flex-1 min-h-[300px] relative">
|
<div className="flex-1 min-h-[300px] relative">
|
||||||
{movementSummary && movementSummary.length > 0 ? (
|
{movementSummary && movementSummary.length > 0 ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -188,7 +189,7 @@ export default function KaryawanPage() {
|
||||||
return <Cell key={`cell-${index}`} fill={colors[index % colors.length]} />;
|
return <Cell key={`cell-${index}`} fill={colors[index % colors.length]} />;
|
||||||
})}
|
})}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip formatter={(value, name) => [`${value} karyawan`, name]} />
|
<Tooltip formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('employee.karyawan')}`, name]} />
|
||||||
<Legend
|
<Legend
|
||||||
verticalAlign="middle"
|
verticalAlign="middle"
|
||||||
align="right"
|
align="right"
|
||||||
|
|
@ -200,14 +201,14 @@ export default function KaryawanPage() {
|
||||||
) : !loadingMovement ? (
|
) : !loadingMovement ? (
|
||||||
<div className="absolute inset-0 flex justify-center items-center">
|
<div className="absolute inset-0 flex justify-center items-center">
|
||||||
<div className="text-gray-400 flex flex-col items-center">
|
<div className="text-gray-400 flex flex-col items-center">
|
||||||
<span className="text-gray-600">Data belum tersedia</span>
|
<span className="text-gray-600">{t('common.dataNotAvailable')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-8 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
<div className="col-span-8 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
||||||
<div className="text-xl font-bold">Ranking (Top 10) Total Pergerakan Karyawan Setiap Perusahaan</div>
|
<div className="text-xl font-bold">{t('employee.rankingPergerakanKaryawan')}</div>
|
||||||
<div className="flex-1 min-h-[300px] relative">
|
<div className="flex-1 min-h-[300px] relative">
|
||||||
{topMovementCompanies && topMovementCompanies.length > 0 ? (
|
{topMovementCompanies && topMovementCompanies.length > 0 ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -217,33 +218,33 @@ export default function KaryawanPage() {
|
||||||
dataKey="organization_code"
|
dataKey="organization_code"
|
||||||
tick={{ fontSize: 12 }}
|
tick={{ fontSize: 12 }}
|
||||||
/>
|
/>
|
||||||
<YAxis label={{ value: 'Jumlah Pergerakan', angle: -90, position: 'insideLeft' }} />
|
<YAxis label={{ value: t('employee.jumlahPergerakan'), angle: -90, position: 'insideLeft' }} />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => [value, name]}
|
formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('employee.karyawan')}`, name]}
|
||||||
labelFormatter={(label) => {
|
labelFormatter={(label) => {
|
||||||
const org = topMovementCompanies.find(e => e.organization_code === label);
|
const org = topMovementCompanies.find(e => e.organization_code === label);
|
||||||
return org?.organization_name || label;
|
return org?.organization_name || label;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Legend />
|
<Legend />
|
||||||
<Bar dataKey="recruitment" fill="#2385DE" name="Recruitment" />
|
<Bar dataKey="recruitment" fill="#2385DE" name={t('employee.recruitment')} />
|
||||||
<Bar dataKey="resignation" fill="#F7B500" name="Resignation" />
|
<Bar dataKey="resignation" fill="#F7B500" name={t('employee.resignation')} />
|
||||||
<Bar dataKey="promotion" fill="#69C9C9" name="Promotion" />
|
<Bar dataKey="promotion" fill="#69C9C9" name={t('employee.promotion')} />
|
||||||
<Bar dataKey="mutation" fill="#8A5FB1" name="Mutation" />
|
<Bar dataKey="mutation" fill="#8A5FB1" name={t('employee.mutation')} />
|
||||||
<Bar dataKey="demotion" fill="#E65550" name="Demotion" />
|
<Bar dataKey="demotion" fill="#E65550" name={t('employee.demotion')} />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : !loadingMovement ? (
|
) : !loadingMovement ? (
|
||||||
<div className="absolute inset-0 flex justify-center items-center">
|
<div className="absolute inset-0 flex justify-center items-center">
|
||||||
<div className="text-gray-400 flex flex-col items-center">
|
<div className="text-gray-400 flex flex-col items-center">
|
||||||
<span className="text-gray-600">Data belum tersedia</span>
|
<span className="text-gray-600">{t('common.dataNotAvailable')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-4 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
<div className="col-span-4 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
||||||
<div className="text-xl font-bold">Penjatuhan Sanksi</div>
|
<div className="text-xl font-bold">{t('employee.penjatuhanSanksi')}</div>
|
||||||
<div className="flex-1 min-h-[300px] flex justify-center items-center flex-col">
|
<div className="flex-1 min-h-[300px] flex justify-center items-center flex-col">
|
||||||
{totalSanctions && totalSanctions.length > 0 ? (
|
{totalSanctions && totalSanctions.length > 0 ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -256,7 +257,7 @@ export default function KaryawanPage() {
|
||||||
/>
|
/>
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => [value, "Total"]}
|
formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('common.total')}`, name]}
|
||||||
labelFormatter={(label) => label.toUpperCase()}
|
labelFormatter={(label) => label.toUpperCase()}
|
||||||
/>
|
/>
|
||||||
<Bar dataKey="count" fill="#F5C322" />
|
<Bar dataKey="count" fill="#F5C322" />
|
||||||
|
|
@ -264,13 +265,13 @@ export default function KaryawanPage() {
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-gray-400 flex flex-col items-center">
|
<div className="text-gray-400 flex flex-col items-center">
|
||||||
<span className="text-gray-600">Data belum tersedia</span>
|
<span className="text-gray-600">{t('common.dataNotAvailable')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-8 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
<div className="col-span-8 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
||||||
<div className="text-xl font-bold">Ranking (Top 10) Total Penjatuhan Sanksi Setiap Perusahaan</div>
|
<div className="text-xl font-bold">{t('employee.rankingSanksi')}</div>
|
||||||
<div className="flex-1 min-h-[300px] flex justify-center items-center flex-col">
|
<div className="flex-1 min-h-[300px] flex justify-center items-center flex-col">
|
||||||
{sanctionSummary && sanctionSummary.length > 0 ? (
|
{sanctionSummary && sanctionSummary.length > 0 ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -282,7 +283,7 @@ export default function KaryawanPage() {
|
||||||
/>
|
/>
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => [value, name]}
|
formatter={(value, name) => [`${Number(value).toLocaleString('id-ID')} ${t('common.total')}`, name]}
|
||||||
labelFormatter={(label) => {
|
labelFormatter={(label) => {
|
||||||
const org = sanctionSummary?.find(e => e.organization_code === label);
|
const org = sanctionSummary?.find(e => e.organization_code === label);
|
||||||
return org?.organization_name || label;
|
return org?.organization_name || label;
|
||||||
|
|
@ -297,7 +298,7 @@ export default function KaryawanPage() {
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : !loadingSanction ? (
|
) : !loadingSanction ? (
|
||||||
<div className="text-gray-400 flex flex-col items-center">
|
<div className="text-gray-400 flex flex-col items-center">
|
||||||
<span className="text-gray-600">Data belum tersedia</span>
|
<span className="text-gray-600">{t('common.dataNotAvailable')}</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import { setFilter } from "@/lib/slice/filter";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { useGetFilterOptionsQuery } from "@/services/api";
|
import { useGetFilterOptionsQuery } from "@/services/api";
|
||||||
import { NeedLoginRoute } from "../RouteGuard";
|
import { NeedLoginRoute } from "../RouteGuard";
|
||||||
|
import { useLocale } from "@/lib/hooks/useLocale";
|
||||||
|
import { LanguageSwitcher } from "@/components/LanguageSwitcher";
|
||||||
|
|
||||||
|
|
||||||
export default function DashboardLayout({children}:{children: React.ReactNode}) {
|
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 filter = useAppSelector((state) => state.filter.filter);
|
||||||
const {data: filterOptions } = useGetFilterOptionsQuery(filter);
|
const {data: filterOptions } = useGetFilterOptionsQuery(filter);
|
||||||
const [region, setRegion] = React.useState("");
|
const [region, setRegion] = React.useState("");
|
||||||
|
const { t } = useLocale();
|
||||||
|
|
||||||
// Helper function to validate date range
|
// Helper function to validate date range
|
||||||
const isValidDateRange = (startDate: string, endDate: string) => {
|
const isValidDateRange = (startDate: string, endDate: string) => {
|
||||||
|
|
@ -67,42 +70,42 @@ export default function DashboardLayout({children}:{children: React.ReactNode})
|
||||||
</div>
|
</div>
|
||||||
<div className="h-4 bg-white"/>
|
<div className="h-4 bg-white"/>
|
||||||
<div className="flex flex-col flex-1 from-[#A36A4D] bg-gradient-to-b to-[#5B2D14] text-white py-8">
|
<div className="flex flex-col flex-1 from-[#A36A4D] bg-gradient-to-b to-[#5B2D14] text-white py-8">
|
||||||
<div className="text-[20px] text-white ml-8 mb-4">Halaman</div>
|
<div className="text-[20px] text-white ml-8 mb-4">{t('navigation.pages')}</div>
|
||||||
<Link className={`flex gap-4 px-8 py-4 ${pathname === '/karyawan' ? `bg-white text-[#664228]`:`text-white hover:bg-white hover:text-[#664228]`} items-center`} href="/karyawan">
|
<Link className={`flex gap-4 px-8 py-4 ${pathname === '/karyawan' ? `bg-white text-[#664228]`:`text-white hover:bg-white hover:text-[#664228]`} items-center`} href="/karyawan">
|
||||||
<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
||||||
<path d="M2 16H5V10H11V16H14V7L8 2.5L2 7V16ZM0 18V6L8 0L16 6V18H9V12H7V18H0Z" />
|
<path d="M2 16H5V10H11V16H14V7L8 2.5L2 7V16ZM0 18V6L8 0L16 6V18H9V12H7V18H0Z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span className="font-semibold text-base">Data Karyawan</span>
|
<span className="font-semibold text-base">{t('navigation.dataKaryawan')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
<Link className={`flex gap-4 px-8 py-4 ${pathname === '/absensi' ? `bg-white text-[#664228]`:`text-white hover:bg-white hover:text-[#664228]`} items-center`} href="/absensi">
|
<Link className={`flex gap-4 px-8 py-4 ${pathname === '/absensi' ? `bg-white text-[#664228]`:`text-white hover:bg-white hover:text-[#664228]`} items-center`} href="/absensi">
|
||||||
<svg width="23" height="16" viewBox="0 0 23 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
<svg width="23" height="16" viewBox="0 0 23 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
||||||
<path d="M16.55 8L13 4.45L14.425 3.05L16.55 5.175L20.8 0.925L22.2 2.35L16.55 8ZM8 8C6.9 8 5.95833 7.60833 5.175 6.825C4.39167 6.04167 4 5.1 4 4C4 2.9 4.39167 1.95833 5.175 1.175C5.95833 0.391667 6.9 0 8 0C9.1 0 10.0417 0.391667 10.825 1.175C11.6083 1.95833 12 2.9 12 4C12 5.1 11.6083 6.04167 10.825 6.825C10.0417 7.60833 9.1 8 8 8ZM0 16V13.2C0 12.6333 0.145833 12.1125 0.4375 11.6375C0.729167 11.1625 1.11667 10.8 1.6 10.55C2.63333 10.0333 3.68333 9.64583 4.75 9.3875C5.81667 9.12917 6.9 9 8 9C9.1 9 10.1833 9.12917 11.25 9.3875C12.3167 9.64583 13.3667 10.0333 14.4 10.55C14.8833 10.8 15.2708 11.1625 15.5625 11.6375C15.8542 12.1125 16 12.6333 16 13.2V16H0ZM2 14H14V13.2C14 13.0167 13.9542 12.85 13.8625 12.7C13.7708 12.55 13.65 12.4333 13.5 12.35C12.6 11.9 11.6917 11.5625 10.775 11.3375C9.85833 11.1125 8.93333 11 8 11C7.06667 11 6.14167 11.1125 5.225 11.3375C4.30833 11.5625 3.4 11.9 2.5 12.35C2.35 12.4333 2.22917 12.55 2.1375 12.7C2.04583 12.85 2 13.0167 2 13.2V14ZM8 6C8.55 6 9.02083 5.80417 9.4125 5.4125C9.80417 5.02083 10 4.55 10 4C10 3.45 9.80417 2.97917 9.4125 2.5875C9.02083 2.19583 8.55 2 8 2C7.45 2 6.97917 2.19583 6.5875 2.5875C6.19583 2.97917 6 3.45 6 4C6 4.55 6.19583 5.02083 6.5875 5.4125C6.97917 5.80417 7.45 6 8 6Z"/>
|
<path d="M16.55 8L13 4.45L14.425 3.05L16.55 5.175L20.8 0.925L22.2 2.35L16.55 8ZM8 8C6.9 8 5.95833 7.60833 5.175 6.825C4.39167 6.04167 4 5.1 4 4C4 2.9 4.39167 1.95833 5.175 1.175C5.95833 0.391667 6.9 0 8 0C9.1 0 10.0417 0.391667 10.825 1.175C11.6083 1.95833 12 2.9 12 4C12 5.1 11.6083 6.04167 10.825 6.825C10.0417 7.60833 9.1 8 8 8ZM0 16V13.2C0 12.6333 0.145833 12.1125 0.4375 11.6375C0.729167 11.1625 1.11667 10.8 1.6 10.55C2.63333 10.0333 3.68333 9.64583 4.75 9.3875C5.81667 9.12917 6.9 9 8 9C9.1 9 10.1833 9.12917 11.25 9.3875C12.3167 9.64583 13.3667 10.0333 14.4 10.55C14.8833 10.8 15.2708 11.1625 15.5625 11.6375C15.8542 12.1125 16 12.6333 16 13.2V16H0ZM2 14H14V13.2C14 13.0167 13.9542 12.85 13.8625 12.7C13.7708 12.55 13.65 12.4333 13.5 12.35C12.6 11.9 11.6917 11.5625 10.775 11.3375C9.85833 11.1125 8.93333 11 8 11C7.06667 11 6.14167 11.1125 5.225 11.3375C4.30833 11.5625 3.4 11.9 2.5 12.35C2.35 12.4333 2.22917 12.55 2.1375 12.7C2.04583 12.85 2 13.0167 2 13.2V14ZM8 6C8.55 6 9.02083 5.80417 9.4125 5.4125C9.80417 5.02083 10 4.55 10 4C10 3.45 9.80417 2.97917 9.4125 2.5875C9.02083 2.19583 8.55 2 8 2C7.45 2 6.97917 2.19583 6.5875 2.5875C6.19583 2.97917 6 3.45 6 4C6 4.55 6.19583 5.02083 6.5875 5.4125C6.97917 5.80417 7.45 6 8 6Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span className="font-semibold text-base">Absensi</span>
|
<span className="font-semibold text-base">{t('navigation.absensi')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
<Link className={`flex gap-4 px-8 py-4 ${pathname === '/turnover' ? `bg-white text-[#664228]`:`text-white hover:bg-white hover:text-[#664228]`} items-center`} href="/turnover">
|
<Link className={`flex gap-4 px-8 py-4 ${pathname === '/turnover' ? `bg-white text-[#664228]`:`text-white hover:bg-white hover:text-[#664228]`} items-center`} href="/turnover">
|
||||||
<svg width="22" height="18" viewBox="0 0 22 18" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
<svg width="22" height="18" viewBox="0 0 22 18" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
||||||
<path d="M7.75 15V11.5H6.25V7.9C6.25 7.51667 6.5125 7.1875 7.0375 6.9125C7.5625 6.6375 8.21667 6.5 9 6.5C9.78333 6.5 10.4375 6.6375 10.9625 6.9125C11.4875 7.1875 11.75 7.51667 11.75 7.9V11.5H10.25V15H7.75ZM9 6C8.56667 6 8.20833 5.85833 7.925 5.575C7.64167 5.29167 7.5 4.93333 7.5 4.5C7.5 4.06667 7.64167 3.70833 7.925 3.425C8.20833 3.14167 8.56667 3 9 3C9.43333 3 9.79167 3.14167 10.075 3.425C10.3583 3.70833 10.5 4.06667 10.5 4.5C10.5 4.93333 10.3583 5.29167 10.075 5.575C9.79167 5.85833 9.43333 6 9 6ZM9 18C6.5 18 4.375 17.125 2.625 15.375C0.875 13.625 0 11.5 0 9C0 7.75 0.2375 6.57917 0.7125 5.4875C1.1875 4.39583 1.82917 3.44583 2.6375 2.6375C3.44583 1.82917 4.39583 1.1875 5.4875 0.7125C6.57917 0.2375 7.75 0 9 0C10.25 0 11.4208 0.2375 12.5125 0.7125C13.6042 1.1875 14.5542 1.82917 15.3625 2.6375C16.1708 3.44583 16.8125 4.39583 17.2875 5.4875C17.7625 6.57917 18 7.75 18 9V9.2L19.825 7.35L21.25 8.75L17 13L12.75 8.75L14.175 7.35L16 9.175V9C16 7.06667 15.3167 5.41667 13.95 4.05C12.5833 2.68333 10.9333 2 9 2C7.06667 2 5.41667 2.68333 4.05 4.05C2.68333 5.41667 2 7.06667 2 9C2.01667 10.9333 2.70417 12.5833 4.0625 13.95C5.42083 15.3167 7.06667 16 9 16C9.95 16 10.8417 15.8208 11.675 15.4625C12.5083 15.1042 13.2417 14.6167 13.875 14L15.3 15.425C14.4833 16.225 13.5375 16.8542 12.4625 17.3125C11.3875 17.7708 10.2333 18 9 18Z" />
|
<path d="M7.75 15V11.5H6.25V7.9C6.25 7.51667 6.5125 7.1875 7.0375 6.9125C7.5625 6.6375 8.21667 6.5 9 6.5C9.78333 6.5 10.4375 6.6375 10.9625 6.9125C11.4875 7.1875 11.75 7.51667 11.75 7.9V11.5H10.25V15H7.75ZM9 6C8.56667 6 8.20833 5.85833 7.925 5.575C7.64167 5.29167 7.5 4.93333 7.5 4.5C7.5 4.06667 7.64167 3.70833 7.925 3.425C8.20833 3.14167 8.56667 3 9 3C9.43333 3 9.79167 3.14167 10.075 3.425C10.3583 3.70833 10.5 4.06667 10.5 4.5C10.5 4.93333 10.3583 5.29167 10.075 5.575C9.79167 5.85833 9.43333 6 9 6ZM9 18C6.5 18 4.375 17.125 2.625 15.375C0.875 13.625 0 11.5 0 9C0 7.75 0.2375 6.57917 0.7125 5.4875C1.1875 4.39583 1.82917 3.44583 2.6375 2.6375C3.44583 1.82917 4.39583 1.1875 5.4875 0.7125C6.57917 0.2375 7.75 0 9 0C10.25 0 11.4208 0.2375 12.5125 0.7125C13.6042 1.1875 14.5542 1.82917 15.3625 2.6375C16.1708 3.44583 16.8125 4.39583 17.2875 5.4875C17.7625 6.57917 18 7.75 18 9V9.2L19.825 7.35L21.25 8.75L17 13L12.75 8.75L14.175 7.35L16 9.175V9C16 7.06667 15.3167 5.41667 13.95 4.05C12.5833 2.68333 10.9333 2 9 2C7.06667 2 5.41667 2.68333 4.05 4.05C2.68333 5.41667 2 7.06667 2 9C2.01667 10.9333 2.70417 12.5833 4.0625 13.95C5.42083 15.3167 7.06667 16 9 16C9.95 16 10.8417 15.8208 11.675 15.4625C12.5083 15.1042 13.2417 14.6167 13.875 14L15.3 15.425C14.4833 16.225 13.5375 16.8542 12.4625 17.3125C11.3875 17.7708 10.2333 18 9 18Z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span className="font-semibold text-base">Turn Over Rate</span>
|
<span className="font-semibold text-base">{t('navigation.turnOverRate')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
<Link className={`flex gap-4 px-8 py-4 ${pathname === '/productivity' ? `bg-white text-[#664228]`:`text-white hover:bg-white hover:text-[#664228]`} items-center`} href="/productivity">
|
<Link className={`flex gap-4 px-8 py-4 ${pathname === '/productivity' ? `bg-white text-[#664228]`:`text-white hover:bg-white hover:text-[#664228]`} items-center`} href="/productivity">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
||||||
<path d="M0 16V8H4V16H0ZM6 16V0H10V16H6ZM12 16V5H16V16H12Z" />
|
<path d="M0 16V8H4V16H0ZM6 16V0H10V16H6ZM12 16V5H16V16H12Z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span className="font-semibold text-base">Produktifitas Karyawan</span>
|
<span className="font-semibold text-base">{t('navigation.produktifitasKaryawan')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
<Link className={`flex gap-4 px-8 py-4 ${pathname === '/hr-cost' ? `bg-white text-[#664228]`:`text-white hover:bg-white hover:text-[#664228]`} items-center`} href="/hrcost">
|
<Link className={`flex gap-4 px-8 py-4 ${pathname === '/hr-cost' ? `bg-white text-[#664228]`:`text-white hover:bg-white hover:text-[#664228]`} items-center`} href="/hrcost">
|
||||||
<svg width="22" height="16" viewBox="0 0 22 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
<svg width="22" height="16" viewBox="0 0 22 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
||||||
<path d="M13 9C12.1667 9 11.4583 8.70833 10.875 8.125C10.2917 7.54167 10 6.83333 10 6C10 5.16667 10.2917 4.45833 10.875 3.875C11.4583 3.29167 12.1667 3 13 3C13.8333 3 14.5417 3.29167 15.125 3.875C15.7083 4.45833 16 5.16667 16 6C16 6.83333 15.7083 7.54167 15.125 8.125C14.5417 8.70833 13.8333 9 13 9ZM6 12C5.45 12 4.97917 11.8042 4.5875 11.4125C4.19583 11.0208 4 10.55 4 10V2C4 1.45 4.19583 0.979167 4.5875 0.5875C4.97917 0.195833 5.45 0 6 0H20C20.55 0 21.0208 0.195833 21.4125 0.5875C21.8042 0.979167 22 1.45 22 2V10C22 10.55 21.8042 11.0208 21.4125 11.4125C21.0208 11.8042 20.55 12 20 12H6ZM8 10H18C18 9.45 18.1958 8.97917 18.5875 8.5875C18.9792 8.19583 19.45 8 20 8V4C19.45 4 18.9792 3.80417 18.5875 3.4125C18.1958 3.02083 18 2.55 18 2H8C8 2.55 7.80417 3.02083 7.4125 3.4125C7.02083 3.80417 6.55 4 6 4V8C6.55 8 7.02083 8.19583 7.4125 8.5875C7.80417 8.97917 8 9.45 8 10ZM19 16H2C1.45 16 0.979167 15.8042 0.5875 15.4125C0.195833 15.0208 0 14.55 0 14V3H2V14H19V16Z"/>
|
<path d="M13 9C12.1667 9 11.4583 8.70833 10.875 8.125C10.2917 7.54167 10 6.83333 10 6C10 5.16667 10.2917 4.45833 10.875 3.875C11.4583 3.29167 12.1667 3 13 3C13.8333 3 14.5417 3.29167 15.125 3.875C15.7083 4.45833 16 5.16667 16 6C16 6.83333 15.7083 7.54167 15.125 8.125C14.5417 8.70833 13.8333 9 13 9ZM6 12C5.45 12 4.97917 11.8042 4.5875 11.4125C4.19583 11.0208 4 10.55 4 10V2C4 1.45 4.19583 0.979167 4.5875 0.5875C4.97917 0.195833 5.45 0 6 0H20C20.55 0 21.0208 0.195833 21.4125 0.5875C21.8042 0.979167 22 1.45 22 2V10C22 10.55 21.8042 11.0208 21.4125 11.4125C21.0208 11.8042 20.55 12 20 12H6ZM8 10H18C18 9.45 18.1958 8.97917 18.5875 8.5875C18.9792 8.19583 19.45 8 20 8V4C19.45 4 18.9792 3.80417 18.5875 3.4125C18.1958 3.02083 18 2.55 18 2H8C8 2.55 7.80417 3.02083 7.4125 3.4125C7.02083 3.80417 6.55 4 6 4V8C6.55 8 7.02083 8.19583 7.4125 8.5875C7.80417 8.97917 8 9.45 8 10ZM19 16H2C1.45 16 0.979167 15.8042 0.5875 15.4125C0.195833 15.0208 0 14.55 0 14V3H2V14H19V16Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span className="font-semibold text-base">HR Cost</span>
|
<span className="font-semibold text-base">{t('navigation.hrCost')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
<Link className="mt-auto flex text-white gap-4 px-8 py-4 hover:bg-white hover:text-[#664228] items-center " href="/logout">
|
<Link className="mt-auto flex text-white gap-4 px-8 py-4 hover:bg-white hover:text-[#664228] items-center " href="/logout">
|
||||||
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
||||||
<path d="M2 18.2554C1.45 18.2554 0.979167 18.0595 0.5875 17.6679C0.195833 17.2762 0 16.8054 0 16.2554V2.25537C0 1.70537 0.195833 1.23454 0.5875 0.842871C0.979167 0.451204 1.45 0.255371 2 0.255371H9V2.25537H2V16.2554H9V18.2554H2ZM13 14.2554L11.625 12.8054L14.175 10.2554H6V8.25537H14.175L11.625 5.70537L13 4.25537L18 9.25537L13 14.2554Z"/>
|
<path d="M2 18.2554C1.45 18.2554 0.979167 18.0595 0.5875 17.6679C0.195833 17.2762 0 16.8054 0 16.2554V2.25537C0 1.70537 0.195833 1.23454 0.5875 0.842871C0.979167 0.451204 1.45 0.255371 2 0.255371H9V2.25537H2V16.2554H9V18.2554H2ZM13 14.2554L11.625 12.8054L14.175 10.2554H6V8.25537H14.175L11.625 5.70537L13 4.25537L18 9.25537L13 14.2554Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span className="font-semibold text-base">Log Out</span>
|
<span className="font-semibold text-base">{t('navigation.logout')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex gap-4 px-8 py-4 mt-4 items-center">
|
<div className="flex gap-4 px-8 py-4 mt-4 items-center">
|
||||||
<span className="text-[12px]">Powered by</span>
|
<span className="text-[12px]">Powered by</span>
|
||||||
|
|
@ -114,10 +117,10 @@ export default function DashboardLayout({children}:{children: React.ReactNode})
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-col h-screen">
|
<div className="w-full flex flex-col h-screen">
|
||||||
<div className="flex bg-white p-4 justify-between">
|
<div className="flex bg-white p-4 justify-between">
|
||||||
<div className="flex gap-1 items-center">
|
<div className="flex items-center gap-4">
|
||||||
<span>ID</span>
|
<div className="ml-auto">
|
||||||
<div className="w-0.5 bg-amber-800 h-4"/>
|
<LanguageSwitcher />
|
||||||
<span>CN</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-4 ml-12">
|
<div className="flex gap-4 ml-12">
|
||||||
<DatePicker label="Start Date" className="w-40"
|
<DatePicker label="Start Date" className="w-40"
|
||||||
|
|
@ -146,7 +149,7 @@ export default function DashboardLayout({children}:{children: React.ReactNode})
|
||||||
className="w-full"
|
className="w-full"
|
||||||
options={filterOptions?.regions ?? []}
|
options={filterOptions?.regions ?? []}
|
||||||
getOptionLabel={(option) => option.name}
|
getOptionLabel={(option) => option.name}
|
||||||
renderInput={(params) => <TextField {...params} label="Region" size="small"/>}
|
renderInput={(params) => <TextField {...params} label={t('filters.region')} size="small"/>}
|
||||||
onChange={(e, value) => {
|
onChange={(e, value) => {
|
||||||
dispatch(setFilter({...filter, organization_code: value?.codes ?? ""}));
|
dispatch(setFilter({...filter, organization_code: value?.codes ?? ""}));
|
||||||
setRegion(value?.codes ?? "");
|
setRegion(value?.codes ?? "");
|
||||||
|
|
@ -156,7 +159,7 @@ export default function DashboardLayout({children}:{children: React.ReactNode})
|
||||||
className="w-full"
|
className="w-full"
|
||||||
options={filterOptions?.organizations ?? []}
|
options={filterOptions?.organizations ?? []}
|
||||||
getOptionLabel={(option) => option.name}
|
getOptionLabel={(option) => option.name}
|
||||||
renderInput={(params) => <TextField {...params} label="Company" size="small"/>}
|
renderInput={(params) => <TextField {...params} label={t('filters.company')} size="small"/>}
|
||||||
onChange={(e, value) => {
|
onChange={(e, value) => {
|
||||||
dispatch(setFilter({...filter, organization_code: value?.code ?? ""}));
|
dispatch(setFilter({...filter, organization_code: value?.code ?? ""}));
|
||||||
if (value?.code == undefined) dispatch(setFilter({...filter, organization_code: region}));
|
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"
|
className="w-full"
|
||||||
options={filterOptions?.estates ?? []}
|
options={filterOptions?.estates ?? []}
|
||||||
getOptionLabel={(option) => option.name}
|
getOptionLabel={(option) => option.name}
|
||||||
renderInput={(params) => <TextField {...params} label="Lokasi" size="small"/>}
|
renderInput={(params) => <TextField {...params} label={t('filters.location')} size="small"/>}
|
||||||
onChange={(e, value) => dispatch(setFilter({...filter, estate_name: value?.name ?? ""}))}
|
onChange={(e, value) => dispatch(setFilter({...filter, estate_name: value?.name ?? ""}))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<TextField label="Posisi" size="small" className="w-40" select value={filter.job_name} onChange={(e) => dispatch(setFilter({...filter, job_name: e.target.value}))}>
|
<TextField label={t('filters.position')} size="small" className="w-40" select value={filter.job_name} onChange={(e) => dispatch(setFilter({...filter, job_name: e.target.value}))}>
|
||||||
<MenuItem value="ALL">Semua</MenuItem>
|
<MenuItem value="ALL">{t('filters.all')}</MenuItem>
|
||||||
<MenuItem value="STAFF">Staff</MenuItem>
|
<MenuItem value="STAFF">{t('filters.staff')}</MenuItem>
|
||||||
<MenuItem value="NON-STAFF">Non Staff</MenuItem>
|
<MenuItem value="NON-STAFF">{t('filters.nonStaff')}</MenuItem>
|
||||||
<MenuItem value="HARVESTERS">Pemanen</MenuItem>
|
<MenuItem value="HARVESTERS">{t('filters.harvesters')}</MenuItem>
|
||||||
<MenuItem value="MAINTENANCE">Perawatan</MenuItem>
|
<MenuItem value="MAINTENANCE">{t('filters.maintenance')}</MenuItem>
|
||||||
</TextField>
|
</TextField>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useAppSelector } from "@/lib/hooks";
|
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 { useGetFilterOptionsQuery, useGetProductivityByRegionQuery, useGetProductivityByAgeQuery, useGetProductivityByTenureQuery, useGetTonnageHarvestGroupEmployeeQuery, useGetTonnageHarvestByEmployeeOriginQuery, useGetTonnageHarvestByEmployeeSalaryQuery, useGetTargetTonnageQuery, useGetOrganizationAttendanceQuery, useGetCostQuery } from "@/services/api";
|
||||||
import { Filter } from "@/services/types";
|
import { Filter } from "@/services/types";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
@ -8,6 +9,7 @@ import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContaine
|
||||||
import { PageLoader } from "@/components/Loader";
|
import { PageLoader } from "@/components/Loader";
|
||||||
|
|
||||||
export default function ProductivityPage() {
|
export default function ProductivityPage() {
|
||||||
|
const { t } = useLocale();
|
||||||
const filter = useAppSelector(state => state.filter.filter);
|
const filter = useAppSelector(state => state.filter.filter);
|
||||||
const { data: filterOptions, isLoading: isLoadingFilterOptions, isFetching: isFetchingFilterOptions } = useGetFilterOptionsQuery(filter);
|
const { data: filterOptions, isLoading: isLoadingFilterOptions, isFetching: isFetchingFilterOptions } = useGetFilterOptionsQuery(filter);
|
||||||
const { data: productivityByRegion, isLoading: isLoadingProductivityByRegion, isFetching: isFetchingProductivityByRegion } = useGetProductivityByRegionQuery(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
|
// Show loader while any data is loading
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <PageLoader text="Memuat data produktivitas..." />;
|
return <PageLoader text={t('productivity.loadingData')} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate total tonnage for percentage calculations (convert kg to tons)
|
// Calculate total tonnage for percentage calculations (convert kg to tons)
|
||||||
|
|
@ -109,7 +111,6 @@ export default function ProductivityPage() {
|
||||||
name: item.salary_range,
|
name: item.salary_range,
|
||||||
value: item.tonnage / 1000, // Convert kg to tons
|
value: item.tonnage / 1000, // Convert kg to tons
|
||||||
count: item.count,
|
count: item.count,
|
||||||
percentage: item.percentage.toFixed(1)
|
|
||||||
})).sort((a, b) => b.value - a.value) || []; // Sort by tonnage descending
|
})).sort((a, b) => b.value - a.value) || []; // Sort by tonnage descending
|
||||||
|
|
||||||
// Calculate current total tonnage from region data (already in tons)
|
// Calculate current total tonnage from region data (already in tons)
|
||||||
|
|
@ -138,14 +139,14 @@ export default function ProductivityPage() {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-12 gap-4">
|
<div className="grid grid-cols-12 gap-4">
|
||||||
<div className="col-span-4 bg-white p-6 rounded-lg shadow">
|
<div className="col-span-4 bg-white p-6 rounded-lg shadow">
|
||||||
<div className="text-xl font-bold">Produktifitas</div>
|
<div className="text-xl font-bold">{t('productivity.produktifitas')}</div>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<div className="text-lg text-gray-600 mb-1">Target Tonase</div>
|
<div className="text-lg text-gray-600 mb-1">{t('productivity.targetTonase')}</div>
|
||||||
<div className="relative w-full bg-blue-300 rounded-lg h-12 overflow-hidden">
|
<div className="relative w-full bg-blue-300 rounded-lg h-12 overflow-hidden">
|
||||||
{/* Target tonnage background (lighter) */}
|
{/* Target tonnage background (lighter) */}
|
||||||
<div className="absolute inset-0 flex items-center justify-center text-white font-medium text-sm">
|
<div className="absolute inset-0 flex items-center justify-center text-white font-medium text-sm">
|
||||||
{targetTonnageValue.toLocaleString('id-ID', { maximumFractionDigits: 0 })} Ton
|
{targetTonnageValue.toLocaleString('id-ID', { maximumFractionDigits: 0 })} {t('common.ton')}
|
||||||
</div>
|
</div>
|
||||||
{/* Current tonnage overlay (darker) */}
|
{/* Current tonnage overlay (darker) */}
|
||||||
<div
|
<div
|
||||||
|
|
@ -153,21 +154,21 @@ export default function ProductivityPage() {
|
||||||
style={{ width: `${Math.min(currentPercentage, 100)}%` }}
|
style={{ width: `${Math.min(currentPercentage, 100)}%` }}
|
||||||
>
|
>
|
||||||
{currentPercentage >= 30 && (
|
{currentPercentage >= 30 && (
|
||||||
<span>{currentTotalTonnage.toLocaleString('id-ID', { maximumFractionDigits: 0 })} Ton</span>
|
<span>{currentTotalTonnage.toLocaleString('id-ID', { maximumFractionDigits: 0 })} {t('common.ton')}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500 mt-1">
|
<div className="text-xs text-gray-500 mt-1">
|
||||||
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')})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<div className="text-lg text-gray-600 mb-1">Rasio Absensi</div>
|
<div className="text-lg text-gray-600 mb-1">{t('productivity.rasioAbsensi')}</div>
|
||||||
<div className="relative w-full bg-teal-300 rounded-lg h-12 overflow-hidden">
|
<div className="relative w-full bg-teal-300 rounded-lg h-12 overflow-hidden">
|
||||||
{/* Total workdays background (lighter) */}
|
{/* Total workdays background (lighter) */}
|
||||||
<div className="absolute inset-0 flex items-center justify-center text-white font-medium text-sm">
|
<div className="absolute inset-0 flex items-center justify-center text-white font-medium text-sm">
|
||||||
{totalWorkdays.toLocaleString('id-ID')} Hari Kerja
|
{totalWorkdays.toLocaleString('id-ID')} {t('productivity.hariKerja')}
|
||||||
</div>
|
</div>
|
||||||
{/* Attendance overlay (darker) */}
|
{/* Attendance overlay (darker) */}
|
||||||
<div
|
<div
|
||||||
|
|
@ -175,24 +176,24 @@ export default function ProductivityPage() {
|
||||||
style={{ width: `${Math.min(attendancePercentage, 100)}%` }}
|
style={{ width: `${Math.min(attendancePercentage, 100)}%` }}
|
||||||
>
|
>
|
||||||
{attendancePercentage >= 30 && (
|
{attendancePercentage >= 30 && (
|
||||||
<span>{totalAttendance.toLocaleString('id-ID')} Masuk</span>
|
<span>{totalAttendance.toLocaleString('id-ID')} {t('productivity.masuk')}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500 mt-1">
|
<div className="text-xs text-gray-500 mt-1">
|
||||||
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')})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<div className="text-lg text-gray-600 mb-1">Cost Per Kilo</div>
|
<div className="text-lg text-gray-600 mb-1">{t('productivity.costPerKilo')}</div>
|
||||||
<div className="text-2xl font-bold text-green-600">Rp {costPerKilo?.toLocaleString('id-ID', { maximumFractionDigits: 0 }) || '0'}</div>
|
<div className="text-2xl font-bold text-green-600">Rp {costPerKilo?.toLocaleString('id-ID', { maximumFractionDigits: 0 }) || '0'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-span-8 bg-white p-6 rounded-lg shadow">
|
<div className="col-span-8 bg-white p-6 rounded-lg shadow">
|
||||||
<div className="text-xl font-bold">Analisa Produktifitas Berdasarkan Wilayah</div>
|
<div className="text-xl font-bold">{t('productivity.analisaProduktifitas')}</div>
|
||||||
<div className="h-96 mt-4">
|
<div className="h-96 mt-4">
|
||||||
{productivityByRegion && productivityByRegion.length > 0 ? (
|
{productivityByRegion && productivityByRegion.length > 0 ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -202,19 +203,19 @@ export default function ProductivityPage() {
|
||||||
dataKey="organization_code"
|
dataKey="organization_code"
|
||||||
tick={{ fontSize: 12 }}
|
tick={{ fontSize: 12 }}
|
||||||
/>
|
/>
|
||||||
<YAxis yAxisId="tonnage" label={{ value: 'Tonnage', angle: -90, position: 'insideLeft' }} />
|
<YAxis yAxisId="tonnage" label={{ value: t('productivity.tonnage'), angle: -90, position: 'insideLeft' }} />
|
||||||
<YAxis yAxisId="count" orientation="right" label={{ value: 'Employee Count', angle: 90, position: 'insideRight' }} />
|
<YAxis yAxisId="count" orientation="right" label={{ value: t('productivity.employeeCount'), angle: 90, position: 'insideRight' }} />
|
||||||
<YAxis yAxisId="ratio" orientation="right" hide />
|
<YAxis yAxisId="ratio" orientation="right" hide />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => {
|
formatter={(value, name) => {
|
||||||
if (name === 'Tonnage') {
|
if (name === t('productivity.tonnage')) {
|
||||||
return [`${Math.round(Number(value)).toLocaleString('id-ID')} ton`, 'Tonnage'];
|
return [`${Math.round(Number(value)).toLocaleString('id-ID')} ${t('common.ton')}`, t('productivity.tonnage')];
|
||||||
}
|
}
|
||||||
if (name === 'Employee Count') {
|
if (name === t('productivity.employeeCount')) {
|
||||||
return [`${Number(value).toLocaleString('id-ID')} employees`, 'Employee Count'];
|
return [`${Number(value).toLocaleString('id-ID')} ${t('common.employees')}`, t('productivity.employeeCount')];
|
||||||
}
|
}
|
||||||
if (name === 'Ratio per Employee') {
|
if (name === t('productivity.ratioPerEmployee')) {
|
||||||
return [`${Number(value).toFixed(2)} ton/employee`, 'Ratio per Employee'];
|
return [`${Number(value).toFixed(2)} ${t('productivity.tonPerEmployee')}`, t('productivity.ratioPerEmployee')];
|
||||||
}
|
}
|
||||||
return [value, name];
|
return [value, name];
|
||||||
}}
|
}}
|
||||||
|
|
@ -224,21 +225,21 @@ export default function ProductivityPage() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Legend />
|
<Legend />
|
||||||
<Bar yAxisId="tonnage" dataKey="tonnage" fill="#3B82F6" name="Tonnage" />
|
<Bar yAxisId="tonnage" dataKey="tonnage" fill="#3B82F6" name={t('productivity.tonnage')} />
|
||||||
<Bar yAxisId="count" dataKey="count" fill="#10B981" name="Employee Count" />
|
<Bar yAxisId="count" dataKey="count" fill="#10B981" name={t('productivity.employeeCount')} />
|
||||||
<Line yAxisId="ratio" type="monotone" dataKey="ratioPerEmployee" stroke="#F97316" strokeWidth={3} dot={{ fill: '#F97316', strokeWidth: 2, r: 4 }} name="Ratio per Employee" />
|
<Line yAxisId="ratio" type="monotone" dataKey="ratioPerEmployee" stroke="#F97316" strokeWidth={3} dot={{ fill: '#F97316', strokeWidth: 2, r: 4 }} name={t('productivity.ratioPerEmployee')} />
|
||||||
</ComposedChart>
|
</ComposedChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex justify-center items-center h-full">
|
<div className="flex justify-center items-center h-full">
|
||||||
<span className="text-gray-600">Data belum tersedia</span>
|
<span className="text-gray-600">{t('common.dataNotAvailable')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-span-4 bg-white p-6 rounded-lg shadow">
|
<div className="col-span-4 bg-white p-6 rounded-lg shadow">
|
||||||
<div className="text-xl font-bold">Output Ton Per Tenaga Kerja</div>
|
<div className="text-xl font-bold">{t('productivity.outputTonPerTenagaKerja')}</div>
|
||||||
{tonnageGroupData && tonnageGroupData.length > 0 ? (
|
{tonnageGroupData && tonnageGroupData.length > 0 ? (
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
<ResponsiveContainer width="100%" height={300}>
|
||||||
<PieChart>
|
<PieChart>
|
||||||
|
|
@ -258,11 +259,11 @@ export default function ProductivityPage() {
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name, props) => [
|
formatter={(value, name, props) => [
|
||||||
`${Number(value).toLocaleString('id-ID')} karyawan`,
|
`${Number(value).toLocaleString('id-ID')} ${t('common.karyawan')}`,
|
||||||
'Jumlah Karyawan'
|
t('productivity.jumlahKaryawan')
|
||||||
]}
|
]}
|
||||||
labelFormatter={(label, payload) => {
|
labelFormatter={(label, payload) => {
|
||||||
return `Range Tonase: ${label}`;
|
return `${t('productivity.rangeTonase')}: ${label}`;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Legend
|
<Legend
|
||||||
|
|
@ -276,13 +277,13 @@ export default function ProductivityPage() {
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center h-64 text-gray-500">
|
<div className="flex items-center justify-center h-64 text-gray-500">
|
||||||
Data belum tersedia
|
{t('common.dataNotAvailable')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-span-4 bg-white p-6 rounded-lg shadow">
|
<div className="col-span-4 bg-white p-6 rounded-lg shadow">
|
||||||
<div className="text-xl font-bold">Output Ton Berdasarkan Gaji</div>
|
<div className="text-xl font-bold">{t('productivity.outputTonBerdasarkanGaji')}</div>
|
||||||
<div className="h-80">
|
<div className="h-80">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<PieChart>
|
<PieChart>
|
||||||
|
|
@ -303,19 +304,19 @@ export default function ProductivityPage() {
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => [
|
formatter={(value, name) => [
|
||||||
`${value.toLocaleString('id-ID')} ton`,
|
`${value.toLocaleString('id-ID')} ${t('common.ton')}`,
|
||||||
'Output Tonnage'
|
t('productivity.outputTonnage')
|
||||||
]}
|
]}
|
||||||
labelFormatter={(label) => `Gaji: ${label}`}
|
labelFormatter={(label) => `${t('productivity.gaji')}: ${label}`}
|
||||||
content={({ active, payload, label }) => {
|
content={({ active, payload, label }) => {
|
||||||
if (active && payload && payload.length) {
|
if (active && payload && payload.length) {
|
||||||
const data = payload[0].payload;
|
const data = payload[0].payload;
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-3 border rounded shadow">
|
<div className="bg-white p-3 border rounded shadow">
|
||||||
<p className="font-semibold">{`Gaji: ${payload[0].name}`}</p>
|
<p className="font-semibold">{`${t('productivity.gaji')}: ${payload[0].name}`}</p>
|
||||||
<p className="text-blue-600">{`Output: ${data.value.toLocaleString('id-ID')} ton`}</p>
|
<p className="text-blue-600">{`${t('productivity.output')}: ${data.value.toLocaleString('id-ID')} ${t('common.ton')}`}</p>
|
||||||
<p className="text-green-600">{`Karyawan: ${data.count.toLocaleString('id-ID')} orang`}</p>
|
<p className="text-green-600">{`${t('common.karyawan')}: ${data.count.toLocaleString('id-ID')} ${t('common.orang')}`}</p>
|
||||||
<p className="text-gray-600">{`Persentase: ${data.percentage}%`}</p>
|
<p className="text-gray-600">{`${t('productivity.persentase')}: ${data.percentage}%`}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -330,7 +331,7 @@ export default function ProductivityPage() {
|
||||||
wrapperStyle={{ paddingLeft: '20px' }}
|
wrapperStyle={{ paddingLeft: '20px' }}
|
||||||
formatter={(value, entry) => (
|
formatter={(value, entry) => (
|
||||||
<span style={{ color: entry.color }}>
|
<span style={{ color: entry.color }}>
|
||||||
{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}%)
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -340,7 +341,7 @@ export default function ProductivityPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-span-4 bg-white p-6 rounded-lg shadow">
|
<div className="col-span-4 bg-white p-6 rounded-lg shadow">
|
||||||
<div className="text-xl font-bold">Output Ton Berdasarkan Domisili</div>
|
<div className="text-xl font-bold">{t('productivity.outputTonBerdasarkanDomisili')}</div>
|
||||||
<div className="h-80">
|
<div className="h-80">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<PieChart>
|
<PieChart>
|
||||||
|
|
@ -360,10 +361,10 @@ export default function ProductivityPage() {
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name, props) => [
|
formatter={(value, name, props) => [
|
||||||
`${props.payload?.count?.toLocaleString('id-ID') ?? 0} karyawan`,
|
`${props.payload?.count?.toLocaleString('id-ID') ?? 0} ${t('common.karyawan')}`,
|
||||||
`${Math.round(Number(props.payload?.value ?? 0)).toLocaleString('id-ID')} ton`
|
`${Math.round(Number(props.payload?.value ?? 0)).toLocaleString('id-ID')} ${t('common.ton')}`
|
||||||
]}
|
]}
|
||||||
labelFormatter={(label) => `Category: ${label}`}
|
labelFormatter={(label) => `${t('productivity.category')}: ${label}`}
|
||||||
/>
|
/>
|
||||||
<Legend
|
<Legend
|
||||||
verticalAlign="middle"
|
verticalAlign="middle"
|
||||||
|
|
@ -373,7 +374,7 @@ export default function ProductivityPage() {
|
||||||
wrapperStyle={{ paddingLeft: '20px' }}
|
wrapperStyle={{ paddingLeft: '20px' }}
|
||||||
formatter={(value, entry) => (
|
formatter={(value, entry) => (
|
||||||
<span style={{ color: entry.color }}>
|
<span style={{ color: entry.color }}>
|
||||||
{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')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -383,66 +384,66 @@ export default function ProductivityPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-span-6 bg-white p-6 rounded-lg shadow">
|
<div className="col-span-6 bg-white p-6 rounded-lg shadow">
|
||||||
<div className="text-xl font-bold">Rasio Usia Karyawan : Tonase</div>
|
<div className="text-xl font-bold">{t('productivity.rasioUsiaKaryawan')}</div>
|
||||||
<div className="h-96">
|
<div className="h-96">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<ComposedChart data={ageBarData}>
|
<ComposedChart data={ageBarData}>
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
<XAxis dataKey="age_range" />
|
<XAxis dataKey="age_range" />
|
||||||
<YAxis yAxisId="tonnage" label={{ value: 'Tonnage', angle: -90, position: 'insideLeft' }} />
|
<YAxis yAxisId="tonnage" label={{ value: t('productivity.tonnage'), angle: -90, position: 'insideLeft' }} />
|
||||||
<YAxis yAxisId="count" orientation="right" label={{ value: 'Employee Count', angle: 90, position: 'insideRight' }} />
|
<YAxis yAxisId="count" orientation="right" label={{ value: t('productivity.employeeCount'), angle: 90, position: 'insideRight' }} />
|
||||||
<YAxis yAxisId="ratio" orientation="right" hide />
|
<YAxis yAxisId="ratio" orientation="right" hide />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => {
|
formatter={(value, name) => {
|
||||||
if (name === 'Tonnage') {
|
if (name === t('productivity.tonnage')) {
|
||||||
return [`${Math.round(Number(value)).toLocaleString('id-ID')} ton`, 'Tonnage'];
|
return [`${Math.round(Number(value)).toLocaleString('id-ID')} ${t('common.ton')}`, t('productivity.tonnage')];
|
||||||
}
|
}
|
||||||
if (name === 'Employee Count') {
|
if (name === t('productivity.employeeCount')) {
|
||||||
return [`${Number(value).toLocaleString('id-ID')} employees`, 'Employee Count'];
|
return [`${Number(value).toLocaleString('id-ID')} ${t('common.employees')}`, t('productivity.employeeCount')];
|
||||||
}
|
}
|
||||||
if (name === 'Ratio per Employee') {
|
if (name === t('productivity.ratioPerEmployee')) {
|
||||||
return [`${Number(value).toFixed(2)} ton/employee`, 'Ratio per Employee'];
|
return [`${Number(value).toFixed(2)} ${t('productivity.tonPerEmployee')}`, t('productivity.ratioPerEmployee')];
|
||||||
}
|
}
|
||||||
return [value, name];
|
return [value, name];
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Legend />
|
<Legend />
|
||||||
<Bar yAxisId="tonnage" dataKey="tonnage" fill="#3B82F6" name="Tonnage" />
|
<Bar yAxisId="tonnage" dataKey="tonnage" fill="#3B82F6" name={t('productivity.tonnage')} />
|
||||||
<Bar yAxisId="count" dataKey="count" fill="#10B981" name="Employee Count" />
|
<Bar yAxisId="count" dataKey="count" fill="#10B981" name={t('productivity.employeeCount')} />
|
||||||
<Line yAxisId="ratio" type="monotone" dataKey="ratioPerEmployee" stroke="#F97316" strokeWidth={3} dot={{ fill: '#F97316', strokeWidth: 2, r: 4 }} name="Ratio per Employee" />
|
<Line yAxisId="ratio" type="monotone" dataKey="ratioPerEmployee" stroke="#F97316" strokeWidth={3} dot={{ fill: '#F97316', strokeWidth: 2, r: 4 }} name={t('productivity.ratioPerEmployee')} />
|
||||||
</ComposedChart>
|
</ComposedChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-span-6 bg-white p-6 rounded-lg shadow">
|
<div className="col-span-6 bg-white p-6 rounded-lg shadow">
|
||||||
<div className="text-xl font-bold">Rasio Masa Kerja Karyawan : Tonase</div>
|
<div className="text-xl font-bold">{t('productivity.rasioMasaKerja')}</div>
|
||||||
<div className="h-96">
|
<div className="h-96">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<ComposedChart data={tenureBarData}>
|
<ComposedChart data={tenureBarData}>
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
<XAxis dataKey="tenure_range" />
|
<XAxis dataKey="tenure_range" />
|
||||||
<YAxis yAxisId="tonnage" label={{ value: 'Tonnage', angle: -90, position: 'insideLeft' }} />
|
<YAxis yAxisId="tonnage" label={{ value: t('productivity.tonnage'), angle: -90, position: 'insideLeft' }} />
|
||||||
<YAxis yAxisId="count" orientation="right" label={{ value: 'Employee Count', angle: 90, position: 'insideRight' }} />
|
<YAxis yAxisId="count" orientation="right" label={{ value: t('productivity.employeeCount'), angle: 90, position: 'insideRight' }} />
|
||||||
<YAxis yAxisId="ratio" orientation="right" hide />
|
<YAxis yAxisId="ratio" orientation="right" hide />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => {
|
formatter={(value, name) => {
|
||||||
if (name === 'Tonnage') {
|
if (name === t('productivity.tonnage')) {
|
||||||
return [`${Math.round(Number(value)).toLocaleString('id-ID')} ton`, 'Tonnage'];
|
return [`${Math.round(Number(value)).toLocaleString('id-ID')} ${t('common.ton')}`, t('productivity.tonnage')];
|
||||||
}
|
}
|
||||||
if (name === 'Employee Count') {
|
if (name === t('productivity.employeeCount')) {
|
||||||
return [`${Number(value).toLocaleString('id-ID')} employees`, 'Employee Count'];
|
return [`${Number(value).toLocaleString('id-ID')} ${t('common.employees')}`, t('productivity.employeeCount')];
|
||||||
}
|
}
|
||||||
if (name === 'Ratio per Employee') {
|
if (name === t('productivity.ratioPerEmployee')) {
|
||||||
return [`${Number(value).toFixed(2)} ton/employee`, 'Ratio per Employee'];
|
return [`${Number(value).toFixed(2)} ${t('productivity.tonPerEmployee')}`, t('productivity.ratioPerEmployee')];
|
||||||
}
|
}
|
||||||
return [value, name];
|
return [value, name];
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Legend />
|
<Legend />
|
||||||
<Bar yAxisId="tonnage" dataKey="tonnage" fill="#3B82F6" name="Tonnage" />
|
<Bar yAxisId="tonnage" dataKey="tonnage" fill="#3B82F6" name={t('productivity.tonnage')} />
|
||||||
<Bar yAxisId="count" dataKey="count" fill="#10B981" name="Employee Count" />
|
<Bar yAxisId="count" dataKey="count" fill="#10B981" name={t('productivity.employeeCount')} />
|
||||||
<Line yAxisId="ratio" type="monotone" dataKey="ratioPerEmployee" stroke="#F97316" strokeWidth={3} dot={{ fill: '#F97316', strokeWidth: 2, r: 4 }} name="Ratio per Employee" />
|
<Line yAxisId="ratio" type="monotone" dataKey="ratioPerEmployee" stroke="#F97316" strokeWidth={3} dot={{ fill: '#F97316', strokeWidth: 2, r: 4 }} name={t('productivity.ratioPerEmployee')} />
|
||||||
</ComposedChart>
|
</ComposedChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client"
|
"use client"
|
||||||
import { useAppSelector } from "@/lib/hooks";
|
import { useAppSelector } from "@/lib/hooks";
|
||||||
import { useDimensions } from "@/lib/hooks/dimension";
|
import { useDimensions } from "@/lib/hooks/dimension";
|
||||||
|
import { useLocale } from "@/lib/hooks/useLocale";
|
||||||
import { useGetEmployeeMovementQuery, useGetMppRecruitmentQuery, useGetResignCategoryQuery, useGetResignReasonQuery, useGetResignSummaryQuery, useGetResignTypeQuery } from "@/services/api";
|
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 { BarChart, Bar, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts";
|
||||||
import { Loader, LucideLoader2 } from "lucide-react";
|
import { Loader, LucideLoader2 } from "lucide-react";
|
||||||
|
|
@ -10,7 +11,7 @@ import { Tooltip as ReactTooltip } from "react-tooltip";
|
||||||
import { PageLoader } from "@/components/Loader";
|
import { PageLoader } from "@/components/Loader";
|
||||||
|
|
||||||
export default function TurnoverPage() {
|
export default function TurnoverPage() {
|
||||||
|
const { t } = useLocale();
|
||||||
const filter = useAppSelector(state => state.filter.filter);
|
const filter = useAppSelector(state => state.filter.filter);
|
||||||
const resignRef = useRef<HTMLDivElement>(null);
|
const resignRef = useRef<HTMLDivElement>(null);
|
||||||
const recruitmentRef = useRef<HTMLDivElement>(null);
|
const recruitmentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
@ -170,13 +171,13 @@ export default function TurnoverPage() {
|
||||||
|
|
||||||
// Show loader while any data is loading
|
// Show loader while any data is loading
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <PageLoader text="Memuat data turnover..." />;
|
return <PageLoader text={t('turnover.loadingData')} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-12 gap-4">
|
<div className="grid grid-cols-12 gap-4">
|
||||||
<div className="col-span-4 bg-white rounded-lg py-4 px-4 max-h-[420px] flex flex-col">
|
<div className="col-span-4 bg-white rounded-lg py-4 px-4 max-h-[420px] flex flex-col">
|
||||||
<div className="text-lg font-bold">Karyawan Baru Seluruh Perusahaan</div>
|
<div className="text-lg font-bold">{t('turnover.karyawanBaruSeluruh')}</div>
|
||||||
{isLoadingEmployeeMovement ? (
|
{isLoadingEmployeeMovement ? (
|
||||||
<div className="flex-1 min-h-[300px] flex justify-center items-center flex-col">
|
<div className="flex-1 min-h-[300px] flex justify-center items-center flex-col">
|
||||||
<LucideLoader2 className="animate-spin" />
|
<LucideLoader2 className="animate-spin" />
|
||||||
|
|
@ -188,15 +189,15 @@ export default function TurnoverPage() {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex mt-6 justify-around gap-3">
|
<div className="flex mt-6 justify-around gap-3">
|
||||||
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
||||||
<div className="text-sm text-[#5A5A5A]">Rasio Recruitment</div>
|
<div className="text-sm text-[#5A5A5A]">{t('turnover.rasioRecruitment')}</div>
|
||||||
<div className="text-xl font-bold">{recruitmentRatio} %</div>
|
<div className="text-xl font-bold">{recruitmentRatio} %</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
||||||
<div className="text-sm text-[#5A5A5A]">Karyawan Baru</div>
|
<div className="text-sm text-[#5A5A5A]">{t('turnover.karyawanBaru')}</div>
|
||||||
<div className="text-xl font-bold">{totalRecruitment}</div>
|
<div className="text-xl font-bold">{totalRecruitment}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
||||||
<div className="text-sm text-[#5A5A5A]">Karyawan Aktif</div>
|
<div className="text-sm text-[#5A5A5A]">{t('turnover.karyawanAktif')}</div>
|
||||||
<div className="text-xl font-bold">{totalActiveEmployees}</div>
|
<div className="text-xl font-bold">{totalActiveEmployees}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -204,7 +205,7 @@ export default function TurnoverPage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-8 bg-white py-4 pl-4 rounded-lg max-h-[420px] flex flex-col">
|
<div className="col-span-8 bg-white py-4 pl-4 rounded-lg max-h-[420px] flex flex-col">
|
||||||
<div className="text-xl font-bold">Man Power Planning per Perusahaan : Recruitment</div>
|
<div className="text-xl font-bold">{t('turnover.manPowerPlanning')}</div>
|
||||||
<div className="flex-1 min-h-[300px] relative">
|
<div className="flex-1 min-h-[300px] relative">
|
||||||
{mppRecruitmentSummary && !isLoadingMppRecruitment ? (
|
{mppRecruitmentSummary && !isLoadingMppRecruitment ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -224,10 +225,10 @@ export default function TurnoverPage() {
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name, props) => {
|
formatter={(value, name, props) => {
|
||||||
const orgData = mppRecruitmentSummary.find(e => e.organization_code === props.payload.organization_code);
|
const orgData = mppRecruitmentSummary.find(e => e.organization_code === props.payload.organization_code);
|
||||||
if (name === "Recruited") {
|
if (name === t('turnover.recruited')) {
|
||||||
return [`${orgData?.recruited || 0} employees`, "Recruited"];
|
return [`${orgData?.recruited || 0} ${t('common.employees')}`, t('turnover.recruited')];
|
||||||
} else {
|
} else {
|
||||||
return [`${orgData?.remaining || 0} employees`, "Remaining"];
|
return [`${orgData?.remaining || 0} ${t('common.employees')}`, t('turnover.remaining')];
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
labelFormatter={(label) => {
|
labelFormatter={(label) => {
|
||||||
|
|
@ -236,41 +237,41 @@ export default function TurnoverPage() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Legend />
|
<Legend />
|
||||||
<Bar dataKey="recruitedPercent" stackId="a" fill="#F7B500" name="Recruited" />
|
<Bar dataKey="recruitedPercent" stackId="a" fill="#F7B500" name={t('turnover.recruited')} />
|
||||||
<Bar dataKey="remainingPercent" stackId="a" fill="#2385DE" name="Remaining" />
|
<Bar dataKey="remainingPercent" stackId="a" fill="#2385DE" name={t('turnover.remaining')} />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : isLoadingMppRecruitment ? (
|
) : isLoadingMppRecruitment ? (
|
||||||
<LucideLoader2 className="absolute top-1/2 left-1/2 animate-spin" />
|
<LucideLoader2 className="absolute top-1/2 left-1/2 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<div className="absolute inset-0 flex justify-center items-center">
|
<div className="absolute inset-0 flex justify-center items-center">
|
||||||
<span className="text-gray-600">Data belum tersedia</span>
|
<span className="text-gray-600">{t('common.dataNotAvailable')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-4 bg-white rounded-lg py-4 px-4 max-h-[420px] flex flex-col">
|
<div className="col-span-4 bg-white rounded-lg py-4 px-4 max-h-[420px] flex flex-col">
|
||||||
<div className="text-lg font-bold">Karyawan Resign Seluruh Perusahaan</div>
|
<div className="text-lg font-bold">{t('turnover.karyawanResignSeluruh')}</div>
|
||||||
<div className="relative h-24 bg-[#C7EBEB] mt-8 rounded-xl" ref={resignRef}>
|
<div className="relative h-24 bg-[#C7EBEB] mt-8 rounded-xl" ref={resignRef}>
|
||||||
<div className={`absolute top-0 left-0 h-24 bg-[#6AC8C8] rounded-xl transition-all duration-1000`} style={{width: `${width * turnOverRatio / 100}px`}}></div>
|
<div className={`absolute top-0 left-0 h-24 bg-[#6AC8C8] rounded-xl transition-all duration-1000`} style={{width: `${width * turnOverRatio / 100}px`}}></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex mt-6 justify-around gap-3">
|
<div className="flex mt-6 justify-around gap-3">
|
||||||
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
||||||
<div className="text-sm text-[#5A5A5A]">Rasio Turn Over</div>
|
<div className="text-sm text-[#5A5A5A]">{t('turnover.rasioTurnOver')}</div>
|
||||||
<div className="text-xl font-bold">{turnOverRatio} %</div>
|
<div className="text-xl font-bold">{turnOverRatio} %</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
||||||
<div className="text-sm text-[#5A5A5A]">Karyawan Resign</div>
|
<div className="text-sm text-[#5A5A5A]">{t('turnover.karyawanResign')}</div>
|
||||||
<div className="text-xl font-bold">{resign}</div>
|
<div className="text-xl font-bold">{resign}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
||||||
<div className="text-sm text-[#5A5A5A]">Karyawan Aktif</div>
|
<div className="text-sm text-[#5A5A5A]">{t('turnover.karyawanAktif')}</div>
|
||||||
<div className="text-xl font-bold">{active}</div>
|
<div className="text-xl font-bold">{active}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-8 bg-white py-4 pl-4 rounded-lg max-h-[420px] flex flex-col">
|
<div className="col-span-8 bg-white py-4 pl-4 rounded-lg max-h-[420px] flex flex-col">
|
||||||
<div className="text-xl font-bold">Resignment per Perusahaan : Jumlah Karyawan</div>
|
<div className="text-xl font-bold">{t('turnover.resignmentPerPerusahaan')}</div>
|
||||||
<div className="flex-1 min-h-[300px] relative">
|
<div className="flex-1 min-h-[300px] relative">
|
||||||
{limitedResignSummary && !isLoadingResignSummary ? (
|
{limitedResignSummary && !isLoadingResignSummary ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -290,10 +291,10 @@ export default function TurnoverPage() {
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name, props) => {
|
formatter={(value, name, props) => {
|
||||||
const orgData = limitedResignSummary.find(e => e.organization_code === props.payload.organization_code);
|
const orgData = limitedResignSummary.find(e => e.organization_code === props.payload.organization_code);
|
||||||
if (name === "Resign") {
|
if (name === t('turnover.resign')) {
|
||||||
return [`${orgData?.count || 0} employees`, "Resign"];
|
return [`${orgData?.count || 0} ${t('common.employees')}`, t('turnover.resign')];
|
||||||
} else {
|
} else {
|
||||||
return [`${orgData?.active || 0} employees`, "Active"];
|
return [`${orgData?.active || 0} ${t('common.employees')}`, t('turnover.active')];
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
labelFormatter={(label) => {
|
labelFormatter={(label) => {
|
||||||
|
|
@ -302,24 +303,24 @@ export default function TurnoverPage() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Legend />
|
<Legend />
|
||||||
<Bar dataKey="resignPercent" stackId="a" fill="#F7B500" name="Resign" />
|
<Bar dataKey="resignPercent" stackId="a" fill="#F7B500" name={t('turnover.resign')} />
|
||||||
<Bar dataKey="activePercent" stackId="a" fill="#2385DE" name="Active" />
|
<Bar dataKey="activePercent" stackId="a" fill="#2385DE" name={t('turnover.active')} />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : isLoadingResignSummary || isFetchingResignSummary ? (
|
) : isLoadingResignSummary || isFetchingResignSummary ? (
|
||||||
<LucideLoader2 className="absolute top-1/2 left-1/2 animate-spin" />
|
<LucideLoader2 className="absolute top-1/2 left-1/2 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<div className="absolute inset-0 flex justify-center items-center">
|
<div className="absolute inset-0 flex justify-center items-center">
|
||||||
<span className="text-gray-600">Data belum tersedia</span>
|
<span className="text-gray-600">{t('common.dataNotAvailable')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[420px] flex flex-col">
|
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[420px] flex flex-col">
|
||||||
<div className="text-xl font-bold">Jenis Pemutusan Hubungan Kerja</div>
|
<div className="text-xl font-bold">{t('turnover.jenisPemutusanHubungan')}</div>
|
||||||
<div className={`flex-1 min-h-[300px] gap-3 flex flex-col mt-8 ${resignType && resignType.length === 0 ? "justify-center items-center" : ""}`}>
|
<div className={`flex-1 min-h-[300px] gap-3 flex flex-col mt-8 ${resignType && resignType.length === 0 ? "justify-center items-center" : ""}`}>
|
||||||
{resignType && resignType.map((resign, index) => (
|
{resignType && resignType.map((resign, index) => (
|
||||||
<div key={index} className="grid grid-cols-12 items-center" data-tooltip-id="tooltip-tor-type" data-tooltip-content={`${resign.count} Employee`}>
|
<div key={index} className="grid grid-cols-12 items-center" data-tooltip-id="tooltip-tor-type" data-tooltip-content={`${resign.count} ${t('common.employee')}`}>
|
||||||
<div className="text-sm col-span-3 text-gray-600">{resign.type}</div>
|
<div className="text-sm col-span-3 text-gray-600">{resign.type}</div>
|
||||||
<div className="col-span-1"/>
|
<div className="col-span-1"/>
|
||||||
<div className="relative col-span-8 h-6 bg-[#2385DE] rounded-lg">
|
<div className="relative col-span-8 h-6 bg-[#2385DE] rounded-lg">
|
||||||
|
|
@ -327,12 +328,12 @@ export default function TurnoverPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{resignType && resignType.length === 0 && <span className="text-black text-sm text-center">Data belum tersedia</span>}
|
{resignType && resignType.length === 0 && <span className="text-black text-sm text-center">{t('common.dataNotAvailable')}</span>}
|
||||||
<ReactTooltip id="tooltip-tor-type" />
|
<ReactTooltip id="tooltip-tor-type" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[420px] flex flex-col">
|
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[420px] flex flex-col">
|
||||||
<div className="text-xl font-bold">Kategory Resign</div>
|
<div className="text-xl font-bold">{t('turnover.kategoriResign')}</div>
|
||||||
<div className="w-4/5 flex-1 flex mx-auto">
|
<div className="w-4/5 flex-1 flex mx-auto">
|
||||||
{resignCategory && resignCategory.length > 0 ? (
|
{resignCategory && resignCategory.length > 0 ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -350,7 +351,7 @@ export default function TurnoverPage() {
|
||||||
<Cell key={`cell-${index}`} fill={getColorByCategory(entry.category)} />
|
<Cell key={`cell-${index}`} fill={getColorByCategory(entry.category)} />
|
||||||
))}
|
))}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip formatter={(value, name) => [`${value} employees`, name]} />
|
<Tooltip formatter={(value, name) => [`${value} ${t('common.employees')}`, name]} />
|
||||||
<Legend
|
<Legend
|
||||||
verticalAlign="bottom"
|
verticalAlign="bottom"
|
||||||
align="center"
|
align="center"
|
||||||
|
|
@ -361,16 +362,16 @@ export default function TurnoverPage() {
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex justify-center items-center h-full">
|
<div className="flex justify-center items-center h-full">
|
||||||
<span className="text-gray-600">Data belum tersedia</span>
|
<span className="text-gray-600">{t('common.dataNotAvailable')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[420px] flex flex-col">
|
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[420px] flex flex-col">
|
||||||
<div className="text-xl font-bold">Alasan Pemutusan Hubungan Kerja</div>
|
<div className="text-xl font-bold">{t('turnover.alasanPemutusanHubungan')}</div>
|
||||||
<div className={`flex-1 min-h-[300px] gap-3 flex flex-col mt-8 ${resignReason && resignReason.length === 0 ? "justify-center items-center" : ""}`}>
|
<div className={`flex-1 min-h-[300px] gap-3 flex flex-col mt-8 ${resignReason && resignReason.length === 0 ? "justify-center items-center" : ""}`}>
|
||||||
{resignReason && resignReason.map((resign, index) => (
|
{resignReason && resignReason.map((resign, index) => (
|
||||||
<div key={index} className="grid grid-cols-12 items-center" data-tooltip-id="tooltip-tor-type" data-tooltip-content={`${resign.count} Employee`}>
|
<div key={index} className="grid grid-cols-12 items-center" data-tooltip-id="tooltip-tor-type" data-tooltip-content={`${resign.count} ${t('common.employee')}`}>
|
||||||
<div className="text-sm col-span-3 text-gray-600">{resign.reason}</div>
|
<div className="text-sm col-span-3 text-gray-600">{resign.reason}</div>
|
||||||
<div className="col-span-1"/>
|
<div className="col-span-1"/>
|
||||||
<div className="relative col-span-8 h-6 bg-[#2385DE] rounded-lg">
|
<div className="relative col-span-8 h-6 bg-[#2385DE] rounded-lg">
|
||||||
|
|
@ -378,7 +379,7 @@ export default function TurnoverPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{resignReason && resignReason.length === 0 && <span className="text-black text-sm text-center">Data belum tersedia</span>}
|
{resignReason && resignReason.length === 0 && <span className="text-black text-sm text-center">{t('common.dataNotAvailable')}</span>}
|
||||||
<ReactTooltip id="tooltip-tor-type" />
|
<ReactTooltip id="tooltip-tor-type" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,22 @@ import { Provider } from "react-redux";
|
||||||
import { StyledEngineProvider } from "@mui/material";
|
import { StyledEngineProvider } from "@mui/material";
|
||||||
import { PersistGate } from "redux-persist/integration/react";
|
import { PersistGate } from "redux-persist/integration/react";
|
||||||
import { GuardedRoute } from "./RouteGuard";
|
import { GuardedRoute } from "./RouteGuard";
|
||||||
|
import { I18nextProvider } from "react-i18next";
|
||||||
|
import i18n from "../lib/i18n";
|
||||||
export default function AppProvider({children}:{children: React.ReactNode}) {
|
export default function AppProvider({children}:{children: React.ReactNode}) {
|
||||||
return (
|
return (
|
||||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
<I18nextProvider i18n={i18n}>
|
||||||
<Provider store={store}>
|
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||||
<PersistGate loading={null} persistor={persistor}>
|
<Provider store={store}>
|
||||||
<StyledEngineProvider injectFirst>
|
<PersistGate loading={null} persistor={persistor}>
|
||||||
<GuardedRoute>
|
<StyledEngineProvider injectFirst>
|
||||||
{children}
|
<GuardedRoute>
|
||||||
</GuardedRoute>
|
{children}
|
||||||
</StyledEngineProvider>
|
</GuardedRoute>
|
||||||
</PersistGate>
|
</StyledEngineProvider>
|
||||||
</Provider>
|
</PersistGate>
|
||||||
</LocalizationProvider>
|
</Provider>
|
||||||
|
</LocalizationProvider>
|
||||||
|
</I18nextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -8,6 +8,8 @@ import { useLoginMutation } from "@/services/api";
|
||||||
import { useAppDispatch } from "@/lib/hooks";
|
import { useAppDispatch } from "@/lib/hooks";
|
||||||
import { setRoles, setToken, setUser } from "@/lib/slice/auth";
|
import { setRoles, setToken, setUser } from "@/lib/slice/auth";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useLocale } from "@/lib/hooks/useLocale";
|
||||||
|
import { LanguageSwitcher } from "@/components/LanguageSwitcher";
|
||||||
|
|
||||||
export default function LoginPage(){
|
export default function LoginPage(){
|
||||||
const [login, {data: loginData, error: loginError}] = useLoginMutation();
|
const [login, {data: loginData, error: loginError}] = useLoginMutation();
|
||||||
|
|
@ -15,6 +17,7 @@ export default function LoginPage(){
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { t } = useLocale();
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -29,18 +32,36 @@ export default function LoginPage(){
|
||||||
return (
|
return (
|
||||||
<div className="relative flex flex-col items-center justify-center min-h-screen bg-black">
|
<div className="relative flex flex-col items-center justify-center min-h-screen bg-black">
|
||||||
<Image src={background} layout="fill" objectFit="cover" className="absolute z-0 opacity-50" alt=""/>
|
<Image src={background} layout="fill" objectFit="cover" className="absolute z-0 opacity-50" alt=""/>
|
||||||
|
|
||||||
|
{/* Language Switcher positioned at top right */}
|
||||||
|
<div className="absolute top-4 right-4 z-20">
|
||||||
|
<LanguageSwitcher />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="relative z-10 flex flex-col items-center justify-center w-full h-full p-4 space-y-4">
|
<div className="relative z-10 flex flex-col items-center justify-center w-full h-full p-4 space-y-4">
|
||||||
<div className="flex flex-col items-center justify-center w-full max-w-sm px-12 pb-6 space-y-8 bg-white rounded-lg">
|
<div className="flex flex-col items-center justify-center w-full max-w-sm px-12 pb-6 space-y-8 bg-white rounded-lg">
|
||||||
<div className="w-full flex flex-col space-y-1">
|
<div className="w-full flex flex-col space-y-1">
|
||||||
<div className="flex items-center justify-center w-full">
|
<div className="flex items-center justify-center w-full">
|
||||||
<Image src={loginImg} width={150} height={150} alt=""/>
|
<Image src={loginImg} width={150} height={150} alt=""/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold text-[#292929] w-full">HRM Dashboard</div>
|
<div className="text-2xl font-bold text-[#292929] w-full">{t('auth.loginTitle')}</div>
|
||||||
<div className="text-sm text-[#9F9F9F] text-start w-full">Masukan Username & Password</div>
|
<div className="text-sm text-[#9F9F9F] text-start w-full">{t('auth.loginSubtitle')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-4 w-full">
|
<div className="flex flex-col space-y-4 w-full">
|
||||||
<input type="text" placeholder="Username" className="w-full px-6 py-4 border border-gray-300 rounded-lg" value={username} onChange={(e) => setUsername(e.target.value)}/>
|
<input
|
||||||
<input type="password" placeholder="Password" className="w-full px-6 py-4 border border-gray-300 rounded-lg" value={password} onChange={(e) => setPassword(e.target.value)}/>
|
type="text"
|
||||||
|
placeholder={t('auth.usernamePlaceholder')}
|
||||||
|
className="w-full px-6 py-4 border border-gray-300 rounded-lg"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
placeholder={t('auth.passwordPlaceholder')}
|
||||||
|
className="w-full px-6 py-4 border border-gray-300 rounded-lg"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-col space-y-1">
|
<div className="w-full flex flex-col space-y-1">
|
||||||
{loginError && <div className="text-xs text-red-500">{(loginError as any).data.message}</div>}
|
{loginError && <div className="text-xs text-red-500">{(loginError as any).data.message}</div>}
|
||||||
|
|
@ -48,9 +69,9 @@ export default function LoginPage(){
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await login({username, password})
|
await login({username, password})
|
||||||
}}
|
}}
|
||||||
>Log In</button>
|
>{t('auth.loginButton')}</button>
|
||||||
<div className="flex gap-4 px-8 py-4 mt-4 items-center justify-center">
|
<div className="flex gap-4 px-8 py-4 mt-4 items-center justify-center">
|
||||||
<div className="text-xs text-[#313678]">Powered by</div>
|
<div className="text-xs text-[#313678]">{t('auth.poweredBy')}</div>
|
||||||
<div>
|
<div>
|
||||||
<Image src={midsuit} alt="midsuit" width={100} height={40}/>
|
<Image src={midsuit} alt="midsuit" width={100} height={40}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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<LanguageSwitcherProps> = ({ className = '' }) => {
|
||||||
|
const { currentLocale, changeLanguage } = useLocale();
|
||||||
|
|
||||||
|
const languages = [
|
||||||
|
{ code: 'id' as SupportedLocale, name: 'Bahasa Indonesia', flag: '🇮🇩' },
|
||||||
|
{ code: 'cn' as SupportedLocale, name: '中文', flag: '🇨🇳' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`relative inline-block ${className}`}>
|
||||||
|
<select
|
||||||
|
value={currentLocale}
|
||||||
|
onChange={(e) => changeLanguage(e.target.value as SupportedLocale)}
|
||||||
|
className="appearance-none bg-white border border-gray-300 rounded-md px-3 py-2 pr-8 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
>
|
||||||
|
{languages.map((lang) => (
|
||||||
|
<option key={lang.code} value={lang.code}>
|
||||||
|
{lang.flag} {lang.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
|
||||||
|
<svg className="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||||
|
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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<SupportedLocale>) => {
|
||||||
|
state.currentLocale = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { setLocale } = localeSlice.actions;
|
||||||
|
export default localeSlice.reducer;
|
||||||
|
|
@ -3,6 +3,7 @@ import { configureStore } from '@reduxjs/toolkit'
|
||||||
import filterReducer from '@/lib/slice/filter'
|
import filterReducer from '@/lib/slice/filter'
|
||||||
import { FLUSH, PAUSE, PERSIST, persistReducer, persistStore, PURGE, REGISTER, REHYDRATE } from "redux-persist";
|
import { FLUSH, PAUSE, PERSIST, persistReducer, persistStore, PURGE, REGISTER, REHYDRATE } from "redux-persist";
|
||||||
import { authSlice } from './slice/auth';
|
import { authSlice } from './slice/auth';
|
||||||
|
import localeReducer from './slice/locale';
|
||||||
import storage from './storage';
|
import storage from './storage';
|
||||||
|
|
||||||
const persistAuth = persistReducer({
|
const persistAuth = persistReducer({
|
||||||
|
|
@ -10,10 +11,16 @@ const persistAuth = persistReducer({
|
||||||
storage: storage,
|
storage: storage,
|
||||||
}, authSlice.reducer)
|
}, authSlice.reducer)
|
||||||
|
|
||||||
|
const persistLocale = persistReducer({
|
||||||
|
key: 'locale',
|
||||||
|
storage: storage,
|
||||||
|
}, localeReducer)
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
[api.reducerPath]: api.reducer,
|
[api.reducerPath]: api.reducer,
|
||||||
auth: persistAuth,
|
auth: persistAuth,
|
||||||
|
locale: persistLocale,
|
||||||
filter: filterReducer,
|
filter: filterReducer,
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
|
|
|
||||||
|
|
@ -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": "访问被拒绝"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
212
yarn.lock
212
yarn.lock
|
|
@ -60,16 +60,9 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.25.6"
|
"@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":
|
"@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.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":
|
|
||||||
version "7.28.4"
|
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==
|
integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==
|
||||||
|
|
||||||
"@babel/template@^7.25.0":
|
"@babel/template@^7.25.0":
|
||||||
|
|
@ -336,14 +329,9 @@
|
||||||
csstype "^3.1.3"
|
csstype "^3.1.3"
|
||||||
prop-types "^15.8.1"
|
prop-types "^15.8.1"
|
||||||
|
|
||||||
"@mui/types@^7.2.15", "@mui/types@^7.2.16":
|
"@mui/types@^7.2.15", "@mui/types@^7.2.16", "@mui/types@^7.4.7":
|
||||||
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":
|
|
||||||
version "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==
|
integrity sha512-8vVje9rdEr1rY8oIkYgP+Su5Kwl6ik7O3jQ0wl78JGSmiZhRHV+vkjooGdKD8pbtZbutXFVTWQYshu2b3sG9zw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.28.4"
|
"@babel/runtime" "^7.28.4"
|
||||||
|
|
@ -372,9 +360,9 @@
|
||||||
prop-types "^15.8.1"
|
prop-types "^15.8.1"
|
||||||
react-is "^18.3.1"
|
react-is "^18.3.1"
|
||||||
|
|
||||||
"@mui/utils@^7.3.2":
|
"@mui/utils@^7.3.3":
|
||||||
version "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==
|
integrity sha512-kwNAUh7bLZ7mRz9JZ+6qfRnnxbE4Zuc+RzXnhSpRSxjTlSTj7b4JxRLXpG+MVtPVtqks5k/XC8No1Vs3x4Z2gg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.28.4"
|
"@babel/runtime" "^7.28.4"
|
||||||
|
|
@ -420,17 +408,17 @@
|
||||||
prop-types "^15.8.1"
|
prop-types "^15.8.1"
|
||||||
|
|
||||||
"@mui/x-data-grid@^8.13.1":
|
"@mui/x-data-grid@^8.13.1":
|
||||||
version "8.13.1"
|
version "8.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/@mui/x-data-grid/-/x-data-grid-8.13.1.tgz#d01d2ca185239e7af14927288206cd3366f8cce2"
|
resolved "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.14.0.tgz"
|
||||||
integrity sha512-64MlyukMoGEDLT3kqdm6tw+rocgMayChj+h+fdAwqD4+2NMQoD5wZElQE+xTNmU0/DPv710X4ENceBRt2hMuGw==
|
integrity sha512-bzUpD83Wx4mawkgquDQUUbLLnpF+JP7Pe7YQx1ixS6W/AlUwXAVagPTOijwchHvlx0Ky11dJvOQAfrnWu6an/Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.28.4"
|
"@babel/runtime" "^7.28.4"
|
||||||
"@mui/utils" "^7.3.2"
|
"@mui/utils" "^7.3.3"
|
||||||
"@mui/x-internals" "8.13.1"
|
"@mui/x-internals" "8.14.0"
|
||||||
"@mui/x-virtualizer" "0.2.2"
|
"@mui/x-virtualizer" "0.2.3"
|
||||||
clsx "^2.1.1"
|
clsx "^2.1.1"
|
||||||
prop-types "^15.8.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":
|
"@mui/x-date-pickers@^7.17.0":
|
||||||
version "7.17.0"
|
version "7.17.0"
|
||||||
|
|
@ -453,70 +441,30 @@
|
||||||
"@babel/runtime" "^7.25.6"
|
"@babel/runtime" "^7.25.6"
|
||||||
"@mui/utils" "^5.16.6"
|
"@mui/utils" "^5.16.6"
|
||||||
|
|
||||||
"@mui/x-internals@8.13.1":
|
"@mui/x-internals@8.14.0":
|
||||||
version "8.13.1"
|
version "8.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/@mui/x-internals/-/x-internals-8.13.1.tgz#ddf8f442121044d75e9141a446fc3988095ffc49"
|
resolved "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.14.0.tgz"
|
||||||
integrity sha512-OKQyCJ9uxtMpjBZCOEQGOR5MhgL1f9HjI4qZHuaLxxtDATK5rcBbVjBF67hI8FzXeF1wrcZP2wsjc4AgGpAo9g==
|
integrity sha512-esYyl61nuuFXiN631TWuPh2tqdoyTdBI/4UXgwH3rytF8jiWvy6prPBPRHEH1nvW3fgw9FoBI48FlOO+yEI8xg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.28.4"
|
"@babel/runtime" "^7.28.4"
|
||||||
"@mui/utils" "^7.3.2"
|
"@mui/utils" "^7.3.3"
|
||||||
reselect "^5.1.1"
|
reselect "^5.1.1"
|
||||||
use-sync-external-store "^1.5.0"
|
use-sync-external-store "^1.6.0"
|
||||||
|
|
||||||
"@mui/x-virtualizer@0.2.2":
|
"@mui/x-virtualizer@0.2.3":
|
||||||
version "0.2.2"
|
version "0.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/@mui/x-virtualizer/-/x-virtualizer-0.2.2.tgz#0d1c0ee0072c5436e7c2571b5e158b91915620d3"
|
resolved "https://registry.npmjs.org/@mui/x-virtualizer/-/x-virtualizer-0.2.3.tgz"
|
||||||
integrity sha512-+ZcGYh/9ykoEofzcAWcJ3n6TBXzCc2ETvytho30wRkYv1ez+8yps0ezns/QvC4JqXBge/3y+e+QatIYjkTltdw==
|
integrity sha512-CZ+VxFmeJaTduAOlSyo5cVek0PV5Y8gm4coyaHEpCvms207J9AoMUKqWIcdwsVGlTH1Y71j35xT/MwHKutZiNw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.28.4"
|
"@babel/runtime" "^7.28.4"
|
||||||
"@mui/utils" "^7.3.2"
|
"@mui/utils" "^7.3.3"
|
||||||
"@mui/x-internals" "8.13.1"
|
"@mui/x-internals" "8.14.0"
|
||||||
|
|
||||||
"@next/env@14.2.11":
|
"@next/env@14.2.11":
|
||||||
version "14.2.11"
|
version "14.2.11"
|
||||||
resolved "https://registry.npmjs.org/@next/env/-/env-14.2.11.tgz"
|
resolved "https://registry.npmjs.org/@next/env/-/env-14.2.11.tgz"
|
||||||
integrity sha512-HYsQRSIXwiNqvzzYThrBwq6RhXo3E0n8j8nQnAs8i4fCEo2Zf/3eS0IiRA8XnRg9Ha0YnpkyJZIZg1qEwemrHw==
|
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":
|
"@next/swc-win32-x64-msvc@14.2.11":
|
||||||
version "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"
|
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"
|
"@nodelib/fs.stat" "2.0.5"
|
||||||
run-parallel "^1.1.9"
|
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"
|
version "2.0.5"
|
||||||
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
|
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
|
||||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||||
|
|
@ -598,7 +546,7 @@
|
||||||
"@react-spring/shared" "~9.7.4"
|
"@react-spring/shared" "~9.7.4"
|
||||||
"@react-spring/types" "~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"
|
version "2.2.7"
|
||||||
resolved "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.7.tgz"
|
resolved "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.7.tgz"
|
||||||
integrity sha512-faI3cZbSdFb8yv9dhDTmGwclW0vk0z5o1cia+kf7gCbaCwHI5e+7tP57mJUv22pNcNbeA62GSrPpfrUfdXcQ6g==
|
integrity sha512-faI3cZbSdFb8yv9dhDTmGwclW0vk0z5o1cia+kf7gCbaCwHI5e+7tP57mJUv22pNcNbeA62GSrPpfrUfdXcQ6g==
|
||||||
|
|
@ -689,14 +637,9 @@
|
||||||
resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz"
|
resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz"
|
||||||
integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==
|
integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==
|
||||||
|
|
||||||
"@types/prop-types@*", "@types/prop-types@^15.7.12":
|
"@types/prop-types@*", "@types/prop-types@^15.7.12", "@types/prop-types@^15.7.15":
|
||||||
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":
|
|
||||||
version "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==
|
integrity sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==
|
||||||
|
|
||||||
"@types/react-dom@^18":
|
"@types/react-dom@^18":
|
||||||
|
|
@ -881,16 +824,16 @@ color-convert@^2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name "~1.1.4"
|
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:
|
color-name@~1.1.4:
|
||||||
version "1.1.4"
|
version "1.1.4"
|
||||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
||||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
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:
|
commander@^4.0.0:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz"
|
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"
|
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz"
|
||||||
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
|
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"
|
version "3.2.4"
|
||||||
resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz"
|
resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz"
|
||||||
integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
|
integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
|
||||||
dependencies:
|
dependencies:
|
||||||
internmap "1 - 2"
|
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"
|
version "3.1.0"
|
||||||
resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz"
|
resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz"
|
||||||
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
|
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"
|
resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz"
|
||||||
integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
|
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"
|
version "3.0.1"
|
||||||
resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz"
|
resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz"
|
||||||
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
|
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
|
||||||
|
|
@ -997,7 +940,7 @@ d3-shape@^3.1.0, d3-shape@^3.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-time "1 - 3"
|
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"
|
version "3.1.0"
|
||||||
resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz"
|
resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz"
|
||||||
integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
|
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"
|
resolved "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz"
|
||||||
integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==
|
integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==
|
||||||
|
|
||||||
delaunator@5, delaunator@^5.0.1:
|
delaunator@^5.0.1, delaunator@5:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz"
|
resolved "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz"
|
||||||
integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==
|
integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==
|
||||||
|
|
@ -1131,11 +1074,6 @@ foreground-child@^3.1.0:
|
||||||
cross-spawn "^7.0.0"
|
cross-spawn "^7.0.0"
|
||||||
signal-exit "^4.0.1"
|
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:
|
function-bind@^1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
|
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:
|
dependencies:
|
||||||
react-is "^16.7.0"
|
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:
|
immer@^10.0.3, immer@^10.1.1:
|
||||||
version "10.1.1"
|
version "10.1.1"
|
||||||
resolved "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz"
|
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"
|
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
|
||||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
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:
|
postcss@^8, postcss@^8.4.23:
|
||||||
version "8.4.47"
|
version "8.4.47"
|
||||||
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz"
|
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"
|
picocolors "^1.1.0"
|
||||||
source-map-js "^1.2.1"
|
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:
|
prop-types@^15.6.2, prop-types@^15.8.1:
|
||||||
version "15.8.1"
|
version "15.8.1"
|
||||||
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
|
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"
|
loose-envify "^1.1.0"
|
||||||
scheduler "^0.23.2"
|
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"
|
version "16.13.1"
|
||||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
|
@ -1564,10 +1529,10 @@ react-is@^18.3.1:
|
||||||
|
|
||||||
react-is@^19.1.1:
|
react-is@^19.1.1:
|
||||||
version "19.2.0"
|
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==
|
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"
|
version "9.1.2"
|
||||||
resolved "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz"
|
resolved "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz"
|
||||||
integrity sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==
|
integrity sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==
|
||||||
|
|
@ -1646,12 +1611,7 @@ redux@^5.0.1:
|
||||||
resolved "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz"
|
resolved "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz"
|
||||||
integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==
|
integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==
|
||||||
|
|
||||||
regenerator-runtime@^0.14.0:
|
reselect@^5.1.0, reselect@^5.1.1, reselect@5.1.1:
|
||||||
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:
|
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz"
|
resolved "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz"
|
||||||
integrity sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==
|
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"
|
resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz"
|
||||||
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
|
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
|
||||||
|
|
||||||
use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.2:
|
use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.2, use-sync-external-store@^1.6.0:
|
||||||
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:
|
|
||||||
version "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==
|
integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==
|
||||||
|
|
||||||
util-deprecate@^1.0.2:
|
util-deprecate@^1.0.2:
|
||||||
|
|
@ -1925,6 +1880,11 @@ victory-vendor@^37.0.2:
|
||||||
d3-time "^3.0.0"
|
d3-time "^3.0.0"
|
||||||
d3-timer "^3.0.1"
|
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:
|
which@^2.0.1:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
|
resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue